[wasm][debugger] Support create, debugging and running wasmbrowser template from...
authorThays Grazia <thaystg@gmail.com>
Fri, 23 Sep 2022 23:43:24 +0000 (20:43 -0300)
committerGitHub <noreply@github.com>
Fri, 23 Sep 2022 23:43:24 +0000 (18:43 -0500)
* Support create, debugging and running wasmbrowser template from VS

* addings extra line in the end of the file

* remove extra spaces

* fix compilation error

* adding extra line in the end of the file

* Addressing @lewing comment.

src/mono/wasm/build/WasmApp.targets
src/mono/wasm/host/BrowserHost.cs
src/mono/wasm/host/WebServer.cs
src/mono/wasm/host/WebServerOptions.cs
src/mono/wasm/host/WebServerStartup.cs
src/mono/wasm/templates/templates/browser/.template.config/template.json
src/mono/wasm/templates/templates/browser/Properties/launchSettings.json [new file with mode: 0644]

index 811e858..fbd1636 100644 (file)
     <WasmDebugLevel Condition="('$(WasmDebugLevel)' == '' or '$(WasmDebugLevel)' == '0') and ('$(DebuggerSupport)' == 'true' or '$(Configuration)' == 'Debug')">-1</WasmDebugLevel>
   </PropertyGroup>
 
+  <ItemGroup>
+    <!-- Allow running/debugging from VS -->
+    <ProjectCapability Include="DotNetCoreWeb"/>
+  </ItemGroup>
+
   <PropertyGroup Label="Identify app bundle directory to run from">
     <!-- Allow running from custom WasmAppDir -->
     <_AppBundleDirForRunCommand Condition="'$(WasmAppDir)' != ''">$(WasmAppDir)</_AppBundleDirForRunCommand>
index bccca9f..f546127 100644 (file)
@@ -71,9 +71,13 @@ internal sealed class BrowserHost
                                                debugging: _args.CommonConfig.Debugging);
         runArgsJson.Save(Path.Combine(_args.CommonConfig.AppPath, "runArgs.json"));
 
+        var urls = new string[] { $"http://localhost:{_args.CommonConfig.HostProperties.WebServerPort}", "https://localhost:0" };
+        if (envVars["ASPNETCORE_URLS"] is not null)
+            urls = envVars["ASPNETCORE_URLS"].Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
+
         (ServerURLs serverURLs, IWebHost host) = await StartWebServerAsync(_args.CommonConfig.AppPath,
                                                                            _args.ForwardConsoleOutput ?? false,
-                                                                           _args.CommonConfig.HostProperties.WebServerPort,
+                                                                           urls,
                                                                            token);
 
         string[] fullUrls = BuildUrls(serverURLs, _args.AppArgs);
@@ -84,7 +88,7 @@ internal sealed class BrowserHost
         await host.WaitForShutdownAsync(token);
     }
 
-    private async Task<(ServerURLs, IWebHost)> StartWebServerAsync(string appPath, bool forwardConsole, int port, CancellationToken token)
+    private async Task<(ServerURLs, IWebHost)> StartWebServerAsync(string appPath, bool forwardConsole, string[] urls, CancellationToken token)
     {
         WasmTestMessagesProcessor? logProcessor = null;
         if (forwardConsole)
@@ -100,7 +104,7 @@ internal sealed class BrowserHost
             ContentRootPath: Path.GetFullPath(appPath),
             WebServerUseCors: true,
             WebServerUseCrossOriginPolicy: true,
-            Port: port
+            Urls: urls
         );
 
         (ServerURLs serverURLs, IWebHost host) = await WebServer.StartAsync(options, _logger, token);
index 51bda60..aed1948 100644 (file)
@@ -20,7 +20,7 @@ public class WebServer
 {
     internal static async Task<(ServerURLs, IWebHost)> StartAsync(WebServerOptions options, ILogger logger, CancellationToken token)
     {
-        string[]? urls = new string[] { $"http://127.0.0.1:{options.Port}", "https://127.0.0.1:0" };
+        string[] urls = options.Urls;
 
         IWebHostBuilder builder = new WebHostBuilder()
             .UseKestrel()
index bc8b5f2..43e05c1 100644 (file)
@@ -15,6 +15,6 @@ internal sealed record WebServerOptions
     string? ContentRootPath,
     bool WebServerUseCors,
     bool WebServerUseCrossOriginPolicy,
-    int Port,
+    string [] Urls,
     string DefaultFileName = "index.html"
 );
index ae5e906..e09cdd6 100644 (file)
@@ -1,13 +1,23 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Net;
 using System.Net.WebSockets;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using System.Web;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Routing;
 using Microsoft.AspNetCore.StaticFiles;
 using Microsoft.Extensions.FileProviders;
 using Microsoft.Extensions.Options;
+using Microsoft.WebAssembly.Diagnostics;
 
 #nullable enable
 
@@ -16,9 +26,32 @@ namespace Microsoft.WebAssembly.AppHost;
 internal sealed class WebServerStartup
 {
     private readonly IWebHostEnvironment _hostingEnvironment;
-
+    private static readonly object LaunchLock = new object();
+    private static string LaunchedDebugProxyUrl = "";
     public WebServerStartup(IWebHostEnvironment hostingEnvironment) => _hostingEnvironment = hostingEnvironment;
 
+    public static int StartDebugProxy(string devToolsHost)
+    {
+        //we need to start another process, otherwise it will be running the BrowserDebugProxy in the same process that will be debugged, so pausing in a breakpoint
+        //on managed code will freeze because it will not be able to continue executing the BrowserDebugProxy to get the locals value
+        var executablePath = Path.Combine(System.AppContext.BaseDirectory, "BrowserDebugHost.dll");
+        var ownerPid = Environment.ProcessId;
+        var generateRandomPort = new Random().Next(5000, 5300);
+        var processStartInfo = new ProcessStartInfo
+        {
+            FileName = "dotnet" + (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ""),
+            Arguments = $"exec \"{executablePath}\" --OwnerPid {ownerPid} --DevToolsUrl {devToolsHost} --DevToolsProxyPort {generateRandomPort}",
+            UseShellExecute = false,
+            RedirectStandardOutput = true,
+        };
+        var debugProxyProcess = Process.Start(processStartInfo);
+        if (debugProxyProcess is null)
+        {
+            throw new InvalidOperationException("Unable to start debug proxy process.");
+        }
+        return generateRandomPort;
+    }
+
     public void Configure(IApplicationBuilder app, IOptions<WebServerOptions> optionsContainer)
     {
         var provider = new FileExtensionContentTypeProvider();
@@ -73,9 +106,43 @@ internal sealed class WebServerStartup
             });
         }
 
-        // app.UseEndpoints(endpoints =>
-        // {
-        //     endpoints.MapFallbackToFile(options.DefaultFileName);
-        // });
+        app.Map("/debug", app =>
+        {
+            app.Run(async (context) =>
+            {
+                //debug from VS
+                var queryParams = HttpUtility.ParseQueryString(context.Request.QueryString.Value!);
+                var browserParam = queryParams.Get("browser");
+                Uri? browserUrl = null;
+                var devToolsHost = "http://localhost:9222";
+                if (browserParam != null)
+                {
+                    browserUrl = new Uri(browserParam);
+                    devToolsHost = $"http://{browserUrl.Host}:{browserUrl.Port}";
+                }
+                lock (LaunchLock)
+                {
+                    if (LaunchedDebugProxyUrl == "")
+                    {
+                        LaunchedDebugProxyUrl = $"http://localhost:{StartDebugProxy(devToolsHost)}";
+                    }
+                }
+                var requestPath = context.Request.Path.ToString();
+                if (requestPath == string.Empty)
+                {
+                    requestPath = "/";
+                }
+                context.Response.Redirect($"{LaunchedDebugProxyUrl}{browserUrl!.PathAndQuery}");
+                await Task.FromResult(0);
+            });
+        });
+        app.UseEndpoints(endpoints =>
+        {
+            endpoints.MapGet("/", context =>
+            {
+                context.Response.Redirect("index.html", permanent: false);
+                return Task.CompletedTask;
+            });
+        });
     }
 }
index 8051e4c..5fa2bac 100644 (file)
@@ -2,6 +2,7 @@
   "$schema": "http://json.schemastore.org/template",
   "author": "Microsoft",
   "classifications": [ "Web", "WebAssembly", "Browser" ],
+  "generatorVersions": "[1.0.0.0-*)",
   "identity": "WebAssembly.Browser",
   "name": "WebAssembly Browser App",
   "shortName": "wasmbrowser",
   "tags": {
     "language": "C#",
     "type": "project"
+  },
+  "symbols": {
+    "kestrelHttpPortGenerated": {
+      "type": "generated",
+      "generator": "port",
+      "parameters": {
+        "low": 5000,
+        "high": 5300
+      },
+           "replaces": "5000"
+    },
+    "kestrelHttpsPortGenerated": {
+      "type": "generated",
+      "generator": "port",
+      "parameters": {
+        "low": 7000,
+        "high": 7300
+      },
+           "replaces": "5001"
+    }
   }
 }
diff --git a/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json b/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json
new file mode 100644 (file)
index 0000000..4807594
--- /dev/null
@@ -0,0 +1,13 @@
+{\r
+    "profiles": {\r
+      "browser.0": {\r
+        "commandName": "Project",\r
+        "launchBrowser": true,\r
+        "environmentVariables": {\r
+          "ASPNETCORE_ENVIRONMENT": "Development"\r
+        },\r
+        "applicationUrl": "https://localhost:5001;http://localhost:5000",\r
+        "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/debug?browser={browserInspectUri}"\r
+      }\r
+    }\r
+}\r