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
|
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}"
|
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
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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|x64.Build.0 = Release|Any CPU
|
||||||
{7509AA1E-3093-4BEE-984F-E11579E98A11}.Release|x86.ActiveCfg = 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
|
{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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -7934,6 +7948,7 @@ Global
|
||||||
{B06040BC-DA28-4923-8CAC-20EB517D471B} = {22D7D74B-565D-4047-97B4-F149B1A13350}
|
{B06040BC-DA28-4923-8CAC-20EB517D471B} = {22D7D74B-565D-4047-97B4-F149B1A13350}
|
||||||
{55CACC1F-FE96-47C8-8073-91F4CAA55C75} = {2A91479A-4ABE-4BB7-9A5E-CA3B9CCFC69E}
|
{55CACC1F-FE96-47C8-8073-91F4CAA55C75} = {2A91479A-4ABE-4BB7-9A5E-CA3B9CCFC69E}
|
||||||
{7509AA1E-3093-4BEE-984F-E11579E98A11} = {7CB09412-C9B0-47E8-A8C3-311AA4CFDE04}
|
{7509AA1E-3093-4BEE-984F-E11579E98A11} = {7CB09412-C9B0-47E8-A8C3-311AA4CFDE04}
|
||||||
|
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E} = {16898702-3E33-41C1-B8D8-4CE3F1D46BD9}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
|
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
JsonSerializer.Serialize(new[] { callId, success, resultOrError }, JsonSerializerOptions));
|
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)
|
if (_clientProxy is null)
|
||||||
{
|
{
|
||||||
|
|
@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
|
|
||||||
Log.BeginInvokeJS(_logger, asyncHandle, identifier);
|
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
|
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.AttachComponent', (componentId, selector) => attachRootComponentToLogicalElement(0, circuit.resolveElement(selector), componentId));
|
||||||
connection.on('JS.BeginInvokeJS', DotNet.jsCallDispatcher.beginInvokeJSFromDotNet);
|
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);
|
const renderQueue = RenderQueue.getOrCreate(logger);
|
||||||
connection.on('JS.RenderBatch', (batchId: number, batchData: Uint8Array) => {
|
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
|
// Configure environment for execution under Mono WebAssembly with shared-memory rendering
|
||||||
const platform = Environment.setPlatform(monoPlatform);
|
const platform = Environment.setPlatform(monoPlatform);
|
||||||
window['Blazor'].platform = platform;
|
window['Blazor'].platform = platform;
|
||||||
|
|
@ -84,6 +87,28 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
|
||||||
platform.callEntryPoint(resourceLoader.bootConfig.entryAssembly);
|
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;
|
window['Blazor'].start = boot;
|
||||||
if (shouldAutoStart()) {
|
if (shouldAutoStart()) {
|
||||||
boot().catch(error => {
|
boot().catch(error => {
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,7 @@ namespace WebAssembly.JSInterop
|
||||||
// in driver.c in the Mono distribution
|
// in driver.c in the Mono distribution
|
||||||
/// See: https://github.com/mono/mono/blob/90574987940959fe386008a850982ea18236a533/sdks/wasm/src/driver.c#L318-L319
|
/// 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)]
|
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||||
public static extern string InvokeJSMarshalled(out string exception, ref long asyncHandle, string functionIdentifier, string argsJson);
|
public static extern TRes InvokeJS<T0, T1, T2, TRes>(out string exception, ref JSCallInfo callInfo, [AllowNull] T0 arg0, [AllowNull] T1 arg1, [AllowNull] T2 arg2);
|
||||||
|
|
||||||
[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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
public abstract class WebAssemblyJSRuntime : JSInProcessRuntime, IJSUnmarshalledRuntime
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <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 callInfo = new JSCallInfo
|
||||||
var result = InternalCalls.InvokeJSMarshalled(out var exception, ref noAsyncHandle, identifier, argsJson);
|
{
|
||||||
|
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
|
return exception != null
|
||||||
? throw new JSException(exception)
|
? throw new JSException(exception)
|
||||||
: result;
|
: result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <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)
|
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
|
// 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)
|
// 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);
|
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 />
|
/// <inheritdoc />
|
||||||
|
|
@ -57,7 +75,14 @@ namespace Microsoft.JSInterop.WebAssembly
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
TResult IJSUnmarshalledRuntime.InvokeUnmarshalled<T0, T1, T2, TResult>(string identifier, T0 arg0, T1 arg1, T2 arg2)
|
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
|
return exception != null
|
||||||
? throw new JSException(exception)
|
? throw new JSException(exception)
|
||||||
: result;
|
: 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]",
|
["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]]",
|
["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}]",
|
["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!",
|
["AsyncThrowSyncException"] = @"""System.InvalidOperationException: Threw a sync exception!",
|
||||||
["AsyncThrowAsyncException"] = @"""System.InvalidOperationException: Threw an async exception!",
|
["AsyncThrowAsyncException"] = @"""System.InvalidOperationException: Threw an async exception!",
|
||||||
["SyncExceptionFromAsyncMethod"] = "Function threw a sync exception!",
|
["SyncExceptionFromAsyncMethod"] = "Function threw a sync exception!",
|
||||||
["AsyncExceptionFromAsyncMethod"] = "Function threw an async exception!",
|
["AsyncExceptionFromAsyncMethod"] = "Function threw an async exception!",
|
||||||
|
["JSObjectReferenceInvokeNonFunctionException"] = "The value 'nonFunction' is not a function.",
|
||||||
["resultReturnDotNetObjectByRefAsync"] = "1001",
|
["resultReturnDotNetObjectByRefAsync"] = "1001",
|
||||||
["instanceMethodThisTypeNameAsync"] = @"""JavaScriptInterop""",
|
["instanceMethodThisTypeNameAsync"] = @"""JavaScriptInterop""",
|
||||||
["instanceMethodStringValueUpperAsync"] = @"""MY STRING""",
|
["instanceMethodStringValueUpperAsync"] = @"""MY STRING""",
|
||||||
|
|
@ -69,6 +72,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
["testDtoAsync"] = "Same",
|
["testDtoAsync"] = "Same",
|
||||||
["returnPrimitiveAsync"] = "123",
|
["returnPrimitiveAsync"] = "123",
|
||||||
["returnArrayAsync"] = "first,second",
|
["returnArrayAsync"] = "first,second",
|
||||||
|
["jsObjectReference.identity"] = "Invoked from JSObjectReference",
|
||||||
|
["jsObjectReference.nested.add"] = "5",
|
||||||
|
["addViaJSObjectReference"] = "5",
|
||||||
|
["jsObjectReferenceModule"] = "Returned from module!",
|
||||||
["syncGenericInstanceMethod"] = @"""Initial value""",
|
["syncGenericInstanceMethod"] = @"""Initial value""",
|
||||||
["asyncGenericInstanceMethod"] = @"""Updated value 1""",
|
["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]",
|
["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]]",
|
["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}]",
|
["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!",
|
["ThrowException"] = @"""System.InvalidOperationException: Threw an exception!",
|
||||||
["ExceptionFromSyncMethod"] = "Function threw an exception!",
|
["ExceptionFromSyncMethod"] = "Function threw an exception!",
|
||||||
["resultReturnDotNetObjectByRefSync"] = "1000",
|
["resultReturnDotNetObjectByRefSync"] = "1000",
|
||||||
|
|
@ -100,6 +109,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
["instanceMethodStringValueUpper"] = @"""MY STRING""",
|
["instanceMethodStringValueUpper"] = @"""MY STRING""",
|
||||||
["instanceMethodIncomingByRef"] = "123",
|
["instanceMethodIncomingByRef"] = "123",
|
||||||
["instanceMethodOutgoingByRef"] = "1234",
|
["instanceMethodOutgoingByRef"] = "1234",
|
||||||
|
["jsInProcessObjectReference.identity"] = "Invoked from JSInProcessObjectReference",
|
||||||
["stringValueUpperSync"] = "MY STRING",
|
["stringValueUpperSync"] = "MY STRING",
|
||||||
["testDtoNonSerializedValueSync"] = "99999",
|
["testDtoNonSerializedValueSync"] = "99999",
|
||||||
["testDtoSync"] = "Same",
|
["testDtoSync"] = "Same",
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@
|
||||||
<p id="@nameof(SyncExceptionFromAsyncMethod)">@SyncExceptionFromAsyncMethod?.Message</p>
|
<p id="@nameof(SyncExceptionFromAsyncMethod)">@SyncExceptionFromAsyncMethod?.Message</p>
|
||||||
<h2>@nameof(AsyncExceptionFromAsyncMethod)</h2>
|
<h2>@nameof(AsyncExceptionFromAsyncMethod)</h2>
|
||||||
<p id="@nameof(AsyncExceptionFromAsyncMethod)">@AsyncExceptionFromAsyncMethod?.Message</p>
|
<p id="@nameof(AsyncExceptionFromAsyncMethod)">@AsyncExceptionFromAsyncMethod?.Message</p>
|
||||||
|
<h2>@nameof(JSObjectReferenceInvokeNonFunctionException)</h2>
|
||||||
|
<p id="@nameof(JSObjectReferenceInvokeNonFunctionException)">@JSObjectReferenceInvokeNonFunctionException?.Message</p>
|
||||||
</div>
|
</div>
|
||||||
@if (DoneWithInterop)
|
@if (DoneWithInterop)
|
||||||
{
|
{
|
||||||
|
|
@ -59,6 +61,7 @@
|
||||||
public JSException ExceptionFromSyncMethod { get; set; }
|
public JSException ExceptionFromSyncMethod { get; set; }
|
||||||
public JSException SyncExceptionFromAsyncMethod { get; set; }
|
public JSException SyncExceptionFromAsyncMethod { get; set; }
|
||||||
public JSException AsyncExceptionFromAsyncMethod { 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> ReceiveDotNetObjectByRefResult { get; set; } = new Dictionary<string, object>();
|
||||||
public IDictionary<string, object> ReceiveDotNetObjectByRefAsyncResult { 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());
|
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;
|
Invocations = invocations;
|
||||||
DoneWithInterop = true;
|
DoneWithInterop = true;
|
||||||
}
|
}
|
||||||
|
|
@ -163,6 +188,14 @@
|
||||||
ReceiveDotNetObjectByRefResult["testDto"] = result.TestDto.Value == passDotNetObjectByRef ? "Same" : "Different";
|
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 class PassDotNetObjectByRefArgs
|
||||||
{
|
{
|
||||||
public string StringValue { get; set; }
|
public string StringValue { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -414,6 +414,47 @@ namespace BasicTestApp.InteropTest
|
||||||
return objectByRef.Value.GetNonSerializedValue();
|
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]
|
[JSInvokable]
|
||||||
public InstanceMethodOutput InstanceMethod(InstanceMethodInput input)
|
public InstanceMethodOutput InstanceMethod(InstanceMethodInput input)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,17 @@ async function invokeDotNetInteropMethodsAsync(shouldSupportSyncInterop, dotNetO
|
||||||
var returnDotNetObjectByRefResult = DotNet.invokeMethod(assemblyName, 'ReturnDotNetObjectByRef');
|
var returnDotNetObjectByRefResult = DotNet.invokeMethod(assemblyName, 'ReturnDotNetObjectByRef');
|
||||||
results['resultReturnDotNetObjectByRefSync'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', returnDotNetObjectByRefResult['Some sync instance']);
|
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', {
|
var instanceMethodResult = instanceMethodsTarget.invokeMethod('InstanceMethod', {
|
||||||
stringValue: 'My string',
|
stringValue: 'My string',
|
||||||
dtoByRef: dotNetObjectByRef
|
dtoByRef: dotNetObjectByRef
|
||||||
|
|
@ -66,6 +77,17 @@ async function invokeDotNetInteropMethodsAsync(shouldSupportSyncInterop, dotNetO
|
||||||
const returnDotNetObjectByRefAsync = await DotNet.invokeMethodAsync(assemblyName, 'ReturnDotNetObjectByRefAsync');
|
const returnDotNetObjectByRefAsync = await DotNet.invokeMethodAsync(assemblyName, 'ReturnDotNetObjectByRefAsync');
|
||||||
results['resultReturnDotNetObjectByRefAsync'] = await DotNet.invokeMethodAsync(assemblyName, 'ExtractNonSerializedValue', returnDotNetObjectByRefAsync['Some async instance']);
|
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', {
|
const instanceMethodAsync = await instanceMethodsTarget.invokeMethodAsync('InstanceMethodAsync', {
|
||||||
stringValue: 'My string',
|
stringValue: 'My string',
|
||||||
dtoByRef: dotNetObjectByRef
|
dtoByRef: dotNetObjectByRef
|
||||||
|
|
@ -167,6 +189,8 @@ window.jsInteropTests = {
|
||||||
asyncFunctionThrowsAsyncException: asyncFunctionThrowsAsyncException,
|
asyncFunctionThrowsAsyncException: asyncFunctionThrowsAsyncException,
|
||||||
returnPrimitive: returnPrimitive,
|
returnPrimitive: returnPrimitive,
|
||||||
returnPrimitiveAsync: returnPrimitiveAsync,
|
returnPrimitiveAsync: returnPrimitiveAsync,
|
||||||
|
returnJSObjectReference: returnJSObjectReference,
|
||||||
|
addViaJSObjectReference: addViaJSObjectReference,
|
||||||
receiveDotNetObjectByRef: receiveDotNetObjectByRef,
|
receiveDotNetObjectByRef: receiveDotNetObjectByRef,
|
||||||
receiveDotNetObjectByRefAsync: receiveDotNetObjectByRefAsync
|
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() {
|
function functionThrowsException() {
|
||||||
throw new Error('Function threw an exception!');
|
throw new Error('Function threw an exception!');
|
||||||
}
|
}
|
||||||
|
|
@ -258,4 +303,4 @@ function receiveDotNetObjectByRefAsync(incomingData) {
|
||||||
testDto: testDto
|
testDto: testDto
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
export type JsonReviver = ((key: any, value: any) => any);
|
||||||
const jsonRevivers: JsonReviver[] = [];
|
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 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 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;
|
let dotNetDispatcher: DotNetCallDispatcher | null = null;
|
||||||
|
|
||||||
|
|
@ -55,6 +114,58 @@ export module DotNet {
|
||||||
return invokePossibleInstanceMethodAsync(assemblyName, methodIdentifier, null, args);
|
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 {
|
function invokePossibleInstanceMethod<T>(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): T {
|
||||||
const dispatcher = getRequiredDispatcher();
|
const dispatcher = getRequiredDispatcher();
|
||||||
if (dispatcher.invokeDotNetFromJS) {
|
if (dispatcher.invokeDotNetFromJS) {
|
||||||
|
|
@ -114,6 +225,14 @@ export module DotNet {
|
||||||
reject: (reason?: any) => void;
|
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.
|
* 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.
|
* Finds the JavaScript function matching the specified identifier.
|
||||||
*
|
*
|
||||||
* @param identifier Identifies the globally-reachable function to be returned.
|
* @param identifier Identifies the globally-reachable function to be returned.
|
||||||
|
* @param targetInstanceId The instance ID of the target JS object.
|
||||||
* @returns A Function instance.
|
* @returns A Function instance.
|
||||||
*/
|
*/
|
||||||
findJSFunction, // Note that this is used by the JS interop code inside Mono WebAssembly itself
|
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.
|
* Invokes the specified synchronous JavaScript function.
|
||||||
*
|
*
|
||||||
* @param identifier Identifies the globally-reachable function to invoke.
|
* @param identifier Identifies the globally-reachable function to invoke.
|
||||||
* @param argsJson JSON representation of arguments to be passed to the function.
|
* @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.
|
* @returns JSON representation of the invocation result.
|
||||||
*/
|
*/
|
||||||
invokeJSFromDotNet: (identifier: string, argsJson: string) => {
|
invokeJSFromDotNet: (identifier: string, argsJson: string, resultType: JSCallResultType, targetInstanceId: number) => {
|
||||||
const result = findJSFunction(identifier).apply(null, parseJsonWithRevivers(argsJson));
|
const returnValue = findJSFunction(identifier, targetInstanceId).apply(null, parseJsonWithRevivers(argsJson));
|
||||||
|
const result = createJSCallResult(returnValue, resultType);
|
||||||
|
|
||||||
return result === null || result === undefined
|
return result === null || result === undefined
|
||||||
? null
|
? null
|
||||||
: JSON.stringify(result, argReplacer);
|
: 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 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 identifier Identifies the globally-reachable function to invoke.
|
||||||
* @param argsJson JSON representation of arguments to be passed to the function.
|
* @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
|
// Coerce synchronous functions into async ones, plus treat
|
||||||
// synchronous exceptions the same as async ones
|
// synchronous exceptions the same as async ones
|
||||||
const promise = new Promise<any>(resolve => {
|
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);
|
resolve(synchronousResultOrPromise);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -196,7 +329,7 @@ export module DotNet {
|
||||||
// On completion, dispatch result back to .NET
|
// On completion, dispatch result back to .NET
|
||||||
// Not using "await" because it codegens a lot of boilerplate
|
// Not using "await" because it codegens a lot of boilerplate
|
||||||
promise.then(
|
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)]))
|
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 {
|
function formatError(error: any): string {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
return `${error.message}\n${error.stack}`;
|
return `${error.message}\n${error.stack}`;
|
||||||
|
|
@ -233,33 +355,20 @@ export module DotNet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findJSFunction(identifier: string): Function {
|
function findJSFunction(identifier: string, targetInstanceId: number): Function {
|
||||||
if (Object.prototype.hasOwnProperty.call(cachedJSFunctions, identifier)) {
|
let targetInstance = cachedJSObjectsById[targetInstanceId];
|
||||||
return cachedJSFunctions[identifier];
|
|
||||||
}
|
|
||||||
|
|
||||||
let result: any = window;
|
if (targetInstance) {
|
||||||
let resultIdentifier = 'window';
|
return targetInstance.findFunction(identifier);
|
||||||
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 {
|
} else {
|
||||||
throw new Error(`The value '${resultIdentifier}' is not a function.`);
|
throw new Error(`JS object instance with ID ${targetInstanceId} does not exist (has it been disposed?).`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function disposeJSObjectReferenceById(id: number) {
|
||||||
|
delete cachedJSObjectsById[id];
|
||||||
|
}
|
||||||
|
|
||||||
class DotNetObject {
|
class DotNetObject {
|
||||||
constructor(private _id: number) {
|
constructor(private _id: number) {
|
||||||
}
|
}
|
||||||
|
|
@ -292,6 +401,33 @@ export module DotNet {
|
||||||
return value;
|
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) {
|
function argReplacer(key: string, value: any) {
|
||||||
return value instanceof DotNetObject ? value.serializeAsArg() : value;
|
return value instanceof DotNetObject ? value.serializeAsArg() : value;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@
|
||||||
"lib": ["es2015", "dom", "es2015.promise"],
|
"lib": ["es2015", "dom", "es2015.promise"],
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"outDir": "dist"
|
"outDir": "dist",
|
||||||
|
"module": "ESNext",
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts"
|
"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.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using Microsoft.JSInterop.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
|
|
@ -12,16 +13,23 @@ namespace Microsoft.JSInterop
|
||||||
public abstract class JSInProcessRuntime : JSRuntime, IJSInProcessRuntime
|
public abstract class JSInProcessRuntime : JSRuntime, IJSInProcessRuntime
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invokes the specified JavaScript function synchronously.
|
/// Initializes a new instance of <see cref="JSInProcessRuntime"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
|
protected JSInProcessRuntime()
|
||||||
/// <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)
|
|
||||||
{
|
{
|
||||||
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)
|
if (resultJson is null)
|
||||||
{
|
{
|
||||||
return default;
|
return default;
|
||||||
|
|
@ -30,12 +38,34 @@ namespace Microsoft.JSInterop
|
||||||
return JsonSerializer.Deserialize<TValue>(resultJson, JsonSerializerOptions);
|
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>
|
/// <summary>
|
||||||
/// Performs a synchronous function invocation.
|
/// Performs a synchronous function invocation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="identifier">The identifier for the function to invoke.</param>
|
/// <param name="identifier">The identifier for the function to invoke.</param>
|
||||||
/// <param name="argsJson">A JSON representation of the arguments.</param>
|
/// <param name="argsJson">A JSON representation of the arguments.</param>
|
||||||
/// <returns>A JSON representation of the result.</returns>
|
/// <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 =
|
Converters =
|
||||||
{
|
{
|
||||||
new DotNetObjectReferenceJsonConverterFactory(this),
|
new DotNetObjectReferenceJsonConverterFactory(this),
|
||||||
|
new JSObjectReferenceJsonConverter<JSObjectReference>(id => new JSObjectReference(this, id)),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -51,6 +52,17 @@ namespace Microsoft.JSInterop
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected TimeSpan? DefaultAsyncTimeout { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Invokes the specified JavaScript function asynchronously.
|
/// Invokes the specified JavaScript function asynchronously.
|
||||||
/// <para>
|
/// <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="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>
|
/// <param name="args">JSON-serializable arguments.</param>
|
||||||
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
/// <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)
|
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object?[]? args)
|
||||||
{
|
=> InvokeAsync<TValue>(0, identifier, 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invokes the specified JavaScript function asynchronously.
|
/// 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="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">
|
/// <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
|
/// 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>
|
||||||
/// <param name="args">JSON-serializable arguments.</param>
|
/// <param name="args">JSON-serializable arguments.</param>
|
||||||
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
/// <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)
|
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 taskId = Interlocked.Increment(ref _nextPendingTaskId);
|
||||||
var tcs = new TaskCompletionSource<TValue>(TaskContinuationOptions.RunContinuationsAsynchronously);
|
var tcs = new TaskCompletionSource<TValue>(TaskContinuationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
@ -112,7 +134,9 @@ namespace Microsoft.JSInterop
|
||||||
var argsJson = args?.Any() == true ?
|
var argsJson = args?.Any() == true ?
|
||||||
JsonSerializer.Serialize(args, JsonSerializerOptions) :
|
JsonSerializer.Serialize(args, JsonSerializerOptions) :
|
||||||
null;
|
null;
|
||||||
BeginInvokeJS(taskId, identifier, argsJson);
|
var resultType = ResultTypeFromGeneric<TValue>();
|
||||||
|
|
||||||
|
BeginInvokeJS(taskId, identifier, argsJson, resultType, targetInstanceId);
|
||||||
|
|
||||||
return new ValueTask<TValue>(tcs.Task);
|
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="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="identifier">The identifier for the function to invoke.</param>
|
||||||
/// <param name="argsJson">A JSON representation of the arguments.</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>
|
/// <summary>
|
||||||
/// Completes an async JS interop call from JavaScript to .NET
|
/// 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 string LastCompletionCallId { get; private set; }
|
||||||
public DotNetInvocationResult LastCompletionResult { 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;
|
LastInvocationAsyncHandle = asyncHandle;
|
||||||
LastInvocationIdentifier = identifier;
|
LastInvocationIdentifier = identifier;
|
||||||
|
|
@ -894,7 +894,7 @@ namespace Microsoft.JSInterop.Infrastructure
|
||||||
_nextInvocationTcs = new TaskCompletionSource<object>();
|
_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;
|
LastInvocationAsyncHandle = default;
|
||||||
LastInvocationIdentifier = identifier;
|
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; }
|
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 });
|
InvokeCalls.Add(new InvokeArgs { Identifier = identifier, ArgsJson = argsJson });
|
||||||
return NextResultJson;
|
return NextResultJson;
|
||||||
|
|
@ -111,7 +111,7 @@ namespace Microsoft.JSInterop
|
||||||
public string? ArgsJson { get; set; }
|
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");
|
=> throw new NotImplementedException("This test only covers sync calls");
|
||||||
|
|
||||||
protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult)
|
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
|
BeginInvokeCalls.Add(new BeginInvokeAsyncArgs
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
internal class TestJSRuntime : JSRuntime
|
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();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue