JSObjectReference (#25028)
This commit is contained in:
parent
0050ece118
commit
8a2f29bb53
|
|
@ -1507,6 +1507,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Diagno
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Diagnostics.HealthChecks.Tests", "src\HealthChecks\HealthChecks\test\Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj", "{7509AA1E-3093-4BEE-984F-E11579E98A11}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.JSInterop.Tests", "src\JSInterop\Microsoft.JSInterop\test\Microsoft.JSInterop.Tests.csproj", "{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -7179,6 +7181,18 @@ Global
|
|||
{7509AA1E-3093-4BEE-984F-E11579E98A11}.Release|x64.Build.0 = Release|Any CPU
|
||||
{7509AA1E-3093-4BEE-984F-E11579E98A11}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{7509AA1E-3093-4BEE-984F-E11579E98A11}.Release|x86.Build.0 = Release|Any CPU
|
||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|x64.Build.0 = Release|Any CPU
|
||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -7934,6 +7948,7 @@ Global
|
|||
{B06040BC-DA28-4923-8CAC-20EB517D471B} = {22D7D74B-565D-4047-97B4-F149B1A13350}
|
||||
{55CACC1F-FE96-47C8-8073-91F4CAA55C75} = {2A91479A-4ABE-4BB7-9A5E-CA3B9CCFC69E}
|
||||
{7509AA1E-3093-4BEE-984F-E11579E98A11} = {7CB09412-C9B0-47E8-A8C3-311AA4CFDE04}
|
||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E} = {16898702-3E33-41C1-B8D8-4CE3F1D46BD9}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
JsonSerializer.Serialize(new[] { callId, success, resultOrError }, JsonSerializerOptions));
|
||||
}
|
||||
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson, JSCallResultType resultType, long targetInstanceId)
|
||||
{
|
||||
if (_clientProxy is null)
|
||||
{
|
||||
|
|
@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
|
||||
Log.BeginInvokeJS(_logger, asyncHandle, identifier);
|
||||
|
||||
_clientProxy.SendAsync("JS.BeginInvokeJS", asyncHandle, identifier, argsJson);
|
||||
_clientProxy.SendAsync("JS.BeginInvokeJS", asyncHandle, identifier, argsJson, (int)resultType, targetInstanceId);
|
||||
}
|
||||
|
||||
public static class Log
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -97,7 +97,7 @@ async function initializeConnection(options: CircuitStartOptions, logger: Logger
|
|||
|
||||
connection.on('JS.AttachComponent', (componentId, selector) => attachRootComponentToLogicalElement(0, circuit.resolveElement(selector), componentId));
|
||||
connection.on('JS.BeginInvokeJS', DotNet.jsCallDispatcher.beginInvokeJSFromDotNet);
|
||||
connection.on('JS.EndInvokeDotNet', (args: string) => DotNet.jsCallDispatcher.endInvokeDotNetFromJS(...(JSON.parse(args) as [string, boolean, unknown])));
|
||||
connection.on('JS.EndInvokeDotNet', (args: string) => DotNet.jsCallDispatcher.endInvokeDotNetFromJS(...(DotNet.parseJsonWithRevivers(args) as [string, boolean, unknown])));
|
||||
|
||||
const renderQueue = RenderQueue.getOrCreate(logger);
|
||||
connection.on('JS.RenderBatch', (batchId: number, batchData: Uint8Array) => {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
|
|||
}
|
||||
});
|
||||
|
||||
// Configure JS interop
|
||||
window['Blazor']._internal.invokeJSFromDotNet = invokeJSFromDotNet;
|
||||
|
||||
// Configure environment for execution under Mono WebAssembly with shared-memory rendering
|
||||
const platform = Environment.setPlatform(monoPlatform);
|
||||
window['Blazor'].platform = platform;
|
||||
|
|
@ -84,6 +87,28 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
|
|||
platform.callEntryPoint(resourceLoader.bootConfig.entryAssembly);
|
||||
}
|
||||
|
||||
function invokeJSFromDotNet(callInfo: Pointer, arg0: any, arg1: any, arg2: any): any {
|
||||
const functionIdentifier = monoPlatform.readStringField(callInfo, 0)!;
|
||||
const resultType = monoPlatform.readInt32Field(callInfo, 4);
|
||||
const marshalledCallArgsJson = monoPlatform.readStringField(callInfo, 8);
|
||||
const targetInstanceId = monoPlatform.readUint64Field(callInfo, 20);
|
||||
|
||||
if (marshalledCallArgsJson !== null) {
|
||||
const marshalledCallAsyncHandle = monoPlatform.readUint64Field(callInfo, 12);
|
||||
|
||||
if (marshalledCallAsyncHandle !== 0) {
|
||||
DotNet.jsCallDispatcher.beginInvokeJSFromDotNet(marshalledCallAsyncHandle, functionIdentifier, marshalledCallArgsJson, resultType, targetInstanceId);
|
||||
return 0;
|
||||
} else {
|
||||
const resultJson = DotNet.jsCallDispatcher.invokeJSFromDotNet(functionIdentifier, marshalledCallArgsJson, resultType, targetInstanceId)!;
|
||||
return resultJson === null ? 0 : BINDING.js_string_to_mono_string(resultJson);
|
||||
}
|
||||
} else {
|
||||
const func = DotNet.jsCallDispatcher.findJSFunction(functionIdentifier, targetInstanceId);
|
||||
return func.call(null, arg0, arg1, arg2);
|
||||
}
|
||||
}
|
||||
|
||||
window['Blazor'].start = boot;
|
||||
if (shouldAutoStart()) {
|
||||
boot().catch(error => {
|
||||
|
|
|
|||
|
|
@ -16,12 +16,7 @@ namespace WebAssembly.JSInterop
|
|||
// in driver.c in the Mono distribution
|
||||
/// See: https://github.com/mono/mono/blob/90574987940959fe386008a850982ea18236a533/sdks/wasm/src/driver.c#L318-L319
|
||||
|
||||
// We're passing asyncHandle by ref not because we want it to be writable, but so it gets
|
||||
// passed as a pointer (4 bytes). We can pass 4-byte values, but not 8-byte ones.
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern string InvokeJSMarshalled(out string exception, ref long asyncHandle, string functionIdentifier, string argsJson);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern TRes InvokeJSUnmarshalled<T0, T1, T2, TRes>(out string exception, string functionIdentifier, [AllowNull] T0 arg0, [AllowNull] T1 arg1, [AllowNull] T2 arg2);
|
||||
public static extern TRes InvokeJS<T0, T1, T2, TRes>(out string exception, ref JSCallInfo callInfo, [AllowNull] T0 arg0, [AllowNull] T1 arg1, [AllowNull] T2 arg2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
// 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.InteropServices;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace WebAssembly.JSInterop
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit, Pack = 4)]
|
||||
internal struct JSCallInfo
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public string FunctionIdentifier;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public JSCallResultType ResultType;
|
||||
|
||||
[FieldOffset(8)]
|
||||
public string MarshalledCallArgsJson;
|
||||
|
||||
[FieldOffset(12)]
|
||||
public long MarshalledCallAsyncHandle;
|
||||
|
||||
[FieldOffset(20)]
|
||||
public long TargetInstanceId;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,19 +14,37 @@ namespace Microsoft.JSInterop.WebAssembly
|
|||
public abstract class WebAssemblyJSRuntime : JSInProcessRuntime, IJSUnmarshalledRuntime
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override string InvokeJS(string identifier, string argsJson)
|
||||
protected override string InvokeJS(string identifier, string argsJson, JSCallResultType resultType, long targetInstanceId)
|
||||
{
|
||||
var noAsyncHandle = default(long);
|
||||
var result = InternalCalls.InvokeJSMarshalled(out var exception, ref noAsyncHandle, identifier, argsJson);
|
||||
var callInfo = new JSCallInfo
|
||||
{
|
||||
FunctionIdentifier = identifier,
|
||||
TargetInstanceId = targetInstanceId,
|
||||
ResultType = resultType,
|
||||
MarshalledCallArgsJson = argsJson ?? "[]",
|
||||
MarshalledCallAsyncHandle = default
|
||||
};
|
||||
|
||||
var result = InternalCalls.InvokeJS<object, object, object, string>(out var exception, ref callInfo, null, null, null);
|
||||
|
||||
return exception != null
|
||||
? throw new JSException(exception)
|
||||
: result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson, JSCallResultType resultType, long targetInstanceId)
|
||||
{
|
||||
InternalCalls.InvokeJSMarshalled(out _, ref asyncHandle, identifier, argsJson);
|
||||
var callInfo = new JSCallInfo
|
||||
{
|
||||
FunctionIdentifier = identifier,
|
||||
TargetInstanceId = targetInstanceId,
|
||||
ResultType = resultType,
|
||||
MarshalledCallArgsJson = argsJson ?? "[]",
|
||||
MarshalledCallAsyncHandle = asyncHandle
|
||||
};
|
||||
|
||||
InternalCalls.InvokeJS<object, object, object, string>(out _, ref callInfo, null, null, null);
|
||||
}
|
||||
|
||||
protected override void EndInvokeDotNet(DotNetInvocationInfo callInfo, in DotNetInvocationResult dispatchResult)
|
||||
|
|
@ -39,7 +57,7 @@ namespace Microsoft.JSInterop.WebAssembly
|
|||
// We pass 0 as the async handle because we don't want the JS-side code to
|
||||
// send back any notification (we're just providing a result for an existing async call)
|
||||
var args = JsonSerializer.Serialize(new[] { callInfo.CallId, dispatchResult.Success, resultOrError }, JsonSerializerOptions);
|
||||
BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args);
|
||||
BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args, JSCallResultType.Default, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -57,7 +75,14 @@ namespace Microsoft.JSInterop.WebAssembly
|
|||
/// <inheritdoc />
|
||||
TResult IJSUnmarshalledRuntime.InvokeUnmarshalled<T0, T1, T2, TResult>(string identifier, T0 arg0, T1 arg1, T2 arg2)
|
||||
{
|
||||
var result = InternalCalls.InvokeJSUnmarshalled<T0, T1, T2, TResult>(out var exception, identifier, arg0, arg1, arg2);
|
||||
var callInfo = new JSCallInfo
|
||||
{
|
||||
FunctionIdentifier = identifier,
|
||||
ResultType = ResultTypeFromGeneric<TResult>()
|
||||
};
|
||||
|
||||
var result = InternalCalls.InvokeJS<T0, T1, T2, TResult>(out var exception, ref callInfo, arg0, arg1, arg2);
|
||||
|
||||
return exception != null
|
||||
? throw new JSException(exception)
|
||||
: result;
|
||||
|
|
|
|||
|
|
@ -55,10 +55,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
["result7Async"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]",
|
||||
["result8Async"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
|
||||
["result9Async"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
|
||||
["roundTripJSObjectReferenceAsync"] = @"""successful""",
|
||||
["invokeDisposedJSObjectReferenceExceptionAsync"] = @"""JS object instance with ID",
|
||||
["AsyncThrowSyncException"] = @"""System.InvalidOperationException: Threw a sync exception!",
|
||||
["AsyncThrowAsyncException"] = @"""System.InvalidOperationException: Threw an async exception!",
|
||||
["SyncExceptionFromAsyncMethod"] = "Function threw a sync exception!",
|
||||
["AsyncExceptionFromAsyncMethod"] = "Function threw an async exception!",
|
||||
["JSObjectReferenceInvokeNonFunctionException"] = "The value 'nonFunction' is not a function.",
|
||||
["resultReturnDotNetObjectByRefAsync"] = "1001",
|
||||
["instanceMethodThisTypeNameAsync"] = @"""JavaScriptInterop""",
|
||||
["instanceMethodStringValueUpperAsync"] = @"""MY STRING""",
|
||||
|
|
@ -69,6 +72,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
["testDtoAsync"] = "Same",
|
||||
["returnPrimitiveAsync"] = "123",
|
||||
["returnArrayAsync"] = "first,second",
|
||||
["jsObjectReference.identity"] = "Invoked from JSObjectReference",
|
||||
["jsObjectReference.nested.add"] = "5",
|
||||
["addViaJSObjectReference"] = "5",
|
||||
["jsObjectReferenceModule"] = "Returned from module!",
|
||||
["syncGenericInstanceMethod"] = @"""Initial value""",
|
||||
["asyncGenericInstanceMethod"] = @"""Updated value 1""",
|
||||
};
|
||||
|
|
@ -93,6 +100,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
["result7"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]",
|
||||
["result8"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
|
||||
["result9"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
|
||||
["roundTripJSObjectReference"] = @"""successful""",
|
||||
["invokeDisposedJSObjectReferenceException"] = @"""JS object instance with ID",
|
||||
["ThrowException"] = @"""System.InvalidOperationException: Threw an exception!",
|
||||
["ExceptionFromSyncMethod"] = "Function threw an exception!",
|
||||
["resultReturnDotNetObjectByRefSync"] = "1000",
|
||||
|
|
@ -100,6 +109,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
["instanceMethodStringValueUpper"] = @"""MY STRING""",
|
||||
["instanceMethodIncomingByRef"] = "123",
|
||||
["instanceMethodOutgoingByRef"] = "1234",
|
||||
["jsInProcessObjectReference.identity"] = "Invoked from JSInProcessObjectReference",
|
||||
["stringValueUpperSync"] = "MY STRING",
|
||||
["testDtoNonSerializedValueSync"] = "99999",
|
||||
["testDtoSync"] = "Same",
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@
|
|||
<p id="@nameof(SyncExceptionFromAsyncMethod)">@SyncExceptionFromAsyncMethod?.Message</p>
|
||||
<h2>@nameof(AsyncExceptionFromAsyncMethod)</h2>
|
||||
<p id="@nameof(AsyncExceptionFromAsyncMethod)">@AsyncExceptionFromAsyncMethod?.Message</p>
|
||||
<h2>@nameof(JSObjectReferenceInvokeNonFunctionException)</h2>
|
||||
<p id="@nameof(JSObjectReferenceInvokeNonFunctionException)">@JSObjectReferenceInvokeNonFunctionException?.Message</p>
|
||||
</div>
|
||||
@if (DoneWithInterop)
|
||||
{
|
||||
|
|
@ -59,6 +61,7 @@
|
|||
public JSException ExceptionFromSyncMethod { get; set; }
|
||||
public JSException SyncExceptionFromAsyncMethod { get; set; }
|
||||
public JSException AsyncExceptionFromAsyncMethod { get; set; }
|
||||
public JSException JSObjectReferenceInvokeNonFunctionException { get; set; }
|
||||
|
||||
public IDictionary<string, object> ReceiveDotNetObjectByRefResult { get; set; } = new Dictionary<string, object>();
|
||||
public IDictionary<string, object> ReceiveDotNetObjectByRefAsyncResult { get; set; } = new Dictionary<string, object>();
|
||||
|
|
@ -134,6 +137,28 @@
|
|||
ReturnValues["returnArray"] = string.Join(",", ((IJSInProcessRuntime)JSRuntime).Invoke<Segment[]>("returnArray").Select(x => x.Source).ToArray());
|
||||
}
|
||||
|
||||
var jsObjectReference = await JSRuntime.InvokeAsync<JSObjectReference>("returnJSObjectReference");
|
||||
ReturnValues["jsObjectReference.identity"] = await jsObjectReference.InvokeAsync<string>("identity", "Invoked from JSObjectReference");
|
||||
ReturnValues["jsObjectReference.nested.add"] = (await jsObjectReference.InvokeAsync<int>("nested.add", 2, 3)).ToString();
|
||||
ReturnValues["addViaJSObjectReference"] = (await JSRuntime.InvokeAsync<int>("addViaJSObjectReference", jsObjectReference, 2, 3)).ToString();
|
||||
|
||||
try
|
||||
{
|
||||
await jsObjectReference.InvokeAsync<object>("nonFunction");
|
||||
}
|
||||
catch (JSException e)
|
||||
{
|
||||
JSObjectReferenceInvokeNonFunctionException = e;
|
||||
}
|
||||
|
||||
var module = await JSRuntime.InvokeAsync<JSObjectReference>("import", "./js/testmodule.js");
|
||||
ReturnValues["jsObjectReferenceModule"] = await module.InvokeAsync<string>("identity", "Returned from module!");
|
||||
|
||||
if (shouldSupportSyncInterop)
|
||||
{
|
||||
InvokeInProcessJSInterop();
|
||||
}
|
||||
|
||||
Invocations = invocations;
|
||||
DoneWithInterop = true;
|
||||
}
|
||||
|
|
@ -163,6 +188,14 @@
|
|||
ReceiveDotNetObjectByRefResult["testDto"] = result.TestDto.Value == passDotNetObjectByRef ? "Same" : "Different";
|
||||
}
|
||||
|
||||
public void InvokeInProcessJSInterop()
|
||||
{
|
||||
var inProcRuntime = ((IJSInProcessRuntime)JSRuntime);
|
||||
|
||||
var jsInProcObjectReference = inProcRuntime.Invoke<JSInProcessObjectReference>("returnJSObjectReference");
|
||||
ReturnValues["jsInProcessObjectReference.identity"] = jsInProcObjectReference.Invoke<string>("identity", "Invoked from JSInProcessObjectReference");
|
||||
}
|
||||
|
||||
public class PassDotNetObjectByRefArgs
|
||||
{
|
||||
public string StringValue { get; set; }
|
||||
|
|
|
|||
|
|
@ -414,6 +414,47 @@ namespace BasicTestApp.InteropTest
|
|||
return objectByRef.Value.GetNonSerializedValue();
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public static JSObjectReference RoundTripJSObjectReference(JSObjectReference jsObjectReference)
|
||||
{
|
||||
return jsObjectReference;
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public static async Task<JSObjectReference> RoundTripJSObjectReferenceAsync(JSObjectReference jSObjectReference)
|
||||
{
|
||||
await Task.Yield();
|
||||
return jSObjectReference;
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public static string InvokeDisposedJSObjectReferenceException(JSInProcessObjectReference jsObjectReference)
|
||||
{
|
||||
try
|
||||
{
|
||||
jsObjectReference.Invoke<object>("noop");
|
||||
return "No exception thrown";
|
||||
}
|
||||
catch (JSException e)
|
||||
{
|
||||
return e.Message;
|
||||
}
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public static async Task<string> InvokeDisposedJSObjectReferenceExceptionAsync(JSObjectReference jsObjectReference)
|
||||
{
|
||||
try
|
||||
{
|
||||
await jsObjectReference.InvokeVoidAsync("noop");
|
||||
return "No exception thrown";
|
||||
}
|
||||
catch (JSException e)
|
||||
{
|
||||
return e.Message;
|
||||
}
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public InstanceMethodOutput InstanceMethod(InstanceMethodInput input)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,6 +30,17 @@ async function invokeDotNetInteropMethodsAsync(shouldSupportSyncInterop, dotNetO
|
|||
var returnDotNetObjectByRefResult = DotNet.invokeMethod(assemblyName, 'ReturnDotNetObjectByRef');
|
||||
results['resultReturnDotNetObjectByRefSync'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', returnDotNetObjectByRefResult['Some sync instance']);
|
||||
|
||||
var jsObjectReference = DotNet.createJSObjectReference({
|
||||
prop: 'successful',
|
||||
noop: function () { }
|
||||
});
|
||||
|
||||
var returnedObject = DotNet.invokeMethod(assemblyName, 'RoundTripJSObjectReference', jsObjectReference);
|
||||
results['roundTripJSObjectReference'] = returnedObject && returnedObject.prop;
|
||||
|
||||
DotNet.disposeJSObjectReference(jsObjectReference);
|
||||
results['invokeDisposedJSObjectReferenceException'] = DotNet.invokeMethod(assemblyName, 'InvokeDisposedJSObjectReferenceException', jsObjectReference);
|
||||
|
||||
var instanceMethodResult = instanceMethodsTarget.invokeMethod('InstanceMethod', {
|
||||
stringValue: 'My string',
|
||||
dtoByRef: dotNetObjectByRef
|
||||
|
|
@ -66,6 +77,17 @@ async function invokeDotNetInteropMethodsAsync(shouldSupportSyncInterop, dotNetO
|
|||
const returnDotNetObjectByRefAsync = await DotNet.invokeMethodAsync(assemblyName, 'ReturnDotNetObjectByRefAsync');
|
||||
results['resultReturnDotNetObjectByRefAsync'] = await DotNet.invokeMethodAsync(assemblyName, 'ExtractNonSerializedValue', returnDotNetObjectByRefAsync['Some async instance']);
|
||||
|
||||
var jsObjectReference = DotNet.createJSObjectReference({
|
||||
prop: 'successful',
|
||||
noop: function () { }
|
||||
});
|
||||
|
||||
var returnedObject = await DotNet.invokeMethodAsync(assemblyName, 'RoundTripJSObjectReferenceAsync', jsObjectReference);
|
||||
results['roundTripJSObjectReferenceAsync'] = returnedObject && returnedObject.prop;
|
||||
|
||||
DotNet.disposeJSObjectReference(jsObjectReference);
|
||||
results['invokeDisposedJSObjectReferenceExceptionAsync'] = await DotNet.invokeMethodAsync(assemblyName, 'InvokeDisposedJSObjectReferenceExceptionAsync', jsObjectReference);
|
||||
|
||||
const instanceMethodAsync = await instanceMethodsTarget.invokeMethodAsync('InstanceMethodAsync', {
|
||||
stringValue: 'My string',
|
||||
dtoByRef: dotNetObjectByRef
|
||||
|
|
@ -167,6 +189,8 @@ window.jsInteropTests = {
|
|||
asyncFunctionThrowsAsyncException: asyncFunctionThrowsAsyncException,
|
||||
returnPrimitive: returnPrimitive,
|
||||
returnPrimitiveAsync: returnPrimitiveAsync,
|
||||
returnJSObjectReference: returnJSObjectReference,
|
||||
addViaJSObjectReference: addViaJSObjectReference,
|
||||
receiveDotNetObjectByRef: receiveDotNetObjectByRef,
|
||||
receiveDotNetObjectByRefAsync: receiveDotNetObjectByRefAsync
|
||||
};
|
||||
|
|
@ -195,6 +219,27 @@ function returnArrayAsync() {
|
|||
});
|
||||
}
|
||||
|
||||
function returnJSObjectReference() {
|
||||
return {
|
||||
identity: function (value) {
|
||||
return value;
|
||||
},
|
||||
nonFunction: 123,
|
||||
nested: {
|
||||
add: function (a, b) {
|
||||
return a + b;
|
||||
}
|
||||
},
|
||||
dispose: function () {
|
||||
DotNet.disposeJSObjectReference(this);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function addViaJSObjectReference(jsObjectReference, a, b) {
|
||||
return jsObjectReference.nested.add(a, b);
|
||||
}
|
||||
|
||||
function functionThrowsException() {
|
||||
throw new Error('Function threw an exception!');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
export function identity(value) {
|
||||
return value;
|
||||
}
|
||||
|
|
@ -6,9 +6,68 @@ export module DotNet {
|
|||
export type JsonReviver = ((key: any, value: any) => any);
|
||||
const jsonRevivers: JsonReviver[] = [];
|
||||
|
||||
class JSObject {
|
||||
_cachedFunctions: Map<string, Function>;
|
||||
|
||||
constructor(private _jsObject: any)
|
||||
{
|
||||
this._cachedFunctions = new Map<string, Function>();
|
||||
}
|
||||
|
||||
public findFunction(identifier: string) {
|
||||
const cachedFunction = this._cachedFunctions.get(identifier);
|
||||
|
||||
if (cachedFunction) {
|
||||
return cachedFunction;
|
||||
}
|
||||
|
||||
let result: any = this._jsObject;
|
||||
let lastSegmentValue: any;
|
||||
|
||||
identifier.split('.').forEach(segment => {
|
||||
if (segment in result) {
|
||||
lastSegmentValue = result;
|
||||
result = result[segment];
|
||||
} else {
|
||||
throw new Error(`Could not find '${identifier}' ('${segment}' was undefined).`);
|
||||
}
|
||||
});
|
||||
|
||||
if (result instanceof Function) {
|
||||
result = result.bind(lastSegmentValue);
|
||||
this._cachedFunctions.set(identifier, result);
|
||||
return result;
|
||||
} else {
|
||||
throw new Error(`The value '${identifier}' is not a function.`);
|
||||
}
|
||||
}
|
||||
|
||||
public getWrappedObject() {
|
||||
return this._jsObject;
|
||||
}
|
||||
}
|
||||
|
||||
const jsObjectIdKey = "__jsObjectId";
|
||||
|
||||
const pendingAsyncCalls: { [id: number]: PendingAsyncCall<any> } = {};
|
||||
const cachedJSFunctions: { [identifier: string]: Function } = {};
|
||||
const windowJSObjectId = 0;
|
||||
const cachedJSObjectsById: { [id: number]: JSObject } = {
|
||||
[windowJSObjectId]: new JSObject(window),
|
||||
};
|
||||
|
||||
cachedJSObjectsById[windowJSObjectId]._cachedFunctions.set('import', (url: any) => {
|
||||
// In most cases developers will want to resolve dynamic imports relative to the base HREF.
|
||||
// However since we're the one calling the import keyword, they would be resolved relative to
|
||||
// this framework bundle URL. Fix this by providing an absolute URL.
|
||||
if (typeof url === 'string' && url.startsWith('./')) {
|
||||
url = document.baseURI + url.substr(2);
|
||||
}
|
||||
|
||||
return import(/* webpackIgnore: true */ url);
|
||||
});
|
||||
|
||||
let nextAsyncCallId = 1; // Start at 1 because zero signals "no response needed"
|
||||
let nextJsObjectId = 1; // Start at 1 because zero is reserved for "window"
|
||||
|
||||
let dotNetDispatcher: DotNetCallDispatcher | null = null;
|
||||
|
||||
|
|
@ -55,6 +114,58 @@ export module DotNet {
|
|||
return invokePossibleInstanceMethodAsync(assemblyName, methodIdentifier, null, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JavaScript object reference that can be passed to .NET via interop calls.
|
||||
*
|
||||
* @param jsObject The JavaScript Object used to create the JavaScript object reference.
|
||||
* @returns The JavaScript object reference (this will be the same instance as the given object).
|
||||
* @throws Error if the given value is not an Object.
|
||||
*/
|
||||
export function createJSObjectReference(jsObject: any): any {
|
||||
if (jsObject && typeof jsObject === 'object') {
|
||||
cachedJSObjectsById[nextJsObjectId] = new JSObject(jsObject);
|
||||
|
||||
const result = {
|
||||
[jsObjectIdKey]: nextJsObjectId
|
||||
};
|
||||
|
||||
nextJsObjectId++;
|
||||
|
||||
return result;
|
||||
} else {
|
||||
throw new Error(`Cannot create a JSObjectReference from the value '${jsObject}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the given JavaScript object reference.
|
||||
*
|
||||
* @param jsObjectReference The JavaScript Object reference.
|
||||
*/
|
||||
export function disposeJSObjectReference(jsObjectReference: any): void {
|
||||
const id = jsObjectReference && jsObjectReference[jsObjectIdKey];
|
||||
|
||||
if (typeof id === 'number') {
|
||||
disposeJSObjectReferenceById(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given JSON string using revivers to restore args passed from .NET to JS.
|
||||
*
|
||||
* @param json The JSON stirng to parse.
|
||||
*/
|
||||
export function parseJsonWithRevivers(json: string): any {
|
||||
return json ? JSON.parse(json, (key, initialValue) => {
|
||||
// Invoke each reviver in order, passing the output from the previous reviver,
|
||||
// so that each one gets a chance to transform the value
|
||||
return jsonRevivers.reduce(
|
||||
(latestValue, reviver) => reviver(key, latestValue),
|
||||
initialValue
|
||||
);
|
||||
}) : null;
|
||||
}
|
||||
|
||||
function invokePossibleInstanceMethod<T>(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): T {
|
||||
const dispatcher = getRequiredDispatcher();
|
||||
if (dispatcher.invokeDotNetFromJS) {
|
||||
|
|
@ -114,6 +225,14 @@ export module DotNet {
|
|||
reject: (reason?: any) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the type of result expected from a JS interop call.
|
||||
*/
|
||||
export enum JSCallResultType {
|
||||
Default = 0,
|
||||
JSObjectReference = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the ability to dispatch calls from JavaScript to a .NET runtime.
|
||||
*/
|
||||
|
|
@ -158,19 +277,31 @@ export module DotNet {
|
|||
* Finds the JavaScript function matching the specified identifier.
|
||||
*
|
||||
* @param identifier Identifies the globally-reachable function to be returned.
|
||||
* @param targetInstanceId The instance ID of the target JS object.
|
||||
* @returns A Function instance.
|
||||
*/
|
||||
findJSFunction, // Note that this is used by the JS interop code inside Mono WebAssembly itself
|
||||
|
||||
/**
|
||||
* Disposes the JavaScript object reference with the specified object ID.
|
||||
*
|
||||
* @param id The ID of the JavaScript object reference.
|
||||
*/
|
||||
disposeJSObjectReferenceById,
|
||||
|
||||
/**
|
||||
* Invokes the specified synchronous JavaScript function.
|
||||
*
|
||||
* @param identifier Identifies the globally-reachable function to invoke.
|
||||
* @param argsJson JSON representation of arguments to be passed to the function.
|
||||
* @param resultType The type of result expected from the JS interop call.
|
||||
* @param targetInstanceId The instance ID of the target JS object.
|
||||
* @returns JSON representation of the invocation result.
|
||||
*/
|
||||
invokeJSFromDotNet: (identifier: string, argsJson: string) => {
|
||||
const result = findJSFunction(identifier).apply(null, parseJsonWithRevivers(argsJson));
|
||||
invokeJSFromDotNet: (identifier: string, argsJson: string, resultType: JSCallResultType, targetInstanceId: number) => {
|
||||
const returnValue = findJSFunction(identifier, targetInstanceId).apply(null, parseJsonWithRevivers(argsJson));
|
||||
const result = createJSCallResult(returnValue, resultType);
|
||||
|
||||
return result === null || result === undefined
|
||||
? null
|
||||
: JSON.stringify(result, argReplacer);
|
||||
|
|
@ -182,12 +313,14 @@ export module DotNet {
|
|||
* @param asyncHandle A value identifying the asynchronous operation. This value will be passed back in a later call to endInvokeJSFromDotNet.
|
||||
* @param identifier Identifies the globally-reachable function to invoke.
|
||||
* @param argsJson JSON representation of arguments to be passed to the function.
|
||||
* @param resultType The type of result expected from the JS interop call.
|
||||
* @param targetInstanceId The ID of the target JS object instance.
|
||||
*/
|
||||
beginInvokeJSFromDotNet: (asyncHandle: number, identifier: string, argsJson: string): void => {
|
||||
beginInvokeJSFromDotNet: (asyncHandle: number, identifier: string, argsJson: string, resultType: JSCallResultType, targetInstanceId: number): void => {
|
||||
// Coerce synchronous functions into async ones, plus treat
|
||||
// synchronous exceptions the same as async ones
|
||||
const promise = new Promise<any>(resolve => {
|
||||
const synchronousResultOrPromise = findJSFunction(identifier).apply(null, parseJsonWithRevivers(argsJson));
|
||||
const synchronousResultOrPromise = findJSFunction(identifier, targetInstanceId).apply(null, parseJsonWithRevivers(argsJson));
|
||||
resolve(synchronousResultOrPromise);
|
||||
});
|
||||
|
||||
|
|
@ -196,7 +329,7 @@ export module DotNet {
|
|||
// On completion, dispatch result back to .NET
|
||||
// Not using "await" because it codegens a lot of boilerplate
|
||||
promise.then(
|
||||
result => getRequiredDispatcher().endInvokeJSFromDotNet(asyncHandle, true, JSON.stringify([asyncHandle, true, result], argReplacer)),
|
||||
result => getRequiredDispatcher().endInvokeJSFromDotNet(asyncHandle, true, JSON.stringify([asyncHandle, true, createJSCallResult(result, resultType)], argReplacer)),
|
||||
error => getRequiredDispatcher().endInvokeJSFromDotNet(asyncHandle, false, JSON.stringify([asyncHandle, false, formatError(error)]))
|
||||
);
|
||||
}
|
||||
|
|
@ -214,17 +347,6 @@ export module DotNet {
|
|||
}
|
||||
}
|
||||
|
||||
function parseJsonWithRevivers(json: string): any {
|
||||
return json ? JSON.parse(json, (key, initialValue) => {
|
||||
// Invoke each reviver in order, passing the output from the previous reviver,
|
||||
// so that each one gets a chance to transform the value
|
||||
return jsonRevivers.reduce(
|
||||
(latestValue, reviver) => reviver(key, latestValue),
|
||||
initialValue
|
||||
);
|
||||
}) : null;
|
||||
}
|
||||
|
||||
function formatError(error: any): string {
|
||||
if (error instanceof Error) {
|
||||
return `${error.message}\n${error.stack}`;
|
||||
|
|
@ -233,31 +355,18 @@ export module DotNet {
|
|||
}
|
||||
}
|
||||
|
||||
function findJSFunction(identifier: string): Function {
|
||||
if (Object.prototype.hasOwnProperty.call(cachedJSFunctions, identifier)) {
|
||||
return cachedJSFunctions[identifier];
|
||||
function findJSFunction(identifier: string, targetInstanceId: number): Function {
|
||||
let targetInstance = cachedJSObjectsById[targetInstanceId];
|
||||
|
||||
if (targetInstance) {
|
||||
return targetInstance.findFunction(identifier);
|
||||
} else {
|
||||
throw new Error(`JS object instance with ID ${targetInstanceId} does not exist (has it been disposed?).`);
|
||||
}
|
||||
}
|
||||
|
||||
let result: any = window;
|
||||
let resultIdentifier = 'window';
|
||||
let lastSegmentValue: any;
|
||||
identifier.split('.').forEach(segment => {
|
||||
if (segment in result) {
|
||||
lastSegmentValue = result;
|
||||
result = result[segment];
|
||||
resultIdentifier += '.' + segment;
|
||||
} else {
|
||||
throw new Error(`Could not find '${segment}' in '${resultIdentifier}'.`);
|
||||
}
|
||||
});
|
||||
|
||||
if (result instanceof Function) {
|
||||
result = result.bind(lastSegmentValue);
|
||||
cachedJSFunctions[identifier] = result;
|
||||
return result;
|
||||
} else {
|
||||
throw new Error(`The value '${resultIdentifier}' is not a function.`);
|
||||
}
|
||||
function disposeJSObjectReferenceById(id: number) {
|
||||
delete cachedJSObjectsById[id];
|
||||
}
|
||||
|
||||
class DotNetObject {
|
||||
|
|
@ -292,6 +401,33 @@ export module DotNet {
|
|||
return value;
|
||||
});
|
||||
|
||||
attachReviver(function reviveJSObjectReference(key: any, value: any) {
|
||||
if (value && typeof value === 'object' && value.hasOwnProperty(jsObjectIdKey)) {
|
||||
const id = value[jsObjectIdKey];
|
||||
const jsObject = cachedJSObjectsById[id];
|
||||
|
||||
if (jsObject) {
|
||||
return jsObject.getWrappedObject();
|
||||
} else {
|
||||
throw new Error(`JS object instance with ID ${id} does not exist (has it been disposed?).`);
|
||||
}
|
||||
}
|
||||
|
||||
// Unrecognized - let another reviver handle it
|
||||
return value;
|
||||
});
|
||||
|
||||
function createJSCallResult(returnValue: any, resultType: JSCallResultType) {
|
||||
switch (resultType) {
|
||||
case JSCallResultType.Default:
|
||||
return returnValue;
|
||||
case JSCallResultType.JSObjectReference:
|
||||
return createJSObjectReference(returnValue);
|
||||
default:
|
||||
throw new Error(`Invalid JS call result type '${resultType}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
function argReplacer(key: string, value: any) {
|
||||
return value instanceof DotNetObject ? value.serializeAsArg() : value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
"lib": ["es2015", "dom", "es2015.promise"],
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"outDir": "dist"
|
||||
"outDir": "dist",
|
||||
"module": "ESNext",
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
// 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;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.JSInterop.Infrastructure
|
||||
{
|
||||
internal sealed class JSObjectReferenceJsonConverter<TJSObjectReference>
|
||||
: JsonConverter<TJSObjectReference> where TJSObjectReference : JSObjectReference
|
||||
{
|
||||
private readonly Func<long, TJSObjectReference> _jsObjectReferenceFactory;
|
||||
|
||||
public JSObjectReferenceJsonConverter(Func<long, TJSObjectReference> jsObjectReferenceFactory)
|
||||
{
|
||||
_jsObjectReferenceFactory = jsObjectReferenceFactory;
|
||||
}
|
||||
|
||||
public override TJSObjectReference? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
long id = -1;
|
||||
|
||||
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.PropertyName)
|
||||
{
|
||||
if (id == -1 && reader.ValueTextEquals(JSObjectReference.IdKey.EncodedUtf8Bytes))
|
||||
{
|
||||
reader.Read();
|
||||
id = reader.GetInt64();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonException($"Unexcepted JSON property {reader.GetString()}.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonException($"Unexcepted JSON token {reader.TokenType}");
|
||||
}
|
||||
}
|
||||
|
||||
if (id == -1)
|
||||
{
|
||||
throw new JsonException($"Required property {JSObjectReference.IdKey} not found.");
|
||||
}
|
||||
|
||||
return _jsObjectReferenceFactory(id);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, TJSObjectReference value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteNumber(JSObjectReference.IdKey, value.Id);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the type of result expected from a JS interop call.
|
||||
/// </summary>
|
||||
public enum JSCallResultType : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the returned value is not treated in a special way.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the returned value is to be treated as a JS object reference.
|
||||
/// </summary>
|
||||
JSObjectReference = 1,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// 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.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a reference to a JavaScript object whose functions can be invoked synchronously.
|
||||
/// </summary>
|
||||
public class JSInProcessObjectReference : JSObjectReference
|
||||
{
|
||||
private readonly JSInProcessRuntime _jsRuntime;
|
||||
|
||||
internal JSInProcessObjectReference(JSInProcessRuntime jsRuntime, long id) : base(jsRuntime, id)
|
||||
{
|
||||
_jsRuntime = jsRuntime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function synchronously.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
|
||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <c>"someScope.someFunction"</c> will invoke the function <c>someScope.someFunction</c> on the target instance.</param>
|
||||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
||||
[return: MaybeNull]
|
||||
public TValue Invoke<TValue>(string identifier, params object[] args)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
return _jsRuntime.Invoke<TValue>(identifier, Id, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using Microsoft.JSInterop.Infrastructure;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
|
|
@ -12,16 +13,23 @@ namespace Microsoft.JSInterop
|
|||
public abstract class JSInProcessRuntime : JSRuntime, IJSInProcessRuntime
|
||||
{
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function synchronously.
|
||||
/// Initializes a new instance of <see cref="JSInProcessRuntime"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
|
||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <c>"someScope.someFunction"</c> will invoke the function <c>window.someScope.someFunction</c>.</param>
|
||||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
||||
[return: MaybeNull]
|
||||
public TValue Invoke<TValue>(string identifier, params object?[]? args)
|
||||
protected JSInProcessRuntime()
|
||||
{
|
||||
var resultJson = InvokeJS(identifier, JsonSerializer.Serialize(args, JsonSerializerOptions));
|
||||
JsonSerializerOptions.Converters.Add(new JSObjectReferenceJsonConverter<JSInProcessObjectReference>(
|
||||
id => new JSInProcessObjectReference(this, id)));
|
||||
}
|
||||
|
||||
[return: MaybeNull]
|
||||
internal TValue Invoke<TValue>(string identifier, long targetInstanceId, params object?[]? args)
|
||||
{
|
||||
var resultJson = InvokeJS(
|
||||
identifier,
|
||||
JsonSerializer.Serialize(args, JsonSerializerOptions),
|
||||
ResultTypeFromGeneric<TValue>(),
|
||||
targetInstanceId);
|
||||
|
||||
if (resultJson is null)
|
||||
{
|
||||
return default;
|
||||
|
|
@ -30,12 +38,34 @@ namespace Microsoft.JSInterop
|
|||
return JsonSerializer.Deserialize<TValue>(resultJson, JsonSerializerOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function synchronously.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
|
||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <c>"someScope.someFunction"</c> will invoke the function <c>window.someScope.someFunction</c>.</param>
|
||||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
||||
[return: MaybeNull]
|
||||
public TValue Invoke<TValue>(string identifier, params object?[]? args)
|
||||
=> Invoke<TValue>(identifier, 0, args);
|
||||
|
||||
/// <summary>
|
||||
/// Performs a synchronous function invocation.
|
||||
/// </summary>
|
||||
/// <param name="identifier">The identifier for the function to invoke.</param>
|
||||
/// <param name="argsJson">A JSON representation of the arguments.</param>
|
||||
/// <returns>A JSON representation of the result.</returns>
|
||||
protected abstract string? InvokeJS(string identifier, string? argsJson);
|
||||
protected virtual string? InvokeJS(string identifier, string? argsJson)
|
||||
=> InvokeJS(identifier, argsJson, JSCallResultType.Default, 0);
|
||||
|
||||
/// <summary>
|
||||
/// Performs a synchronous function invocation.
|
||||
/// </summary>
|
||||
/// <param name="identifier">The identifier for the function to invoke.</param>
|
||||
/// <param name="argsJson">A JSON representation of the arguments.</param>
|
||||
/// <param name="resultType">The type of result expected from the invocation.</param>
|
||||
/// <param name="targetInstanceId">The instance ID of the target JS object.</param>
|
||||
/// <returns>A JSON representation of the result.</returns>
|
||||
protected abstract string? InvokeJS(string identifier, string? argsJson, JSCallResultType resultType, long targetInstanceId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
// 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;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a reference to a JavaScript object.
|
||||
/// </summary>
|
||||
public class JSObjectReference : IAsyncDisposable
|
||||
{
|
||||
internal static readonly JsonEncodedText IdKey = JsonEncodedText.Encode("__jsObjectId");
|
||||
|
||||
private readonly JSRuntime _jsRuntime;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
internal long Id { get; }
|
||||
|
||||
internal JSObjectReference(JSRuntime jsRuntime, long id)
|
||||
{
|
||||
_jsRuntime = jsRuntime;
|
||||
|
||||
Id = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <c>"someScope.someFunction"</c> will invoke the function <c>someScope.someFunction</c> on the target instance.</param>
|
||||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>A <see cref="ValueTask"/> that represents the asynchronous invocation operation.</returns>
|
||||
public async ValueTask InvokeVoidAsync(string identifier, params object[] args)
|
||||
{
|
||||
await InvokeAsync<object>(identifier, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function asynchronously.
|
||||
/// <para>
|
||||
/// <see cref="JSRuntime"/> will apply timeouts to this operation based on the value configured in <see cref="JSRuntime.DefaultAsyncTimeout"/>. To dispatch a call with a different, or no timeout,
|
||||
/// consider using <see cref="InvokeAsync{TValue}(string, CancellationToken, object[])" />.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
|
||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <c>"someScope.someFunction"</c> will invoke the function <c>someScope.someFunction</c> on the target instance.</param>
|
||||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
||||
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, params object[] args)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
return _jsRuntime.InvokeAsync<TValue>(Id, identifier, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function asynchronously.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
|
||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <c>"someScope.someFunction"</c> will invoke the function <c>someScope.someFunction</c> on the target instance.</param>
|
||||
/// <param name="cancellationToken">
|
||||
/// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts
|
||||
/// (<see cref="JSRuntime.DefaultAsyncTimeout"/>) from being applied.
|
||||
/// </param>
|
||||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
||||
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, params object[] args)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
return _jsRuntime.InvokeAsync<TValue>(Id, identifier, cancellationToken, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the <see cref="JSObjectReference"/>, freeing its resources and disabling it from further use.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="ValueTask"/> representing the completion of the operation.</returns>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
await _jsRuntime.InvokeVoidAsync("DotNet.jsCallDispatcher.disposeJSObjectReferenceById", Id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception if this instance has been disposed.
|
||||
/// </summary>
|
||||
protected void ThrowIfDisposed()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ namespace Microsoft.JSInterop
|
|||
Converters =
|
||||
{
|
||||
new DotNetObjectReferenceJsonConverterFactory(this),
|
||||
new JSObjectReferenceJsonConverter<JSObjectReference>(id => new JSObjectReference(this, id)),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -51,6 +52,17 @@ namespace Microsoft.JSInterop
|
|||
/// </summary>
|
||||
protected TimeSpan? DefaultAsyncTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="JSCallResultType"/> from the given generic type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">
|
||||
/// The type of the result of the relevant JS interop call.
|
||||
/// </typeparam>
|
||||
protected static JSCallResultType ResultTypeFromGeneric<TResult>()
|
||||
=> typeof(TResult) == typeof(JSObjectReference) || typeof(TResult) == typeof(JSInProcessObjectReference) ?
|
||||
JSCallResultType.JSObjectReference :
|
||||
JSCallResultType.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function asynchronously.
|
||||
/// <para>
|
||||
|
|
@ -62,17 +74,8 @@ namespace Microsoft.JSInterop
|
|||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <c>"someScope.someFunction"</c> will invoke the function <c>window.someScope.someFunction</c>.</param>
|
||||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
||||
public async ValueTask<TValue> InvokeAsync<TValue>(string identifier, object?[]? args)
|
||||
{
|
||||
if (DefaultAsyncTimeout.HasValue)
|
||||
{
|
||||
using var cts = new CancellationTokenSource(DefaultAsyncTimeout.Value);
|
||||
// We need to await here due to the using
|
||||
return await InvokeAsync<TValue>(identifier, cts.Token, args);
|
||||
}
|
||||
|
||||
return await InvokeAsync<TValue>(identifier, CancellationToken.None, args);
|
||||
}
|
||||
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object?[]? args)
|
||||
=> InvokeAsync<TValue>(0, identifier, args);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function asynchronously.
|
||||
|
|
@ -81,11 +84,30 @@ namespace Microsoft.JSInterop
|
|||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <c>"someScope.someFunction"</c> will invoke the function <c>window.someScope.someFunction</c>.</param>
|
||||
/// <param name="cancellationToken">
|
||||
/// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts
|
||||
/// (<see cref="JSRuntime.DefaultAsyncTimeout"/>) from being applied.
|
||||
/// (<see cref="DefaultAsyncTimeout"/>) from being applied.
|
||||
/// </param>
|
||||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
||||
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object?[]? args)
|
||||
=> InvokeAsync<TValue>(0, identifier, cancellationToken, args);
|
||||
|
||||
internal async ValueTask<TValue> InvokeAsync<TValue>(long targetInstanceId, string identifier, object?[]? args)
|
||||
{
|
||||
if (DefaultAsyncTimeout.HasValue)
|
||||
{
|
||||
using var cts = new CancellationTokenSource(DefaultAsyncTimeout.Value);
|
||||
// We need to await here due to the using
|
||||
return await InvokeAsync<TValue>(targetInstanceId, identifier, cts.Token, args);
|
||||
}
|
||||
|
||||
return await InvokeAsync<TValue>(targetInstanceId, identifier, CancellationToken.None, args);
|
||||
}
|
||||
|
||||
internal ValueTask<TValue> InvokeAsync<TValue>(
|
||||
long targetInstanceId,
|
||||
string identifier,
|
||||
CancellationToken cancellationToken,
|
||||
object?[]? args)
|
||||
{
|
||||
var taskId = Interlocked.Increment(ref _nextPendingTaskId);
|
||||
var tcs = new TaskCompletionSource<TValue>(TaskContinuationOptions.RunContinuationsAsynchronously);
|
||||
|
|
@ -112,7 +134,9 @@ namespace Microsoft.JSInterop
|
|||
var argsJson = args?.Any() == true ?
|
||||
JsonSerializer.Serialize(args, JsonSerializerOptions) :
|
||||
null;
|
||||
BeginInvokeJS(taskId, identifier, argsJson);
|
||||
var resultType = ResultTypeFromGeneric<TValue>();
|
||||
|
||||
BeginInvokeJS(taskId, identifier, argsJson, resultType, targetInstanceId);
|
||||
|
||||
return new ValueTask<TValue>(tcs.Task);
|
||||
}
|
||||
|
|
@ -138,7 +162,18 @@ namespace Microsoft.JSInterop
|
|||
/// <param name="taskId">The identifier for the function invocation, or zero if no async callback is required.</param>
|
||||
/// <param name="identifier">The identifier for the function to invoke.</param>
|
||||
/// <param name="argsJson">A JSON representation of the arguments.</param>
|
||||
protected abstract void BeginInvokeJS(long taskId, string identifier, string? argsJson);
|
||||
protected virtual void BeginInvokeJS(long taskId, string identifier, string? argsJson)
|
||||
=> BeginInvokeJS(taskId, identifier, argsJson, JSCallResultType.Default, 0);
|
||||
|
||||
/// <summary>
|
||||
/// Begins an asynchronous function invocation.
|
||||
/// </summary>
|
||||
/// <param name="taskId">The identifier for the function invocation, or zero if no async callback is required.</param>
|
||||
/// <param name="identifier">The identifier for the function to invoke.</param>
|
||||
/// <param name="argsJson">A JSON representation of the arguments.</param>
|
||||
/// <param name="resultType">The type of result expected from the invocation.</param>
|
||||
/// <param name="targetInstanceId">The instance ID of the target JS object.</param>
|
||||
protected abstract void BeginInvokeJS(long taskId, string identifier, string? argsJson, JSCallResultType resultType, long targetInstanceId);
|
||||
|
||||
/// <summary>
|
||||
/// Completes an async JS interop call from JavaScript to .NET
|
||||
|
|
|
|||
|
|
@ -885,7 +885,7 @@ namespace Microsoft.JSInterop.Infrastructure
|
|||
public string LastCompletionCallId { get; private set; }
|
||||
public DotNetInvocationResult LastCompletionResult { get; private set; }
|
||||
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson, JSCallResultType resultType, long targetInstanceId)
|
||||
{
|
||||
LastInvocationAsyncHandle = asyncHandle;
|
||||
LastInvocationIdentifier = identifier;
|
||||
|
|
@ -894,7 +894,7 @@ namespace Microsoft.JSInterop.Infrastructure
|
|||
_nextInvocationTcs = new TaskCompletionSource<object>();
|
||||
}
|
||||
|
||||
protected override string InvokeJS(string identifier, string argsJson)
|
||||
protected override string InvokeJS(string identifier, string argsJson, JSCallResultType resultType, long targetInstanceId)
|
||||
{
|
||||
LastInvocationAsyncHandle = default;
|
||||
LastInvocationIdentifier = identifier;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
// 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.Text.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop.Infrastructure
|
||||
{
|
||||
public class JSObjectReferenceJsonConverterTest
|
||||
{
|
||||
private readonly JSRuntime JSRuntime = new TestJSRuntime();
|
||||
private JsonSerializerOptions JsonSerializerOptions => JSRuntime.JsonSerializerOptions;
|
||||
|
||||
[Fact]
|
||||
public void Read_Throws_IfJsonIsMissingJSObjectIdProperty()
|
||||
{
|
||||
// Arrange
|
||||
var json = "{}";
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<JSObjectReference>(json, JsonSerializerOptions));
|
||||
Assert.Equal("Required property __jsObjectId not found.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Read_Throws_IfJsonContainsUnknownContent()
|
||||
{
|
||||
// Arrange
|
||||
var json = "{\"foo\":2}";
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<JSObjectReference>(json, JsonSerializerOptions));
|
||||
Assert.Equal("Unexcepted JSON property foo.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Read_Throws_IfJsonIsIncomplete()
|
||||
{
|
||||
// Arrange
|
||||
var json = $"{{\"__jsObjectId\":5";
|
||||
|
||||
// Act & Assert
|
||||
var ex = Record.Exception(() => JsonSerializer.Deserialize<JSObjectReference>(json, JsonSerializerOptions));
|
||||
Assert.IsAssignableFrom<JsonException>(ex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Read_Throws_IfJSObjectIdAppearsMultipleTimes()
|
||||
{
|
||||
// Arrange
|
||||
var json = $"{{\"__jsObjectId\":3,\"__jsObjectId\":7}}";
|
||||
|
||||
// Act & Assert
|
||||
var ex = Record.Exception(() => JsonSerializer.Deserialize<JSObjectReference>(json, JsonSerializerOptions));
|
||||
Assert.IsAssignableFrom<JsonException>(ex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Read_ReadsJson()
|
||||
{
|
||||
// Arrange
|
||||
var expectedId = 3;
|
||||
var json = $"{{\"__jsObjectId\":{expectedId}}}";
|
||||
|
||||
// Act
|
||||
var deserialized = JsonSerializer.Deserialize<JSObjectReference>(json, JsonSerializerOptions)!;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedId, deserialized?.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Write_WritesValidJson()
|
||||
{
|
||||
// Arrange
|
||||
var jsObjectRef = new JSObjectReference(JSRuntime, 7);
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(jsObjectRef, JsonSerializerOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Equal($"{{\"__jsObjectId\":{jsObjectRef.Id}}}", json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -99,7 +99,7 @@ namespace Microsoft.JSInterop
|
|||
|
||||
public string? NextResultJson { get; set; }
|
||||
|
||||
protected override string? InvokeJS(string identifier, string? argsJson)
|
||||
protected override string? InvokeJS(string identifier, string? argsJson, JSCallResultType resultType, long targetInstanceId)
|
||||
{
|
||||
InvokeCalls.Add(new InvokeArgs { Identifier = identifier, ArgsJson = argsJson });
|
||||
return NextResultJson;
|
||||
|
|
@ -111,7 +111,7 @@ namespace Microsoft.JSInterop
|
|||
public string? ArgsJson { get; set; }
|
||||
}
|
||||
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string? argsJson)
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string? argsJson, JSCallResultType resultType, long targetInstanceId)
|
||||
=> throw new NotImplementedException("This test only covers sync calls");
|
||||
|
||||
protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
// 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;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.JSInterop.Infrastructure;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop.Tests
|
||||
{
|
||||
public class JSObjectReferenceTest
|
||||
{
|
||||
[Fact]
|
||||
public void JSObjectReference_InvokeAsync_CallsUnderlyingJSRuntimeInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var jsObject = new JSObjectReference(jsRuntime, 0);
|
||||
|
||||
// Act
|
||||
_ = jsObject.InvokeAsync<object>("test", "arg1", "arg2");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, jsRuntime.BeginInvokeJSInvocationCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JSInProcessObjectReference_Invoke_CallsUnderlyingJSRuntimeInvoke()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSInProcessRuntime();
|
||||
var jsObject = new JSInProcessObjectReference(jsRuntime, 0);
|
||||
|
||||
// Act
|
||||
jsObject.Invoke<object>("test", "arg1", "arg2");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, jsRuntime.InvokeJSInvocationCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task JSObjectReference_Dispose_DisallowsFurtherInteropCalls()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var jsObject = new JSObjectReference(jsRuntime, 0);
|
||||
|
||||
// Act
|
||||
_ = jsObject.DisposeAsync();
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await jsObject.InvokeAsync<object>("test", "arg1", "arg2"));
|
||||
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await jsObject.InvokeAsync<object>("test", CancellationToken.None, "arg1", "arg2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JSInProcessObjectReference_Dispose_DisallowsFurtherInteropCalls()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSInProcessRuntime();
|
||||
var jsObject = new JSInProcessObjectReference(jsRuntime, 0);
|
||||
|
||||
// Act
|
||||
_ = jsObject.DisposeAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ObjectDisposedException>(() => jsObject.Invoke<object>("test", "arg1", "arg2"));
|
||||
}
|
||||
|
||||
class TestJSRuntime : JSRuntime
|
||||
{
|
||||
public int BeginInvokeJSInvocationCount { get; private set; }
|
||||
|
||||
protected override void BeginInvokeJS(long taskId, string identifier, string? argsJson, JSCallResultType resultType, long targetInstanceId)
|
||||
{
|
||||
BeginInvokeJSInvocationCount++;
|
||||
}
|
||||
|
||||
protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class TestJSInProcessRuntime : JSInProcessRuntime
|
||||
{
|
||||
public int InvokeJSInvocationCount { get; private set; }
|
||||
|
||||
protected override void BeginInvokeJS(long taskId, string identifier, string? argsJson, JSCallResultType resultType, long targetInstanceId)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string? InvokeJS(string identifier, string? argsJson, JSCallResultType resultType, long targetInstanceId)
|
||||
{
|
||||
InvokeJSInvocationCount++;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -377,7 +377,7 @@ namespace Microsoft.JSInterop
|
|||
});
|
||||
}
|
||||
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string? argsJson)
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string? argsJson, JSCallResultType resultType, long targetInstanceId)
|
||||
{
|
||||
BeginInvokeCalls.Add(new BeginInvokeAsyncArgs
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace Microsoft.JSInterop
|
|||
{
|
||||
internal class TestJSRuntime : JSRuntime
|
||||
{
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string? argsJson)
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string? argsJson, JSCallResultType resultType, long targetInstanceId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue