From df3822fab27a7553fbf42dd7c00592b235e45374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Tue, 18 Mar 2025 16:16:23 +0100 Subject: [PATCH 01/25] WIP implementation with JSInvocationInfo --- .../UnsupportedJavaScriptRuntime.cs | 19 +++ .../Samples/BlazorServerApp/Pages/Index.razor | 121 ++++++++++++++- .../BlazorServerApp/Pages/_Layout.cshtml | 1 + .../BlazorServerApp/wwwroot/custom-script.js | 55 +++++++ .../Server/src/Circuits/RemoteJSRuntime.cs | 8 +- .../test/ProtectedBrowserStorageTest.cs | 42 +++++- .../JSInterop/src/WebAssemblyJSRuntime.cs | 5 + .../test/RemoteAuthenticationServiceTests.cs | 31 ++++ .../test/RemoteAuthenticatorCoreTests.cs | 42 ++++++ .../test/PullFromJSDataStreamTest.cs | 38 ++++- .../WebView/src/Services/WebViewJSRuntime.cs | 6 + .../src/src/Microsoft.JSInterop.ts | 138 +++++++++++++++++- .../src/IJSObjectReference.cs | 20 +++ .../Microsoft.JSInterop/src/IJSRuntime.cs | 12 ++ .../src/Implementation/JSObjectReference.cs | 69 ++++++++- .../src/Infrastructure/JSCallType.cs | 12 ++ .../src/Infrastructure/JSInvocationInfo.cs | 53 +++++++ .../Microsoft.JSInterop/src/JSRuntime.cs | 65 ++++++--- src/submodules/googletest | 2 +- 19 files changed, 693 insertions(+), 46 deletions(-) create mode 100644 src/Components/Samples/BlazorServerApp/wwwroot/custom-script.js create mode 100644 src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSCallType.cs create mode 100644 src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSInvocationInfo.cs diff --git a/src/Components/Endpoints/src/DependencyInjection/UnsupportedJavaScriptRuntime.cs b/src/Components/Endpoints/src/DependencyInjection/UnsupportedJavaScriptRuntime.cs index 3c7af336b23a..456f47a2b70c 100644 --- a/src/Components/Endpoints/src/DependencyInjection/UnsupportedJavaScriptRuntime.cs +++ b/src/Components/Endpoints/src/DependencyInjection/UnsupportedJavaScriptRuntime.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using Microsoft.JSInterop; namespace Microsoft.AspNetCore.Components.Endpoints; @@ -14,4 +15,22 @@ public ValueTask InvokeAsync(string identifier, CancellationToke ValueTask IJSRuntime.InvokeAsync(string identifier, object?[]? args) => throw new InvalidOperationException(Message); + + public ValueTask InvokeNewAsync(string identifier, object?[]? args) + => throw new InvalidOperationException(Message); + + public ValueTask InvokeNewAsync(string identifier, CancellationToken cancellationToken, object?[]? args) + => throw new InvalidOperationException(Message); + + public ValueTask GetValueAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier) + => throw new InvalidOperationException(Message); + + public ValueTask GetValueAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, CancellationToken cancellationToken) + => throw new InvalidOperationException(Message); + + public ValueTask SetValueAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, TValue value) + => throw new InvalidOperationException(Message); + + public ValueTask SetValueAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, TValue value, CancellationToken cancellationToken) + => throw new InvalidOperationException(Message); } diff --git a/src/Components/Samples/BlazorServerApp/Pages/Index.razor b/src/Components/Samples/BlazorServerApp/Pages/Index.razor index 7b5a15e0e22b..c145cc3c44cd 100644 --- a/src/Components/Samples/BlazorServerApp/Pages/Index.razor +++ b/src/Components/Samples/BlazorServerApp/Pages/Index.razor @@ -1,7 +1,126 @@ -@page "/" +@inject IJSRuntime JSRuntime +@page "/" Index

Hello, world!

Welcome to your new app. + +
+ +
+ +
+ Message: + +
+ +
+ Title: + + +
+ +
+ + @CurrentTitle +
+ +
+ @TestObjectDisplay
+ + + + +
+ +
+ + + @AnimalMessage +
+ +@code { + private string? Message { get; set; } + private string? CurrentTitle { get; set; } + private string? NewTitle { get; set; } + private string? TestObjectDisplay { get; set; } + private string? AnimalMessage { get; set; } + + private async Task LogDefault() + { + await JSRuntime.InvokeVoidAsync("logDefault"); + } + + private async Task LogMessage(string message) + { + await JSRuntime.InvokeVoidAsync("logMessage", message); + await JSRuntime.InvokeVoidAsync("console.log", $"Console: {message}"); + } + + private async Task SetDocumentTitle(string title) + { + await JSRuntime.InvokeVoidAsync("setDocumentTitle", title); + } + + private async Task SetDocumentTitleDirectly(string title) + { + await JSRuntime.SetValueAsync("document.title", title); + } + + private async Task GetDocumentTitle() + { + CurrentTitle = await JSRuntime.GetValueAsync("document.title"); + } + + private async Task GetTestObjectState() + { + var model = await JSRuntime.InvokeAsync("getTestObject"); + TestObjectDisplay = $"Serialized state: {model.Num} | {model.Text} | {model.GetOnlyProperty}"; + } + + private async Task GetTestObjectStateViaReference() + { + var objectRef = await JSRuntime.GetValueAsync("testObject"); + var model = await objectRef.GetValueAsync(); + TestObjectDisplay = $"Serialized state via reference: {model.Num} | {model.Text} | {model.GetOnlyProperty}"; + } + + private async Task GetTestObjectStateViaReferenceFromFunction() + { + var objectRef = await JSRuntime.InvokeAsync("getTestObject"); + var numValue = await objectRef.GetValueAsync("num"); + var textValue = await objectRef.GetValueAsync("text"); + var getOnlyProperty = await objectRef.GetValueAsync("getOnlyProperty"); + TestObjectDisplay = $"State via reference from function: {numValue} | {textValue} | {getOnlyProperty}"; + } + + private async Task GetTestObjectStateViaReferenceFromProperty() + { + var objectRef = await JSRuntime.GetValueAsync("testObject"); + var numValue = await objectRef.GetValueAsync("num"); + var textValue = await objectRef.GetValueAsync("text"); + var getOnlyProperty = await objectRef.GetValueAsync("getOnlyProperty"); + TestObjectDisplay = $"State via reference from property: {numValue} | {textValue} | {getOnlyProperty}"; + } + + private async Task CreateDog() + { + var dogRef = await JSRuntime.InvokeNewAsync("Dog", ["Igor"]); + AnimalMessage = await dogRef.InvokeAsync("bark"); + } + + private async Task CreateCat() + { + var catRef = await JSRuntime.InvokeNewAsync("Cat", ["Mikeš"]); + AnimalMessage = await catRef.InvokeAsync("meow"); + } + + class TestObjectModel + { + public int Num { get; set; } + public string? Text { get; set; } + public int GetOnlyProperty { get; set; } + } +} diff --git a/src/Components/Samples/BlazorServerApp/Pages/_Layout.cshtml b/src/Components/Samples/BlazorServerApp/Pages/_Layout.cshtml index 91499422deb9..dcf6423af39c 100644 --- a/src/Components/Samples/BlazorServerApp/Pages/_Layout.cshtml +++ b/src/Components/Samples/BlazorServerApp/Pages/_Layout.cshtml @@ -23,5 +23,6 @@ configureSignalR: builder => builder.configureLogging("debug") // LogLevel.Debug }); + diff --git a/src/Components/Samples/BlazorServerApp/wwwroot/custom-script.js b/src/Components/Samples/BlazorServerApp/wwwroot/custom-script.js new file mode 100644 index 000000000000..7907e5e0c559 --- /dev/null +++ b/src/Components/Samples/BlazorServerApp/wwwroot/custom-script.js @@ -0,0 +1,55 @@ +window.getDocumentTitle = function () { + return document.title; +} + +window.setDocumentTitle = function (title) { + document.title = title; +}; + +window.logDefault = function () { + console.log("This is a default log message"); +} + +window.logMessage = function (message) { + console.log(message); +} + +window.testObject = { + num: 10, + text: "Hello World", + log: function () { + console.log(this.text); + }, + get getOnlyProperty() { + return this.num; + }, + set setOnlyProperty(value) { + this.num = value; + }, +} + +window.getTestObject = function () { + return window.testObject; +} + +window.Cat = class { + constructor(name) { + this.name = name; + } + + meow() { + const text = `${this.name} says Meow!`; + console.log(text); + return text; + } +} + +window.Dog = function (name) { + this.name = name; +} + +window.Dog.prototype.bark = function () { + const text = `${this.name} says Woof!`; + console.log(text); + return text; +} diff --git a/src/Components/Server/src/Circuits/RemoteJSRuntime.cs b/src/Components/Server/src/Circuits/RemoteJSRuntime.cs index 4ced7924b291..9b703da30ffb 100644 --- a/src/Components/Server/src/Circuits/RemoteJSRuntime.cs +++ b/src/Components/Server/src/Circuits/RemoteJSRuntime.cs @@ -104,7 +104,7 @@ protected override void SendByteArray(int id, byte[] data) _clientProxy.SendAsync("JS.ReceiveByteArray", id, data); } - protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson, JSCallResultType resultType, long targetInstanceId) + protected override void BeginInvokeJS(JSInvocationInfo invocationInfo) { if (_clientProxy is null) { @@ -123,9 +123,11 @@ protected override void BeginInvokeJS(long asyncHandle, string identifier, strin } } - Log.BeginInvokeJS(_logger, asyncHandle, identifier); + var invocationInfoJson = invocationInfo.ToJson(); - _clientProxy.SendAsync("JS.BeginInvokeJS", asyncHandle, identifier, argsJson, (int)resultType, targetInstanceId); + Log.BeginInvokeJS(_logger, invocationInfo.AsyncHandle ?? -1, invocationInfo.Identifier); + + _clientProxy.SendAsync("JS.BeginInvokeJS", invocationInfoJson); } protected override void ReceiveByteArray(int id, byte[] data) diff --git a/src/Components/Server/test/ProtectedBrowserStorageTest.cs b/src/Components/Server/test/ProtectedBrowserStorageTest.cs index 89da1b351874..47bde59295a5 100644 --- a/src/Components/Server/test/ProtectedBrowserStorageTest.cs +++ b/src/Components/Server/test/ProtectedBrowserStorageTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; using System.Text; using System.Text.Json; @@ -352,19 +353,54 @@ private static string ProtectionPrefix(string purpose) class TestJSRuntime : IJSRuntime { - public List<(string Identifier, object[] Args)> Invocations { get; } - = new List<(string Identifier, object[] Args)>(); + public List<(string Identifier, object[] Args, JSCallType CallType)> Invocations { get; } = []; public object NextInvocationResult { get; set; } public ValueTask InvokeAsync(string identifier, CancellationToken cancellationToken, object[] args) { - Invocations.Add((identifier, args)); + Invocations.Add((identifier, args, JSCallType.FunctionCall)); return (ValueTask)NextInvocationResult; } public ValueTask InvokeAsync(string identifier, object[] args) => InvokeAsync(identifier, cancellationToken: CancellationToken.None, args: args); + + public ValueTask InvokeNewAsync(string identifier, object[] args) + { + Invocations.Add((identifier, args, JSCallType.NewCall)); + return (ValueTask)NextInvocationResult; + } + + public ValueTask InvokeNewAsync(string identifier, CancellationToken cancellationToken, object[] args) + { + Invocations.Add((identifier, args, JSCallType.NewCall)); + return (ValueTask)NextInvocationResult; + } + + public ValueTask GetValueAsync(string identifier) + { + Invocations.Add((identifier, [], JSCallType.GetValue)); + return (ValueTask)NextInvocationResult; + } + + public ValueTask GetValueAsync(string identifier, CancellationToken cancellationToken) + { + Invocations.Add((identifier, [], JSCallType.GetValue)); + return (ValueTask)NextInvocationResult; + } + + public ValueTask SetValueAsync(string identifier, TValue value) + { + Invocations.Add((identifier, [value], JSCallType.SetValue)); + return ValueTask.CompletedTask; + } + + public ValueTask SetValueAsync(string identifier, TValue value, CancellationToken cancellationToken) + { + Invocations.Add((identifier, [value], JSCallType.SetValue)); + return ValueTask.CompletedTask; + } } class TestProtectedBrowserStorage : ProtectedBrowserStorage diff --git a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs index 21f62b89f369..6415dc002112 100644 --- a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs +++ b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs @@ -41,6 +41,11 @@ protected override void BeginInvokeJS(long asyncHandle, string identifier, [Stri InternalCalls.InvokeJSJson(identifier, targetInstanceId, (int)resultType, argsJson ?? "[]", asyncHandle); } + protected override void BeginInvokeJSX(long taskId, string identifier, [StringSyntax("Json")] string? argsJson, JSCallType callType, JSCallResultType resultType, long targetInstanceId) + { + throw new NotImplementedException(); + } + /// [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "TODO: This should be in the xml suppressions file, but can't be because https://github.com/mono/linker/issues/2006")] protected override void EndInvokeDotNet(DotNetInvocationInfo callInfo, in DotNetInvocationResult dispatchResult) diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticationServiceTests.cs b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticationServiceTests.cs index 79f3bb98b680..1c920e7bda95 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticationServiceTests.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticationServiceTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using System.Security.Claims; using System.Text.Json; using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; @@ -543,6 +544,36 @@ public ValueTask InvokeAsync(string identifier, CancellationToke return new ValueTask((TValue)GetInvocationResult(identifier)); } + public ValueTask GetValueAsync(string identifier) + { + throw new NotImplementedException(); + } + + public ValueTask GetValueAsync(string identifier, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public ValueTask InvokeNewAsync(string identifier, object[] args) + { + throw new NotImplementedException(); + } + + public ValueTask InvokeNewAsync(string identifier, CancellationToken cancellationToken, object[] args) + { + throw new NotImplementedException(); + } + + public ValueTask SetValueAsync(string identifier, TValue value) + { + throw new NotImplementedException(); + } + + public ValueTask SetValueAsync(string identifier, TValue value, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + private object GetInvocationResult(string identifier) { switch (identifier) diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs index 68ce23ac5ffe..82b6945d739c 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using System.Runtime.ExceptionServices; using System.Security.Claims; using Microsoft.AspNetCore.Components.Authorization; @@ -777,6 +778,17 @@ public override ValueTask SetSignOutState() private class TestJsRuntime : IJSRuntime { public (string identifier, object[] args) LastInvocation { get; set; } + + public ValueTask GetValueAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier) + { + throw new NotImplementedException(); + } + + public ValueTask GetValueAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + public ValueTask InvokeAsync(string identifier, object[] args) { LastInvocation = (identifier, args); @@ -788,6 +800,36 @@ public ValueTask InvokeAsync(string identifier, CancellationToke LastInvocation = (identifier, args); return default; } + + public ValueTask InvokeAsyncX<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, object[] args) + { + throw new NotImplementedException(); + } + + public ValueTask InvokeAsyncX<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, CancellationToken cancellationToken, object[] args) + { + throw new NotImplementedException(); + } + + public ValueTask InvokeNewAsync(string identifier, object[] args) + { + throw new NotImplementedException(); + } + + public ValueTask InvokeNewAsync(string identifier, CancellationToken cancellationToken, object[] args) + { + throw new NotImplementedException(); + } + + public ValueTask SetValueAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, TValue value) + { + throw new NotImplementedException(); + } + + public ValueTask SetValueAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, TValue value, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } } public class TestRemoteAuthenticatorView : RemoteAuthenticatorViewCore diff --git a/src/Components/WebAssembly/WebAssembly/test/PullFromJSDataStreamTest.cs b/src/Components/WebAssembly/WebAssembly/test/PullFromJSDataStreamTest.cs index 59eafab522be..76c798810985 100644 --- a/src/Components/WebAssembly/WebAssembly/test/PullFromJSDataStreamTest.cs +++ b/src/Components/WebAssembly/WebAssembly/test/PullFromJSDataStreamTest.cs @@ -118,7 +118,7 @@ public TestJSRuntime(byte[] data = default) _data = data; } - public virtual ValueTask InvokeAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, CancellationToken cancellationToken, object[] args) + public virtual ValueTask InvokeAsync(string identifier, CancellationToken cancellationToken, object[] args) { Assert.Equal("Blazor._internal.getJSDataStreamChunk", identifier); if (typeof(TValue) != typeof(byte[])) @@ -130,10 +130,40 @@ public TestJSRuntime(byte[] data = default) return ValueTask.FromResult((TValue)(object)_data.Skip((int)offset).Take(bytesToRead).ToArray()); } - public async ValueTask InvokeAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, object[] args) + public async ValueTask InvokeAsync(string identifier, object[] args) { return await InvokeAsync(identifier, CancellationToken.None, args); } + + public ValueTask InvokeNewAsync(string identifier, object[] args) + { + throw new NotImplementedException(); + } + + public ValueTask InvokeNewAsync(string identifier, CancellationToken cancellationToken, object[] args) + { + throw new NotImplementedException(); + } + + public ValueTask GetValueAsync(string identifier) + { + throw new NotImplementedException(); + } + + public ValueTask GetValueAsync(string identifier, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public ValueTask SetValueAsync(string identifier, TValue value) + { + throw new NotImplementedException(); + } + + public ValueTask SetValueAsync(string identifier, TValue value, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } } class TestJSRuntime_ProvidesInsufficientData : TestJSRuntime @@ -142,7 +172,7 @@ public TestJSRuntime_ProvidesInsufficientData(byte[] data) : base(data) { } - public override ValueTask InvokeAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, CancellationToken cancellationToken, object[] args) + public override ValueTask InvokeAsync(string identifier, CancellationToken cancellationToken, object[] args) { var offset = (long)args[1]; var bytesToRead = (int)args[2]; @@ -156,7 +186,7 @@ public TestJSRuntime_ProvidesExcessData(byte[] data) : base(data) { } - public override ValueTask InvokeAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, CancellationToken cancellationToken, object[] args) + public override ValueTask InvokeAsync(string identifier, CancellationToken cancellationToken, object[] args) { var offset = (long)args[1]; var bytesToRead = (int)args[2]; diff --git a/src/Components/WebView/WebView/src/Services/WebViewJSRuntime.cs b/src/Components/WebView/WebView/src/Services/WebViewJSRuntime.cs index 6633bbb4bc5b..484528f70981 100644 --- a/src/Components/WebView/WebView/src/Services/WebViewJSRuntime.cs +++ b/src/Components/WebView/WebView/src/Services/WebViewJSRuntime.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using System.Text.Json; using Microsoft.JSInterop; using Microsoft.JSInterop.Infrastructure; @@ -38,6 +39,11 @@ protected override void BeginInvokeJS(long taskId, string identifier, string arg _ipcSender.BeginInvokeJS(taskId, identifier, argsJson, resultType, targetInstanceId); } + protected override void BeginInvokeJSX(long taskId, string identifier, [StringSyntax("Json")] string? argsJson, JSCallType callType, JSCallResultType resultType, long targetInstanceId) + { + throw new NotImplementedException(); + } + protected override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult) { var resultJsonOrErrorMessage = invocationResult.Success diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index 4668ba6ce930..75f409fb8816 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -21,6 +21,11 @@ export module DotNet { // Provides access to the "current" call dispatcher without having to flow it through nested function calls. let currentCallDispatcher : CallDispatcher | undefined; + export interface ObjectMemberDescriptor { + parent: any; + name: string; + } + class JSObject { _cachedFunctions: Map; @@ -57,6 +62,24 @@ export module DotNet { throw new Error(`The value '${identifier}' is not a function.`); } + public findMember(identifier: string): ObjectMemberDescriptor { + let current: any = this._jsObject; + let parent: any = null; + + const keys = identifier.split("."); + + for (const key of keys) { + if (current && typeof current === 'object' && key in current) { + parent = current; + current = current[key]; + } else { + return { parent, name: key }; + } + } + + return { parent, name: keys[keys.length - 1] }; + } + public getWrappedObject() { return this._jsObject; } @@ -256,6 +279,25 @@ export module DotNet { JSVoidResult = 3, } + /** + * Represents the type of operation that should be performed in JS. + */ + export enum JSCallType { + FunctionCall = 1, + NewCall = 2, + GetValue = 3, + SetValue = 4 + } + + export interface JSInvocationInfo { + asyncHandle?: number, + targetInstanceId: number, + identifier: string | null, + callType: JSCallType, + resultType: JSCallResultType, + argsJson: string | null, + } + /** * Represents the ability to dispatch calls from JavaScript to a .NET runtime. */ @@ -320,10 +362,11 @@ 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 callType The type of operation that should be performed in JS. * @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 | null, resultType: JSCallResultType, targetInstanceId: number): void; + beginInvokeJSFromDotNet(invocationInfoString: string): void; /** * Receives notification that an async call from JS to .NET has completed. @@ -398,14 +441,77 @@ export module DotNet { : stringifyArgs(this, result); } - beginInvokeJSFromDotNet(asyncHandle: number, identifier: string, argsJson: string | null, resultType: JSCallResultType, targetInstanceId: number): void { + handleJSFunctionCall(identifier: string, member: ObjectMemberDescriptor, args: any): any { + const func = member.parent[member.name]; + + if (typeof func === "function") { + return func.call(member.parent, ...(args || [])); + } else { + throw new Error(`The value '${identifier}' is not a function.`); + } + } + + handleJSNewCall(identifier: string, member: ObjectMemberDescriptor, args: any): any { + const func = member.parent[member.name]; + + if (typeof func === "function") { + try { + // The new call throws if the function is not constructible (e.g. an arrow function) + return new func(...(args || [])); + } catch (err) { + if (err instanceof TypeError) { + throw new Error(`The value '${identifier}' is not a constructor function.`); + } else { + throw err; + } + } + } else { + throw new Error(`The value '${identifier}' is not a function.`); + } + } + + handleJSPropertyGet(identifier: string, instance: ObjectMemberDescriptor): any { + return identifier.length > 0 ? instance.parent[instance.name] : instance.parent; + } + + handleJSPropertySet(identifier: string, instance: ObjectMemberDescriptor, args: any) { + const value = args[0]; + instance.parent[instance.name] = value; + } + + handleJSCall(identifier: string | null, argsJson: string | null, targetInstanceId: number, callType: JSCallType) { + identifier ??= ""; + const args = parseJsonWithRevivers(this, argsJson); + const member = findObjectMember(identifier, targetInstanceId); + let returnValue = null; + + if (callType === JSCallType.FunctionCall) { + returnValue = this.handleJSFunctionCall(identifier, member, args); + } else if (callType === JSCallType.NewCall) { + returnValue = this.handleJSNewCall(identifier, member, args); + } else if (callType === JSCallType.GetValue) { + returnValue = this.handleJSPropertyGet(identifier, member); + } else if (callType === JSCallType.SetValue) { + this.handleJSPropertySet(identifier, member, args); + } + + return returnValue; + } + + beginInvokeJSFromDotNet(invocationInfoString: string): void { + // TODO(OR): Remove log + console.log(invocationInfoString); + const invocationInfo: JSInvocationInfo = JSON.parse(invocationInfoString); + + // TODO(OR): Remove log + console.log(invocationInfo); + const { asyncHandle, identifier, argsJson, callType, resultType, targetInstanceId } = invocationInfo; + // Coerce synchronous functions into async ones, plus treat // synchronous exceptions the same as async ones const promise = new Promise(resolve => { - const args = parseJsonWithRevivers(this, argsJson); - const jsFunction = findJSFunction(identifier, targetInstanceId); - const synchronousResultOrPromise = jsFunction(...(args || [])); - resolve(synchronousResultOrPromise); + const valueOrPromise = this.handleJSCall(identifier, argsJson, targetInstanceId, callType); + resolve(valueOrPromise); }); // We only listen for a result if the caller wants to be notified about it @@ -414,14 +520,14 @@ export module DotNet { // Not using "await" because it codegens a lot of boilerplate promise. then(result => stringifyArgs(this, [ - asyncHandle, + asyncHandle, true, createJSCallResult(result, resultType) ])). then( result => this._dotNetCallDispatcher.endInvokeJSFromDotNet(asyncHandle, true, result), error => this._dotNetCallDispatcher.endInvokeJSFromDotNet(asyncHandle, false, JSON.stringify([ - asyncHandle, + asyncHandle, false, formatError(error) ])) @@ -555,6 +661,22 @@ export module DotNet { throw new Error(`JS object instance with ID ${targetInstanceId} does not exist (has it been disposed?).`); } + export function findObjectMember(identifier: string, targetInstanceId: number): ObjectMemberDescriptor { + // TODO(OR): Remove log + console.log("findObjectMember", identifier, targetInstanceId) + const targetInstance = cachedJSObjectsById[targetInstanceId]; + + if (!targetInstance) { + throw new Error(`JS object instance with ID ${targetInstanceId} does not exist (has it been disposed?).`); + } + + if (identifier === "") { + return { parent: targetInstance.getWrappedObject(), name: "" }; + } + + return targetInstance.findMember(identifier); + } + export function disposeJSObjectReferenceById(id: number) { delete cachedJSObjectsById[id]; } diff --git a/src/JSInterop/Microsoft.JSInterop/src/IJSObjectReference.cs b/src/JSInterop/Microsoft.JSInterop/src/IJSObjectReference.cs index 55a99827849d..ec11c1ce70e8 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/IJSObjectReference.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/IJSObjectReference.cs @@ -36,4 +36,24 @@ public interface IJSObjectReference : IAsyncDisposable /// JSON-serializable arguments. /// An instance of obtained by JSON-deserializing the return value. ValueTask InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken, object?[]? args); + + ValueTask InvokeAsyncX<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, object?[]? args); + + ValueTask InvokeAsyncX<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken, object?[]? args); + + ValueTask InvokeNewAsync(string identifier, object?[]? args); + + ValueTask InvokeNewAsync(string identifier, CancellationToken cancellationToken, object?[]? args); + + ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(); + + ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(CancellationToken cancellationToken); + + ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier); + + ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken); + + ValueTask SetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value); + + ValueTask SetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value, CancellationToken cancellationToken); } diff --git a/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs index 0313bf613470..24c2bbc39faa 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs @@ -36,4 +36,16 @@ public interface IJSRuntime /// JSON-serializable arguments. /// An instance of obtained by JSON-deserializing the return value. ValueTask InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken, object?[]? args); + + ValueTask InvokeNewAsync(string identifier, object?[]? args); + + ValueTask InvokeNewAsync(string identifier, CancellationToken cancellationToken, object?[]? args); + + ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier); + + ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken); + + ValueTask SetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value); + + ValueTask SetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value, CancellationToken cancellationToken); } diff --git a/src/JSInterop/Microsoft.JSInterop/src/Implementation/JSObjectReference.cs b/src/JSInterop/Microsoft.JSInterop/src/Implementation/JSObjectReference.cs index 8661dd4b5ad8..89862e50a027 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Implementation/JSObjectReference.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Implementation/JSObjectReference.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; +using Microsoft.JSInterop.Infrastructure; using static Microsoft.AspNetCore.Internal.LinkerFlags; namespace Microsoft.JSInterop.Implementation; @@ -37,7 +38,7 @@ protected internal JSObjectReference(JSRuntime jsRuntime, long id) { ThrowIfDisposed(); - return _jsRuntime.InvokeAsync(Id, identifier, args); + return _jsRuntime.InvokeAsync(Id, identifier, JSCallType.FunctionCall, args); } /// @@ -45,7 +46,71 @@ protected internal JSObjectReference(JSRuntime jsRuntime, long id) { ThrowIfDisposed(); - return _jsRuntime.InvokeAsync(Id, identifier, cancellationToken, args); + return _jsRuntime.InvokeAsync(Id, identifier, JSCallType.FunctionCall, cancellationToken, args); + } + + /// + public ValueTask InvokeNewAsync(string identifier, object?[]? args) + { + ThrowIfDisposed(); + + return _jsRuntime.InvokeAsync(Id, identifier, JSCallType.NewCall, args); + } + + /// + public ValueTask InvokeNewAsync(string identifier, CancellationToken cancellationToken, object?[]? args) + { + ThrowIfDisposed(); + + return _jsRuntime.InvokeAsync(Id, identifier, JSCallType.NewCall, cancellationToken, args); + } + + /// + public ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>() + { + ThrowIfDisposed(); + + return _jsRuntime.InvokeAsync(Id, string.Empty, JSCallType.GetValue, null); + } + + /// + public ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(CancellationToken cancellationToken) + { + ThrowIfDisposed(); + + return _jsRuntime.InvokeAsync(Id, string.Empty, JSCallType.GetValue, cancellationToken, null); + } + + /// + public ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier) + { + ThrowIfDisposed(); + + return _jsRuntime.InvokeAsync(Id, identifier, JSCallType.GetValue, null); + } + + /// + public ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken) + { + ThrowIfDisposed(); + + return _jsRuntime.InvokeAsync(Id, identifier, JSCallType.GetValue, null); + } + + /// + public async ValueTask SetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value) + { + ThrowIfDisposed(); + + await _jsRuntime.InvokeAsync(Id, identifier, JSCallType.SetValue, [value]); + } + + /// + public async ValueTask SetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value, CancellationToken cancellationToken) + { + ThrowIfDisposed(); + + await _jsRuntime.InvokeAsync(Id, identifier, JSCallType.SetValue, [value]); } /// diff --git a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSCallType.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSCallType.cs new file mode 100644 index 000000000000..ee8af7a09666 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSCallType.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.JSInterop.Infrastructure; + +public enum JSCallType : int +{ + FunctionCall = 1, + NewCall = 2, + GetValue = 3, + SetValue = 4, +} diff --git a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSInvocationInfo.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSInvocationInfo.cs new file mode 100644 index 000000000000..4bb7fa1a4c4b --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSInvocationInfo.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.JSInterop.Infrastructure; + +/// +/// TODO(OR) +/// +public sealed class JSInvocationInfo +{ + /// + /// The identifier for the interop call, or zero if no async callback is required. + /// + public long? AsyncHandle { get; init; } + + /// + /// The instance ID of the target JS object. + /// + public long TargetInstanceId { get; init; } + + /// + /// The identifier for the function to invoke or property to access. + /// + public string? Identifier { get; init; } + + /// + /// TODO(OR) + /// + public JSCallType CallType { get; init; } + + /// + /// The type of result expected from the invocation. + /// + public JSCallResultType ResultType { get; init; } + + /// + /// A JSON representation of the arguments. + /// + public string? ArgsJson { get; init; } + + /// + /// TODO(OR) + /// + /// TODO(OR) + public string ToJson() => JsonSerializer.Serialize(this, JSInvocationInfoSourceGenerationContext.Default.JSInvocationInfo); +} + +[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, GenerationMode = JsonSourceGenerationMode.Serialization)] +[JsonSerializable(typeof(JSInvocationInfo))] +internal partial class JSInvocationInfoSourceGenerationContext : JsonSerializerContext; diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs index 3cbd024d2755..b2c971aac7c1 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs @@ -66,7 +66,7 @@ protected JSRuntime() /// JSON-serializable arguments. /// An instance of obtained by JSON-deserializing the return value. public ValueTask InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, object?[]? args) - => InvokeAsync(0, identifier, args); + => InvokeAsync(0, identifier, JSCallType.FunctionCall, args); /// /// Invokes the specified JavaScript function asynchronously. @@ -80,24 +80,49 @@ protected JSRuntime() /// JSON-serializable arguments. /// An instance of obtained by JSON-deserializing the return value. public ValueTask InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken, object?[]? args) - => InvokeAsync(0, identifier, cancellationToken, args); + => InvokeAsync(0, identifier, JSCallType.FunctionCall, cancellationToken, args); - internal async ValueTask InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(long targetInstanceId, string identifier, object?[]? args) + /// + public ValueTask InvokeNewAsync(string identifier, object?[]? args) + => InvokeAsync(0, identifier, JSCallType.NewCall, args); + + /// + public ValueTask InvokeNewAsync(string identifier, CancellationToken cancellationToken, object?[]? args) + => InvokeAsync(0, identifier, JSCallType.NewCall, cancellationToken, args); + + /// + public ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier) + => InvokeAsync(0, identifier, JSCallType.GetValue, null); + + /// + public ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken) + => InvokeAsync(0, identifier, JSCallType.GetValue, cancellationToken, null); + + /// + public async ValueTask SetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value) + => await InvokeAsync(0, identifier, JSCallType.SetValue, [value]); + + /// + public async ValueTask SetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value, CancellationToken cancellationToken) + => await InvokeAsync(0, identifier, JSCallType.SetValue, cancellationToken, [value]); + + internal async ValueTask InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(long targetInstanceId, string identifier, JSCallType callType, object?[]? args) { if (DefaultAsyncTimeout.HasValue) { using var cts = new CancellationTokenSource(DefaultAsyncTimeout.Value); // We need to await here due to the using - return await InvokeAsync(targetInstanceId, identifier, cts.Token, args); + return await InvokeAsync(targetInstanceId, identifier, callType, cts.Token, args); } - return await InvokeAsync(targetInstanceId, identifier, CancellationToken.None, args); + return await InvokeAsync(targetInstanceId, identifier, callType, CancellationToken.None, args); } [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "We expect application code is configured to ensure JS interop arguments are linker friendly.")] internal ValueTask InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>( long targetInstanceId, string identifier, + JSCallType callType, CancellationToken cancellationToken, object?[]? args) { @@ -127,8 +152,17 @@ protected JSRuntime() JsonSerializer.Serialize(args, JsonSerializerOptions) : null; var resultType = JSCallResultTypeHelper.FromGeneric(); + var invocationInfo = new JSInvocationInfo + { + AsyncHandle = taskId, + TargetInstanceId = targetInstanceId, + Identifier = identifier, + CallType = callType, + ResultType = resultType, + ArgsJson = argsJson, + }; - BeginInvokeJS(taskId, identifier, argsJson, resultType, targetInstanceId); + BeginInvokeJS(invocationInfo); return new ValueTask(tcs.Task); } @@ -148,24 +182,7 @@ private void CleanupTasksAndRegistrations(long taskId) } } - /// - /// Begins an asynchronous function invocation. - /// - /// The identifier for the function invocation, or zero if no async callback is required. - /// The identifier for the function to invoke. - /// A JSON representation of the arguments. - protected virtual void BeginInvokeJS(long taskId, string identifier, [StringSyntax(StringSyntaxAttribute.Json)] string? argsJson) - => BeginInvokeJS(taskId, identifier, argsJson, JSCallResultType.Default, 0); - - /// - /// Begins an asynchronous function invocation. - /// - /// The identifier for the function invocation, or zero if no async callback is required. - /// The identifier for the function to invoke. - /// A JSON representation of the arguments. - /// The type of result expected from the invocation. - /// The instance ID of the target JS object. - protected abstract void BeginInvokeJS(long taskId, string identifier, [StringSyntax(StringSyntaxAttribute.Json)] string? argsJson, JSCallResultType resultType, long targetInstanceId); + protected abstract void BeginInvokeJS(JSInvocationInfo invocationInfo); /// /// Completes an async JS interop call from JavaScript to .NET diff --git a/src/submodules/googletest b/src/submodules/googletest index 4902ea2d7c6f..2b6b042a7744 160000 --- a/src/submodules/googletest +++ b/src/submodules/googletest @@ -1 +1 @@ -Subproject commit 4902ea2d7c6faed89b6facee00baa34bb108fc0d +Subproject commit 2b6b042a77446ff322cd7522ca068d9f2a21c1d1 From bdea46dee2827e9e5bdeaae96f019ef14f78518d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Tue, 18 Mar 2025 18:30:41 +0100 Subject: [PATCH 02/25] WIP implementation for other platforms, fix tests --- .../Server/src/Circuits/RemoteJSRuntime.cs | 2 +- .../Web.JS/src/Boot.WebAssembly.Common.ts | 10 +++-- src/Components/Web.JS/src/GlobalExports.ts | 3 +- .../JSInterop/src/WebAssemblyJSRuntime.cs | 16 ++++---- .../WebView/WebView/src/IpcSender.cs | 8 ++-- .../WebView/src/Services/WebViewJSRuntime.cs | 11 ++--- .../src/src/Microsoft.JSInterop.ts | 4 +- .../src/IJSObjectReference.cs | 4 -- .../Microsoft.JSInterop/src/IJSRuntime.cs | 41 +++++++++++++++++++ .../src/Infrastructure/JSInvocationInfo.cs | 4 +- .../src/JSInProcessRuntime.cs | 2 + .../Infrastructure/DotNetDispatcherTest.cs | 10 ++--- .../test/JSInProcessRuntimeTest.cs | 2 +- .../test/JSObjectReferenceTest.cs | 4 +- .../Microsoft.JSInterop/test/JSRuntimeTest.cs | 20 ++------- .../Microsoft.JSInterop/test/TestJSRuntime.cs | 2 +- 16 files changed, 88 insertions(+), 55 deletions(-) diff --git a/src/Components/Server/src/Circuits/RemoteJSRuntime.cs b/src/Components/Server/src/Circuits/RemoteJSRuntime.cs index 9b703da30ffb..99508d055c20 100644 --- a/src/Components/Server/src/Circuits/RemoteJSRuntime.cs +++ b/src/Components/Server/src/Circuits/RemoteJSRuntime.cs @@ -125,7 +125,7 @@ protected override void BeginInvokeJS(JSInvocationInfo invocationInfo) var invocationInfoJson = invocationInfo.ToJson(); - Log.BeginInvokeJS(_logger, invocationInfo.AsyncHandle ?? -1, invocationInfo.Identifier); + Log.BeginInvokeJS(_logger, invocationInfo.AsyncHandle, invocationInfo.Identifier); _clientProxy.SendAsync("JS.BeginInvokeJS", invocationInfoJson); } diff --git a/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts b/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts index c653c789c810..b15365b5cd57 100644 --- a/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts +++ b/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts @@ -14,6 +14,7 @@ import { addDispatchEventMiddleware } from './Rendering/WebRendererInteropMethod import { WebAssemblyComponentDescriptor, WebAssemblyServerOptions, discoverWebAssemblyPersistedState } from './Services/ComponentDescriptorDiscovery'; import { receiveDotNetDataStream } from './StreamingInterop'; import { WebAssemblyComponentAttacher } from './Platform/WebAssemblyComponentAttacher'; +import { DotNet } from '@microsoft/dotnet-js-interop'; import { MonoConfig } from '@microsoft/dotnet-runtime'; import { RootComponentManager } from './Services/RootComponentManager'; import { WebRendererId } from './Rendering/WebRendererId'; @@ -263,12 +264,13 @@ async function scheduleAfterStarted(operations: string): Promise { Blazor._internal.updateRootComponents(operations); } -function invokeJSJson(identifier: string, targetInstanceId: number, resultType: number, argsJson: string, asyncHandle: number): string | null { - if (asyncHandle !== 0) { - dispatcher.beginInvokeJSFromDotNet(asyncHandle, identifier, argsJson, resultType, targetInstanceId); +function invokeJSJson(invocationInfoString: string): string | null { + const invocationInfo: DotNet.JSInvocationInfo = JSON.parse(invocationInfoString); + if (invocationInfo.asyncHandle !== 0) { + dispatcher.beginInvokeJSFromDotNet(invocationInfoString); return null; } else { - return dispatcher.invokeJSFromDotNet(identifier, argsJson, resultType, targetInstanceId); + return dispatcher.invokeJSFromDotNet(invocationInfo.identifier ?? "", invocationInfo.argsJson ?? "[]", invocationInfo.resultType, invocationInfo.targetInstanceId); } } diff --git a/src/Components/Web.JS/src/GlobalExports.ts b/src/Components/Web.JS/src/GlobalExports.ts index 71d9b052c177..434901fbf3df 100644 --- a/src/Components/Web.JS/src/GlobalExports.ts +++ b/src/Components/Web.JS/src/GlobalExports.ts @@ -17,6 +17,7 @@ import { getNextChunk } from './StreamingInterop'; import { RootComponentsFunctions } from './Rendering/JSRootComponents'; import { attachWebRendererInterop } from './Rendering/WebRendererInteropMethods'; import { WebStartOptions } from './Platform/WebStartOptions'; +import { DotNet } from '@microsoft/dotnet-js-interop'; import { RuntimeAPI } from '@microsoft/dotnet-runtime'; import { JSEventRegistry } from './Services/JSEventRegistry'; @@ -52,7 +53,7 @@ export interface IBlazor { forceCloseConnection?: () => Promise; InputFile?: typeof InputFile; NavigationLock: typeof NavigationLock; - invokeJSJson?: (identifier: string, targetInstanceId: number, resultType: number, argsJson: string, asyncHandle: number) => string | null; + invokeJSJson?: (invocationInfoString: string) => string | null; endInvokeDotNetFromJS?: (callId: string, success: boolean, resultJsonOrErrorMessage: string) => void; receiveByteArray?: (id: number, data: Uint8Array) => void; getPersistedState?: () => string; diff --git a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs index 6415dc002112..ae12bb8231f2 100644 --- a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs +++ b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs @@ -27,6 +27,7 @@ protected override string InvokeJS(string identifier, [StringSyntax(StringSyntax { try { + // TODO(OR): Add support for CallType return InternalCalls.InvokeJSJson(identifier, targetInstanceId, (int)resultType, argsJson ?? "[]", 0); } catch (Exception ex) @@ -36,14 +37,15 @@ protected override string InvokeJS(string identifier, [StringSyntax(StringSyntax } /// - protected override void BeginInvokeJS(long asyncHandle, string identifier, [StringSyntax(StringSyntaxAttribute.Json)] string? argsJson, JSCallResultType resultType, long targetInstanceId) + protected override void BeginInvokeJS(JSInvocationInfo invocationInfo) { - InternalCalls.InvokeJSJson(identifier, targetInstanceId, (int)resultType, argsJson ?? "[]", asyncHandle); - } - - protected override void BeginInvokeJSX(long taskId, string identifier, [StringSyntax("Json")] string? argsJson, JSCallType callType, JSCallResultType resultType, long targetInstanceId) - { - throw new NotImplementedException(); + // TODO(OR): Add support for CallType + InternalCalls.InvokeJSJson( + invocationInfo.Identifier ?? "", + invocationInfo.TargetInstanceId, + (int)invocationInfo.ResultType, + invocationInfo.ArgsJson ?? "[]", + invocationInfo.AsyncHandle); } /// diff --git a/src/Components/WebView/WebView/src/IpcSender.cs b/src/Components/WebView/WebView/src/IpcSender.cs index 60edbd12a8c0..74db080a5072 100644 --- a/src/Components/WebView/WebView/src/IpcSender.cs +++ b/src/Components/WebView/WebView/src/IpcSender.cs @@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.ExceptionServices; using Microsoft.AspNetCore.Components.RenderTree; -using Microsoft.JSInterop; +using Microsoft.JSInterop.Infrastructure; namespace Microsoft.AspNetCore.Components.WebView; @@ -49,9 +49,11 @@ public void AttachToDocument(int componentId, string selector) DispatchMessageWithErrorHandling(IpcCommon.Serialize(IpcCommon.OutgoingMessageType.AttachToDocument, componentId, selector)); } - public void BeginInvokeJS(long taskId, string identifier, string argsJson, JSCallResultType resultType, long targetInstanceId) + public void BeginInvokeJS(JSInvocationInfo invocationInfo) { - DispatchMessageWithErrorHandling(IpcCommon.Serialize(IpcCommon.OutgoingMessageType.BeginInvokeJS, taskId, identifier, argsJson, resultType, targetInstanceId)); + // TODO(OR): Make sure the client side works with this + var invocationInfoJson = invocationInfo.ToJson(); + DispatchMessageWithErrorHandling(IpcCommon.Serialize(IpcCommon.OutgoingMessageType.BeginInvokeJS, invocationInfoJson)); } public void EndInvokeDotNet(string callId, bool success, string invocationResultOrError) diff --git a/src/Components/WebView/WebView/src/Services/WebViewJSRuntime.cs b/src/Components/WebView/WebView/src/Services/WebViewJSRuntime.cs index 484528f70981..9a9128b7a400 100644 --- a/src/Components/WebView/WebView/src/Services/WebViewJSRuntime.cs +++ b/src/Components/WebView/WebView/src/Services/WebViewJSRuntime.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; using System.Text.Json; using Microsoft.JSInterop; using Microsoft.JSInterop.Infrastructure; @@ -29,19 +28,15 @@ public void AttachToWebView(IpcSender ipcSender) public JsonSerializerOptions ReadJsonSerializerOptions() => JsonSerializerOptions; - protected override void BeginInvokeJS(long taskId, string identifier, string argsJson, JSCallResultType resultType, long targetInstanceId) + protected override void BeginInvokeJS(JSInvocationInfo invocationInfo) { if (_ipcSender is null) { throw new InvalidOperationException("Cannot invoke JavaScript outside of a WebView context."); } - _ipcSender.BeginInvokeJS(taskId, identifier, argsJson, resultType, targetInstanceId); - } - - protected override void BeginInvokeJSX(long taskId, string identifier, [StringSyntax("Json")] string? argsJson, JSCallType callType, JSCallResultType resultType, long targetInstanceId) - { - throw new NotImplementedException(); + // TODO(OR): Add CallType support + _ipcSender.BeginInvokeJS(invocationInfo); } protected override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult) diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index 75f409fb8816..f783cd7ac247 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -290,7 +290,7 @@ export module DotNet { } export interface JSInvocationInfo { - asyncHandle?: number, + asyncHandle: number, targetInstanceId: number, identifier: string | null, callType: JSCallType, @@ -355,6 +355,7 @@ export module DotNet { * @returns JSON representation of the invocation result. */ invokeJSFromDotNet(identifier: string, argsJson: string, resultType: JSCallResultType, targetInstanceId: number): string | null; + // TODO(OR): Add support for CallType, refactor to use JSInvocationInfo /** * Invokes the specified synchronous or asynchronous JavaScript function. @@ -430,6 +431,7 @@ export module DotNet { return this._dotNetCallDispatcher; } + // TODO(OR): Add support for CallType, refactor to use JSInvocationInfo invokeJSFromDotNet(identifier: string, argsJson: string, resultType: JSCallResultType, targetInstanceId: number): string | null { const args = parseJsonWithRevivers(this, argsJson); const jsFunction = findJSFunction(identifier, targetInstanceId); diff --git a/src/JSInterop/Microsoft.JSInterop/src/IJSObjectReference.cs b/src/JSInterop/Microsoft.JSInterop/src/IJSObjectReference.cs index ec11c1ce70e8..bfa03005f1b2 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/IJSObjectReference.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/IJSObjectReference.cs @@ -37,10 +37,6 @@ public interface IJSObjectReference : IAsyncDisposable /// An instance of obtained by JSON-deserializing the return value. ValueTask InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken, object?[]? args); - ValueTask InvokeAsyncX<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, object?[]? args); - - ValueTask InvokeAsyncX<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken, object?[]? args); - ValueTask InvokeNewAsync(string identifier, object?[]? args); ValueTask InvokeNewAsync(string identifier, CancellationToken cancellationToken, object?[]? args); diff --git a/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs index 24c2bbc39faa..609ec663a74e 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs @@ -37,15 +37,56 @@ public interface IJSRuntime /// An instance of obtained by JSON-deserializing the return value. ValueTask InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken, object?[]? args); + /// + /// TODO(OR): Add documentation + /// + /// + /// + /// ValueTask InvokeNewAsync(string identifier, object?[]? args); + /// + /// TODO(OR): Add documentation + /// + /// + /// + /// + /// ValueTask InvokeNewAsync(string identifier, CancellationToken cancellationToken, object?[]? args); + /// + /// TODO(OR): Add documentation + /// + /// + /// + /// ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier); + /// + /// TODO(OR): Add documentation + /// + /// + /// + /// + /// ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken); + /// + /// TODO(OR): Add documentation + /// + /// + /// + /// + /// ValueTask SetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value); + /// + /// TODO(OR): Add documentation + /// + /// + /// + /// + /// + /// ValueTask SetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value, CancellationToken cancellationToken); } diff --git a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSInvocationInfo.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSInvocationInfo.cs index 4bb7fa1a4c4b..ac5d8c011658 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSInvocationInfo.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSInvocationInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; @@ -14,7 +15,7 @@ public sealed class JSInvocationInfo /// /// The identifier for the interop call, or zero if no async callback is required. /// - public long? AsyncHandle { get; init; } + public long AsyncHandle { get; init; } /// /// The instance ID of the target JS object. @@ -39,6 +40,7 @@ public sealed class JSInvocationInfo /// /// A JSON representation of the arguments. /// + [StringSyntax(StringSyntaxAttribute.Json)] public string? ArgsJson { get; init; } /// diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs index 4092349c3882..6b0acf7f7c56 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs @@ -45,6 +45,8 @@ public abstract class JSInProcessRuntime : JSRuntime, IJSInProcessRuntime public TValue Invoke<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, params object?[]? args) => Invoke(identifier, 0, args); + // TODO(OR): Add sync variants of new IJSRuntime methods + /// /// Performs a synchronous function invocation. /// diff --git a/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs index 6987c9282940..3e928ec5e5b0 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs @@ -1072,18 +1072,18 @@ public class TestJSRuntime : JSInProcessRuntime { private TaskCompletionSource _nextInvocationTcs = new TaskCompletionSource(); public Task NextInvocationTask => _nextInvocationTcs.Task; - public long LastInvocationAsyncHandle { get; private set; } + public long? LastInvocationAsyncHandle { get; private set; } public string LastInvocationIdentifier { get; private set; } public string LastInvocationArgsJson { get; private set; } public string LastCompletionCallId { get; private set; } public DotNetInvocationResult LastCompletionResult { get; private set; } - protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson, JSCallResultType resultType, long targetInstanceId) + protected override void BeginInvokeJS(JSInvocationInfo invocationInfo) { - LastInvocationAsyncHandle = asyncHandle; - LastInvocationIdentifier = identifier; - LastInvocationArgsJson = argsJson; + LastInvocationAsyncHandle = invocationInfo.AsyncHandle; + LastInvocationIdentifier = invocationInfo.Identifier; + LastInvocationArgsJson = invocationInfo.ArgsJson; _nextInvocationTcs.SetResult(); _nextInvocationTcs = new TaskCompletionSource(); } diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeTest.cs index 5ad8aaa94159..779a9b4659c4 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeTest.cs @@ -107,7 +107,7 @@ public class InvokeArgs public string? ArgsJson { get; set; } } - protected override void BeginInvokeJS(long asyncHandle, string identifier, string? argsJson, JSCallResultType resultType, long targetInstanceId) + protected override void BeginInvokeJS(JSInvocationInfo invocationInfo) => throw new NotImplementedException("This test only covers sync calls"); protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult) diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSObjectReferenceTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSObjectReferenceTest.cs index 909f3f0ddb28..bc245284ff07 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSObjectReferenceTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSObjectReferenceTest.cs @@ -69,7 +69,7 @@ class TestJSRuntime : JSRuntime { public int BeginInvokeJSInvocationCount { get; private set; } - protected override void BeginInvokeJS(long taskId, string identifier, string? argsJson, JSCallResultType resultType, long targetInstanceId) + protected override void BeginInvokeJS(JSInvocationInfo invocationInfo) { BeginInvokeJSInvocationCount++; } @@ -83,7 +83,7 @@ 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 void BeginInvokeJS(JSInvocationInfo invocationInfo) { } diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs index 96f3f5265ec4..323d7914c522 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs @@ -445,8 +445,8 @@ private class TestPoco class TestJSRuntime : JSRuntime { - public List BeginInvokeCalls = new List(); - public List EndInvokeDotNetCalls = new List(); + public List BeginInvokeCalls = []; + public List EndInvokeDotNetCalls = []; public TimeSpan? DefaultTimeout { @@ -456,13 +456,6 @@ public TimeSpan? DefaultTimeout } } - public class BeginInvokeAsyncArgs - { - public long AsyncHandle { get; set; } - public string? Identifier { get; set; } - public string? ArgsJson { get; set; } - } - public class EndInvokeDotNetArgs { public string? CallId { get; set; } @@ -482,14 +475,9 @@ protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocation }); } - protected override void BeginInvokeJS(long asyncHandle, string identifier, string? argsJson, JSCallResultType resultType, long targetInstanceId) + protected override void BeginInvokeJS(JSInvocationInfo invocationInfo) { - BeginInvokeCalls.Add(new BeginInvokeAsyncArgs - { - AsyncHandle = asyncHandle, - Identifier = identifier, - ArgsJson = argsJson, - }); + BeginInvokeCalls.Add(invocationInfo); } protected internal override Task TransmitStreamAsync(long streamId, DotNetStreamReference dotNetStreamReference) diff --git a/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs index accd66db1934..7d026fc31b42 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs @@ -7,7 +7,7 @@ namespace Microsoft.JSInterop; internal class TestJSRuntime : JSRuntime { - protected override void BeginInvokeJS(long asyncHandle, string identifier, string? argsJson, JSCallResultType resultType, long targetInstanceId) + protected override void BeginInvokeJS(JSInvocationInfo invocationInfo) { throw new NotImplementedException(); } From 8e51e9c3db34bc6fa1ac6d0aa16d8f709869368b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Fri, 21 Mar 2025 19:55:21 +0100 Subject: [PATCH 03/25] Fix function calls, add JS tests --- package-lock.json | 25 +- .../BlazorServerApp/BlazorServerApp.csproj | 4 + .../Samples/BlazorServerApp/Pages/Index.razor | 63 +- .../BlazorServerApp/wwwroot/custom-module.js | 3 + .../Web.JS/src/Boot.WebAssembly.Common.ts | 8 +- .../src/Platform/Circuits/CircuitManager.ts | 8 +- .../src/Rendering/Events/EventDelegator.ts | 2 + src/JSInterop/JSInterop.sln | 36 + .../src/babel.config.js | 4 + .../Microsoft.JSInterop.JS/src/jest.config.js | 33 + .../Microsoft.JSInterop.JS/src/package.json | 14 +- .../src/src/Microsoft.JSInterop.ts | 1494 +++++++++-------- .../src/test/CallDispatcher.test.ts | 169 ++ .../src/test/findObjectMember.test.ts | 70 + .../src/Infrastructure/JSInvocationInfo.cs | 6 +- .../Infrastructure/DotNetDispatcherTest.cs | 2 +- 16 files changed, 1200 insertions(+), 741 deletions(-) create mode 100644 src/Components/Samples/BlazorServerApp/wwwroot/custom-module.js create mode 100644 src/JSInterop/JSInterop.sln create mode 100644 src/JSInterop/Microsoft.JSInterop.JS/src/babel.config.js create mode 100644 src/JSInterop/Microsoft.JSInterop.JS/src/jest.config.js create mode 100644 src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts create mode 100644 src/JSInterop/Microsoft.JSInterop.JS/src/test/findObjectMember.test.ts diff --git a/package-lock.json b/package-lock.json index efe7f3711259..0cc648741eb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18401,14 +18401,21 @@ }, "src/JSInterop/Microsoft.JSInterop.JS/src": { "name": "@microsoft/dotnet-js-interop", - "version": "9.0.0-dev", + "version": "10.0.0-dev", "license": "MIT", "devDependencies": { + "@babel/core": "^7.23.6", + "@babel/preset-env": "^7.23.6", + "@babel/preset-typescript": "^7.26.0", "@typescript-eslint/eslint-plugin": "^6.15.0", "@typescript-eslint/parser": "^6.15.0", + "babel-jest": "^29.7.0", "eslint": "^8.56.0", "eslint-plugin-jsdoc": "^46.9.1", "eslint-plugin-prefer-arrow": "^1.2.3", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jest-junit": "^16.0.0", "rimraf": "^5.0.5", "typescript": "^5.3.3" } @@ -18688,6 +18695,22 @@ "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, + "src/JSInterop/Microsoft.JSInterop.JS/src/node_modules/jest-junit": { + "version": "16.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/jest-junit/-/jest-junit-16.0.0.tgz", + "integrity": "sha1-2DjoxWHPn91+tU9jAgd37uQTZ4U=", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + }, + "engines": { + "node": ">=10.12.0" + } + }, "src/JSInterop/Microsoft.JSInterop.JS/src/node_modules/minimatch": { "version": "9.0.3", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/minimatch/-/minimatch-9.0.3.tgz", diff --git a/src/Components/Samples/BlazorServerApp/BlazorServerApp.csproj b/src/Components/Samples/BlazorServerApp/BlazorServerApp.csproj index 02f0243552d8..1035814a2137 100644 --- a/src/Components/Samples/BlazorServerApp/BlazorServerApp.csproj +++ b/src/Components/Samples/BlazorServerApp/BlazorServerApp.csproj @@ -6,6 +6,10 @@ enable + + + + diff --git a/src/Components/Samples/BlazorServerApp/Pages/Index.razor b/src/Components/Samples/BlazorServerApp/Pages/Index.razor index c145cc3c44cd..9d41393dd65e 100644 --- a/src/Components/Samples/BlazorServerApp/Pages/Index.razor +++ b/src/Components/Samples/BlazorServerApp/Pages/Index.razor @@ -1,19 +1,21 @@ -@inject IJSRuntime JSRuntime +@implements IAsyncDisposable +@inject IJSRuntime JSRuntime @page "/" -Index +My index

Hello, world!

Welcome to your new app.
- +
Message: +
@@ -41,12 +43,30 @@ Welcome to your new app. @AnimalMessage
+
+ + + @ErrorMessage +
+ @code { private string? Message { get; set; } private string? CurrentTitle { get; set; } private string? NewTitle { get; set; } private string? TestObjectDisplay { get; set; } private string? AnimalMessage { get; set; } + private string? ErrorMessage { get; set; } + + private IJSObjectReference? module; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + module = await JSRuntime.InvokeAsync("import", "../custom-module.js"); + } + } + private async Task LogDefault() { @@ -59,6 +79,16 @@ Welcome to your new app. await JSRuntime.InvokeVoidAsync("console.log", $"Console: {message}"); } + + private async Task LogFromModule(string message) + { + if (module != null) + { + await module.InvokeVoidAsync("logFromModule", message); + } + } + + private async Task SetDocumentTitle(string title) { await JSRuntime.InvokeVoidAsync("setDocumentTitle", title); @@ -117,10 +147,37 @@ Welcome to your new app. AnimalMessage = await catRef.InvokeAsync("meow"); } + private async Task GetInvalid(MouseEventArgs args) + { + var objectRef = await JSRuntime.GetValueAsync("testObject"); + var value = await objectRef.GetValueAsync("setOnlyProperty"); + } + + private async Task SetValid(MouseEventArgs args) + { + var objectRef = await JSRuntime.GetValueAsync("testObject"); + await objectRef.SetValueAsync("getOnlyProperty", 123); + } + + async ValueTask IAsyncDisposable.DisposeAsync() + { + if (module is not null) + { + try + { + await module.DisposeAsync(); + } + catch (JSDisconnectedException) + { + } + } + } + class TestObjectModel { public int Num { get; set; } public string? Text { get; set; } public int GetOnlyProperty { get; set; } } + } diff --git a/src/Components/Samples/BlazorServerApp/wwwroot/custom-module.js b/src/Components/Samples/BlazorServerApp/wwwroot/custom-module.js new file mode 100644 index 000000000000..4c32c97086ff --- /dev/null +++ b/src/Components/Samples/BlazorServerApp/wwwroot/custom-module.js @@ -0,0 +1,3 @@ +export function logFromModule(msg) { + console.log(`Message from custom-module.js: ${msg}`); +} diff --git a/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts b/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts index b15365b5cd57..b832c0ab5aa2 100644 --- a/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts +++ b/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts @@ -264,13 +264,13 @@ async function scheduleAfterStarted(operations: string): Promise { Blazor._internal.updateRootComponents(operations); } -function invokeJSJson(invocationInfoString: string): string | null { - const invocationInfo: DotNet.JSInvocationInfo = JSON.parse(invocationInfoString); +function invokeJSJson(invocationInfoJson: string): string | null { + const invocationInfo: DotNet.JSInvocationInfo = JSON.parse(invocationInfoJson); if (invocationInfo.asyncHandle !== 0) { - dispatcher.beginInvokeJSFromDotNet(invocationInfoString); + dispatcher.beginInvokeJSFromDotNet(invocationInfo); return null; } else { - return dispatcher.invokeJSFromDotNet(invocationInfo.identifier ?? "", invocationInfo.argsJson ?? "[]", invocationInfo.resultType, invocationInfo.targetInstanceId); + return dispatcher.invokeJSFromDotNet(invocationInfo); } } diff --git a/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts b/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts index 7788f9d4358f..cf9f1f8c1830 100644 --- a/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts +++ b/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts @@ -127,7 +127,7 @@ export class CircuitManager implements DotNet.DotNetCallDispatcher { const connection = connectionBuilder.build(); connection.on('JS.AttachComponent', (componentId, selector) => attachRootComponentToLogicalElement(WebRendererId.Server, this.resolveElement(selector), componentId, false)); - connection.on('JS.BeginInvokeJS', this._dispatcher.beginInvokeJSFromDotNet.bind(this._dispatcher)); + connection.on('JS.BeginInvokeJS', (invocationInfoJson: string) => this.beginInvokeJSJson(invocationInfoJson)); connection.on('JS.EndInvokeDotNet', this._dispatcher.endInvokeDotNetFromJS.bind(this._dispatcher)); connection.on('JS.ReceiveByteArray', this._dispatcher.receiveByteArray.bind(this._dispatcher)); @@ -232,6 +232,12 @@ export class CircuitManager implements DotNet.DotNetCallDispatcher { return true; } + private beginInvokeJSJson(invocationInfoJson: string) { + const invocationInfo: DotNet.JSInvocationInfo = JSON.parse(invocationInfoJson); + console.log("CIRCUIT", invocationInfoJson, invocationInfo); + this._dispatcher.beginInvokeJSFromDotNet(invocationInfo); + } + // Implements DotNet.DotNetCallDispatcher public beginInvokeDotNetFromJS(callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): void { this.throwIfDispatchingWhenDisposed(); diff --git a/src/Components/Web.JS/src/Rendering/Events/EventDelegator.ts b/src/Components/Web.JS/src/Rendering/Events/EventDelegator.ts index c64b38ee952d..c75ea6411906 100644 --- a/src/Components/Web.JS/src/Rendering/Events/EventDelegator.ts +++ b/src/Components/Web.JS/src/Rendering/Events/EventDelegator.ts @@ -136,6 +136,8 @@ export class EventDelegator { return; } + console.log("onGlobalEvent", isRendererAttached(this.browserRendererId), evt); + if (!isRendererAttached(this.browserRendererId)) { // when connection closed, it will detachWebRendererInterop, so we need to check if the renderer is still attached return; diff --git a/src/JSInterop/JSInterop.sln b/src/JSInterop/JSInterop.sln new file mode 100644 index 000000000000..03e30c29c0ad --- /dev/null +++ b/src/JSInterop/JSInterop.sln @@ -0,0 +1,36 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.JSInterop", "Microsoft.JSInterop", "{858B5D40-ED74-2150-A8A2-365B5B420ABA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.JSInterop", "Microsoft.JSInterop\src\Microsoft.JSInterop.csproj", "{CF7471C8-620A-6EBA-58DD-0A6B81AF790B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.JSInterop.Tests", "Microsoft.JSInterop\test\Microsoft.JSInterop.Tests.csproj", "{60AF6814-2890-E499-8733-A9657CAAD7C1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CF7471C8-620A-6EBA-58DD-0A6B81AF790B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF7471C8-620A-6EBA-58DD-0A6B81AF790B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF7471C8-620A-6EBA-58DD-0A6B81AF790B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF7471C8-620A-6EBA-58DD-0A6B81AF790B}.Release|Any CPU.Build.0 = Release|Any CPU + {60AF6814-2890-E499-8733-A9657CAAD7C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60AF6814-2890-E499-8733-A9657CAAD7C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60AF6814-2890-E499-8733-A9657CAAD7C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60AF6814-2890-E499-8733-A9657CAAD7C1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {CF7471C8-620A-6EBA-58DD-0A6B81AF790B} = {858B5D40-ED74-2150-A8A2-365B5B420ABA} + {60AF6814-2890-E499-8733-A9657CAAD7C1} = {858B5D40-ED74-2150-A8A2-365B5B420ABA} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {332B6471-80B9-4C1B-9A85-046F3BEBDB06} + EndGlobalSection +EndGlobal diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/babel.config.js b/src/JSInterop/Microsoft.JSInterop.JS/src/babel.config.js new file mode 100644 index 000000000000..8bd00d8d7eb0 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/babel.config.js @@ -0,0 +1,4 @@ +export const presets = [ + ['@babel/preset-env', { targets: { node: 'current' } }], + '@babel/preset-typescript', +]; \ No newline at end of file diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/jest.config.js b/src/JSInterop/Microsoft.JSInterop.JS/src/jest.config.js new file mode 100644 index 000000000000..fa4cbc4cf3bd --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/jest.config.js @@ -0,0 +1,33 @@ +/* + * For a detailed explanation regarding each configuration property, visit: + * https://jestjs.io/docs/configuration + */ + +import path from "path"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const ROOT_DIR = path.resolve(__dirname, "..", "..", "..", ".."); + +/** @type {import('jest').Config} */ + +const config = { + roots: ["/src", "/test"], + testMatch: ["**/*.test.ts"], + moduleFileExtensions: ["js", "ts"], + transform: { + "^.+\\.(js|ts)$": "babel-jest", + }, + moduleDirectories: ["node_modules", "src"], + testEnvironment: "jsdom", + reporters: [ + "default", + [ + path.resolve(ROOT_DIR, "node_modules", "jest-junit", "index.js"), + { "outputDirectory": path.resolve(ROOT_DIR, "artifacts", "log"), "outputName": `${process.platform}` + ".components-webjs.junit.xml" } + ] + ], +}; + +export default config; \ No newline at end of file diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/package.json b/src/JSInterop/Microsoft.JSInterop.JS/src/package.json index 76a5ad8e4cb9..4a91339cf24f 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/package.json +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/package.json @@ -1,12 +1,15 @@ { "name": "@microsoft/dotnet-js-interop", - "version": "9.0.0-dev", + "version": "10.0.0-dev", "description": "Provides abstractions and features for interop between .NET and JavaScript code.", "main": "dist/src/Microsoft.JSInterop.js", "types": "dist/src/Microsoft.JSInterop.d.ts", "type": "module", "scripts": { "clean": "rimraf ./dist", + "test": "jest", + "test:watch": "jest --watch", + "test:debug": "node --nolazy --inspect-brk ./node_modules/jest/bin/jest.js --runInBand --colors --verbose", "build": "npm run clean && npm run build:esm", "build:lint": "eslint -c .eslintrc.json --ext .ts ./src", "build:esm": "tsc --project ./tsconfig.json", @@ -26,12 +29,19 @@ "dist/**" ], "devDependencies": { + "@babel/core": "^7.23.6", + "@babel/preset-env": "^7.23.6", + "@babel/preset-typescript": "^7.26.0", "@typescript-eslint/eslint-plugin": "^6.15.0", "@typescript-eslint/parser": "^6.15.0", + "babel-jest": "^29.7.0", "eslint": "^8.56.0", "eslint-plugin-jsdoc": "^46.9.1", "eslint-plugin-prefer-arrow": "^1.2.3", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jest-junit": "^16.0.0", "rimraf": "^5.0.5", "typescript": "^5.3.3" } -} +} \ No newline at end of file diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index f783cd7ac247..3457572c07b6 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -4,810 +4,852 @@ // This is a single-file self-contained module to avoid the need for a Webpack build export module DotNet { - export type JsonReviver = ((key: any, value: any) => any); - const jsonRevivers: JsonReviver[] = []; + export type JsonReviver = ((key: any, value: any) => any); + const jsonRevivers: JsonReviver[] = []; - const jsObjectIdKey = "__jsObjectId"; - const dotNetObjectRefKey = "__dotNetObject"; - const byteArrayRefKey = "__byte[]"; - const dotNetStreamRefKey = "__dotNetStream"; - const jsStreamReferenceLengthKey = "__jsStreamReferenceLength"; + const jsObjectIdKey = "__jsObjectId"; + const dotNetObjectRefKey = "__dotNetObject"; + const byteArrayRefKey = "__byte[]"; + const dotNetStreamRefKey = "__dotNetStream"; + const jsStreamReferenceLengthKey = "__jsStreamReferenceLength"; - // If undefined, no dispatcher has been attached yet. - // If null, this means more than one dispatcher was attached, so no default can be determined. - // Otherwise, there was only one dispatcher registered. We keep track of this instance to keep legacy APIs working. - let defaultCallDispatcher: CallDispatcher | null | undefined; + // If undefined, no dispatcher has been attached yet. + // If null, this means more than one dispatcher was attached, so no default can be determined. + // Otherwise, there was only one dispatcher registered. We keep track of this instance to keep legacy APIs working. + let defaultCallDispatcher: CallDispatcher | null | undefined; - // Provides access to the "current" call dispatcher without having to flow it through nested function calls. - let currentCallDispatcher : CallDispatcher | undefined; + // Provides access to the "current" call dispatcher without having to flow it through nested function calls. + let currentCallDispatcher: CallDispatcher | undefined; - export interface ObjectMemberDescriptor { - parent: any; - name: string; - } + /** + * Represents a resolved property of a JS object. + * + * @param parent The immediate parent object that contains the property. + * @param name The name of the property. + * @param func Reference to a function if the member is a function. Otherwise, undefined. + */ + interface ObjectMemberDescriptor { + parent: any; + name: string; + func?: Function; + } - class JSObject { - _cachedFunctions: Map; + class JSObject { + // TODO(oroztocil): Is it correct to cache functions/properties when they can change? + _cachedMembers: Map; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - constructor(private _jsObject: any) { - this._cachedFunctions = new Map(); - } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(private _jsObject: any) { + this._cachedMembers = new Map(); + } - public findFunction(identifier: string) { - const cachedFunction = this._cachedFunctions.get(identifier); + public findMember(identifier: string): ObjectMemberDescriptor { + if (identifier === "") { + return { parent: this.getWrappedObject(), name: "" }; + } - if (cachedFunction) { - return cachedFunction; - } + const cachedMember = this._cachedMembers.get(identifier); - 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; - } + if (cachedMember) { + return cachedMember; + } - throw new Error(`The value '${identifier}' is not a function.`); - } + let current: any = this._jsObject; + let parent: any = null; - public findMember(identifier: string): ObjectMemberDescriptor { - let current: any = this._jsObject; - let parent: any = null; + const keys = identifier.split("."); - const keys = identifier.split("."); + // First, we iterate over all but the last key. We throw error for missing intermediate keys. + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; - for (const key of keys) { - if (current && typeof current === 'object' && key in current) { - parent = current; - current = current[key]; - } else { - return { parent, name: key }; + if (current && typeof current === 'object' && key in current) { + parent = current; + current = current[key]; + } else { + throw new Error(`Could not find '${identifier}' ('${key}' was undefined).`); + } } + + // We don't check the last key because error handling depends on the type of operation. + const lastKey = keys[keys.length - 1]; + parent = current; + current = current[lastKey]; + + const result = { parent, name: lastKey } as ObjectMemberDescriptor; + + if (typeof current === "function") { + result.func = current.bind(parent); + } + + this._cachedMembers.set(identifier, result); + return result; } - return { parent, name: keys[keys.length - 1] }; + public getWrappedObject() { + return this._jsObject; + } } - public getWrappedObject() { - return this._jsObject; - } - } - - 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 = new URL(url.substring(2), document.baseURI).toString(); - } - - return import(/* webpackIgnore: true */ url); - }); - - let nextJsObjectId = 1; // Start at 1 because zero is reserved for "window" - - /** - * Creates a .NET call dispatcher to use for handling invocations between JavaScript and a .NET runtime. - * - * @param dotNetCallDispatcher An object that can dispatch calls from JavaScript to a .NET runtime. - */ - export function attachDispatcher(dotNetCallDispatcher: DotNetCallDispatcher): ICallDispatcher { - const result = new CallDispatcher(dotNetCallDispatcher); - if (defaultCallDispatcher === undefined) { - // This was the first dispatcher registered, so it becomes the default. This exists purely for - // backwards compatibility. - defaultCallDispatcher = result; - } else if (defaultCallDispatcher) { - // There is already a default dispatcher. Now that there are multiple to choose from, there can - // be no acceptable default, so we nullify the default dispatcher. - defaultCallDispatcher = null; - } - - return result; - } - - /** - * Adds a JSON reviver callback that will be used when parsing arguments received from .NET. - * @param reviver The reviver to add. - */ - export function attachReviver(reviver: JsonReviver) { - jsonRevivers.push(reviver); - } - - /** - * Invokes the specified .NET public method synchronously. Not all hosting scenarios support - * synchronous invocation, so if possible use invokeMethodAsync instead. - * - * @deprecated Use DotNetObject to invoke instance methods instead. - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns The result of the operation. - */ - export function invokeMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T { - const dispatcher = getDefaultCallDispatcher(); - return dispatcher.invokeDotNetStaticMethod(assemblyName, methodIdentifier, ...args); - } - - /** - * Invokes the specified .NET public method asynchronously. - * - * @deprecated Use DotNetObject to invoke instance methods instead. - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns A promise representing the result of the operation. - */ - export function invokeMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise { - const dispatcher = getDefaultCallDispatcher(); - return dispatcher.invokeDotNetStaticMethodAsync(assemblyName, methodIdentifier, ...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; - } - - throw new Error(`Cannot create a JSObjectReference from the value '${jsObject}'.`); - } - - /** - * Creates a JavaScript data reference that can be passed to .NET via interop calls. - * - * @param streamReference The ArrayBufferView or Blob used to create the JavaScript stream reference. - * @returns The JavaScript data reference (this will be the same instance as the given object). - * @throws Error if the given value is not an Object or doesn't have a valid byteLength. - */ - export function createJSStreamReference(streamReference: ArrayBuffer | ArrayBufferView | Blob | any): any { - let length = -1; - - // If we're given a raw Array Buffer, we interpret it as a `Uint8Array` as - // ArrayBuffers' aren't directly readable. - if (streamReference instanceof ArrayBuffer) { - streamReference = new Uint8Array(streamReference); - } - - if (streamReference instanceof Blob) { - length = streamReference.size; - } else if (streamReference.buffer instanceof ArrayBuffer) { - if (streamReference.byteLength === undefined) { - throw new Error(`Cannot create a JSStreamReference from the value '${streamReference}' as it doesn't have a byteLength.`); - } + const windowJSObjectId = 0; + const cachedJSObjectsById: { [id: number]: JSObject } = { + [windowJSObjectId]: new JSObject(window) + }; + + const windowObject = cachedJSObjectsById[windowJSObjectId]; + windowObject._cachedMembers.set("import", { parent: windowObject, name: "import", func: (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 = new URL(url.substring(2), document.baseURI).toString(); + } + + return import(/* webpackIgnore: true */ url); + }}); + + let nextJsObjectId = 1; // Start at 1 because zero is reserved for "window" - length = streamReference.byteLength; - } else { - throw new Error("Supplied value is not a typed array or blob."); - } - - const result: any = { - [jsStreamReferenceLengthKey]: length - }; - - try { - const jsObjectReference = createJSObjectReference(streamReference); - result[jsObjectIdKey] = jsObjectReference[jsObjectIdKey]; - } catch (error) { - throw new Error(`Cannot create a JSStreamReference from the value '${streamReference}'.`); - } - - return result; - } - - /** - * 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); - } - } - - function parseJsonWithRevivers(callDispatcher: CallDispatcher, json: string | null): any { - currentCallDispatcher = callDispatcher; - const result = 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; - currentCallDispatcher = undefined; - return result; - } - - function getDefaultCallDispatcher(): CallDispatcher { - if (defaultCallDispatcher === undefined) { - throw new Error("No call dispatcher has been set."); - } else if (defaultCallDispatcher === null) { - throw new Error("There are multiple .NET runtimes present, so a default dispatcher could not be resolved. Use DotNetObject to invoke .NET instance methods."); - } else { - return defaultCallDispatcher; - } - } - - interface PendingAsyncCall { - resolve: (value?: T | PromiseLike) => void; - reject: (reason?: any) => void; - } - - /** - * Represents the type of result expected from a JS interop call. - */ - // eslint-disable-next-line no-shadow - export enum JSCallResultType { - Default = 0, - JSObjectReference = 1, - JSStreamReference = 2, - JSVoidResult = 3, - } - - /** - * Represents the type of operation that should be performed in JS. - */ - export enum JSCallType { - FunctionCall = 1, - NewCall = 2, - GetValue = 3, - SetValue = 4 - } - - export interface JSInvocationInfo { - asyncHandle: number, - targetInstanceId: number, - identifier: string | null, - callType: JSCallType, - resultType: JSCallResultType, - argsJson: string | null, - } - - /** - * Represents the ability to dispatch calls from JavaScript to a .NET runtime. - */ - export interface DotNetCallDispatcher { /** - * Optional. If implemented, invoked by the runtime to perform a synchronous call to a .NET method. + * Creates a .NET call dispatcher to use for handling invocations between JavaScript and a .NET runtime. * - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null or undefined to call static methods. - * @param argsJson JSON representation of arguments to pass to the method. - * @returns JSON representation of the result of the invocation. + * @param dotNetCallDispatcher An object that can dispatch calls from JavaScript to a .NET runtime. + */ + export function attachDispatcher(dotNetCallDispatcher: DotNetCallDispatcher): ICallDispatcher { + const result = new CallDispatcher(dotNetCallDispatcher); + if (defaultCallDispatcher === undefined) { + // This was the first dispatcher registered, so it becomes the default. This exists purely for + // backwards compatibility. + defaultCallDispatcher = result; + } else if (defaultCallDispatcher) { + // There is already a default dispatcher. Now that there are multiple to choose from, there can + // be no acceptable default, so we nullify the default dispatcher. + defaultCallDispatcher = null; + } + + return result; + } + + /** + * Adds a JSON reviver callback that will be used when parsing arguments received from .NET. + * @param reviver The reviver to add. */ - invokeDotNetFromJS?(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): string | null; + export function attachReviver(reviver: JsonReviver) { + jsonRevivers.push(reviver); + } /** - * Invoked by the runtime to begin an asynchronous call to a .NET method. + * Invokes the specified .NET public method synchronously. Not all hosting scenarios support + * synchronous invocation, so if possible use invokeMethodAsync instead. * - * @param callId A value identifying the asynchronous operation. This value should be passed back in a later call from .NET to JS. - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods. + * @deprecated Use DotNetObject to invoke instance methods instead. + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null to call static methods. - * @param argsJson JSON representation of arguments to pass to the method. + * @param args Arguments to pass to the method, each of which must be JSON-serializable. + * @returns The result of the operation. */ - beginInvokeDotNetFromJS(callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): void; + export function invokeMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T { + const dispatcher = getDefaultCallDispatcher(); + return dispatcher.invokeDotNetStaticMethod(assemblyName, methodIdentifier, ...args); + } /** - * Invoked by the runtime to complete an asynchronous JavaScript function call started from .NET + * Invokes the specified .NET public method asynchronously. * - * @param callId A value identifying the asynchronous operation. - * @param succeded Whether the operation succeeded or not. - * @param resultOrError The serialized result or the serialized error from the async operation. + * @deprecated Use DotNetObject to invoke instance methods instead. + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param args Arguments to pass to the method, each of which must be JSON-serializable. + * @returns A promise representing the result of the operation. */ - endInvokeJSFromDotNet(callId: number, succeeded: boolean, resultOrError: any): void; + export function invokeMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise { + const dispatcher = getDefaultCallDispatcher(); + return dispatcher.invokeDotNetStaticMethodAsync(assemblyName, methodIdentifier, ...args); + } /** - * Invoked by the runtime to transfer a byte array from JS to .NET. - * @param id The identifier for the byte array used during revival. - * @param data The byte array being transferred for eventual revival. + * 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. */ - sendByteArray(id: number, data: Uint8Array): void; - } + export function createJSObjectReference(jsObject: any): any { + if (jsObject && typeof jsObject === "object") { + cachedJSObjectsById[nextJsObjectId] = new JSObject(jsObject); + + const result = { + [jsObjectIdKey]: nextJsObjectId + }; + + nextJsObjectId++; + + return result; + } + + throw new Error(`Cannot create a JSObjectReference from the value '${jsObject}'.`); + } - /** - * Represents the ability to facilitate function call dispatching between JavaScript and a .NET runtime. - */ - export interface ICallDispatcher { /** - * Invokes the specified synchronous JavaScript function. + * Creates a JavaScript data reference that can be passed to .NET via interop calls. * - * @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. + * @param streamReference The ArrayBufferView or Blob used to create the JavaScript stream reference. + * @returns The JavaScript data reference (this will be the same instance as the given object). + * @throws Error if the given value is not an Object or doesn't have a valid byteLength. */ - invokeJSFromDotNet(identifier: string, argsJson: string, resultType: JSCallResultType, targetInstanceId: number): string | null; - // TODO(OR): Add support for CallType, refactor to use JSInvocationInfo + export function createJSStreamReference(streamReference: ArrayBuffer | ArrayBufferView | Blob | any): any { + let length = -1; + + // If we're given a raw Array Buffer, we interpret it as a `Uint8Array` as + // ArrayBuffers' aren't directly readable. + if (streamReference instanceof ArrayBuffer) { + streamReference = new Uint8Array(streamReference); + } + + if (streamReference instanceof Blob) { + length = streamReference.size; + } else if (streamReference.buffer instanceof ArrayBuffer) { + if (streamReference.byteLength === undefined) { + throw new Error(`Cannot create a JSStreamReference from the value '${streamReference}' as it doesn't have a byteLength.`); + } + + length = streamReference.byteLength; + } else { + throw new Error("Supplied value is not a typed array or blob."); + } + + const result: any = { + [jsStreamReferenceLengthKey]: length + }; + + try { + const jsObjectReference = createJSObjectReference(streamReference); + result[jsObjectIdKey] = jsObjectReference[jsObjectIdKey]; + } catch (error) { + throw new Error(`Cannot create a JSStreamReference from the value '${streamReference}'.`); + } + + return result; + } /** - * Invokes the specified synchronous or asynchronous JavaScript function. + * Disposes the given JavaScript object reference. * - * @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 callType The type of operation that should be performed in JS. - * @param resultType The type of result expected from the JS interop call. - * @param targetInstanceId The ID of the target JS object instance. + * @param jsObjectReference The JavaScript Object reference. */ - beginInvokeJSFromDotNet(invocationInfoString: string): void; + export function disposeJSObjectReference(jsObjectReference: any): void { + const id = jsObjectReference && jsObjectReference[jsObjectIdKey]; + + if (typeof id === "number") { + disposeJSObjectReferenceById(id); + } + } + + function parseJsonWithRevivers(callDispatcher: CallDispatcher, json: string | null): any { + currentCallDispatcher = callDispatcher; + const result = 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; + currentCallDispatcher = undefined; + return result; + } + + function getDefaultCallDispatcher(): CallDispatcher { + if (defaultCallDispatcher === undefined) { + throw new Error("No call dispatcher has been set."); + } else if (defaultCallDispatcher === null) { + throw new Error("There are multiple .NET runtimes present, so a default dispatcher could not be resolved. Use DotNetObject to invoke .NET instance methods."); + } else { + return defaultCallDispatcher; + } + } + + interface PendingAsyncCall { + resolve: (value?: T | PromiseLike) => void; + reject: (reason?: any) => void; + } /** - * Receives notification that an async call from JS to .NET has completed. - * @param asyncCallId The identifier supplied in an earlier call to beginInvokeDotNetFromJS. - * @param success A flag to indicate whether the operation completed successfully. - * @param resultJsonOrExceptionMessage Either the operation result as JSON, or an error message. + * Represents the type of result expected from a JS interop call. */ - endInvokeDotNetFromJS(asyncCallId: string, success: boolean, resultJsonOrExceptionMessage: string): void; + // eslint-disable-next-line no-shadow + export enum JSCallResultType { + Default = 0, + JSObjectReference = 1, + JSStreamReference = 2, + JSVoidResult = 3, + } /** - * Invokes the specified .NET public static method synchronously. Not all hosting scenarios support - * synchronous invocation, so if possible use invokeMethodAsync instead. - * - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns The result of the operation. + * Represents the type of operation that should be performed in JS. */ - invokeDotNetStaticMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T; + export enum JSCallType { + FunctionCall = 1, + NewCall = 2, + GetValue = 3, + SetValue = 4 + } /** - * Invokes the specified .NET public static method asynchronously. - * - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns A promise representing the result of the operation. + * @param asyncHandle A value identifying an asynchronous operation that is passed back in a later call to endInvokeJSFromDotNet. If the call is synchronous, this value is zero. + * @param targetInstanceId The ID of the target JS object instance. + * @param identifier The identifier of the function to invoke or property to access. + * @param callType The type of operation that should be performed in JS. + * @param resultType The type of result expected from the JS interop call. + * @param argsJson JSON array of arguments to be passed to the operation. First element is used when setting a property value. */ - invokeDotNetStaticMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise; + export interface JSInvocationInfo { + asyncHandle: number, + targetInstanceId: number, + identifier: string, + callType: JSCallType, + resultType: JSCallResultType, + argsJson: string | null, + } /** - * Receives notification that a byte array is being transferred from .NET to JS. - * @param id The identifier for the byte array used during revival. - * @param data The byte array being transferred for eventual revival. + * Represents the ability to dispatch calls from JavaScript to a .NET runtime. */ - receiveByteArray(id: number, data: Uint8Array): void + export interface DotNetCallDispatcher { + /** + * Optional. If implemented, invoked by the runtime to perform a synchronous call to a .NET method. + * + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null or undefined to call static methods. + * @param argsJson JSON representation of arguments to pass to the method. + * @returns JSON representation of the result of the invocation. + */ + invokeDotNetFromJS?(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): string | null; + + /** + * Invoked by the runtime to begin an asynchronous call to a .NET method. + * + * @param callId A value identifying the asynchronous operation. This value should be passed back in a later call from .NET to JS. + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null to call static methods. + * @param argsJson JSON representation of arguments to pass to the method. + */ + beginInvokeDotNetFromJS(callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): void; + + /** + * Invoked by the runtime to complete an asynchronous JavaScript function call started from .NET + * + * @param callId A value identifying the asynchronous operation. + * @param succeded Whether the operation succeeded or not. + * @param resultOrError The serialized result or the serialized error from the async operation. + */ + endInvokeJSFromDotNet(callId: number, succeeded: boolean, resultOrError: any): void; + + /** + * Invoked by the runtime to transfer a byte array from JS to .NET. + * @param id The identifier for the byte array used during revival. + * @param data The byte array being transferred for eventual revival. + */ + sendByteArray(id: number, data: Uint8Array): void; + } /** - * Supplies a stream of data being sent from .NET. - * - * @param streamId The identifier previously passed to JSRuntime's BeginTransmittingStream in .NET code. - * @param stream The stream data. + * Represents the ability to facilitate function call dispatching between JavaScript and a .NET runtime. */ - supplyDotNetStream(streamId: number, stream: ReadableStream): void; - } + export interface ICallDispatcher { + /** + * Invokes the specified synchronous JavaScript function. + * + * @param invocationInfo Configuration of the interop call. + */ + invokeJSFromDotNet(invocationInfo: JSInvocationInfo): string | null; + + /** + * Invokes the specified synchronous or asynchronous JavaScript function. + * + * @param invocationInfo Configuration of the interop call. + */ + beginInvokeJSFromDotNet(invocationInfo: JSInvocationInfo): void; + + /** + * Receives notification that an async call from JS to .NET has completed. + * @param asyncCallId The identifier supplied in an earlier call to beginInvokeDotNetFromJS. + * @param success A flag to indicate whether the operation completed successfully. + * @param resultJsonOrExceptionMessage Either the operation result as JSON, or an error message. + */ + endInvokeDotNetFromJS(asyncCallId: string, success: boolean, resultJsonOrExceptionMessage: string): void; + + /** + * Invokes the specified .NET public static method synchronously. Not all hosting scenarios support + * synchronous invocation, so if possible use invokeMethodAsync instead. + * + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param args Arguments to pass to the method, each of which must be JSON-serializable. + * @returns The result of the operation. + */ + invokeDotNetStaticMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T; + + /** + * Invokes the specified .NET public static method asynchronously. + * + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param args Arguments to pass to the method, each of which must be JSON-serializable. + * @returns A promise representing the result of the operation. + */ + invokeDotNetStaticMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise; + + /** + * Receives notification that a byte array is being transferred from .NET to JS. + * @param id The identifier for the byte array used during revival. + * @param data The byte array being transferred for eventual revival. + */ + receiveByteArray(id: number, data: Uint8Array): void + + /** + * Supplies a stream of data being sent from .NET. + * + * @param streamId The identifier previously passed to JSRuntime's BeginTransmittingStream in .NET code. + * @param stream The stream data. + */ + supplyDotNetStream(streamId: number, stream: ReadableStream): void; + } - class CallDispatcher implements ICallDispatcher { - private readonly _byteArraysToBeRevived = new Map(); + class CallDispatcher implements ICallDispatcher { + private readonly _byteArraysToBeRevived = new Map(); - private readonly _pendingDotNetToJSStreams = new Map(); + private readonly _pendingDotNetToJSStreams = new Map(); - private readonly _pendingAsyncCalls: { [id: number]: PendingAsyncCall } = {}; + private readonly _pendingAsyncCalls: { [id: number]: PendingAsyncCall } = {}; - private _nextAsyncCallId = 1; // Start at 1 because zero signals "no response needed" + private _nextAsyncCallId = 1; // Start at 1 because zero signals "no response needed" - // eslint-disable-next-line no-empty-function - constructor(private readonly _dotNetCallDispatcher: DotNetCallDispatcher) { - } + // eslint-disable-next-line no-empty-function + constructor(private readonly _dotNetCallDispatcher: DotNetCallDispatcher) { + } - getDotNetCallDispatcher(): DotNetCallDispatcher { - return this._dotNetCallDispatcher; - } + getDotNetCallDispatcher(): DotNetCallDispatcher { + return this._dotNetCallDispatcher; + } - // TODO(OR): Add support for CallType, refactor to use JSInvocationInfo - invokeJSFromDotNet(identifier: string, argsJson: string, resultType: JSCallResultType, targetInstanceId: number): string | null { - const args = parseJsonWithRevivers(this, argsJson); - const jsFunction = findJSFunction(identifier, targetInstanceId); - const returnValue = jsFunction(...(args || [])); - const result = createJSCallResult(returnValue, resultType); + invokeJSFromDotNet(invocationInfo: JSInvocationInfo): string | null { + const { targetInstanceId, identifier, callType, resultType, argsJson } = invocationInfo; + const returnValue = this.handleJSCall(targetInstanceId, identifier, callType, argsJson); + const result = createJSCallResult(returnValue, resultType); - return result === null || result === undefined - ? null - : stringifyArgs(this, result); - } + return result === null || result === undefined + ? null + : stringifyArgs(this, result); + } - handleJSFunctionCall(identifier: string, member: ObjectMemberDescriptor, args: any): any { - const func = member.parent[member.name]; + beginInvokeJSFromDotNet(invocationInfo: JSInvocationInfo): void { + const { asyncHandle, targetInstanceId, identifier, callType, resultType, argsJson } = invocationInfo; + + // Coerce synchronous functions into async ones, plus treat + // synchronous exceptions the same as async ones + const promise = new Promise(resolve => { + const valueOrPromise = this.handleJSCall(targetInstanceId, identifier, callType, argsJson); + resolve(valueOrPromise); + }); + + // We only listen for a result if the caller wants to be notified about it + if (asyncHandle) { + // On completion, dispatch result back to .NET + // Not using "await" because it codegens a lot of boilerplate + promise. + then(result => stringifyArgs(this, [ + asyncHandle, + true, + createJSCallResult(result, resultType) + ])). + then( + result => this._dotNetCallDispatcher.endInvokeJSFromDotNet(asyncHandle, true, result), + error => this._dotNetCallDispatcher.endInvokeJSFromDotNet(asyncHandle, false, JSON.stringify([ + asyncHandle, + false, + formatError(error) + ])) + ); + } + } - if (typeof func === "function") { - return func.call(member.parent, ...(args || [])); - } else { - throw new Error(`The value '${identifier}' is not a function.`); - } - } - - handleJSNewCall(identifier: string, member: ObjectMemberDescriptor, args: any): any { - const func = member.parent[member.name]; - - if (typeof func === "function") { - try { - // The new call throws if the function is not constructible (e.g. an arrow function) - return new func(...(args || [])); - } catch (err) { - if (err instanceof TypeError) { - throw new Error(`The value '${identifier}' is not a constructor function.`); - } else { - throw err; - } - } - } else { - throw new Error(`The value '${identifier}' is not a function.`); - } - } - - handleJSPropertyGet(identifier: string, instance: ObjectMemberDescriptor): any { - return identifier.length > 0 ? instance.parent[instance.name] : instance.parent; - } - - handleJSPropertySet(identifier: string, instance: ObjectMemberDescriptor, args: any) { - const value = args[0]; - instance.parent[instance.name] = value; - } - - handleJSCall(identifier: string | null, argsJson: string | null, targetInstanceId: number, callType: JSCallType) { - identifier ??= ""; - const args = parseJsonWithRevivers(this, argsJson); - const member = findObjectMember(identifier, targetInstanceId); - let returnValue = null; - - if (callType === JSCallType.FunctionCall) { - returnValue = this.handleJSFunctionCall(identifier, member, args); - } else if (callType === JSCallType.NewCall) { - returnValue = this.handleJSNewCall(identifier, member, args); - } else if (callType === JSCallType.GetValue) { - returnValue = this.handleJSPropertyGet(identifier, member); - } else if (callType === JSCallType.SetValue) { - this.handleJSPropertySet(identifier, member, args); - } + handleJSCall(targetInstanceId: number, identifier: string, callType: JSCallType, argsJson: string | null) { + const args = parseJsonWithRevivers(this, argsJson) ?? []; + const member = findObjectMember(identifier, targetInstanceId); + let returnValue = null; + + if (callType === JSCallType.FunctionCall) { + returnValue = this.handleJSFunctionCall(identifier, member.func, args); + } else if (callType === JSCallType.NewCall) { + returnValue = this.handleJSNewCall(identifier, member.func, args); + } else if (callType === JSCallType.GetValue) { + returnValue = this.handleJSPropertyGet(identifier, member); + } else if (callType === JSCallType.SetValue) { + this.handleJSPropertySet(identifier, member, args); + } else { + throw new Error(`Unsupported call type '${callType}'.`); + } - return returnValue; - } - - beginInvokeJSFromDotNet(invocationInfoString: string): void { - // TODO(OR): Remove log - console.log(invocationInfoString); - const invocationInfo: JSInvocationInfo = JSON.parse(invocationInfoString); - - // TODO(OR): Remove log - console.log(invocationInfo); - const { asyncHandle, identifier, argsJson, callType, resultType, targetInstanceId } = invocationInfo; - - // Coerce synchronous functions into async ones, plus treat - // synchronous exceptions the same as async ones - const promise = new Promise(resolve => { - const valueOrPromise = this.handleJSCall(identifier, argsJson, targetInstanceId, callType); - resolve(valueOrPromise); - }); - - // We only listen for a result if the caller wants to be notified about it - if (asyncHandle) { - // On completion, dispatch result back to .NET - // Not using "await" because it codegens a lot of boilerplate - promise. - then(result => stringifyArgs(this, [ - asyncHandle, - true, - createJSCallResult(result, resultType) - ])). - then( - result => this._dotNetCallDispatcher.endInvokeJSFromDotNet(asyncHandle, true, result), - error => this._dotNetCallDispatcher.endInvokeJSFromDotNet(asyncHandle, false, JSON.stringify([ - asyncHandle, - false, - formatError(error) - ])) - ); - } - } - - endInvokeDotNetFromJS(asyncCallId: string, success: boolean, resultJsonOrExceptionMessage: string): void { - const resultOrError = success - ? parseJsonWithRevivers(this, resultJsonOrExceptionMessage) - : new Error(resultJsonOrExceptionMessage); - this.completePendingCall(parseInt(asyncCallId, 10), success, resultOrError); - } - - invokeDotNetStaticMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T { - return this.invokeDotNetMethod(assemblyName, methodIdentifier, null, args); - } - - invokeDotNetStaticMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise { - return this.invokeDotNetMethodAsync(assemblyName, methodIdentifier, null, args); - } - - invokeDotNetMethod(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): T { - if (this._dotNetCallDispatcher.invokeDotNetFromJS) { - const argsJson = stringifyArgs(this, args); - const resultJson = this._dotNetCallDispatcher.invokeDotNetFromJS(assemblyName, methodIdentifier, dotNetObjectId, argsJson); - return resultJson ? parseJsonWithRevivers(this, resultJson) : null; - } + return returnValue; + } - throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeDotNetMethodAsync instead."); - } + handleJSFunctionCall(identifier: string, func: any, args: unknown[]): any { + if (typeof func === "function") { + return func(...args); + } else { + throw new Error(`The value '${identifier}' is not a function.`); + } + } - invokeDotNetMethodAsync(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): Promise { - if (assemblyName && dotNetObjectId) { - throw new Error(`For instance method calls, assemblyName should be null. Received '${assemblyName}'.`); - } + handleJSNewCall(identifier: string, func: any, args: unknown[]): any { + if (typeof func === "function") { + try { + // The new call throws if the function is not constructible (e.g. an arrow function) + return new func(...args); + } catch (err) { + if (err instanceof TypeError) { + throw new Error(`The value '${identifier}' is not a constructor function.`); + } else { + throw err; + } + } + } else { + throw new Error(`The value '${identifier}' is not a function.`); + } + } - const asyncCallId = this._nextAsyncCallId++; - const resultPromise = new Promise((resolve, reject) => { - this._pendingAsyncCalls[asyncCallId] = { resolve, reject }; - }); - - try { - const argsJson = stringifyArgs(this, args); - this._dotNetCallDispatcher.beginInvokeDotNetFromJS(asyncCallId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); - } catch (ex) { - // Synchronous failure - this.completePendingCall(asyncCallId, false, ex); - } + handleJSPropertyGet(identifier: string, property: ObjectMemberDescriptor): any { + if (property.name === "") { + return property.parent; + } - return resultPromise; - } + // TODO(oroztocil): Do we want to throw on undefined property or return null? + if (!this.isReadableProperty(property.parent, property.name)) { + throw new Error(`The property '${identifier}' is not defined or is not readable.`); + } - receiveByteArray(id: number, data: Uint8Array): void { - this._byteArraysToBeRevived.set(id, data); - } + // For empty identifier, we return the object itself to support "dereferencing" JS object references + return property.parent[property.name]; + } - processByteArray(id: number): Uint8Array | null { - const result = this._byteArraysToBeRevived.get(id); - if (!result) { - return null; - } + handleJSPropertySet(identifier: string, property: ObjectMemberDescriptor, args: unknown[]) { + // TODO(OR): Test with get only properties + if (!this.isWritableProperty(property.parent, property.name)) { + throw new Error(`The property '${identifier}' does is not writable.`); + } - this._byteArraysToBeRevived.delete(id); - return result; - } - - supplyDotNetStream(streamId: number, stream: ReadableStream) { - if (this._pendingDotNetToJSStreams.has(streamId)) { - // The receiver is already waiting, so we can resolve the promise now and stop tracking this - const pendingStream = this._pendingDotNetToJSStreams.get(streamId)!; - this._pendingDotNetToJSStreams.delete(streamId); - pendingStream.resolve!(stream); - } else { - // The receiver hasn't started waiting yet, so track a pre-completed entry it can attach to later - const pendingStream = new PendingStream(); - pendingStream.resolve!(stream); - this._pendingDotNetToJSStreams.set(streamId, pendingStream); - } - } - - getDotNetStreamPromise(streamId: number): Promise { - // We might already have started receiving the stream, or maybe it will come later. - // We have to handle both possible orderings, but we can count on it coming eventually because - // it's not something the developer gets to control, and it would be an error if it doesn't. - let result: Promise; - if (this._pendingDotNetToJSStreams.has(streamId)) { - // We've already started receiving the stream, so no longer need to track it as pending - result = this._pendingDotNetToJSStreams.get(streamId)!.streamPromise!; - this._pendingDotNetToJSStreams.delete(streamId); - } else { - // We haven't started receiving it yet, so add an entry to track it as pending - const pendingStream = new PendingStream(); - this._pendingDotNetToJSStreams.set(streamId, pendingStream); - result = pendingStream.streamPromise; - } + const value = args[0]; + property.parent[property.name] = value; + } - return result; - } + isReadableProperty(obj: any, propName: string) { + const descriptor = Object.getOwnPropertyDescriptor(obj, propName); - private completePendingCall(asyncCallId: number, success: boolean, resultOrError: any) { - if (!this._pendingAsyncCalls.hasOwnProperty(asyncCallId)) { - throw new Error(`There is no pending async call with ID ${asyncCallId}.`); - } + if (!descriptor) { + return false; + } - const asyncCall = this._pendingAsyncCalls[asyncCallId]; - delete this._pendingAsyncCalls[asyncCallId]; - if (success) { - asyncCall.resolve(resultOrError); - } else { - asyncCall.reject(resultOrError); + // Return true for data property + if (descriptor.hasOwnProperty('value')) { + return true + } + + // Return true for accessor property with defined getter + return descriptor.hasOwnProperty('get') && typeof descriptor.get === 'function'; } - } - } - - function formatError(error: Error | string): string { - if (error instanceof Error) { - return `${error.message}\n${error.stack}`; - } - - return error ? error.toString() : "null"; - } - - export function findJSFunction(identifier: string, targetInstanceId: number): Function { - const targetInstance = cachedJSObjectsById[targetInstanceId]; - - if (targetInstance) { - return targetInstance.findFunction(identifier); - } - - throw new Error(`JS object instance with ID ${targetInstanceId} does not exist (has it been disposed?).`); - } - - export function findObjectMember(identifier: string, targetInstanceId: number): ObjectMemberDescriptor { - // TODO(OR): Remove log - console.log("findObjectMember", identifier, targetInstanceId) - const targetInstance = cachedJSObjectsById[targetInstanceId]; - - if (!targetInstance) { - throw new Error(`JS object instance with ID ${targetInstanceId} does not exist (has it been disposed?).`); - } - - if (identifier === "") { - return { parent: targetInstance.getWrappedObject(), name: "" }; - } - - return targetInstance.findMember(identifier); - } - - export function disposeJSObjectReferenceById(id: number) { - delete cachedJSObjectsById[id]; - } - - export class DotNetObject { - // eslint-disable-next-line no-empty-function - constructor(private readonly _id: number, private readonly _callDispatcher: CallDispatcher) { - } - - public invokeMethod(methodIdentifier: string, ...args: any[]): T { - return this._callDispatcher.invokeDotNetMethod(null, methodIdentifier, this._id, args); - } - - public invokeMethodAsync(methodIdentifier: string, ...args: any[]): Promise { - return this._callDispatcher.invokeDotNetMethodAsync(null, methodIdentifier, this._id, args); - } - - public dispose() { - const promise = this._callDispatcher.invokeDotNetMethodAsync(null, "__Dispose", this._id, null); - promise.catch(error => console.error(error)); - } - - public serializeAsArg() { - return { [dotNetObjectRefKey]: this._id }; - } - } - - attachReviver(function reviveReference(key: any, value: any) { - if (value && typeof value === "object") { - if (value.hasOwnProperty(dotNetObjectRefKey)) { - return new DotNetObject(value[dotNetObjectRefKey], currentCallDispatcher!); - } else if (value.hasOwnProperty(jsObjectIdKey)) { - const id = value[jsObjectIdKey]; - const jsObject = cachedJSObjectsById[id]; - - if (jsObject) { - return jsObject.getWrappedObject(); - } - - throw new Error(`JS object instance with Id '${id}' does not exist. It may have been disposed.`); - } else if (value.hasOwnProperty(byteArrayRefKey)) { - const index = value[byteArrayRefKey]; - const byteArray = currentCallDispatcher!.processByteArray(index); - if (byteArray === undefined) { - throw new Error(`Byte array index '${index}' does not exist.`); - } - return byteArray; - } else if (value.hasOwnProperty(dotNetStreamRefKey)) { - const streamId = value[dotNetStreamRefKey]; - const streamPromise = currentCallDispatcher!.getDotNetStreamPromise(streamId); - return new DotNetStream(streamPromise); + + isWritableProperty(obj: any, propName: string) { + const descriptor = Object.getOwnPropertyDescriptor(obj, propName); + + // Return true for undefined property if the property can be added + if (!descriptor) { + return Object.isExtensible(obj); + } + + // Return true for writable data property + if (descriptor.hasOwnProperty('value') && descriptor.writable) { + return true; + } + + // Return true for accessor property with defined setter + return descriptor.hasOwnProperty('set') && typeof descriptor.set === 'function'; } - } - - // Unrecognized - let another reviver handle it - return value; - }); - - class DotNetStream { - // eslint-disable-next-line no-empty-function - constructor(private readonly _streamPromise: Promise) { - } - - /** - * Supplies a readable stream of data being sent from .NET. - */ - stream(): Promise { - return this._streamPromise; - } - - /** - * Supplies a array buffer of data being sent from .NET. - * Note there is a JavaScript limit on the size of the ArrayBuffer equal to approximately 2GB. - */ - async arrayBuffer(): Promise { - return new Response(await this.stream()).arrayBuffer(); - } - } - - class PendingStream { - streamPromise: Promise; - - resolve?: (value: ReadableStream) => void; - - reject?: (reason: any) => void; - - constructor() { - this.streamPromise = new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - }); - } - } - - function createJSCallResult(returnValue: any, resultType: JSCallResultType) { - switch (resultType) { - case JSCallResultType.Default: - return returnValue; - case JSCallResultType.JSObjectReference: - return createJSObjectReference(returnValue); - case JSCallResultType.JSStreamReference: - return createJSStreamReference(returnValue); - case JSCallResultType.JSVoidResult: - return null; - default: - throw new Error(`Invalid JS call result type '${resultType}'.`); - } - } - - let nextByteArrayIndex = 0; - function stringifyArgs(callDispatcher: CallDispatcher, args: any[] | null) { - nextByteArrayIndex = 0; - currentCallDispatcher = callDispatcher; - const result = JSON.stringify(args, argReplacer); - currentCallDispatcher = undefined; - return result; - } - - function argReplacer(key: string, value: any) { - if (value instanceof DotNetObject) { - return value.serializeAsArg(); - } else if (value instanceof Uint8Array) { - const dotNetCallDispatcher = currentCallDispatcher!.getDotNetCallDispatcher(); - dotNetCallDispatcher!.sendByteArray(nextByteArrayIndex, value); - const jsonValue = { [byteArrayRefKey]: nextByteArrayIndex }; - nextByteArrayIndex++; - return jsonValue; - } - - return value; - } + + endInvokeDotNetFromJS(asyncCallId: string, success: boolean, resultJsonOrExceptionMessage: string): void { + const resultOrError = success + ? parseJsonWithRevivers(this, resultJsonOrExceptionMessage) + : new Error(resultJsonOrExceptionMessage); + this.completePendingCall(parseInt(asyncCallId, 10), success, resultOrError); + } + + invokeDotNetStaticMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T { + return this.invokeDotNetMethod(assemblyName, methodIdentifier, null, args); + } + + invokeDotNetStaticMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise { + return this.invokeDotNetMethodAsync(assemblyName, methodIdentifier, null, args); + } + + invokeDotNetMethod(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): T { + if (this._dotNetCallDispatcher.invokeDotNetFromJS) { + const argsJson = stringifyArgs(this, args); + const resultJson = this._dotNetCallDispatcher.invokeDotNetFromJS(assemblyName, methodIdentifier, dotNetObjectId, argsJson); + return resultJson ? parseJsonWithRevivers(this, resultJson) : null; + } + + throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeDotNetMethodAsync instead."); + } + + invokeDotNetMethodAsync(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): Promise { + if (assemblyName && dotNetObjectId) { + throw new Error(`For instance method calls, assemblyName should be null. Received '${assemblyName}'.`); + } + + const asyncCallId = this._nextAsyncCallId++; + const resultPromise = new Promise((resolve, reject) => { + this._pendingAsyncCalls[asyncCallId] = { resolve, reject }; + }); + + try { + const argsJson = stringifyArgs(this, args); + this._dotNetCallDispatcher.beginInvokeDotNetFromJS(asyncCallId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); + } catch (ex) { + // Synchronous failure + this.completePendingCall(asyncCallId, false, ex); + } + + return resultPromise; + } + + receiveByteArray(id: number, data: Uint8Array): void { + this._byteArraysToBeRevived.set(id, data); + } + + processByteArray(id: number): Uint8Array | null { + const result = this._byteArraysToBeRevived.get(id); + if (!result) { + return null; + } + + this._byteArraysToBeRevived.delete(id); + return result; + } + + supplyDotNetStream(streamId: number, stream: ReadableStream) { + if (this._pendingDotNetToJSStreams.has(streamId)) { + // The receiver is already waiting, so we can resolve the promise now and stop tracking this + const pendingStream = this._pendingDotNetToJSStreams.get(streamId)!; + this._pendingDotNetToJSStreams.delete(streamId); + pendingStream.resolve!(stream); + } else { + // The receiver hasn't started waiting yet, so track a pre-completed entry it can attach to later + const pendingStream = new PendingStream(); + pendingStream.resolve!(stream); + this._pendingDotNetToJSStreams.set(streamId, pendingStream); + } + } + + getDotNetStreamPromise(streamId: number): Promise { + // We might already have started receiving the stream, or maybe it will come later. + // We have to handle both possible orderings, but we can count on it coming eventually because + // it's not something the developer gets to control, and it would be an error if it doesn't. + let result: Promise; + if (this._pendingDotNetToJSStreams.has(streamId)) { + // We've already started receiving the stream, so no longer need to track it as pending + result = this._pendingDotNetToJSStreams.get(streamId)!.streamPromise!; + this._pendingDotNetToJSStreams.delete(streamId); + } else { + // We haven't started receiving it yet, so add an entry to track it as pending + const pendingStream = new PendingStream(); + this._pendingDotNetToJSStreams.set(streamId, pendingStream); + result = pendingStream.streamPromise; + } + + return result; + } + + private completePendingCall(asyncCallId: number, success: boolean, resultOrError: any) { + if (!this._pendingAsyncCalls.hasOwnProperty(asyncCallId)) { + throw new Error(`There is no pending async call with ID ${asyncCallId}.`); + } + + const asyncCall = this._pendingAsyncCalls[asyncCallId]; + delete this._pendingAsyncCalls[asyncCallId]; + if (success) { + asyncCall.resolve(resultOrError); + } else { + asyncCall.reject(resultOrError); + } + } + } + + function formatError(error: Error | string): string { + if (error instanceof Error) { + return `${error.message}\n${error.stack}`; + } + + return error ? error.toString() : "null"; + } + + export function findJSFunction(identifier: string, targetInstanceId: number): Function { + const targetInstance = cachedJSObjectsById[targetInstanceId]; + + if (targetInstance) { + const member = targetInstance.findMember(identifier); + + if (!member.func) { + throw new Error(`The value '${identifier}' is not a function.`); + } + + return member.func; + } + + throw new Error(`JS object instance with ID ${targetInstanceId} does not exist (has it been disposed?).`); + } + + export function findObjectMember(identifier: string, targetInstanceId: number): ObjectMemberDescriptor { + const targetInstance = cachedJSObjectsById[targetInstanceId]; + + if (!targetInstance) { + throw new Error(`JS object instance with ID ${targetInstanceId} does not exist (has it been disposed?).`); + } + + return targetInstance.findMember(identifier); + } + + export function disposeJSObjectReferenceById(id: number) { + delete cachedJSObjectsById[id]; + } + + export class DotNetObject { + // eslint-disable-next-line no-empty-function + constructor(private readonly _id: number, private readonly _callDispatcher: CallDispatcher) { + } + + public invokeMethod(methodIdentifier: string, ...args: any[]): T { + return this._callDispatcher.invokeDotNetMethod(null, methodIdentifier, this._id, args); + } + + public invokeMethodAsync(methodIdentifier: string, ...args: any[]): Promise { + return this._callDispatcher.invokeDotNetMethodAsync(null, methodIdentifier, this._id, args); + } + + public dispose() { + const promise = this._callDispatcher.invokeDotNetMethodAsync(null, "__Dispose", this._id, null); + promise.catch(error => console.error(error)); + } + + public serializeAsArg() { + return { [dotNetObjectRefKey]: this._id }; + } + } + + attachReviver(function reviveReference(key: any, value: any) { + if (value && typeof value === "object") { + if (value.hasOwnProperty(dotNetObjectRefKey)) { + return new DotNetObject(value[dotNetObjectRefKey], currentCallDispatcher!); + } else if (value.hasOwnProperty(jsObjectIdKey)) { + const id = value[jsObjectIdKey]; + const jsObject = cachedJSObjectsById[id]; + + if (jsObject) { + return jsObject.getWrappedObject(); + } + + throw new Error(`JS object instance with Id '${id}' does not exist. It may have been disposed.`); + } else if (value.hasOwnProperty(byteArrayRefKey)) { + const index = value[byteArrayRefKey]; + const byteArray = currentCallDispatcher!.processByteArray(index); + if (byteArray === undefined) { + throw new Error(`Byte array index '${index}' does not exist.`); + } + return byteArray; + } else if (value.hasOwnProperty(dotNetStreamRefKey)) { + const streamId = value[dotNetStreamRefKey]; + const streamPromise = currentCallDispatcher!.getDotNetStreamPromise(streamId); + return new DotNetStream(streamPromise); + } + } + + // Unrecognized - let another reviver handle it + return value; + }); + + class DotNetStream { + // eslint-disable-next-line no-empty-function + constructor(private readonly _streamPromise: Promise) { + } + + /** + * Supplies a readable stream of data being sent from .NET. + */ + stream(): Promise { + return this._streamPromise; + } + + /** + * Supplies a array buffer of data being sent from .NET. + * Note there is a JavaScript limit on the size of the ArrayBuffer equal to approximately 2GB. + */ + async arrayBuffer(): Promise { + return new Response(await this.stream()).arrayBuffer(); + } + } + + class PendingStream { + streamPromise: Promise; + + resolve?: (value: ReadableStream) => void; + + reject?: (reason: any) => void; + + constructor() { + this.streamPromise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + } + } + + function createJSCallResult(returnValue: any, resultType: JSCallResultType) { + switch (resultType) { + case JSCallResultType.Default: + return returnValue; + case JSCallResultType.JSObjectReference: + return createJSObjectReference(returnValue); + case JSCallResultType.JSStreamReference: + return createJSStreamReference(returnValue); + case JSCallResultType.JSVoidResult: + return null; + default: + throw new Error(`Invalid JS call result type '${resultType}'.`); + } + } + + let nextByteArrayIndex = 0; + function stringifyArgs(callDispatcher: CallDispatcher, args: any[] | null) { + nextByteArrayIndex = 0; + currentCallDispatcher = callDispatcher; + const result = JSON.stringify(args, argReplacer); + currentCallDispatcher = undefined; + return result; + } + + function argReplacer(key: string, value: any) { + if (value instanceof DotNetObject) { + return value.serializeAsArg(); + } else if (value instanceof Uint8Array) { + const dotNetCallDispatcher = currentCallDispatcher!.getDotNetCallDispatcher(); + dotNetCallDispatcher!.sendByteArray(nextByteArrayIndex, value); + const jsonValue = { [byteArrayRefKey]: nextByteArrayIndex }; + nextByteArrayIndex++; + return jsonValue; + } + + return value; + } } diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts new file mode 100644 index 000000000000..bb5476313dbf --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts @@ -0,0 +1,169 @@ +import { expect } from "@jest/globals"; +import { DotNet } from "../src/Microsoft.JSInterop"; + +const jsObjectId = "__jsObjectId"; +const dotNetCallDispatcher: DotNet.DotNetCallDispatcher = { + beginInvokeDotNetFromJS: function (callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): void { }, + endInvokeJSFromDotNet: function (callId: number, succeeded: boolean, resultOrError: any): void { }, + sendByteArray: function (id: number, data: Uint8Array): void { } +} +const dispatcher: DotNet.ICallDispatcher = DotNet.attachDispatcher(dotNetCallDispatcher); + +describe("CallDispatcher", () => { + test("should handle functions with no arguments", () => { + const mockFunc = jest.fn(() => 1); + const objectId = DotNet.createJSObjectReference({ mockFunc })[jsObjectId]; + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "mockFunc", + callType: DotNet.JSCallType.FunctionCall, + resultType: DotNet.JSCallResultType.Default, + argsJson: null + }; + + const result = dispatcher.invokeJSFromDotNet(invocationInfo); + expect(result).toBe("1"); + expect(mockFunc).toHaveBeenCalledWith(); + }); + + test("should call the function with provided arguments and return the result", () => { + const mockFunc = jest.fn((a, b) => a + b); + const objectId = DotNet.createJSObjectReference({ mockFunc })[jsObjectId]; + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "mockFunc", + callType: DotNet.JSCallType.FunctionCall, + resultType: DotNet.JSCallResultType.Default, + argsJson: JSON.stringify([1, 2]) + }; + + const result = dispatcher.invokeJSFromDotNet(invocationInfo); + + expect(result).toBe("3"); + expect(mockFunc).toHaveBeenCalledWith(1, 2); + }); + + test("should throw an error if the provided value is not a function", () => { + const mockFunc = jest.fn((a, b) => a + b); + const objectId = DotNet.createJSObjectReference({ mockFunc })[jsObjectId]; + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "notAFunction", + callType: DotNet.JSCallType.FunctionCall, + resultType: DotNet.JSCallResultType.Default, + argsJson: JSON.stringify([1, 2]) + }; + + expect(() => dispatcher.invokeJSFromDotNet(invocationInfo)).toThrowError("The value 'notAFunction' is not a function."); + }); + + test("should handle functions that throw errors", () => { + const mockFunc = jest.fn(() => { throw new Error("Test error"); }); + const objectId = DotNet.createJSObjectReference({ mockFunc })[jsObjectId]; + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "mockFunc", + callType: DotNet.JSCallType.FunctionCall, + resultType: DotNet.JSCallResultType.Default, + argsJson: null + }; + + expect(() => dispatcher.invokeJSFromDotNet(invocationInfo)).toThrowError("Test error"); + }); + + test("get value simple", () => { + const testObject = { a: 10 }; + const objectId = DotNet.createJSObjectReference(testObject)[jsObjectId]; + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "a", + callType: DotNet.JSCallType.GetValue, + resultType: DotNet.JSCallResultType.Default, + argsJson: null + }; + + const result = dispatcher.invokeJSFromDotNet(invocationInfo); + + expect(result).toBe("10"); + }); + + test("get value nested", () => { + const testObject = { a: { b: 20 } }; + const objectId = DotNet.createJSObjectReference(testObject)[jsObjectId]; + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "a.b", + callType: DotNet.JSCallType.GetValue, + resultType: DotNet.JSCallResultType.Default, + argsJson: null + }; + + const result = dispatcher.invokeJSFromDotNet(invocationInfo); + + expect(result).toBe("20"); + }); + + test("get value undefined throws", () => { + const testObject = { a: 10 }; + const objectId = DotNet.createJSObjectReference(testObject)[jsObjectId]; + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "b", + callType: DotNet.JSCallType.GetValue, + resultType: DotNet.JSCallResultType.Default, + argsJson: null + }; + + expect(() => dispatcher.invokeJSFromDotNet(invocationInfo)).toThrowError(`The property 'b' is not defined or is not readable.`); + }); + + test("get value object ref", () => { + const testObject = { a: { b: 20 } }; + const objectId = DotNet.createJSObjectReference(testObject)[jsObjectId]; + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "a", + callType: DotNet.JSCallType.GetValue, + resultType: DotNet.JSCallResultType.JSObjectReference, + argsJson: null + }; + + const result = dispatcher.invokeJSFromDotNet(invocationInfo); + + expect(result).toMatch("__jsObjectId"); + }); + + test("get value object deref", () => { + const testObject = { a: { b: 20 } }; + const objectId = DotNet.createJSObjectReference(testObject)[jsObjectId]; + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "", + callType: DotNet.JSCallType.GetValue, + resultType: DotNet.JSCallResultType.Default, + argsJson: null + }; + + const result = dispatcher.invokeJSFromDotNet(invocationInfo); + + expect(result).toBe(JSON.stringify(testObject)) + }); +}); diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/test/findObjectMember.test.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/test/findObjectMember.test.ts new file mode 100644 index 000000000000..8b035e5de2b8 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/test/findObjectMember.test.ts @@ -0,0 +1,70 @@ +import { expect } from '@jest/globals'; +import { DotNet } from "../src/Microsoft.JSInterop"; + +describe('findObjectMember', () => { + let objectId: number; + + beforeAll(() => { + objectId = DotNet.createJSObjectReference({ + a: { + b: { + c: 42, + d: function () { return 'hello'; }, + e: class { constructor() { } } + } + } + })["__jsObjectId"]; + }); + + test('resolves data member', () => { + const result = DotNet.findObjectMember('a.b.c', objectId); + expect(result).toEqual({ parent: { c: 42, d: expect.any(Function), e: expect.any(Function) }, name: 'c', func: undefined }); + }); + + test('resolves function member', () => { + const result = DotNet.findObjectMember('a.b.d', objectId); + expect(result).toEqual({ parent: { c: 42, d: expect.any(Function), e: expect.any(Function) }, name: 'd', func: expect.any(Function) }); + }); + + test('resolves constructor function member', () => { + const result = DotNet.findObjectMember('a.b.e', objectId); + expect(result).toEqual({ parent: { c: 42, d: expect.any(Function), e: expect.any(Function) }, name: 'e', func: expect.any(Function) }); + }); + + test('resolves property member', () => { + const result = DotNet.findObjectMember('a.b', objectId); + expect(result).toEqual({ parent: { b: { c: 42, d: expect.any(Function), e: expect.any(Function) } }, name: 'b', func: undefined }); + }); + + test('resolves undefined member', () => { + const result = DotNet.findObjectMember('a.b.c.f', objectId); + expect(result).toEqual({ parent: 42, name: 'f', func: undefined }); + }); + + test('throws error for non-existent instance ID', () => { + expect(() => DotNet.findObjectMember('a.b.c', 999)).toThrow('JS object instance with ID 999 does not exist (has it been disposed?).'); + }); +}); + +describe('findObjectMember with window object', () => { + test('resolves document.title', () => { + document.title = 'Test Title'; + const result = DotNet.findObjectMember('document.title', 0); + expect(result).toEqual({ parent: document, name: 'title', func: undefined }); + }); + + test('resolves window.location', () => { + const result = DotNet.findObjectMember('location', 0); + expect(result).toEqual({ parent: expect.any(Object), name: 'location', func: undefined }); + }); + + test('resolves window.alert', () => { + const result = DotNet.findObjectMember('alert', 0); + expect(result).toEqual({ parent: expect.any(Object), name: 'alert', func: expect.any(Function) }); + }); + + test('resolves undefined for non-existent window member', () => { + const result = DotNet.findObjectMember('nonExistentMember', 0); + expect(result).toEqual({ parent: expect.any(Object), name: 'nonExistentMember', func: undefined }); + }); +}); \ No newline at end of file diff --git a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSInvocationInfo.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSInvocationInfo.cs index ac5d8c011658..b1257b3b3b2c 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSInvocationInfo.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSInvocationInfo.cs @@ -23,12 +23,12 @@ public sealed class JSInvocationInfo public long TargetInstanceId { get; init; } /// - /// The identifier for the function to invoke or property to access. + /// The identifier of the function to invoke or property to access. /// - public string? Identifier { get; init; } + public required string Identifier { get; init; } /// - /// TODO(OR) + /// The type of operation that should be performed in JS. /// public JSCallType CallType { get; init; } diff --git a/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs index 3e928ec5e5b0..5fa02f08cf14 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs @@ -1072,7 +1072,7 @@ public class TestJSRuntime : JSInProcessRuntime { private TaskCompletionSource _nextInvocationTcs = new TaskCompletionSource(); public Task NextInvocationTask => _nextInvocationTcs.Task; - public long? LastInvocationAsyncHandle { get; private set; } + public long LastInvocationAsyncHandle { get; private set; } public string LastInvocationIdentifier { get; private set; } public string LastInvocationArgsJson { get; private set; } From f227e098942760e5f2deed1f9d0863dd1555367a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Mon, 24 Mar 2025 17:42:19 +0100 Subject: [PATCH 04/25] Add JS interop tests --- .../src/Platform/Circuits/CircuitManager.ts | 1 - .../src/src/Microsoft.JSInterop.ts | 11 +- .../src/test/CallDispatcher.test.ts | 374 ++++++++++++++++-- .../src/test/findObjectMember.test.ts | 68 ++-- 4 files changed, 382 insertions(+), 72 deletions(-) diff --git a/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts b/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts index cf9f1f8c1830..2d080bc4fcc6 100644 --- a/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts +++ b/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts @@ -234,7 +234,6 @@ export class CircuitManager implements DotNet.DotNetCallDispatcher { private beginInvokeJSJson(invocationInfoJson: string) { const invocationInfo: DotNet.JSInvocationInfo = JSON.parse(invocationInfoJson); - console.log("CIRCUIT", invocationInfoJson, invocationInfo); this._dispatcher.beginInvokeJSFromDotNet(invocationInfo); } diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index 3457572c07b6..056b3e806b27 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -372,7 +372,7 @@ export module DotNet { * * @param invocationInfo Configuration of the interop call. */ - beginInvokeJSFromDotNet(invocationInfo: JSInvocationInfo): void; + beginInvokeJSFromDotNet(invocationInfo: JSInvocationInfo): Promise | null; /** * Receives notification that an async call from JS to .NET has completed. @@ -446,7 +446,7 @@ export module DotNet { : stringifyArgs(this, result); } - beginInvokeJSFromDotNet(invocationInfo: JSInvocationInfo): void { + beginInvokeJSFromDotNet(invocationInfo: JSInvocationInfo): Promise | null { const { asyncHandle, targetInstanceId, identifier, callType, resultType, argsJson } = invocationInfo; // Coerce synchronous functions into async ones, plus treat @@ -460,7 +460,7 @@ export module DotNet { if (asyncHandle) { // On completion, dispatch result back to .NET // Not using "await" because it codegens a lot of boilerplate - promise. + return promise. then(result => stringifyArgs(this, [ asyncHandle, true, @@ -474,6 +474,8 @@ export module DotNet { formatError(error) ])) ); + } else { + return null; } } @@ -537,9 +539,8 @@ export module DotNet { } handleJSPropertySet(identifier: string, property: ObjectMemberDescriptor, args: unknown[]) { - // TODO(OR): Test with get only properties if (!this.isWritableProperty(property.parent, property.name)) { - throw new Error(`The property '${identifier}' does is not writable.`); + throw new Error(`The property '${identifier}' is not writable.`); } const value = args[0]; diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts index bb5476313dbf..69c2ed59ca53 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts @@ -2,22 +2,26 @@ import { expect } from "@jest/globals"; import { DotNet } from "../src/Microsoft.JSInterop"; const jsObjectId = "__jsObjectId"; +let lastAsyncResult: null | { callId: number, succeeded: boolean, resultOrError: any } = null; const dotNetCallDispatcher: DotNet.DotNetCallDispatcher = { beginInvokeDotNetFromJS: function (callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): void { }, - endInvokeJSFromDotNet: function (callId: number, succeeded: boolean, resultOrError: any): void { }, - sendByteArray: function (id: number, data: Uint8Array): void { } + sendByteArray: function (id: number, data: Uint8Array): void { }, + endInvokeJSFromDotNet: function (callId: number, succeeded: boolean, resultOrError: any): void { + lastAsyncResult = { callId, succeeded, resultOrError }; + }, } const dispatcher: DotNet.ICallDispatcher = DotNet.attachDispatcher(dotNetCallDispatcher); +const getObjectReferenceId = (obj: any) => DotNet.createJSObjectReference(obj)[jsObjectId]; describe("CallDispatcher", () => { - test("should handle functions with no arguments", () => { - const mockFunc = jest.fn(() => 1); - const objectId = DotNet.createJSObjectReference({ mockFunc })[jsObjectId]; + test("FunctionCall: Function with no arguments is invoked and returns value", () => { + const testFunc = jest.fn(() => 1); + const objectId = getObjectReferenceId({ testFunc }); const invocationInfo: DotNet.JSInvocationInfo = { asyncHandle: 0, targetInstanceId: objectId, - identifier: "mockFunc", + identifier: "testFunc", callType: DotNet.JSCallType.FunctionCall, resultType: DotNet.JSCallResultType.Default, argsJson: null @@ -25,17 +29,17 @@ describe("CallDispatcher", () => { const result = dispatcher.invokeJSFromDotNet(invocationInfo); expect(result).toBe("1"); - expect(mockFunc).toHaveBeenCalledWith(); + expect(testFunc).toHaveBeenCalled(); }); - test("should call the function with provided arguments and return the result", () => { - const mockFunc = jest.fn((a, b) => a + b); - const objectId = DotNet.createJSObjectReference({ mockFunc })[jsObjectId]; + test("FunctionCall: Function with arguments is invoked and returns value", () => { + const testFunc = jest.fn((a, b) => a + b); + const objectId = getObjectReferenceId({ testFunc }); const invocationInfo: DotNet.JSInvocationInfo = { asyncHandle: 0, targetInstanceId: objectId, - identifier: "mockFunc", + identifier: "testFunc", callType: DotNet.JSCallType.FunctionCall, resultType: DotNet.JSCallResultType.Default, argsJson: JSON.stringify([1, 2]) @@ -44,33 +48,73 @@ describe("CallDispatcher", () => { const result = dispatcher.invokeJSFromDotNet(invocationInfo); expect(result).toBe("3"); - expect(mockFunc).toHaveBeenCalledWith(1, 2); + expect(testFunc).toHaveBeenCalledWith(1, 2); }); - test("should throw an error if the provided value is not a function", () => { - const mockFunc = jest.fn((a, b) => a + b); - const objectId = DotNet.createJSObjectReference({ mockFunc })[jsObjectId]; + test("FunctionCall: Non-function value is invoked and throws", () => { + const objectId = getObjectReferenceId({ x: 1 }); const invocationInfo: DotNet.JSInvocationInfo = { asyncHandle: 0, targetInstanceId: objectId, - identifier: "notAFunction", + identifier: "x", + callType: DotNet.JSCallType.FunctionCall, + resultType: DotNet.JSCallResultType.Default, + argsJson: null + }; + + expect(() => dispatcher.invokeJSFromDotNet(invocationInfo)).toThrowError("The value 'x' is not a function."); + }); + + test("FunctionCall: Function is invoked via async interop and returns value", () => { + const testFunc = jest.fn((a, b) => a + b); + const objectId = getObjectReferenceId({ testFunc }); + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 1, + targetInstanceId: objectId, + identifier: "testFunc", callType: DotNet.JSCallType.FunctionCall, resultType: DotNet.JSCallResultType.Default, argsJson: JSON.stringify([1, 2]) }; - expect(() => dispatcher.invokeJSFromDotNet(invocationInfo)).toThrowError("The value 'notAFunction' is not a function."); + const promise = dispatcher.beginInvokeJSFromDotNet(invocationInfo); + + promise?.then(() => { + expect(testFunc).toHaveBeenCalledWith(1, 2); + expect(lastAsyncResult).toStrictEqual({ callId: 1, succeeded: true, resultOrError: "[1,true,3]" }); + }); + }); + + test("FunctionCall: Non-function value is invoked via async interop and throws", () => { + const objectId = getObjectReferenceId({ x: 1 }); + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 1, + targetInstanceId: objectId, + identifier: "x", + callType: DotNet.JSCallType.FunctionCall, + resultType: DotNet.JSCallResultType.Default, + argsJson: null + }; + + const promise = dispatcher.beginInvokeJSFromDotNet(invocationInfo); + + promise?.then(() => { + expect(lastAsyncResult?.succeeded).toBe(false); + expect(lastAsyncResult?.resultOrError).toMatch("The value 'x' is not a function."); + }); }); - test("should handle functions that throw errors", () => { - const mockFunc = jest.fn(() => { throw new Error("Test error"); }); - const objectId = DotNet.createJSObjectReference({ mockFunc })[jsObjectId]; + test("FunctionCall: should handle functions that throw errors", () => { + const testFunc = jest.fn(() => { throw new Error("Test error"); }); + const objectId = getObjectReferenceId({ testFunc }); const invocationInfo: DotNet.JSInvocationInfo = { asyncHandle: 0, targetInstanceId: objectId, - identifier: "mockFunc", + identifier: "testFunc", callType: DotNet.JSCallType.FunctionCall, resultType: DotNet.JSCallResultType.Default, argsJson: null @@ -79,9 +123,47 @@ describe("CallDispatcher", () => { expect(() => dispatcher.invokeJSFromDotNet(invocationInfo)).toThrowError("Test error"); }); - test("get value simple", () => { + test("NewCall: Constructor function is invoked and returns reference to new object", () => { + window["testCtor"] = function () { this.a = 10; }; + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: 0, + identifier: "testCtor", + callType: DotNet.JSCallType.NewCall, + resultType: DotNet.JSCallResultType.JSObjectReference, + argsJson: null + }; + + const result = dispatcher.invokeJSFromDotNet(invocationInfo); + + expect(result).toMatch("__jsObjectId"); + }); + + test("NewCall: Class constructor is invoked and returns reference to the new instance", () => { + const TestClass = class { + a: number; + constructor() { this.a = 10; } + }; + const objectId = getObjectReferenceId({ TestClass }); + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "TestClass", + callType: DotNet.JSCallType.NewCall, + resultType: DotNet.JSCallResultType.JSObjectReference, + argsJson: null + }; + + const result = dispatcher.invokeJSFromDotNet(invocationInfo); + + expect(result).toMatch("__jsObjectId"); + }); + + test("GetValue: Simple property value is retrived", () => { const testObject = { a: 10 }; - const objectId = DotNet.createJSObjectReference(testObject)[jsObjectId]; + const objectId = getObjectReferenceId(testObject); const invocationInfo: DotNet.JSInvocationInfo = { asyncHandle: 0, @@ -97,9 +179,9 @@ describe("CallDispatcher", () => { expect(result).toBe("10"); }); - test("get value nested", () => { + test("GetValue: Nested property value is retrieved", () => { const testObject = { a: { b: 20 } }; - const objectId = DotNet.createJSObjectReference(testObject)[jsObjectId]; + const objectId = getObjectReferenceId(testObject); const invocationInfo: DotNet.JSInvocationInfo = { asyncHandle: 0, @@ -115,9 +197,9 @@ describe("CallDispatcher", () => { expect(result).toBe("20"); }); - test("get value undefined throws", () => { + test("GetValue: Reading undefined property throws", () => { const testObject = { a: 10 }; - const objectId = DotNet.createJSObjectReference(testObject)[jsObjectId]; + const objectId = getObjectReferenceId(testObject); const invocationInfo: DotNet.JSInvocationInfo = { asyncHandle: 0, @@ -128,12 +210,12 @@ describe("CallDispatcher", () => { argsJson: null }; - expect(() => dispatcher.invokeJSFromDotNet(invocationInfo)).toThrowError(`The property 'b' is not defined or is not readable.`); + expect(() => dispatcher.invokeJSFromDotNet(invocationInfo)).toThrowError("The property 'b' is not defined or is not readable."); }); - test("get value object ref", () => { + test("GetValue: Object reference is retrieved", () => { const testObject = { a: { b: 20 } }; - const objectId = DotNet.createJSObjectReference(testObject)[jsObjectId]; + const objectId = getObjectReferenceId(testObject); const invocationInfo: DotNet.JSInvocationInfo = { asyncHandle: 0, @@ -149,9 +231,9 @@ describe("CallDispatcher", () => { expect(result).toMatch("__jsObjectId"); }); - test("get value object deref", () => { + test("GetValue: Object value is retrieved from reference and empty identifier", () => { const testObject = { a: { b: 20 } }; - const objectId = DotNet.createJSObjectReference(testObject)[jsObjectId]; + const objectId = getObjectReferenceId(testObject); const invocationInfo: DotNet.JSInvocationInfo = { asyncHandle: 0, @@ -166,4 +248,232 @@ describe("CallDispatcher", () => { expect(result).toBe(JSON.stringify(testObject)) }); + + test("GetValue: Reading from setter-only property throws", () => { + const testObject = { set a(_: any) { } }; + const objectId = getObjectReferenceId(testObject); + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "a", + callType: DotNet.JSCallType.GetValue, + resultType: DotNet.JSCallResultType.Default, + argsJson: null + }; + + expect(() => dispatcher.invokeJSFromDotNet(invocationInfo)).toThrowError("The property 'a' is not defined or is not readable"); + }); + + test("SetValue: Simple property is updated", () => { + const testObject = { a: 10 }; + const objectId = getObjectReferenceId(testObject); + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "a", + callType: DotNet.JSCallType.SetValue, + resultType: DotNet.JSCallResultType.JSVoidResult, + argsJson: JSON.stringify([20]) + }; + + dispatcher.invokeJSFromDotNet(invocationInfo); + + expect(testObject.a).toBe(20); + }); + + test("SetValue: Nested property is updated", () => { + const testObject = { a: { b: 10 } }; + const objectId = getObjectReferenceId(testObject); + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "a.b", + callType: DotNet.JSCallType.SetValue, + resultType: DotNet.JSCallResultType.JSVoidResult, + argsJson: JSON.stringify([20]) + }; + + dispatcher.invokeJSFromDotNet(invocationInfo); + + expect(testObject.a.b).toBe(20); + }); + + test("SetValue: Undefined property can be set", () => { + const testObject = { a: 10 }; + const objectId = getObjectReferenceId(testObject); + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "b", + callType: DotNet.JSCallType.SetValue, + resultType: DotNet.JSCallResultType.JSVoidResult, + argsJson: JSON.stringify([30]) + }; + + dispatcher.invokeJSFromDotNet(invocationInfo); + + expect((testObject as any).b).toBe(30); + }); + + test("SetValue: Writing to getter-only property throws", () => { + const testObject = { get a() { return 10; } }; + const objectId = getObjectReferenceId(testObject); + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "a", + callType: DotNet.JSCallType.SetValue, + resultType: DotNet.JSCallResultType.JSVoidResult, + argsJson: JSON.stringify([20]) + }; + + expect(() => dispatcher.invokeJSFromDotNet(invocationInfo)).toThrowError("The property 'a' is not writable."); + }); + + test("SetValue: Writing to non-writable data property throws", () => { + const testObject = Object.create({}, { a: { value: 10, writable: false } }); + const objectId = getObjectReferenceId(testObject); + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "a", + callType: DotNet.JSCallType.SetValue, + resultType: DotNet.JSCallResultType.JSVoidResult, + argsJson: JSON.stringify([20]) + }; + + expect(() => dispatcher.invokeJSFromDotNet(invocationInfo)).toThrowError("The property 'a' is not writable."); + }); + + test("SetValue + GetValue: Updated primitive value is read", () => { + const testObject = { a: 10 }; + const objectId = getObjectReferenceId(testObject); + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "a", + callType: DotNet.JSCallType.SetValue, + resultType: DotNet.JSCallResultType.JSVoidResult, + argsJson: JSON.stringify([20]) + }; + + dispatcher.invokeJSFromDotNet(invocationInfo); + + const invocationInfo2: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "a", + callType: DotNet.JSCallType.GetValue, + resultType: DotNet.JSCallResultType.Default, + argsJson: null + }; + + const result = dispatcher.invokeJSFromDotNet(invocationInfo2); + + expect(result).toBe("20"); + }); + + test("SetValue + GetValue: Updated object value is read", () => { + const objA = {}; + const objARef = DotNet.createJSObjectReference(objA); + const objB = { x: 30 }; + const objBRef = DotNet.createJSObjectReference(objB); + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objARef[jsObjectId], + identifier: "b", + callType: DotNet.JSCallType.SetValue, + resultType: DotNet.JSCallResultType.JSVoidResult, + argsJson: JSON.stringify([objBRef]) + }; + + dispatcher.invokeJSFromDotNet(invocationInfo); + + const invocationInfo2: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objARef[jsObjectId], + identifier: "", + callType: DotNet.JSCallType.GetValue, + resultType: DotNet.JSCallResultType.Default, + argsJson: null + }; + + const result = dispatcher.invokeJSFromDotNet(invocationInfo2); + const resultObj = JSON.parse(result ?? "{}"); + + expect((resultObj as any).b.x).toBe(30); + }); + + test("NewCall + GetValue: Class constructor is invoked and the new instance value is retrieved", () => { + const TestClass = class { + a: number; + constructor() { this.a = 20; } + }; + const objectId = getObjectReferenceId({ TestClass }); + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "TestClass", + callType: DotNet.JSCallType.NewCall, + resultType: DotNet.JSCallResultType.JSObjectReference, + argsJson: null + }; + + const result = dispatcher.invokeJSFromDotNet(invocationInfo); + const newObjectId = JSON.parse(result ?? "")[jsObjectId]; + + const invocationInfo2: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: newObjectId, + identifier: "a", + callType: DotNet.JSCallType.GetValue, + resultType: DotNet.JSCallResultType.Default, + argsJson: null + }; + + const result2 = dispatcher.invokeJSFromDotNet(invocationInfo2); + + expect(result2).toBe("20"); + }); + + test("NewCall + FunctionCall: Class constructor is invoked and method is invoked on the new instance", () => { + const TestClass = class { + f() { return 30; } + }; + const objectId = getObjectReferenceId({ TestClass }); + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "TestClass", + callType: DotNet.JSCallType.NewCall, + resultType: DotNet.JSCallResultType.JSObjectReference, + argsJson: null + }; + + const result = dispatcher.invokeJSFromDotNet(invocationInfo); + const newObjectId = JSON.parse(result ?? "")[jsObjectId]; + + const invocationInfo2: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: newObjectId, + identifier: "f", + callType: DotNet.JSCallType.FunctionCall, + resultType: DotNet.JSCallResultType.Default, + argsJson: null + }; + + const result2 = dispatcher.invokeJSFromDotNet(invocationInfo2); + + expect(result2).toBe("30"); + }); }); diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/test/findObjectMember.test.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/test/findObjectMember.test.ts index 8b035e5de2b8..16a83a3a14e0 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/test/findObjectMember.test.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/test/findObjectMember.test.ts @@ -1,7 +1,7 @@ -import { expect } from '@jest/globals'; +import { expect } from "@jest/globals"; import { DotNet } from "../src/Microsoft.JSInterop"; -describe('findObjectMember', () => { +describe("findObjectMember", () => { let objectId: number; beforeAll(() => { @@ -9,62 +9,62 @@ describe('findObjectMember', () => { a: { b: { c: 42, - d: function () { return 'hello'; }, + d: function () { return "hello"; }, e: class { constructor() { } } } } })["__jsObjectId"]; }); - test('resolves data member', () => { - const result = DotNet.findObjectMember('a.b.c', objectId); - expect(result).toEqual({ parent: { c: 42, d: expect.any(Function), e: expect.any(Function) }, name: 'c', func: undefined }); + test("Resolves data member", () => { + const result = DotNet.findObjectMember("a.b.c", objectId); + expect(result).toEqual({ parent: { c: 42, d: expect.any(Function), e: expect.any(Function) }, name: "c", func: undefined }); }); - test('resolves function member', () => { - const result = DotNet.findObjectMember('a.b.d', objectId); - expect(result).toEqual({ parent: { c: 42, d: expect.any(Function), e: expect.any(Function) }, name: 'd', func: expect.any(Function) }); + test("Resolves function member", () => { + const result = DotNet.findObjectMember("a.b.d", objectId); + expect(result).toEqual({ parent: { c: 42, d: expect.any(Function), e: expect.any(Function) }, name: "d", func: expect.any(Function) }); }); - test('resolves constructor function member', () => { - const result = DotNet.findObjectMember('a.b.e', objectId); - expect(result).toEqual({ parent: { c: 42, d: expect.any(Function), e: expect.any(Function) }, name: 'e', func: expect.any(Function) }); + test("Resolves constructor function member", () => { + const result = DotNet.findObjectMember("a.b.e", objectId); + expect(result).toEqual({ parent: { c: 42, d: expect.any(Function), e: expect.any(Function) }, name: "e", func: expect.any(Function) }); }); - test('resolves property member', () => { - const result = DotNet.findObjectMember('a.b', objectId); - expect(result).toEqual({ parent: { b: { c: 42, d: expect.any(Function), e: expect.any(Function) } }, name: 'b', func: undefined }); + test("Resolves property member", () => { + const result = DotNet.findObjectMember("a.b", objectId); + expect(result).toEqual({ parent: { b: { c: 42, d: expect.any(Function), e: expect.any(Function) } }, name: "b", func: undefined }); }); - test('resolves undefined member', () => { - const result = DotNet.findObjectMember('a.b.c.f', objectId); - expect(result).toEqual({ parent: 42, name: 'f', func: undefined }); + test("Resolves undefined member", () => { + const result = DotNet.findObjectMember("a.b.c.f", objectId); + expect(result).toEqual({ parent: 42, name: "f", func: undefined }); }); - test('throws error for non-existent instance ID', () => { - expect(() => DotNet.findObjectMember('a.b.c', 999)).toThrow('JS object instance with ID 999 does not exist (has it been disposed?).'); + test("Throws error for non-existent instance ID", () => { + expect(() => DotNet.findObjectMember("a.b.c", 999)).toThrow("JS object instance with ID 999 does not exist (has it been disposed?)."); }); }); -describe('findObjectMember with window object', () => { - test('resolves document.title', () => { - document.title = 'Test Title'; - const result = DotNet.findObjectMember('document.title', 0); - expect(result).toEqual({ parent: document, name: 'title', func: undefined }); +describe("findObjectMember with window object", () => { + test("Resolves document.title", () => { + document.title = "Test Title"; + const result = DotNet.findObjectMember("document.title", 0); + expect(result).toEqual({ parent: document, name: "title", func: undefined }); }); - test('resolves window.location', () => { - const result = DotNet.findObjectMember('location', 0); - expect(result).toEqual({ parent: expect.any(Object), name: 'location', func: undefined }); + test("Resolves window.location", () => { + const result = DotNet.findObjectMember("location", 0); + expect(result).toEqual({ parent: expect.any(Object), name: "location", func: undefined }); }); - test('resolves window.alert', () => { - const result = DotNet.findObjectMember('alert', 0); - expect(result).toEqual({ parent: expect.any(Object), name: 'alert', func: expect.any(Function) }); + test("Resolves window.alert", () => { + const result = DotNet.findObjectMember("alert", 0); + expect(result).toEqual({ parent: expect.any(Object), name: "alert", func: expect.any(Function) }); }); - test('resolves undefined for non-existent window member', () => { - const result = DotNet.findObjectMember('nonExistentMember', 0); - expect(result).toEqual({ parent: expect.any(Object), name: 'nonExistentMember', func: undefined }); + test("Resolves undefined for non-existent window member", () => { + const result = DotNet.findObjectMember("nonExistentMember", 0); + expect(result).toEqual({ parent: expect.any(Object), name: "nonExistentMember", func: undefined }); }); }); \ No newline at end of file From 9717446c2f2d2bb5cebb2eee6199319733ed8c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Tue, 25 Mar 2025 11:52:45 +0100 Subject: [PATCH 05/25] Add new interop methods to InProcessRuntime --- .../Samples/BlazorServerApp/Pages/Index.razor | 11 ++- .../BlazorServerApp/wwwroot/custom-script.js | 8 +- .../IInternalWebJSInProcessRuntime.cs | 5 +- src/Components/Web/src/WebRenderer.cs | 12 ++- .../JSInterop/src/InternalCalls.cs | 7 +- .../JSInterop/src/WebAssemblyJSRuntime.cs | 16 ++-- .../Services/DefaultWebAssemblyJSRuntime.cs | 4 +- .../src/src/Microsoft.JSInterop.ts | 65 +++++++++------ .../src/IJSInProcessRuntime.cs | 27 +++++++ .../Microsoft.JSInterop/src/IJSRuntime.cs | 67 +++++++++------- .../src/Infrastructure/JSInvocationInfo.cs | 15 ++-- .../src/JSInProcessRuntime.cs | 79 +++++++++++++------ .../Microsoft.JSInterop/src/JSRuntime.cs | 6 +- .../src/PublicAPI.Unshipped.txt | 34 ++++++++ 14 files changed, 241 insertions(+), 115 deletions(-) diff --git a/src/Components/Samples/BlazorServerApp/Pages/Index.razor b/src/Components/Samples/BlazorServerApp/Pages/Index.razor index 9d41393dd65e..0ab6f6c76554 100644 --- a/src/Components/Samples/BlazorServerApp/Pages/Index.razor +++ b/src/Components/Samples/BlazorServerApp/Pages/Index.razor @@ -45,7 +45,8 @@ Welcome to your new app.
- + + @ErrorMessage
@@ -153,12 +154,17 @@ Welcome to your new app. var value = await objectRef.GetValueAsync("setOnlyProperty"); } - private async Task SetValid(MouseEventArgs args) + private async Task SetInvalid(MouseEventArgs args) { var objectRef = await JSRuntime.GetValueAsync("testObject"); await objectRef.SetValueAsync("getOnlyProperty", 123); } + private async Task SetInvalidViaFunction(MouseEventArgs args) + { + await JSRuntime.InvokeVoidAsync("invalidAccess"); + } + async ValueTask IAsyncDisposable.DisposeAsync() { if (module is not null) @@ -179,5 +185,4 @@ Welcome to your new app. public string? Text { get; set; } public int GetOnlyProperty { get; set; } } - } diff --git a/src/Components/Samples/BlazorServerApp/wwwroot/custom-script.js b/src/Components/Samples/BlazorServerApp/wwwroot/custom-script.js index 7907e5e0c559..0c3aba05d23a 100644 --- a/src/Components/Samples/BlazorServerApp/wwwroot/custom-script.js +++ b/src/Components/Samples/BlazorServerApp/wwwroot/custom-script.js @@ -1,3 +1,5 @@ +"use strict"; + window.getDocumentTitle = function () { return document.title; } @@ -25,7 +27,11 @@ window.testObject = { }, set setOnlyProperty(value) { this.num = value; - }, + } +} + +window.invalidAccess = function () { + window.testObject.getOnlyProperty = 20; } window.getTestObject = function () { diff --git a/src/Components/Web/src/Internal/IInternalWebJSInProcessRuntime.cs b/src/Components/Web/src/Internal/IInternalWebJSInProcessRuntime.cs index dc3383f45ced..2d47bec9e2ec 100644 --- a/src/Components/Web/src/Internal/IInternalWebJSInProcessRuntime.cs +++ b/src/Components/Web/src/Internal/IInternalWebJSInProcessRuntime.cs @@ -2,8 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using Microsoft.JSInterop; +using Microsoft.JSInterop.Infrastructure; namespace Microsoft.AspNetCore.Components.Web.Internal; @@ -16,5 +15,5 @@ public interface IInternalWebJSInProcessRuntime /// /// For internal framework use only. /// - string InvokeJS(string identifier, [StringSyntax(StringSyntaxAttribute.Json)] string? argsJson, JSCallResultType resultType, long targetInstanceId); + string InvokeJS(JSInvocationInfo invocationInfo); } diff --git a/src/Components/Web/src/WebRenderer.cs b/src/Components/Web/src/WebRenderer.cs index becd887453d0..5e7979602166 100644 --- a/src/Components/Web/src/WebRenderer.cs +++ b/src/Components/Web/src/WebRenderer.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.JSInterop; +using Microsoft.JSInterop.Infrastructure; using static Microsoft.AspNetCore.Internal.LinkerFlags; namespace Microsoft.AspNetCore.Components.RenderTree; @@ -130,7 +131,16 @@ private void AttachWebRendererInterop(IJSRuntime jsRuntime, JsonSerializerOption newJsonOptions.TypeInfoResolverChain.Add(WebRendererSerializerContext.Default); newJsonOptions.TypeInfoResolverChain.Add(JsonConverterFactoryTypeInfoResolver>.Instance); var argsJson = JsonSerializer.Serialize(args, newJsonOptions); - inProcessRuntime.InvokeJS(JSMethodIdentifier, argsJson, JSCallResultType.JSVoidResult, 0); + var invocationInfo = new JSInvocationInfo + { + AsyncHandle = 0, + TargetInstanceId = 0, + Identifier = JSMethodIdentifier, + CallType = JSCallType.FunctionCall, + ResultType = JSCallResultType.JSVoidResult, + ArgsJson = argsJson, + }; + inProcessRuntime.InvokeJS(invocationInfo); } else { diff --git a/src/Components/WebAssembly/JSInterop/src/InternalCalls.cs b/src/Components/WebAssembly/JSInterop/src/InternalCalls.cs index 0b35b9509204..fecb1b4ece72 100644 --- a/src/Components/WebAssembly/JSInterop/src/InternalCalls.cs +++ b/src/Components/WebAssembly/JSInterop/src/InternalCalls.cs @@ -18,12 +18,7 @@ internal static partial class InternalCalls public static extern TRes InvokeJS(out string exception, ref JSCallInfo callInfo, [AllowNull] T0 arg0, [AllowNull] T1 arg1, [AllowNull] T2 arg2); [JSImport("Blazor._internal.invokeJSJson", "blazor-internal")] - public static partial string InvokeJSJson( - string identifier, - [JSMarshalAs] long targetInstanceId, - int resultType, - string argsJson, - [JSMarshalAs] long asyncHandle); + public static partial string InvokeJSJson(string invocationInfoString); [JSImport("Blazor._internal.endInvokeDotNetFromJS", "blazor-internal")] public static partial void EndInvokeDotNetFromJS( diff --git a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs index ae12bb8231f2..aa05e5e87c55 100644 --- a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs +++ b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs @@ -23,12 +23,13 @@ protected WebAssemblyJSRuntime() } /// - protected override string InvokeJS(string identifier, [StringSyntax(StringSyntaxAttribute.Json)] string? argsJson, JSCallResultType resultType, long targetInstanceId) + protected override string? InvokeJS(JSInvocationInfo invocationInfo) { + var invocationInfoJson = invocationInfo.ToJson(); + try { - // TODO(OR): Add support for CallType - return InternalCalls.InvokeJSJson(identifier, targetInstanceId, (int)resultType, argsJson ?? "[]", 0); + return InternalCalls.InvokeJSJson(invocationInfoJson); } catch (Exception ex) { @@ -39,13 +40,8 @@ protected override string InvokeJS(string identifier, [StringSyntax(StringSyntax /// protected override void BeginInvokeJS(JSInvocationInfo invocationInfo) { - // TODO(OR): Add support for CallType - InternalCalls.InvokeJSJson( - invocationInfo.Identifier ?? "", - invocationInfo.TargetInstanceId, - (int)invocationInfo.ResultType, - invocationInfo.ArgsJson ?? "[]", - invocationInfo.AsyncHandle); + var invocationInfoJson = invocationInfo.ToJson(); + InternalCalls.InvokeJSJson(invocationInfoJson); } /// diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs b/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs index 57850a705ebb..3bc59ea63dd3 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs @@ -166,6 +166,6 @@ protected override Task TransmitStreamAsync(long streamId, DotNetStreamReference return TransmitDataStreamToJS.TransmitStreamAsync(this, "Blazor._internal.receiveWebAssemblyDotNetDataStream", streamId, dotNetStreamReference); } - string IInternalWebJSInProcessRuntime.InvokeJS(string identifier, string? argsJson, JSCallResultType resultType, long targetInstanceId) - => InvokeJS(identifier, argsJson, resultType, targetInstanceId); + string IInternalWebJSInProcessRuntime.InvokeJS(JSInvocationInfo invocationInfo) + => InvokeJS(invocationInfo); } diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index 056b3e806b27..537ca8fd43a6 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -510,7 +510,7 @@ export module DotNet { handleJSNewCall(identifier: string, func: any, args: unknown[]): any { if (typeof func === "function") { try { - // The new call throws if the function is not constructible (e.g. an arrow function) + // The new expression throws if the function is not constructible (e.g. an arrow function). return new func(...args); } catch (err) { if (err instanceof TypeError) { @@ -534,7 +534,7 @@ export module DotNet { throw new Error(`The property '${identifier}' is not defined or is not readable.`); } - // For empty identifier, we return the object itself to support "dereferencing" JS object references + // For empty identifier, we return the object itself to support "dereferencing" JS object references. return property.parent[property.name]; } @@ -548,37 +548,56 @@ export module DotNet { } isReadableProperty(obj: any, propName: string) { - const descriptor = Object.getOwnPropertyDescriptor(obj, propName); - - if (!descriptor) { + // Return false for missing property. + if (!(propName in obj)) { return false; } - // Return true for data property - if (descriptor.hasOwnProperty('value')) { - return true + // If the property is present we examine its descriptor, potentially needing to walk up the prototype chain. + while (obj !== undefined) { + const descriptor = Object.getOwnPropertyDescriptor(obj, propName); + + if (descriptor) { + // Return true for data property + if (descriptor.hasOwnProperty('value')) { + return true + } + + // Return true for accessor property with defined getter. + return descriptor.hasOwnProperty('get') && typeof descriptor.get === 'function'; + } + + obj = Object.getPrototypeOf(obj); } - - // Return true for accessor property with defined getter - return descriptor.hasOwnProperty('get') && typeof descriptor.get === 'function'; - } + + return false; + } isWritableProperty(obj: any, propName: string) { - const descriptor = Object.getOwnPropertyDescriptor(obj, propName); - - // Return true for undefined property if the property can be added - if (!descriptor) { + // Return true for missing property if the property can be added. + if (!(propName in obj)) { return Object.isExtensible(obj); } - // Return true for writable data property - if (descriptor.hasOwnProperty('value') && descriptor.writable) { - return true; + // If the property is present we examine its descriptor, potentially needing to walk up the prototype chain. + while (obj !== undefined) { + const descriptor = Object.getOwnPropertyDescriptor(obj, propName); + + if (descriptor) { + // Return true for writable data property. + if (descriptor.hasOwnProperty('value') && descriptor.writable) { + return true; + } + + // Return true for accessor property with defined setter. + return descriptor.hasOwnProperty('set') && typeof descriptor.set === 'function'; + } + + obj = Object.getPrototypeOf(obj); } - - // Return true for accessor property with defined setter - return descriptor.hasOwnProperty('set') && typeof descriptor.set === 'function'; - } + + return false; + } endInvokeDotNetFromJS(asyncCallId: string, success: boolean, resultJsonOrExceptionMessage: string): void { const resultOrError = success diff --git a/src/JSInterop/Microsoft.JSInterop/src/IJSInProcessRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/IJSInProcessRuntime.cs index 82a49f14acc2..1e8c067f85cf 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/IJSInProcessRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/IJSInProcessRuntime.cs @@ -20,4 +20,31 @@ public interface IJSInProcessRuntime : IJSRuntime /// An instance of obtained by JSON-deserializing the return value. [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] TResult Invoke<[DynamicallyAccessedMembers(JsonSerialized)] TResult>(string identifier, params object?[]? args); + + /// + /// Invokes the specified JavaScript constructor function synchronously. The function is invoked with the new operator. + /// + /// An identifier for the constructor function to invoke. For example, the value "someScope.SomeClass" will invoke the constructor window.someScope.SomeClass. + /// JSON-serializable arguments. + /// An instance that represents the created JS object. + [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] + IJSObjectReference InvokeNew(string identifier, object?[]? args); + + /// + /// Reads the value of the specified JavaScript property synchronously. + /// + /// The JSON-serializable return type. + /// An identifier for the property to read. For example, the value "someScope.someProp" will read the value of the property window.someScope.someProp. + /// An instance of obtained by JSON-deserializing the return value. + [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] + TValue GetValue<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier); + + /// + /// Updates the value of the specified JavaScript property synchronously. If the property is not defined on the target object, it will be created. + /// + /// JSON-serializable argument type. + /// An identifier for the property to set. For example, the value "someScope.someProp" will update the property window.someScope.someProp. + /// JSON-serializable value. + [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] + void SetValue<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value); } diff --git a/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs index 609ec663a74e..0050c357c49c 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs @@ -38,55 +38,64 @@ public interface IJSRuntime ValueTask InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken, object?[]? args); /// - /// TODO(OR): Add documentation + /// Invokes the specified JavaScript constructor function asynchronously. The function is invoked with the new operator. /// - /// - /// - /// + /// An identifier for the constructor function to invoke. For example, the value "someScope.SomeClass" will invoke the constructor window.someScope.SomeClass. + /// JSON-serializable arguments. + /// An instance that represents the created JS object. ValueTask InvokeNewAsync(string identifier, object?[]? args); /// - /// TODO(OR): Add documentation + /// Invokes the specified JavaScript constructor function asynchronously. The function is invoked with the new operator. /// - /// - /// - /// - /// + /// An identifier for the constructor function to invoke. For example, the value "someScope.SomeClass" will invoke the constructor window.someScope.SomeClass. + /// + /// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts + /// () from being applied. + /// + /// JSON-serializable arguments. + /// An instance that represents the created JS object. ValueTask InvokeNewAsync(string identifier, CancellationToken cancellationToken, object?[]? args); /// - /// TODO(OR): Add documentation + /// Reads the value of the specified JavaScript property asynchronously. /// - /// - /// - /// + /// The JSON-serializable return type. + /// An identifier for the property to read. For example, the value "someScope.someProp" will read the value of the property window.someScope.someProp. + /// An instance of obtained by JSON-deserializing the return value. ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier); /// - /// TODO(OR): Add documentation + /// Reads the value of the specified JavaScript property asynchronously. /// - /// - /// - /// - /// + /// The JSON-serializable return type. + /// An identifier for the property to read. For example, the value "someScope.someProp" will read the value of the property window.someScope.someProp. + /// + /// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts + /// () from being applied. + /// + /// An instance of obtained by JSON-deserializing the return value. ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken); /// - /// TODO(OR): Add documentation + /// Updates the value of the specified JavaScript property asynchronously. If the property is not defined on the target object, it will be created. /// - /// - /// - /// - /// + /// JSON-serializable argument type. + /// An identifier for the property to set. For example, the value "someScope.someProp" will update the property window.someScope.someProp. + /// JSON-serializable value. + /// A that represents the asynchronous invocation operation. ValueTask SetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value); /// - /// TODO(OR): Add documentation + /// Updates the value of the specified JavaScript property asynchronously. If the property is not defined on the target object, it will be created. /// - /// - /// - /// - /// - /// + /// JSON-serializable argument type. + /// An identifier for the property to set. For example, the value "someScope.someProp" will update the property window.someScope.someProp. + /// JSON-serializable value. + /// + /// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts + /// () from being applied. + /// + /// A that represents the asynchronous invocation operation. ValueTask SetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value, CancellationToken cancellationToken); } diff --git a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSInvocationInfo.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSInvocationInfo.cs index b1257b3b3b2c..3e094c434c5e 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSInvocationInfo.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSInvocationInfo.cs @@ -8,19 +8,19 @@ namespace Microsoft.JSInterop.Infrastructure; /// -/// TODO(OR) +/// Configuration of an interop call from .NET to JavaScript. /// public sealed class JSInvocationInfo { /// /// The identifier for the interop call, or zero if no async callback is required. /// - public long AsyncHandle { get; init; } + public required long AsyncHandle { get; init; } /// /// The instance ID of the target JS object. /// - public long TargetInstanceId { get; init; } + public required long TargetInstanceId { get; init; } /// /// The identifier of the function to invoke or property to access. @@ -30,23 +30,22 @@ public sealed class JSInvocationInfo /// /// The type of operation that should be performed in JS. /// - public JSCallType CallType { get; init; } + public required JSCallType CallType { get; init; } /// /// The type of result expected from the invocation. /// - public JSCallResultType ResultType { get; init; } + public required JSCallResultType ResultType { get; init; } /// /// A JSON representation of the arguments. /// [StringSyntax(StringSyntaxAttribute.Json)] - public string? ArgsJson { get; init; } + public required string? ArgsJson { get; init; } /// - /// TODO(OR) + /// Converts the instance to a JSON string representation. /// - /// TODO(OR) public string ToJson() => JsonSerializer.Serialize(this, JSInvocationInfoSourceGenerationContext.Default.JSInvocationInfo); } diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs index 6b0acf7f7c56..4ca28529af7c 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; +using Microsoft.JSInterop.Infrastructure; using static Microsoft.AspNetCore.Internal.LinkerFlags; namespace Microsoft.JSInterop; @@ -12,14 +13,48 @@ namespace Microsoft.JSInterop; /// public abstract class JSInProcessRuntime : JSRuntime, IJSInProcessRuntime { + /// + /// Invokes the specified JavaScript function synchronously. + /// + /// The JSON-serializable return type. + /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// JSON-serializable arguments. + /// An instance of obtained by JSON-deserializing the return value. [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] - internal TValue Invoke<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, long targetInstanceId, params object?[]? args) + public TValue Invoke<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, params object?[]? args) + => Invoke(identifier, 0, JSCallType.FunctionCall, args); + + /// + [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] + public IJSObjectReference InvokeNew(string identifier, object?[]? args) + => Invoke(identifier, 0, JSCallType.NewCall, args); + + /// + [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] + public TValue GetValue<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier) + => Invoke(identifier, 0, JSCallType.GetValue); + + /// + [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] + public void SetValue<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value) + => Invoke(identifier, 0, JSCallType.SetValue, value); + + [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] + internal TValue Invoke<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, long targetInstanceId, JSCallType callType, params object?[]? args) { - var resultJson = InvokeJS( - identifier, - JsonSerializer.Serialize(args, JsonSerializerOptions), - JSCallResultTypeHelper.FromGeneric(), - targetInstanceId); + var argsJson = args is not null && args.Length != 0 ? JsonSerializer.Serialize(args, JsonSerializerOptions) : "[]"; + var resultType = JSCallResultTypeHelper.FromGeneric(); + var invocationInfo = new JSInvocationInfo + { + AsyncHandle = 0, + TargetInstanceId = targetInstanceId, + Identifier = identifier, + CallType = callType, + ResultType = resultType, + ArgsJson = argsJson, + }; + + var resultJson = InvokeJS(invocationInfo); // While the result of deserialization could be null, we're making a // quality of life decision and letting users explicitly determine if they expect @@ -34,35 +69,27 @@ public abstract class JSInProcessRuntime : JSRuntime, IJSInProcessRuntime return result; } - /// - /// Invokes the specified JavaScript function synchronously. - /// - /// The JSON-serializable return type. - /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. - /// JSON-serializable arguments. - /// An instance of obtained by JSON-deserializing the return value. - [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] - public TValue Invoke<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, params object?[]? args) - => Invoke(identifier, 0, args); - - // TODO(OR): Add sync variants of new IJSRuntime methods - /// /// Performs a synchronous function invocation. /// - /// The identifier for the function to invoke. - /// A JSON representation of the arguments. + /// Configuration of the interop call. /// A JSON representation of the result. - protected virtual string? InvokeJS(string identifier, [StringSyntax(StringSyntaxAttribute.Json)] string? argsJson) - => InvokeJS(identifier, argsJson, JSCallResultType.Default, 0); + protected abstract string? InvokeJS(JSInvocationInfo invocationInfo); /// /// Performs a synchronous function invocation. /// /// The identifier for the function to invoke. /// A JSON representation of the arguments. - /// The type of result expected from the invocation. - /// The instance ID of the target JS object. /// A JSON representation of the result. - protected abstract string? InvokeJS(string identifier, [StringSyntax(StringSyntaxAttribute.Json)] string? argsJson, JSCallResultType resultType, long targetInstanceId); + protected virtual string? InvokeJS(string identifier, [StringSyntax(StringSyntaxAttribute.Json)] string? argsJson) + => InvokeJS(new JSInvocationInfo + { + AsyncHandle = 0, + TargetInstanceId = 0, + Identifier = identifier, + CallType = JSCallType.FunctionCall, + ResultType = JSCallResultType.Default, + ArgsJson = argsJson, + }); } diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs index b2c971aac7c1..fbe4440eb816 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs @@ -148,9 +148,7 @@ public ValueTask InvokeNewAsync(string identifier, Cancellat return new ValueTask(tcs.Task); } - var argsJson = args is not null && args.Length != 0 ? - JsonSerializer.Serialize(args, JsonSerializerOptions) : - null; + var argsJson = args is not null && args.Length != 0 ? JsonSerializer.Serialize(args, JsonSerializerOptions) : "[]"; var resultType = JSCallResultTypeHelper.FromGeneric(); var invocationInfo = new JSInvocationInfo { @@ -182,6 +180,8 @@ private void CleanupTasksAndRegistrations(long taskId) } } + // TODO(OR): Restore deleted BeginInvokeJS overload + protected abstract void BeginInvokeJS(JSInvocationInfo invocationInfo); /// diff --git a/src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt b/src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..ff6c202dde9d 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt +++ b/src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt @@ -1 +1,35 @@ #nullable enable +abstract Microsoft.JSInterop.JSRuntime.BeginInvokeJS(Microsoft.JSInterop.Infrastructure.JSInvocationInfo! invocationInfo) -> void +Microsoft.JSInterop.IJSInProcessRuntime.GetValue(string! identifier) -> TValue +Microsoft.JSInterop.IJSInProcessRuntime.InvokeNew(string! identifier, object?[]? args) -> Microsoft.JSInterop.IJSObjectReference! +Microsoft.JSInterop.IJSInProcessRuntime.SetValue(string! identifier, TValue value) -> void +Microsoft.JSInterop.IJSRuntime.GetValueAsync(string! identifier) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.IJSRuntime.GetValueAsync(string! identifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.IJSRuntime.InvokeNewAsync(string! identifier, object?[]? args) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.IJSRuntime.InvokeNewAsync(string! identifier, System.Threading.CancellationToken cancellationToken, object?[]? args) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.IJSRuntime.SetValueAsync(string! identifier, TValue value) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.IJSRuntime.SetValueAsync(string! identifier, TValue value, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.Infrastructure.JSInvocationInfo +Microsoft.JSInterop.Infrastructure.JSInvocationInfo.ArgsJson.get -> string? +Microsoft.JSInterop.Infrastructure.JSInvocationInfo.ArgsJson.init -> void +Microsoft.JSInterop.Infrastructure.JSInvocationInfo.AsyncHandle.get -> long +Microsoft.JSInterop.Infrastructure.JSInvocationInfo.AsyncHandle.init -> void +Microsoft.JSInterop.Infrastructure.JSInvocationInfo.CallType.get -> Microsoft.JSInterop.Infrastructure.JSCallType +Microsoft.JSInterop.Infrastructure.JSInvocationInfo.CallType.init -> void +Microsoft.JSInterop.Infrastructure.JSInvocationInfo.Identifier.get -> string! +Microsoft.JSInterop.Infrastructure.JSInvocationInfo.Identifier.init -> void +Microsoft.JSInterop.Infrastructure.JSInvocationInfo.JSInvocationInfo() -> void +Microsoft.JSInterop.Infrastructure.JSInvocationInfo.ResultType.get -> Microsoft.JSInterop.JSCallResultType +Microsoft.JSInterop.Infrastructure.JSInvocationInfo.ResultType.init -> void +Microsoft.JSInterop.Infrastructure.JSInvocationInfo.TargetInstanceId.get -> long +Microsoft.JSInterop.Infrastructure.JSInvocationInfo.TargetInstanceId.init -> void +Microsoft.JSInterop.Infrastructure.JSInvocationInfo.ToJson() -> string! +Microsoft.JSInterop.JSInProcessRuntime.GetValue(string! identifier) -> TValue +Microsoft.JSInterop.JSInProcessRuntime.InvokeNew(string! identifier, object?[]? args) -> Microsoft.JSInterop.IJSObjectReference! +Microsoft.JSInterop.JSInProcessRuntime.SetValue(string! identifier, TValue value) -> void +Microsoft.JSInterop.JSRuntime.GetValueAsync(string! identifier) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.JSRuntime.GetValueAsync(string! identifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.JSRuntime.InvokeNewAsync(string! identifier, object?[]? args) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.JSRuntime.InvokeNewAsync(string! identifier, System.Threading.CancellationToken cancellationToken, object?[]? args) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.JSRuntime.SetValueAsync(string! identifier, TValue value) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.JSRuntime.SetValueAsync(string! identifier, TValue value, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask From e653a00ddb1c11eec576fe1bd90d5edbee6ae7ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Tue, 25 Mar 2025 17:41:00 +0100 Subject: [PATCH 06/25] WIP wasm & webview fixes --- .../BlazorUnitedApp.Client.csproj | 4 - .../BlazorUnitedApp.Client/HelloWorld.razor | 184 +++++++++++++++++- .../Samples/BlazorUnitedApp/App.razor | 1 + .../BlazorUnitedApp/BlazorUnitedApp.csproj | 8 + .../BlazorUnitedApp/wwwroot/custom-module.js | 3 + .../BlazorUnitedApp/wwwroot/custom-script.js | 61 ++++++ .../Platform/WebView/WebViewIpcReceiver.ts | 7 +- .../Web/src/PublicAPI.Unshipped.txt | 1 + .../JSInterop/src/PublicAPI.Unshipped.txt | 2 + .../WebView/src/Services/WebViewJSRuntime.cs | 1 - .../test/Infrastructure/AssertHelpers.cs | 8 +- .../src/IJSObjectReference.cs | 64 ++++++ .../src/Infrastructure/JSCallType.cs | 18 ++ .../Microsoft.JSInterop/src/JSRuntime.cs | 23 ++- .../src/PublicAPI.Unshipped.txt | 14 ++ 15 files changed, 389 insertions(+), 10 deletions(-) create mode 100644 src/Components/Samples/BlazorUnitedApp/wwwroot/custom-module.js create mode 100644 src/Components/Samples/BlazorUnitedApp/wwwroot/custom-script.js diff --git a/src/Components/Samples/BlazorUnitedApp.Client/BlazorUnitedApp.Client.csproj b/src/Components/Samples/BlazorUnitedApp.Client/BlazorUnitedApp.Client.csproj index 3de34000e0fc..3584296ef03c 100644 --- a/src/Components/Samples/BlazorUnitedApp.Client/BlazorUnitedApp.Client.csproj +++ b/src/Components/Samples/BlazorUnitedApp.Client/BlazorUnitedApp.Client.csproj @@ -11,8 +11,4 @@ - - - - diff --git a/src/Components/Samples/BlazorUnitedApp.Client/HelloWorld.razor b/src/Components/Samples/BlazorUnitedApp.Client/HelloWorld.razor index 9bde6d3ba70b..bc822bd9a04c 100644 --- a/src/Components/Samples/BlazorUnitedApp.Client/HelloWorld.razor +++ b/src/Components/Samples/BlazorUnitedApp.Client/HelloWorld.razor @@ -1 +1,183 @@ -

Hello webassembly!

+@implements IAsyncDisposable +@inject IJSRuntime JSRuntime + +

Welcome to WebAssembly!

+ +
+ +
+ +
+ Message: + + +
+ +
+ Title: + + +
+ +
+ + @CurrentTitle +
+ +
+ @TestObjectDisplay
+ + + + +
+ +
+ + + @AnimalMessage +
+ +
+ + + + @ErrorMessage +
+ +@code { + private string? Message { get; set; } + private string? CurrentTitle { get; set; } + private string? NewTitle { get; set; } + private string? TestObjectDisplay { get; set; } + private string? AnimalMessage { get; set; } + private string? ErrorMessage { get; set; } + + private IJSObjectReference? module; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + module = await JSRuntime.InvokeAsync("import", "../custom-module.js"); + } + } + + + private async Task LogDefault() + { + await JSRuntime.InvokeVoidAsync("logDefault"); + } + + private async Task LogMessage(string message) + { + await JSRuntime.InvokeVoidAsync("logMessage", message); + await JSRuntime.InvokeVoidAsync("console.log", $"Console: {message}"); + } + + + private async Task LogFromModule(string message) + { + if (module != null) + { + await module.InvokeVoidAsync("logFromModule", message); + } + } + + + private async Task SetDocumentTitle(string title) + { + await JSRuntime.InvokeVoidAsync("setDocumentTitle", title); + } + + private async Task SetDocumentTitleDirectly(string title) + { + await JSRuntime.SetValueAsync("document.title", title); + } + + private async Task GetDocumentTitle() + { + CurrentTitle = await JSRuntime.GetValueAsync("document.title"); + } + + private async Task GetTestObjectState() + { + var model = await JSRuntime.InvokeAsync("getTestObject"); + TestObjectDisplay = $"Serialized state: {model.Num} | {model.Text} | {model.GetOnlyProperty}"; + } + + private async Task GetTestObjectStateViaReference() + { + var objectRef = await JSRuntime.GetValueAsync("testObject"); + var model = await objectRef.GetValueAsync(); + TestObjectDisplay = $"Serialized state via reference: {model.Num} | {model.Text} | {model.GetOnlyProperty}"; + } + + private async Task GetTestObjectStateViaReferenceFromFunction() + { + var objectRef = await JSRuntime.InvokeAsync("getTestObject"); + var numValue = await objectRef.GetValueAsync("num"); + var textValue = await objectRef.GetValueAsync("text"); + var getOnlyProperty = await objectRef.GetValueAsync("getOnlyProperty"); + TestObjectDisplay = $"State via reference from function: {numValue} | {textValue} | {getOnlyProperty}"; + } + + private async Task GetTestObjectStateViaReferenceFromProperty() + { + var objectRef = await JSRuntime.GetValueAsync("testObject"); + var numValue = await objectRef.GetValueAsync("num"); + var textValue = await objectRef.GetValueAsync("text"); + var getOnlyProperty = await objectRef.GetValueAsync("getOnlyProperty"); + TestObjectDisplay = $"State via reference from property: {numValue} | {textValue} | {getOnlyProperty}"; + } + + private async Task CreateDog() + { + var dogRef = await JSRuntime.InvokeNewAsync("Dog", ["Igor"]); + AnimalMessage = await dogRef.InvokeAsync("bark"); + } + + private async Task CreateCat() + { + var catRef = await JSRuntime.InvokeNewAsync("Cat", ["Mikeš"]); + AnimalMessage = await catRef.InvokeAsync("meow"); + } + + private async Task GetInvalid(MouseEventArgs args) + { + var objectRef = await JSRuntime.GetValueAsync("testObject"); + var value = await objectRef.GetValueAsync("setOnlyProperty"); + } + + private async Task SetInvalid(MouseEventArgs args) + { + var objectRef = await JSRuntime.GetValueAsync("testObject"); + await objectRef.SetValueAsync("getOnlyProperty", 123); + } + + private async Task SetInvalidViaFunction(MouseEventArgs args) + { + await JSRuntime.InvokeVoidAsync("invalidAccess"); + } + + async ValueTask IAsyncDisposable.DisposeAsync() + { + if (module is not null) + { + try + { + await module.DisposeAsync(); + } + catch (JSDisconnectedException) + { + } + } + } + + class TestObjectModel + { + public int Num { get; set; } + public string? Text { get; set; } + public int GetOnlyProperty { get; set; } + } +} diff --git a/src/Components/Samples/BlazorUnitedApp/App.razor b/src/Components/Samples/BlazorUnitedApp/App.razor index e04f7fc9e8e4..6a78a6b94655 100644 --- a/src/Components/Samples/BlazorUnitedApp/App.razor +++ b/src/Components/Samples/BlazorUnitedApp/App.razor @@ -16,5 +16,6 @@ + diff --git a/src/Components/Samples/BlazorUnitedApp/BlazorUnitedApp.csproj b/src/Components/Samples/BlazorUnitedApp/BlazorUnitedApp.csproj index 62460261002b..2b101e305f63 100644 --- a/src/Components/Samples/BlazorUnitedApp/BlazorUnitedApp.csproj +++ b/src/Components/Samples/BlazorUnitedApp/BlazorUnitedApp.csproj @@ -6,6 +6,14 @@ enable + + + + + + + + diff --git a/src/Components/Samples/BlazorUnitedApp/wwwroot/custom-module.js b/src/Components/Samples/BlazorUnitedApp/wwwroot/custom-module.js new file mode 100644 index 000000000000..4c32c97086ff --- /dev/null +++ b/src/Components/Samples/BlazorUnitedApp/wwwroot/custom-module.js @@ -0,0 +1,3 @@ +export function logFromModule(msg) { + console.log(`Message from custom-module.js: ${msg}`); +} diff --git a/src/Components/Samples/BlazorUnitedApp/wwwroot/custom-script.js b/src/Components/Samples/BlazorUnitedApp/wwwroot/custom-script.js new file mode 100644 index 000000000000..0c3aba05d23a --- /dev/null +++ b/src/Components/Samples/BlazorUnitedApp/wwwroot/custom-script.js @@ -0,0 +1,61 @@ +"use strict"; + +window.getDocumentTitle = function () { + return document.title; +} + +window.setDocumentTitle = function (title) { + document.title = title; +}; + +window.logDefault = function () { + console.log("This is a default log message"); +} + +window.logMessage = function (message) { + console.log(message); +} + +window.testObject = { + num: 10, + text: "Hello World", + log: function () { + console.log(this.text); + }, + get getOnlyProperty() { + return this.num; + }, + set setOnlyProperty(value) { + this.num = value; + } +} + +window.invalidAccess = function () { + window.testObject.getOnlyProperty = 20; +} + +window.getTestObject = function () { + return window.testObject; +} + +window.Cat = class { + constructor(name) { + this.name = name; + } + + meow() { + const text = `${this.name} says Meow!`; + console.log(text); + return text; + } +} + +window.Dog = function (name) { + this.name = name; +} + +window.Dog.prototype.bark = function () { + const text = `${this.name} says Woof!`; + console.log(text); + return text; +} diff --git a/src/Components/Web.JS/src/Platform/WebView/WebViewIpcReceiver.ts b/src/Components/Web.JS/src/Platform/WebView/WebViewIpcReceiver.ts index 7ceef8fe746b..3f1daac4349c 100644 --- a/src/Components/Web.JS/src/Platform/WebView/WebViewIpcReceiver.ts +++ b/src/Components/Web.JS/src/Platform/WebView/WebViewIpcReceiver.ts @@ -33,7 +33,7 @@ export function startIpcReceiver(): void { showErrorNotification(); }, - 'BeginInvokeJS': dispatcher.beginInvokeJSFromDotNet.bind(dispatcher), + 'BeginInvokeJS': beginInvokeJSJson, 'EndInvokeDotNet': dispatcher.endInvokeDotNetFromJS.bind(dispatcher), @@ -80,3 +80,8 @@ function base64ToArrayBuffer(base64: string): Uint8Array { } return result; } + +function beginInvokeJSJson(invocationInfoJson: string) { + const invocationInfo = JSON.parse(invocationInfoJson); + dispatcher.beginInvokeJSFromDotNet(invocationInfo); +} \ No newline at end of file diff --git a/src/Components/Web/src/PublicAPI.Unshipped.txt b/src/Components/Web/src/PublicAPI.Unshipped.txt index 397955cb832c..4660d613c39e 100644 --- a/src/Components/Web/src/PublicAPI.Unshipped.txt +++ b/src/Components/Web/src/PublicAPI.Unshipped.txt @@ -1,2 +1,3 @@ #nullable enable +Microsoft.AspNetCore.Components.Web.Internal.IInternalWebJSInProcessRuntime.InvokeJS(Microsoft.JSInterop.Infrastructure.JSInvocationInfo! invocationInfo) -> string! virtual Microsoft.AspNetCore.Components.Routing.NavLink.ShouldMatch(string! uriAbsolute) -> bool \ No newline at end of file diff --git a/src/Components/WebAssembly/JSInterop/src/PublicAPI.Unshipped.txt b/src/Components/WebAssembly/JSInterop/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..066afaafa0b1 100644 --- a/src/Components/WebAssembly/JSInterop/src/PublicAPI.Unshipped.txt +++ b/src/Components/WebAssembly/JSInterop/src/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +override Microsoft.JSInterop.WebAssembly.WebAssemblyJSRuntime.BeginInvokeJS(Microsoft.JSInterop.Infrastructure.JSInvocationInfo! invocationInfo) -> void +override Microsoft.JSInterop.WebAssembly.WebAssemblyJSRuntime.InvokeJS(Microsoft.JSInterop.Infrastructure.JSInvocationInfo! invocationInfo) -> string? diff --git a/src/Components/WebView/WebView/src/Services/WebViewJSRuntime.cs b/src/Components/WebView/WebView/src/Services/WebViewJSRuntime.cs index 9a9128b7a400..f4beafaa4a5f 100644 --- a/src/Components/WebView/WebView/src/Services/WebViewJSRuntime.cs +++ b/src/Components/WebView/WebView/src/Services/WebViewJSRuntime.cs @@ -35,7 +35,6 @@ protected override void BeginInvokeJS(JSInvocationInfo invocationInfo) throw new InvalidOperationException("Cannot invoke JavaScript outside of a WebView context."); } - // TODO(OR): Add CallType support _ipcSender.BeginInvokeJS(invocationInfo); } diff --git a/src/Components/WebView/WebView/test/Infrastructure/AssertHelpers.cs b/src/Components/WebView/WebView/test/Infrastructure/AssertHelpers.cs index 1a1338d13b28..bd6c979be17b 100644 --- a/src/Components/WebView/WebView/test/Infrastructure/AssertHelpers.cs +++ b/src/Components/WebView/WebView/test/Infrastructure/AssertHelpers.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text.Json; using Microsoft.AspNetCore.Components.RenderTree; +using Microsoft.JSInterop.Infrastructure; namespace Microsoft.AspNetCore.Components.WebView; @@ -11,8 +13,10 @@ internal static void IsAttachWebRendererInteropMessage(string message) { Assert.True(IpcCommon.TryDeserializeOutgoing(message, out var messageType, out var args)); Assert.Equal(IpcCommon.OutgoingMessageType.BeginInvokeJS, messageType); - Assert.Equal(5, args.Count); - Assert.Equal("Blazor._internal.attachWebRendererInterop", args[1].GetString()); + Assert.Equal(1, args.Count); + + var invocationInfo = JsonSerializer.Deserialize(args[1].GetString()); + Assert.Equal("Blazor._internal.attachWebRendererInterop", invocationInfo.Identifier); } internal static void IsAttachToDocumentMessage(string message, int componentId, string selector) diff --git a/src/JSInterop/Microsoft.JSInterop/src/IJSObjectReference.cs b/src/JSInterop/Microsoft.JSInterop/src/IJSObjectReference.cs index bfa03005f1b2..e0a9482f223d 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/IJSObjectReference.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/IJSObjectReference.cs @@ -37,19 +37,83 @@ public interface IJSObjectReference : IAsyncDisposable /// An instance of obtained by JSON-deserializing the return value. ValueTask InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken, object?[]? args); + /// + /// Invokes the specified JavaScript constructor function asynchronously. The function is invoked with the new operator. + /// + /// An identifier for the constructor function to invoke. For example, the value "someScope.SomeClass" will invoke the constructor someScope.SomeClass on the target instance. + /// JSON-serializable arguments. + /// An instance that represents the created JS object. ValueTask InvokeNewAsync(string identifier, object?[]? args); + /// + /// Invokes the specified JavaScript constructor function asynchronously. The function is invoked with the new operator. + /// + /// An identifier for the constructor function to invoke. For example, the value "someScope.SomeClass" will invoke the constructor someScope.SomeClass on the target instance. + /// + /// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts + /// () from being applied. + /// + /// JSON-serializable arguments. + /// An instance that represents the created JS object. ValueTask InvokeNewAsync(string identifier, CancellationToken cancellationToken, object?[]? args); + /// + /// Reads the value of the referenced JavaScript object asynchronously. + /// + /// The JSON-serializable model of the object. + /// An instance of obtained by JSON-deserializing the return value. ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(); + /// + /// Reads the value of the referenced JavaScript object asynchronously. + /// + /// The JSON-serializable model of the object. + /// + /// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts + /// () from being applied. + /// + /// An instance of obtained by JSON-deserializing the return value. ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(CancellationToken cancellationToken); + /// + /// Reads the value of the specified JavaScript property asynchronously. + /// + /// The JSON-serializable return type. + /// An identifier for the property to read. For example, the value "someScope.someProp" will read the value of the property someScope.someProp on the target instance. + /// An instance of obtained by JSON-deserializing the return value. ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier); + /// + /// Reads the value of the specified JavaScript property asynchronously. + /// + /// The JSON-serializable return type. + /// An identifier for the property to read. For example, the value "someScope.someProp" will read the value of the property window.someScope.someProp. + /// + /// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts + /// () from being applied. + /// + /// An instance of obtained by JSON-deserializing the return value. ValueTask GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken); + /// + /// Updates the value of the specified JavaScript property asynchronously. If the property is not defined on the target object, it will be created. + /// + /// JSON-serializable argument type. + /// An identifier for the property to set. For example, the value "someScope.someProp" will update the property someScope.someProp on the target instance. + /// JSON-serializable value. + /// A that represents the asynchronous invocation operation. ValueTask SetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value); + /// + /// Updates the value of the specified JavaScript property asynchronously. If the property is not defined on the target object, it will be created. + /// + /// JSON-serializable argument type. + /// An identifier for the property to set. For example, the value "someScope.someProp" will update the property someScope.someProp on the target instance. + /// JSON-serializable value. + /// + /// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts + /// () from being applied. + /// + /// A that represents the asynchronous invocation operation. ValueTask SetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value, CancellationToken cancellationToken); } diff --git a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSCallType.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSCallType.cs index ee8af7a09666..a217a800f159 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSCallType.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSCallType.cs @@ -3,10 +3,28 @@ namespace Microsoft.JSInterop.Infrastructure; +/// +/// Describes type of operation invoked in JavaScript via interop. +/// public enum JSCallType : int { + /// + /// Represents a regular function invocation. + /// FunctionCall = 1, + + /// + /// Represents a constructor function invocation with the new operator. + /// NewCall = 2, + + /// + /// Represents reading a property value. + /// GetValue = 3, + + /// + /// Represents updating or defining a property value. + /// SetValue = 4, } diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs index fbe4440eb816..865a37a53510 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs @@ -180,8 +180,29 @@ private void CleanupTasksAndRegistrations(long taskId) } } - // TODO(OR): Restore deleted BeginInvokeJS overload + /// + /// Begins an asynchronous function invocation. + /// + /// The identifier for the function invocation, or zero if no async callback is required. + /// The identifier for the function to invoke. + /// A JSON representation of the arguments. + protected virtual void BeginInvokeJS(long taskId, string identifier, [StringSyntax(StringSyntaxAttribute.Json)] string? argsJson) + { + BeginInvokeJS(new JSInvocationInfo + { + AsyncHandle = taskId, + TargetInstanceId = 0, + Identifier = identifier, + CallType = JSCallType.FunctionCall, + ResultType = JSCallResultType.Default, + ArgsJson = argsJson, + }); + } + /// + /// Begins an asynchronous function invocation. + /// + /// protected abstract void BeginInvokeJS(JSInvocationInfo invocationInfo); /// diff --git a/src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt b/src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt index ff6c202dde9d..218fd067501f 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt +++ b/src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt @@ -1,14 +1,28 @@ #nullable enable +abstract Microsoft.JSInterop.JSInProcessRuntime.InvokeJS(Microsoft.JSInterop.Infrastructure.JSInvocationInfo! invocationInfo) -> string? abstract Microsoft.JSInterop.JSRuntime.BeginInvokeJS(Microsoft.JSInterop.Infrastructure.JSInvocationInfo! invocationInfo) -> void Microsoft.JSInterop.IJSInProcessRuntime.GetValue(string! identifier) -> TValue Microsoft.JSInterop.IJSInProcessRuntime.InvokeNew(string! identifier, object?[]? args) -> Microsoft.JSInterop.IJSObjectReference! Microsoft.JSInterop.IJSInProcessRuntime.SetValue(string! identifier, TValue value) -> void +Microsoft.JSInterop.IJSObjectReference.GetValueAsync() -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.IJSObjectReference.GetValueAsync(string! identifier) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.IJSObjectReference.GetValueAsync(string! identifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.IJSObjectReference.GetValueAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.IJSObjectReference.InvokeNewAsync(string! identifier, object?[]? args) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.IJSObjectReference.InvokeNewAsync(string! identifier, System.Threading.CancellationToken cancellationToken, object?[]? args) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.IJSObjectReference.SetValueAsync(string! identifier, TValue value) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.IJSObjectReference.SetValueAsync(string! identifier, TValue value, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask Microsoft.JSInterop.IJSRuntime.GetValueAsync(string! identifier) -> System.Threading.Tasks.ValueTask Microsoft.JSInterop.IJSRuntime.GetValueAsync(string! identifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask Microsoft.JSInterop.IJSRuntime.InvokeNewAsync(string! identifier, object?[]? args) -> System.Threading.Tasks.ValueTask Microsoft.JSInterop.IJSRuntime.InvokeNewAsync(string! identifier, System.Threading.CancellationToken cancellationToken, object?[]? args) -> System.Threading.Tasks.ValueTask Microsoft.JSInterop.IJSRuntime.SetValueAsync(string! identifier, TValue value) -> System.Threading.Tasks.ValueTask Microsoft.JSInterop.IJSRuntime.SetValueAsync(string! identifier, TValue value, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.Infrastructure.JSCallType +Microsoft.JSInterop.Infrastructure.JSCallType.FunctionCall = 1 -> Microsoft.JSInterop.Infrastructure.JSCallType +Microsoft.JSInterop.Infrastructure.JSCallType.GetValue = 3 -> Microsoft.JSInterop.Infrastructure.JSCallType +Microsoft.JSInterop.Infrastructure.JSCallType.NewCall = 2 -> Microsoft.JSInterop.Infrastructure.JSCallType +Microsoft.JSInterop.Infrastructure.JSCallType.SetValue = 4 -> Microsoft.JSInterop.Infrastructure.JSCallType Microsoft.JSInterop.Infrastructure.JSInvocationInfo Microsoft.JSInterop.Infrastructure.JSInvocationInfo.ArgsJson.get -> string? Microsoft.JSInterop.Infrastructure.JSInvocationInfo.ArgsJson.init -> void From 40092750aef8774291ccfa93e1729802441d4434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Wed, 26 Mar 2025 13:39:52 +0100 Subject: [PATCH 07/25] WebView test fixes --- .../Web.JS/src/Platform/WebView/WebViewIpcReceiver.ts | 2 +- .../Web.JS/src/Rendering/Events/EventDelegator.ts | 2 -- .../WebView/WebView/test/Infrastructure/AssertHelpers.cs | 8 +++++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Components/Web.JS/src/Platform/WebView/WebViewIpcReceiver.ts b/src/Components/Web.JS/src/Platform/WebView/WebViewIpcReceiver.ts index 3f1daac4349c..310cc738e385 100644 --- a/src/Components/Web.JS/src/Platform/WebView/WebViewIpcReceiver.ts +++ b/src/Components/Web.JS/src/Platform/WebView/WebViewIpcReceiver.ts @@ -84,4 +84,4 @@ function base64ToArrayBuffer(base64: string): Uint8Array { function beginInvokeJSJson(invocationInfoJson: string) { const invocationInfo = JSON.parse(invocationInfoJson); dispatcher.beginInvokeJSFromDotNet(invocationInfo); -} \ No newline at end of file +} diff --git a/src/Components/Web.JS/src/Rendering/Events/EventDelegator.ts b/src/Components/Web.JS/src/Rendering/Events/EventDelegator.ts index c75ea6411906..c64b38ee952d 100644 --- a/src/Components/Web.JS/src/Rendering/Events/EventDelegator.ts +++ b/src/Components/Web.JS/src/Rendering/Events/EventDelegator.ts @@ -136,8 +136,6 @@ export class EventDelegator { return; } - console.log("onGlobalEvent", isRendererAttached(this.browserRendererId), evt); - if (!isRendererAttached(this.browserRendererId)) { // when connection closed, it will detachWebRendererInterop, so we need to check if the renderer is still attached return; diff --git a/src/Components/WebView/WebView/test/Infrastructure/AssertHelpers.cs b/src/Components/WebView/WebView/test/Infrastructure/AssertHelpers.cs index bd6c979be17b..31a9348bf9ea 100644 --- a/src/Components/WebView/WebView/test/Infrastructure/AssertHelpers.cs +++ b/src/Components/WebView/WebView/test/Infrastructure/AssertHelpers.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Text.Json; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.JSInterop.Infrastructure; @@ -9,13 +10,18 @@ namespace Microsoft.AspNetCore.Components.WebView; public class AssertHelpers { + private static readonly JsonSerializerOptions _jsonSerializerOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + internal static void IsAttachWebRendererInteropMessage(string message) { Assert.True(IpcCommon.TryDeserializeOutgoing(message, out var messageType, out var args)); Assert.Equal(IpcCommon.OutgoingMessageType.BeginInvokeJS, messageType); Assert.Equal(1, args.Count); - var invocationInfo = JsonSerializer.Deserialize(args[1].GetString()); + var invocationInfo = JsonSerializer.Deserialize(args[0].GetString(), _jsonSerializerOptions); Assert.Equal("Blazor._internal.attachWebRendererInterop", invocationInfo.Identifier); } From 5e273e37b589a1bc5a321c3433c49b6b04f281d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Wed, 26 Mar 2025 17:08:25 +0100 Subject: [PATCH 08/25] Add DotNetToJSInterop component to BasicTestApp --- .../src/Platform/Circuits/CircuitManager.ts | 2 + .../Web.JS/src/Platform/Mono/MonoPlatform.ts | 1 + .../src/Platform/WebView/WebViewIpcSender.ts | 2 + .../BasicTestApp/DotNetToJSInterop.razor | 245 ++++++++++++++++++ .../test/testassets/BasicTestApp/Index.razor | 1 + .../src/src/Microsoft.JSInterop.ts | 1 + 6 files changed, 252 insertions(+) create mode 100644 src/Components/test/testassets/BasicTestApp/DotNetToJSInterop.razor diff --git a/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts b/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts index 2d080bc4fcc6..6e18a0920a60 100644 --- a/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts +++ b/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts @@ -245,6 +245,8 @@ export class CircuitManager implements DotNet.DotNetCallDispatcher { // Implements DotNet.DotNetCallDispatcher public endInvokeJSFromDotNet(asyncHandle: number, succeeded: boolean, argsJson: any): void { + console.log("signalr endInvokeJSFromDotNet", asyncHandle, succeeded, argsJson); + this.throwIfDispatchingWhenDisposed(); this._connection!.send('EndInvokeJSFromDotNet', asyncHandle, succeeded, argsJson); } diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts index 9eff6375548c..8c4eb68f3cf1 100644 --- a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts +++ b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts @@ -265,6 +265,7 @@ function attachInteropInvoker(): void { ); }, endInvokeJSFromDotNet: (asyncHandle, succeeded, serializedArgs): void => { + console.log("mono endInvokeJSFromDotNet", asyncHandle, succeeded, serializedArgs); Blazor._internal.dotNetExports!.EndInvokeJS(serializedArgs); }, sendByteArray: (id: number, data: Uint8Array): void => { diff --git a/src/Components/Web.JS/src/Platform/WebView/WebViewIpcSender.ts b/src/Components/Web.JS/src/Platform/WebView/WebViewIpcSender.ts index 9f0fe4189991..f309c85beb09 100644 --- a/src/Components/Web.JS/src/Platform/WebView/WebViewIpcSender.ts +++ b/src/Components/Web.JS/src/Platform/WebView/WebViewIpcSender.ts @@ -16,6 +16,8 @@ export function sendBeginInvokeDotNetFromJS(callId: number, assemblyName: string } export function sendEndInvokeJSFromDotNet(asyncHandle: number, succeeded: boolean, argsJson: any): void { + console.log("webview sendEndInvokeJSFromDotNet", asyncHandle, succeeded, argsJson); + send('EndInvokeJS', asyncHandle, succeeded, argsJson); } diff --git a/src/Components/test/testassets/BasicTestApp/DotNetToJSInterop.razor b/src/Components/test/testassets/BasicTestApp/DotNetToJSInterop.razor new file mode 100644 index 000000000000..832245a27ecd --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/DotNetToJSInterop.razor @@ -0,0 +1,245 @@ +@using Microsoft.JSInterop +@* @implements IAsyncDisposable *@ +@inject IJSRuntime JSRuntime + + + +

DotNetToJSInterop

+ +
+ +
+ +
+ Message: + + @* *@ +
+ +
+ Title: + + +
+ +
+ + @CurrentTitle +
+ +
+ @TestObjectDisplay
+ + + + +
+ +
+ + + @AnimalMessage +
+ +
+ + + + @ErrorMessage +
+ +@code { + private string? Message { get; set; } + private string? CurrentTitle { get; set; } + private string? NewTitle { get; set; } + private string? TestObjectDisplay { get; set; } + private string? AnimalMessage { get; set; } + private string? ErrorMessage { get; set; } + + // private IJSObjectReference? module; + + // protected override async Task OnAfterRenderAsync(bool firstRender) + // { + // if (firstRender) + // { + // module = await JSRuntime.InvokeAsync("import", "../custom-module.js"); + // } + // } + + private async Task LogDefault() + { + await JSRuntime.InvokeVoidAsync("logDefault"); + } + + private async Task LogMessage(string message) + { + await JSRuntime.InvokeVoidAsync("logMessage", message); + await JSRuntime.InvokeVoidAsync("console.log", $"Console: {message}"); + } + + // private async Task LogFromModule(string message) + // { + // if (module != null) + // { + // await module.InvokeVoidAsync("logFromModule", message); + // } + // } + + private async Task SetDocumentTitle(string title) + { + await JSRuntime.InvokeVoidAsync("setDocumentTitle", title); + } + + private async Task SetDocumentTitleDirectly(string title) + { + await JSRuntime.SetValueAsync("document.title", title); + } + + private async Task GetDocumentTitle() + { + CurrentTitle = await JSRuntime.GetValueAsync("document.title"); + } + + private async Task GetTestObjectState() + { + var model = await JSRuntime.InvokeAsync("getTestObject"); + TestObjectDisplay = $"Serialized state: {model.Num} | {model.Text} | {model.GetOnlyProperty}"; + } + + private async Task GetTestObjectStateViaReference() + { + var objectRef = await JSRuntime.GetValueAsync("testObject"); + var model = await objectRef.GetValueAsync(); + TestObjectDisplay = $"Serialized state via reference: {model.Num} | {model.Text} | {model.GetOnlyProperty}"; + } + + private async Task GetTestObjectStateViaReferenceFromFunction() + { + var objectRef = await JSRuntime.InvokeAsync("getTestObject"); + var numValue = await objectRef.GetValueAsync("num"); + var textValue = await objectRef.GetValueAsync("text"); + var getOnlyProperty = await objectRef.GetValueAsync("getOnlyProperty"); + TestObjectDisplay = $"State via reference from function: {numValue} | {textValue} | {getOnlyProperty}"; + } + + private async Task GetTestObjectStateViaReferenceFromProperty() + { + var objectRef = await JSRuntime.GetValueAsync("testObject"); + var numValue = await objectRef.GetValueAsync("num"); + var textValue = await objectRef.GetValueAsync("text"); + var getOnlyProperty = await objectRef.GetValueAsync("getOnlyProperty"); + TestObjectDisplay = $"State via reference from property: {numValue} | {textValue} | {getOnlyProperty}"; + } + + private async Task CreateDog() + { + var dogRef = await JSRuntime.InvokeNewAsync("Dog", ["Igor"]); + AnimalMessage = await dogRef.InvokeAsync("bark"); + } + + private async Task CreateCat() + { + var catRef = await JSRuntime.InvokeNewAsync("Cat", ["Mikeš"]); + AnimalMessage = await catRef.InvokeAsync("meow"); + } + + private async Task GetInvalid(MouseEventArgs args) + { + var objectRef = await JSRuntime.GetValueAsync("testObject"); + var value = await objectRef.GetValueAsync("setOnlyProperty"); + } + + private async Task SetInvalid(MouseEventArgs args) + { + var objectRef = await JSRuntime.GetValueAsync("testObject"); + await objectRef.SetValueAsync("getOnlyProperty", 123); + } + + private async Task SetInvalidViaFunction(MouseEventArgs args) + { + await JSRuntime.InvokeVoidAsync("invalidAccess"); + } + + // async ValueTask IAsyncDisposable.DisposeAsync() + // { + // if (module is not null) + // { + // try + // { + // await module.DisposeAsync(); + // } + // catch (JSDisconnectedException) + // { + // } + // } + // } + + class TestObjectModel + { + public int Num { get; set; } + public string? Text { get; set; } + public int GetOnlyProperty { get; set; } + } +} diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor index 3ce2eb27dc95..1b6be21e915f 100644 --- a/src/Components/test/testassets/BasicTestApp/Index.razor +++ b/src/Components/test/testassets/BasicTestApp/Index.razor @@ -67,6 +67,7 @@ + diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index 8cbd94780e0f..4a7ebae0d563 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -448,6 +448,7 @@ export module DotNet { beginInvokeJSFromDotNet(invocationInfo: JSInvocationInfo): Promise | null { const { asyncHandle, targetInstanceId, identifier, callType, resultType, argsJson } = invocationInfo; + console.log("beginInvokeJSFromDotNet", invocationInfo); // Coerce synchronous functions into async ones, plus treat // synchronous exceptions the same as async ones From 951de5103cc89801026798f77ff6e218b4fccded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Thu, 27 Mar 2025 18:23:06 +0100 Subject: [PATCH 09/25] WIP add E2E tests for new interop methods --- .../src/Platform/Circuits/CircuitManager.ts | 2 +- .../Web.JS/src/Platform/Mono/MonoPlatform.ts | 2 +- .../src/Platform/WebView/WebViewIpcSender.ts | 2 +- .../test/E2ETest/Tests/InteropTest.cs | 6 ++++ .../BasicTestApp/InteropComponent.razor | 31 +++++++++++++++++++ .../BasicTestApp/wwwroot/js/jsinteroptests.js | 21 +++++++++++++ .../src/src/Microsoft.JSInterop.ts | 3 +- .../src/IJSInProcessObjectReference.cs | 27 ++++++++++++++++ .../JSInProcessObjectReference.cs | 30 +++++++++++++++++- .../src/PublicAPI.Unshipped.txt | 6 ++++ 10 files changed, 125 insertions(+), 5 deletions(-) diff --git a/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts b/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts index 6e18a0920a60..dfc1e6269329 100644 --- a/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts +++ b/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts @@ -245,7 +245,7 @@ export class CircuitManager implements DotNet.DotNetCallDispatcher { // Implements DotNet.DotNetCallDispatcher public endInvokeJSFromDotNet(asyncHandle: number, succeeded: boolean, argsJson: any): void { - console.log("signalr endInvokeJSFromDotNet", asyncHandle, succeeded, argsJson); + // console.log("signalr endInvokeJSFromDotNet", asyncHandle, succeeded, argsJson); this.throwIfDispatchingWhenDisposed(); this._connection!.send('EndInvokeJSFromDotNet', asyncHandle, succeeded, argsJson); diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts index 8c4eb68f3cf1..aa8fdca346e9 100644 --- a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts +++ b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts @@ -265,7 +265,7 @@ function attachInteropInvoker(): void { ); }, endInvokeJSFromDotNet: (asyncHandle, succeeded, serializedArgs): void => { - console.log("mono endInvokeJSFromDotNet", asyncHandle, succeeded, serializedArgs); + // console.log("mono endInvokeJSFromDotNet", asyncHandle, succeeded, serializedArgs); Blazor._internal.dotNetExports!.EndInvokeJS(serializedArgs); }, sendByteArray: (id: number, data: Uint8Array): void => { diff --git a/src/Components/Web.JS/src/Platform/WebView/WebViewIpcSender.ts b/src/Components/Web.JS/src/Platform/WebView/WebViewIpcSender.ts index f309c85beb09..994baf534064 100644 --- a/src/Components/Web.JS/src/Platform/WebView/WebViewIpcSender.ts +++ b/src/Components/Web.JS/src/Platform/WebView/WebViewIpcSender.ts @@ -16,7 +16,7 @@ export function sendBeginInvokeDotNetFromJS(callId: number, assemblyName: string } export function sendEndInvokeJSFromDotNet(asyncHandle: number, succeeded: boolean, argsJson: any): void { - console.log("webview sendEndInvokeJSFromDotNet", asyncHandle, succeeded, argsJson); + // console.log("webview sendEndInvokeJSFromDotNet", asyncHandle, succeeded, argsJson); send('EndInvokeJS', asyncHandle, succeeded, argsJson); } diff --git a/src/Components/test/E2ETest/Tests/InteropTest.cs b/src/Components/test/E2ETest/Tests/InteropTest.cs index f85e4036f398..253ccf237c6d 100644 --- a/src/Components/test/E2ETest/Tests/InteropTest.cs +++ b/src/Components/test/E2ETest/Tests/InteropTest.cs @@ -92,6 +92,9 @@ public void CanInvokeDotNetMethods() ["invokeAsyncThrowsUndefinedJSObjectReference"] = "Success", ["invokeAsyncThrowsNullJSObjectReference"] = "Success", ["disposeJSObjectReferenceAsync"] = "Success", + // New JS interop methods + ["getValueFromDataPropertyAsync"] = "10", + ["getValueFromGetterAsync"] = "20", }; var expectedSyncValues = new Dictionary @@ -141,6 +144,9 @@ public void CanInvokeDotNetMethods() ["genericInstanceMethod"] = @"""Updated value 2""", ["requestDotNetStreamReference"] = @"""Success""", ["requestDotNetStreamWrapperReference"] = @"""Success""", + // New JS interop methods + ["getValueFromDataProperty"] = "10", + ["getValueFromGetter"] = "20", }; // Include the sync assertions only when running under WebAssembly diff --git a/src/Components/test/testassets/BasicTestApp/InteropComponent.razor b/src/Components/test/testassets/BasicTestApp/InteropComponent.razor index 6310b0e745be..9b7ffc98eaae 100644 --- a/src/Components/test/testassets/BasicTestApp/InteropComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/InteropComponent.razor @@ -280,6 +280,13 @@ var dotNetStreamReferenceWrapper = DotNetStreamReferenceInterop.GetDotNetStreamWrapperReference(); ReturnValues["dotNetToJSReceiveDotNetStreamWrapperReferenceAsync"] = await JSRuntime.InvokeAsync("jsInteropTests.receiveDotNetStreamWrapperReference", dotNetStreamReferenceWrapper); + await GetValueAsyncTests(); + + if (shouldSupportSyncInterop) + { + GetValueSyncTests(); + } + Invocations = invocations; DoneWithInterop = true; } @@ -410,6 +417,30 @@ } } + private void InvokeNewTests() + { + + } + + private async Task GetValueAsyncTests() + { + ReturnValues["getValueFromDataPropertyAsync"] = (await JSRuntime.GetValueAsync("testObject.num")).ToString(); + ReturnValues["getValueFromGetterAsync"] = (await JSRuntime.GetValueAsync("testObject.getOnlyProperty")).ToString(); + } + + private void GetValueSyncTests() + { + var inProcRuntime = ((IJSInProcessRuntime)JSRuntime); + + ReturnValues["getValueFromDataProperty"] = inProcRuntime.GetValue("testObject.num").ToString(); + ReturnValues["getValueFromGetter"] = inProcRuntime.GetValue("testObject.getOnlyProperty").ToString(); + } + + private void SetValueTests() + { + + } + public class PassDotNetObjectByRefArgs { public string StringValue { get; set; } diff --git a/src/Components/test/testassets/BasicTestApp/wwwroot/js/jsinteroptests.js b/src/Components/test/testassets/BasicTestApp/wwwroot/js/jsinteroptests.js index dac70d67e6ec..6a7b3484fc03 100644 --- a/src/Components/test/testassets/BasicTestApp/wwwroot/js/jsinteroptests.js +++ b/src/Components/test/testassets/BasicTestApp/wwwroot/js/jsinteroptests.js @@ -438,3 +438,24 @@ async function receiveDotNetStreamReference(streamRef) { async function receiveDotNetStreamWrapperReference(wrapper) { return await validateDotNetStreamWrapperReference(wrapper); } + +class TestClass { + constructor(text, object) { + this.text = text; + this.object = object; + } + + getTextLength() { + return this.text.length; + } +} + +window.testObject = { + num: 10, + get getOnlyProperty() { + return 20; + }, + set setOnlyProperty(value) { + this.num = value; + } +} diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index 4a7ebae0d563..8845061fce4d 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -437,6 +437,7 @@ export module DotNet { } invokeJSFromDotNet(invocationInfo: JSInvocationInfo): string | null { + //console.log("invocationInfo", JSON.stringify(invocationInfo)); const { targetInstanceId, identifier, callType, resultType, argsJson } = invocationInfo; const returnValue = this.handleJSCall(targetInstanceId, identifier, callType, argsJson); const result = createJSCallResult(returnValue, resultType); @@ -447,8 +448,8 @@ export module DotNet { } beginInvokeJSFromDotNet(invocationInfo: JSInvocationInfo): Promise | null { + //console.log("beginInvokeJSFromDotNet", invocationInfo); const { asyncHandle, targetInstanceId, identifier, callType, resultType, argsJson } = invocationInfo; - console.log("beginInvokeJSFromDotNet", invocationInfo); // Coerce synchronous functions into async ones, plus treat // synchronous exceptions the same as async ones diff --git a/src/JSInterop/Microsoft.JSInterop/src/IJSInProcessObjectReference.cs b/src/JSInterop/Microsoft.JSInterop/src/IJSInProcessObjectReference.cs index d7e352659199..39f376a11953 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/IJSInProcessObjectReference.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/IJSInProcessObjectReference.cs @@ -20,4 +20,31 @@ public interface IJSInProcessObjectReference : IJSObjectReference, IDisposable /// An instance of obtained by JSON-deserializing the return value. [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] TValue Invoke<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, params object?[]? args); + + /// + /// Invokes the specified JavaScript constructor function synchronously. The function is invoked with the new operator. + /// + /// An identifier for the constructor function to invoke. For example, the value "someScope.SomeClass" will invoke the constructor window.someScope.SomeClass. + /// JSON-serializable arguments. + /// An instance that represents the created JS object. + [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] + IJSInProcessObjectReference InvokeNew(string identifier, object?[]? args); + + /// + /// Reads the value of the specified JavaScript property synchronously. + /// + /// The JSON-serializable return type. + /// An identifier for the property to read. For example, the value "someScope.someProp" will read the value of the property window.someScope.someProp. + /// An instance of obtained by JSON-deserializing the return value. + [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] + TValue GetValue<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier); + + /// + /// Updates the value of the specified JavaScript property synchronously. If the property is not defined on the target object, it will be created. + /// + /// JSON-serializable argument type. + /// An identifier for the property to set. For example, the value "someScope.someProp" will update the property window.someScope.someProp. + /// JSON-serializable value. + [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] + void SetValue<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value); } diff --git a/src/JSInterop/Microsoft.JSInterop/src/Implementation/JSInProcessObjectReference.cs b/src/JSInterop/Microsoft.JSInterop/src/Implementation/JSInProcessObjectReference.cs index 46e4af57b4d7..abe9472251cc 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Implementation/JSInProcessObjectReference.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Implementation/JSInProcessObjectReference.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices.JavaScript; +using Microsoft.JSInterop.Infrastructure; using static Microsoft.AspNetCore.Internal.LinkerFlags; namespace Microsoft.JSInterop.Implementation; @@ -30,7 +31,34 @@ protected internal JSInProcessObjectReference(JSInProcessRuntime jsRuntime, long { ThrowIfDisposed(); - return _jsRuntime.Invoke(identifier, Id, args); + return _jsRuntime.Invoke(identifier, Id, JSCallType.FunctionCall, args); + } + + /// + [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] + public IJSInProcessObjectReference InvokeNew(string identifier, object?[]? args) + { + ThrowIfDisposed(); + + return _jsRuntime.Invoke(identifier, Id, JSCallType.NewCall, args); + } + + /// + [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] + public TValue GetValue<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier) + { + ThrowIfDisposed(); + + return _jsRuntime.Invoke(identifier, Id, JSCallType.GetValue); + } + + /// + [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] + public void SetValue<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value) + { + ThrowIfDisposed(); + + _jsRuntime.Invoke(identifier, Id, JSCallType.SetValue, value); } /// diff --git a/src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt b/src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt index 218fd067501f..0e1cc19eee07 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt +++ b/src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt @@ -1,6 +1,9 @@ #nullable enable abstract Microsoft.JSInterop.JSInProcessRuntime.InvokeJS(Microsoft.JSInterop.Infrastructure.JSInvocationInfo! invocationInfo) -> string? abstract Microsoft.JSInterop.JSRuntime.BeginInvokeJS(Microsoft.JSInterop.Infrastructure.JSInvocationInfo! invocationInfo) -> void +Microsoft.JSInterop.IJSInProcessObjectReference.GetValue(string! identifier) -> TValue +Microsoft.JSInterop.IJSInProcessObjectReference.InvokeNew(string! identifier, object?[]? args) -> Microsoft.JSInterop.IJSInProcessObjectReference! +Microsoft.JSInterop.IJSInProcessObjectReference.SetValue(string! identifier, TValue value) -> void Microsoft.JSInterop.IJSInProcessRuntime.GetValue(string! identifier) -> TValue Microsoft.JSInterop.IJSInProcessRuntime.InvokeNew(string! identifier, object?[]? args) -> Microsoft.JSInterop.IJSObjectReference! Microsoft.JSInterop.IJSInProcessRuntime.SetValue(string! identifier, TValue value) -> void @@ -18,6 +21,9 @@ Microsoft.JSInterop.IJSRuntime.InvokeNewAsync(string! identifier, object?[]? arg Microsoft.JSInterop.IJSRuntime.InvokeNewAsync(string! identifier, System.Threading.CancellationToken cancellationToken, object?[]? args) -> System.Threading.Tasks.ValueTask Microsoft.JSInterop.IJSRuntime.SetValueAsync(string! identifier, TValue value) -> System.Threading.Tasks.ValueTask Microsoft.JSInterop.IJSRuntime.SetValueAsync(string! identifier, TValue value, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +Microsoft.JSInterop.Implementation.JSInProcessObjectReference.GetValue(string! identifier) -> TValue +Microsoft.JSInterop.Implementation.JSInProcessObjectReference.InvokeNew(string! identifier, object?[]? args) -> Microsoft.JSInterop.IJSInProcessObjectReference! +Microsoft.JSInterop.Implementation.JSInProcessObjectReference.SetValue(string! identifier, TValue value) -> void Microsoft.JSInterop.Infrastructure.JSCallType Microsoft.JSInterop.Infrastructure.JSCallType.FunctionCall = 1 -> Microsoft.JSInterop.Infrastructure.JSCallType Microsoft.JSInterop.Infrastructure.JSCallType.GetValue = 3 -> Microsoft.JSInterop.Infrastructure.JSCallType From 7056120d01f74953e79a89a37ef0c463ec6c60de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Fri, 28 Mar 2025 17:28:14 +0100 Subject: [PATCH 10/25] Add & fix E2E tests for interop --- .../test/E2ETest/Tests/InteropTest.cs | 30 ++- .../BasicTestApp/InteropComponent.razor | 178 +++++++++++++++++- .../BasicTestApp/wwwroot/js/jsinteroptests.js | 44 ++--- .../src/IJSInProcessRuntime.cs | 2 +- .../src/JSInProcessRuntime.cs | 4 +- .../src/JSRuntimeExtensions.cs | 50 +++++ .../src/PublicAPI.Unshipped.txt | 7 +- 7 files changed, 277 insertions(+), 38 deletions(-) diff --git a/src/Components/test/E2ETest/Tests/InteropTest.cs b/src/Components/test/E2ETest/Tests/InteropTest.cs index 253ccf237c6d..bb0e371cb902 100644 --- a/src/Components/test/E2ETest/Tests/InteropTest.cs +++ b/src/Components/test/E2ETest/Tests/InteropTest.cs @@ -28,7 +28,7 @@ protected override void InitializeAsyncCore() } [Fact] - public void CanInvokeDotNetMethods() + public void CanInvokeInteropMethods() { // Arrange var expectedAsyncValues = new Dictionary @@ -92,9 +92,21 @@ public void CanInvokeDotNetMethods() ["invokeAsyncThrowsUndefinedJSObjectReference"] = "Success", ["invokeAsyncThrowsNullJSObjectReference"] = "Success", ["disposeJSObjectReferenceAsync"] = "Success", - // New JS interop methods + // GetValue tests ["getValueFromDataPropertyAsync"] = "10", ["getValueFromGetterAsync"] = "20", + ["getValueFromSetterAsync"] = "Success", + ["getValueFromUndefinedPropertyAsync"] = "Success", + // SetValueTests + ["setValueToDataPropertyAsync"] = "30", + ["setValueToSetterAsync"] = "40", + ["setValueToUndefinedPropertyAsync"] = "50", + ["setValueToGetterAsync"] = "Success", + // InvokeNew tests + ["invokeNewWithClassConstructorAsync"] = "Success", + ["invokeNewWithClassConstructorAsync.dataProperty"] = "abraka", + ["invokeNewWithClassConstructorAsync.function"] = "6", + ["invokeNewWithNonConstructorAsync"] = "Success", }; var expectedSyncValues = new Dictionary @@ -144,9 +156,21 @@ public void CanInvokeDotNetMethods() ["genericInstanceMethod"] = @"""Updated value 2""", ["requestDotNetStreamReference"] = @"""Success""", ["requestDotNetStreamWrapperReference"] = @"""Success""", - // New JS interop methods + // GetValue tests ["getValueFromDataProperty"] = "10", ["getValueFromGetter"] = "20", + ["getValueFromSetter"] = "Success", + ["getValueFromUndefinedProperty"] = "Success", + // SetValue tests + ["setValueToDataProperty"] = "30", + ["setValueToSetter"] = "40", + ["setValueToUndefinedProperty"] = "50", + ["setValueToGetter"] = "Success", + // InvokeNew tests + ["invokeNewWithClassConstructor"] = "Success", + ["invokeNewWithClassConstructor.dataProperty"] = "abraka", + ["invokeNewWithClassConstructor.function"] = "6", + ["invokeNewWithNonConstructor"] = "Success", }; // Include the sync assertions only when running under WebAssembly diff --git a/src/Components/test/testassets/BasicTestApp/InteropComponent.razor b/src/Components/test/testassets/BasicTestApp/InteropComponent.razor index 9b7ffc98eaae..1009732d9e87 100644 --- a/src/Components/test/testassets/BasicTestApp/InteropComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/InteropComponent.razor @@ -287,6 +287,20 @@ GetValueSyncTests(); } + await SetValueAsyncTests(); + + if (shouldSupportSyncInterop) + { + SetValueTests(); + } + + await InvokeNewAsyncTests(); + + if (shouldSupportSyncInterop) + { + InvokeNewTests(); + } + Invocations = invocations; DoneWithInterop = true; } @@ -417,28 +431,174 @@ } } - private void InvokeNewTests() - { - - } - private async Task GetValueAsyncTests() { - ReturnValues["getValueFromDataPropertyAsync"] = (await JSRuntime.GetValueAsync("testObject.num")).ToString(); - ReturnValues["getValueFromGetterAsync"] = (await JSRuntime.GetValueAsync("testObject.getOnlyProperty")).ToString(); + ReturnValues["getValueFromDataPropertyAsync"] = (await JSRuntime.GetValueAsync("jsInteropTests.testObject.num")).ToString(); + ReturnValues["getValueFromGetterAsync"] = (await JSRuntime.GetValueAsync("jsInteropTests.testObject.getOnlyProperty")).ToString(); + + try + { + var _ = await JSRuntime.GetValueAsync("jsInteropTests.testObject.setOnlyProperty"); + } + catch (JSException) + { + ReturnValues["getValueFromSetterAsync"] = "Success"; + } + catch (Exception ex) + { + ReturnValues["getValueFromSetterAsync"] = $"Failure: {ex.Message}"; + } + + try + { + var _ = await JSRuntime.GetValueAsync("jsInteropTests.testObject.undefinedProperty"); + } + catch (JSException) + { + ReturnValues["getValueFromUndefinedPropertyAsync"] = "Success"; + } + catch (Exception ex) + { + ReturnValues["getValueFromUndefinedPropertyAsync"] = $"Failure: {ex.Message}"; + } } private void GetValueSyncTests() { var inProcRuntime = ((IJSInProcessRuntime)JSRuntime); - ReturnValues["getValueFromDataProperty"] = inProcRuntime.GetValue("testObject.num").ToString(); - ReturnValues["getValueFromGetter"] = inProcRuntime.GetValue("testObject.getOnlyProperty").ToString(); + ReturnValues["getValueFromDataProperty"] = inProcRuntime.GetValue("jsInteropTests.testObject.num").ToString(); + ReturnValues["getValueFromGetter"] = inProcRuntime.GetValue("jsInteropTests.testObject.getOnlyProperty").ToString(); + + try + { + var _ = inProcRuntime.GetValue("jsInteropTests.testObject.setOnlyProperty"); + } + catch (JSException) + { + ReturnValues["getValueFromSetter"] = "Success"; + } + catch (Exception ex) + { + ReturnValues["getValueFromSetter"] = $"Failure: {ex.Message}"; + } + + try + { + var _ = inProcRuntime.GetValue("jsInteropTests.testObject.undefinedProperty"); + } + catch (JSException) + { + ReturnValues["getValueFromUndefinedProperty"] = "Success"; + } + catch (Exception ex) + { + ReturnValues["getValueFromUndefinedProperty"] = $"Failure: {ex.Message}"; + } + } + + private async Task SetValueAsyncTests() + { + ReturnValues["getValueFromGetterAsync"] = (await JSRuntime.GetValueAsync("jsInteropTests.testObject.getOnlyProperty")).ToString(); + + await JSRuntime.SetValueAsync("jsInteropTests.testObject.num", 30); + ReturnValues["setValueToDataPropertyAsync"] = (await JSRuntime.GetValueAsync("jsInteropTests.testObject.num")).ToString(); + + await JSRuntime.SetValueAsync("jsInteropTests.testObject.setOnlyProperty", 40); + ReturnValues["setValueToSetterAsync"] = (await JSRuntime.GetValueAsync("jsInteropTests.testObject.num")).ToString(); + + await JSRuntime.SetValueAsync("jsInteropTests.testObject.newProperty", 50); + ReturnValues["setValueToUndefinedPropertyAsync"] = (await JSRuntime.GetValueAsync("jsInteropTests.testObject.newProperty")).ToString(); + + try + { + await JSRuntime.SetValueAsync("jsInteropTests.testObject.getOnlyProperty", 50); + } + catch (JSException) + { + ReturnValues["setValueToGetterAsync"] = "Success"; + } + catch (Exception ex) + { + ReturnValues["setValueToGetterAsync"] = $"Failure: {ex.Message}"; + } } private void SetValueTests() { + var inProcRuntime = ((IJSInProcessRuntime)JSRuntime); + + ReturnValues["getValueFromGetter"] = inProcRuntime.GetValue("jsInteropTests.testObject.getOnlyProperty").ToString(); + + inProcRuntime.SetValue("jsInteropTests.testObject.num", 30); + ReturnValues["setValueToDataProperty"] = inProcRuntime.GetValue("jsInteropTests.testObject.num").ToString(); + inProcRuntime.SetValue("jsInteropTests.testObject.setOnlyProperty", 40); + ReturnValues["setValueToSetter"] = inProcRuntime.GetValue("jsInteropTests.testObject.num").ToString(); + + inProcRuntime.SetValue("jsInteropTests.testObject.newProperty", 50); + ReturnValues["setValueToUndefinedProperty"] = inProcRuntime.GetValue("jsInteropTests.testObject.newProperty").ToString(); + + try + { + inProcRuntime.SetValue("jsInteropTests.testObject.getOnlyProperty", 60); + } + catch (JSException) + { + ReturnValues["setValueToGetter"] = "Success"; + } + catch (Exception ex) + { + ReturnValues["setValueToGetter"] = $"Failure: {ex.Message}"; + } + } + + private async Task InvokeNewAsyncTests() + { + var testClassRef = await JSRuntime.InvokeNewAsync("jsInteropTests.TestClass", "abraka"); + + ReturnValues["invokeNewWithClassConstructorAsync"] = testClassRef is IJSObjectReference ? "Success" : "Failure"; + ReturnValues["invokeNewWithClassConstructorAsync.dataProperty"] = await testClassRef.GetValueAsync("text"); + ReturnValues["invokeNewWithClassConstructorAsync.function"] = (await testClassRef.InvokeAsync("getTextLength")).ToString(); + + try + { + var nonConstructorRef = await JSRuntime.InvokeNewAsync("jsInteropTests.nonConstructorFunction"); + ReturnValues["invokeNewWithNonConstructorAsync"] = nonConstructorRef is null ? "Failure: null" : "Failure: not null"; + } + catch (JSException) + { + ReturnValues["invokeNewWithNonConstructorAsync"] = "Success"; + } + catch (Exception ex) + { + ReturnValues["invokeNewWithNonConstructorAsync"] = $"Failure: {ex.Message}"; + } + } + + private void InvokeNewTests() + { + var inProcRuntime = ((IJSInProcessRuntime)JSRuntime); + + var testClassRef = inProcRuntime.InvokeNew("jsInteropTests.TestClass", "abraka"); + + ReturnValues["invokeNewWithClassConstructor"] = testClassRef is IJSInProcessObjectReference ? "Success" : "Failure"; + ReturnValues["invokeNewWithClassConstructor.dataProperty"] = testClassRef.GetValue("text"); + ReturnValues["invokeNewWithClassConstructor.function"] = testClassRef.Invoke("getTextLength").ToString(); + + try + { + var nonConstructorRef = inProcRuntime.InvokeNew("jsInteropTests.nonConstructorFunction"); + ReturnValues["invokeNewWithNonConstructor"] = nonConstructorRef is null ? "Failure: null" : "Failure: not null"; + } + catch (JSException) + { + ReturnValues["invokeNewWithNonConstructor"] = "Success"; + } + catch (Exception ex) + { + ReturnValues["invokeNewWithNonConstructor"] = $"Failure: {ex.Message}"; + } } public class PassDotNetObjectByRefArgs diff --git a/src/Components/test/testassets/BasicTestApp/wwwroot/js/jsinteroptests.js b/src/Components/test/testassets/BasicTestApp/wwwroot/js/jsinteroptests.js index 6a7b3484fc03..2a22736a58e2 100644 --- a/src/Components/test/testassets/BasicTestApp/wwwroot/js/jsinteroptests.js +++ b/src/Components/test/testassets/BasicTestApp/wwwroot/js/jsinteroptests.js @@ -217,6 +217,26 @@ function createArgumentList(argumentNumber, dotNetObjectByRef) { return array; } +class TestClass { + constructor(text) { + this.text = text; + } + + getTextLength() { + return this.text.length; + } +} + +const testObject = { + num: 10, + get getOnlyProperty() { + return 20; + }, + set setOnlyProperty(value) { + this.num = value; + } +} + window.jsInteropTests = { invokeDotNetInteropMethodsAsync: invokeDotNetInteropMethodsAsync, collectInteropResults: collectInteropResults, @@ -233,6 +253,9 @@ window.jsInteropTests = { receiveDotNetObjectByRefAsync: receiveDotNetObjectByRefAsync, receiveDotNetStreamReference: receiveDotNetStreamReference, receiveDotNetStreamWrapperReference: receiveDotNetStreamWrapperReference, + TestClass: TestClass, + nonConstructorFunction: () => { return 42; }, + testObject: testObject, }; function returnUndefined() { @@ -438,24 +461,3 @@ async function receiveDotNetStreamReference(streamRef) { async function receiveDotNetStreamWrapperReference(wrapper) { return await validateDotNetStreamWrapperReference(wrapper); } - -class TestClass { - constructor(text, object) { - this.text = text; - this.object = object; - } - - getTextLength() { - return this.text.length; - } -} - -window.testObject = { - num: 10, - get getOnlyProperty() { - return 20; - }, - set setOnlyProperty(value) { - this.num = value; - } -} diff --git a/src/JSInterop/Microsoft.JSInterop/src/IJSInProcessRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/IJSInProcessRuntime.cs index 1e8c067f85cf..ed1ea479c8d1 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/IJSInProcessRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/IJSInProcessRuntime.cs @@ -28,7 +28,7 @@ public interface IJSInProcessRuntime : IJSRuntime /// JSON-serializable arguments. /// An instance that represents the created JS object. [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] - IJSObjectReference InvokeNew(string identifier, object?[]? args); + IJSInProcessObjectReference InvokeNew(string identifier, params object?[]? args); /// /// Reads the value of the specified JavaScript property synchronously. diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs index 4ca28529af7c..01f9ee62ab25 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs @@ -26,8 +26,8 @@ public abstract class JSInProcessRuntime : JSRuntime, IJSInProcessRuntime /// [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] - public IJSObjectReference InvokeNew(string identifier, object?[]? args) - => Invoke(identifier, 0, JSCallType.NewCall, args); + public IJSInProcessObjectReference InvokeNew(string identifier, params object?[]? args) + => Invoke(identifier, 0, JSCallType.NewCall, args); /// [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeExtensions.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeExtensions.cs index 5a4202ed15da..aa05aa681cad 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeExtensions.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeExtensions.cs @@ -117,4 +117,54 @@ public static async ValueTask InvokeVoidAsync(this IJSRuntime jsRuntime, string await jsRuntime.InvokeAsync(identifier, cancellationToken, args); } + + /// + /// Invokes the specified JavaScript constructor function asynchronously. The function is invoked with the new operator. + /// + /// The . + /// An identifier for the constructor function to invoke. For example, the value "someScope.SomeClass" will invoke the constructor window.someScope.SomeClass. + /// JSON-serializable arguments. + /// An instance that represents the created JS object. + public static ValueTask InvokeNewAsync(this IJSRuntime jsRuntime, string identifier, params object?[]? args) + { + ArgumentNullException.ThrowIfNull(jsRuntime); + + return jsRuntime.InvokeNewAsync(identifier, args); + } + + /// + /// Invokes the specified JavaScript constructor function asynchronously. The function is invoked with the new operator. + /// + /// The . + /// An identifier for the constructor function to invoke. For example, the value "someScope.SomeClass" will invoke the constructor window.someScope.SomeClass. + /// + /// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts + /// () from being applied. + /// + /// JSON-serializable arguments. + /// An instance that represents the created JS object. + public static ValueTask InvokeNewAsync(this IJSRuntime jsRuntime, string identifier, CancellationToken cancellationToken, object?[]? args) + { + ArgumentNullException.ThrowIfNull(jsRuntime); + + return jsRuntime.InvokeNewAsync(identifier, cancellationToken, args); + } + + /// + /// Invokes the specified JavaScript constructor function asynchronously. The function is invoked with the new operator. + /// + /// The . + /// An identifier for the constructor function to invoke. For example, the value "someScope.SomeClass" will invoke the constructor window.someScope.SomeClass. + /// The duration after which to cancel the async operation. Overrides default timeouts (). + /// JSON-serializable arguments. + /// An instance that represents the created JS object. + public static ValueTask InvokeNewAsync(this IJSRuntime jsRuntime, string identifier, TimeSpan timeout, object?[]? args) + { + ArgumentNullException.ThrowIfNull(jsRuntime); + + using var cancellationTokenSource = timeout == Timeout.InfiniteTimeSpan ? null : new CancellationTokenSource(timeout); + var cancellationToken = cancellationTokenSource?.Token ?? CancellationToken.None; + + return jsRuntime.InvokeNewAsync(identifier, cancellationToken, args); + } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt b/src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt index 0e1cc19eee07..91636feb9d7f 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt +++ b/src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt @@ -5,7 +5,7 @@ Microsoft.JSInterop.IJSInProcessObjectReference.GetValue(string! identif Microsoft.JSInterop.IJSInProcessObjectReference.InvokeNew(string! identifier, object?[]? args) -> Microsoft.JSInterop.IJSInProcessObjectReference! Microsoft.JSInterop.IJSInProcessObjectReference.SetValue(string! identifier, TValue value) -> void Microsoft.JSInterop.IJSInProcessRuntime.GetValue(string! identifier) -> TValue -Microsoft.JSInterop.IJSInProcessRuntime.InvokeNew(string! identifier, object?[]? args) -> Microsoft.JSInterop.IJSObjectReference! +Microsoft.JSInterop.IJSInProcessRuntime.InvokeNew(string! identifier, params object?[]? args) -> Microsoft.JSInterop.IJSInProcessObjectReference! Microsoft.JSInterop.IJSInProcessRuntime.SetValue(string! identifier, TValue value) -> void Microsoft.JSInterop.IJSObjectReference.GetValueAsync() -> System.Threading.Tasks.ValueTask Microsoft.JSInterop.IJSObjectReference.GetValueAsync(string! identifier) -> System.Threading.Tasks.ValueTask @@ -45,7 +45,7 @@ Microsoft.JSInterop.Infrastructure.JSInvocationInfo.TargetInstanceId.get -> long Microsoft.JSInterop.Infrastructure.JSInvocationInfo.TargetInstanceId.init -> void Microsoft.JSInterop.Infrastructure.JSInvocationInfo.ToJson() -> string! Microsoft.JSInterop.JSInProcessRuntime.GetValue(string! identifier) -> TValue -Microsoft.JSInterop.JSInProcessRuntime.InvokeNew(string! identifier, object?[]? args) -> Microsoft.JSInterop.IJSObjectReference! +Microsoft.JSInterop.JSInProcessRuntime.InvokeNew(string! identifier, params object?[]? args) -> Microsoft.JSInterop.IJSInProcessObjectReference! Microsoft.JSInterop.JSInProcessRuntime.SetValue(string! identifier, TValue value) -> void Microsoft.JSInterop.JSRuntime.GetValueAsync(string! identifier) -> System.Threading.Tasks.ValueTask Microsoft.JSInterop.JSRuntime.GetValueAsync(string! identifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask @@ -53,3 +53,6 @@ Microsoft.JSInterop.JSRuntime.InvokeNewAsync(string! identifier, object?[]? args Microsoft.JSInterop.JSRuntime.InvokeNewAsync(string! identifier, System.Threading.CancellationToken cancellationToken, object?[]? args) -> System.Threading.Tasks.ValueTask Microsoft.JSInterop.JSRuntime.SetValueAsync(string! identifier, TValue value) -> System.Threading.Tasks.ValueTask Microsoft.JSInterop.JSRuntime.SetValueAsync(string! identifier, TValue value, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +static Microsoft.JSInterop.JSRuntimeExtensions.InvokeNewAsync(this Microsoft.JSInterop.IJSRuntime! jsRuntime, string! identifier, params object?[]? args) -> System.Threading.Tasks.ValueTask +static Microsoft.JSInterop.JSRuntimeExtensions.InvokeNewAsync(this Microsoft.JSInterop.IJSRuntime! jsRuntime, string! identifier, System.Threading.CancellationToken cancellationToken, object?[]? args) -> System.Threading.Tasks.ValueTask +static Microsoft.JSInterop.JSRuntimeExtensions.InvokeNewAsync(this Microsoft.JSInterop.IJSRuntime! jsRuntime, string! identifier, System.TimeSpan timeout, object?[]? args) -> System.Threading.Tasks.ValueTask From e7294ce2c92e80ef97d4eeefd3edda4b97bc56af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Fri, 28 Mar 2025 17:45:29 +0100 Subject: [PATCH 11/25] Add test for reading JS property from prototype --- .../src/test/CallDispatcher.test.ts | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts index 69c2ed59ca53..6be7ff80841e 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts @@ -197,6 +197,26 @@ describe("CallDispatcher", () => { expect(result).toBe("20"); }); + test("GetValue: Property defined on prototype is retrieved", () => { + const grandParentPrototype = { a: 30 }; + const parentPrototype = Object.create(grandParentPrototype); + const testObject = Object.create(parentPrototype); + const objectId = getObjectReferenceId(testObject); + + const invocationInfo: DotNet.JSInvocationInfo = { + asyncHandle: 0, + targetInstanceId: objectId, + identifier: "a", + callType: DotNet.JSCallType.GetValue, + resultType: DotNet.JSCallResultType.Default, + argsJson: null + }; + + const result = dispatcher.invokeJSFromDotNet(invocationInfo); + + expect(result).toBe("30"); + }); + test("GetValue: Reading undefined property throws", () => { const testObject = { a: 10 }; const objectId = getObjectReferenceId(testObject); @@ -231,7 +251,7 @@ describe("CallDispatcher", () => { expect(result).toMatch("__jsObjectId"); }); - test("GetValue: Object value is retrieved from reference and empty identifier", () => { + test("GetValue: Object value is retrieved from reference with empty identifier", () => { const testObject = { a: { b: 20 } }; const objectId = getObjectReferenceId(testObject); From 42617e4028bb120e00aab2e66bc2d342c43094ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Fri, 28 Mar 2025 19:00:08 +0100 Subject: [PATCH 12/25] Remove debug prints and unwanted changes --- src/Components/Components.sln | 536 ++++++ src/Components/Components.slnf | 424 ++++- src/Components/Web.JS/src/GlobalExports.ts | 1 - .../src/Platform/Circuits/CircuitManager.ts | 2 - .../Web.JS/src/Platform/Mono/MonoPlatform.ts | 1 - .../src/Platform/WebView/WebViewIpcSender.ts | 2 - .../test/RemoteAuthenticationServiceTests.cs | 1 - .../test/RemoteAuthenticatorCoreTests.cs | 1 - .../WebView/WebView/src/IpcSender.cs | 1 - src/JSInterop/JSInterop.sln | 36 - .../src/src/Microsoft.JSInterop.ts | 1514 ++++++++--------- 11 files changed, 1715 insertions(+), 804 deletions(-) create mode 100644 src/Components/Components.sln delete mode 100644 src/JSInterop/JSInterop.sln diff --git a/src/Components/Components.sln b/src/Components/Components.sln new file mode 100644 index 000000000000..102d4434a551 --- /dev/null +++ b/src/Components/Components.sln @@ -0,0 +1,536 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Analyzers", "Analyzers", "{F883AD26-E2D9-0064-49F2-B333EF800629}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authorization", "Authorization", "{278F3FFB-8C21-435B-507F-61B78986A400}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarkapps", "benchmarkapps", "{04B96F53-E713-70DE-173F-A95873598AF4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Components", "Components", "{F30895DC-C3AC-FF44-FFF8-D51D98AE1B61}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CustomElements", "CustomElements", "{1956B0E3-C3A6-1D29-BB64-D91F2292772C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Endpoints", "Endpoints", "{DCF9C18E-19C8-9748-4C1C-32AB5F62F46B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Forms", "Forms", "{5D4ABE75-A633-7578-A6F0-BB88D48E78A4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{5D20AA90-6969-D8BD-9DCD-8634F4692FDA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{DAD03CFC-D5FA-AC94-41C7-9ABAB326F09E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0C88DD14-F956-CE84-757C-A364CCF449FC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{256F4E96-6A73-9ABE-786F-840C672A50F4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "QuickGrid", "QuickGrid", "{453459A7-61B0-99BD-5FE4-DC2B7EAB7FCC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAssembly", "WebAssembly", "{032573FE-E973-EE49-F164-6E17D60C24C1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebView", "WebView", "{F75C3326-23C9-B46B-1CDE-63D2E405EED1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.Analyzers", "Analyzers\src\Microsoft.AspNetCore.Components.Analyzers.csproj", "{E1ED6FA5-4DEB-A512-45B4-EDB7CD97BA9D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.Analyzers.Tests", "Analyzers\test\Microsoft.AspNetCore.Components.Analyzers.Tests.csproj", "{EB0043B9-6579-7370-BED1-D06FE9DFC755}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.Authorization", "Authorization\src\Microsoft.AspNetCore.Components.Authorization.csproj", "{BD2C8FC3-3703-7EF9-4551-70298314EF37}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.Authorization.Tests", "Authorization\test\Microsoft.AspNetCore.Components.Authorization.Tests.csproj", "{CA605D3B-4E98-B5CB-64BA-5F4BB643CEC8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazingPizza.Server", "benchmarkapps\BlazingPizza.Server\BlazingPizza.Server.csproj", "{A19F1FF1-BB91-3A1F-2D5C-FA6137FD214F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.Performance", "Components\perf\Microsoft.AspNetCore.Components.Performance.csproj", "{99B7D762-BB6F-C5F9-41F8-9A7D13A9B9FE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components", "Components\src\Microsoft.AspNetCore.Components.csproj", "{C45B3BA4-7E52-73AB-4942-5899597D4C36}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.Tests", "Components\test\Microsoft.AspNetCore.Components.Tests.csproj", "{D554BB1E-51B7-3752-8566-135C9ADB7DCA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.CustomElements", "CustomElements\src\Microsoft.AspNetCore.Components.CustomElements.csproj", "{AD2A718F-7A4F-70A8-DEF8-2A89689A36DC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.Endpoints", "Endpoints\src\Microsoft.AspNetCore.Components.Endpoints.csproj", "{E8540B19-EB66-03C6-13D2-6EE233C61524}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.Endpoints.Tests", "Endpoints\test\Microsoft.AspNetCore.Components.Endpoints.Tests.csproj", "{9B6BB5AE-8E5B-531A-F5B6-AB5D3B952452}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.Forms", "Forms\src\Microsoft.AspNetCore.Components.Forms.csproj", "{AF6B0E54-144A-5ECE-91FF-55B1BFEE5039}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.Forms.Tests", "Forms\test\Microsoft.AspNetCore.Components.Forms.Tests.csproj", "{D3B32404-5953-67EE-DBA7-6A8F1AB1A0B9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorServerApp", "Samples\BlazorServerApp\BlazorServerApp.csproj", "{FC12C86F-5866-69D4-B7E5-3F0EB4E3F754}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorUnitedApp", "Samples\BlazorUnitedApp\BlazorUnitedApp.csproj", "{74BEC791-7060-4E1D-E77C-2490EFE2DFD0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorUnitedApp.Client", "Samples\BlazorUnitedApp.Client\BlazorUnitedApp.Client.csproj", "{1FCE400C-3875-3963-2105-084823B55013}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.Server", "Server\src\Microsoft.AspNetCore.Components.Server.csproj", "{3893606D-3E03-0FFD-95B4-A93121438268}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.Server.Tests", "Server\test\Microsoft.AspNetCore.Components.Server.Tests.csproj", "{8A37E723-99EF-15F0-8C1F-E9755F8EDEEF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.E2ETests", "test\E2ETest\Microsoft.AspNetCore.Components.E2ETests.csproj", "{AEEBE2B8-B7E4-178C-5640-626E89EF0C75}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.Web", "Web\src\Microsoft.AspNetCore.Components.Web.csproj", "{B6384FF0-6E94-4ADE-F932-F4E886C1CADD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.Web.Tests", "Web\test\Microsoft.AspNetCore.Components.Web.Tests.csproj", "{8F18837A-BB27-ECF0-C1AA-4838E1C2A781}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Wasm.Performance", "Wasm.Performance", "{595095FC-11E7-9A6C-713C-1AA81E4007ED}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.ConsoleHost", "benchmarkapps\Wasm.Performance\ConsoleHost\Wasm.Performance.ConsoleHost.csproj", "{CC7E1BFF-D2BF-D91B-92DC-5B5B99898A68}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.Driver", "benchmarkapps\Wasm.Performance\Driver\Wasm.Performance.Driver.csproj", "{672746A2-DFDD-0444-0D70-E6B7CB8A4ABE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.TestApp", "benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj", "{A4977A0A-A95B-7C35-F319-521331739C01}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.AspNetCore.Components.QuickGrid", "Microsoft.AspNetCore.Components.QuickGrid", "{E329DD7B-EA06-0E35-2452-F0374B45C92D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.QuickGrid", "QuickGrid\Microsoft.AspNetCore.Components.QuickGrid\src\Microsoft.AspNetCore.Components.QuickGrid.csproj", "{8BF2A51C-F22C-0D39-AAC7-7AE776B3D3D0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter", "Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter", "{8B02DB0C-FE4B-328D-C414-580634248F4A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter", "QuickGrid\Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter\src\Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter.csproj", "{62F0E24C-EE9B-D5B3-D2CF-19358722D081}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasicTestApp", "test\testassets\BasicTestApp\BasicTestApp.csproj", "{94769285-DE8C-43CF-8F43-2E6081DE261F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Components.TestServer", "test\testassets\Components.TestServer\Components.TestServer.csproj", "{005A99AE-9829-5288-081E-6AA2C7068F41}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Components.WasmMinimal", "test\testassets\Components.WasmMinimal\Components.WasmMinimal.csproj", "{384D771F-1D94-6B01-A1C3-435584F4B3BF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Components.WasmRemoteAuthentication", "test\testassets\Components.WasmRemoteAuthentication\Components.WasmRemoteAuthentication.csproj", "{DA514A3D-030F-3753-55AC-95601ADF7FE9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ComponentsApp.App", "test\testassets\ComponentsApp.App\ComponentsApp.App.csproj", "{FEEE3D25-53D4-9F34-27B7-CBC04873009F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ComponentsApp.Server", "test\testassets\ComponentsApp.Server\ComponentsApp.Server.csproj", "{ACC68622-35F7-164F-FD1B-E68510FB877C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GlobalizationWasmApp", "test\testassets\GlobalizationWasmApp\GlobalizationWasmApp.csproj", "{DB54C63C-C3DD-FDB5-0D9E-EE12177496DE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LazyTestContentPackage", "test\testassets\LazyTestContentPackage\LazyTestContentPackage.csproj", "{42C50CDC-3AF3-2866-9186-AE532C918BBA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NotReferencedInWasmCodePackage", "test\testassets\NotReferencedInWasmCodePackage\NotReferencedInWasmCodePackage.csproj", "{0262DCC6-3D46-CBCE-4888-9F37FDAAE470}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestContentPackage", "test\testassets\TestContentPackage\TestContentPackage.csproj", "{B7BBE6FD-A4D7-3416-ECEB-05A6B5239937}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication.Msal", "Authentication.Msal", "{A79C6110-8F76-1DB1-0FD8-A69EDA72E8BC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Authentication.WebAssembly.Msal", "WebAssembly\Authentication.Msal\src\Microsoft.Authentication.WebAssembly.Msal.csproj", "{3C135F94-99F0-BF85-CD5D-F2BD288FC230}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DevServer", "DevServer", "{5ED11752-357D-2F9A-0D62-6FBFEC2923EA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.WebAssembly.DevServer", "WebAssembly\DevServer\src\Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj", "{8FC99803-7FB1-E7FD-F579-4FF49A324139}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "JSInterop", "JSInterop", "{2D1527BB-743D-5D96-93C8-1B5741ADC02B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.JSInterop.WebAssembly", "WebAssembly\JSInterop\src\Microsoft.JSInterop.WebAssembly.csproj", "{C004A041-806C-248D-6972-BC44CBD8AFDB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{F24703D6-F12F-EE78-E988-49EB11C8433B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.WebAssembly.Server", "WebAssembly\Server\src\Microsoft.AspNetCore.Components.WebAssembly.Server.csproj", "{69AB9EB3-9FB3-F134-F7BB-6F20719F293D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.WebAssembly.Server.Tests", "WebAssembly\Server\test\Microsoft.AspNetCore.Components.WebAssembly.Server.Tests.csproj", "{D9D391EA-769E-A96E-D230-453C9B9365DE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{F60C0A93-E7A1-AA4F-C800-18D21FD1802D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomBasePathApp", "WebAssembly\testassets\CustomBasePathApp\CustomBasePathApp.csproj", "{F2EF8EC2-4C19-61B5-DCF4-9014E729C057}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HostedInAspNet.Client", "WebAssembly\testassets\HostedInAspNet.Client\HostedInAspNet.Client.csproj", "{2EEDD992-0F07-908F-52D7-52E0D0816172}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HostedInAspNet.Server", "WebAssembly\testassets\HostedInAspNet.Server\HostedInAspNet.Server.csproj", "{BABB04C9-D860-D830-3E50-7AF5D1CDB1C3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StandaloneApp", "WebAssembly\testassets\StandaloneApp\StandaloneApp.csproj", "{35FF93EC-4CEB-09BC-C3C1-B218E4FEBB90}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThreadingApp", "WebAssembly\testassets\ThreadingApp\ThreadingApp.csproj", "{D94FCCB0-E6DD-75AF-9D33-8B2E88D851DF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThreadingApp.Server", "WebAssembly\testassets\ThreadingApp.Server\ThreadingApp.Server.csproj", "{FEF03057-3DED-46E9-44C2-E5A71183CBF7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Prerendered.Client", "WebAssembly\testassets\Wasm.Prerendered.Client\Wasm.Prerendered.Client.csproj", "{D7372AB6-0FF4-A406-B5D8-2DD7AEA27565}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Prerendered.Server", "WebAssembly\testassets\Wasm.Prerendered.Server\Wasm.Prerendered.Server.csproj", "{E7C427B4-5DAA-2DEC-D4FE-25513848B07E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WasmLinkerTest", "WebAssembly\testassets\WasmLinkerTest\WasmLinkerTest.csproj", "{8C83C95C-9A82-1DFA-A4DB-90EC50F32D86}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAssembly", "WebAssembly", "{2114FDDC-431A-A228-9FBD-A2D77B669641}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.WebAssembly", "WebAssembly\WebAssembly\src\Microsoft.AspNetCore.Components.WebAssembly.csproj", "{72B9C791-DBD1-32DB-C566-B5D3B1D405E1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.WebAssembly.Tests", "WebAssembly\WebAssembly\test\Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj", "{F3C7C76A-FF30-4935-E238-7C5A838E3BCE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAssembly.Authentication", "WebAssembly.Authentication", "{2C226B6F-947F-806E-AB56-5318C87BE998}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.WebAssembly.Authentication", "WebAssembly\WebAssembly.Authentication\src\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj", "{585E4EE6-E918-DDEC-79AD-C956E6A19E4B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests", "WebAssembly\WebAssembly.Authentication\test\Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj", "{C80EB816-1E52-8D59-6C14-5567A23B5D82}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E199B8FB-B34C-E815-5C68-8C4C236C3F5B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.WebViewE2E.Test", "WebView\test\E2ETest\Microsoft.AspNetCore.Components.WebViewE2E.Test.csproj", "{C44AFCA8-EDE5-0389-CFE8-22A83AC3ECF7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebView", "WebView", "{CA12ED3E-B35C-B56F-F8CA-F6C9996B003D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.WebView", "WebView\WebView\src\Microsoft.AspNetCore.Components.WebView.csproj", "{491EE542-B9EB-10C7-06A2-6F2053C50482}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.WebView.Test", "WebView\WebView\test\Microsoft.AspNetCore.Components.WebView.Test.csproj", "{BD1EEC86-7644-5761-1346-089385B5A749}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{83B7D554-7FE9-7791-B8EF-9CEB0E9B38ED}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HostedBlazorWebassemblyApp", "HostedBlazorWebassemblyApp", "{D8364222-347F-695D-960D-5F83D75F4A3E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HostedBlazorWebassemblyApp.Client", "WebAssembly\Samples\HostedBlazorWebassemblyApp\Client\HostedBlazorWebassemblyApp.Client.csproj", "{3F8E5EC2-DB89-39DB-E3FA-43EB5A64A425}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HostedBlazorWebassemblyApp.Server", "WebAssembly\Samples\HostedBlazorWebassemblyApp\Server\HostedBlazorWebassemblyApp.Server.csproj", "{A46AAF9E-39CC-085A-4EA7-A99537EA00E6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HostedBlazorWebassemblyApp.Shared", "WebAssembly\Samples\HostedBlazorWebassemblyApp\Shared\HostedBlazorWebassemblyApp.Shared.csproj", "{A0ACE183-5E76-FEB4-09F5-7ABE8795E99B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{6636F944-0A81-C8B7-09AA-414B5978CE4C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PhotinoPlatform", "PhotinoPlatform", "{316E34B4-CD88-C8D3-E9E3-33F5B40E2CD1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.WebView.Photino", "WebView\Samples\PhotinoPlatform\src\Microsoft.AspNetCore.Components.WebView.Photino.csproj", "{DFB12E12-A532-EF12-87F4-BD537FF1662F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{978EA837-C2CB-DF69-BCE3-815ADE704D37}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhotinoTestApp", "WebView\Samples\PhotinoPlatform\testassets\PhotinoTestApp\PhotinoTestApp.csproj", "{9201E12B-135F-5E14-0672-AB5561520397}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E1ED6FA5-4DEB-A512-45B4-EDB7CD97BA9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1ED6FA5-4DEB-A512-45B4-EDB7CD97BA9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1ED6FA5-4DEB-A512-45B4-EDB7CD97BA9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1ED6FA5-4DEB-A512-45B4-EDB7CD97BA9D}.Release|Any CPU.Build.0 = Release|Any CPU + {EB0043B9-6579-7370-BED1-D06FE9DFC755}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB0043B9-6579-7370-BED1-D06FE9DFC755}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB0043B9-6579-7370-BED1-D06FE9DFC755}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB0043B9-6579-7370-BED1-D06FE9DFC755}.Release|Any CPU.Build.0 = Release|Any CPU + {BD2C8FC3-3703-7EF9-4551-70298314EF37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD2C8FC3-3703-7EF9-4551-70298314EF37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD2C8FC3-3703-7EF9-4551-70298314EF37}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD2C8FC3-3703-7EF9-4551-70298314EF37}.Release|Any CPU.Build.0 = Release|Any CPU + {CA605D3B-4E98-B5CB-64BA-5F4BB643CEC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA605D3B-4E98-B5CB-64BA-5F4BB643CEC8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA605D3B-4E98-B5CB-64BA-5F4BB643CEC8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA605D3B-4E98-B5CB-64BA-5F4BB643CEC8}.Release|Any CPU.Build.0 = Release|Any CPU + {A19F1FF1-BB91-3A1F-2D5C-FA6137FD214F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A19F1FF1-BB91-3A1F-2D5C-FA6137FD214F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A19F1FF1-BB91-3A1F-2D5C-FA6137FD214F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A19F1FF1-BB91-3A1F-2D5C-FA6137FD214F}.Release|Any CPU.Build.0 = Release|Any CPU + {99B7D762-BB6F-C5F9-41F8-9A7D13A9B9FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99B7D762-BB6F-C5F9-41F8-9A7D13A9B9FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99B7D762-BB6F-C5F9-41F8-9A7D13A9B9FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99B7D762-BB6F-C5F9-41F8-9A7D13A9B9FE}.Release|Any CPU.Build.0 = Release|Any CPU + {C45B3BA4-7E52-73AB-4942-5899597D4C36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C45B3BA4-7E52-73AB-4942-5899597D4C36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C45B3BA4-7E52-73AB-4942-5899597D4C36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C45B3BA4-7E52-73AB-4942-5899597D4C36}.Release|Any CPU.Build.0 = Release|Any CPU + {D554BB1E-51B7-3752-8566-135C9ADB7DCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D554BB1E-51B7-3752-8566-135C9ADB7DCA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D554BB1E-51B7-3752-8566-135C9ADB7DCA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D554BB1E-51B7-3752-8566-135C9ADB7DCA}.Release|Any CPU.Build.0 = Release|Any CPU + {AD2A718F-7A4F-70A8-DEF8-2A89689A36DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD2A718F-7A4F-70A8-DEF8-2A89689A36DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD2A718F-7A4F-70A8-DEF8-2A89689A36DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD2A718F-7A4F-70A8-DEF8-2A89689A36DC}.Release|Any CPU.Build.0 = Release|Any CPU + {E8540B19-EB66-03C6-13D2-6EE233C61524}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8540B19-EB66-03C6-13D2-6EE233C61524}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8540B19-EB66-03C6-13D2-6EE233C61524}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8540B19-EB66-03C6-13D2-6EE233C61524}.Release|Any CPU.Build.0 = Release|Any CPU + {9B6BB5AE-8E5B-531A-F5B6-AB5D3B952452}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B6BB5AE-8E5B-531A-F5B6-AB5D3B952452}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B6BB5AE-8E5B-531A-F5B6-AB5D3B952452}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B6BB5AE-8E5B-531A-F5B6-AB5D3B952452}.Release|Any CPU.Build.0 = Release|Any CPU + {AF6B0E54-144A-5ECE-91FF-55B1BFEE5039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF6B0E54-144A-5ECE-91FF-55B1BFEE5039}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF6B0E54-144A-5ECE-91FF-55B1BFEE5039}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF6B0E54-144A-5ECE-91FF-55B1BFEE5039}.Release|Any CPU.Build.0 = Release|Any CPU + {D3B32404-5953-67EE-DBA7-6A8F1AB1A0B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3B32404-5953-67EE-DBA7-6A8F1AB1A0B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3B32404-5953-67EE-DBA7-6A8F1AB1A0B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3B32404-5953-67EE-DBA7-6A8F1AB1A0B9}.Release|Any CPU.Build.0 = Release|Any CPU + {FC12C86F-5866-69D4-B7E5-3F0EB4E3F754}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC12C86F-5866-69D4-B7E5-3F0EB4E3F754}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC12C86F-5866-69D4-B7E5-3F0EB4E3F754}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC12C86F-5866-69D4-B7E5-3F0EB4E3F754}.Release|Any CPU.Build.0 = Release|Any CPU + {74BEC791-7060-4E1D-E77C-2490EFE2DFD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74BEC791-7060-4E1D-E77C-2490EFE2DFD0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74BEC791-7060-4E1D-E77C-2490EFE2DFD0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74BEC791-7060-4E1D-E77C-2490EFE2DFD0}.Release|Any CPU.Build.0 = Release|Any CPU + {1FCE400C-3875-3963-2105-084823B55013}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FCE400C-3875-3963-2105-084823B55013}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FCE400C-3875-3963-2105-084823B55013}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FCE400C-3875-3963-2105-084823B55013}.Release|Any CPU.Build.0 = Release|Any CPU + {3893606D-3E03-0FFD-95B4-A93121438268}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3893606D-3E03-0FFD-95B4-A93121438268}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3893606D-3E03-0FFD-95B4-A93121438268}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3893606D-3E03-0FFD-95B4-A93121438268}.Release|Any CPU.Build.0 = Release|Any CPU + {8A37E723-99EF-15F0-8C1F-E9755F8EDEEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A37E723-99EF-15F0-8C1F-E9755F8EDEEF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A37E723-99EF-15F0-8C1F-E9755F8EDEEF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A37E723-99EF-15F0-8C1F-E9755F8EDEEF}.Release|Any CPU.Build.0 = Release|Any CPU + {AEEBE2B8-B7E4-178C-5640-626E89EF0C75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AEEBE2B8-B7E4-178C-5640-626E89EF0C75}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AEEBE2B8-B7E4-178C-5640-626E89EF0C75}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AEEBE2B8-B7E4-178C-5640-626E89EF0C75}.Release|Any CPU.Build.0 = Release|Any CPU + {B6384FF0-6E94-4ADE-F932-F4E886C1CADD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6384FF0-6E94-4ADE-F932-F4E886C1CADD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6384FF0-6E94-4ADE-F932-F4E886C1CADD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6384FF0-6E94-4ADE-F932-F4E886C1CADD}.Release|Any CPU.Build.0 = Release|Any CPU + {8F18837A-BB27-ECF0-C1AA-4838E1C2A781}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F18837A-BB27-ECF0-C1AA-4838E1C2A781}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F18837A-BB27-ECF0-C1AA-4838E1C2A781}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F18837A-BB27-ECF0-C1AA-4838E1C2A781}.Release|Any CPU.Build.0 = Release|Any CPU + {CC7E1BFF-D2BF-D91B-92DC-5B5B99898A68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC7E1BFF-D2BF-D91B-92DC-5B5B99898A68}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC7E1BFF-D2BF-D91B-92DC-5B5B99898A68}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC7E1BFF-D2BF-D91B-92DC-5B5B99898A68}.Release|Any CPU.Build.0 = Release|Any CPU + {672746A2-DFDD-0444-0D70-E6B7CB8A4ABE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {672746A2-DFDD-0444-0D70-E6B7CB8A4ABE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {672746A2-DFDD-0444-0D70-E6B7CB8A4ABE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {672746A2-DFDD-0444-0D70-E6B7CB8A4ABE}.Release|Any CPU.Build.0 = Release|Any CPU + {A4977A0A-A95B-7C35-F319-521331739C01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4977A0A-A95B-7C35-F319-521331739C01}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4977A0A-A95B-7C35-F319-521331739C01}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4977A0A-A95B-7C35-F319-521331739C01}.Release|Any CPU.Build.0 = Release|Any CPU + {8BF2A51C-F22C-0D39-AAC7-7AE776B3D3D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BF2A51C-F22C-0D39-AAC7-7AE776B3D3D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BF2A51C-F22C-0D39-AAC7-7AE776B3D3D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BF2A51C-F22C-0D39-AAC7-7AE776B3D3D0}.Release|Any CPU.Build.0 = Release|Any CPU + {62F0E24C-EE9B-D5B3-D2CF-19358722D081}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62F0E24C-EE9B-D5B3-D2CF-19358722D081}.Debug|Any CPU.Build.0 = Debug|Any CPU + {62F0E24C-EE9B-D5B3-D2CF-19358722D081}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62F0E24C-EE9B-D5B3-D2CF-19358722D081}.Release|Any CPU.Build.0 = Release|Any CPU + {94769285-DE8C-43CF-8F43-2E6081DE261F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94769285-DE8C-43CF-8F43-2E6081DE261F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94769285-DE8C-43CF-8F43-2E6081DE261F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94769285-DE8C-43CF-8F43-2E6081DE261F}.Release|Any CPU.Build.0 = Release|Any CPU + {005A99AE-9829-5288-081E-6AA2C7068F41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {005A99AE-9829-5288-081E-6AA2C7068F41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {005A99AE-9829-5288-081E-6AA2C7068F41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {005A99AE-9829-5288-081E-6AA2C7068F41}.Release|Any CPU.Build.0 = Release|Any CPU + {384D771F-1D94-6B01-A1C3-435584F4B3BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {384D771F-1D94-6B01-A1C3-435584F4B3BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {384D771F-1D94-6B01-A1C3-435584F4B3BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {384D771F-1D94-6B01-A1C3-435584F4B3BF}.Release|Any CPU.Build.0 = Release|Any CPU + {DA514A3D-030F-3753-55AC-95601ADF7FE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA514A3D-030F-3753-55AC-95601ADF7FE9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA514A3D-030F-3753-55AC-95601ADF7FE9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA514A3D-030F-3753-55AC-95601ADF7FE9}.Release|Any CPU.Build.0 = Release|Any CPU + {FEEE3D25-53D4-9F34-27B7-CBC04873009F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FEEE3D25-53D4-9F34-27B7-CBC04873009F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FEEE3D25-53D4-9F34-27B7-CBC04873009F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FEEE3D25-53D4-9F34-27B7-CBC04873009F}.Release|Any CPU.Build.0 = Release|Any CPU + {ACC68622-35F7-164F-FD1B-E68510FB877C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACC68622-35F7-164F-FD1B-E68510FB877C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACC68622-35F7-164F-FD1B-E68510FB877C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACC68622-35F7-164F-FD1B-E68510FB877C}.Release|Any CPU.Build.0 = Release|Any CPU + {DB54C63C-C3DD-FDB5-0D9E-EE12177496DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB54C63C-C3DD-FDB5-0D9E-EE12177496DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB54C63C-C3DD-FDB5-0D9E-EE12177496DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB54C63C-C3DD-FDB5-0D9E-EE12177496DE}.Release|Any CPU.Build.0 = Release|Any CPU + {42C50CDC-3AF3-2866-9186-AE532C918BBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42C50CDC-3AF3-2866-9186-AE532C918BBA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42C50CDC-3AF3-2866-9186-AE532C918BBA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42C50CDC-3AF3-2866-9186-AE532C918BBA}.Release|Any CPU.Build.0 = Release|Any CPU + {0262DCC6-3D46-CBCE-4888-9F37FDAAE470}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0262DCC6-3D46-CBCE-4888-9F37FDAAE470}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0262DCC6-3D46-CBCE-4888-9F37FDAAE470}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0262DCC6-3D46-CBCE-4888-9F37FDAAE470}.Release|Any CPU.Build.0 = Release|Any CPU + {B7BBE6FD-A4D7-3416-ECEB-05A6B5239937}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7BBE6FD-A4D7-3416-ECEB-05A6B5239937}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7BBE6FD-A4D7-3416-ECEB-05A6B5239937}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7BBE6FD-A4D7-3416-ECEB-05A6B5239937}.Release|Any CPU.Build.0 = Release|Any CPU + {3C135F94-99F0-BF85-CD5D-F2BD288FC230}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C135F94-99F0-BF85-CD5D-F2BD288FC230}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C135F94-99F0-BF85-CD5D-F2BD288FC230}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C135F94-99F0-BF85-CD5D-F2BD288FC230}.Release|Any CPU.Build.0 = Release|Any CPU + {8FC99803-7FB1-E7FD-F579-4FF49A324139}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FC99803-7FB1-E7FD-F579-4FF49A324139}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FC99803-7FB1-E7FD-F579-4FF49A324139}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FC99803-7FB1-E7FD-F579-4FF49A324139}.Release|Any CPU.Build.0 = Release|Any CPU + {C004A041-806C-248D-6972-BC44CBD8AFDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C004A041-806C-248D-6972-BC44CBD8AFDB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C004A041-806C-248D-6972-BC44CBD8AFDB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C004A041-806C-248D-6972-BC44CBD8AFDB}.Release|Any CPU.Build.0 = Release|Any CPU + {69AB9EB3-9FB3-F134-F7BB-6F20719F293D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69AB9EB3-9FB3-F134-F7BB-6F20719F293D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69AB9EB3-9FB3-F134-F7BB-6F20719F293D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69AB9EB3-9FB3-F134-F7BB-6F20719F293D}.Release|Any CPU.Build.0 = Release|Any CPU + {D9D391EA-769E-A96E-D230-453C9B9365DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D9D391EA-769E-A96E-D230-453C9B9365DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D9D391EA-769E-A96E-D230-453C9B9365DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D9D391EA-769E-A96E-D230-453C9B9365DE}.Release|Any CPU.Build.0 = Release|Any CPU + {F2EF8EC2-4C19-61B5-DCF4-9014E729C057}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2EF8EC2-4C19-61B5-DCF4-9014E729C057}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2EF8EC2-4C19-61B5-DCF4-9014E729C057}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2EF8EC2-4C19-61B5-DCF4-9014E729C057}.Release|Any CPU.Build.0 = Release|Any CPU + {2EEDD992-0F07-908F-52D7-52E0D0816172}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2EEDD992-0F07-908F-52D7-52E0D0816172}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2EEDD992-0F07-908F-52D7-52E0D0816172}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2EEDD992-0F07-908F-52D7-52E0D0816172}.Release|Any CPU.Build.0 = Release|Any CPU + {BABB04C9-D860-D830-3E50-7AF5D1CDB1C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BABB04C9-D860-D830-3E50-7AF5D1CDB1C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BABB04C9-D860-D830-3E50-7AF5D1CDB1C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BABB04C9-D860-D830-3E50-7AF5D1CDB1C3}.Release|Any CPU.Build.0 = Release|Any CPU + {35FF93EC-4CEB-09BC-C3C1-B218E4FEBB90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35FF93EC-4CEB-09BC-C3C1-B218E4FEBB90}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35FF93EC-4CEB-09BC-C3C1-B218E4FEBB90}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35FF93EC-4CEB-09BC-C3C1-B218E4FEBB90}.Release|Any CPU.Build.0 = Release|Any CPU + {D94FCCB0-E6DD-75AF-9D33-8B2E88D851DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D94FCCB0-E6DD-75AF-9D33-8B2E88D851DF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D94FCCB0-E6DD-75AF-9D33-8B2E88D851DF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D94FCCB0-E6DD-75AF-9D33-8B2E88D851DF}.Release|Any CPU.Build.0 = Release|Any CPU + {FEF03057-3DED-46E9-44C2-E5A71183CBF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FEF03057-3DED-46E9-44C2-E5A71183CBF7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FEF03057-3DED-46E9-44C2-E5A71183CBF7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FEF03057-3DED-46E9-44C2-E5A71183CBF7}.Release|Any CPU.Build.0 = Release|Any CPU + {D7372AB6-0FF4-A406-B5D8-2DD7AEA27565}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7372AB6-0FF4-A406-B5D8-2DD7AEA27565}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7372AB6-0FF4-A406-B5D8-2DD7AEA27565}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7372AB6-0FF4-A406-B5D8-2DD7AEA27565}.Release|Any CPU.Build.0 = Release|Any CPU + {E7C427B4-5DAA-2DEC-D4FE-25513848B07E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E7C427B4-5DAA-2DEC-D4FE-25513848B07E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E7C427B4-5DAA-2DEC-D4FE-25513848B07E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E7C427B4-5DAA-2DEC-D4FE-25513848B07E}.Release|Any CPU.Build.0 = Release|Any CPU + {8C83C95C-9A82-1DFA-A4DB-90EC50F32D86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C83C95C-9A82-1DFA-A4DB-90EC50F32D86}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C83C95C-9A82-1DFA-A4DB-90EC50F32D86}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C83C95C-9A82-1DFA-A4DB-90EC50F32D86}.Release|Any CPU.Build.0 = Release|Any CPU + {72B9C791-DBD1-32DB-C566-B5D3B1D405E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72B9C791-DBD1-32DB-C566-B5D3B1D405E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72B9C791-DBD1-32DB-C566-B5D3B1D405E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72B9C791-DBD1-32DB-C566-B5D3B1D405E1}.Release|Any CPU.Build.0 = Release|Any CPU + {F3C7C76A-FF30-4935-E238-7C5A838E3BCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3C7C76A-FF30-4935-E238-7C5A838E3BCE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3C7C76A-FF30-4935-E238-7C5A838E3BCE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3C7C76A-FF30-4935-E238-7C5A838E3BCE}.Release|Any CPU.Build.0 = Release|Any CPU + {585E4EE6-E918-DDEC-79AD-C956E6A19E4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {585E4EE6-E918-DDEC-79AD-C956E6A19E4B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {585E4EE6-E918-DDEC-79AD-C956E6A19E4B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {585E4EE6-E918-DDEC-79AD-C956E6A19E4B}.Release|Any CPU.Build.0 = Release|Any CPU + {C80EB816-1E52-8D59-6C14-5567A23B5D82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C80EB816-1E52-8D59-6C14-5567A23B5D82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C80EB816-1E52-8D59-6C14-5567A23B5D82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C80EB816-1E52-8D59-6C14-5567A23B5D82}.Release|Any CPU.Build.0 = Release|Any CPU + {C44AFCA8-EDE5-0389-CFE8-22A83AC3ECF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C44AFCA8-EDE5-0389-CFE8-22A83AC3ECF7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C44AFCA8-EDE5-0389-CFE8-22A83AC3ECF7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C44AFCA8-EDE5-0389-CFE8-22A83AC3ECF7}.Release|Any CPU.Build.0 = Release|Any CPU + {491EE542-B9EB-10C7-06A2-6F2053C50482}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {491EE542-B9EB-10C7-06A2-6F2053C50482}.Debug|Any CPU.Build.0 = Debug|Any CPU + {491EE542-B9EB-10C7-06A2-6F2053C50482}.Release|Any CPU.ActiveCfg = Release|Any CPU + {491EE542-B9EB-10C7-06A2-6F2053C50482}.Release|Any CPU.Build.0 = Release|Any CPU + {BD1EEC86-7644-5761-1346-089385B5A749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD1EEC86-7644-5761-1346-089385B5A749}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD1EEC86-7644-5761-1346-089385B5A749}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD1EEC86-7644-5761-1346-089385B5A749}.Release|Any CPU.Build.0 = Release|Any CPU + {3F8E5EC2-DB89-39DB-E3FA-43EB5A64A425}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F8E5EC2-DB89-39DB-E3FA-43EB5A64A425}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F8E5EC2-DB89-39DB-E3FA-43EB5A64A425}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F8E5EC2-DB89-39DB-E3FA-43EB5A64A425}.Release|Any CPU.Build.0 = Release|Any CPU + {A46AAF9E-39CC-085A-4EA7-A99537EA00E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A46AAF9E-39CC-085A-4EA7-A99537EA00E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A46AAF9E-39CC-085A-4EA7-A99537EA00E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A46AAF9E-39CC-085A-4EA7-A99537EA00E6}.Release|Any CPU.Build.0 = Release|Any CPU + {A0ACE183-5E76-FEB4-09F5-7ABE8795E99B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0ACE183-5E76-FEB4-09F5-7ABE8795E99B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0ACE183-5E76-FEB4-09F5-7ABE8795E99B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0ACE183-5E76-FEB4-09F5-7ABE8795E99B}.Release|Any CPU.Build.0 = Release|Any CPU + {DFB12E12-A532-EF12-87F4-BD537FF1662F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DFB12E12-A532-EF12-87F4-BD537FF1662F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFB12E12-A532-EF12-87F4-BD537FF1662F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DFB12E12-A532-EF12-87F4-BD537FF1662F}.Release|Any CPU.Build.0 = Release|Any CPU + {9201E12B-135F-5E14-0672-AB5561520397}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9201E12B-135F-5E14-0672-AB5561520397}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9201E12B-135F-5E14-0672-AB5561520397}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9201E12B-135F-5E14-0672-AB5561520397}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E1ED6FA5-4DEB-A512-45B4-EDB7CD97BA9D} = {F883AD26-E2D9-0064-49F2-B333EF800629} + {EB0043B9-6579-7370-BED1-D06FE9DFC755} = {F883AD26-E2D9-0064-49F2-B333EF800629} + {BD2C8FC3-3703-7EF9-4551-70298314EF37} = {278F3FFB-8C21-435B-507F-61B78986A400} + {CA605D3B-4E98-B5CB-64BA-5F4BB643CEC8} = {278F3FFB-8C21-435B-507F-61B78986A400} + {A19F1FF1-BB91-3A1F-2D5C-FA6137FD214F} = {04B96F53-E713-70DE-173F-A95873598AF4} + {99B7D762-BB6F-C5F9-41F8-9A7D13A9B9FE} = {F30895DC-C3AC-FF44-FFF8-D51D98AE1B61} + {C45B3BA4-7E52-73AB-4942-5899597D4C36} = {F30895DC-C3AC-FF44-FFF8-D51D98AE1B61} + {D554BB1E-51B7-3752-8566-135C9ADB7DCA} = {F30895DC-C3AC-FF44-FFF8-D51D98AE1B61} + {AD2A718F-7A4F-70A8-DEF8-2A89689A36DC} = {1956B0E3-C3A6-1D29-BB64-D91F2292772C} + {E8540B19-EB66-03C6-13D2-6EE233C61524} = {DCF9C18E-19C8-9748-4C1C-32AB5F62F46B} + {9B6BB5AE-8E5B-531A-F5B6-AB5D3B952452} = {DCF9C18E-19C8-9748-4C1C-32AB5F62F46B} + {AF6B0E54-144A-5ECE-91FF-55B1BFEE5039} = {5D4ABE75-A633-7578-A6F0-BB88D48E78A4} + {D3B32404-5953-67EE-DBA7-6A8F1AB1A0B9} = {5D4ABE75-A633-7578-A6F0-BB88D48E78A4} + {FC12C86F-5866-69D4-B7E5-3F0EB4E3F754} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA} + {74BEC791-7060-4E1D-E77C-2490EFE2DFD0} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA} + {1FCE400C-3875-3963-2105-084823B55013} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA} + {3893606D-3E03-0FFD-95B4-A93121438268} = {DAD03CFC-D5FA-AC94-41C7-9ABAB326F09E} + {8A37E723-99EF-15F0-8C1F-E9755F8EDEEF} = {DAD03CFC-D5FA-AC94-41C7-9ABAB326F09E} + {AEEBE2B8-B7E4-178C-5640-626E89EF0C75} = {0C88DD14-F956-CE84-757C-A364CCF449FC} + {B6384FF0-6E94-4ADE-F932-F4E886C1CADD} = {256F4E96-6A73-9ABE-786F-840C672A50F4} + {8F18837A-BB27-ECF0-C1AA-4838E1C2A781} = {256F4E96-6A73-9ABE-786F-840C672A50F4} + {595095FC-11E7-9A6C-713C-1AA81E4007ED} = {04B96F53-E713-70DE-173F-A95873598AF4} + {CC7E1BFF-D2BF-D91B-92DC-5B5B99898A68} = {595095FC-11E7-9A6C-713C-1AA81E4007ED} + {672746A2-DFDD-0444-0D70-E6B7CB8A4ABE} = {595095FC-11E7-9A6C-713C-1AA81E4007ED} + {A4977A0A-A95B-7C35-F319-521331739C01} = {595095FC-11E7-9A6C-713C-1AA81E4007ED} + {E329DD7B-EA06-0E35-2452-F0374B45C92D} = {453459A7-61B0-99BD-5FE4-DC2B7EAB7FCC} + {8BF2A51C-F22C-0D39-AAC7-7AE776B3D3D0} = {E329DD7B-EA06-0E35-2452-F0374B45C92D} + {8B02DB0C-FE4B-328D-C414-580634248F4A} = {453459A7-61B0-99BD-5FE4-DC2B7EAB7FCC} + {62F0E24C-EE9B-D5B3-D2CF-19358722D081} = {8B02DB0C-FE4B-328D-C414-580634248F4A} + {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} = {0C88DD14-F956-CE84-757C-A364CCF449FC} + {94769285-DE8C-43CF-8F43-2E6081DE261F} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} + {005A99AE-9829-5288-081E-6AA2C7068F41} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} + {384D771F-1D94-6B01-A1C3-435584F4B3BF} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} + {DA514A3D-030F-3753-55AC-95601ADF7FE9} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} + {FEEE3D25-53D4-9F34-27B7-CBC04873009F} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} + {ACC68622-35F7-164F-FD1B-E68510FB877C} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} + {DB54C63C-C3DD-FDB5-0D9E-EE12177496DE} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} + {42C50CDC-3AF3-2866-9186-AE532C918BBA} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} + {0262DCC6-3D46-CBCE-4888-9F37FDAAE470} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} + {B7BBE6FD-A4D7-3416-ECEB-05A6B5239937} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} + {A79C6110-8F76-1DB1-0FD8-A69EDA72E8BC} = {032573FE-E973-EE49-F164-6E17D60C24C1} + {3C135F94-99F0-BF85-CD5D-F2BD288FC230} = {A79C6110-8F76-1DB1-0FD8-A69EDA72E8BC} + {5ED11752-357D-2F9A-0D62-6FBFEC2923EA} = {032573FE-E973-EE49-F164-6E17D60C24C1} + {8FC99803-7FB1-E7FD-F579-4FF49A324139} = {5ED11752-357D-2F9A-0D62-6FBFEC2923EA} + {2D1527BB-743D-5D96-93C8-1B5741ADC02B} = {032573FE-E973-EE49-F164-6E17D60C24C1} + {C004A041-806C-248D-6972-BC44CBD8AFDB} = {2D1527BB-743D-5D96-93C8-1B5741ADC02B} + {F24703D6-F12F-EE78-E988-49EB11C8433B} = {032573FE-E973-EE49-F164-6E17D60C24C1} + {69AB9EB3-9FB3-F134-F7BB-6F20719F293D} = {F24703D6-F12F-EE78-E988-49EB11C8433B} + {D9D391EA-769E-A96E-D230-453C9B9365DE} = {F24703D6-F12F-EE78-E988-49EB11C8433B} + {F60C0A93-E7A1-AA4F-C800-18D21FD1802D} = {032573FE-E973-EE49-F164-6E17D60C24C1} + {F2EF8EC2-4C19-61B5-DCF4-9014E729C057} = {F60C0A93-E7A1-AA4F-C800-18D21FD1802D} + {2EEDD992-0F07-908F-52D7-52E0D0816172} = {F60C0A93-E7A1-AA4F-C800-18D21FD1802D} + {BABB04C9-D860-D830-3E50-7AF5D1CDB1C3} = {F60C0A93-E7A1-AA4F-C800-18D21FD1802D} + {35FF93EC-4CEB-09BC-C3C1-B218E4FEBB90} = {F60C0A93-E7A1-AA4F-C800-18D21FD1802D} + {D94FCCB0-E6DD-75AF-9D33-8B2E88D851DF} = {F60C0A93-E7A1-AA4F-C800-18D21FD1802D} + {FEF03057-3DED-46E9-44C2-E5A71183CBF7} = {F60C0A93-E7A1-AA4F-C800-18D21FD1802D} + {D7372AB6-0FF4-A406-B5D8-2DD7AEA27565} = {F60C0A93-E7A1-AA4F-C800-18D21FD1802D} + {E7C427B4-5DAA-2DEC-D4FE-25513848B07E} = {F60C0A93-E7A1-AA4F-C800-18D21FD1802D} + {8C83C95C-9A82-1DFA-A4DB-90EC50F32D86} = {F60C0A93-E7A1-AA4F-C800-18D21FD1802D} + {2114FDDC-431A-A228-9FBD-A2D77B669641} = {032573FE-E973-EE49-F164-6E17D60C24C1} + {72B9C791-DBD1-32DB-C566-B5D3B1D405E1} = {2114FDDC-431A-A228-9FBD-A2D77B669641} + {F3C7C76A-FF30-4935-E238-7C5A838E3BCE} = {2114FDDC-431A-A228-9FBD-A2D77B669641} + {2C226B6F-947F-806E-AB56-5318C87BE998} = {032573FE-E973-EE49-F164-6E17D60C24C1} + {585E4EE6-E918-DDEC-79AD-C956E6A19E4B} = {2C226B6F-947F-806E-AB56-5318C87BE998} + {C80EB816-1E52-8D59-6C14-5567A23B5D82} = {2C226B6F-947F-806E-AB56-5318C87BE998} + {E199B8FB-B34C-E815-5C68-8C4C236C3F5B} = {F75C3326-23C9-B46B-1CDE-63D2E405EED1} + {C44AFCA8-EDE5-0389-CFE8-22A83AC3ECF7} = {E199B8FB-B34C-E815-5C68-8C4C236C3F5B} + {CA12ED3E-B35C-B56F-F8CA-F6C9996B003D} = {F75C3326-23C9-B46B-1CDE-63D2E405EED1} + {491EE542-B9EB-10C7-06A2-6F2053C50482} = {CA12ED3E-B35C-B56F-F8CA-F6C9996B003D} + {BD1EEC86-7644-5761-1346-089385B5A749} = {CA12ED3E-B35C-B56F-F8CA-F6C9996B003D} + {83B7D554-7FE9-7791-B8EF-9CEB0E9B38ED} = {032573FE-E973-EE49-F164-6E17D60C24C1} + {D8364222-347F-695D-960D-5F83D75F4A3E} = {83B7D554-7FE9-7791-B8EF-9CEB0E9B38ED} + {3F8E5EC2-DB89-39DB-E3FA-43EB5A64A425} = {D8364222-347F-695D-960D-5F83D75F4A3E} + {A46AAF9E-39CC-085A-4EA7-A99537EA00E6} = {D8364222-347F-695D-960D-5F83D75F4A3E} + {A0ACE183-5E76-FEB4-09F5-7ABE8795E99B} = {D8364222-347F-695D-960D-5F83D75F4A3E} + {6636F944-0A81-C8B7-09AA-414B5978CE4C} = {F75C3326-23C9-B46B-1CDE-63D2E405EED1} + {316E34B4-CD88-C8D3-E9E3-33F5B40E2CD1} = {6636F944-0A81-C8B7-09AA-414B5978CE4C} + {DFB12E12-A532-EF12-87F4-BD537FF1662F} = {316E34B4-CD88-C8D3-E9E3-33F5B40E2CD1} + {978EA837-C2CB-DF69-BCE3-815ADE704D37} = {316E34B4-CD88-C8D3-E9E3-33F5B40E2CD1} + {9201E12B-135F-5E14-0672-AB5561520397} = {978EA837-C2CB-DF69-BCE3-815ADE704D37} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B7933EA7-FBFC-4C5B-BC70-A09DDF3FDD77} + EndGlobalSection +EndGlobal diff --git a/src/Components/Components.slnf b/src/Components/Components.slnf index 45c51cd0f4ee..49856058cbec 100644 --- a/src/Components/Components.slnf +++ b/src/Components/Components.slnf @@ -2,9 +2,27 @@ "solution": { "path": "..\\..\\AspNetCore.sln", "projects": [ + "eng\\tools\\BaselineGenerator\\BaselineGenerator.csproj", + "eng\\tools\\HelixTestRunner\\HelixTestRunner.csproj", + "eng\\tools\\RepoTasks\\RepoTasks.csproj", + "src\\Analyzers\\Analyzers\\src\\Microsoft.AspNetCore.Analyzers.csproj", + "src\\Analyzers\\Analyzers\\test\\Microsoft.AspNetCore.Analyzers.Test.csproj", "src\\Analyzers\\Microsoft.AspNetCore.Analyzer.Testing\\src\\Microsoft.AspNetCore.Analyzer.Testing.csproj", + "src\\Antiforgery\\samples\\MinimalFormSample\\MinimalFormSample.csproj", "src\\Antiforgery\\src\\Microsoft.AspNetCore.Antiforgery.csproj", + "src\\Antiforgery\\test\\Microsoft.AspNetCore.Antiforgery.Test.csproj", "src\\Assets\\Microsoft.AspNetCore.App.Internal.Assets.csproj", + "src\\Azure\\AzureAppServices.HostingStartup\\src\\Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj", + "src\\Azure\\AzureAppServicesIntegration\\src\\Microsoft.AspNetCore.AzureAppServicesIntegration.csproj", + "src\\Azure\\AzureAppServicesIntegration\\test\\Microsoft.AspNetCore.AzureAppServicesIntegration.Tests.csproj", + "src\\Azure\\samples\\AzureAppServicesHostingStartupSample\\AzureAppServicesHostingStartupSample.csproj", + "src\\Azure\\samples\\AzureAppServicesSample\\AzureAppServicesSample.csproj", + "src\\BuildAfterTargetingPack\\BuildAfterTargetingPack.csproj", + "src\\Caching\\SqlServer\\src\\Microsoft.Extensions.Caching.SqlServer.csproj", + "src\\Caching\\SqlServer\\test\\Microsoft.Extensions.Caching.SqlServer.Tests.csproj", + "src\\Caching\\StackExchangeRedis\\src\\Microsoft.Extensions.Caching.StackExchangeRedis.csproj", + "src\\Caching\\StackExchangeRedis\\test\\Microsoft.Extensions.Caching.StackExchangeRedis.Tests.csproj", + "src\\Caching\\perf\\MicroBenchmarks\\Microsoft.Extensions.Caching.MicroBenchmarks\\Microsoft.Extensions.Caching.MicroBenchmarks.csproj", "src\\Components\\Analyzers\\src\\Microsoft.AspNetCore.Components.Analyzers.csproj", "src\\Components\\Analyzers\\test\\Microsoft.AspNetCore.Components.Analyzers.Tests.csproj", "src\\Components\\Authorization\\src\\Microsoft.AspNetCore.Components.Authorization.csproj", @@ -53,6 +71,7 @@ "src\\Components\\Web\\src\\Microsoft.AspNetCore.Components.Web.csproj", "src\\Components\\Web\\test\\Microsoft.AspNetCore.Components.Web.Tests.csproj", "src\\Components\\benchmarkapps\\BlazingPizza.Server\\BlazingPizza.Server.csproj", + "src\\Components\\benchmarkapps\\Wasm.Performance\\ConsoleHost\\Wasm.Performance.ConsoleHost.csproj", "src\\Components\\benchmarkapps\\Wasm.Performance\\Driver\\Wasm.Performance.Driver.csproj", "src\\Components\\benchmarkapps\\Wasm.Performance\\TestApp\\Wasm.Performance.TestApp.csproj", "src\\Components\\test\\E2ETest\\Microsoft.AspNetCore.Components.E2ETests.csproj", @@ -64,97 +83,500 @@ "src\\Components\\test\\testassets\\ComponentsApp.Server\\ComponentsApp.Server.csproj", "src\\Components\\test\\testassets\\GlobalizationWasmApp\\GlobalizationWasmApp.csproj", "src\\Components\\test\\testassets\\LazyTestContentPackage\\LazyTestContentPackage.csproj", + "src\\Components\\test\\testassets\\NotReferencedInWasmCodePackage\\NotReferencedInWasmCodePackage.csproj", "src\\Components\\test\\testassets\\TestContentPackage\\TestContentPackage.csproj", + "src\\Configuration.KeyPerFile\\src\\Microsoft.Extensions.Configuration.KeyPerFile.csproj", + "src\\Configuration.KeyPerFile\\test\\Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj", "src\\DataProtection\\Abstractions\\src\\Microsoft.AspNetCore.DataProtection.Abstractions.csproj", + "src\\DataProtection\\Abstractions\\test\\Microsoft.AspNetCore.DataProtection.Abstractions.Tests.csproj", "src\\DataProtection\\Cryptography.Internal\\src\\Microsoft.AspNetCore.Cryptography.Internal.csproj", + "src\\DataProtection\\Cryptography.Internal\\test\\Microsoft.AspNetCore.Cryptography.Internal.Tests.csproj", "src\\DataProtection\\Cryptography.KeyDerivation\\src\\Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj", + "src\\DataProtection\\Cryptography.KeyDerivation\\test\\Microsoft.AspNetCore.Cryptography.KeyDerivation.Tests.csproj", "src\\DataProtection\\DataProtection\\src\\Microsoft.AspNetCore.DataProtection.csproj", + "src\\DataProtection\\DataProtection\\test\\Microsoft.AspNetCore.DataProtection.Tests\\Microsoft.AspNetCore.DataProtection.Tests.csproj", + "src\\DataProtection\\EntityFrameworkCore\\src\\Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj", + "src\\DataProtection\\EntityFrameworkCore\\test\\Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test.csproj", "src\\DataProtection\\Extensions\\src\\Microsoft.AspNetCore.DataProtection.Extensions.csproj", + "src\\DataProtection\\Extensions\\test\\Microsoft.AspNetCore.DataProtection.Extensions.Tests.csproj", + "src\\DataProtection\\StackExchangeRedis\\src\\Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj", + "src\\DataProtection\\StackExchangeRedis\\test\\Microsoft.AspNetCore.DataProtection.StackExchangeRedis.Tests.csproj", + "src\\DataProtection\\samples\\CustomEncryptorSample\\CustomEncryptorSample.csproj", + "src\\DataProtection\\samples\\EntityFrameworkCoreSample\\EntityFrameworkCoreSample.csproj", + "src\\DataProtection\\samples\\KeyManagementSample\\KeyManagementSample.csproj", + "src\\DataProtection\\samples\\KeyManagementSimulator\\KeyManagementSimulator.csproj", + "src\\DataProtection\\samples\\NonDISample\\NonDISample.csproj", + "src\\DataProtection\\samples\\Redis\\Redis.csproj", + "src\\DefaultBuilder\\samples\\SampleApp\\DefaultBuilder.SampleApp.csproj", "src\\DefaultBuilder\\src\\Microsoft.AspNetCore.csproj", + "src\\DefaultBuilder\\test\\Microsoft.AspNetCore.FunctionalTests\\Microsoft.AspNetCore.FunctionalTests.csproj", + "src\\DefaultBuilder\\test\\Microsoft.AspNetCore.Tests\\Microsoft.AspNetCore.Tests.csproj", "src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj", + "src\\Extensions\\Features\\test\\Microsoft.Extensions.Features.Tests.csproj", "src\\Features\\JsonPatch\\src\\Microsoft.AspNetCore.JsonPatch.csproj", + "src\\Features\\JsonPatch\\test\\Microsoft.AspNetCore.JsonPatch.Tests.csproj", "src\\FileProviders\\Embedded\\src\\Microsoft.Extensions.FileProviders.Embedded.csproj", + "src\\FileProviders\\Embedded\\test\\Microsoft.Extensions.FileProviders.Embedded.Tests.csproj", + "src\\FileProviders\\Manifest.MSBuildTask\\src\\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj", + "src\\FileProviders\\Manifest.MSBuildTask\\test\\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Tests.csproj", + "src\\Framework\\App.Ref\\src\\Microsoft.AspNetCore.App.Ref.sfxproj", + "src\\Framework\\App.Runtime\\src\\Microsoft.AspNetCore.App.Runtime.sfxproj", + "src\\Framework\\AspNetCoreAnalyzers\\samples\\WebAppSample\\WebAppSample.csproj", + "src\\Framework\\AspNetCoreAnalyzers\\src\\Analyzers\\Microsoft.AspNetCore.App.Analyzers.csproj", + "src\\Framework\\AspNetCoreAnalyzers\\src\\CodeFixes\\Microsoft.AspNetCore.App.CodeFixes.csproj", + "src\\Framework\\AspNetCoreAnalyzers\\src\\SourceGenerators\\Microsoft.AspNetCore.App.SourceGenerators.csproj", + "src\\Framework\\AspNetCoreAnalyzers\\test\\Microsoft.AspNetCore.App.Analyzers.Test.csproj", + "src\\Framework\\test\\Microsoft.AspNetCore.App.UnitTests.csproj", + "src\\Grpc\\Interop\\test\\InteropTests\\InteropTests.csproj", + "src\\Grpc\\Interop\\test\\testassets\\InteropClient\\InteropClient.csproj", + "src\\Grpc\\Interop\\test\\testassets\\InteropWebsite\\InteropWebsite.csproj", + "src\\Grpc\\JsonTranscoding\\perf\\Microsoft.AspNetCore.Grpc.Microbenchmarks\\Microsoft.AspNetCore.Grpc.Microbenchmarks.csproj", + "src\\Grpc\\JsonTranscoding\\src\\Microsoft.AspNetCore.Grpc.JsonTranscoding\\Microsoft.AspNetCore.Grpc.JsonTranscoding.csproj", + "src\\Grpc\\JsonTranscoding\\src\\Microsoft.AspNetCore.Grpc.Swagger\\Microsoft.AspNetCore.Grpc.Swagger.csproj", + "src\\Grpc\\JsonTranscoding\\test\\Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests\\Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests.csproj", + "src\\Grpc\\JsonTranscoding\\test\\Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests\\Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.csproj", + "src\\Grpc\\JsonTranscoding\\test\\Microsoft.AspNetCore.Grpc.Swagger.Tests\\Microsoft.AspNetCore.Grpc.Swagger.Tests.csproj", + "src\\Grpc\\JsonTranscoding\\test\\testassets\\IntegrationTestsWebsite\\IntegrationTestsWebsite.csproj", + "src\\Grpc\\JsonTranscoding\\test\\testassets\\Sandbox\\Sandbox.csproj", + "src\\HealthChecks\\Abstractions\\src\\Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj", + "src\\HealthChecks\\HealthChecks\\src\\Microsoft.Extensions.Diagnostics.HealthChecks.csproj", + "src\\HealthChecks\\HealthChecks\\test\\Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj", "src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj", "src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj", + "src\\Hosting\\Hosting\\test\\Microsoft.AspNetCore.Hosting.Tests.csproj", "src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj", + "src\\Hosting\\Server.IntegrationTesting\\src\\Microsoft.AspNetCore.Server.IntegrationTesting.csproj", "src\\Hosting\\TestHost\\src\\Microsoft.AspNetCore.TestHost.csproj", + "src\\Hosting\\TestHost\\test\\Microsoft.AspNetCore.TestHost.Tests.csproj", + "src\\Hosting\\WindowsServices\\src\\Microsoft.AspNetCore.Hosting.WindowsServices.csproj", + "src\\Hosting\\WindowsServices\\test\\Microsoft.AspNetCore.Hosting.WindowsServices.Tests.csproj", + "src\\Hosting\\samples\\GenericWebHost\\GenericWebHost.csproj", + "src\\Hosting\\samples\\SampleStartups\\SampleStartups.csproj", + "src\\Hosting\\test\\FunctionalTests\\Microsoft.AspNetCore.Hosting.FunctionalTests.csproj", + "src\\Hosting\\test\\testassets\\IStartupInjectionAssemblyName\\IStartupInjectionAssemblyName.csproj", + "src\\Hosting\\test\\testassets\\TestStartupAssembly1\\TestStartupAssembly1.csproj", "src\\Html.Abstractions\\src\\Microsoft.AspNetCore.Html.Abstractions.csproj", + "src\\Html.Abstractions\\test\\Microsoft.AspNetCore.Html.Abstractions.Tests.csproj", + "src\\HttpClientFactory\\Polly\\src\\Microsoft.Extensions.Http.Polly.csproj", + "src\\HttpClientFactory\\Polly\\test\\Microsoft.Extensions.Http.Polly.Tests.csproj", "src\\Http\\Authentication.Abstractions\\src\\Microsoft.AspNetCore.Authentication.Abstractions.csproj", "src\\Http\\Authentication.Core\\src\\Microsoft.AspNetCore.Authentication.Core.csproj", + "src\\Http\\Authentication.Core\\test\\Microsoft.AspNetCore.Authentication.Core.Test.csproj", "src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj", + "src\\Http\\Headers\\test\\Microsoft.Net.Http.Headers.Tests.csproj", + "src\\Http\\Http.Abstractions\\perf\\Microbenchmarks\\Microsoft.AspNetCore.Http.Abstractions.Microbenchmarks.csproj", "src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj", + "src\\Http\\Http.Abstractions\\test\\Microsoft.AspNetCore.Http.Abstractions.Tests.csproj", + "src\\Http\\Http.Extensions\\gen\\Microsoft.AspNetCore.Http.RequestDelegateGenerator\\Microsoft.AspNetCore.Http.RequestDelegateGenerator.csproj", + "src\\Http\\Http.Extensions\\gen\\Microsoft.AspNetCore.Http.ValidationsGenerator\\Microsoft.AspNetCore.Http.ValidationsGenerator.csproj", "src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj", + "src\\Http\\Http.Extensions\\test\\Microsoft.AspNetCore.Http.Extensions.Tests.csproj", "src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj", + "src\\Http\\Http.Results\\src\\Microsoft.AspNetCore.Http.Results.csproj", + "src\\Http\\Http.Results\\test\\Microsoft.AspNetCore.Http.Results.Tests.csproj", + "src\\Http\\Http.Results\\tools\\ResultsOfTGenerator\\ResultsOfTGenerator.csproj", + "src\\Http\\Http\\perf\\Microbenchmarks\\Microsoft.AspNetCore.Http.Microbenchmarks.csproj", "src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj", + "src\\Http\\Http\\test\\Microsoft.AspNetCore.Http.Tests.csproj", "src\\Http\\Metadata\\src\\Microsoft.AspNetCore.Metadata.csproj", + "src\\Http\\Owin\\src\\Microsoft.AspNetCore.Owin.csproj", + "src\\Http\\Owin\\test\\Microsoft.AspNetCore.Owin.Tests.csproj", "src\\Http\\Routing.Abstractions\\src\\Microsoft.AspNetCore.Routing.Abstractions.csproj", + "src\\Http\\Routing.Abstractions\\test\\Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests.csproj", + "src\\Http\\Routing\\perf\\Microbenchmarks\\Microsoft.AspNetCore.Routing.Microbenchmarks.csproj", "src\\Http\\Routing\\src\\Microsoft.AspNetCore.Routing.csproj", + "src\\Http\\Routing\\test\\FunctionalTests\\Microsoft.AspNetCore.Routing.FunctionalTests.csproj", + "src\\Http\\Routing\\test\\UnitTests\\Microsoft.AspNetCore.Routing.Tests.csproj", + "src\\Http\\Routing\\test\\testassets\\Benchmarks\\Benchmarks.csproj", + "src\\Http\\Routing\\test\\testassets\\RoutingSandbox\\RoutingSandbox.csproj", + "src\\Http\\Routing\\test\\testassets\\RoutingWebSite\\RoutingWebSite.csproj", + "src\\Http\\WebUtilities\\perf\\Microbenchmarks\\Microsoft.AspNetCore.WebUtilities.Microbenchmarks.csproj", "src\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj", + "src\\Http\\WebUtilities\\test\\Microsoft.AspNetCore.WebUtilities.Tests.csproj", + "src\\Http\\samples\\MinimalSample\\MinimalSample.csproj", + "src\\Http\\samples\\SampleApp\\HttpAbstractions.SampleApp.csproj", "src\\Identity\\Core\\src\\Microsoft.AspNetCore.Identity.csproj", "src\\Identity\\EntityFrameworkCore\\src\\Microsoft.AspNetCore.Identity.EntityFrameworkCore.csproj", + "src\\Identity\\EntityFrameworkCore\\test\\EF.InMemory.Test\\Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test.csproj", + "src\\Identity\\EntityFrameworkCore\\test\\EF.Test\\Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test.csproj", "src\\Identity\\Extensions.Core\\src\\Microsoft.Extensions.Identity.Core.csproj", "src\\Identity\\Extensions.Stores\\src\\Microsoft.Extensions.Identity.Stores.csproj", + "src\\Identity\\Specification.Tests\\src\\Microsoft.AspNetCore.Identity.Specification.Tests.csproj", "src\\Identity\\UI\\src\\Microsoft.AspNetCore.Identity.UI.csproj", + "src\\Identity\\samples\\IdentitySample.ApiEndpoints\\IdentitySample.ApiEndpoints.csproj", + "src\\Identity\\samples\\IdentitySample.DefaultUI\\IdentitySample.DefaultUI.csproj", + "src\\Identity\\samples\\IdentitySample.Mvc\\IdentitySample.Mvc.csproj", + "src\\Identity\\test\\Identity.FunctionalTests\\Microsoft.AspNetCore.Identity.FunctionalTests.csproj", + "src\\Identity\\test\\Identity.Test\\Microsoft.AspNetCore.Identity.Test.csproj", + "src\\Identity\\test\\InMemory.Test\\Microsoft.AspNetCore.Identity.InMemory.Test.csproj", + "src\\Identity\\testassets\\Identity.DefaultUI.WebSite\\Identity.DefaultUI.WebSite.csproj", + "src\\Installers\\Windows\\AspNetCoreModule-Setup\\CustomAction\\aspnetcoreCA.vcxproj", + "src\\Installers\\Windows\\AspNetCoreModule-Setup\\IIS-Setup\\IIS-Common\\lib\\IISSetup.CommonLib.vcxproj", + "src\\Installers\\Windows\\AspNetCoreModule-Setup\\IIS-Setup\\iisca\\lib\\iisca.vcxproj", + "src\\Installers\\Windows\\WindowsHostingBundle\\WindowsHostingBundle.wixproj", "src\\JSInterop\\Microsoft.JSInterop\\src\\Microsoft.JSInterop.csproj", + "src\\JSInterop\\Microsoft.JSInterop\\test\\Microsoft.JSInterop.Tests.csproj", "src\\Localization\\Abstractions\\src\\Microsoft.Extensions.Localization.Abstractions.csproj", "src\\Localization\\Localization\\src\\Microsoft.Extensions.Localization.csproj", + "src\\Localization\\Localization\\test\\Microsoft.Extensions.Localization.RootNamespace.Tests\\Microsoft.Extensions.Localization.RootNamespace.Tests.csproj", + "src\\Localization\\Localization\\test\\Microsoft.Extensions.Localization.Tests\\Microsoft.Extensions.Localization.Tests.csproj", + "src\\Logging.AzureAppServices\\src\\Microsoft.Extensions.Logging.AzureAppServices.csproj", + "src\\Logging.AzureAppServices\\test\\Microsoft.Extensions.Logging.AzureAppServices.Tests.csproj", "src\\Middleware\\CORS\\src\\Microsoft.AspNetCore.Cors.csproj", + "src\\Middleware\\CORS\\test\\UnitTests\\Microsoft.AspNetCore.Cors.Test.csproj", + "src\\Middleware\\CORS\\test\\testassets\\CorsMiddlewareWebSite\\CorsMiddlewareWebSite.csproj", + "src\\Middleware\\ConcurrencyLimiter\\perf\\Microbenchmarks\\Microsoft.AspNetCore.ConcurrencyLimiter.Microbenchmarks.csproj", + "src\\Middleware\\ConcurrencyLimiter\\sample\\ConcurrencyLimiterSample.csproj", + "src\\Middleware\\ConcurrencyLimiter\\src\\Microsoft.AspNetCore.ConcurrencyLimiter.csproj", + "src\\Middleware\\ConcurrencyLimiter\\test\\Microsoft.AspNetCore.ConcurrencyLimiter.Tests.csproj", "src\\Middleware\\Diagnostics.Abstractions\\src\\Microsoft.AspNetCore.Diagnostics.Abstractions.csproj", + "src\\Middleware\\Diagnostics.EntityFrameworkCore\\src\\Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.csproj", + "src\\Middleware\\Diagnostics.EntityFrameworkCore\\test\\FunctionalTests\\Diagnostics.EFCore.FunctionalTests.csproj", + "src\\Middleware\\Diagnostics.EntityFrameworkCore\\test\\UnitTests\\Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests.csproj", "src\\Middleware\\Diagnostics\\src\\Microsoft.AspNetCore.Diagnostics.csproj", + "src\\Middleware\\Diagnostics\\test\\FunctionalTests\\Diagnostics.FunctionalTests.csproj", + "src\\Middleware\\Diagnostics\\test\\UnitTests\\Microsoft.AspNetCore.Diagnostics.Tests.csproj", + "src\\Middleware\\Diagnostics\\test\\testassets\\ClassLibraryWithPortablePdbs\\ClassLibraryWithPortablePdbs.csproj", + "src\\Middleware\\Diagnostics\\test\\testassets\\DatabaseErrorPageSample\\DatabaseErrorPageSample.csproj", + "src\\Middleware\\Diagnostics\\test\\testassets\\DeveloperExceptionPageSample\\DeveloperExceptionPageSample.csproj", + "src\\Middleware\\Diagnostics\\test\\testassets\\ExceptionHandlerSample\\ExceptionHandlerSample.csproj", + "src\\Middleware\\Diagnostics\\test\\testassets\\StatusCodePagesSample\\StatusCodePagesSample.csproj", + "src\\Middleware\\Diagnostics\\test\\testassets\\WelcomePageSample\\WelcomePageSample.csproj", + "src\\Middleware\\HeaderPropagation\\samples\\HeaderPropagationSample\\HeaderPropagationSample.csproj", + "src\\Middleware\\HeaderPropagation\\src\\Microsoft.AspNetCore.HeaderPropagation.csproj", + "src\\Middleware\\HeaderPropagation\\test\\Microsoft.AspNetCore.HeaderPropagation.Tests.csproj", + "src\\Middleware\\HealthChecks.EntityFrameworkCore\\src\\Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.csproj", + "src\\Middleware\\HealthChecks.EntityFrameworkCore\\test\\Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.Tests.csproj", + "src\\Middleware\\HealthChecks\\src\\Microsoft.AspNetCore.Diagnostics.HealthChecks.csproj", + "src\\Middleware\\HealthChecks\\test\\UnitTests\\Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests.csproj", + "src\\Middleware\\HealthChecks\\test\\testassets\\HealthChecksSample\\HealthChecksSample.csproj", + "src\\Middleware\\HostFiltering\\sample\\HostFilteringSample.csproj", "src\\Middleware\\HostFiltering\\src\\Microsoft.AspNetCore.HostFiltering.csproj", + "src\\Middleware\\HostFiltering\\test\\Microsoft.AspNetCore.HostFiltering.Tests.csproj", + "src\\Middleware\\HttpLogging\\samples\\HttpLogging.Sample\\HttpLogging.Sample.csproj", + "src\\Middleware\\HttpLogging\\samples\\Logging.W3C.Sample\\Logging.W3C.Sample.csproj", + "src\\Middleware\\HttpLogging\\src\\Microsoft.AspNetCore.HttpLogging.csproj", + "src\\Middleware\\HttpLogging\\test\\Microsoft.AspNetCore.HttpLogging.Tests.csproj", + "src\\Middleware\\HttpOverrides\\sample\\HttpOverridesSample.csproj", "src\\Middleware\\HttpOverrides\\src\\Microsoft.AspNetCore.HttpOverrides.csproj", + "src\\Middleware\\HttpOverrides\\test\\Microsoft.AspNetCore.HttpOverrides.Tests.csproj", + "src\\Middleware\\HttpsPolicy\\sample\\HttpsPolicySample.csproj", "src\\Middleware\\HttpsPolicy\\src\\Microsoft.AspNetCore.HttpsPolicy.csproj", + "src\\Middleware\\HttpsPolicy\\test\\Microsoft.AspNetCore.HttpsPolicy.Tests.csproj", + "src\\Middleware\\Localization.Routing\\src\\Microsoft.AspNetCore.Localization.Routing.csproj", + "src\\Middleware\\Localization.Routing\\test\\Microsoft.AspNetCore.Localization.Routing.Tests.csproj", + "src\\Middleware\\Localization\\sample\\LocalizationSample.csproj", "src\\Middleware\\Localization\\src\\Microsoft.AspNetCore.Localization.csproj", + "src\\Middleware\\Localization\\test\\FunctionalTests\\Microsoft.AspNetCore.Localization.FunctionalTests.csproj", + "src\\Middleware\\Localization\\test\\UnitTests\\Microsoft.AspNetCore.Localization.Tests.csproj", + "src\\Middleware\\Localization\\testassets\\LocalizationWebsite\\LocalizationWebsite.csproj", + "src\\Middleware\\Localization\\testassets\\ResourcesClassLibraryNoAttribute\\ResourcesClassLibraryNoAttribute.csproj", + "src\\Middleware\\Localization\\testassets\\ResourcesClassLibraryWithAttribute\\ResourcesClassLibraryWithAttribute.csproj", + "src\\Middleware\\Microsoft.AspNetCore.OutputCaching.StackExchangeRedis\\src\\Microsoft.AspNetCore.OutputCaching.StackExchangeRedis.csproj", + "src\\Middleware\\Microsoft.AspNetCore.OutputCaching.StackExchangeRedis\\test\\Microsoft.AspNetCore.OutputCaching.StackExchangeRedis.Tests.csproj", + "src\\Middleware\\MiddlewareAnalysis\\samples\\MiddlewareAnalysisSample\\MiddlewareAnalysisSample.csproj", + "src\\Middleware\\MiddlewareAnalysis\\src\\Microsoft.AspNetCore.MiddlewareAnalysis.csproj", + "src\\Middleware\\MiddlewareAnalysis\\test\\Microsoft.AspNetCore.MiddlewareAnalysis.Tests.csproj", + "src\\Middleware\\OutputCaching\\perf\\Microbenchmarks\\Microsoft.AspNetCore.OutputCaching.Microbenchmarks.csproj", + "src\\Middleware\\OutputCaching\\samples\\OutputCachingSample\\OutputCachingSample.csproj", + "src\\Middleware\\OutputCaching\\src\\Microsoft.AspNetCore.OutputCaching.csproj", + "src\\Middleware\\OutputCaching\\test\\Microsoft.AspNetCore.OutputCaching.Tests.csproj", + "src\\Middleware\\RateLimiting\\samples\\RateLimitingSample\\RateLimitingSample.csproj", + "src\\Middleware\\RateLimiting\\src\\Microsoft.AspNetCore.RateLimiting.csproj", + "src\\Middleware\\RateLimiting\\test\\Microsoft.AspNetCore.RateLimiting.Tests.csproj", + "src\\Middleware\\RequestDecompression\\perf\\Microbenchmarks\\Microsoft.AspNetCore.RequestDecompression.Microbenchmarks.csproj", + "src\\Middleware\\RequestDecompression\\sample\\RequestDecompressionSample.csproj", + "src\\Middleware\\RequestDecompression\\src\\Microsoft.AspNetCore.RequestDecompression.csproj", + "src\\Middleware\\RequestDecompression\\test\\Microsoft.AspNetCore.RequestDecompression.Tests.csproj", "src\\Middleware\\ResponseCaching.Abstractions\\src\\Microsoft.AspNetCore.ResponseCaching.Abstractions.csproj", + "src\\Middleware\\ResponseCaching\\samples\\ResponseCachingSample\\ResponseCachingSample.csproj", + "src\\Middleware\\ResponseCaching\\src\\Microsoft.AspNetCore.ResponseCaching.csproj", + "src\\Middleware\\ResponseCaching\\test\\Microsoft.AspNetCore.ResponseCaching.Tests.csproj", + "src\\Middleware\\ResponseCompression\\perf\\Microbenchmarks\\Microsoft.AspNetCore.ResponseCompression.Microbenchmarks.csproj", + "src\\Middleware\\ResponseCompression\\sample\\ResponseCompressionSample.csproj", "src\\Middleware\\ResponseCompression\\src\\Microsoft.AspNetCore.ResponseCompression.csproj", + "src\\Middleware\\ResponseCompression\\test\\Microsoft.AspNetCore.ResponseCompression.Tests.csproj", + "src\\Middleware\\Rewrite\\sample\\RewriteSample.csproj", + "src\\Middleware\\Rewrite\\src\\Microsoft.AspNetCore.Rewrite.csproj", + "src\\Middleware\\Rewrite\\test\\Microsoft.AspNetCore.Rewrite.Tests.csproj", + "src\\Middleware\\Session\\samples\\SessionSample.csproj", + "src\\Middleware\\Session\\src\\Microsoft.AspNetCore.Session.csproj", + "src\\Middleware\\Session\\test\\Microsoft.AspNetCore.Session.Tests.csproj", + "src\\Middleware\\Spa\\SpaProxy\\src\\Microsoft.AspNetCore.SpaProxy.csproj", + "src\\Middleware\\Spa\\SpaServices.Extensions\\src\\Microsoft.AspNetCore.SpaServices.Extensions.csproj", + "src\\Middleware\\Spa\\SpaServices.Extensions\\test\\Microsoft.AspNetCore.SpaServices.Extensions.Tests.csproj", + "src\\Middleware\\StaticFiles\\samples\\StaticFileSample\\StaticFileSample.csproj", "src\\Middleware\\StaticFiles\\src\\Microsoft.AspNetCore.StaticFiles.csproj", + "src\\Middleware\\StaticFiles\\test\\FunctionalTests\\Microsoft.AspNetCore.StaticFiles.FunctionalTests.csproj", + "src\\Middleware\\StaticFiles\\test\\UnitTests\\Microsoft.AspNetCore.StaticFiles.Tests.csproj", + "src\\Middleware\\WebSockets\\samples\\EchoApp\\EchoApp.csproj", + "src\\Middleware\\WebSockets\\samples\\TestServer\\WebSockets.TestServer.csproj", "src\\Middleware\\WebSockets\\src\\Microsoft.AspNetCore.WebSockets.csproj", + "src\\Middleware\\WebSockets\\test\\ConformanceTests\\AutobahnTestApp\\AutobahnTestApp.csproj", + "src\\Middleware\\WebSockets\\test\\ConformanceTests\\Microsoft.AspNetCore.WebSockets.ConformanceTests.csproj", + "src\\Middleware\\WebSockets\\test\\UnitTests\\Microsoft.AspNetCore.WebSockets.Tests.csproj", + "src\\Middleware\\perf\\Microbenchmarks\\Microsoft.AspNetCore.WebSockets.Microbenchmarks.csproj", + "src\\Middleware\\perf\\ResponseCaching.Microbenchmarks\\Microsoft.AspNetCore.ResponseCaching.Microbenchmarks.csproj", "src\\Mvc\\Mvc.Abstractions\\src\\Microsoft.AspNetCore.Mvc.Abstractions.csproj", + "src\\Mvc\\Mvc.Abstractions\\test\\Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj", "src\\Mvc\\Mvc.Analyzers\\src\\Microsoft.AspNetCore.Mvc.Analyzers.csproj", + "src\\Mvc\\Mvc.Analyzers\\test\\Mvc.Analyzers.Test.csproj", + "src\\Mvc\\Mvc.Api.Analyzers\\src\\Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj", + "src\\Mvc\\Mvc.Api.Analyzers\\test\\Mvc.Api.Analyzers.Test.csproj", "src\\Mvc\\Mvc.ApiExplorer\\src\\Microsoft.AspNetCore.Mvc.ApiExplorer.csproj", + "src\\Mvc\\Mvc.ApiExplorer\\test\\Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj", "src\\Mvc\\Mvc.Core\\src\\Microsoft.AspNetCore.Mvc.Core.csproj", + "src\\Mvc\\Mvc.Core\\test\\Microsoft.AspNetCore.Mvc.Core.Test.csproj", "src\\Mvc\\Mvc.Cors\\src\\Microsoft.AspNetCore.Mvc.Cors.csproj", + "src\\Mvc\\Mvc.Cors\\test\\Microsoft.AspNetCore.Mvc.Cors.Test.csproj", "src\\Mvc\\Mvc.DataAnnotations\\src\\Microsoft.AspNetCore.Mvc.DataAnnotations.csproj", + "src\\Mvc\\Mvc.DataAnnotations\\test\\Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj", "src\\Mvc\\Mvc.Formatters.Json\\src\\Microsoft.AspNetCore.Mvc.Formatters.Json.csproj", + "src\\Mvc\\Mvc.Formatters.Xml\\src\\Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj", + "src\\Mvc\\Mvc.Formatters.Xml\\test\\Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj", "src\\Mvc\\Mvc.Localization\\src\\Microsoft.AspNetCore.Mvc.Localization.csproj", + "src\\Mvc\\Mvc.Localization\\test\\Microsoft.AspNetCore.Mvc.Localization.Test.csproj", "src\\Mvc\\Mvc.NewtonsoftJson\\src\\Microsoft.AspNetCore.Mvc.NewtonsoftJson.csproj", + "src\\Mvc\\Mvc.NewtonsoftJson\\test\\Microsoft.AspNetCore.Mvc.NewtonsoftJson.Test.csproj", + "src\\Mvc\\Mvc.Razor.RuntimeCompilation\\src\\Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.csproj", + "src\\Mvc\\Mvc.Razor.RuntimeCompilation\\test\\Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.Test.csproj", "src\\Mvc\\Mvc.RazorPages\\src\\Microsoft.AspNetCore.Mvc.RazorPages.csproj", + "src\\Mvc\\Mvc.RazorPages\\test\\Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj", "src\\Mvc\\Mvc.Razor\\src\\Microsoft.AspNetCore.Mvc.Razor.csproj", + "src\\Mvc\\Mvc.Razor\\test\\Microsoft.AspNetCore.Mvc.Razor.Test.csproj", "src\\Mvc\\Mvc.TagHelpers\\src\\Microsoft.AspNetCore.Mvc.TagHelpers.csproj", + "src\\Mvc\\Mvc.TagHelpers\\test\\Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj", + "src\\Mvc\\Mvc.Testing.Tasks\\src\\Microsoft.AspNetCore.Mvc.Testing.Tasks.csproj", + "src\\Mvc\\Mvc.Testing\\src\\Microsoft.AspNetCore.Mvc.Testing.csproj", "src\\Mvc\\Mvc.ViewFeatures\\src\\Microsoft.AspNetCore.Mvc.ViewFeatures.csproj", + "src\\Mvc\\Mvc.ViewFeatures\\test\\Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj", "src\\Mvc\\Mvc\\src\\Microsoft.AspNetCore.Mvc.csproj", + "src\\Mvc\\Mvc\\test\\Microsoft.AspNetCore.Mvc.Test.csproj", + "src\\Mvc\\perf\\Microbenchmarks\\Microsoft.AspNetCore.Mvc.Views\\Microsoft.AspNetCore.Mvc.Views.Microbenchmarks.csproj", + "src\\Mvc\\perf\\Microbenchmarks\\Microsoft.AspNetCore.Mvc\\Microsoft.AspNetCore.Mvc.Microbenchmarks.csproj", + "src\\Mvc\\samples\\MvcFormSample\\MvcFormSample.csproj", + "src\\Mvc\\samples\\MvcSandbox\\MvcSandbox.csproj", + "src\\Mvc\\shared\\Mvc.Core.TestCommon\\Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj", + "src\\Mvc\\shared\\Mvc.TestDiagnosticListener\\Microsoft.AspNetCore.Mvc.TestDiagnosticListener.csproj", + "src\\Mvc\\shared\\Mvc.Views.TestCommon\\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", + "src\\Mvc\\test\\Mvc.FunctionalTests\\Microsoft.AspNetCore.Mvc.FunctionalTests.csproj", + "src\\Mvc\\test\\Mvc.IntegrationTests\\Microsoft.AspNetCore.Mvc.IntegrationTests.csproj", + "src\\Mvc\\test\\WebSites\\ApiExplorerWebSite\\ApiExplorerWebSite.csproj", + "src\\Mvc\\test\\WebSites\\ApplicationModelWebSite\\ApplicationModelWebSite.csproj", + "src\\Mvc\\test\\WebSites\\BasicWebSite\\BasicWebSite.csproj", + "src\\Mvc\\test\\WebSites\\ControllersFromServicesClassLibrary\\ControllersFromServicesClassLibrary.csproj", + "src\\Mvc\\test\\WebSites\\ControllersFromServicesWebSite\\ControllersFromServicesWebSite.csproj", + "src\\Mvc\\test\\WebSites\\CorsWebSite\\CorsWebSite.csproj", + "src\\Mvc\\test\\WebSites\\ErrorPageMiddlewareWebSite\\ErrorPageMiddlewareWebSite.csproj", + "src\\Mvc\\test\\WebSites\\FilesWebSite\\FilesWebSite.csproj", + "src\\Mvc\\test\\WebSites\\FormatterWebSite\\FormatterWebSite.csproj", + "src\\Mvc\\test\\WebSites\\GenericHostWebSite\\GenericHostWebSite.csproj", + "src\\Mvc\\test\\WebSites\\HtmlGenerationWebSite\\HtmlGenerationWebSite.csproj", + "src\\Mvc\\test\\WebSites\\RazorBuildWebSite.PrecompiledViews\\RazorBuildWebSite.PrecompiledViews.csproj", + "src\\Mvc\\test\\WebSites\\RazorBuildWebSite.Views\\RazorBuildWebSite.Views.csproj", + "src\\Mvc\\test\\WebSites\\RazorBuildWebSite\\RazorBuildWebSite.csproj", + "src\\Mvc\\test\\WebSites\\RazorPagesClassLibrary\\RazorPagesClassLibrary.csproj", + "src\\Mvc\\test\\WebSites\\RazorPagesWebSite\\RazorPagesWebSite.csproj", + "src\\Mvc\\test\\WebSites\\RazorWebSite\\RazorWebSite.csproj", + "src\\Mvc\\test\\WebSites\\RoutingWebSite\\Mvc.RoutingWebSite.csproj", + "src\\Mvc\\test\\WebSites\\SecurityWebSite\\SecurityWebSite.csproj", + "src\\Mvc\\test\\WebSites\\SimpleWebSiteWithWebApplicationBuilderException\\SimpleWebSiteWithWebApplicationBuilderException.csproj", + "src\\Mvc\\test\\WebSites\\SimpleWebSiteWithWebApplicationBuilder\\SimpleWebSiteWithWebApplicationBuilder.csproj", + "src\\Mvc\\test\\WebSites\\SimpleWebSite\\SimpleWebSite.csproj", + "src\\Mvc\\test\\WebSites\\TagHelpersWebSite\\TagHelpersWebSite.csproj", + "src\\Mvc\\test\\WebSites\\VersioningWebSite\\VersioningWebSite.csproj", + "src\\Mvc\\test\\WebSites\\XmlFormattersWebSite\\XmlFormattersWebSite.csproj", "src\\ObjectPool\\src\\Microsoft.Extensions.ObjectPool.csproj", + "src\\OpenApi\\gen\\Microsoft.AspNetCore.OpenApi.SourceGenerators.csproj", + "src\\OpenApi\\perf\\Microbenchmarks\\Microsoft.AspNetCore.OpenApi.Microbenchmarks.csproj", + "src\\OpenApi\\sample\\Sample.csproj", + "src\\OpenApi\\src\\Microsoft.AspNetCore.OpenApi.csproj", + "src\\OpenApi\\test\\Microsoft.AspNetCore.OpenApi.Build.Tests\\Microsoft.AspNetCore.OpenApi.Build.Tests.csproj", + "src\\OpenApi\\test\\Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests\\Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests.csproj", + "src\\OpenApi\\test\\Microsoft.AspNetCore.OpenApi.Tests\\Microsoft.AspNetCore.OpenApi.Tests.csproj", + "src\\ProjectTemplates\\Web.Client.ItemTemplates\\Microsoft.DotNet.Web.Client.ItemTemplates.csproj", + "src\\ProjectTemplates\\Web.ItemTemplates\\Microsoft.DotNet.Web.ItemTemplates.csproj", + "src\\ProjectTemplates\\Web.ProjectTemplates\\Microsoft.DotNet.Web.ProjectTemplates.csproj", + "src\\ProjectTemplates\\test\\Templates.Blazor.Tests\\Templates.Blazor.Tests.csproj", + "src\\ProjectTemplates\\test\\Templates.Blazor.WebAssembly.Auth.Tests\\Templates.Blazor.WebAssembly.Auth.Tests.csproj", + "src\\ProjectTemplates\\test\\Templates.Blazor.WebAssembly.Tests\\Templates.Blazor.WebAssembly.Tests.csproj", + "src\\ProjectTemplates\\test\\Templates.Mvc.Tests\\Templates.Mvc.Tests.csproj", + "src\\ProjectTemplates\\test\\Templates.Tests\\Templates.Tests.csproj", "src\\Razor\\Razor.Runtime\\src\\Microsoft.AspNetCore.Razor.Runtime.csproj", + "src\\Razor\\Razor.Runtime\\test\\Microsoft.AspNetCore.Razor.Runtime.Test.csproj", "src\\Razor\\Razor\\src\\Microsoft.AspNetCore.Razor.csproj", + "src\\Razor\\Razor\\test\\Microsoft.AspNetCore.Razor.Test.csproj", + "src\\Security\\Authentication\\BearerToken\\src\\Microsoft.AspNetCore.Authentication.BearerToken.csproj", + "src\\Security\\Authentication\\Certificate\\samples\\Certificate.Optional.Sample\\Certificate.Optional.Sample.csproj", + "src\\Security\\Authentication\\Certificate\\samples\\Certificate.Sample\\Certificate.Sample.csproj", + "src\\Security\\Authentication\\Certificate\\src\\Microsoft.AspNetCore.Authentication.Certificate.csproj", + "src\\Security\\Authentication\\Cookies\\samples\\CookieSample\\CookieSample.csproj", + "src\\Security\\Authentication\\Cookies\\samples\\CookieSessionSample\\CookieSessionSample.csproj", "src\\Security\\Authentication\\Cookies\\src\\Microsoft.AspNetCore.Authentication.Cookies.csproj", "src\\Security\\Authentication\\Core\\src\\Microsoft.AspNetCore.Authentication.csproj", + "src\\Security\\Authentication\\Facebook\\src\\Microsoft.AspNetCore.Authentication.Facebook.csproj", + "src\\Security\\Authentication\\Google\\src\\Microsoft.AspNetCore.Authentication.Google.csproj", + "src\\Security\\Authentication\\JwtBearer\\samples\\JwtBearerSample\\JwtBearerSample.csproj", + "src\\Security\\Authentication\\JwtBearer\\samples\\MinimalJwtBearerSample\\MinimalJwtBearerSample.csproj", + "src\\Security\\Authentication\\JwtBearer\\src\\Microsoft.AspNetCore.Authentication.JwtBearer.csproj", + "src\\Security\\Authentication\\MicrosoftAccount\\src\\Microsoft.AspNetCore.Authentication.MicrosoftAccount.csproj", + "src\\Security\\Authentication\\Negotiate\\samples\\NegotiateAuthSample\\NegotiateAuthSample.csproj", + "src\\Security\\Authentication\\Negotiate\\src\\Microsoft.AspNetCore.Authentication.Negotiate.csproj", + "src\\Security\\Authentication\\Negotiate\\test\\Negotiate.FunctionalTest\\Microsoft.AspNetCore.Authentication.Negotiate.FunctionalTest.csproj", + "src\\Security\\Authentication\\Negotiate\\test\\Negotiate.Test\\Microsoft.AspNetCore.Authentication.Negotiate.Test.csproj", + "src\\Security\\Authentication\\OAuth\\src\\Microsoft.AspNetCore.Authentication.OAuth.csproj", + "src\\Security\\Authentication\\OpenIdConnect\\samples\\MinimalOpenIdConnectSample\\MinimalOpenIdConnectSample.csproj", + "src\\Security\\Authentication\\OpenIdConnect\\samples\\OpenIdConnectSample\\OpenIdConnectSample.csproj", + "src\\Security\\Authentication\\OpenIdConnect\\src\\Microsoft.AspNetCore.Authentication.OpenIdConnect.csproj", + "src\\Security\\Authentication\\Twitter\\src\\Microsoft.AspNetCore.Authentication.Twitter.csproj", + "src\\Security\\Authentication\\WsFederation\\samples\\WsFedSample\\WsFedSample.csproj", + "src\\Security\\Authentication\\WsFederation\\src\\Microsoft.AspNetCore.Authentication.WsFederation.csproj", + "src\\Security\\Authentication\\samples\\SocialSample\\SocialSample.csproj", + "src\\Security\\Authentication\\test\\Microsoft.AspNetCore.Authentication.Test.csproj", "src\\Security\\Authorization\\Core\\src\\Microsoft.AspNetCore.Authorization.csproj", "src\\Security\\Authorization\\Policy\\src\\Microsoft.AspNetCore.Authorization.Policy.csproj", + "src\\Security\\Authorization\\test\\Microsoft.AspNetCore.Authorization.Test.csproj", + "src\\Security\\CookiePolicy\\samples\\CookiePolicySample\\CookiePolicySample.csproj", "src\\Security\\CookiePolicy\\src\\Microsoft.AspNetCore.CookiePolicy.csproj", + "src\\Security\\CookiePolicy\\test\\Microsoft.AspNetCore.CookiePolicy.Test.csproj", + "src\\Security\\perf\\Microbenchmarks\\Microsoft.AspNetCore.Security.Microbenchmarks.csproj", + "src\\Security\\samples\\ClaimsTransformation\\ClaimsTransformation.csproj", + "src\\Security\\samples\\Cookies\\Cookies.csproj", + "src\\Security\\samples\\CustomAuthorizationFailureResponse\\CustomAuthorizationFailureResponse.csproj", + "src\\Security\\samples\\CustomPolicyProvider\\CustomPolicyProvider.csproj", + "src\\Security\\samples\\DynamicSchemes\\DynamicSchemes.csproj", + "src\\Security\\samples\\Identity.ExternalClaims\\Identity.ExternalClaims.csproj", + "src\\Security\\samples\\PathSchemeSelection\\PathSchemeSelection.csproj", + "src\\Security\\samples\\StaticFilesAuth\\StaticFilesAuth.csproj", + "src\\Security\\test\\AuthSamples.FunctionalTests\\AuthSamples.FunctionalTests.csproj", "src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj", + "src\\Servers\\HttpSys\\perf\\Microbenchmarks\\Microsoft.AspNetCore.Server.HttpSys.Microbenchmarks.csproj", + "src\\Servers\\HttpSys\\samples\\HotAddSample\\HotAddSample.csproj", + "src\\Servers\\HttpSys\\samples\\QueueSharing\\QueueSharing.csproj", + "src\\Servers\\HttpSys\\samples\\SelfHostServer\\SelfHostServer.csproj", + "src\\Servers\\HttpSys\\samples\\TestClient\\TestClient.csproj", + "src\\Servers\\HttpSys\\src\\Microsoft.AspNetCore.Server.HttpSys.csproj", + "src\\Servers\\HttpSys\\test\\FunctionalTests\\Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj", + "src\\Servers\\HttpSys\\test\\NonHelixTests\\Microsoft.AspNetCore.Server.HttpSys.NonHelixTests.csproj", + "src\\Servers\\HttpSys\\test\\Tests\\Microsoft.AspNetCore.Server.HttpSys.Tests.csproj", + "src\\Servers\\HttpSys\\test\\testassets\\DelegationSite\\DelegationSite.csproj", + "src\\Servers\\IIS\\AspNetCoreModuleV2\\AspNetCore\\AspNetCore.vcxproj", + "src\\Servers\\IIS\\AspNetCoreModuleV2\\CommonLibTests\\CommonLibTests.vcxproj", + "src\\Servers\\IIS\\AspNetCoreModuleV2\\CommonLib\\CommonLib.vcxproj", + "src\\Servers\\IIS\\AspNetCoreModuleV2\\IISLib\\IISLib.vcxproj", + "src\\Servers\\IIS\\AspNetCoreModuleV2\\InProcessRequestHandler\\InProcessRequestHandler.vcxproj", + "src\\Servers\\IIS\\AspNetCoreModuleV2\\OutOfProcessRequestHandler\\OutOfProcessRequestHandler.vcxproj", + "src\\Servers\\IIS\\AspNetCoreModuleV2\\RequestHandlerLib\\RequestHandlerLib.vcxproj", + "src\\Servers\\IIS\\AspNetCoreModuleV2\\Symbols\\Microsoft.AspNetCore.ANCMSymbols.csproj", + "src\\Servers\\IIS\\AspNetCoreModuleV2\\gtest\\gtest.vcxproj", + "src\\Servers\\IIS\\IISIntegration\\samples\\IISSample\\IISSample.csproj", "src\\Servers\\IIS\\IISIntegration\\src\\Microsoft.AspNetCore.Server.IISIntegration.csproj", + "src\\Servers\\IIS\\IISIntegration\\test\\Tests\\Microsoft.AspNetCore.Server.IISIntegration.Tests.csproj", + "src\\Servers\\IIS\\IIS\\perf\\Microbenchmarks\\IIS.Microbenchmarks.csproj", + "src\\Servers\\IIS\\IIS\\samples\\NativeIISSample\\NativeIISSample.csproj", "src\\Servers\\IIS\\IIS\\src\\Microsoft.AspNetCore.Server.IIS.csproj", + "src\\Servers\\IIS\\IIS\\test\\IIS.FunctionalTests\\IIS.FunctionalTests.csproj", + "src\\Servers\\IIS\\IIS\\test\\IIS.LongTests\\IIS.LongTests.csproj", + "src\\Servers\\IIS\\IIS\\test\\IIS.NewHandler.FunctionalTests\\IIS.NewHandler.FunctionalTests.csproj", + "src\\Servers\\IIS\\IIS\\test\\IIS.NewShim.FunctionalTests\\IIS.NewShim.FunctionalTests.csproj", + "src\\Servers\\IIS\\IIS\\test\\IIS.ShadowCopy.Tests\\IIS.ShadowCopy.Tests.csproj", + "src\\Servers\\IIS\\IIS\\test\\IIS.Tests\\IIS.Tests.csproj", + "src\\Servers\\IIS\\IIS\\test\\IISExpress.FunctionalTests\\IISExpress.FunctionalTests.csproj", + "src\\Servers\\IIS\\IIS\\test\\testassets\\IIS.Common.TestLib\\IIS.Common.TestLib.csproj", + "src\\Servers\\IIS\\IIS\\test\\testassets\\InProcessNewShimWebSite\\InProcessNewShimWebSite.csproj", + "src\\Servers\\IIS\\IIS\\test\\testassets\\InProcessWebSite\\InProcessWebSite.csproj", + "src\\Servers\\IIS\\IIS\\test\\testassets\\TestTasks\\TestTasks.csproj", + "src\\Servers\\IIS\\IntegrationTesting.IIS\\src\\Microsoft.AspNetCore.Server.IntegrationTesting.IIS.csproj", "src\\Servers\\Kestrel\\Core\\src\\Microsoft.AspNetCore.Server.Kestrel.Core.csproj", + "src\\Servers\\Kestrel\\Core\\test\\Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj", "src\\Servers\\Kestrel\\Kestrel\\src\\Microsoft.AspNetCore.Server.Kestrel.csproj", + "src\\Servers\\Kestrel\\Kestrel\\test\\Microsoft.AspNetCore.Server.Kestrel.Tests.csproj", "src\\Servers\\Kestrel\\Transport.NamedPipes\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.csproj", + "src\\Servers\\Kestrel\\Transport.NamedPipes\\test\\Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Tests.csproj", "src\\Servers\\Kestrel\\Transport.Quic\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj", + "src\\Servers\\Kestrel\\Transport.Quic\\test\\Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj", "src\\Servers\\Kestrel\\Transport.Sockets\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj", + "src\\Servers\\Kestrel\\perf\\Microbenchmarks\\Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj", + "src\\Servers\\Kestrel\\samples\\Http2SampleApp\\Http2SampleApp.csproj", + "src\\Servers\\Kestrel\\samples\\Http3SampleApp\\Http3SampleApp.csproj", + "src\\Servers\\Kestrel\\samples\\HttpClientApp\\HttpClientApp.csproj", + "src\\Servers\\Kestrel\\samples\\LargeResponseApp\\LargeResponseApp.csproj", + "src\\Servers\\Kestrel\\samples\\PlaintextApp\\PlaintextApp.csproj", + "src\\Servers\\Kestrel\\samples\\SampleApp\\Kestrel.SampleApp.csproj", + "src\\Servers\\Kestrel\\samples\\SystemdTestApp\\SystemdTestApp.csproj", + "src\\Servers\\Kestrel\\samples\\WebTransportInteractiveSampleApp\\WebTransportInteractiveSampleApp.csproj", + "src\\Servers\\Kestrel\\samples\\WebTransportSampleApp\\WebTransportSampleApp.csproj", + "src\\Servers\\Kestrel\\samples\\http2cat\\http2cat.csproj", + "src\\Servers\\Kestrel\\stress\\HttpStress.csproj", + "src\\Servers\\Kestrel\\test\\InMemory.FunctionalTests\\InMemory.FunctionalTests.csproj", + "src\\Servers\\Kestrel\\test\\Interop.FunctionalTests\\Interop.FunctionalTests.csproj", + "src\\Servers\\Kestrel\\test\\Sockets.BindTests\\Sockets.BindTests.csproj", + "src\\Servers\\Kestrel\\test\\Sockets.FunctionalTests\\Sockets.FunctionalTests.csproj", + "src\\Servers\\Kestrel\\tools\\CodeGenerator\\CodeGenerator.csproj", + "src\\Servers\\test\\FunctionalTests\\ServerComparison.FunctionalTests.csproj", + "src\\Servers\\testassets\\ServerComparison.TestSites\\ServerComparison.TestSites.csproj", "src\\Shared\\BrowserTesting\\src\\Microsoft.AspNetCore.BrowserTesting.csproj", + "src\\Shared\\test\\Shared.Tests\\Microsoft.AspNetCore.Shared.Tests.csproj", "src\\SignalR\\clients\\csharp\\Client.Core\\src\\Microsoft.AspNetCore.SignalR.Client.Core.csproj", + "src\\SignalR\\clients\\csharp\\Client.SourceGenerator\\src\\Microsoft.AspNetCore.SignalR.Client.SourceGenerator.csproj", "src\\SignalR\\clients\\csharp\\Client\\src\\Microsoft.AspNetCore.SignalR.Client.csproj", + "src\\SignalR\\clients\\csharp\\Client\\test\\FunctionalTests\\Microsoft.AspNetCore.SignalR.Client.FunctionalTests.csproj", + "src\\SignalR\\clients\\csharp\\Client\\test\\UnitTests\\Microsoft.AspNetCore.SignalR.Client.Tests.csproj", "src\\SignalR\\clients\\csharp\\Http.Connections.Client\\src\\Microsoft.AspNetCore.Http.Connections.Client.csproj", + "src\\SignalR\\clients\\ts\\FunctionalTests\\SignalR.Client.FunctionalTestApp.csproj", "src\\SignalR\\common\\Http.Connections.Common\\src\\Microsoft.AspNetCore.Http.Connections.Common.csproj", "src\\SignalR\\common\\Http.Connections\\src\\Microsoft.AspNetCore.Http.Connections.csproj", + "src\\SignalR\\common\\Http.Connections\\test\\Microsoft.AspNetCore.Http.Connections.Tests.csproj", "src\\SignalR\\common\\Protocols.Json\\src\\Microsoft.AspNetCore.SignalR.Protocols.Json.csproj", "src\\SignalR\\common\\Protocols.MessagePack\\src\\Microsoft.AspNetCore.SignalR.Protocols.MessagePack.csproj", "src\\SignalR\\common\\Protocols.NewtonsoftJson\\src\\Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj", "src\\SignalR\\common\\SignalR.Common\\src\\Microsoft.AspNetCore.SignalR.Common.csproj", + "src\\SignalR\\common\\SignalR.Common\\test\\Microsoft.AspNetCore.SignalR.Common.Tests.csproj", + "src\\SignalR\\common\\testassets\\Tests.Utils\\Microsoft.AspNetCore.SignalR.Tests.Utils.csproj", + "src\\SignalR\\perf\\Microbenchmarks\\Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj", + "src\\SignalR\\samples\\ClientSample\\ClientSample.csproj", + "src\\SignalR\\samples\\JwtClientSample\\JwtClientSample.csproj", + "src\\SignalR\\samples\\JwtSample\\JwtSample.csproj", + "src\\SignalR\\samples\\SignalRSamples\\SignalRSamples.csproj", + "src\\SignalR\\samples\\SocialWeather\\SocialWeather.csproj", + "src\\SignalR\\samples\\WebSocketSample\\WebSocketSample.csproj", "src\\SignalR\\server\\Core\\src\\Microsoft.AspNetCore.SignalR.Core.csproj", "src\\SignalR\\server\\SignalR\\src\\Microsoft.AspNetCore.SignalR.csproj", + "src\\SignalR\\server\\SignalR\\test\\Microsoft.AspNetCore.SignalR.Tests\\Microsoft.AspNetCore.SignalR.Tests.csproj", + "src\\SignalR\\server\\Specification.Tests\\src\\Microsoft.AspNetCore.SignalR.Specification.Tests.csproj", + "src\\SignalR\\server\\StackExchangeRedis\\src\\Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj", + "src\\SignalR\\server\\StackExchangeRedis\\test\\Microsoft.AspNetCore.SignalR.StackExchangeRedis.Tests.csproj", + "src\\SiteExtensions\\LoggingAggregate\\src\\Microsoft.AspNetCore.AzureAppServices.SiteExtension\\Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj", + "src\\SiteExtensions\\LoggingAggregate\\test\\Microsoft.AspNetCore.AzureAppServices.SiteExtension.Tests\\Microsoft.AspNetCore.AzureAppServices.SiteExtension.Tests.csproj", + "src\\SiteExtensions\\LoggingBranch\\LB.csproj", + "src\\SiteExtensions\\Microsoft.Web.Xdt.Extensions\\src\\Microsoft.Web.Xdt.Extensions.csproj", + "src\\SiteExtensions\\Microsoft.Web.Xdt.Extensions\\tests\\Microsoft.Web.Xdt.Extensions.Tests.csproj", + "src\\SiteExtensions\\Sdk\\HostingStartup\\HostingStartup.csproj", "src\\StaticAssets\\src\\Microsoft.AspNetCore.StaticAssets.csproj", "src\\StaticAssets\\test\\Microsoft.AspNetCore.StaticAssets.Tests.csproj", "src\\Testing\\src\\Microsoft.AspNetCore.InternalTesting.csproj", + "src\\Testing\\test\\Microsoft.AspNetCore.InternalTesting.Tests.csproj", + "src\\Tools\\Extensions.ApiDescription.Client\\src\\Microsoft.Extensions.ApiDescription.Client.csproj", + "src\\Tools\\Extensions.ApiDescription.Client\\test\\Microsoft.Extensions.ApiDescription.Client.Tests.csproj", + "src\\Tools\\Extensions.ApiDescription.Server\\src\\Microsoft.Extensions.ApiDescription.Server.csproj", + "src\\Tools\\FirstRunCertGenerator\\src\\Microsoft.AspNetCore.DeveloperCertificates.XPlat.csproj", + "src\\Tools\\FirstRunCertGenerator\\test\\Microsoft.AspNetCore.DeveloperCertificates.XPlat.Tests.csproj", + "src\\Tools\\GetDocumentInsider\\sample\\GetDocumentSample.csproj", + "src\\Tools\\GetDocumentInsider\\src\\GetDocument.Insider.csproj", + "src\\Tools\\GetDocumentInsider\\tests\\GetDocumentInsider.Tests.csproj", + "src\\Tools\\LinkabilityChecker\\LinkabilityChecker.csproj", + "src\\Tools\\Microsoft.dotnet-openapi\\src\\Microsoft.dotnet-openapi.csproj", + "src\\Tools\\Microsoft.dotnet-openapi\\test\\dotnet-microsoft.openapi.Tests.csproj", + "src\\Tools\\SDK-Analyzers\\Components\\src\\Microsoft.AspNetCore.Components.SdkAnalyzers.csproj", + "src\\Tools\\SDK-Analyzers\\Components\\test\\Microsoft.AspNetCore.Components.SdkAnalyzers.Tests.csproj", + "src\\Tools\\dotnet-dev-certs\\src\\dotnet-dev-certs.csproj", + "src\\Tools\\dotnet-getdocument\\src\\dotnet-getdocument.csproj", + "src\\Tools\\dotnet-sql-cache\\src\\dotnet-sql-cache.csproj", + "src\\Tools\\dotnet-user-jwts\\src\\dotnet-user-jwts.csproj", + "src\\Tools\\dotnet-user-jwts\\test\\dotnet-user-jwts.Tests.csproj", + "src\\Tools\\dotnet-user-secrets\\src\\dotnet-user-secrets.csproj", + "src\\Tools\\dotnet-user-secrets\\test\\dotnet-user-secrets.Tests.csproj", "src\\WebEncoders\\src\\Microsoft.Extensions.WebEncoders.csproj" ] } -} +} \ No newline at end of file diff --git a/src/Components/Web.JS/src/GlobalExports.ts b/src/Components/Web.JS/src/GlobalExports.ts index 434901fbf3df..32918c605c07 100644 --- a/src/Components/Web.JS/src/GlobalExports.ts +++ b/src/Components/Web.JS/src/GlobalExports.ts @@ -17,7 +17,6 @@ import { getNextChunk } from './StreamingInterop'; import { RootComponentsFunctions } from './Rendering/JSRootComponents'; import { attachWebRendererInterop } from './Rendering/WebRendererInteropMethods'; import { WebStartOptions } from './Platform/WebStartOptions'; -import { DotNet } from '@microsoft/dotnet-js-interop'; import { RuntimeAPI } from '@microsoft/dotnet-runtime'; import { JSEventRegistry } from './Services/JSEventRegistry'; diff --git a/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts b/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts index dfc1e6269329..2d080bc4fcc6 100644 --- a/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts +++ b/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts @@ -245,8 +245,6 @@ export class CircuitManager implements DotNet.DotNetCallDispatcher { // Implements DotNet.DotNetCallDispatcher public endInvokeJSFromDotNet(asyncHandle: number, succeeded: boolean, argsJson: any): void { - // console.log("signalr endInvokeJSFromDotNet", asyncHandle, succeeded, argsJson); - this.throwIfDispatchingWhenDisposed(); this._connection!.send('EndInvokeJSFromDotNet', asyncHandle, succeeded, argsJson); } diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts index aa8fdca346e9..9eff6375548c 100644 --- a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts +++ b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts @@ -265,7 +265,6 @@ function attachInteropInvoker(): void { ); }, endInvokeJSFromDotNet: (asyncHandle, succeeded, serializedArgs): void => { - // console.log("mono endInvokeJSFromDotNet", asyncHandle, succeeded, serializedArgs); Blazor._internal.dotNetExports!.EndInvokeJS(serializedArgs); }, sendByteArray: (id: number, data: Uint8Array): void => { diff --git a/src/Components/Web.JS/src/Platform/WebView/WebViewIpcSender.ts b/src/Components/Web.JS/src/Platform/WebView/WebViewIpcSender.ts index 994baf534064..9f0fe4189991 100644 --- a/src/Components/Web.JS/src/Platform/WebView/WebViewIpcSender.ts +++ b/src/Components/Web.JS/src/Platform/WebView/WebViewIpcSender.ts @@ -16,8 +16,6 @@ export function sendBeginInvokeDotNetFromJS(callId: number, assemblyName: string } export function sendEndInvokeJSFromDotNet(asyncHandle: number, succeeded: boolean, argsJson: any): void { - // console.log("webview sendEndInvokeJSFromDotNet", asyncHandle, succeeded, argsJson); - send('EndInvokeJS', asyncHandle, succeeded, argsJson); } diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticationServiceTests.cs b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticationServiceTests.cs index 1c920e7bda95..5b11f935cace 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticationServiceTests.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticationServiceTests.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; using System.Security.Claims; using System.Text.Json; using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs index 82b6945d739c..e8243c70db94 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; using System.Runtime.ExceptionServices; using System.Security.Claims; using Microsoft.AspNetCore.Components.Authorization; diff --git a/src/Components/WebView/WebView/src/IpcSender.cs b/src/Components/WebView/WebView/src/IpcSender.cs index 74db080a5072..5a341bac0d54 100644 --- a/src/Components/WebView/WebView/src/IpcSender.cs +++ b/src/Components/WebView/WebView/src/IpcSender.cs @@ -51,7 +51,6 @@ public void AttachToDocument(int componentId, string selector) public void BeginInvokeJS(JSInvocationInfo invocationInfo) { - // TODO(OR): Make sure the client side works with this var invocationInfoJson = invocationInfo.ToJson(); DispatchMessageWithErrorHandling(IpcCommon.Serialize(IpcCommon.OutgoingMessageType.BeginInvokeJS, invocationInfoJson)); } diff --git a/src/JSInterop/JSInterop.sln b/src/JSInterop/JSInterop.sln deleted file mode 100644 index 03e30c29c0ad..000000000000 --- a/src/JSInterop/JSInterop.sln +++ /dev/null @@ -1,36 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.2.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.JSInterop", "Microsoft.JSInterop", "{858B5D40-ED74-2150-A8A2-365B5B420ABA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.JSInterop", "Microsoft.JSInterop\src\Microsoft.JSInterop.csproj", "{CF7471C8-620A-6EBA-58DD-0A6B81AF790B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.JSInterop.Tests", "Microsoft.JSInterop\test\Microsoft.JSInterop.Tests.csproj", "{60AF6814-2890-E499-8733-A9657CAAD7C1}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CF7471C8-620A-6EBA-58DD-0A6B81AF790B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CF7471C8-620A-6EBA-58DD-0A6B81AF790B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CF7471C8-620A-6EBA-58DD-0A6B81AF790B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CF7471C8-620A-6EBA-58DD-0A6B81AF790B}.Release|Any CPU.Build.0 = Release|Any CPU - {60AF6814-2890-E499-8733-A9657CAAD7C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {60AF6814-2890-E499-8733-A9657CAAD7C1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {60AF6814-2890-E499-8733-A9657CAAD7C1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {60AF6814-2890-E499-8733-A9657CAAD7C1}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {CF7471C8-620A-6EBA-58DD-0A6B81AF790B} = {858B5D40-ED74-2150-A8A2-365B5B420ABA} - {60AF6814-2890-E499-8733-A9657CAAD7C1} = {858B5D40-ED74-2150-A8A2-365B5B420ABA} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {332B6471-80B9-4C1B-9A85-046F3BEBDB06} - EndGlobalSection -EndGlobal diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index 8845061fce4d..192621469715 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -4,874 +4,872 @@ // This is a single-file self-contained module to avoid the need for a Webpack build export module DotNet { - export type JsonReviver = ((key: any, value: any) => any); - const jsonRevivers: JsonReviver[] = []; - - const jsObjectIdKey = "__jsObjectId"; - const dotNetObjectRefKey = "__dotNetObject"; - const byteArrayRefKey = "__byte[]"; - const dotNetStreamRefKey = "__dotNetStream"; - const jsStreamReferenceLengthKey = "__jsStreamReferenceLength"; - - // If undefined, no dispatcher has been attached yet. - // If null, this means more than one dispatcher was attached, so no default can be determined. - // Otherwise, there was only one dispatcher registered. We keep track of this instance to keep legacy APIs working. - let defaultCallDispatcher: CallDispatcher | null | undefined; - - // Provides access to the "current" call dispatcher without having to flow it through nested function calls. - let currentCallDispatcher: CallDispatcher | undefined; - - /** - * Represents a resolved property of a JS object. - * - * @param parent The immediate parent object that contains the property. - * @param name The name of the property. - * @param func Reference to a function if the member is a function. Otherwise, undefined. - */ - interface ObjectMemberDescriptor { - parent: any; - name: string; - func?: Function; - } + export type JsonReviver = ((key: any, value: any) => any); + const jsonRevivers: JsonReviver[] = []; - class JSObject { - // TODO(oroztocil): Is it correct to cache functions/properties when they can change? - _cachedMembers: Map; + const jsObjectIdKey = "__jsObjectId"; + const dotNetObjectRefKey = "__dotNetObject"; + const byteArrayRefKey = "__byte[]"; + const dotNetStreamRefKey = "__dotNetStream"; + const jsStreamReferenceLengthKey = "__jsStreamReferenceLength"; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - constructor(private _jsObject: any) { - this._cachedMembers = new Map(); - } + // If undefined, no dispatcher has been attached yet. + // If null, this means more than one dispatcher was attached, so no default can be determined. + // Otherwise, there was only one dispatcher registered. We keep track of this instance to keep legacy APIs working. + let defaultCallDispatcher: CallDispatcher | null | undefined; - public findMember(identifier: string): ObjectMemberDescriptor { - if (identifier === "") { - return { parent: this.getWrappedObject(), name: "" }; - } + // Provides access to the "current" call dispatcher without having to flow it through nested function calls. + let currentCallDispatcher: CallDispatcher | undefined; - const cachedMember = this._cachedMembers.get(identifier); + /** + * Represents a resolved property of a JS object. + * + * @param parent The immediate parent object that contains the property. + * @param name The name of the property. + * @param func Reference to a function if the member is a function. Otherwise, undefined. + */ + interface ObjectMemberDescriptor { + parent: any; + name: string; + func?: Function; + } - if (cachedMember) { - return cachedMember; - } + class JSObject { + // TODO(oroztocil): Is it correct to cache functions/properties when they can change? + _cachedMembers: Map; - let current: any = this._jsObject; - let parent: any = null; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(private _jsObject: any) { + this._cachedMembers = new Map(); + } - const keys = identifier.split("."); + public findMember(identifier: string): ObjectMemberDescriptor { + if (identifier === "") { + return { parent: this.getWrappedObject(), name: "" }; + } - // First, we iterate over all but the last key. We throw error for missing intermediate keys. - for (let i = 0; i < keys.length - 1; i++) { - const key = keys[i]; + const cachedMember = this._cachedMembers.get(identifier); - if (current && typeof current === 'object' && key in current) { - parent = current; - current = current[key]; - } else { - throw new Error(`Could not find '${identifier}' ('${key}' was undefined).`); - } - } + if (cachedMember) { + return cachedMember; + } - // We don't check the last key because error handling depends on the type of operation. - const lastKey = keys[keys.length - 1]; - parent = current; - current = current[lastKey]; + let current: any = this._jsObject; + let parent: any = null; - const result = { parent, name: lastKey } as ObjectMemberDescriptor; + const keys = identifier.split("."); - if (typeof current === "function") { - result.func = current.bind(parent); - } + // First, we iterate over all but the last key. We throw error for missing intermediate keys. + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; - this._cachedMembers.set(identifier, result); - return result; - } + if (current && typeof current === 'object' && key in current) { + parent = current; + current = current[key]; + } else { + throw new Error(`Could not find '${identifier}' ('${key}' was undefined).`); + } + } - public getWrappedObject() { - return this._jsObject; - } - } + // We don't check the last key because error handling depends on the type of operation. + const lastKey = keys[keys.length - 1]; + parent = current; + current = current[lastKey]; - const windowJSObjectId = 0; - const cachedJSObjectsById: { [id: number]: JSObject } = { - [windowJSObjectId]: new JSObject(window) - }; - - const windowObject = cachedJSObjectsById[windowJSObjectId]; - windowObject._cachedMembers.set("import", { parent: windowObject, name: "import", func: (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 = new URL(url.substring(2), document.baseURI).toString(); - } + const result = { parent, name: lastKey } as ObjectMemberDescriptor; - return import(/* webpackIgnore: true */ url); - }}); - - let nextJsObjectId = 1; // Start at 1 because zero is reserved for "window" - - /** - * Creates a .NET call dispatcher to use for handling invocations between JavaScript and a .NET runtime. - * - * @param dotNetCallDispatcher An object that can dispatch calls from JavaScript to a .NET runtime. - */ - export function attachDispatcher(dotNetCallDispatcher: DotNetCallDispatcher): ICallDispatcher { - const result = new CallDispatcher(dotNetCallDispatcher); - if (defaultCallDispatcher === undefined) { - // This was the first dispatcher registered, so it becomes the default. This exists purely for - // backwards compatibility. - defaultCallDispatcher = result; - } else if (defaultCallDispatcher) { - // There is already a default dispatcher. Now that there are multiple to choose from, there can - // be no acceptable default, so we nullify the default dispatcher. - defaultCallDispatcher = null; - } + if (typeof current === "function") { + result.func = current.bind(parent); + } - return result; - } + this._cachedMembers.set(identifier, result); + return result; + } - /** - * Adds a JSON reviver callback that will be used when parsing arguments received from .NET. - * @param reviver The reviver to add. - */ - export function attachReviver(reviver: JsonReviver) { - jsonRevivers.push(reviver); - } + public getWrappedObject() { + return this._jsObject; + } + } + + const windowJSObjectId = 0; + const cachedJSObjectsById: { [id: number]: JSObject } = { + [windowJSObjectId]: new JSObject(window) + }; + + const windowObject = cachedJSObjectsById[windowJSObjectId]; + windowObject._cachedMembers.set("import", { parent: windowObject, name: "import", func: (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 = new URL(url.substring(2), document.baseURI).toString(); + } + + return import(/* webpackIgnore: true */ url); + }}); + + let nextJsObjectId = 1; // Start at 1 because zero is reserved for "window" /** - * Invokes the specified .NET public method synchronously. Not all hosting scenarios support - * synchronous invocation, so if possible use invokeMethodAsync instead. + * Creates a .NET call dispatcher to use for handling invocations between JavaScript and a .NET runtime. + * + * @param dotNetCallDispatcher An object that can dispatch calls from JavaScript to a .NET runtime. + */ + export function attachDispatcher(dotNetCallDispatcher: DotNetCallDispatcher): ICallDispatcher { + const result = new CallDispatcher(dotNetCallDispatcher); + if (defaultCallDispatcher === undefined) { + // This was the first dispatcher registered, so it becomes the default. This exists purely for + // backwards compatibility. + defaultCallDispatcher = result; + } else if (defaultCallDispatcher) { + // There is already a default dispatcher. Now that there are multiple to choose from, there can + // be no acceptable default, so we nullify the default dispatcher. + defaultCallDispatcher = null; + } + + return result; + } + + /** + * Adds a JSON reviver callback that will be used when parsing arguments received from .NET. + * @param reviver The reviver to add. + */ + export function attachReviver(reviver: JsonReviver) { + jsonRevivers.push(reviver); + } + +/** + * Invokes the specified .NET public method synchronously. Not all hosting scenarios support + * synchronous invocation, so if possible use invokeMethodAsync instead. + * + * @deprecated Use DotNetObject to invoke instance methods instead. + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param args Arguments to pass to the method, each of which must be JSON-serializable. + * @returns The result of the operation. + */ +export function invokeMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T | null { + const dispatcher = getDefaultCallDispatcher(); + return dispatcher.invokeDotNetStaticMethod(assemblyName, methodIdentifier, ...args); +} + + /** + * Invokes the specified .NET public method asynchronously. * * @deprecated Use DotNetObject to invoke instance methods instead. * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns The result of the operation. + * @returns A promise representing the result of the operation. */ - export function invokeMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T | null { + export function invokeMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise { const dispatcher = getDefaultCallDispatcher(); - return dispatcher.invokeDotNetStaticMethod(assemblyName, methodIdentifier, ...args); + return dispatcher.invokeDotNetStaticMethodAsync(assemblyName, methodIdentifier, ...args); } - /** - * Invokes the specified .NET public method asynchronously. - * - * @deprecated Use DotNetObject to invoke instance methods instead. - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns A promise representing the result of the operation. - */ - export function invokeMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise { - const dispatcher = getDefaultCallDispatcher(); - return dispatcher.invokeDotNetStaticMethodAsync(assemblyName, methodIdentifier, ...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); - /** - * 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 + }; - const result = { - [jsObjectIdKey]: nextJsObjectId - }; + nextJsObjectId++; - nextJsObjectId++; + return result; + } - return result; - } + throw new Error(`Cannot create a JSObjectReference from the value '${jsObject}'.`); + } - throw new Error(`Cannot create a JSObjectReference from the value '${jsObject}'.`); - } + /** + * Creates a JavaScript data reference that can be passed to .NET via interop calls. + * + * @param streamReference The ArrayBufferView or Blob used to create the JavaScript stream reference. + * @returns The JavaScript data reference (this will be the same instance as the given object). + * @throws Error if the given value is not an Object or doesn't have a valid byteLength. + */ + export function createJSStreamReference(streamReference: ArrayBuffer | ArrayBufferView | Blob | any): any { + let length = -1; - /** - * Creates a JavaScript data reference that can be passed to .NET via interop calls. - * - * @param streamReference The ArrayBufferView or Blob used to create the JavaScript stream reference. - * @returns The JavaScript data reference (this will be the same instance as the given object). - * @throws Error if the given value is not an Object or doesn't have a valid byteLength. - */ - export function createJSStreamReference(streamReference: ArrayBuffer | ArrayBufferView | Blob | any): any { - let length = -1; - - // If we're given a raw Array Buffer, we interpret it as a `Uint8Array` as - // ArrayBuffers' aren't directly readable. - if (streamReference instanceof ArrayBuffer) { - streamReference = new Uint8Array(streamReference); - } + // If we're given a raw Array Buffer, we interpret it as a `Uint8Array` as + // ArrayBuffers' aren't directly readable. + if (streamReference instanceof ArrayBuffer) { + streamReference = new Uint8Array(streamReference); + } - if (streamReference instanceof Blob) { - length = streamReference.size; - } else if (streamReference.buffer instanceof ArrayBuffer) { - if (streamReference.byteLength === undefined) { - throw new Error(`Cannot create a JSStreamReference from the value '${streamReference}' as it doesn't have a byteLength.`); - } + if (streamReference instanceof Blob) { + length = streamReference.size; + } else if (streamReference.buffer instanceof ArrayBuffer) { + if (streamReference.byteLength === undefined) { + throw new Error(`Cannot create a JSStreamReference from the value '${streamReference}' as it doesn't have a byteLength.`); + } - length = streamReference.byteLength; - } else { - throw new Error("Supplied value is not a typed array or blob."); - } + length = streamReference.byteLength; + } else { + throw new Error("Supplied value is not a typed array or blob."); + } - const result: any = { - [jsStreamReferenceLengthKey]: length - }; + const result: any = { + [jsStreamReferenceLengthKey]: length + }; - try { - const jsObjectReference = createJSObjectReference(streamReference); - result[jsObjectIdKey] = jsObjectReference[jsObjectIdKey]; - } catch (error) { - throw new Error(`Cannot create a JSStreamReference from the value '${streamReference}'.`); - } + try { + const jsObjectReference = createJSObjectReference(streamReference); + result[jsObjectIdKey] = jsObjectReference[jsObjectIdKey]; + } catch (error) { + throw new Error(`Cannot create a JSStreamReference from the value '${streamReference}'.`); + } - return result; - } + return result; + } - /** - * Disposes the given JavaScript object reference. - * - * @param jsObjectReference The JavaScript Object reference. - */ - export function disposeJSObjectReference(jsObjectReference: any): void { - const id = jsObjectReference && jsObjectReference[jsObjectIdKey]; + /** + * 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); - } - } + if (typeof id === "number") { + disposeJSObjectReferenceById(id); + } + } - function parseJsonWithRevivers(callDispatcher: CallDispatcher, json: string | null): any { - currentCallDispatcher = callDispatcher; - const result = 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; - currentCallDispatcher = undefined; - return result; - } + function parseJsonWithRevivers(callDispatcher: CallDispatcher, json: string | null): any { + currentCallDispatcher = callDispatcher; + const result = 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; + currentCallDispatcher = undefined; + return result; + } - function getDefaultCallDispatcher(): CallDispatcher { - if (defaultCallDispatcher === undefined) { - throw new Error("No call dispatcher has been set."); - } else if (defaultCallDispatcher === null) { - throw new Error("There are multiple .NET runtimes present, so a default dispatcher could not be resolved. Use DotNetObject to invoke .NET instance methods."); - } else { - return defaultCallDispatcher; - } - } + function getDefaultCallDispatcher(): CallDispatcher { + if (defaultCallDispatcher === undefined) { + throw new Error("No call dispatcher has been set."); + } else if (defaultCallDispatcher === null) { + throw new Error("There are multiple .NET runtimes present, so a default dispatcher could not be resolved. Use DotNetObject to invoke .NET instance methods."); + } else { + return defaultCallDispatcher; + } + } - interface PendingAsyncCall { - resolve: (value?: T | PromiseLike) => void; - reject: (reason?: any) => void; - } + interface PendingAsyncCall { + resolve: (value?: T | PromiseLike) => void; + reject: (reason?: any) => void; + } - /** - * Represents the type of result expected from a JS interop call. - */ - // eslint-disable-next-line no-shadow - export enum JSCallResultType { - Default = 0, - JSObjectReference = 1, - JSStreamReference = 2, - JSVoidResult = 3, - } + /** + * Represents the type of result expected from a JS interop call. + */ + // eslint-disable-next-line no-shadow + export enum JSCallResultType { + Default = 0, + JSObjectReference = 1, + JSStreamReference = 2, + JSVoidResult = 3, + } - /** - * Represents the type of operation that should be performed in JS. - */ - export enum JSCallType { - FunctionCall = 1, - NewCall = 2, - GetValue = 3, - SetValue = 4 - } + /** + * Represents the type of operation that should be performed in JS. + */ + export enum JSCallType { + FunctionCall = 1, + NewCall = 2, + GetValue = 3, + SetValue = 4 + } - /** - * @param asyncHandle A value identifying an asynchronous operation that is passed back in a later call to endInvokeJSFromDotNet. If the call is synchronous, this value is zero. - * @param targetInstanceId The ID of the target JS object instance. - * @param identifier The identifier of the function to invoke or property to access. - * @param callType The type of operation that should be performed in JS. - * @param resultType The type of result expected from the JS interop call. - * @param argsJson JSON array of arguments to be passed to the operation. First element is used when setting a property value. - */ - export interface JSInvocationInfo { - asyncHandle: number, - targetInstanceId: number, - identifier: string, - callType: JSCallType, - resultType: JSCallResultType, - argsJson: string | null, - } + /** + * @param asyncHandle A value identifying an asynchronous operation that is passed back in a later call to endInvokeJSFromDotNet. If the call is synchronous, this value is zero. + * @param targetInstanceId The ID of the target JS object instance. + * @param identifier The identifier of the function to invoke or property to access. + * @param callType The type of operation that should be performed in JS. + * @param resultType The type of result expected from the JS interop call. + * @param argsJson JSON array of arguments to be passed to the operation. First element is used when setting a property value. + */ + export interface JSInvocationInfo { + asyncHandle: number, + targetInstanceId: number, + identifier: string, + callType: JSCallType, + resultType: JSCallResultType, + argsJson: string | null, + } - /** - * Represents the ability to dispatch calls from JavaScript to a .NET runtime. - */ - export interface DotNetCallDispatcher { - /** - * Optional. If implemented, invoked by the runtime to perform a synchronous call to a .NET method. - * - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null or undefined to call static methods. - * @param argsJson JSON representation of arguments to pass to the method. - * @returns JSON representation of the result of the invocation. - */ - invokeDotNetFromJS?(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): string | null; - - /** - * Invoked by the runtime to begin an asynchronous call to a .NET method. - * - * @param callId A value identifying the asynchronous operation. This value should be passed back in a later call from .NET to JS. - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null to call static methods. - * @param argsJson JSON representation of arguments to pass to the method. - */ - beginInvokeDotNetFromJS(callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): void; - - /** - * Invoked by the runtime to complete an asynchronous JavaScript function call started from .NET - * - * @param callId A value identifying the asynchronous operation. - * @param succeded Whether the operation succeeded or not. - * @param resultOrError The serialized result or the serialized error from the async operation. - */ - endInvokeJSFromDotNet(callId: number, succeeded: boolean, resultOrError: any): void; - - /** - * Invoked by the runtime to transfer a byte array from JS to .NET. - * @param id The identifier for the byte array used during revival. - * @param data The byte array being transferred for eventual revival. - */ - sendByteArray(id: number, data: Uint8Array): void; - } + /** + * Represents the ability to dispatch calls from JavaScript to a .NET runtime. + */ + export interface DotNetCallDispatcher { + /** + * Optional. If implemented, invoked by the runtime to perform a synchronous call to a .NET method. + * + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null or undefined to call static methods. + * @param argsJson JSON representation of arguments to pass to the method. + * @returns JSON representation of the result of the invocation. + */ + invokeDotNetFromJS?(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): string | null; + + /** + * Invoked by the runtime to begin an asynchronous call to a .NET method. + * + * @param callId A value identifying the asynchronous operation. This value should be passed back in a later call from .NET to JS. + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null to call static methods. + * @param argsJson JSON representation of arguments to pass to the method. + */ + beginInvokeDotNetFromJS(callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): void; + + /** + * Invoked by the runtime to complete an asynchronous JavaScript function call started from .NET + * + * @param callId A value identifying the asynchronous operation. + * @param succeded Whether the operation succeeded or not. + * @param resultOrError The serialized result or the serialized error from the async operation. + */ + endInvokeJSFromDotNet(callId: number, succeeded: boolean, resultOrError: any): void; + + /** + * Invoked by the runtime to transfer a byte array from JS to .NET. + * @param id The identifier for the byte array used during revival. + * @param data The byte array being transferred for eventual revival. + */ + sendByteArray(id: number, data: Uint8Array): void; + } - /** - * Represents the ability to facilitate function call dispatching between JavaScript and a .NET runtime. - */ - export interface ICallDispatcher { - /** - * Invokes the specified synchronous JavaScript function. - * - * @param invocationInfo Configuration of the interop call. - */ - invokeJSFromDotNet(invocationInfo: JSInvocationInfo): string | null; - - /** - * Invokes the specified synchronous or asynchronous JavaScript function. - * - * @param invocationInfo Configuration of the interop call. - */ - beginInvokeJSFromDotNet(invocationInfo: JSInvocationInfo): Promise | null; - - /** - * Receives notification that an async call from JS to .NET has completed. - * @param asyncCallId The identifier supplied in an earlier call to beginInvokeDotNetFromJS. - * @param success A flag to indicate whether the operation completed successfully. - * @param resultJsonOrExceptionMessage Either the operation result as JSON, or an error message. - */ - endInvokeDotNetFromJS(asyncCallId: string, success: boolean, resultJsonOrExceptionMessage: string): void; - - /** - * Invokes the specified .NET public static method synchronously. Not all hosting scenarios support - * synchronous invocation, so if possible use invokeMethodAsync instead. - * - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns The result of the operation. - */ - invokeDotNetStaticMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): | null; - - /** - * Invokes the specified .NET public static method asynchronously. - * - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns A promise representing the result of the operation. - */ - invokeDotNetStaticMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise; - - /** - * Receives notification that a byte array is being transferred from .NET to JS. - * @param id The identifier for the byte array used during revival. - * @param data The byte array being transferred for eventual revival. - */ - receiveByteArray(id: number, data: Uint8Array): void - - /** - * Supplies a stream of data being sent from .NET. - * - * @param streamId The identifier previously passed to JSRuntime's BeginTransmittingStream in .NET code. - * @param stream The stream data. - */ - supplyDotNetStream(streamId: number, stream: ReadableStream): void; - } + /** + * Represents the ability to facilitate function call dispatching between JavaScript and a .NET runtime. + */ + export interface ICallDispatcher { + /** + * Invokes the specified synchronous JavaScript function. + * + * @param invocationInfo Configuration of the interop call. + */ + invokeJSFromDotNet(invocationInfo: JSInvocationInfo): string | null; + + /** + * Invokes the specified synchronous or asynchronous JavaScript function. + * + * @param invocationInfo Configuration of the interop call. + */ + beginInvokeJSFromDotNet(invocationInfo: JSInvocationInfo): Promise | null; + + /** + * Receives notification that an async call from JS to .NET has completed. + * @param asyncCallId The identifier supplied in an earlier call to beginInvokeDotNetFromJS. + * @param success A flag to indicate whether the operation completed successfully. + * @param resultJsonOrExceptionMessage Either the operation result as JSON, or an error message. + */ + endInvokeDotNetFromJS(asyncCallId: string, success: boolean, resultJsonOrExceptionMessage: string): void; - class CallDispatcher implements ICallDispatcher { - private readonly _byteArraysToBeRevived = new Map(); + /** + * Invokes the specified .NET public static method synchronously. Not all hosting scenarios support + * synchronous invocation, so if possible use invokeMethodAsync instead. + * + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param args Arguments to pass to the method, each of which must be JSON-serializable. + * @returns The result of the operation. + */ + invokeDotNetStaticMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): | null; + + /** + * Invokes the specified .NET public static method asynchronously. + * + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param args Arguments to pass to the method, each of which must be JSON-serializable. + * @returns A promise representing the result of the operation. + */ + invokeDotNetStaticMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise; + + /** + * Receives notification that a byte array is being transferred from .NET to JS. + * @param id The identifier for the byte array used during revival. + * @param data The byte array being transferred for eventual revival. + */ + receiveByteArray(id: number, data: Uint8Array): void + + /** + * Supplies a stream of data being sent from .NET. + * + * @param streamId The identifier previously passed to JSRuntime's BeginTransmittingStream in .NET code. + * @param stream The stream data. + */ + supplyDotNetStream(streamId: number, stream: ReadableStream): void; + } - private readonly _pendingDotNetToJSStreams = new Map(); + class CallDispatcher implements ICallDispatcher { + private readonly _byteArraysToBeRevived = new Map(); - private readonly _pendingAsyncCalls: { [id: number]: PendingAsyncCall } = {}; + private readonly _pendingDotNetToJSStreams = new Map(); - private _nextAsyncCallId = 1; // Start at 1 because zero signals "no response needed" + private readonly _pendingAsyncCalls: { [id: number]: PendingAsyncCall } = {}; - // eslint-disable-next-line no-empty-function - constructor(private readonly _dotNetCallDispatcher: DotNetCallDispatcher) { - } + private _nextAsyncCallId = 1; // Start at 1 because zero signals "no response needed" - getDotNetCallDispatcher(): DotNetCallDispatcher { - return this._dotNetCallDispatcher; - } + // eslint-disable-next-line no-empty-function + constructor(private readonly _dotNetCallDispatcher: DotNetCallDispatcher) { + } - invokeJSFromDotNet(invocationInfo: JSInvocationInfo): string | null { - //console.log("invocationInfo", JSON.stringify(invocationInfo)); - const { targetInstanceId, identifier, callType, resultType, argsJson } = invocationInfo; - const returnValue = this.handleJSCall(targetInstanceId, identifier, callType, argsJson); - const result = createJSCallResult(returnValue, resultType); + getDotNetCallDispatcher(): DotNetCallDispatcher { + return this._dotNetCallDispatcher; + } - return result === null || result === undefined - ? null - : stringifyArgs(this, result); - } + invokeJSFromDotNet(invocationInfo: JSInvocationInfo): string | null { + const { targetInstanceId, identifier, callType, resultType, argsJson } = invocationInfo; + const returnValue = this.handleJSCall(targetInstanceId, identifier, callType, argsJson); + const result = createJSCallResult(returnValue, resultType); - beginInvokeJSFromDotNet(invocationInfo: JSInvocationInfo): Promise | null { - //console.log("beginInvokeJSFromDotNet", invocationInfo); - const { asyncHandle, targetInstanceId, identifier, callType, resultType, argsJson } = invocationInfo; - - // Coerce synchronous functions into async ones, plus treat - // synchronous exceptions the same as async ones - const promise = new Promise(resolve => { - const valueOrPromise = this.handleJSCall(targetInstanceId, identifier, callType, argsJson); - resolve(valueOrPromise); - }); - - // We only listen for a result if the caller wants to be notified about it - if (asyncHandle) { - // On completion, dispatch result back to .NET - // Not using "await" because it codegens a lot of boilerplate - return promise. - then(result => stringifyArgs(this, [ - asyncHandle, - true, - createJSCallResult(result, resultType) - ])). - then( - result => this._dotNetCallDispatcher.endInvokeJSFromDotNet(asyncHandle, true, result), - error => this._dotNetCallDispatcher.endInvokeJSFromDotNet(asyncHandle, false, JSON.stringify([ - asyncHandle, - false, - formatError(error) - ])) - ); - } else { - return null; - } - } + return result === null || result === undefined + ? null + : stringifyArgs(this, result); + } - handleJSCall(targetInstanceId: number, identifier: string, callType: JSCallType, argsJson: string | null) { - const args = parseJsonWithRevivers(this, argsJson) ?? []; - const member = findObjectMember(identifier, targetInstanceId); - let returnValue = null; - - if (callType === JSCallType.FunctionCall) { - returnValue = this.handleJSFunctionCall(identifier, member.func, args); - } else if (callType === JSCallType.NewCall) { - returnValue = this.handleJSNewCall(identifier, member.func, args); - } else if (callType === JSCallType.GetValue) { - returnValue = this.handleJSPropertyGet(identifier, member); - } else if (callType === JSCallType.SetValue) { - this.handleJSPropertySet(identifier, member, args); - } else { - throw new Error(`Unsupported call type '${callType}'.`); - } - - return returnValue; - } + beginInvokeJSFromDotNet(invocationInfo: JSInvocationInfo): Promise | null { + const { asyncHandle, targetInstanceId, identifier, callType, resultType, argsJson } = invocationInfo; + + // Coerce synchronous functions into async ones, plus treat + // synchronous exceptions the same as async ones + const promise = new Promise(resolve => { + const valueOrPromise = this.handleJSCall(targetInstanceId, identifier, callType, argsJson); + resolve(valueOrPromise); + }); + + // We only listen for a result if the caller wants to be notified about it + if (asyncHandle) { + // On completion, dispatch result back to .NET + // Not using "await" because it codegens a lot of boilerplate + return promise. + then(result => stringifyArgs(this, [ + asyncHandle, + true, + createJSCallResult(result, resultType) + ])). + then( + result => this._dotNetCallDispatcher.endInvokeJSFromDotNet(asyncHandle, true, result), + error => this._dotNetCallDispatcher.endInvokeJSFromDotNet(asyncHandle, false, JSON.stringify([ + asyncHandle, + false, + formatError(error) + ])) + ); + } else { + return null; + } + } - handleJSFunctionCall(identifier: string, func: any, args: unknown[]): any { - if (typeof func === "function") { - return func(...args); - } else { - throw new Error(`The value '${identifier}' is not a function.`); - } - } + handleJSCall(targetInstanceId: number, identifier: string, callType: JSCallType, argsJson: string | null) { + const args = parseJsonWithRevivers(this, argsJson) ?? []; + const member = findObjectMember(identifier, targetInstanceId); + let returnValue = null; + + if (callType === JSCallType.FunctionCall) { + returnValue = this.handleJSFunctionCall(identifier, member.func, args); + } else if (callType === JSCallType.NewCall) { + returnValue = this.handleJSNewCall(identifier, member.func, args); + } else if (callType === JSCallType.GetValue) { + returnValue = this.handleJSPropertyGet(identifier, member); + } else if (callType === JSCallType.SetValue) { + this.handleJSPropertySet(identifier, member, args); + } else { + throw new Error(`Unsupported call type '${callType}'.`); + } - handleJSNewCall(identifier: string, func: any, args: unknown[]): any { - if (typeof func === "function") { - try { - // The new expression throws if the function is not constructible (e.g. an arrow function). - return new func(...args); - } catch (err) { - if (err instanceof TypeError) { - throw new Error(`The value '${identifier}' is not a constructor function.`); - } else { - throw err; - } - } - } else { - throw new Error(`The value '${identifier}' is not a function.`); - } - } + return returnValue; + } - handleJSPropertyGet(identifier: string, property: ObjectMemberDescriptor): any { - if (property.name === "") { - return property.parent; - } + handleJSFunctionCall(identifier: string, func: any, args: unknown[]): any { + if (typeof func === "function") { + return func(...args); + } else { + throw new Error(`The value '${identifier}' is not a function.`); + } + } - // TODO(oroztocil): Do we want to throw on undefined property or return null? - if (!this.isReadableProperty(property.parent, property.name)) { - throw new Error(`The property '${identifier}' is not defined or is not readable.`); - } + handleJSNewCall(identifier: string, func: any, args: unknown[]): any { + if (typeof func === "function") { + try { + // The new expression throws if the function is not constructible (e.g. an arrow function). + return new func(...args); + } catch (err) { + if (err instanceof TypeError) { + throw new Error(`The value '${identifier}' is not a constructor function.`); + } else { + throw err; + } + } + } else { + throw new Error(`The value '${identifier}' is not a function.`); + } + } - // For empty identifier, we return the object itself to support "dereferencing" JS object references. - return property.parent[property.name]; - } + handleJSPropertyGet(identifier: string, property: ObjectMemberDescriptor): any { + if (property.name === "") { + return property.parent; + } - handleJSPropertySet(identifier: string, property: ObjectMemberDescriptor, args: unknown[]) { - if (!this.isWritableProperty(property.parent, property.name)) { - throw new Error(`The property '${identifier}' is not writable.`); - } + // TODO(oroztocil): Do we want to throw on undefined property or return null? + if (!this.isReadableProperty(property.parent, property.name)) { + throw new Error(`The property '${identifier}' is not defined or is not readable.`); + } - const value = args[0]; - property.parent[property.name] = value; - } + // For empty identifier, we return the object itself to support "dereferencing" JS object references. + return property.parent[property.name]; + } - isReadableProperty(obj: any, propName: string) { - // Return false for missing property. - if (!(propName in obj)) { - return false; - } - - // If the property is present we examine its descriptor, potentially needing to walk up the prototype chain. - while (obj !== undefined) { - const descriptor = Object.getOwnPropertyDescriptor(obj, propName); - - if (descriptor) { - // Return true for data property - if (descriptor.hasOwnProperty('value')) { - return true - } - - // Return true for accessor property with defined getter. - return descriptor.hasOwnProperty('get') && typeof descriptor.get === 'function'; - } - - obj = Object.getPrototypeOf(obj); - } - - return false; - } - - isWritableProperty(obj: any, propName: string) { - // Return true for missing property if the property can be added. - if (!(propName in obj)) { - return Object.isExtensible(obj); - } - - // If the property is present we examine its descriptor, potentially needing to walk up the prototype chain. - while (obj !== undefined) { - const descriptor = Object.getOwnPropertyDescriptor(obj, propName); - - if (descriptor) { - // Return true for writable data property. - if (descriptor.hasOwnProperty('value') && descriptor.writable) { - return true; - } - - // Return true for accessor property with defined setter. - return descriptor.hasOwnProperty('set') && typeof descriptor.set === 'function'; - } - - obj = Object.getPrototypeOf(obj); - } - - return false; - } + handleJSPropertySet(identifier: string, property: ObjectMemberDescriptor, args: unknown[]) { + if (!this.isWritableProperty(property.parent, property.name)) { + throw new Error(`The property '${identifier}' is not writable.`); + } - endInvokeDotNetFromJS(asyncCallId: string, success: boolean, resultJsonOrExceptionMessage: string): void { - const resultOrError = success - ? parseJsonWithRevivers(this, resultJsonOrExceptionMessage) - : new Error(resultJsonOrExceptionMessage); - this.completePendingCall(parseInt(asyncCallId, 10), success, resultOrError); - } + const value = args[0]; + property.parent[property.name] = value; + } + + isReadableProperty(obj: any, propName: string) { + // Return false for missing property. + if (!(propName in obj)) { + return false; + } + + // If the property is present we examine its descriptor, potentially needing to walk up the prototype chain. + while (obj !== undefined) { + const descriptor = Object.getOwnPropertyDescriptor(obj, propName); + + if (descriptor) { + // Return true for data property + if (descriptor.hasOwnProperty('value')) { + return true + } + + // Return true for accessor property with defined getter. + return descriptor.hasOwnProperty('get') && typeof descriptor.get === 'function'; + } + + obj = Object.getPrototypeOf(obj); + } + + return false; + } + + isWritableProperty(obj: any, propName: string) { + // Return true for missing property if the property can be added. + if (!(propName in obj)) { + return Object.isExtensible(obj); + } + + // If the property is present we examine its descriptor, potentially needing to walk up the prototype chain. + while (obj !== undefined) { + const descriptor = Object.getOwnPropertyDescriptor(obj, propName); + + if (descriptor) { + // Return true for writable data property. + if (descriptor.hasOwnProperty('value') && descriptor.writable) { + return true; + } + + // Return true for accessor property with defined setter. + return descriptor.hasOwnProperty('set') && typeof descriptor.set === 'function'; + } + + obj = Object.getPrototypeOf(obj); + } + + return false; + } - invokeDotNetStaticMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T | null { - return this.invokeDotNetMethod(assemblyName, methodIdentifier, null, args); + endInvokeDotNetFromJS(asyncCallId: string, success: boolean, resultJsonOrExceptionMessage: string): void { + const resultOrError = success + ? parseJsonWithRevivers(this, resultJsonOrExceptionMessage) + : new Error(resultJsonOrExceptionMessage); + this.completePendingCall(parseInt(asyncCallId, 10), success, resultOrError); } - invokeDotNetStaticMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise { - return this.invokeDotNetMethodAsync(assemblyName, methodIdentifier, null, args); + invokeDotNetStaticMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T | null { + return this.invokeDotNetMethod(assemblyName, methodIdentifier, null, args); + } + + invokeDotNetStaticMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise { + return this.invokeDotNetMethodAsync(assemblyName, methodIdentifier, null, args); + } + + invokeDotNetMethod(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): T | null { + if (this._dotNetCallDispatcher.invokeDotNetFromJS) { + const argsJson = stringifyArgs(this, args); + const resultJson = this._dotNetCallDispatcher.invokeDotNetFromJS(assemblyName, methodIdentifier, dotNetObjectId, argsJson); + return resultJson ? parseJsonWithRevivers(this, resultJson) : null; } - invokeDotNetMethod(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): T | null { - if (this._dotNetCallDispatcher.invokeDotNetFromJS) { - const argsJson = stringifyArgs(this, args); - const resultJson = this._dotNetCallDispatcher.invokeDotNetFromJS(assemblyName, methodIdentifier, dotNetObjectId, argsJson); - return resultJson ? parseJsonWithRevivers(this, resultJson) : null; + throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeDotNetMethodAsync instead."); + } + + invokeDotNetMethodAsync(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): Promise { + if (assemblyName && dotNetObjectId) { + throw new Error(`For instance method calls, assemblyName should be null. Received '${assemblyName}'.`); } - throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeDotNetMethodAsync instead."); - } + const asyncCallId = this._nextAsyncCallId++; + const resultPromise = new Promise((resolve, reject) => { + this._pendingAsyncCalls[asyncCallId] = { resolve, reject }; + }); - invokeDotNetMethodAsync(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): Promise { - if (assemblyName && dotNetObjectId) { - throw new Error(`For instance method calls, assemblyName should be null. Received '${assemblyName}'.`); - } - - const asyncCallId = this._nextAsyncCallId++; - const resultPromise = new Promise((resolve, reject) => { - this._pendingAsyncCalls[asyncCallId] = { resolve, reject }; - }); - - try { - const argsJson = stringifyArgs(this, args); - this._dotNetCallDispatcher.beginInvokeDotNetFromJS(asyncCallId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); - } catch (ex) { - // Synchronous failure - this.completePendingCall(asyncCallId, false, ex); - } - - return resultPromise; - } + try { + const argsJson = stringifyArgs(this, args); + this._dotNetCallDispatcher.beginInvokeDotNetFromJS(asyncCallId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); + } catch (ex) { + // Synchronous failure + this.completePendingCall(asyncCallId, false, ex); + } - receiveByteArray(id: number, data: Uint8Array): void { - this._byteArraysToBeRevived.set(id, data); - } + return resultPromise; + } - processByteArray(id: number): Uint8Array | null { - const result = this._byteArraysToBeRevived.get(id); - if (!result) { - return null; - } + receiveByteArray(id: number, data: Uint8Array): void { + this._byteArraysToBeRevived.set(id, data); + } - this._byteArraysToBeRevived.delete(id); - return result; - } + processByteArray(id: number): Uint8Array | null { + const result = this._byteArraysToBeRevived.get(id); + if (!result) { + return null; + } - supplyDotNetStream(streamId: number, stream: ReadableStream) { - if (this._pendingDotNetToJSStreams.has(streamId)) { - // The receiver is already waiting, so we can resolve the promise now and stop tracking this - const pendingStream = this._pendingDotNetToJSStreams.get(streamId)!; - this._pendingDotNetToJSStreams.delete(streamId); - pendingStream.resolve!(stream); - } else { - // The receiver hasn't started waiting yet, so track a pre-completed entry it can attach to later - const pendingStream = new PendingStream(); - pendingStream.resolve!(stream); - this._pendingDotNetToJSStreams.set(streamId, pendingStream); - } - } + this._byteArraysToBeRevived.delete(id); + return result; + } - getDotNetStreamPromise(streamId: number): Promise { - // We might already have started receiving the stream, or maybe it will come later. - // We have to handle both possible orderings, but we can count on it coming eventually because - // it's not something the developer gets to control, and it would be an error if it doesn't. - let result: Promise; - if (this._pendingDotNetToJSStreams.has(streamId)) { - // We've already started receiving the stream, so no longer need to track it as pending - result = this._pendingDotNetToJSStreams.get(streamId)!.streamPromise!; - this._pendingDotNetToJSStreams.delete(streamId); - } else { - // We haven't started receiving it yet, so add an entry to track it as pending - const pendingStream = new PendingStream(); - this._pendingDotNetToJSStreams.set(streamId, pendingStream); - result = pendingStream.streamPromise; - } - - return result; - } + supplyDotNetStream(streamId: number, stream: ReadableStream) { + if (this._pendingDotNetToJSStreams.has(streamId)) { + // The receiver is already waiting, so we can resolve the promise now and stop tracking this + const pendingStream = this._pendingDotNetToJSStreams.get(streamId)!; + this._pendingDotNetToJSStreams.delete(streamId); + pendingStream.resolve!(stream); + } else { + // The receiver hasn't started waiting yet, so track a pre-completed entry it can attach to later + const pendingStream = new PendingStream(); + pendingStream.resolve!(stream); + this._pendingDotNetToJSStreams.set(streamId, pendingStream); + } + } - private completePendingCall(asyncCallId: number, success: boolean, resultOrError: any) { - if (!this._pendingAsyncCalls.hasOwnProperty(asyncCallId)) { - throw new Error(`There is no pending async call with ID ${asyncCallId}.`); - } - - const asyncCall = this._pendingAsyncCalls[asyncCallId]; - delete this._pendingAsyncCalls[asyncCallId]; - if (success) { - asyncCall.resolve(resultOrError); - } else { - asyncCall.reject(resultOrError); - } - } - } + getDotNetStreamPromise(streamId: number): Promise { + // We might already have started receiving the stream, or maybe it will come later. + // We have to handle both possible orderings, but we can count on it coming eventually because + // it's not something the developer gets to control, and it would be an error if it doesn't. + let result: Promise; + if (this._pendingDotNetToJSStreams.has(streamId)) { + // We've already started receiving the stream, so no longer need to track it as pending + result = this._pendingDotNetToJSStreams.get(streamId)!.streamPromise!; + this._pendingDotNetToJSStreams.delete(streamId); + } else { + // We haven't started receiving it yet, so add an entry to track it as pending + const pendingStream = new PendingStream(); + this._pendingDotNetToJSStreams.set(streamId, pendingStream); + result = pendingStream.streamPromise; + } - function formatError(error: Error | string): string { - if (error instanceof Error) { - return `${error.message}\n${error.stack}`; - } + return result; + } - return error ? error.toString() : "null"; - } + private completePendingCall(asyncCallId: number, success: boolean, resultOrError: any) { + if (!this._pendingAsyncCalls.hasOwnProperty(asyncCallId)) { + throw new Error(`There is no pending async call with ID ${asyncCallId}.`); + } - export function findJSFunction(identifier: string, targetInstanceId: number): Function { - const targetInstance = cachedJSObjectsById[targetInstanceId]; - - if (targetInstance) { - const member = targetInstance.findMember(identifier); + const asyncCall = this._pendingAsyncCalls[asyncCallId]; + delete this._pendingAsyncCalls[asyncCallId]; + if (success) { + asyncCall.resolve(resultOrError); + } else { + asyncCall.reject(resultOrError); + } + } + } - if (!member.func) { - throw new Error(`The value '${identifier}' is not a function.`); - } + function formatError(error: Error | string): string { + if (error instanceof Error) { + return `${error.message}\n${error.stack}`; + } - return member.func; - } - - throw new Error(`JS object instance with ID ${targetInstanceId} does not exist (has it been disposed?).`); - } + return error ? error.toString() : "null"; + } - export function findObjectMember(identifier: string, targetInstanceId: number): ObjectMemberDescriptor { - const targetInstance = cachedJSObjectsById[targetInstanceId]; + export function findJSFunction(identifier: string, targetInstanceId: number): Function { + const targetInstance = cachedJSObjectsById[targetInstanceId]; - if (!targetInstance) { - throw new Error(`JS object instance with ID ${targetInstanceId} does not exist (has it been disposed?).`); - } + if (targetInstance) { + const member = targetInstance.findMember(identifier); - return targetInstance.findMember(identifier); - } + if (!member.func) { + throw new Error(`The value '${identifier}' is not a function.`); + } - export function disposeJSObjectReferenceById(id: number) { - delete cachedJSObjectsById[id]; - } + return member.func; + } - export class DotNetObject { - // eslint-disable-next-line no-empty-function - constructor(private readonly _id: number, private readonly _callDispatcher: CallDispatcher) { - } + throw new Error(`JS object instance with ID ${targetInstanceId} does not exist (has it been disposed?).`); + } + + export function findObjectMember(identifier: string, targetInstanceId: number): ObjectMemberDescriptor { + const targetInstance = cachedJSObjectsById[targetInstanceId]; - public invokeMethod(methodIdentifier: string, ...args: any[]): T | null { - return this._callDispatcher.invokeDotNetMethod(null, methodIdentifier, this._id, args); + if (!targetInstance) { + throw new Error(`JS object instance with ID ${targetInstanceId} does not exist (has it been disposed?).`); } - public invokeMethodAsync(methodIdentifier: string, ...args: any[]): Promise { - return this._callDispatcher.invokeDotNetMethodAsync(null, methodIdentifier, this._id, args); - } + return targetInstance.findMember(identifier); + } - public dispose() { - const promise = this._callDispatcher.invokeDotNetMethodAsync(null, "__Dispose", this._id, null); - promise.catch(error => console.error(error)); - } + export function disposeJSObjectReferenceById(id: number) { + delete cachedJSObjectsById[id]; + } - public serializeAsArg() { - return { [dotNetObjectRefKey]: this._id }; - } + export class DotNetObject { + // eslint-disable-next-line no-empty-function + constructor(private readonly _id: number, private readonly _callDispatcher: CallDispatcher) { + } + + public invokeMethod(methodIdentifier: string, ...args: any[]): T | null { + return this._callDispatcher.invokeDotNetMethod(null, methodIdentifier, this._id, args); } - attachReviver(function reviveReference(key: any, value: any) { - if (value && typeof value === "object") { - if (value.hasOwnProperty(dotNetObjectRefKey)) { - return new DotNetObject(value[dotNetObjectRefKey], currentCallDispatcher!); - } else if (value.hasOwnProperty(jsObjectIdKey)) { - const id = value[jsObjectIdKey]; - const jsObject = cachedJSObjectsById[id]; - - if (jsObject) { - return jsObject.getWrappedObject(); - } - - throw new Error(`JS object instance with Id '${id}' does not exist. It may have been disposed.`); - } else if (value.hasOwnProperty(byteArrayRefKey)) { - const index = value[byteArrayRefKey]; - const byteArray = currentCallDispatcher!.processByteArray(index); - if (byteArray === undefined) { - throw new Error(`Byte array index '${index}' does not exist.`); - } - return byteArray; - } else if (value.hasOwnProperty(dotNetStreamRefKey)) { - const streamId = value[dotNetStreamRefKey]; - const streamPromise = currentCallDispatcher!.getDotNetStreamPromise(streamId); - return new DotNetStream(streamPromise); - } - } + public invokeMethodAsync(methodIdentifier: string, ...args: any[]): Promise { + return this._callDispatcher.invokeDotNetMethodAsync(null, methodIdentifier, this._id, args); + } - // Unrecognized - let another reviver handle it - return value; - }); + public dispose() { + const promise = this._callDispatcher.invokeDotNetMethodAsync(null, "__Dispose", this._id, null); + promise.catch(error => console.error(error)); + } - class DotNetStream { - // eslint-disable-next-line no-empty-function - constructor(private readonly _streamPromise: Promise) { - } + public serializeAsArg() { + return { [dotNetObjectRefKey]: this._id }; + } + } - /** - * Supplies a readable stream of data being sent from .NET. - */ - stream(): Promise { - return this._streamPromise; - } + attachReviver(function reviveReference(key: any, value: any) { + if (value && typeof value === "object") { + if (value.hasOwnProperty(dotNetObjectRefKey)) { + return new DotNetObject(value[dotNetObjectRefKey], currentCallDispatcher!); + } else if (value.hasOwnProperty(jsObjectIdKey)) { + const id = value[jsObjectIdKey]; + const jsObject = cachedJSObjectsById[id]; + + if (jsObject) { + return jsObject.getWrappedObject(); + } + + throw new Error(`JS object instance with Id '${id}' does not exist. It may have been disposed.`); + } else if (value.hasOwnProperty(byteArrayRefKey)) { + const index = value[byteArrayRefKey]; + const byteArray = currentCallDispatcher!.processByteArray(index); + if (byteArray === undefined) { + throw new Error(`Byte array index '${index}' does not exist.`); + } + return byteArray; + } else if (value.hasOwnProperty(dotNetStreamRefKey)) { + const streamId = value[dotNetStreamRefKey]; + const streamPromise = currentCallDispatcher!.getDotNetStreamPromise(streamId); + return new DotNetStream(streamPromise); + } + } - /** - * Supplies a array buffer of data being sent from .NET. - * Note there is a JavaScript limit on the size of the ArrayBuffer equal to approximately 2GB. - */ - async arrayBuffer(): Promise { - return new Response(await this.stream()).arrayBuffer(); - } - } + // Unrecognized - let another reviver handle it + return value; + }); - class PendingStream { - streamPromise: Promise; + class DotNetStream { + // eslint-disable-next-line no-empty-function + constructor(private readonly _streamPromise: Promise) { + } - resolve?: (value: ReadableStream) => void; + /** + * Supplies a readable stream of data being sent from .NET. + */ + stream(): Promise { + return this._streamPromise; + } - reject?: (reason: any) => void; + /** + * Supplies a array buffer of data being sent from .NET. + * Note there is a JavaScript limit on the size of the ArrayBuffer equal to approximately 2GB. + */ + async arrayBuffer(): Promise { + return new Response(await this.stream()).arrayBuffer(); + } + } - constructor() { - this.streamPromise = new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - }); - } - } + class PendingStream { + streamPromise: Promise; - function createJSCallResult(returnValue: any, resultType: JSCallResultType) { - switch (resultType) { - case JSCallResultType.Default: - return returnValue; - case JSCallResultType.JSObjectReference: - return createJSObjectReference(returnValue); - case JSCallResultType.JSStreamReference: - return createJSStreamReference(returnValue); - case JSCallResultType.JSVoidResult: - return null; - default: - throw new Error(`Invalid JS call result type '${resultType}'.`); - } - } + resolve?: (value: ReadableStream) => void; - let nextByteArrayIndex = 0; - function stringifyArgs(callDispatcher: CallDispatcher, args: any[] | null) { - nextByteArrayIndex = 0; - currentCallDispatcher = callDispatcher; - const result = JSON.stringify(args, argReplacer); - currentCallDispatcher = undefined; - return result; - } + reject?: (reason: any) => void; - function argReplacer(key: string, value: any) { - if (value instanceof DotNetObject) { - return value.serializeAsArg(); - } else if (value instanceof Uint8Array) { - const dotNetCallDispatcher = currentCallDispatcher!.getDotNetCallDispatcher(); - dotNetCallDispatcher!.sendByteArray(nextByteArrayIndex, value); - const jsonValue = { [byteArrayRefKey]: nextByteArrayIndex }; - nextByteArrayIndex++; - return jsonValue; - } + constructor() { + this.streamPromise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + } + } - return value; - } + function createJSCallResult(returnValue: any, resultType: JSCallResultType) { + switch (resultType) { + case JSCallResultType.Default: + return returnValue; + case JSCallResultType.JSObjectReference: + return createJSObjectReference(returnValue); + case JSCallResultType.JSStreamReference: + return createJSStreamReference(returnValue); + case JSCallResultType.JSVoidResult: + return null; + default: + throw new Error(`Invalid JS call result type '${resultType}'.`); + } + } + + let nextByteArrayIndex = 0; + function stringifyArgs(callDispatcher: CallDispatcher, args: any[] | null) { + nextByteArrayIndex = 0; + currentCallDispatcher = callDispatcher; + const result = JSON.stringify(args, argReplacer); + currentCallDispatcher = undefined; + return result; + } + + function argReplacer(key: string, value: any) { + if (value instanceof DotNetObject) { + return value.serializeAsArg(); + } else if (value instanceof Uint8Array) { + const dotNetCallDispatcher = currentCallDispatcher!.getDotNetCallDispatcher(); + dotNetCallDispatcher!.sendByteArray(nextByteArrayIndex, value); + const jsonValue = { [byteArrayRefKey]: nextByteArrayIndex }; + nextByteArrayIndex++; + return jsonValue; + } + + return value; + } } From 21caedd27c17aeb62c1f9ed4083430be7935076b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Fri, 28 Mar 2025 19:11:17 +0100 Subject: [PATCH 13/25] Remove unwanted indentation changes in legacy code --- .../src/src/Microsoft.JSInterop.ts | 274 +++++++++--------- 1 file changed, 137 insertions(+), 137 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index 192621469715..db075c2381d4 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -138,20 +138,20 @@ export module DotNet { jsonRevivers.push(reviver); } -/** - * Invokes the specified .NET public method synchronously. Not all hosting scenarios support - * synchronous invocation, so if possible use invokeMethodAsync instead. - * - * @deprecated Use DotNetObject to invoke instance methods instead. - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns The result of the operation. - */ -export function invokeMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T | null { - const dispatcher = getDefaultCallDispatcher(); - return dispatcher.invokeDotNetStaticMethod(assemblyName, methodIdentifier, ...args); -} + /** + * Invokes the specified .NET public method synchronously. Not all hosting scenarios support + * synchronous invocation, so if possible use invokeMethodAsync instead. + * + * @deprecated Use DotNetObject to invoke instance methods instead. + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param args Arguments to pass to the method, each of which must be JSON-serializable. + * @returns The result of the operation. + */ + export function invokeMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T | null { + const dispatcher = getDefaultCallDispatcher(); + return dispatcher.invokeDotNetStaticMethod(assemblyName, methodIdentifier, ...args); + } /** * Invokes the specified .NET public method asynchronously. @@ -271,8 +271,8 @@ export function invokeMethod(assemblyName: string, methodIdentifier: string, } interface PendingAsyncCall { - resolve: (value?: T | PromiseLike) => void; - reject: (reason?: any) => void; + resolve: (value?: T | PromiseLike) => void; + reject: (reason?: any) => void; } /** @@ -280,20 +280,20 @@ export function invokeMethod(assemblyName: string, methodIdentifier: string, */ // eslint-disable-next-line no-shadow export enum JSCallResultType { - Default = 0, - JSObjectReference = 1, - JSStreamReference = 2, - JSVoidResult = 3, + Default = 0, + JSObjectReference = 1, + JSStreamReference = 2, + JSVoidResult = 3, } /** * Represents the type of operation that should be performed in JS. */ export enum JSCallType { - FunctionCall = 1, - NewCall = 2, - GetValue = 3, - SetValue = 4 + FunctionCall = 1, + NewCall = 2, + GetValue = 3, + SetValue = 4 } /** @@ -317,106 +317,106 @@ export function invokeMethod(assemblyName: string, methodIdentifier: string, * Represents the ability to dispatch calls from JavaScript to a .NET runtime. */ export interface DotNetCallDispatcher { - /** - * Optional. If implemented, invoked by the runtime to perform a synchronous call to a .NET method. - * - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null or undefined to call static methods. - * @param argsJson JSON representation of arguments to pass to the method. - * @returns JSON representation of the result of the invocation. - */ - invokeDotNetFromJS?(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): string | null; - - /** - * Invoked by the runtime to begin an asynchronous call to a .NET method. - * - * @param callId A value identifying the asynchronous operation. This value should be passed back in a later call from .NET to JS. - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null to call static methods. - * @param argsJson JSON representation of arguments to pass to the method. - */ - beginInvokeDotNetFromJS(callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): void; - - /** - * Invoked by the runtime to complete an asynchronous JavaScript function call started from .NET - * - * @param callId A value identifying the asynchronous operation. - * @param succeded Whether the operation succeeded or not. - * @param resultOrError The serialized result or the serialized error from the async operation. - */ - endInvokeJSFromDotNet(callId: number, succeeded: boolean, resultOrError: any): void; - - /** - * Invoked by the runtime to transfer a byte array from JS to .NET. - * @param id The identifier for the byte array used during revival. - * @param data The byte array being transferred for eventual revival. - */ - sendByteArray(id: number, data: Uint8Array): void; + /** + * Optional. If implemented, invoked by the runtime to perform a synchronous call to a .NET method. + * + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null or undefined to call static methods. + * @param argsJson JSON representation of arguments to pass to the method. + * @returns JSON representation of the result of the invocation. + */ + invokeDotNetFromJS?(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): string | null; + + /** + * Invoked by the runtime to begin an asynchronous call to a .NET method. + * + * @param callId A value identifying the asynchronous operation. This value should be passed back in a later call from .NET to JS. + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null to call static methods. + * @param argsJson JSON representation of arguments to pass to the method. + */ + beginInvokeDotNetFromJS(callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): void; + + /** + * Invoked by the runtime to complete an asynchronous JavaScript function call started from .NET + * + * @param callId A value identifying the asynchronous operation. + * @param succeded Whether the operation succeeded or not. + * @param resultOrError The serialized result or the serialized error from the async operation. + */ + endInvokeJSFromDotNet(callId: number, succeeded: boolean, resultOrError: any): void; + + /** + * Invoked by the runtime to transfer a byte array from JS to .NET. + * @param id The identifier for the byte array used during revival. + * @param data The byte array being transferred for eventual revival. + */ + sendByteArray(id: number, data: Uint8Array): void; } /** * Represents the ability to facilitate function call dispatching between JavaScript and a .NET runtime. */ export interface ICallDispatcher { - /** - * Invokes the specified synchronous JavaScript function. - * - * @param invocationInfo Configuration of the interop call. - */ - invokeJSFromDotNet(invocationInfo: JSInvocationInfo): string | null; - - /** - * Invokes the specified synchronous or asynchronous JavaScript function. - * - * @param invocationInfo Configuration of the interop call. - */ - beginInvokeJSFromDotNet(invocationInfo: JSInvocationInfo): Promise | null; - - /** - * Receives notification that an async call from JS to .NET has completed. - * @param asyncCallId The identifier supplied in an earlier call to beginInvokeDotNetFromJS. - * @param success A flag to indicate whether the operation completed successfully. - * @param resultJsonOrExceptionMessage Either the operation result as JSON, or an error message. - */ - endInvokeDotNetFromJS(asyncCallId: string, success: boolean, resultJsonOrExceptionMessage: string): void; - - /** - * Invokes the specified .NET public static method synchronously. Not all hosting scenarios support - * synchronous invocation, so if possible use invokeMethodAsync instead. - * - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns The result of the operation. - */ - invokeDotNetStaticMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): | null; - - /** - * Invokes the specified .NET public static method asynchronously. - * - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns A promise representing the result of the operation. - */ - invokeDotNetStaticMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise; - - /** - * Receives notification that a byte array is being transferred from .NET to JS. - * @param id The identifier for the byte array used during revival. - * @param data The byte array being transferred for eventual revival. - */ - receiveByteArray(id: number, data: Uint8Array): void - - /** - * Supplies a stream of data being sent from .NET. - * - * @param streamId The identifier previously passed to JSRuntime's BeginTransmittingStream in .NET code. - * @param stream The stream data. - */ - supplyDotNetStream(streamId: number, stream: ReadableStream): void; + /** + * Invokes the specified synchronous JavaScript function. + * + * @param invocationInfo Configuration of the interop call. + */ + invokeJSFromDotNet(invocationInfo: JSInvocationInfo): string | null; + + /** + * Invokes the specified synchronous or asynchronous JavaScript function. + * + * @param invocationInfo Configuration of the interop call. + */ + beginInvokeJSFromDotNet(invocationInfo: JSInvocationInfo): Promise | null; + + /** + * Receives notification that an async call from JS to .NET has completed. + * @param asyncCallId The identifier supplied in an earlier call to beginInvokeDotNetFromJS. + * @param success A flag to indicate whether the operation completed successfully. + * @param resultJsonOrExceptionMessage Either the operation result as JSON, or an error message. + */ + endInvokeDotNetFromJS(asyncCallId: string, success: boolean, resultJsonOrExceptionMessage: string): void; + + /** + * Invokes the specified .NET public static method synchronously. Not all hosting scenarios support + * synchronous invocation, so if possible use invokeMethodAsync instead. + * + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param args Arguments to pass to the method, each of which must be JSON-serializable. + * @returns The result of the operation. + */ + invokeDotNetStaticMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): | null; + + /** + * Invokes the specified .NET public static method asynchronously. + * + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param args Arguments to pass to the method, each of which must be JSON-serializable. + * @returns A promise representing the result of the operation. + */ + invokeDotNetStaticMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise; + + /** + * Receives notification that a byte array is being transferred from .NET to JS. + * @param id The identifier for the byte array used during revival. + * @param data The byte array being transferred for eventual revival. + */ + receiveByteArray(id: number, data: Uint8Array): void + + /** + * Supplies a stream of data being sent from .NET. + * + * @param streamId The identifier previously passed to JSRuntime's BeginTransmittingStream in .NET code. + * @param stream The stream data. + */ + supplyDotNetStream(streamId: number, stream: ReadableStream): void; } class CallDispatcher implements ICallDispatcher { @@ -606,23 +606,23 @@ export function invokeMethod(assemblyName: string, methodIdentifier: string, this.completePendingCall(parseInt(asyncCallId, 10), success, resultOrError); } - invokeDotNetStaticMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T | null { - return this.invokeDotNetMethod(assemblyName, methodIdentifier, null, args); - } + invokeDotNetStaticMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T | null { + return this.invokeDotNetMethod(assemblyName, methodIdentifier, null, args); + } invokeDotNetStaticMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise { return this.invokeDotNetMethodAsync(assemblyName, methodIdentifier, null, args); } - invokeDotNetMethod(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): T | null { + invokeDotNetMethod(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): T | null { if (this._dotNetCallDispatcher.invokeDotNetFromJS) { const argsJson = stringifyArgs(this, args); const resultJson = this._dotNetCallDispatcher.invokeDotNetFromJS(assemblyName, methodIdentifier, dotNetObjectId, argsJson); return resultJson ? parseJsonWithRevivers(this, resultJson) : null; } - throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeDotNetMethodAsync instead."); - } + throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeDotNetMethodAsync instead."); + } invokeDotNetMethodAsync(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): Promise { if (assemblyName && dotNetObjectId) { @@ -750,9 +750,9 @@ export function invokeMethod(assemblyName: string, methodIdentifier: string, constructor(private readonly _id: number, private readonly _callDispatcher: CallDispatcher) { } - public invokeMethod(methodIdentifier: string, ...args: any[]): T | null { - return this._callDispatcher.invokeDotNetMethod(null, methodIdentifier, this._id, args); - } + public invokeMethod(methodIdentifier: string, ...args: any[]): T | null { + return this._callDispatcher.invokeDotNetMethod(null, methodIdentifier, this._id, args); + } public invokeMethodAsync(methodIdentifier: string, ...args: any[]): Promise { return this._callDispatcher.invokeDotNetMethodAsync(null, methodIdentifier, this._id, args); @@ -837,16 +837,16 @@ export function invokeMethod(assemblyName: string, methodIdentifier: string, function createJSCallResult(returnValue: any, resultType: JSCallResultType) { switch (resultType) { - case JSCallResultType.Default: - return returnValue; - case JSCallResultType.JSObjectReference: - return createJSObjectReference(returnValue); - case JSCallResultType.JSStreamReference: - return createJSStreamReference(returnValue); - case JSCallResultType.JSVoidResult: - return null; - default: - throw new Error(`Invalid JS call result type '${resultType}'.`); + case JSCallResultType.Default: + return returnValue; + case JSCallResultType.JSObjectReference: + return createJSObjectReference(returnValue); + case JSCallResultType.JSStreamReference: + return createJSStreamReference(returnValue); + case JSCallResultType.JSVoidResult: + return null; + default: + throw new Error(`Invalid JS call result type '${resultType}'.`); } } From c7f49d668f24776f81e77cf284b4c2dde821e935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Mon, 31 Mar 2025 15:36:53 +0200 Subject: [PATCH 14/25] Fix API, remove uneeded files --- .../BlazorServerApp/BlazorServerApp.csproj | 4 - .../Samples/BlazorServerApp/Pages/Index.razor | 185 +----------------- .../BlazorServerApp/Pages/_Layout.cshtml | 1 - .../BlazorServerApp/wwwroot/custom-module.js | 3 - .../BlazorServerApp/wwwroot/custom-script.js | 61 ------ .../BlazorUnitedApp.Client/HelloWorld.razor | 184 +---------------- .../Samples/BlazorUnitedApp/App.razor | 1 - .../BlazorUnitedApp/BlazorUnitedApp.csproj | 8 - .../BlazorUnitedApp/wwwroot/custom-module.js | 3 - .../BlazorUnitedApp/wwwroot/custom-script.js | 61 ------ .../Server/src/Circuits/RemoteJSRuntime.cs | 15 ++ .../test/ProtectedBrowserStorageTest.cs | 1 - .../IInternalWebJSInProcessRuntime.cs | 7 + .../JSInterop/src/WebAssemblyJSRuntime.cs | 32 +++ .../test/RemoteAuthenticatorCoreTests.cs | 23 +-- .../Services/DefaultWebAssemblyJSRuntime.cs | 15 ++ .../WebView/src/Services/WebViewJSRuntime.cs | 15 ++ .../BasicTestApp/DotNetToJSInterop.razor | 132 ++++--------- .../src/{jest.config.js => jest.config.mjs} | 4 +- .../src/JSInProcessRuntime.cs | 24 ++- .../Microsoft.JSInterop/src/JSRuntime.cs | 10 + .../src/PublicAPI.Unshipped.txt | 8 + 22 files changed, 165 insertions(+), 632 deletions(-) delete mode 100644 src/Components/Samples/BlazorServerApp/wwwroot/custom-module.js delete mode 100644 src/Components/Samples/BlazorServerApp/wwwroot/custom-script.js delete mode 100644 src/Components/Samples/BlazorUnitedApp/wwwroot/custom-module.js delete mode 100644 src/Components/Samples/BlazorUnitedApp/wwwroot/custom-script.js rename src/JSInterop/Microsoft.JSInterop.JS/src/{jest.config.js => jest.config.mjs} (88%) diff --git a/src/Components/Samples/BlazorServerApp/BlazorServerApp.csproj b/src/Components/Samples/BlazorServerApp/BlazorServerApp.csproj index 1035814a2137..02f0243552d8 100644 --- a/src/Components/Samples/BlazorServerApp/BlazorServerApp.csproj +++ b/src/Components/Samples/BlazorServerApp/BlazorServerApp.csproj @@ -6,10 +6,6 @@ enable - - - - diff --git a/src/Components/Samples/BlazorServerApp/Pages/Index.razor b/src/Components/Samples/BlazorServerApp/Pages/Index.razor index 0ab6f6c76554..7b5a15e0e22b 100644 --- a/src/Components/Samples/BlazorServerApp/Pages/Index.razor +++ b/src/Components/Samples/BlazorServerApp/Pages/Index.razor @@ -1,188 +1,7 @@ -@implements IAsyncDisposable -@inject IJSRuntime JSRuntime -@page "/" +@page "/" -My index +Index

Hello, world!

Welcome to your new app. - -
- -
- -
- Message: - - -
- -
- Title: - - -
- -
- - @CurrentTitle -
- -
- @TestObjectDisplay
- - - - -
- -
- - - @AnimalMessage -
- -
- - - - @ErrorMessage -
- -@code { - private string? Message { get; set; } - private string? CurrentTitle { get; set; } - private string? NewTitle { get; set; } - private string? TestObjectDisplay { get; set; } - private string? AnimalMessage { get; set; } - private string? ErrorMessage { get; set; } - - private IJSObjectReference? module; - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - module = await JSRuntime.InvokeAsync("import", "../custom-module.js"); - } - } - - - private async Task LogDefault() - { - await JSRuntime.InvokeVoidAsync("logDefault"); - } - - private async Task LogMessage(string message) - { - await JSRuntime.InvokeVoidAsync("logMessage", message); - await JSRuntime.InvokeVoidAsync("console.log", $"Console: {message}"); - } - - - private async Task LogFromModule(string message) - { - if (module != null) - { - await module.InvokeVoidAsync("logFromModule", message); - } - } - - - private async Task SetDocumentTitle(string title) - { - await JSRuntime.InvokeVoidAsync("setDocumentTitle", title); - } - - private async Task SetDocumentTitleDirectly(string title) - { - await JSRuntime.SetValueAsync("document.title", title); - } - - private async Task GetDocumentTitle() - { - CurrentTitle = await JSRuntime.GetValueAsync("document.title"); - } - - private async Task GetTestObjectState() - { - var model = await JSRuntime.InvokeAsync("getTestObject"); - TestObjectDisplay = $"Serialized state: {model.Num} | {model.Text} | {model.GetOnlyProperty}"; - } - - private async Task GetTestObjectStateViaReference() - { - var objectRef = await JSRuntime.GetValueAsync("testObject"); - var model = await objectRef.GetValueAsync(); - TestObjectDisplay = $"Serialized state via reference: {model.Num} | {model.Text} | {model.GetOnlyProperty}"; - } - - private async Task GetTestObjectStateViaReferenceFromFunction() - { - var objectRef = await JSRuntime.InvokeAsync("getTestObject"); - var numValue = await objectRef.GetValueAsync("num"); - var textValue = await objectRef.GetValueAsync("text"); - var getOnlyProperty = await objectRef.GetValueAsync("getOnlyProperty"); - TestObjectDisplay = $"State via reference from function: {numValue} | {textValue} | {getOnlyProperty}"; - } - - private async Task GetTestObjectStateViaReferenceFromProperty() - { - var objectRef = await JSRuntime.GetValueAsync("testObject"); - var numValue = await objectRef.GetValueAsync("num"); - var textValue = await objectRef.GetValueAsync("text"); - var getOnlyProperty = await objectRef.GetValueAsync("getOnlyProperty"); - TestObjectDisplay = $"State via reference from property: {numValue} | {textValue} | {getOnlyProperty}"; - } - - private async Task CreateDog() - { - var dogRef = await JSRuntime.InvokeNewAsync("Dog", ["Igor"]); - AnimalMessage = await dogRef.InvokeAsync("bark"); - } - - private async Task CreateCat() - { - var catRef = await JSRuntime.InvokeNewAsync("Cat", ["Mikeš"]); - AnimalMessage = await catRef.InvokeAsync("meow"); - } - - private async Task GetInvalid(MouseEventArgs args) - { - var objectRef = await JSRuntime.GetValueAsync("testObject"); - var value = await objectRef.GetValueAsync("setOnlyProperty"); - } - - private async Task SetInvalid(MouseEventArgs args) - { - var objectRef = await JSRuntime.GetValueAsync("testObject"); - await objectRef.SetValueAsync("getOnlyProperty", 123); - } - - private async Task SetInvalidViaFunction(MouseEventArgs args) - { - await JSRuntime.InvokeVoidAsync("invalidAccess"); - } - - async ValueTask IAsyncDisposable.DisposeAsync() - { - if (module is not null) - { - try - { - await module.DisposeAsync(); - } - catch (JSDisconnectedException) - { - } - } - } - - class TestObjectModel - { - public int Num { get; set; } - public string? Text { get; set; } - public int GetOnlyProperty { get; set; } - } -} diff --git a/src/Components/Samples/BlazorServerApp/Pages/_Layout.cshtml b/src/Components/Samples/BlazorServerApp/Pages/_Layout.cshtml index dcf6423af39c..91499422deb9 100644 --- a/src/Components/Samples/BlazorServerApp/Pages/_Layout.cshtml +++ b/src/Components/Samples/BlazorServerApp/Pages/_Layout.cshtml @@ -23,6 +23,5 @@ configureSignalR: builder => builder.configureLogging("debug") // LogLevel.Debug }); - diff --git a/src/Components/Samples/BlazorServerApp/wwwroot/custom-module.js b/src/Components/Samples/BlazorServerApp/wwwroot/custom-module.js deleted file mode 100644 index 4c32c97086ff..000000000000 --- a/src/Components/Samples/BlazorServerApp/wwwroot/custom-module.js +++ /dev/null @@ -1,3 +0,0 @@ -export function logFromModule(msg) { - console.log(`Message from custom-module.js: ${msg}`); -} diff --git a/src/Components/Samples/BlazorServerApp/wwwroot/custom-script.js b/src/Components/Samples/BlazorServerApp/wwwroot/custom-script.js deleted file mode 100644 index 0c3aba05d23a..000000000000 --- a/src/Components/Samples/BlazorServerApp/wwwroot/custom-script.js +++ /dev/null @@ -1,61 +0,0 @@ -"use strict"; - -window.getDocumentTitle = function () { - return document.title; -} - -window.setDocumentTitle = function (title) { - document.title = title; -}; - -window.logDefault = function () { - console.log("This is a default log message"); -} - -window.logMessage = function (message) { - console.log(message); -} - -window.testObject = { - num: 10, - text: "Hello World", - log: function () { - console.log(this.text); - }, - get getOnlyProperty() { - return this.num; - }, - set setOnlyProperty(value) { - this.num = value; - } -} - -window.invalidAccess = function () { - window.testObject.getOnlyProperty = 20; -} - -window.getTestObject = function () { - return window.testObject; -} - -window.Cat = class { - constructor(name) { - this.name = name; - } - - meow() { - const text = `${this.name} says Meow!`; - console.log(text); - return text; - } -} - -window.Dog = function (name) { - this.name = name; -} - -window.Dog.prototype.bark = function () { - const text = `${this.name} says Woof!`; - console.log(text); - return text; -} diff --git a/src/Components/Samples/BlazorUnitedApp.Client/HelloWorld.razor b/src/Components/Samples/BlazorUnitedApp.Client/HelloWorld.razor index bc822bd9a04c..9bde6d3ba70b 100644 --- a/src/Components/Samples/BlazorUnitedApp.Client/HelloWorld.razor +++ b/src/Components/Samples/BlazorUnitedApp.Client/HelloWorld.razor @@ -1,183 +1 @@ -@implements IAsyncDisposable -@inject IJSRuntime JSRuntime - -

Welcome to WebAssembly!

- -
- -
- -
- Message: - - -
- -
- Title: - - -
- -
- - @CurrentTitle -
- -
- @TestObjectDisplay
- - - - -
- -
- - - @AnimalMessage -
- -
- - - - @ErrorMessage -
- -@code { - private string? Message { get; set; } - private string? CurrentTitle { get; set; } - private string? NewTitle { get; set; } - private string? TestObjectDisplay { get; set; } - private string? AnimalMessage { get; set; } - private string? ErrorMessage { get; set; } - - private IJSObjectReference? module; - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - module = await JSRuntime.InvokeAsync("import", "../custom-module.js"); - } - } - - - private async Task LogDefault() - { - await JSRuntime.InvokeVoidAsync("logDefault"); - } - - private async Task LogMessage(string message) - { - await JSRuntime.InvokeVoidAsync("logMessage", message); - await JSRuntime.InvokeVoidAsync("console.log", $"Console: {message}"); - } - - - private async Task LogFromModule(string message) - { - if (module != null) - { - await module.InvokeVoidAsync("logFromModule", message); - } - } - - - private async Task SetDocumentTitle(string title) - { - await JSRuntime.InvokeVoidAsync("setDocumentTitle", title); - } - - private async Task SetDocumentTitleDirectly(string title) - { - await JSRuntime.SetValueAsync("document.title", title); - } - - private async Task GetDocumentTitle() - { - CurrentTitle = await JSRuntime.GetValueAsync("document.title"); - } - - private async Task GetTestObjectState() - { - var model = await JSRuntime.InvokeAsync("getTestObject"); - TestObjectDisplay = $"Serialized state: {model.Num} | {model.Text} | {model.GetOnlyProperty}"; - } - - private async Task GetTestObjectStateViaReference() - { - var objectRef = await JSRuntime.GetValueAsync("testObject"); - var model = await objectRef.GetValueAsync(); - TestObjectDisplay = $"Serialized state via reference: {model.Num} | {model.Text} | {model.GetOnlyProperty}"; - } - - private async Task GetTestObjectStateViaReferenceFromFunction() - { - var objectRef = await JSRuntime.InvokeAsync("getTestObject"); - var numValue = await objectRef.GetValueAsync("num"); - var textValue = await objectRef.GetValueAsync("text"); - var getOnlyProperty = await objectRef.GetValueAsync("getOnlyProperty"); - TestObjectDisplay = $"State via reference from function: {numValue} | {textValue} | {getOnlyProperty}"; - } - - private async Task GetTestObjectStateViaReferenceFromProperty() - { - var objectRef = await JSRuntime.GetValueAsync("testObject"); - var numValue = await objectRef.GetValueAsync("num"); - var textValue = await objectRef.GetValueAsync("text"); - var getOnlyProperty = await objectRef.GetValueAsync("getOnlyProperty"); - TestObjectDisplay = $"State via reference from property: {numValue} | {textValue} | {getOnlyProperty}"; - } - - private async Task CreateDog() - { - var dogRef = await JSRuntime.InvokeNewAsync("Dog", ["Igor"]); - AnimalMessage = await dogRef.InvokeAsync("bark"); - } - - private async Task CreateCat() - { - var catRef = await JSRuntime.InvokeNewAsync("Cat", ["Mikeš"]); - AnimalMessage = await catRef.InvokeAsync("meow"); - } - - private async Task GetInvalid(MouseEventArgs args) - { - var objectRef = await JSRuntime.GetValueAsync("testObject"); - var value = await objectRef.GetValueAsync("setOnlyProperty"); - } - - private async Task SetInvalid(MouseEventArgs args) - { - var objectRef = await JSRuntime.GetValueAsync("testObject"); - await objectRef.SetValueAsync("getOnlyProperty", 123); - } - - private async Task SetInvalidViaFunction(MouseEventArgs args) - { - await JSRuntime.InvokeVoidAsync("invalidAccess"); - } - - async ValueTask IAsyncDisposable.DisposeAsync() - { - if (module is not null) - { - try - { - await module.DisposeAsync(); - } - catch (JSDisconnectedException) - { - } - } - } - - class TestObjectModel - { - public int Num { get; set; } - public string? Text { get; set; } - public int GetOnlyProperty { get; set; } - } -} +

Hello webassembly!

diff --git a/src/Components/Samples/BlazorUnitedApp/App.razor b/src/Components/Samples/BlazorUnitedApp/App.razor index 6a78a6b94655..e04f7fc9e8e4 100644 --- a/src/Components/Samples/BlazorUnitedApp/App.razor +++ b/src/Components/Samples/BlazorUnitedApp/App.razor @@ -16,6 +16,5 @@ - diff --git a/src/Components/Samples/BlazorUnitedApp/BlazorUnitedApp.csproj b/src/Components/Samples/BlazorUnitedApp/BlazorUnitedApp.csproj index 2b101e305f63..62460261002b 100644 --- a/src/Components/Samples/BlazorUnitedApp/BlazorUnitedApp.csproj +++ b/src/Components/Samples/BlazorUnitedApp/BlazorUnitedApp.csproj @@ -6,14 +6,6 @@ enable - - - - - - - - diff --git a/src/Components/Samples/BlazorUnitedApp/wwwroot/custom-module.js b/src/Components/Samples/BlazorUnitedApp/wwwroot/custom-module.js deleted file mode 100644 index 4c32c97086ff..000000000000 --- a/src/Components/Samples/BlazorUnitedApp/wwwroot/custom-module.js +++ /dev/null @@ -1,3 +0,0 @@ -export function logFromModule(msg) { - console.log(`Message from custom-module.js: ${msg}`); -} diff --git a/src/Components/Samples/BlazorUnitedApp/wwwroot/custom-script.js b/src/Components/Samples/BlazorUnitedApp/wwwroot/custom-script.js deleted file mode 100644 index 0c3aba05d23a..000000000000 --- a/src/Components/Samples/BlazorUnitedApp/wwwroot/custom-script.js +++ /dev/null @@ -1,61 +0,0 @@ -"use strict"; - -window.getDocumentTitle = function () { - return document.title; -} - -window.setDocumentTitle = function (title) { - document.title = title; -}; - -window.logDefault = function () { - console.log("This is a default log message"); -} - -window.logMessage = function (message) { - console.log(message); -} - -window.testObject = { - num: 10, - text: "Hello World", - log: function () { - console.log(this.text); - }, - get getOnlyProperty() { - return this.num; - }, - set setOnlyProperty(value) { - this.num = value; - } -} - -window.invalidAccess = function () { - window.testObject.getOnlyProperty = 20; -} - -window.getTestObject = function () { - return window.testObject; -} - -window.Cat = class { - constructor(name) { - this.name = name; - } - - meow() { - const text = `${this.name} says Meow!`; - console.log(text); - return text; - } -} - -window.Dog = function (name) { - this.name = name; -} - -window.Dog.prototype.bark = function () { - const text = `${this.name} says Woof!`; - console.log(text); - return text; -} diff --git a/src/Components/Server/src/Circuits/RemoteJSRuntime.cs b/src/Components/Server/src/Circuits/RemoteJSRuntime.cs index 99508d055c20..8f8861d8400a 100644 --- a/src/Components/Server/src/Circuits/RemoteJSRuntime.cs +++ b/src/Components/Server/src/Circuits/RemoteJSRuntime.cs @@ -104,6 +104,21 @@ protected override void SendByteArray(int id, byte[] data) _clientProxy.SendAsync("JS.ReceiveByteArray", id, data); } + protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson, JSCallResultType resultType, long targetInstanceId) + { + var invocationInfo = new JSInvocationInfo + { + AsyncHandle = asyncHandle, + TargetInstanceId = targetInstanceId, + Identifier = identifier, + CallType = JSCallType.FunctionCall, + ResultType = resultType, + ArgsJson = argsJson, + }; + + BeginInvokeJS(invocationInfo); + } + protected override void BeginInvokeJS(JSInvocationInfo invocationInfo) { if (_clientProxy is null) diff --git a/src/Components/Server/test/ProtectedBrowserStorageTest.cs b/src/Components/Server/test/ProtectedBrowserStorageTest.cs index 47bde59295a5..e42734f2bac1 100644 --- a/src/Components/Server/test/ProtectedBrowserStorageTest.cs +++ b/src/Components/Server/test/ProtectedBrowserStorageTest.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; using System.Text; using System.Text.Json; diff --git a/src/Components/Web/src/Internal/IInternalWebJSInProcessRuntime.cs b/src/Components/Web/src/Internal/IInternalWebJSInProcessRuntime.cs index 2d47bec9e2ec..02512f142ce8 100644 --- a/src/Components/Web/src/Internal/IInternalWebJSInProcessRuntime.cs +++ b/src/Components/Web/src/Internal/IInternalWebJSInProcessRuntime.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using Microsoft.JSInterop; using Microsoft.JSInterop.Infrastructure; namespace Microsoft.AspNetCore.Components.Web.Internal; @@ -12,6 +14,11 @@ namespace Microsoft.AspNetCore.Components.Web.Internal; [EditorBrowsable(EditorBrowsableState.Never)] public interface IInternalWebJSInProcessRuntime { + /// + /// For internal framework use only. + /// + string InvokeJS(string identifier, [StringSyntax(StringSyntaxAttribute.Json)] string? argsJson, JSCallResultType resultType, long targetInstanceId); + /// /// For internal framework use only. /// diff --git a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs index aa05e5e87c55..d4a0d1d1b9b0 100644 --- a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs +++ b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs @@ -22,6 +22,22 @@ protected WebAssemblyJSRuntime() JsonSerializerOptions.Converters.Insert(0, new WebAssemblyJSObjectReferenceJsonConverter(this)); } + /// + protected override string InvokeJS(string identifier, [StringSyntax(StringSyntaxAttribute.Json)] string? argsJson, JSCallResultType resultType, long targetInstanceId) + { + var invocationInfo = new JSInvocationInfo + { + AsyncHandle = 0, + TargetInstanceId = targetInstanceId, + Identifier = identifier, + CallType = JSCallType.FunctionCall, + ResultType = resultType, + ArgsJson = argsJson, + }; + + return InvokeJS(invocationInfo) ?? ""; + } + /// protected override string? InvokeJS(JSInvocationInfo invocationInfo) { @@ -37,6 +53,22 @@ protected WebAssemblyJSRuntime() } } + /// + protected override void BeginInvokeJS(long asyncHandle, string identifier, [StringSyntax(StringSyntaxAttribute.Json)] string? argsJson, JSCallResultType resultType, long targetInstanceId) + { + var invocationInfo = new JSInvocationInfo + { + AsyncHandle = asyncHandle, + TargetInstanceId = targetInstanceId, + Identifier = identifier, + CallType = JSCallType.FunctionCall, + ResultType = resultType, + ArgsJson = argsJson, + }; + + BeginInvokeJS(invocationInfo); + } + /// protected override void BeginInvokeJS(JSInvocationInfo invocationInfo) { diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs index e8243c70db94..1720e5756ab4 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs @@ -778,16 +778,6 @@ private class TestJsRuntime : IJSRuntime { public (string identifier, object[] args) LastInvocation { get; set; } - public ValueTask GetValueAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier) - { - throw new NotImplementedException(); - } - - public ValueTask GetValueAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - public ValueTask InvokeAsync(string identifier, object[] args) { LastInvocation = (identifier, args); @@ -800,32 +790,31 @@ public ValueTask InvokeAsync(string identifier, CancellationToke return default; } - public ValueTask InvokeAsyncX<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, object[] args) + public ValueTask InvokeNewAsync(string identifier, object[] args) { throw new NotImplementedException(); } - public ValueTask InvokeAsyncX<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, CancellationToken cancellationToken, object[] args) + public ValueTask InvokeNewAsync(string identifier, CancellationToken cancellationToken, object[] args) { throw new NotImplementedException(); } - public ValueTask InvokeNewAsync(string identifier, object[] args) + public ValueTask GetValueAsync(string identifier) { throw new NotImplementedException(); } - public ValueTask InvokeNewAsync(string identifier, CancellationToken cancellationToken, object[] args) + public ValueTask GetValueAsync(string identifier, CancellationToken cancellationToken) { throw new NotImplementedException(); } - - public ValueTask SetValueAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, TValue value) + public ValueTask SetValueAsync(string identifier, TValue value) { throw new NotImplementedException(); } - public ValueTask SetValueAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, TValue value, CancellationToken cancellationToken) + public ValueTask SetValueAsync(string identifier, TValue value, CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs b/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs index 3bc59ea63dd3..832657d6cb9c 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs @@ -166,6 +166,21 @@ protected override Task TransmitStreamAsync(long streamId, DotNetStreamReference return TransmitDataStreamToJS.TransmitStreamAsync(this, "Blazor._internal.receiveWebAssemblyDotNetDataStream", streamId, dotNetStreamReference); } + string IInternalWebJSInProcessRuntime.InvokeJS(string identifier, string? argsJson, JSCallResultType resultType, long targetInstanceId) + { + var invocationInfo = new JSInvocationInfo + { + AsyncHandle = 0, + TargetInstanceId = targetInstanceId, + Identifier = identifier, + CallType = JSCallType.FunctionCall, + ResultType = resultType, + ArgsJson = argsJson + }; + + return InvokeJS(invocationInfo); + } + string IInternalWebJSInProcessRuntime.InvokeJS(JSInvocationInfo invocationInfo) => InvokeJS(invocationInfo); } diff --git a/src/Components/WebView/WebView/src/Services/WebViewJSRuntime.cs b/src/Components/WebView/WebView/src/Services/WebViewJSRuntime.cs index f4beafaa4a5f..0d4f400fb11c 100644 --- a/src/Components/WebView/WebView/src/Services/WebViewJSRuntime.cs +++ b/src/Components/WebView/WebView/src/Services/WebViewJSRuntime.cs @@ -28,6 +28,21 @@ public void AttachToWebView(IpcSender ipcSender) public JsonSerializerOptions ReadJsonSerializerOptions() => JsonSerializerOptions; + protected override void BeginInvokeJS(long taskId, string identifier, string argsJson, JSCallResultType resultType, long targetInstanceId) + { + var invocationInfo = new JSInvocationInfo + { + AsyncHandle = taskId, + Identifier = identifier, + ArgsJson = argsJson, + CallType = JSCallType.FunctionCall, + ResultType = resultType, + TargetInstanceId = targetInstanceId, + }; + + BeginInvokeJS(invocationInfo); + } + protected override void BeginInvokeJS(JSInvocationInfo invocationInfo) { if (_ipcSender is null) diff --git a/src/Components/test/testassets/BasicTestApp/DotNetToJSInterop.razor b/src/Components/test/testassets/BasicTestApp/DotNetToJSInterop.razor index 832245a27ecd..5ccc6d7c7f9e 100644 --- a/src/Components/test/testassets/BasicTestApp/DotNetToJSInterop.razor +++ b/src/Components/test/testassets/BasicTestApp/DotNetToJSInterop.razor @@ -1,5 +1,4 @@ @using Microsoft.JSInterop -@* @implements IAsyncDisposable *@ @inject IJSRuntime JSRuntime