You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by pu...@apache.org on 2014/09/19 00:49:51 UTC

[16/27] moving stuff around

http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/CordovaView.xaml.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/CordovaView.xaml.cs b/template/cordovalib/CordovaView.xaml.cs
new file mode 100644
index 0000000..0514aa6
--- /dev/null
+++ b/template/cordovalib/CordovaView.xaml.cs
@@ -0,0 +1,567 @@
+/*
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+*/
+
+using System.Globalization;
+using Microsoft.Phone.Controls;
+using Microsoft.Phone.Shell;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.IO.IsolatedStorage;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using WPCordovaClassLib.Cordova;
+using WPCordovaClassLib.Cordova.JSON;
+using WPCordovaClassLib.CordovaLib;
+
+
+
+namespace WPCordovaClassLib
+{
+    public partial class CordovaView : UserControl
+    {
+
+        /// <summary>
+        /// Indicates whether web control has been loaded and no additional initialization is needed.
+        /// Prevents data clearing during page transitions.
+        /// </summary>
+        private bool IsBrowserInitialized = false;
+
+        /// <summary>
+        /// Set when the user attaches a back button handler inside the WebBrowser
+        /// </summary>
+        private bool OverrideBackButton = false;
+
+        /// <summary>
+        /// Sentinal to keep track of page changes as a result of the hardware back button
+        /// Set to false when the back-button is pressed, which calls js window.history.back()
+        /// If the page changes as a result of the back button the event is cancelled.
+        /// </summary>
+        private bool PageDidChange = false;
+
+        private static string AppRoot = "";
+
+
+        /// <summary>
+        /// Handles native api calls
+        /// </summary>
+        private NativeExecution nativeExecution;
+
+        protected BrowserMouseHelper bmHelper;
+
+        private ConfigHandler configHandler;
+
+        protected bool IsExiting = false;
+
+        private Dictionary<string, IBrowserDecorator> browserDecorators;
+
+        public System.Windows.Controls.Grid _LayoutRoot
+        {
+            get
+            {
+                return ((System.Windows.Controls.Grid)(this.FindName("LayoutRoot")));
+            }
+        }
+
+        public WebBrowser Browser
+        {
+            get
+            {
+                return CordovaBrowser;
+            }
+        }
+
+
+
+        /*
+         * Setting StartPageUri only has an effect if called before the view is loaded.
+         **/
+        protected Uri _startPageUri = null;
+        public Uri StartPageUri
+        {
+            get
+            {
+                if (_startPageUri == null)
+                {
+                    // default
+
+                    return new Uri(AppRoot + "www/index.html", UriKind.Relative);
+                }
+                else
+                {
+                    return _startPageUri;
+                }
+            }
+            set
+            {
+                if (!this.IsBrowserInitialized)
+                {
+                    _startPageUri = value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets whether to suppress bouncy scrolling of
+        /// the WebBrowser control;
+        /// </summary>
+        public bool DisableBouncyScrolling
+        {
+            get;
+            set;
+        }
+
+        public CordovaView()
+        {
+
+            InitializeComponent();
+
+            if (DesignerProperties.IsInDesignTool)
+            {
+                return;
+            }
+
+
+            StartupMode mode = PhoneApplicationService.Current.StartupMode;
+
+            if (mode == StartupMode.Launch)
+            {
+                PhoneApplicationService service = PhoneApplicationService.Current;
+                service.Activated += new EventHandler<Microsoft.Phone.Shell.ActivatedEventArgs>(AppActivated);
+                service.Launching += new EventHandler<LaunchingEventArgs>(AppLaunching);
+                service.Deactivated += new EventHandler<DeactivatedEventArgs>(AppDeactivated);
+                service.Closing += new EventHandler<ClosingEventArgs>(AppClosing);
+            }
+            else
+            {
+
+            }
+
+            // initializes native execution logic
+            configHandler = new ConfigHandler();
+            configHandler.LoadAppPackageConfig();
+
+            if (configHandler.ContentSrc != null)
+            {
+                if (Uri.IsWellFormedUriString(configHandler.ContentSrc, UriKind.Absolute))
+                {
+                    this.StartPageUri = new Uri(configHandler.ContentSrc, UriKind.Absolute);
+                }
+                else
+                {
+                    this.StartPageUri = new Uri(AppRoot + "www/" + configHandler.ContentSrc, UriKind.Relative);
+                }
+            }
+
+            browserDecorators = new Dictionary<string, IBrowserDecorator>();
+
+            nativeExecution = new NativeExecution(ref this.CordovaBrowser);
+            bmHelper = new BrowserMouseHelper(ref this.CordovaBrowser);
+
+            ApplyConfigurationPreferences();
+
+            CreateDecorators();
+        }
+
+        /// <summary>
+        /// Applies configuration preferences. Only BackgroundColor+fullscreen is currently supported.
+        /// </summary>
+        private void ApplyConfigurationPreferences()
+        {
+            string bgColor = configHandler.GetPreference("backgroundcolor");
+
+            if (!String.IsNullOrEmpty(bgColor))
+            {
+                try
+                {
+                    Browser.Background = new SolidColorBrush(ColorFromHex(bgColor));
+                }
+                catch (Exception ex)
+                {
+                    Debug.WriteLine("Unable to parse BackgroundColor value '{0}'. Error: {1}", bgColor, ex.Message);
+                }
+            }
+        }
+
+        /*
+         *   browserDecorators are a collection of plugin-like classes (IBrowserDecorator) that add some bit of functionality to the browser.
+         *   These are somewhat different than plugins in that they are usually not async and patch a browser feature that we would
+         *   already expect to have.  Essentially these are browser polyfills that are patched from the outside in.
+         * */
+        void CreateDecorators()
+        {
+            XHRHelper xhrProxy = new XHRHelper();
+            xhrProxy.Browser = CordovaBrowser;
+            browserDecorators.Add("XHRLOCAL", xhrProxy);
+
+            OrientationHelper orientHelper = new OrientationHelper();
+            orientHelper.Browser = CordovaBrowser;
+            browserDecorators.Add("Orientation", orientHelper);
+
+            ConsoleHelper console = new ConsoleHelper();
+            console.Browser = CordovaBrowser;
+            browserDecorators.Add("ConsoleLog", console);
+
+        }
+
+        void AppClosing(object sender, ClosingEventArgs e)
+        {
+            Debug.WriteLine("AppClosing");
+        }
+
+        void AppDeactivated(object sender, DeactivatedEventArgs e)
+        {
+            Debug.WriteLine("INFO: AppDeactivated because " + e.Reason);
+            try
+            {
+                CordovaBrowser.InvokeScript("eval", new string[] { "cordova.fireDocumentEvent('pause');" });
+            }
+            catch (Exception)
+            {
+                Debug.WriteLine("ERROR: Pause event error");
+            }
+        }
+
+        void AppLaunching(object sender, LaunchingEventArgs e)
+        {
+            Debug.WriteLine("INFO: AppLaunching");
+        }
+
+        void AppActivated(object sender, Microsoft.Phone.Shell.ActivatedEventArgs e)
+        {
+            Debug.WriteLine("INFO: AppActivated");
+            try
+            {
+                CordovaBrowser.InvokeScript("eval", new string[] { "cordova.fireDocumentEvent('resume');" });
+            }
+            catch (Exception)
+            {
+                Debug.WriteLine("ERROR: Resume event error");
+            }
+        }
+
+        void CordovaBrowser_Loaded(object sender, RoutedEventArgs e)
+        {
+
+            this.bmHelper.ScrollDisabled = this.DisableBouncyScrolling;
+
+            if (DesignerProperties.IsInDesignTool)
+            {
+                return;
+            }
+
+            // prevents refreshing web control to initial state during pages transitions
+            if (this.IsBrowserInitialized) return;
+
+
+            try
+            {
+
+                // Before we possibly clean the ISO-Store, we need to grab our generated UUID, so we can rewrite it after.
+                string deviceUUID = "";
+
+                using (IsolatedStorageFile appStorage = IsolatedStorageFile.GetUserStoreForApplication())
+                {
+                    try
+                    {
+                        IsolatedStorageFileStream fileStream = new IsolatedStorageFileStream("DeviceID.txt", FileMode.Open, FileAccess.Read, appStorage);
+
+                        using (StreamReader reader = new StreamReader(fileStream))
+                        {
+                            deviceUUID = reader.ReadLine();
+                        }
+                    }
+                    catch (Exception /*ex*/)
+                    {
+                        deviceUUID = Guid.NewGuid().ToString();
+                        Debug.WriteLine("Updating IsolatedStorage for APP:DeviceID :: " + deviceUUID);
+                        IsolatedStorageFileStream file = new IsolatedStorageFileStream("DeviceID.txt", FileMode.Create, FileAccess.Write, appStorage);
+                        using (StreamWriter writeFile = new StreamWriter(file))
+                        {
+                            writeFile.WriteLine(deviceUUID);
+                            writeFile.Close();
+                        }
+                    }
+                }
+
+                CordovaBrowser.Navigate(StartPageUri);
+                IsBrowserInitialized = true;
+                AttachHardwareButtonHandlers();
+            }
+            catch (Exception ex)
+            {
+                Debug.WriteLine("ERROR: Exception in CordovaBrowser_Loaded :: {0}", ex.Message);
+            }
+        }
+
+        void AttachHardwareButtonHandlers()
+        {
+            PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame;
+            if (frame != null)
+            {
+                PhoneApplicationPage page = frame.Content as PhoneApplicationPage;
+
+                if (page != null)
+                {
+                    page.BackKeyPress += new EventHandler<CancelEventArgs>(page_BackKeyPress);
+                    // CB-2347 -jm
+                    string fullscreen = configHandler.GetPreference("fullscreen");
+                    bool bFullScreen = false;
+                    if (bool.TryParse(fullscreen, out bFullScreen) && bFullScreen)
+                    {
+                        SystemTray.SetIsVisible(page, false);
+                    }
+                }
+            }
+        }
+
+        void page_BackKeyPress(object sender, CancelEventArgs e)
+        {
+
+            if (OverrideBackButton)
+            {
+                try
+                {
+                    CordovaBrowser.InvokeScript("eval", new string[] { "cordova.fireDocumentEvent('backbutton', {}, true);" });
+                    e.Cancel = true;
+                }
+                catch (Exception ex)
+                {
+                    Debug.WriteLine("Exception while invoking backbutton into cordova view: " + ex.Message);
+                }
+            }
+            else
+            {
+                try
+                {
+                    PageDidChange = false;
+
+                    Uri uriBefore = this.Browser.Source;
+                    // calling js history.back with result in a page change if history was valid.
+                    CordovaBrowser.InvokeScript("eval", new string[] { "(function(){window.history.back();})()" });
+
+                    Uri uriAfter = this.Browser.Source;
+
+                    e.Cancel = PageDidChange || (uriBefore != uriAfter);
+                }
+                catch (Exception)
+                {
+                    e.Cancel = false; // exit the app ... ?
+                }
+            }
+        }
+
+        void CordovaBrowser_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
+        {
+            if (IsExiting)
+            {
+                // Special case, we navigate to about:blank when we are about to exit.
+                IsolatedStorageSettings.ApplicationSettings.Save();
+                Application.Current.Terminate();
+                return;
+            }
+
+            Debug.WriteLine("CordovaBrowser_LoadCompleted");
+
+            string version = "?";
+            System.Windows.Resources.StreamResourceInfo streamInfo = Application.GetResourceStream(new Uri("VERSION", UriKind.Relative));
+            if (streamInfo != null)
+            {
+                using(StreamReader sr = new StreamReader(streamInfo.Stream))
+                {
+                    version = sr.ReadLine();
+                }
+            }
+            Debug.WriteLine("Apache Cordova native platform version " + version + " is starting");
+
+            string[] autoloadPlugs = this.configHandler.AutoloadPlugins;
+            foreach (string plugName in autoloadPlugs)
+            {
+                nativeExecution.AutoLoadCommand(plugName);
+            }
+
+            // send js code to fire ready event
+            string nativeReady = "(function(){ cordova.require('cordova/channel').onNativeReady.fire()})();";
+            try
+            {
+                CordovaBrowser.InvokeScript("eval", new string[] { nativeReady });
+            }
+            catch (Exception /*ex*/)
+            {
+                Debug.WriteLine("Error calling js to fire nativeReady event. Did you include cordova.js in your html script tag?");
+            }
+            // attach js code to dispatch exitApp 
+            string appExitHandler = "(function(){navigator.app = navigator.app || {}; navigator.app.exitApp= function(){cordova.exec(null,null,'CoreEvents','__exitApp',[]); }})();";
+            try
+            {
+                CordovaBrowser.InvokeScript("eval", new string[] { appExitHandler });
+            }
+            catch (Exception /*ex*/)
+            {
+                Debug.WriteLine("Error calling js to add appExit funtion.");
+            }
+
+            if (this.CordovaBrowser.Opacity < 1)
+            {
+                FadeIn.Begin();
+            }
+        }
+
+
+        void CordovaBrowser_Navigating(object sender, NavigatingEventArgs e)
+        {
+            if (!configHandler.URLIsAllowed(e.Uri.ToString()))
+            {
+                Debug.WriteLine("Whitelist exception: Stopping browser from navigating to :: " + e.Uri.ToString());
+                e.Cancel = true;
+                return;
+            }
+
+            this.PageDidChange = true;
+            this.nativeExecution.ResetAllCommands();
+        }
+
+        /*
+         *  This method does the work of routing commands
+         *  NotifyEventArgs.Value contains a string passed from JS
+         *  If the command already exists in our map, we will just attempt to call the method(action) specified, and pass the args along
+         *  Otherwise, we create a new instance of the command, add it to the map, and call it ...
+         *  This method may also receive JS error messages caught by window.onerror, in any case where the commandStr does not appear to be a valid command
+         *  it is simply output to the debugger output, and the method returns.
+         *
+         **/
+        void CordovaBrowser_ScriptNotify(object sender, NotifyEventArgs e)
+        {
+            string commandStr = e.Value;
+
+            string commandName = commandStr.Split('/').FirstOrDefault();
+
+            if (browserDecorators.ContainsKey(commandName))
+            {
+                browserDecorators[commandName].HandleCommand(commandStr);
+                return;
+            }
+
+            CordovaCommandCall commandCallParams = CordovaCommandCall.Parse(commandStr);
+
+            if (commandCallParams == null)
+            {
+                // ERROR
+                Debug.WriteLine("ScriptNotify :: " + commandStr);
+            }
+            else if (commandCallParams.Service == "CoreEvents")
+            {
+                switch (commandCallParams.Action.ToLower())
+                {
+                    case "overridebackbutton":
+                        string arg0 = JsonHelper.Deserialize<string[]>(commandCallParams.Args)[0];
+                        this.OverrideBackButton = (arg0 != null && arg0.Length > 0 && arg0.ToLower() == "true");
+                        break;
+                    case "__exitapp":
+                        Debug.WriteLine("Received exitApp command from javascript, app will now exit.");
+                        CordovaBrowser.InvokeScript("eval", new string[] { "cordova.fireDocumentEvent('pause');" });
+                        CordovaBrowser.InvokeScript("eval", new string[] { "setTimeout(function(){ cordova.fireDocumentEvent('exit'); cordova.exec(null,null,'CoreEvents','__finalexit',[]); },0);" });
+                        break;
+                    case "__finalexit":
+                        IsExiting = true;
+                        // hide the browser to prevent white flashes, since about:blank seems to always be white
+                        CordovaBrowser.Opacity = 0d; 
+                        CordovaBrowser.Navigate(new Uri("about:blank", UriKind.Absolute));
+                        break;
+                }
+            }
+            else
+            {
+                if (configHandler.IsPluginAllowed(commandCallParams.Service))
+                {
+                    commandCallParams.Namespace = configHandler.GetNamespaceForCommand(commandCallParams.Service);
+                    nativeExecution.ProcessCommand(commandCallParams);
+                }
+                else
+                {
+                    Debug.WriteLine("Error::Plugin not allowed in config.xml. " + commandCallParams.Service);
+                }
+            }
+        }
+
+        public void LoadPage(string url)
+        {
+            if (this.configHandler.URLIsAllowed(url))
+            {
+                this.CordovaBrowser.Navigate(new Uri(url, UriKind.RelativeOrAbsolute));
+            }
+            else
+            {
+                Debug.WriteLine("Oops, Can't load url based on config.xml :: " + url);
+            }
+        }
+
+        private void CordovaBrowser_Unloaded(object sender, RoutedEventArgs e)
+        {
+            IBrowserDecorator console;
+            if (browserDecorators.TryGetValue("ConsoleLog", out console))
+            {
+                ((ConsoleHelper)console).DetachHandler();
+            }
+
+            PhoneApplicationService service = PhoneApplicationService.Current;
+            service.Activated -= new EventHandler<Microsoft.Phone.Shell.ActivatedEventArgs>(AppActivated);
+            service.Launching -= new EventHandler<LaunchingEventArgs>(AppLaunching);
+            service.Deactivated -= new EventHandler<DeactivatedEventArgs>(AppDeactivated);
+            service.Closing -= new EventHandler<ClosingEventArgs>(AppClosing);
+        }
+
+        private void CordovaBrowser_NavigationFailed(object sender, System.Windows.Navigation.NavigationFailedEventArgs e)
+        {
+            Debug.WriteLine("CordovaBrowser_NavigationFailed :: " + e.Uri.ToString());
+        }
+
+        private void CordovaBrowser_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
+        {
+           foreach(IBrowserDecorator iBD in browserDecorators.Values)
+           {
+               iBD.InjectScript();
+           }
+        }
+
+        /// <summary>
+        /// Converts hex color string to a new System.Windows.Media.Color structure.
+        /// If the hex is only rgb, it will be full opacity.
+        /// </summary>
+        protected Color ColorFromHex(string hexString)
+        {
+            string cleanHex = hexString.Replace("#", "").Replace("0x", "");
+            // turn #FFF into #FFFFFF
+            if (cleanHex.Length == 3)
+            {
+                cleanHex = "" + cleanHex[0] + cleanHex[0] + cleanHex[1] + cleanHex[1] + cleanHex[2] + cleanHex[2];
+            }
+            // add an alpha 100% if it is missing
+            if (cleanHex.Length == 6)
+            {
+                cleanHex = "FF" + cleanHex;
+            }
+            int argb = Int32.Parse(cleanHex, NumberStyles.HexNumber);
+            Color clr = Color.FromArgb((byte)((argb & 0xff000000) >> 0x18),
+                              (byte)((argb & 0xff0000) >> 0x10),
+                              (byte)((argb & 0xff00) >> 8),
+                              (byte)(argb & 0xff));
+            return clr;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/IBrowserDecorator.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/IBrowserDecorator.cs b/template/cordovalib/IBrowserDecorator.cs
new file mode 100644
index 0000000..bc1dbee
--- /dev/null
+++ b/template/cordovalib/IBrowserDecorator.cs
@@ -0,0 +1,30 @@
+/*  
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+    
+    http://www.apache.org/licenses/LICENSE-2.0
+    
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+*/
+
+using Microsoft.Phone.Controls;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WPCordovaClassLib.CordovaLib
+{
+    interface IBrowserDecorator
+    {
+        WebBrowser Browser { get; set; }
+        void InjectScript();
+        bool HandleCommand(string cmd);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/ImageExifHelper.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/ImageExifHelper.cs b/template/cordovalib/ImageExifHelper.cs
new file mode 100644
index 0000000..62b6462
--- /dev/null
+++ b/template/cordovalib/ImageExifHelper.cs
@@ -0,0 +1,209 @@
+/*  
+	Licensed under the Apache License, Version 2.0 (the "License");
+	you may not use this file except in compliance with the License.
+	You may obtain a copy of the License at
+	
+	http://www.apache.org/licenses/LICENSE-2.0
+	
+	Unless required by applicable law or agreed to in writing, software
+	distributed under the License is distributed on an "AS IS" BASIS,
+	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+	See the License for the specific language governing permissions and
+	limitations under the License.
+ 
+*/
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Windows.Media.Imaging;
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+    public class ImageExifOrientation
+    {
+        public const int Portrait = 1;
+        public const int PortraitUpsideDown = 3;
+        public const int LandscapeLeft = 6;
+        public const int LandscapeRight = 8;
+    }
+
+    public class ImageExifHelper
+    {
+
+        public static Stream RotateStream(Stream stream, int angle)
+        {
+            stream.Position = 0;
+            if (angle % 90 != 0 || angle < 0)
+            {
+                throw new ArgumentException();
+            }
+            if (angle % 360 == 0)
+            {
+                return stream;
+            }
+
+            angle = angle % 360;
+
+            BitmapImage bitmap = new BitmapImage();
+            bitmap.SetSource(stream);
+            WriteableBitmap wbSource = new WriteableBitmap(bitmap);
+
+            WriteableBitmap wbTarget = null;
+
+            int srcPixelWidth = wbSource.PixelWidth;
+            int srcPixelHeight = wbSource.PixelHeight;
+
+            if (angle % 180 == 0)
+            {
+                wbTarget = new WriteableBitmap(srcPixelWidth, srcPixelHeight);
+            }
+            else
+            {
+                wbTarget = new WriteableBitmap(srcPixelHeight, srcPixelWidth);
+            }
+
+            int destPixelWidth = wbTarget.PixelWidth;
+            int[] srcPxls = wbSource.Pixels;
+            int[] destPxls = wbTarget.Pixels;
+
+            // this ugly if/else is to avoid a conditional check for every pixel
+            if (angle == 90)
+            {
+                for (int x = 0; x < srcPixelWidth; x++)
+                {
+                    for (int y = 0; y < srcPixelHeight; y++)
+                    {
+                        destPxls[(srcPixelHeight - y - 1) + (x * destPixelWidth)] = srcPxls[x + y * srcPixelWidth];
+                    }
+                }
+            }
+            else if (angle == 180)
+            {
+                for (int x = 0; x < srcPixelWidth; x++)
+                {
+                    for (int y = 0; y < srcPixelHeight; y++)
+                    {
+                        destPxls[(srcPixelWidth - x - 1) + (srcPixelHeight - y - 1) * srcPixelWidth] = srcPxls[x + y * srcPixelWidth];
+                    }
+                }
+            }
+            else if (angle == 270)
+            {
+                for (int x = 0; x < srcPixelWidth; x++)
+                {
+                    for (int y = 0; y < srcPixelHeight; y++)
+                    {
+                        destPxls[y + (srcPixelWidth - x - 1) * destPixelWidth] = srcPxls[x + y * srcPixelWidth];
+                    }
+                }
+            }
+
+            MemoryStream targetStream = new MemoryStream();
+            wbTarget.SaveJpeg(targetStream, destPixelWidth, wbTarget.PixelHeight, 0, 100);
+            return targetStream;
+        }
+
+        public static int getImageOrientationFromStream(Stream imgStream)
+        {
+
+            // 0xFFD8 : jpgHeader
+            // 0xFFE1 :
+            // 0x???? : length of exif data
+            // 0x????, 0x???? : Chars 'E','x','i','f'
+            // 0x0000 : 2 empty bytes
+            // <== mark beginning of tags SIZE:ID:VALUE
+            // 0x???? : 'II' or 'MM' for Intel or Motorola ( always getting II on my WP7 devices ), determines littleEndian-ness
+            // 0x002A : marker value
+            // 0x???? : offset to the Image File Data
+
+            // XXXX possible space before actual tag data ... we skip to mark + offset
+
+            // 0x???? number of exif tags present
+
+            // make sure we are at the beginning
+            imgStream.Seek(0, SeekOrigin.Begin);
+            BinaryReader reader = new BinaryReader(imgStream);
+
+            byte[] jpgHdr = reader.ReadBytes(2); // always (0xFFD8)
+
+            byte start = reader.ReadByte(); // 0xFF
+            byte index = reader.ReadByte(); // 0xE1
+
+            while (start == 0xFF && index != 0xE1) // This never seems to happen, todo: optimize
+            {
+                // Get the data length
+                ushort dLen = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
+                // skip along
+                reader.ReadBytes(dLen - 2);
+                start = reader.ReadByte();
+                index = reader.ReadByte();
+            }
+
+            // It's only success if we found the 0xFFE1 marker
+            if (start != 0xFF || index != 0xE1)
+            {
+                //   throw new Exception("Could not find Exif data block");
+                Debug.WriteLine("Did not find EXIF data");
+                return 0;
+            }
+
+            // read 2 byte length of EXIF data
+            ushort exifLen = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
+            String exif = ""; // build the string
+            for (var n = 0; n < 4; n++)
+            {
+                exif += reader.ReadChar();
+            }
+            if (exif != "Exif")
+            {
+                // did not find exif data ...
+                Debug.WriteLine("Did not find EXIF data");
+                return 0;
+            }
+
+            // read 2 empty bytes
+            //ushort emptyBytes = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
+            reader.ReadBytes(2);
+
+            long headerMark = reader.BaseStream.Position; // where are we now <==
+
+            //bool isLEndian = (reader.ReadChar() + "" + reader.ReadChar()) == "II";
+            reader.ReadBytes(2); // 'II' or 'MM', but we don't care
+
+            if (0x002A != BitConverter.ToUInt16(reader.ReadBytes(2), 0))
+            {
+                Debug.WriteLine("Error in data != 0x002A");
+                return 0;
+            }
+
+            // Get the offset to the IFD (image file directory)
+            ushort imgOffset = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
+
+            imgStream.Position = headerMark + imgOffset;
+            ushort tagCount = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
+            for (ushort x = 0; x < tagCount; x++)
+            {
+                // Orientation = 0x112, aka 274
+                if (0x112 == BitConverter.ToUInt16(reader.ReadBytes(2), 0))
+                {
+                    ushort dType = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
+                    // don't care ..
+                    uint comps = reader.ReadUInt32();
+                    byte[] tagData = reader.ReadBytes(4);
+                    int orientation = (int)tagData[0];
+                    Debug.WriteLine("orientation = " + orientation.ToString());
+                    return orientation;
+                    // 6 means rotate clockwise 90 deg
+                    // 8 means rotate counter-clockwise 90 deg
+                    // 1 means all is good
+                    // 3 means flip vertical
+                }
+                // skip to the next item, 12 bytes each
+                reader.BaseStream.Seek(10, SeekOrigin.Current);
+            }
+            return 0;
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/JSON/JsonHelper.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/JSON/JsonHelper.cs b/template/cordovalib/JSON/JsonHelper.cs
new file mode 100644
index 0000000..44511f6
--- /dev/null
+++ b/template/cordovalib/JSON/JsonHelper.cs
@@ -0,0 +1,97 @@
+/*  
+	Licensed under the Apache License, Version 2.0 (the "License");
+	you may not use this file except in compliance with the License.
+	You may obtain a copy of the License at
+	
+	http://www.apache.org/licenses/LICENSE-2.0
+	
+	Unless required by applicable law or agreed to in writing, software
+	distributed under the License is distributed on an "AS IS" BASIS,
+	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+	See the License for the specific language governing permissions and
+	limitations under the License.
+*/
+
+using System;
+using System.Net;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Ink;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Shapes;
+using System.Runtime.Serialization.Json;
+using System.IO;
+using System.Collections.Generic;
+using System.Text;
+using System.Diagnostics;
+
+namespace WPCordovaClassLib.Cordova.JSON
+{
+    /// <summary>
+    /// Provides JSON serialization/deserialization functionality.
+    /// </summary>
+    public static class JsonHelper
+    {
+        /// <summary>
+        /// Serializes object to JSON string representation
+        /// </summary>
+        /// <param name="obj">object to serialize</param>
+        /// <returns>JSON representation of the object. Returns 'null' string for null passed as argument</returns>
+        public static string Serialize(object obj)
+        {
+            if (obj == null)
+            {
+                return "null";
+            }
+
+            DataContractJsonSerializer ser = new DataContractJsonSerializer(obj.GetType());
+
+            MemoryStream ms = new MemoryStream();
+            ser.WriteObject(ms, obj);
+
+            ms.Position = 0;
+
+            string json = String.Empty;
+
+            using (StreamReader sr = new StreamReader(ms))
+            {
+                json = sr.ReadToEnd();
+            }
+
+            ms.Close();
+
+            return json;
+
+        }
+
+        /// <summary>
+        /// Parses json string to object instance
+        /// </summary>
+        /// <typeparam name="T">type of the object</typeparam>
+        /// <param name="json">json string representation of the object</param>
+        /// <returns>Deserialized object instance</returns>
+        public static T Deserialize<T>(string json)
+        {
+            DataContractJsonSerializer deserializer = new DataContractJsonSerializer(typeof(T));
+            object result = null;
+            try
+            {
+                using (MemoryStream mem = new MemoryStream(Encoding.UTF8.GetBytes(json)))
+                {
+                    result = deserializer.ReadObject(mem);
+                }
+            }
+            catch (Exception ex)
+            {
+                Debug.WriteLine(ex.Message);
+                Debug.WriteLine("Failed to deserialize " + typeof(T) + " with JSON value :: " + json);
+            }
+
+            return (T)result;
+
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/MimeTypeMapper.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/MimeTypeMapper.cs b/template/cordovalib/MimeTypeMapper.cs
new file mode 100644
index 0000000..a2794f5
--- /dev/null
+++ b/template/cordovalib/MimeTypeMapper.cs
@@ -0,0 +1,101 @@
+/*  
+	Licensed under the Apache License, Version 2.0 (the "License");
+	you may not use this file except in compliance with the License.
+	You may obtain a copy of the License at
+	
+	http://www.apache.org/licenses/LICENSE-2.0
+	
+	Unless required by applicable law or agreed to in writing, software
+	distributed under the License is distributed on an "AS IS" BASIS,
+	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+	See the License for the specific language governing permissions and
+	limitations under the License.
+*/
+
+using System.Collections.Generic;
+using System.IO;
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+    /// <summary>
+    /// Represents file extension to mime type mapper.
+    /// </summary>
+    public static class MimeTypeMapper
+    {
+        /// <summary>
+        /// For unknown type it is recommended to use 'application/octet-stream'
+        /// http://stackoverflow.com/questions/1176022/unknown-file-type-mime
+        /// </summary>
+        private static string DefaultMimeType = "application/octet-stream";
+
+        /// <summary>
+        /// Stores mime type for all necessary extension
+        /// </summary>
+        private static readonly Dictionary<string, string> MIMETypesDictionary = new Dictionary<string, string>
+                                                                             {                                                                                
+                                                                                 {"avi", "video/x-msvideo"},
+                                                                                 {"bmp", "image/bmp"},                                                                                                                                                                
+                                                                                 {"gif", "image/gif"},
+                                                                                 {"html","text/html"},                                                                              
+                                                                                 {"jpe", "image/jpeg"},
+                                                                                 {"jpeg", "image/jpeg"},
+                                                                                 {"jpg", "image/jpeg"},     
+                                                                                 {"js","text/javascript"},                                                                         
+                                                                                 {"mov", "video/quicktime"},
+                                                                                 {"mp2", "audio/mpeg"},
+                                                                                 {"mp3", "audio/mpeg"},
+                                                                                 {"mp4", "video/mp4"},
+                                                                                 {"mpe", "video/mpeg"},
+                                                                                 {"mpeg", "video/mpeg"},
+                                                                                 {"mpg", "video/mpeg"},
+                                                                                 {"mpga", "audio/mpeg"},                                                                                
+                                                                                 {"pbm", "image/x-portable-bitmap"},
+                                                                                 {"pcm", "audio/x-pcm"},
+                                                                                 {"pct", "image/pict"},
+                                                                                 {"pgm", "image/x-portable-graymap"},
+                                                                                 {"pic", "image/pict"},
+                                                                                 {"pict", "image/pict"},
+                                                                                 {"png", "image/png"},
+                                                                                 {"pnm", "image/x-portable-anymap"},
+                                                                                 {"pnt", "image/x-macpaint"},
+                                                                                 {"pntg", "image/x-macpaint"},
+                                                                                 {"ppm", "image/x-portable-pixmap"},
+                                                                                 {"qt", "video/quicktime"},
+                                                                                 {"ra", "audio/x-pn-realaudio"},
+                                                                                 {"ram", "audio/x-pn-realaudio"},
+                                                                                 {"ras", "image/x-cmu-raster"},
+                                                                                 {"rgb", "image/x-rgb"},
+                                                                                 {"snd", "audio/basic"},
+                                                                                 {"txt", "text/plain"},
+                                                                                 {"tif", "image/tiff"},
+                                                                                 {"tiff", "image/tiff"},
+                                                                                 {"wav", "audio/x-wav"},
+                                                                                 {"wbmp", "image/vnd.wap.wbmp"},
+
+                                                                             };
+        /// <summary>
+        /// Gets mime type by file extension
+        /// </summary>
+        /// <param name="fileName">file name to extract extension</param>
+        /// <returns>mime type</returns>
+        public static string GetMimeType(string fileName)
+        {
+            string ext = Path.GetExtension(fileName);
+
+            // invalid extension
+            if (string.IsNullOrEmpty(ext) || !ext.StartsWith("."))
+            {
+                return DefaultMimeType;
+            }
+
+            ext = ext.Remove(0, 1);
+
+            if (MIMETypesDictionary.ContainsKey(ext))
+            {
+                return MIMETypesDictionary[ext];
+            }
+
+            return DefaultMimeType;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/NativeExecution.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/NativeExecution.cs b/template/cordovalib/NativeExecution.cs
new file mode 100644
index 0000000..18ca910
--- /dev/null
+++ b/template/cordovalib/NativeExecution.cs
@@ -0,0 +1,259 @@
+/*
+	Licensed under the Apache License, Version 2.0 (the "License");
+	you may not use this file except in compliance with the License.
+	You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+	Unless required by applicable law or agreed to in writing, software
+	distributed under the License is distributed on an "AS IS" BASIS,
+	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+	See the License for the specific language governing permissions and
+	limitations under the License.
+*/
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+using Microsoft.Devices;
+using Microsoft.Phone.Controls;
+using WPCordovaClassLib.Cordova.Commands;
+using System.Collections.Generic;
+using System.Windows;
+
+namespace WPCordovaClassLib.Cordova
+{
+    /// <summary>
+    /// Implements logic to execute native command and return result back.
+    /// All commands are executed asynchronous.
+    /// </summary>
+    public class NativeExecution
+    {
+        /// <summary>
+        /// Reference to web part where application is hosted
+        /// </summary>
+        private readonly WebBrowser webBrowser;
+
+        /// <summary>
+        /// List of commands with attached handlers
+        /// </summary>
+        private List<BaseCommand> commands;
+
+        /// <summary>
+        /// Creates new instance of a NativeExecution class.
+        /// </summary>
+        /// <param name="browser">Reference to web part where application is hosted</param>
+        public NativeExecution(ref WebBrowser browser)
+        {
+            if (browser == null)
+            {
+                throw new ArgumentNullException("browser");
+            }
+
+            this.webBrowser = browser;
+            this.commands = new List<BaseCommand>();
+            webBrowser.Unloaded += webBrowser_Unloaded;
+        }
+
+        /// <summary>
+        /// Detaches event handlers to prevent memory leak on page navigation
+        /// </summary>
+        void webBrowser_Unloaded(object sender, RoutedEventArgs e)
+        {
+            for (int i = commands.Count - 1; i >= 0; i--)
+            {
+                if (commands[i] != null)
+                {
+                    commands[i].DetachHandlers();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Returns where application is running on emulator
+        /// </summary>
+        /// <returns>True if running on emulator, otherwise False</returns>
+        public static bool IsRunningOnEmulator()
+        {
+            return Microsoft.Devices.Environment.DeviceType == DeviceType.Emulator;
+        }
+
+        public void ResetAllCommands()
+        {
+            CommandFactory.ResetAllCommands();
+        }
+
+        public void AutoLoadCommand(string commandService)
+        {
+            BaseCommand bc = CommandFactory.CreateByServiceName(commandService);
+            if (bc != null)
+            {
+                bc.OnInit();
+            }
+
+        }
+
+        /// <summary>
+        /// Executes command and returns result back.
+        /// </summary>
+        /// <param name="commandCallParams">Command to execute</param>
+        public void ProcessCommand(CordovaCommandCall commandCallParams)
+        {
+
+            if (commandCallParams == null)
+            {
+                throw new ArgumentNullException("commandCallParams");
+            }
+
+            try
+            {
+                BaseCommand bc = CommandFactory.CreateByServiceName(commandCallParams.Service, commandCallParams.Namespace);
+
+                if (bc == null)
+                {
+                    this.OnCommandResult(commandCallParams.CallbackId, new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION));
+                    return;
+                }
+
+                EventHandler<PluginResult> OnCommandResultHandler = delegate(object o, PluginResult res)
+                {
+                    if (res.CallbackId == null || res.CallbackId == commandCallParams.CallbackId)
+                    {
+                        this.OnCommandResult(commandCallParams.CallbackId, res);
+                        if (!res.KeepCallback)
+                        {
+                            bc.RemoveResultHandler(commandCallParams.CallbackId);
+                        }
+                    }
+                };
+
+                //bc.OnCommandResult += OnCommandResultHandler;
+                bc.AddResultHandler(commandCallParams.CallbackId, OnCommandResultHandler);
+
+                EventHandler<ScriptCallback> OnCustomScriptHandler = delegate(object o, ScriptCallback script)
+                {
+                    this.InvokeScriptCallback(script);
+                };
+
+                bc.OnCustomScript += OnCustomScriptHandler;
+
+                ThreadStart methodInvokation = () =>
+                {
+                    try
+                    {
+                        bc.InvokeMethodNamed(commandCallParams.CallbackId, commandCallParams.Action, commandCallParams.Args);
+                        commands.Add(bc);
+                    }
+                    catch (Exception ex)
+                    {
+                        Debug.WriteLine("ERROR: Exception in ProcessCommand :: " + ex.Message);
+                        bc.RemoveResultHandler(commandCallParams.CallbackId);
+                        bc.OnCustomScript -= OnCustomScriptHandler;
+
+                        Debug.WriteLine("ERROR: failed to InvokeMethodNamed :: " + commandCallParams.Action + " on Object :: " + commandCallParams.Service);
+                        this.OnCommandResult(commandCallParams.CallbackId, new PluginResult(PluginResult.Status.INVALID_ACTION));
+                        return;
+                    }
+                };
+
+                new Thread(methodInvokation).Start();
+
+            }
+            catch (Exception ex)
+            {
+                // ERROR
+                Debug.WriteLine(String.Format("ERROR: Unable to execute command :: {0}:{1}:{2} ",
+                    commandCallParams.Service, commandCallParams.Action, ex.Message));
+
+                this.OnCommandResult(commandCallParams.CallbackId, new PluginResult(PluginResult.Status.ERROR));
+                return;
+            }
+        }
+
+        /// <summary>
+        /// Handles command execution result.
+        /// </summary>
+        /// <param name="callbackId">Command callback identifier on client side</param>
+        /// <param name="result">Execution result</param>
+        private void OnCommandResult(string callbackId, PluginResult result)
+        {
+            #region  args checking
+
+            if (result == null)
+            {
+                Debug.WriteLine("ERROR: OnCommandResult missing result argument");
+                return;
+            }
+
+            if (String.IsNullOrEmpty(callbackId))
+            {
+                Debug.WriteLine("ERROR: OnCommandResult missing callbackId argument");
+                return;
+            }
+
+            if (!String.IsNullOrEmpty(result.CallbackId) && callbackId != result.CallbackId)
+            {
+                Debug.WriteLine("Multiple Overlapping Results :: " + result.CallbackId + " :: " + callbackId);
+                return;
+            }
+
+            #endregion
+
+            string jsonResult = result.ToJSONString();
+
+            string callback;
+            string args = string.Format("('{0}',{1});", callbackId, jsonResult);
+
+            if (result.Result == PluginResult.Status.NO_RESULT ||
+               result.Result == PluginResult.Status.OK)
+            {
+                callback = @"(function(callbackId,args) {
+                try { args.message = JSON.parse(args.message); } catch (ex) { }
+                cordova.callbackSuccess(callbackId,args);
+                })" + args;
+            }
+            else
+            {
+                callback = @"(function(callbackId,args) {
+                try { args.message = JSON.parse(args.message); } catch (ex) { }
+                cordova.callbackError(callbackId,args);
+                })" + args;
+            }
+            this.InvokeScriptCallback(new ScriptCallback("eval", new string[] { callback }));
+
+        }
+
+        /// <summary>
+        /// Executes client java script
+        /// </summary>
+        /// <param name="script">Script to execute on client side</param>
+        private void InvokeScriptCallback(ScriptCallback script)
+        {
+            if (script == null)
+            {
+                throw new ArgumentNullException("script");
+            }
+
+            if (String.IsNullOrEmpty(script.ScriptName))
+            {
+                throw new ArgumentNullException("ScriptName");
+            }
+
+            //Debug.WriteLine("INFO:: About to invoke ::" + script.ScriptName + " with args ::" + script.Args[0]);
+            this.webBrowser.Dispatcher.BeginInvoke((ThreadStart)delegate()
+            {
+                try
+                {
+                    //Debug.WriteLine("INFO:: InvokingScript::" + script.ScriptName + " with args ::" + script.Args[0]);
+                    this.webBrowser.InvokeScript(script.ScriptName, script.Args);
+                }
+                catch (Exception ex)
+                {
+                    Debug.WriteLine("ERROR: Exception in InvokeScriptCallback :: " + ex.Message);
+                }
+
+            });
+        }
+
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/OrientationHelper.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/OrientationHelper.cs b/template/cordovalib/OrientationHelper.cs
new file mode 100644
index 0000000..299f9dd
--- /dev/null
+++ b/template/cordovalib/OrientationHelper.cs
@@ -0,0 +1,131 @@
+/*  
+	Licensed under the Apache License, Version 2.0 (the "License");
+	you may not use this file except in compliance with the License.
+	You may obtain a copy of the License at
+	
+	http://www.apache.org/licenses/LICENSE-2.0
+	
+	Unless required by applicable law or agreed to in writing, software
+	distributed under the License is distributed on an "AS IS" BASIS,
+	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+	See the License for the specific language governing permissions and
+	limitations under the License.
+*/
+
+using System;
+using System.Net;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Ink;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Shapes;
+using Microsoft.Phone.Controls;
+using WPCordovaClassLib.CordovaLib;
+
+namespace WPCordovaClassLib.Cordova
+{
+    public class OrientationHelper : IBrowserDecorator
+    {
+        public WebBrowser Browser { get; set; }
+
+        public PhoneApplicationPage Page
+        {
+            get
+            {
+                PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame;
+                if (frame != null)
+                {
+                    return frame.Content as PhoneApplicationPage;
+                }
+                return null;
+            }
+        }
+
+        // private PageOrientation CurrentOrientation = PageOrientation.PortraitUp;
+        //private PageOrientation[] SupportedOrientations; // TODO:
+
+        public void InjectScript()
+        {
+            int i = 0;
+
+            switch (Page.Orientation)
+            {
+                case PageOrientation.Portrait: // intentional fall through
+                case PageOrientation.PortraitUp:
+                    i = 0;
+                    break;
+                case PageOrientation.PortraitDown:
+                    i = 180;
+                    break;
+                case PageOrientation.Landscape: // intentional fall through
+                case PageOrientation.LandscapeLeft:
+                    i = -90;
+                    break;
+                case PageOrientation.LandscapeRight:
+                    i = 90;
+                    break;
+            }
+            string jsCallback = String.Format("window.orientation = {0};", i);
+
+            try
+            {
+                Browser.InvokeScript("eval", new string[] { jsCallback });
+            }
+            catch (Exception)
+            {
+            }
+        }
+
+        void page_OrientationChanged(object sender, OrientationChangedEventArgs e)
+        {
+            int i = 0;
+
+            switch (e.Orientation)
+            {
+                case PageOrientation.Portrait: // intentional fall through
+                case PageOrientation.PortraitUp:
+                    i = 0;
+                    break;
+                case PageOrientation.PortraitDown:
+                    i = 180;
+                    break;
+                case PageOrientation.Landscape: // intentional fall through
+                case PageOrientation.LandscapeLeft:
+                    i = -90;
+                    break;
+                case PageOrientation.LandscapeRight:
+                    i = 90;
+                    break;
+            }
+            // Cordova.fireEvent('orientationchange', window);
+            string jsCallback = String.Format("window.orientation = {0};", i);
+
+            try
+            {
+
+                Browser.InvokeScript("eval", new string[] { jsCallback });
+
+                jsCallback = "var evt = document.createEvent('HTMLEvents');";
+                jsCallback += "evt.initEvent( 'orientationchange', true, false );";
+                jsCallback += "window.dispatchEvent(evt);";
+                jsCallback += "if(window.onorientationchange){window.onorientationchange(evt);}";
+
+                Browser.InvokeScript("eval", new string[] {jsCallback});
+            }
+            catch (Exception)
+            {
+            }
+        }
+
+        public bool HandleCommand(string commandStr)
+        {
+            // No commands are currently accepted.
+            return true;
+        }
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/PluginResult.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/PluginResult.cs b/template/cordovalib/PluginResult.cs
new file mode 100644
index 0000000..00017d2
--- /dev/null
+++ b/template/cordovalib/PluginResult.cs
@@ -0,0 +1,139 @@
+/*  
+	Licensed under the Apache License, Version 2.0 (the "License");
+	you may not use this file except in compliance with the License.
+	You may obtain a copy of the License at
+	
+	http://www.apache.org/licenses/LICENSE-2.0
+	
+	Unless required by applicable law or agreed to in writing, software
+	distributed under the License is distributed on an "AS IS" BASIS,
+	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+	See the License for the specific language governing permissions and
+	limitations under the License.
+*/
+
+using System;
+using System.Net;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Ink;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Shapes;
+using System.Text;
+using System.Diagnostics;
+
+namespace WPCordovaClassLib.Cordova
+{
+    /// <summary>
+    /// Represents command execution result
+    /// </summary>
+    public class PluginResult : EventArgs
+    {
+        /// <summary>
+        /// Predefined resultant messages
+        /// </summary>
+        public static string[] StatusMessages = new string[] 
+		{
+			"No result",
+			"OK",
+			"Class not found",
+			"Illegal access",
+			"Instantiation error",
+			"Malformed url",
+			"IO error",
+			"Invalid action",
+			"JSON error",
+			"Error"
+		};
+
+        /// <summary>
+        /// Possible command results status codes
+        /// </summary>
+        public enum Status : int
+        {
+            NO_RESULT = 0,
+            OK,
+            CLASS_NOT_FOUND_EXCEPTION,
+            ILLEGAL_ACCESS_EXCEPTION,
+            INSTANTIATION_EXCEPTION,
+            MALFORMED_URL_EXCEPTION,
+            IO_EXCEPTION,
+            INVALID_ACTION,
+            JSON_EXCEPTION,
+            ERROR
+        };
+
+        public Status Result { get; private set; }
+        public string Message { get; set; }
+        public bool KeepCallback { get; set; }
+        public string CallbackId { get; set; }
+
+        /// <summary>
+        /// Whether command succeded or not
+        /// </summary>
+        public bool IsSuccess
+        {
+            get
+            {
+                return this.Result == Status.OK || this.Result == Status.NO_RESULT;
+            }
+        }
+
+        /// <summary>
+        /// Creates new instance of the PluginResult class.
+        /// </summary>
+        /// <param name="status">Execution result</param>
+        public PluginResult(Status status)
+            : this(status, PluginResult.StatusMessages[(int)status])
+        {
+        }
+
+        /// <summary>
+        /// Creates new instance of the PluginResult class.
+        /// </summary>
+        /// <param name="status">Execution result</param>
+        /// <param name="message">The message</param>
+        public PluginResult(Status status, object message)
+        {
+            this.Result = status;
+            this.Message = JSON.JsonHelper.Serialize(message);
+        }
+
+        public string ToJSONString()
+        {
+            string res = String.Format("\"status\":{0},\"message\":{1},\"keepCallback\":{2}",
+                (int)this.Result,
+                this.Message,
+                this.KeepCallback.ToString().ToLower());
+
+            res = "{" + res + "}";
+            return res;
+
+        }
+
+        [Obsolete]
+        public string ToCallbackString(string callbackId, string successCallback, string errorCallback)
+        {
+            if (this.IsSuccess)
+            {
+                StringBuilder buf = new StringBuilder("");
+                buf.Append(String.Format("{0}('{1}',{2});", successCallback, callbackId, this.ToJSONString()));
+                return buf.ToString();
+            }
+            else
+            {
+                return String.Format("{0}('{1}',{2});", errorCallback, callbackId, this.ToJSONString());
+            }
+        }
+
+        public override String ToString()
+        {
+            return this.ToJSONString();
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/ScriptCallback.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/ScriptCallback.cs b/template/cordovalib/ScriptCallback.cs
new file mode 100644
index 0000000..05bba06
--- /dev/null
+++ b/template/cordovalib/ScriptCallback.cs
@@ -0,0 +1,75 @@
+/*  
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+    
+    http://www.apache.org/licenses/LICENSE-2.0
+    
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+*/
+
+using System;
+using System.Net;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Ink;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Shapes;
+using WPCordovaClassLib.Cordova.JSON;
+using System.Diagnostics;
+
+namespace WPCordovaClassLib.Cordova
+{
+    /// <summary>
+    /// Represents client script function to execute
+    /// </summary>
+    public class ScriptCallback : EventArgs
+    {
+        /// <summary>
+        /// The scripting function to execute.
+        /// </summary>
+        public string ScriptName { get; private set; }
+
+        /// <summary>
+        /// A variable number of strings to pass to the function as parameters.
+        /// </summary>
+        public string[] Args { get; private set; }
+
+        /// <summary>
+        /// Creates new instance of a ScriptCallback class.
+        /// </summary>
+        /// <param name="function">The scripting function to execute</param>
+        /// <param name="args">A variable number of strings to pass to the function as parameters</param>
+        public ScriptCallback(string function, string[] args)
+        {
+            this.ScriptName = function;
+            this.Args = args;
+        }
+
+        /// <summary>
+        /// Creates new instance of a ScriptCallback class.
+        /// </summary>
+        /// <param name="function">The scripting function to execute</param>
+        /// <param name="id">The id argument</param>
+        /// <param name="msg">The message argument</param>
+        /// <param name="value">The value argument</param>
+        public ScriptCallback(string function, string id, object msg, object value)
+        {
+            this.ScriptName = function;
+
+            String arg = String.Format("{{\"id\": {0}, \"msg\": {1}, \"value\": {2}}}",
+                 JsonHelper.Serialize(id), JsonHelper.Serialize(msg), JsonHelper.Serialize(value));
+
+            this.Args = new string[] { arg };
+        }
+
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/XHRHelper.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/XHRHelper.cs b/template/cordovalib/XHRHelper.cs
new file mode 100644
index 0000000..81256fe
--- /dev/null
+++ b/template/cordovalib/XHRHelper.cs
@@ -0,0 +1,341 @@
+/*  
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+    
+    http://www.apache.org/licenses/LICENSE-2.0
+    
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+*/
+
+using Microsoft.Phone.Controls;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.IO.IsolatedStorage;
+using System.Linq;
+using System.Text;
+using System.Windows;
+
+namespace WPCordovaClassLib.CordovaLib
+{
+    public class XHRHelper : IBrowserDecorator
+    {
+
+        public WebBrowser Browser { get; set; }
+        public PhoneApplicationPage Page { get; set; }
+
+        public void InjectScript()
+        {
+            string script = @"(function(win, doc) {
+
+    var __XHRShimAliases = {};
+
+    window.__onXHRLocalCallback = function (responseCode, responseText, reqId) {
+        if (__XHRShimAliases[reqId]){
+            var alias = __XHRShimAliases[reqId];
+            if (alias){
+                delete __XHRShimAliases[reqId];
+                if (responseCode == '200'){
+                    alias.onResult && alias.onResult(responseText);
+                    Object.defineProperty(alias, 'responseXML', {
+                        get: function () {
+                            return new DOMParser().parseFromString(this.responseText, 'text/xml');
+                        }
+                    });
+                } else {
+                    alias.onError && alias.onError(responseText);
+                }
+            }
+        }
+    };
+
+    var docDomain = null;
+    try {
+        docDomain = doc.domain;
+    } catch (err) {}
+
+    if (!docDomain || docDomain.length === 0) {
+
+        var aliasXHR = win.XMLHttpRequest;
+
+        var XHRShim = function() {};
+        win.XMLHttpRequest = XHRShim;
+        XHRShim.noConflict = aliasXHR;
+        XHRShim.UNSENT = 0;
+        XHRShim.OPENED = 1;
+        XHRShim.HEADERS_RECEIVED = 2;
+        XHRShim.LOADING = 3;
+        XHRShim.DONE = 4;
+        XHRShim.prototype = {
+            isAsync: false,
+            onreadystatechange: null,
+            readyState: 0,
+            _url: '',
+            timeout: 0,
+            withCredentials: false,
+            _requestHeaders: null,
+            open: function (reqType, uri, isAsync, user, password) {
+
+                if (uri && uri.indexOf('http') === 0) {
+                    if (!this.wrappedXHR) {
+                        this.wrappedXHR = new aliasXHR();
+                        var self = this;
+                        if (this.timeout > 0) {
+                            this.wrappedXHR.timeout = this.timeout;
+                        }
+                        Object.defineProperty(this, 'timeout', {
+                            set: function(val) {
+                                this.wrappedXHR.timeout = val;
+                            },
+                            get: function() {
+                                return this.wrappedXHR.timeout;
+                            }
+                        });
+                        if (this.withCredentials) {
+                            this.wrappedXHR.withCredentials = this.withCredentials;
+                        }
+                        Object.defineProperty(this, 'withCredentials', {
+                            set: function(val) {
+                                this.wrappedXHR.withCredentials = val;
+                            },
+                            get: function() {
+                                return this.wrappedXHR.withCredentials;
+                            }
+                        });
+                        Object.defineProperty(this, 'status', {
+                            get: function() {
+                                return this.wrappedXHR.status;
+                            }
+                        });
+                        Object.defineProperty(this, 'responseText', {
+                            get: function() {
+                                return this.wrappedXHR.responseText;
+                            }
+                        });
+                        Object.defineProperty(this, 'statusText', {
+                            get: function() {
+                                return this.wrappedXHR.statusText;
+                            }
+                        });
+                        Object.defineProperty(this, 'responseXML', {
+                            get: function() {
+                                return this.wrappedXHR.responseXML;
+                            }
+                        });
+                        Object.defineProperty(this, 'response', {
+                            get: function() {
+                                return this.wrappedXHR.response;
+                            }
+                        });
+                        Object.defineProperty(this, 'responseType', {
+                            set: function(val) {
+                                return this.wrappedXHR.responseType = val;
+                            }
+                        });
+                        this.getResponseHeader = function(header) {
+                            return this.wrappedXHR.getResponseHeader(header);
+                        };
+                        this.getAllResponseHeaders = function() {
+                            return this.wrappedXHR.getAllResponseHeaders();
+                        };
+                        this.wrappedXHR.onreadystatechange = function() {
+                            self.changeReadyState(self.wrappedXHR.readyState);
+                        };
+                    }
+                    return this.wrappedXHR.open(reqType, uri, isAsync, user, password);
+                }
+                else
+                {
+                    this.isAsync = isAsync;
+                    this.reqType = reqType;
+                    this._url = uri;
+                }
+            },
+            statusText: '',
+            changeReadyState: function(newState) {
+                this.readyState = newState;
+                if (this.onreadystatechange) {
+                    // mimic simple 'readystatechange' event which should be passed as per spec
+                    var evt = {type: 'readystatechange', target: this, timeStamp: new Date().getTime()};
+                    this.onreadystatechange(evt);
+                }
+                if (this.readyState == XHRShim.DONE){
+                    this.onload && this.onload();
+                }
+            },
+            addEventListener: function (type, listener, useCapture){
+                if (this.wrappedXHR) {
+                    this.wrappedXHR.addEventListener(type, listener, useCapture);
+                } else {
+                    this['on' + type] = listener;
+                }
+            },
+            removeEventListener: function (type, listener, useCapture){
+                if (this.wrappedXHR) {
+                    this.wrappedXHR.removeEventListener(type, listener, useCapture);
+                } else {
+                    if (this['on' + type] == listener) { // if listener is currently used
+                        delete this['on' + type];
+                    }
+                }
+            },
+            setRequestHeader: function(header, value) {
+                if (this.wrappedXHR) {
+                    this.wrappedXHR.setRequestHeader(header, value);
+                }
+            },
+            getResponseHeader: function(header) {
+                return this.wrappedXHR ? this.wrappedXHR.getResponseHeader(header) : '';
+            },
+            getAllResponseHeaders: function() {
+                return this.wrappedXHR ? this.wrappedXHR.getAllResponseHeaders() : '';
+            },
+            overrideMimeType: function(mimetype) {
+                return this.wrappedXHR ? this.wrappedXHR.overrideMimeType(mimetype) : '';
+            },
+            responseText: '',
+            responseXML: '',
+            onResult: function(res) {
+                this.status = 200;
+                if (typeof res == 'object') {
+                    res = JSON.stringify(res);
+                }
+                this.responseText = res;
+                this.responseXML = res;
+                this.changeReadyState(XHRShim.DONE);
+            },
+            onError: function(err) {
+                this.status = 404;
+                this.changeReadyState(XHRShim.DONE);
+            },
+            abort: function() {
+                if (this.wrappedXHR) {
+                    return this.wrappedXHR.abort();
+                }
+            },
+            send: function(data) {
+                if (this.wrappedXHR) {
+                    return this.wrappedXHR.send(data);
+                }
+                else {
+                    this.changeReadyState(XHRShim.OPENED);
+                    var alias = this;
+
+                    var root = window.location.href.split('#')[0];   // remove hash
+                    
+                    var rootPath = root.substr(0,root.lastIndexOf('/')) + '/';
+                    // Removing unwanted slashes after x-wmapp0 from the basePath, URI cannot process x-wmapp0: /www or //www
+                    var basePath = rootPath.replace(/:\/+/gi, ':');
+
+                    //console.log( 'Stripping protocol if present and removing leading / characters' );
+                    var resolvedUrl =
+                            // remove protocol from the beginning of the url if present
+                            ( this._url.indexOf( window.location.protocol ) === 0 ?
+                                this._url.substring( window.location.protocol.length ) :
+                                this._url )
+                            // get rid of all the starting slashes
+                            .replace(/^[/]*/, '')
+                            .split('#')[0]; // remove hash
+
+                    var wwwFolderPath = navigator.userAgent.indexOf('MSIE 9.0') > -1 ? 'app/www/' : 'www/';
+
+                    // handle special case where url is of form app/www but we are loaded just from /www
+                    if( resolvedUrl.indexOf('app/www') == 0 ) {
+                        resolvedUrl = window.location.protocol  + wwwFolderPath + resolvedUrl.substr(7);
+                    }
+                    else if( resolvedUrl.indexOf('www') == 0) {
+                        resolvedUrl = window.location.protocol  + wwwFolderPath + resolvedUrl.substr(4);
+                    }
+
+                    if(resolvedUrl.indexOf(':') < 0) {
+                        resolvedUrl = basePath + resolvedUrl; // consider it relative
+                    }
+
+                    // Generate unique request ID
+                    var reqId = new Date().getTime().toString() + Math.random();
+
+                    var funk = function () {
+                        __XHRShimAliases[reqId] = alias;
+
+                        alias.changeReadyState(XHRShim.LOADING);
+                        window.external.Notify('XHRLOCAL/' + reqId + '/' + resolvedUrl);
+                    };
+
+                    this.isAsync ? setTimeout(funk, 0) : funk();
+                }
+            },
+            status: 404
+        };
+    }
+})(window, document); ";
+
+
+            Browser.InvokeScript("eval", new string[] { script });
+        }
+
+        public bool HandleCommand(string commandStr)
+        {
+            if (commandStr.IndexOf("XHRLOCAL") == 0)
+            {
+                var reqStr = commandStr.Replace("XHRLOCAL/", "").Split(new char[] {'/'}, 2);
+                string reqId = reqStr[0];
+                string url = reqStr[1];
+
+                Uri uri = new Uri(url, UriKind.RelativeOrAbsolute);
+                try
+                {
+                    using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+                    {
+                        if (isoFile.FileExists(uri.AbsolutePath))
+                        {
+                            using (TextReader reader = new StreamReader(isoFile.OpenFile(uri.AbsolutePath, FileMode.Open, FileAccess.Read)))
+                            {
+                                string text = reader.ReadToEnd();
+                                Browser.InvokeScript("__onXHRLocalCallback", new string[] { "200", text, reqId });
+                                return true;
+                            }
+                        }
+                    }
+                }
+                catch (Exception ex)
+                {
+                    Debug.WriteLine("ERROR: Exception in HandleCommand: " + ex);
+                }
+
+                Uri relUri = new Uri(uri.AbsolutePath, UriKind.Relative);
+
+                var resource = Application.GetResourceStream(relUri);
+                try
+                {
+                    if (resource == null)
+                    {
+                        // 404 ?
+                        Browser.InvokeScript("__onXHRLocalCallback", new string[] { "404", string.Empty, reqId });
+                        return true;
+                    }
+                    else
+                    {
+                        using (StreamReader streamReader = new StreamReader(resource.Stream))
+                        {
+                            string text = streamReader.ReadToEnd();
+                            Browser.InvokeScript("__onXHRLocalCallback", new string[] { "200", text, reqId });
+                            return true;
+                        }
+                    }
+                }
+                catch (Exception ex)
+                {
+                    Debug.WriteLine("ERROR: Exception in HandleCommand: " + ex);
+                }
+            }
+
+            return false;
+        }
+    }
+}