-
Notifications
You must be signed in to change notification settings - Fork 10.4k
[Blazor][WebView] Abstractions #30456
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
Conversation
|
d7fbb6d
to
01dffd1
Compare
@javiercn @Eilon I had to rebase this onto |
Sounds good to me |
private readonly Dictionary<string, int> _componentIdBySelector = new(); | ||
private readonly Dispatcher _dispatcher; | ||
private readonly IpcSender _ipcSender; | ||
private long nextRenderBatchId = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this here?
For Blazor Server I can understand, but for Blazor WebView I think we can rely on a simple ACK without having to keep track of the id
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It just helps us discover more quickly if we have any bugs related to out-of-order delivery.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can this situation arise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can have bugs, for example something to do with how we dispatch the messages or executions onto the dispatcher, which might allow for things to get out of order. It shouldn't happen, but there's no harm making sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW in my experience I've never seen such a bug while working on MBB and haven't seen it from customers, but perhaps it can more easily be an issue while we develop the code, so it seems like a good precaution. I cannot imagine such a bug would be easy to debug when things simply explode and behave oddly.
if (message != null && message.StartsWith(_ipcMessagePrefix, StringComparison.Ordinal)) | ||
{ | ||
var messageAfterPrefix = message.AsSpan(_ipcMessagePrefix.Length); | ||
var parsed = (JsonElement[])JsonSerializer.Deserialize(messageAfterPrefix, typeof(JsonElement[]), JsonSerializerOptionsProvider.Options); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something to think about here is that we are double-serializing JSON data when we do JS Interop
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I agree. I think we can make this interop mechanism more low-level and performant later if we want to. Hopefully it's all very much contained to this one file (and the equivalent on the .ts
side) so it should be easy to reason about any changes.
public IpcReceiver(WebViewManager manager) | ||
{ | ||
_manager = manager; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should reconsider the direction of this dependency.
The WebViewManager creates and manages the IPCReceiver, but then the receiver calls back to the manager and both reference each other.
I think we might be better of if we follow an alternative approach:
- Handle initialization outside the IpcReceiver.
- Instead of dispatching the "AttachToPage" we expose an "Initialize" event that the manager subscribes to from outside.
That way all interactions always go Manager -> (IPCXXX) and there is no coupling both ways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely, sounds good to me! I agree it's a bit unexpected that it relies on an internal
method at the moment. The only drawback that concerns me about using an event is that it's hard to deal with asynchrony and exceptions, but we can figure something out.
|
||
namespace Microsoft.AspNetCore.Components.WebView.WebView2 | ||
{ | ||
public class WebView2WebViewManager : WebViewManager |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At some point we'll need to decide whether we want all this stuff to be public. It's not really an interesting part of the API surface from a public consumption perspective, yet it allows us to have common code between WPF, WinForms, and .NET MAUI.
No need to decide anything now. Keeping this in a separate assembly will help us ensure we have good layering for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, the layering is fine for now.
Later on we should consider replacing the M.A.C.WebView.WebView2 package with use of a shared-source file for the WebView2WebViewManager
abstraction. It would make no difference to functionality, but would mean we just don't have to ship an extra publicly-consumable package for what is intended as an internal abstraction.
await _currentPageContext.Renderer.AddRootComponentAsync( | ||
rootComponent.ComponentType, | ||
selector, | ||
rootComponent.Parameters); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about automatically re-rendering the components in this situation.
There's a chance that rootComponent.Parameters contains objects that have been mutated and given that we might go to a different host page, there is a chance that the same root components might not apply to the new page?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have more of an end-to-end scenario in mind that would be a good example of this?
I would expect in most cases people won't use the root-component parameters at all, and in the cases where they do, it's so that different host pages can have different global settings, or so that you could have a single root component shared across Blazor WebAssembly/Server/WebView and pass them some info or flags that differ based on the hosting environment. In these scenarios it wouldn't be something you'd try to mutate, and if you did try and nothing happened, that seems like it would be fine.
given that we might go to a different host page
As it stands, the BlazorWebView
controls only let you configure a single fixed host page per control, so the scenario doesn't apply. You can write to the HostPage
property on the control, but once it's initialized, it ignores any such changes. We might want to make it throw if you try to write to that property after initialization.
protected bool TryGetResponseContent(string uri, bool isPageLoad, out int statusCode, out string statusMessage, out Stream content, out string headers) | ||
=> _staticContentProvider.TryGetResponseContent(uri, isPageLoad, out statusCode, out statusMessage, out content, out headers); | ||
|
||
internal async Task AttachToPageAsync(string baseUrl, string startUrl) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, "spicy" thought is that we need to have a "root component" SetParameters API here to dispatch parameter updates to top-level components.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, if this becomes a common scenario then I definitely agree. However the usability of mutating root component parameters from a WPF/WinForms control might not be great, and there will always be alternatives like sending commands via some DI service. I'd suggest we wait to see some specific customer demand here before implementing this.
@javiercn @Eilon We should decide what milestone we're trying to reach before merging this into What are the remaining pieces we want to have in before merging?
Anything else? |
@SteveSandersonMS spot on. BTW for WinForms I took the lazy route and used your RootComponent type. Certainly not meant to be a final API, so thanks for updating it! Regarding merging into main: Let's discuss today and we should easily be able to assign tasks to get them done, especially the more trivial ones (headers, doc comments, etc.). |
We have a few projects that can only be built in |
Should be equivalent functionality and limitations to WPF.
* Layering cleanups * Tests and bug fixes
cedd707
to
7a0be28
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#yolo
I retroactively sign off 😀 |
Hi @Eilon. It looks like you just commented on a closed PR. The team will most probably miss it. If you'd like to bring something important up to their attention, consider filing a new issue and add enough details to build context. |
WIP This is the initial setup for the Blazor abstractions for running on a WebView. Just sharing the branch for now, details and a design write-up to come a bit later.
The goal is to provide the fundamental primitives for Blazor WebView as well as a "Headless" webview that we can use in our unit tests.