-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Description
Summary
This is a prerequisite for completing the JS object reference work.
Rather than rely on specific combinations of parameters and invocation semantics being hardcoded in the dotnet.wasm runtime, we should replace the underlying transport with a simpler, more flexible mechanism. This will give us the ability to extend it for JS object reference, and any other enhancements we want in the future, without needing more runtime changes.
Motivation
Today, Blazor WebAssembly JS interop is achieved via two InternalCall methods, whose JavaScript implementations are hardcoded inside the runtime here and here. These two JS methods declare specific combinations of parameters for sync vs async calls and also know about marshalled vs unmarshalled calls.
Having this logic in the runtime limits flexibility for us when working in the aspnetcore repo. For example, as part of the JS object reference work, we'll need to add another parameter such as targetObjectId, but:
- It's inconvenient to thread this extra param through lots of layers, including working across repos
- In the case of unmarshalled interop, it's not clear we even can add another parameter. Historically, the Emscripten internal calls were limited to a maximum of 5 params, and
mono_wasm_invoke_js_unmarshalledalready uses 5 (not sure if the param count limit has been raised since). We could work around this in the marshalled case by repurposingargsJsonto hold arbitrary framework-controlled metadata with the true args embedded inside it, but there's no such flexibility for unmarshalled interop.
Rather than fighting this every time we want to extend JS interop, I propose a different and simpler way of transporting the interop calls, which unifies the sync/async cases and the marshalled/unmarshalled cases, plus lets us pass through arbitrary unlimited metadata which can be changed purely within the aspnetcore repo without needing runtime changes. To enable this, I've already made and merged a PR in the runtime repo. What's left is for us to wire this up to Blazor.
Goals
- Be able to add an extra param for JS object reference in all interop forms (sync/async/unmarshalled)
- Be free to make other arbitrary changes to what info we provide, and what are the semantics of the calls, in the future without needing runtime changes. Example: to add another flag saying the return value should not be JSON serialized.
- Exactly retain the semantics of sync/async/unmarshalled interop calls (e.g., parameter formats, async completion flow, error handling), plus no meaningful extra overhead on unmarshalled calls.
- Don't change any public APIs. This is an internal implementation change only.
Non-goals
- Retaining back-compat with any code relying on the private
InternalCallmethods (e.g., through private reflection).
Scenarios
This should work with all the flavors of JS interop: sync, async, unmarshalled.
Risks
- We might accidentally change some very subtle detail about the semantics of the calls. TBH I think we won't because this should be a pretty mechanical change.
Interactions with other features
Although JS interop is used extensively throughout Blazor and by user code, since we intend to retain the existing semantics exactly, there should be no impact on any other feature.
This does not affect or touch JS interop in Blazor Server in any way at all.