-
Notifications
You must be signed in to change notification settings - Fork 10.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JSInterop support for invoking constructors, properties, and supplying callbacks #31151
Comments
Thanks for contacting us. |
We had several reports of these things in this area and some of them block using basic JS functionality without having to write a wrapper. |
For wasm only apps there is https://github.com/dotnet/runtime/tree/main/src/libraries/System.Private.Runtime.InteropServices.JavaScript if you aren't afraid of the |
We don't recommend using those directly, since they are wasm specific and I believe might change in the future |
We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process. |
We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process. |
Thanks for contacting us. We're moving this issue to the |
Thanks for contacting us. We're moving this issue to the |
Raising my support for this issue. With the current Something like giving |
With the new partial properties in C# 13, this seems like a good case for source generators? For a private project I generate "type safe" wrappers for JSObject (using source generators) and it works really well. |
Design notes
With that in mind, here are a few examples: // Get a reference to a JS function
var fn = JSRuntime.InvokeAsync<IJsFunctionReference>("atob");
// Get the href property of the window.location object
var url = JSRuntime.InvokeAsync<string>("window.location.href");
// Set the href property of the window.location object
JSRuntime.InvokeAsync("window.location.href", "http://www.google.com");
// Invoke a constructor
var urlObj = JSRuntime.InvokeAsync<IJSObjectReference>("URL", "https://www.example.com"); |
Some of these could come for free if this restriction is somehow removed: #55259 |
I like it a lot. The one thing I'm missing to make this feature complete is the option to make it possible to also set attributes. The current API change is cool as it uses the existing method, but I worry that it might not fit that well if we expand the API in the future. So even if we don't add an option for setting an attribute as a part of solving this issue I would expect that others would request it in the future. And then when we need to add that and use the existing pattern of using the same method, we would simply use the second argument as the new value for the attribute which would read as: JSRuntime.InvokeVoidAsync("window.myVariable", newValue); But that would seem rather unintuitive as you would not be able to read from that method whether So I would suggest to add a method specifically for getting an attribute called |
I additionally want to point out that I called the suggested methods |
I didn't explicitly mention it, but setting a property would also work
If |
I think we should rather has API with which can explicitly express the intent. Because Regarding callbacks, so the JS function is also JS object. In Blazor it could get assigned a interop ID. So we could abuse |
JSFunction will be an object under the hood, they need to be tracked with an object id. |
It would behave the same way it has behaved since the beginning; if foo is a function, it will invoke it, if not, it'll set the value. This currently fails if Today you have to write wrappers around all these scenarios, with the proposed changes, 99% of those scenarios don't require a wrapper, for the potential 1% that remains, you can still write the wrapper. |
Extending the JS interop solutionI wrote the following notes while working on extending the current interop capabilities (from .NET to JS) in order to document issues I ran into, and to prepare for discussion about how to deal with them. 1. GoalsWe want to add support for these features:
Note: In this version, we only deal with the asynchronous API and ignore the synchronous API available via 2. Current stateAt the moment, we support calling JS functions from .NET. This is covered in the existing API by The invoked function can:
When adding support for other operations over interop, we need to know if we can (and want) to cover them with the existing 3. Invoking constructorsCurrently, it is not possible to invoke a JS function with The goal is to support invocation such as: // Using the existing API
var urlObj = JSRuntime.InvokeAsync<IJSObjectReference>("URL", "https://www.example.com"); The problem with overloading the existing API is that, there seems to be no dependable way of differentiating a "regular" function from a constructor function. More precisely, as summarized in this post:
The first two cases are simple. However, the third case means we can't determine, in general, just by examining the function object if we should invoke a resolved function directly or with This can be easily demonstrated using the common approaches for determining if a function is a constructor (is constructible). One such approach is based on checking the function's window.f = function(num) { return num * 2; }
console.log(!!(f.prototype && f.prototype.constructor === f)) // true
console.log(new f(1)) // window.f { prototype: ... }
console.log(f(1)) // 2 If we would determine At the same time we most likely don't want to call the following function window.cat = function(name) { this.name = name; }
console.log(!!(cat.prototype && cat.prototype.constructor === cat)) // true
console.log(new cat("Tom")) // window.cat { name: "Tom" }
console.log(cat("Tom")) // undefined Other approaches, namely using Proxy objects or invoking the function with Therefore, it seems necessary to extend the interop API. If we don't rely on just We can let users express their intent by using e.g. 4. Getting and setting property valuesUnlike with constructors, we could cover reading and writing property values using var url = await JSRuntime.InvokeAsync<string>("window.location.href");
await JSRuntime.InvokeAsync("window.location.href", "http://www.google.com"); The benefit is the ease of implementation: We can both reuse the existing API methods and not need to modify the interop infrastructure (e.g. functions like An alternative would be to introduce new dedicated API methods such as: var currentTitle = await JSRuntime.GetValueAsync<string>("document.title");
var name = await catReference.GetValueAsync<string>("name");
await JSRuntime.SetValueAsync("document.title", "Brave new title");
await catReference.SetValueAsync("name", "Tom"); Arguments for such extension include:
We can illustrate the last point. First, it is not uncommon that JS libraries have APIs which support a direct value or a callback function that provides the value. For example: interface SomeType {
x: number | () => number;
}
function computeX() { ... }
const obj: SomeType = {
x: computeX
} Now, when // This just invokes the callback and ignores the result
await JSRuntime.InvokeAsync("obj.x", 100);
// This would be unambiguous and would change the value of obj.x
await JSRuntime.SetValueAsync("obj.x", 100); Second, with separate // Lets say someFunc returns a function
var refToSomeFunc = await JSRuntime.GetValueAsync<IJSFunctionReference>("someFunc");
var refToSomeFuncResult = await JSRuntime.InvokeAsync<IJSFunctionReference>("someFunc"); We could also enable cleanly getting value of the entire object from its var catModel = await catReference.GetValueAsync<CatModel>(); This could be implemented with 5. Function referencesThis area needs to be specified more. There are multiple open questions:
6. Summary
|
@oroztocil really good exploration of the issue! The things you point out align well with what others have also pointed out regarding separate methods/overloads for the new features. Related to the function references topic I also think we should consider what an |
@oroztocil great write-up. I'm convinced by your arguments, so let's go with the more explicit approach that includes new APIs. I was hoping that we could make this work without having to use different APIs, but I don't think the complexity it adds is worth it. So as a summary:
Other considerations:
Overall, this looks great. I think we can break this down into at least three separate PRs:
|
I'm not sure about synchronous versions. Is it's worth it just for completeness sake ? Synchronous versions would be only supported with the single-threaded runtime.
Are we also considering callbacks (implemented in C#) ? When the function (and it's closure) is registered with a interop handle, when it would be collected ? |
The text was updated successfully, but these errors were encountered: