diff --git a/samples/MonoSanity/wwwroot/index.html b/samples/MonoSanity/wwwroot/index.html index 003d37c2fa..51feee8def 100644 --- a/samples/MonoSanity/wwwroot/index.html +++ b/samples/MonoSanity/wwwroot/index.html @@ -104,6 +104,19 @@ function triggerJsException() { throw new Error('This is a JavaScript exception.'); } + + // Normally, applications would use the higher-level APIs for registering invocable + // functions, and for invoking them with automatic argument/result marshalling. + // But since this project is trying to test low-level Mono runtime capabilities, + // we implement our own marshalling here. + window.__blazorRegisteredFunctions = { + evaluateJsExpression: function (dotNetStringExpression) { + var result = eval(dotnetStringToJavaScriptString(dotNetStringExpression)); + return result === null || result === undefined + ? result // Pass through null/undefined so we can verify this is handled upstream + : javaScriptStringToDotNetString(result.toString()); + } + }; diff --git a/samples/MonoSanity/wwwroot/loader.js b/samples/MonoSanity/wwwroot/loader.js index 85e52e46d9..e812ba837c 100644 --- a/samples/MonoSanity/wwwroot/loader.js +++ b/samples/MonoSanity/wwwroot/loader.js @@ -43,6 +43,21 @@ } }; + window.dotnetStringToJavaScriptString = function dotnetStringToJavaScriptString(mono_obj) { + if (mono_obj === 0) + return null; + var mono_string_get_utf8 = Module.cwrap('mono_wasm_string_get_utf8', 'number', ['number']); + var raw = mono_string_get_utf8(mono_obj); + var res = Module.UTF8ToString(raw); + Module._free(raw); + return res; + }; + + window.javaScriptStringToDotNetString = function dotnetStringToJavaScriptString(javaScriptString) { + var mono_string = Module.cwrap('mono_wasm_string_from_js', 'number', ['string']); + return mono_string(javaScriptString); + }; + function preloadAssemblies(loadAssemblyUrls) { var loadBclAssemblies = [ 'mscorlib', @@ -77,21 +92,10 @@ xhr.onerror = onerror; xhr.send(null); } - - function dotnetStringToJavaScriptString(mono_obj) { - if (mono_obj === 0) - return null; - var mono_string_get_utf8 = Module.cwrap('mono_wasm_string_get_utf8', 'number', ['number']); - var raw = mono_string_get_utf8(mono_obj); - var res = Module.UTF8ToString(raw); - Module._free(raw); - return res; - } function callMethod(method, target, args) { var stack = Module.Runtime.stackSave(); var invoke_method = Module.cwrap('mono_wasm_invoke_method', 'number', ['number', 'number', 'number']); - var mono_string = Module.cwrap('mono_wasm_string_from_js', 'number', ['string']); try { var argsBuffer = Module.Runtime.stackAlloc(args.length); @@ -103,7 +107,7 @@ Module.setValue(managedInt, argVal, 'i32'); Module.setValue(argsBuffer + i * 4, managedInt, 'i32'); } else if (typeof argVal === 'string') { - var managedString = mono_string(argVal); + var managedString = javaScriptStringToDotNetString(argVal); Module.setValue(argsBuffer + i * 4, managedString, 'i32'); } else { throw new Error('Unsupported arg type: ' + typeof argVal); diff --git a/samples/MonoSanityClient/Examples.cs b/samples/MonoSanityClient/Examples.cs index 6b11939016..369c1db873 100644 --- a/samples/MonoSanityClient/Examples.cs +++ b/samples/MonoSanityClient/Examples.cs @@ -31,10 +31,10 @@ namespace MonoSanityClient public static string EvaluateJavaScript(string expression) { - var result = Runtime.InvokeJS(expression, out var resultIsException); - if (resultIsException != 0) + var result = Runtime.InvokeJS(out var exceptionMessage, "evaluateJsExpression", expression, null, null); + if (exceptionMessage != null) { - return $".NET got exception: {result}"; + return $".NET got exception: {exceptionMessage}"; } return $".NET received: {(result ?? "(NULL)")}"; diff --git a/samples/MonoSanityClient/MonoSanityClient.csproj b/samples/MonoSanityClient/MonoSanityClient.csproj index b406f105e6..2c544bf90c 100644 --- a/samples/MonoSanityClient/MonoSanityClient.csproj +++ b/samples/MonoSanityClient/MonoSanityClient.csproj @@ -7,4 +7,9 @@ + + + + + diff --git a/samples/MonoSanityClient/WebAssembly.Runtime.cs b/samples/MonoSanityClient/WebAssembly.Runtime.cs deleted file mode 100644 index bc461b992b..0000000000 --- a/samples/MonoSanityClient/WebAssembly.Runtime.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Runtime.CompilerServices; - -namespace WebAssembly -{ - internal static class Runtime - { - // The exact namespace, type, and method name must match the corresponding entry in - // driver.c in the Mono distribution - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern string InvokeJS(string str, out int resultIsException); - } -} diff --git a/src/Microsoft.Blazor.Browser/Interop/RegisteredFunction.cs b/src/Microsoft.Blazor.Browser/Interop/RegisteredFunction.cs new file mode 100644 index 0000000000..a26f2018af --- /dev/null +++ b/src/Microsoft.Blazor.Browser/Interop/RegisteredFunction.cs @@ -0,0 +1,101 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using WebAssembly; + +namespace Microsoft.Blazor.Browser.Interop +{ + /// + /// Provides methods for invoking preregistered JavaScript functions from .NET code. + /// + public static class RegisteredFunction + { + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// When using this overload, all arguments will be supplied as + /// references, meaning that any reference types will be boxed. If you are passing + /// 3 or fewer arguments, it is preferable to instead call the overload that + /// specifies generic type arguments for each argument. + /// + /// The .NET type corresponding to the function's return value type. + /// The identifier used when registering the target function. + /// The arguments to pass, each of which will be supplied as a instance. + /// The result of the function invocation. + public static TRes Invoke(string identifier, params object[] args) + { + var result = Runtime.InvokeJSArray(out var exception, identifier, args); + return exception != null + ? throw new JavaScriptException(exception) + : result; + } + + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The .NET type corresponding to the function's return value type. + /// The identifier used when registering the target function. + /// The result of the function invocation. + public static TRes Invoke(string identifier) + { + var result = Runtime.InvokeJS(out var exception, identifier, null, null, null); + return exception != null + ? throw new JavaScriptException(exception) + : result; + } + + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The type of the first argument. + /// The .NET type corresponding to the function's return value type. + /// The identifier used when registering the target function. + /// The first argument. + /// The result of the function invocation. + public static TRes Invoke(string identifier, T0 arg0) + { + var result = Runtime.InvokeJS(out var exception, identifier, arg0, null, null); + return exception != null + ? throw new JavaScriptException(exception) + : result; + } + + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The type of the first argument. + /// The type of the second argument. + /// The .NET type corresponding to the function's return value type. + /// The identifier used when registering the target function. + /// The first argument. + /// The second argument. + /// The result of the function invocation. + public static TRes Invoke(string identifier, T0 arg0, T1 arg1) + { + var result = Runtime.InvokeJS(out var exception, identifier, arg0, arg1, null); + return exception != null + ? throw new JavaScriptException(exception) + : result; + } + + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The type of the first argument. + /// The type of the second argument. + /// The type of the third argument. + /// The .NET type corresponding to the function's return value type. + /// The identifier used when registering the target function. + /// The first argument. + /// The second argument. + /// The third argument. + /// The result of the function invocation. + public static TRes Invoke(string identifier, T0 arg0, T1 arg1, T2 arg2) + { + var result = Runtime.InvokeJS(out var exception, identifier, arg0, arg1, arg2); + return exception != null + ? throw new JavaScriptException(exception) + : result; + } + } +} diff --git a/src/Microsoft.Blazor.Browser/Interop/WebAssembly.Runtime.cs b/src/Microsoft.Blazor.Browser/Interop/WebAssembly.Runtime.cs index 4f44807817..45f4965a56 100644 --- a/src/Microsoft.Blazor.Browser/Interop/WebAssembly.Runtime.cs +++ b/src/Microsoft.Blazor.Browser/Interop/WebAssembly.Runtime.cs @@ -1,33 +1,19 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.Blazor.Browser.Interop; using System.Runtime.CompilerServices; namespace WebAssembly { internal static class Runtime { - public static string EvaluateJavaScript(string expression) - { - var result = InvokeJS(expression, out var resultIsException); - - if (resultIsException != 0) - { - throw new JavaScriptException(result); - } - - return result; - } - - // The exact namespace, type, and method name must match the corresponding entry in + // The exact namespace, type, and method names must match the corresponding entry in // driver.c in the Mono distribution - [MethodImpl(MethodImplOptions.InternalCall)] - static extern string InvokeJS(string str, out int resultIsException); - // The exact namespace, type, and method name must match the corresponding entry in - // driver.c in the Mono distribution [MethodImpl(MethodImplOptions.InternalCall)] - public static extern object InvokeJSUnmarshalled(string funcExpression, object[] args, out int resultIsException); + public static extern TRes InvokeJS(out string exception, string funcName, T0 arg0, T1 arg1, T2 arg2); + + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern TRes InvokeJSArray(out string exception, string funcName, object[] args); } } diff --git a/src/Microsoft.Blazor.Browser/Renderer.cs b/src/Microsoft.Blazor.Browser/Renderer.cs index c71e7b9a14..c2f6b39e58 100644 --- a/src/Microsoft.Blazor.Browser/Renderer.cs +++ b/src/Microsoft.Blazor.Browser/Renderer.cs @@ -7,7 +7,6 @@ namespace Microsoft.Blazor.Browser { public Renderer() { - WebAssembly.Runtime.EvaluateJavaScript("console.log('Renderer')"); } } } diff --git a/test/Microsoft.Blazor.E2ETest/Tests/MonoSanityTest.cs b/test/Microsoft.Blazor.E2ETest/Tests/MonoSanityTest.cs index e44a26a8a0..2c227d5568 100644 --- a/test/Microsoft.Blazor.E2ETest/Tests/MonoSanityTest.cs +++ b/test/Microsoft.Blazor.E2ETest/Tests/MonoSanityTest.cs @@ -75,7 +75,7 @@ namespace Microsoft.Blazor.E2ETest.Tests SetValue(Browser, "callJsEvalExpression", "triggerJsException()"); Browser.FindElement(By.CssSelector("#callJs button")).Click(); var result = GetValue(Browser, "callJsResult"); - Assert.StartsWith(".NET got exception: Error: This is a JavaScript exception.", result); + Assert.StartsWith(".NET got exception: This is a JavaScript exception.", result); // Also verify we got a stack trace Assert.Contains("at triggerJsException", result);