From da6b54e226adfd3a18d8ad98d618c2350ebd8351 Mon Sep 17 00:00:00 2001
From: Jimmy Schementi <jschementi@gmail.com>
Date: Sat, 18 Jul 2009 03:01:50 -0700
Subject: [PATCH] Beginnings of "XAP-less" Silverlight application support.

Adds a "downloadScripts" initParam to indicate whether paths to script files and XAML files should be resolved to the web-server (relative to the XAP), if the file is not found in the XAP. Also adds "downloadScriptsFrom" to customize what path they are based off of.

Files are downloaded synchronously as code runs, by having the PlatformAdaptationLayer use a new BrowserVirtualFilesystem: HttpVirtualFilesystem. So when a language's include mechanism is used (require , load, import, etc), it will download the file by using XmlHttpRequest in synchronous mode. It will also cache each downloaded file's contents, so a file will only be downloaded once, even if it is included multiple times.

Note: this change decreases the need for Chiron significantly. In this mode, Chiron only creates a XAP file with the necessary assemblies and AppManifest.xaml, if it doesn't exist already. One could imagine providing a XAP file for each language, and then Chiron wouldn't be needed for development. However, for using the latest language assemblies, and auto-detection of languages used, it's still useful. So some changes have been made to Chiron to support this better:

A "localApplicationRoot" appSetting has been added to Chiron to adjust where it looks for script files (when determining what languages are used). This allows you to make sure Chiron is looking in the same place your application is downloading scripts or xaml from, so it can still generate a correct XAP file for you. "application/ruby" and "application/python" mime-types have been also added to Chrion also to enable downloading of those script files.

All appSettings are now overridable as command-line switches: /u[rlPrefix], /e[xtUrlPrefix], /l[ocalAppRoot]

See Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox for an example of how this works.

Adds a stub for similar support through IsolatedStorage, but that will come later.

Also tweaks the DynamicEngine.CreateRuntimeSetup method to better support other DLR-hosts in Silverlight.
---
 Merlin/Main/Hosts/SilverLight/Chiron/App.config    |   10 ++
 Merlin/Main/Hosts/SilverLight/Chiron/Chiron.cs     |  123 +++++++++++++++-----
 Merlin/Main/Hosts/SilverLight/Chiron/XapBuilder.cs |    3 +
 .../BrowserScriptHost.cs                           |   11 ++
 .../BrowserVirtualFilesystem.cs                    |   60 ++++++++++
 .../DynamicApplication.cs                          |   24 +++-
 .../DynamicEngine.cs                               |   29 +++--
 .../Microsoft.Scripting.SilverLight/Settings.cs    |   11 ++-
 .../Tests/tests/manual/test_foox/app.py            |    9 ++
 .../Tests/tests/manual/test_foox/app.xaml          |    9 ++
 .../Tests/tests/manual/test_foox/css/screen.css    |   15 +++
 .../Tests/tests/manual/test_foox/index.html        |  121 +++++++++++++++++++
 .../Tests/tests/manual/test_foox/js/error.js       |   32 +++++
 .../Tests/tests/manual/test_foox/run.bat           |    1 +
 14 files changed, 412 insertions(+), 46 deletions(-)
 create mode 100644 Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/app.py
 create mode 100644 Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/app.xaml
 create mode 100644 Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/css/screen.css
 create mode 100644 Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/index.html
 create mode 100644 Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/js/error.js
 create mode 100644 Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/run.bat

diff --git a/Merlin/Main/Hosts/SilverLight/Chiron/App.config b/Merlin/Main/Hosts/SilverLight/Chiron/App.config
index 60e0143..67c3225 100644
--- a/Merlin/Main/Hosts/SilverLight/Chiron/App.config
+++ b/Merlin/Main/Hosts/SilverLight/Chiron/App.config
@@ -8,6 +8,14 @@
 
   <appSettings>
     <!--
+    By default, Chiron assumes all script files are inside the XAP file.
+    However, for development purposes Microsoft.Scripting.Silverlight
+    supports script files outside the XAP file. "localApplicationRoot" tells
+    Chiron the path, relative to the XAP file, to look for script files.
+    <add key="localApplicationRoot" value="app" />
+    -->
+
+    <!--
     "externalUrlPrefix" provides the base URL to load language assemblies
     as Silverlight .slvx files (Transparent Platform Extensions). This is only
     avaliable in Silverlight 3. Uncomment the line below to use a well-known version
@@ -109,6 +117,8 @@
     <mimeMap fileExtension=".jpeg" mimeType="image/jpeg" />
     <mimeMap fileExtension=".png"  mimeType="image/png" />
     <mimeMap fileExtension=".ico"  mimeType="image/x-icon" />
+    <mimeMap fileExtension=".rb"   mimeType="application/ruby" />
+    <mimeMap fileExtension=".py"   mimeType="application/python" />
     <mimeMap fileExtension=".js"   mimeType="application/x-javascript" />
     <mimeMap fileExtension=".xaml" mimeType="application/xaml+xml" />
     <mimeMap fileExtension=".xap"  mimeType="application/x-zip-compressed" />
diff --git a/Merlin/Main/Hosts/SilverLight/Chiron/Chiron.cs b/Merlin/Main/Hosts/SilverLight/Chiron/Chiron.cs
index b127145..508d9ea 100644
--- a/Merlin/Main/Hosts/SilverLight/Chiron/Chiron.cs
+++ b/Merlin/Main/Hosts/SilverLight/Chiron/Chiron.cs
@@ -41,7 +41,7 @@ namespace Chiron {
         // these properties are lazy loaded so we don't parse the configuration file unless necessary
         static AppManifestTemplate _ManifestTemplate;
         static Dictionary<string, LanguageInfo> _Languages;
-        static string _UrlPrefix, _LocalAssemblyPath, _ExternalUrlPrefix;
+        static string _UrlPrefix, _LocalAssemblyPath, _ExternalUrlPrefix, _LocalApplicationRoot;
         static Dictionary<string, string> _MimeMap;
 
         static int Main(string[] args) {
@@ -85,14 +85,31 @@ Options:
     Specifies directory on disk (default: the current directory)
 
   /r[efpath]:<path>
-    Path where assemblies are located. Default is same directory
-    as Chiron.exe. Overrides appSettings.localAssemblyPath in 
-    Chiron.exe.config
+    Path where assemblies are located.
+    Overrides appSettings.localAssemblyPath in Chiron.exe.config
 
-  /path:<path1;path2;..;pathn>
+  /p[ath]:<path1;path2;..;pathn>
     semi-color-separated directories to be included in the XAP file,
     in addition to what is specified by /d
 
+  /l[ocalAppRoot]:<relative path>
+    Path to look for script files on the web-server, rather than in
+    the XAP file (which is default). Path is relative to the XAP file.
+    If Chiron is generating the AppManifest.xaml, it will use this to 
+    find which languages the application depends on.
+
+  /e[xtUrlPrefix]:<absolute uri> (>= Silverlight 3 only)
+    Does not put the assemblies inside the XAP file, and references the
+    appropriate slvx files from the Uri provided.
+    Overrides appSettings.externalUrlPrefix in Chiron.exe.config
+
+  /u[rlprefix]:<relative or absolute uri>
+    appends a relative or absolute Uri to each language assembly added
+    to the AppManifest.xaml. Also does not put the assemblies inside the 
+    xap. If it's a relative Uri and /w is also given, Chiron will serve
+    the assemblies from the Uri, relative to the server root.
+    Overrides appSettings.urlPrefix in Chiron.exe.config
+
   /x[ap[file]]:<file>
     Specifies XAP file to generate. Only XAPs a directory; does not
     generate a manifest or add dynamic language DLLs; see /z for that
@@ -265,7 +282,31 @@ Options:
                     _browser = true;
                     _webserver = true;
                     break;
-                case "path":
+                case "l": case "localAppRoot":
+                    try {
+                        LocalApplicationRoot = val;
+                    } catch {
+                        _error = string.Format("Invalid localAppRoot '{0}'", val);
+                        return;
+                    }
+                    break;
+                case "e": case "extUrlPrefix":
+                    try {
+                        ExternalUrlPrefix = val;
+                    } catch {
+                        _error = string.Format("Invalid externalUrlPrefix '{0}'", val);
+                        return;
+                    }
+                    break;
+                case "u": case "urlPrefix":
+                    try {
+                        UrlPrefix = val;
+                    } catch {
+                        _error = string.Format("Invalid urlPrefix '{0}'", val);
+                        return;
+                    }
+                    break;
+                case "p": case "path":
                     ParseAndSetLocalPath(val);
                     break;
                 case "m": case "manifest":
@@ -358,21 +399,23 @@ Options:
         internal static string UrlPrefix {
             get {
                 if (_UrlPrefix == null) {
-                    _UrlPrefix = ConfigurationManager.AppSettings["urlPrefix"];
-                    if (_UrlPrefix == null) {
-                        _UrlPrefix = "";
-                    } else {
-                        if (!_UrlPrefix.EndsWith("/"))
-                            _UrlPrefix += '/';
-
-                        // validate
-                        Uri uri = new Uri(_UrlPrefix, UriKind.RelativeOrAbsolute);
-                        if (!uri.IsAbsoluteUri && !_UrlPrefix.StartsWith("/"))
-                            throw new ConfigurationErrorsException("urlPrefix must be an absolute URI or start with a /");
-                    }
+                    UrlPrefix = ConfigurationManager.AppSettings["urlPrefix"];
                 }
                 return _UrlPrefix;
             }
+            set {
+                _UrlPrefix = value;
+                if (_UrlPrefix == null) _UrlPrefix = "";
+                else {
+                    if (!_UrlPrefix.EndsWith("/")) _UrlPrefix += '/';
+                    // validate
+                    Uri uri = new Uri(_UrlPrefix, UriKind.RelativeOrAbsolute);
+                    if (!uri.IsAbsoluteUri && !_UrlPrefix.StartsWith("/")) {
+                        _UrlPrefix = null;
+                        throw new ConfigurationErrorsException("urlPrefix must be an absolute URI or start with a /");
+                    }
+                }
+            }
         }
 
         /// <summary>
@@ -380,18 +423,42 @@ Options:
         /// </summary>
         internal static string ExternalUrlPrefix {
             get {
-                if (_ExternalUrlPrefix == null) {
-                    _ExternalUrlPrefix = ConfigurationManager.AppSettings["externalUrlPrefix"];
-                    if (_ExternalUrlPrefix != null) {
-                        if (!_ExternalUrlPrefix.EndsWith("/"))
-                            _ExternalUrlPrefix += '/';
-                        // validate
-                        Uri uri = new Uri(_ExternalUrlPrefix, UriKind.RelativeOrAbsolute);
-                        if (!uri.IsAbsoluteUri)
-                            throw new ConfigurationErrorsException("externalUrlPrefix must be an absolute URI");
+                if (_ExternalUrlPrefix == null)
+                    ExternalUrlPrefix = ConfigurationManager.AppSettings["externalUrlPrefix"];
+                return _ExternalUrlPrefix;
+            }
+            set {
+                _ExternalUrlPrefix = value;
+                if (_ExternalUrlPrefix != null) {
+                    if (!_ExternalUrlPrefix.EndsWith("/")) _ExternalUrlPrefix += '/';
+                    // validate
+                    Uri uri = new Uri(_ExternalUrlPrefix, UriKind.RelativeOrAbsolute);                    if (!uri.IsAbsoluteUri) {
+                        _ExternalUrlPrefix = null;
+                        throw new ConfigurationErrorsException("externalUrlPrefix must be an absolute URI");
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Optional path to look for script files outside the XAP
+        /// </summary>
+        internal static string LocalApplicationRoot {
+            get {
+                if (_LocalApplicationRoot == null)
+                    LocalApplicationRoot = ConfigurationManager.AppSettings["localApplicationRoot"];
+                return _LocalApplicationRoot;
+            }
+            set {
+                _LocalApplicationRoot = value;
+                if (_LocalApplicationRoot != null) {
+                    // validate
+                    Uri uri = new Uri(_LocalApplicationRoot, UriKind.RelativeOrAbsolute);
+                    if (uri.IsAbsoluteUri) {
+                        _LocalApplicationRoot = null;
+                        throw new ConfigurationErrorsException("localApplicationRoot must be a relative URI");
                     }
                 }
-                return _ExternalUrlPrefix;
             }
         }
 
diff --git a/Merlin/Main/Hosts/SilverLight/Chiron/XapBuilder.cs b/Merlin/Main/Hosts/SilverLight/Chiron/XapBuilder.cs
index 9f23cb2..9589bb6 100644
--- a/Merlin/Main/Hosts/SilverLight/Chiron/XapBuilder.cs
+++ b/Merlin/Main/Hosts/SilverLight/Chiron/XapBuilder.cs
@@ -223,6 +223,9 @@ namespace Chiron {
         // Scans the application's directory to find all files whose extension 
         // matches one of Chiron's known languages
         internal static ICollection<LanguageInfo> FindSourceLanguages(string dir) {
+            if (Chiron.LocalApplicationRoot != null)
+                dir = Path.Combine(Path.Combine(dir, ".."), Chiron.LocalApplicationRoot);
+
             Dictionary<LanguageInfo, bool> result = new Dictionary<LanguageInfo, bool>();
 
             foreach (string file in Directory.GetFiles(dir, "*", SearchOption.AllDirectories)) {
diff --git a/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/BrowserScriptHost.cs b/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/BrowserScriptHost.cs
index 37a45d6..cdcab3a 100644
--- a/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/BrowserScriptHost.cs
+++ b/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/BrowserScriptHost.cs
@@ -146,5 +146,16 @@ namespace Microsoft.Scripting.Silverlight {
             VirtualFilesystem = new XapVirtualFilesystem();
         }
     }
+
+    /// <summary>
+    /// PlatformAdaptationLayer to download files over HTTP.
+    /// </summary>
+    internal sealed class HttpPAL : BrowserPAL {
+        internal static new readonly BrowserPAL PAL = new HttpPAL();
+
+        private HttpPAL() {
+            VirtualFilesystem = new HttpVirtualFilesystem();
+        }
+    }
 }
 
diff --git a/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/BrowserVirtualFilesystem.cs b/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/BrowserVirtualFilesystem.cs
index a8614a1..5f23565 100644
--- a/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/BrowserVirtualFilesystem.cs
+++ b/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/BrowserVirtualFilesystem.cs
@@ -132,4 +132,64 @@ namespace Microsoft.Scripting.Silverlight {
         }
         #endregion
     }
+
+    /// <summary>
+    /// Download files synchronously 
+    /// </summary>
+    public class HttpVirtualFilesystem : BrowserVirtualFilesystem {
+        public override string Name() { return "Web server"; }
+
+        private Dictionary<Uri, string> _cache = new Dictionary<Uri, string>();
+
+        protected override Stream GetFileInternal(object baseUri, Uri relativeUri) {
+            baseUri = baseUri ?? DefaultBaseUri();
+            var fullUri = new Uri(NormalizePath(((Uri)baseUri).AbsoluteUri) + relativeUri.ToString(), UriKind.Absolute);
+            string content;
+
+            if (_cache.ContainsKey(fullUri)) {
+                content = _cache[fullUri];
+                if (content == null) return null;
+                return new MemoryStream(System.Text.Encoding.UTF8.GetBytes(content));
+            }
+
+            var request = HtmlPage.Window.CreateInstance("XMLHttpRequest");
+            request.Invoke("open", "GET", fullUri.AbsoluteUri, false);
+            request.Invoke("send", "");
+
+            if (request.GetProperty("status").ToString() != "200") {
+                _cache[fullUri] = null;
+                return null;
+            }
+
+            content = request.GetProperty("responseText").ToString();
+            _cache[fullUri] = content;
+            return new MemoryStream(System.Text.Encoding.UTF8.GetBytes(content));
+        }
+
+        private Uri DefaultBaseUri() {
+            var uri = Application.Current.Host.Source;
+            var server = uri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
+            var path = NormalizePath(Path.GetDirectoryName(uri.LocalPath));
+            var defaultBaseUri = new Uri(new Uri(server), path);
+            if (Settings.DownloadScripts)
+                defaultBaseUri = new Uri(NormalizePath(Path.Combine(defaultBaseUri.AbsoluteUri, Settings.DownloadScriptsFrom)));
+            return defaultBaseUri;
+        }
+
+        internal void ClearCache() {
+            _cache = null;
+            _cache = new Dictionary<Uri, string>();
+        }
+    }
+
+    /// <summary>
+    /// Read and write files from Isolated Storage
+    /// </summary>
+    public class IsolatedStorageVirtualFilesystem : BrowserVirtualFilesystem {
+        public override string Name() { return "Isolated Storage"; }
+
+        protected override Stream GetFileInternal(object baseUri, Uri relativeUri) {
+            throw new NotImplementedException();
+        }
+    }
 }
diff --git a/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/DynamicApplication.cs b/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/DynamicApplication.cs
index 4574adc..2d81012 100644
--- a/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/DynamicApplication.cs
+++ b/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/DynamicApplication.cs
@@ -24,7 +24,9 @@ using System.Xml;
 using Microsoft.Scripting.Hosting;
 using Microsoft.Scripting.Runtime;
 using Microsoft.Scripting.Utils;
-using System.Net;
+using System.Net;
+using System.Text.RegularExpressions;
+using System.Windows.Markup;
 
 namespace Microsoft.Scripting.Silverlight {
 
@@ -90,7 +92,7 @@ namespace Microsoft.Scripting.Silverlight {
 
         [Obsolete("Use DynamicApplication.Current.Engine.CreateRuntimeSetup() instead")]
         public static ScriptRuntimeSetup CreateRuntimeSetup() {
-            return DynamicApplication.Current.Engine.CreateRuntimeSetup();
+            return DynamicEngine.CreateRuntimeSetup();
         }
         #endregion
 
@@ -136,8 +138,22 @@ namespace Microsoft.Scripting.Silverlight {
         /// <param name="component">The object to load the XAML into</param>
         /// <param name="uri">relative Uri of the XAML file</param>
         public new object LoadComponent(object component, Uri relativeUri) {
-            Application.LoadComponent(component, relativeUri);
-            return component;
+            if (Application.GetResourceStream(relativeUri) == null && Settings.DownloadScripts) {
+                var xamlStream = HttpPAL.PAL.VirtualFilesystem.GetFile(relativeUri);
+                if (xamlStream != null) {
+                    string xaml;
+                    using (StreamReader sr = new StreamReader(xamlStream)) {
+                        xaml = sr.ReadToEnd();
+                    }
+                    component = XamlReader.Load(Regex.Replace(xaml, "x:Class=\".*?\"", ""));
+                    return component;
+                }
+            } else {
+                Application.LoadComponent(component, relativeUri);
+                return component;
+            }
+            return null;
+
         }
 
         /// <summary>
diff --git a/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/DynamicEngine.cs b/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/DynamicEngine.cs
index 063764e..946aa77 100644
--- a/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/DynamicEngine.cs
+++ b/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/DynamicEngine.cs
@@ -34,15 +34,23 @@ namespace Microsoft.Scripting.Silverlight {
         public void Start() {
             InitializeRuntime(Settings.Debug);
             Run(Settings.GetEntryPoint(), Settings.ConsoleEnabled);
+        }
+
+        public static ScriptRuntimeSetup CreateRuntimeSetup(bool debugMode) {
+            ScriptRuntimeSetup setup = TryParseFile();
+            if (setup == null) {
+                setup = LoadFromAssemblies(DynamicApplication.Current != null ?
+                    DynamicApplication.Current.AppManifest.Assemblies :
+                    new DynamicAppManifest().Assemblies);
+            }
+            setup.HostType = typeof(BrowserScriptHost);
+            setup.Options["SearchPaths"] = new string[] { String.Empty };
+            setup.DebugMode = debugMode;
+            return setup;
         }
 
-        public ScriptRuntimeSetup CreateRuntimeSetup() {
-            ScriptRuntimeSetup setup = TryParseFile();
-            if (setup == null) {
-                setup = LoadFromAssemblies(DynamicApplication.Current.AppManifest.Assemblies);
-            }
-            setup.HostType = typeof(BrowserScriptHost);
-            return setup;
+        public static ScriptRuntimeSetup CreateRuntimeSetup() {
+            return CreateRuntimeSetup(false);
         }
 
         private void LoadDefaultAssemblies() {
@@ -56,10 +64,7 @@ namespace Microsoft.Scripting.Silverlight {
         }
 
         private void InitializeRuntime(bool debugMode) {
-            RuntimeSetup = CreateRuntimeSetup();
-            RuntimeSetup.DebugMode = debugMode;
-            RuntimeSetup.Options["SearchPaths"] = new string[] { String.Empty };
-
+            RuntimeSetup = CreateRuntimeSetup(debugMode);
             Runtime = new ScriptRuntime(RuntimeSetup);
             LoadDefaultAssemblies();
         }
@@ -106,7 +111,7 @@ namespace Microsoft.Scripting.Silverlight {
             return setup;
         }
 
-        public ScriptRuntimeSetup TryParseFile() {
+        public static ScriptRuntimeSetup TryParseFile() {
             Stream configFile = BrowserPAL.PAL.VirtualFilesystem.GetFile(Settings.LanguagesConfigFile);
             if (configFile == null) return null;
 
diff --git a/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/Settings.cs b/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/Settings.cs
index 82ec22b..dac28f8 100644
--- a/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/Settings.cs
+++ b/Merlin/Main/Hosts/SilverLight/Microsoft.Scripting.SilverLight/Settings.cs
@@ -56,7 +56,14 @@ namespace Microsoft.Scripting.Silverlight {
         private static string _entryPoint;
 
         internal static string GetEntryPoint() {
-            return Settings.EntryPoint;
+            try {
+                return Settings.EntryPoint;
+            } catch (ApplicationException e) {
+                if (!Settings.DownloadScripts)
+                    throw e;
+                BrowserPAL.PAL = HttpPAL.PAL;
+                return Settings.EntryPoint;
+            }
         }
 
         /// <summary>
@@ -111,7 +118,7 @@ namespace Microsoft.Scripting.Silverlight {
             set {
                 if(value) {
                     if (DownloadScriptsFrom == null)
-                        DownloadScriptsFrom = "app";
+                        DownloadScriptsFrom = ".";
                 } else {
                     DownloadScriptsFrom = null;
                 }
diff --git a/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/app.py b/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/app.py
new file mode 100644
index 0000000..264c7dd
--- /dev/null
+++ b/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/app.py
@@ -0,0 +1,9 @@
+from System.Windows import Application
+from System.Windows.Controls import UserControl
+
+class App:
+  def __init__(self):
+    root = Application.Current.LoadRootVisual(UserControl(), "app.xaml")
+    root.Message.Text = "Welcome to Python and Silverlight!"
+
+App()
\ No newline at end of file
diff --git a/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/app.xaml b/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/app.xaml
new file mode 100644
index 0000000..7d60463
--- /dev/null
+++ b/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/app.xaml
@@ -0,0 +1,9 @@
+<UserControl x:Class="System.Windows.Controls.UserControl"
+	xmlns="http://schemas.microsoft.com/client/2007"
+	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+
+  <Grid x:Name="layout_root" Background="White">
+    <TextBlock x:Name="Message" FontSize="30" />
+  </Grid>
+
+</UserControl>
diff --git a/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/css/screen.css b/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/css/screen.css
new file mode 100644
index 0000000..a8edfe5
--- /dev/null
+++ b/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/css/screen.css
@@ -0,0 +1,15 @@
+html, body {
+  height: 100%;
+  overflow: auto;
+}
+body {
+  padding: 0;
+  margin: 0;
+}
+#silverlightControlHost {
+  height: 100%;
+}
+#errorLocation {
+  font-size: small;
+  color: Gray;
+}
diff --git a/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/index.html b/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/index.html
new file mode 100644
index 0000000..6268fa5
--- /dev/null
+++ b/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/index.html
@@ -0,0 +1,121 @@
+﻿<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" >
+<head>
+    <title>Silverlight Template</title>
+
+    <!-- Basic stylesheet for application -->
+    <link type="text/css" rel="stylesheet" href="css/screen.css" />
+
+    <!-- Defines "onSilverlightError" function for Silverlight plugin -->
+    <script type="text/javascript" src="js/error.js"></script>
+</head>
+
+<body>
+  <!-- 
+    Runtime errors from Silverlight will be displayed here.
+    This will contain debugging information and should be 
+    removed or hidden when debugging is completed 
+  -->
+  <div id='errorLocation'></div>
+
+  <div id="silverlightControlHost">
+
+    <!-- 
+      Silverlight plug-in control 
+      http://msdn.microsoft.com/en-us/library/cc189089(VS.95).aspx#silverlight_plug_in_configuring
+    -->
+    <object data="data:application/x-silverlight," type="application/x-silverlight-2" width="100%" height="100%">
+
+      <!-- 
+        "source" points to the actual Silverlight application
+        If using "Chiron /w", value should be the "<your app folder>.xap"
+      -->
+      <param name="source" value="app.xap"/>
+
+      <!-- 
+        "initParams" is a comma-seperated way to pass key=value pair arguments 
+        into your Silverlight application. Dynamic Languages use special 
+        arguments to configure the application:
+    
+        * start = app.(rb|py|js)
+          - this is the entry-point file to the application. 
+          - By default, it will look for any file named "app", regardless of 
+            the extension. The extension will be used to figure out the language. 
+          - This option can be set to anything you want, but it must include 
+            the extension.
+
+        * debug = [true]|false
+          - Runs your code as debug-able; stack traces will be shown if an error
+            occurs. 
+          - This lets you attach the browser to the Visual Studio
+            debugger and step through the running program (only when the
+            Silverlight tools are installed).
+          - When omitted/set to false, all errors will be silent 
+            (for deployment purposes)
+
+        * reportErrors = [HTML-element-ID]
+          - In the event of an error, the error window will be written into the 
+            innerHTML property of the HTML element with an ID attribute matching 
+            the value of this field.
+          - If there is no matching ID, a HTML element is created with that ID, 
+            and the error window inserted.
+          - If this field is omitted, no errors will be shown.
+            + You can define the "onerror" param, which will let you handle any 
+              error with JavaScript (the index.html templates do this, if you
+              want sample code).
+          - This just causes HTML to be generated in the HTML element; the styling 
+            of the error window is defined in a separate error.css file that must 
+            be included in the page.
+
+        * exceptionDetail = true|[false]
+          - If set to true, this will also show the entire managed stack trace 
+            in the error window rather than just the dynamic stack trace. 
+            This is useful when debugging C#/Visual Basic when called from a 
+            dynamic language.
+
+        * console = true|[false]
+          - If set to true, will show a read-eval-print loop (REPL) window at
+          the bottom of the page, for whatever language the start script is in.
+
+        * downloadScripts = true|[false]
+          - If set to true, will try to download script and XAML files from the
+            web-server if they are not found in the XAP file.
+      -->
+      <param name="initParams" value="debug=true, reportErrors=errorLocation, console=true, downloadScripts=true" />
+        
+      <!-- Handle all Silverlight errors with function defined in javascripts/error.js -->
+      <param name="onerror" value="onSilverlightError" />
+      
+      <!-- 
+        Other properties of the Silverlight plug-in. For documentation on this, see:
+        http://msdn.microsoft.com/en-us/library/cc189089(VS.95).aspx#silverlight_plug_in_configuring
+      -->
+      <param name="background" value="white" />
+      <param name="windowless" value="true" />
+
+      <!--
+	  <param name="minRuntimeVersion" value="2.0.31005.0" />
+      <param name="autoUpgrade" value="true" />
+      -->
+
+      <!-- 
+        Shows a "Install Microsoft Silverlight" link if Silverlight is 
+        not installed
+      -->
+      <a href="http://go.microsoft.com/fwlink/?LinkID=124807" style="text-decoration: none;">
+        <img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style: none"/>
+      </a>
+
+    </object>
+
+    <!-- 
+      <iframe> needed to prevent Safari from caching the page and reloading
+      the plugin when the user navigates back to a previously-visted 
+      Silverlight page.
+    -->
+    <iframe style='visibility:hidden;height:0;width:0;border:0px'></iframe>
+  
+  </div>
+
+</body>
+</html>
diff --git a/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/js/error.js b/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/js/error.js
new file mode 100644
index 0000000..fbe7954
--- /dev/null
+++ b/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/js/error.js
@@ -0,0 +1,32 @@
+function onSilverlightError(sender, args) {
+    var appSource = "";
+    if (sender != null && sender != 0) {
+        appSource = sender.getHost().Source;
+    } 
+    var errorType = args.ErrorType;
+    var iErrorCode = args.ErrorCode;
+    
+    var errMsg = "Unhandled Error in Silverlight 2 Application " +  appSource + "\n" ;
+
+    errMsg += "Code: "+ iErrorCode + "    \n";
+    errMsg += "Category: " + errorType + "       \n";
+    errMsg += "Message: " + args.ErrorMessage + "     \n";
+
+    if (errorType == "ParserError")
+    {
+        errMsg += "File: " + args.xamlFile + "     \n";
+        errMsg += "Line: " + args.lineNumber + "     \n";
+        errMsg += "Position: " + args.charPosition + "     \n";
+    }
+    else if (errorType == "RuntimeError")
+    {           
+        if (args.lineNumber != 0)
+        {
+            errMsg += "Line: " + args.lineNumber + "     \n";
+            errMsg += "Position: " +  args.charPosition + "     \n";
+        }
+        errMsg += "MethodName: " + args.methodName + "     \n";
+    }
+
+    throw new Error(errMsg);
+}
diff --git a/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/run.bat b/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/run.bat
new file mode 100644
index 0000000..fdef1b3
--- /dev/null
+++ b/Merlin/Main/Hosts/SilverLight/Tests/tests/manual/test_foox/run.bat
@@ -0,0 +1 @@
+"%MERLIN_ROOT%\bin\Silverlight Debug\Chiron.exe" /b /l:"."
-- 
1.6.2.2.1669.g7eaf8

