dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.24k stars 1.76k forks source link

[Enhancement] Updating WebView control to support WebView applications #1148

Open odhanson opened 3 years ago

odhanson commented 3 years ago

Summary

Blazor desktop applications use today their own implementation of cross platform WebViews inside BlazorWebView control. My team is working on an alternative solution for WebView desktop applications which uses React and TypeScript for the client side. In order to share the same infrastructure to display Web content inside a WebView control, I propose to introduce the missing functionality for the exiting WebView control (or introduce a new control) and then refactor BlazorWebView to use it.

API Changes

I propose the following API changes to WebView:

MessageReceived event:

The MessageReceived will be used to allow JavaScript code to send string messages to the C# host. The string message could be anything, but would typically be an application specific JSON string.

public event EventHandler<WebMessageReceivedEventArgs> MessageReceived;

Where WebMessageReceivedEventArgs consists of:

public class WebMessageReceivedEventArgs : EventArgs
{
  public WebMessageReceivedEventArgs(string value)
  {
  }

  public string Message { get; private set; }
}

This will allow developers to register for the event, and listen to incoming string messages posted from the JavaScript runtime. For each platform, we will inject JavaScript code into the WebView that will setup a postMessage method for the the client side. The developers can send messages to the host via JavaScript in the following way:

window.host.postMessage('This is my message');

Custom URI scheme

In most cases, a WebView application will want to embed the web client html/js/css files inside the application assembly. We would allow the developer to introduces a custom Uri scheme. For example: ms-webview-app://myapp/index.htm

For that we will need to provide some WebViewConfiguration that can be passed when we create the control (is that even possible in MAUI ? I didn't find something similar). Alternatively, we can expose WebViewConfiguration as a get only property of WebView, and then they can be configured from within the XAML

    public class WebViewConfiguration
    {
        /// <summary>
        /// Controls whether communication from the host to the webview's top level HTML document is allowed via window.host.postMessage
        /// and ScriptNotify event is enabled
        /// </summary>
        public bool EnableMessages { get; set; }

        /// <summary>
        /// Controls whether the user is able to use the context menu or keyboard shortcuts to open the DevTools window.
        /// </summary>
        public bool EnableDevTools { get; set;}

        /// <summary>
        /// The URI Scheme to use
        /// </summary>
        public string UriScheme { get; set;}

        /// <summary>
        /// Used to allow the host to resolve Uri requests into content streams
        /// </summary>
        public IUriToStreamResolver? UriToStreamResolver { get; set;}
    }

This will allow the developer to register a custom URI (for example: "ms-webview-app") and also register a resolver which will be called when someone navigates to a URI that uses that scheme. IUriToStreamResolver is defined as:

    public interface IUriToStreamResolver
    {
        bool TryGetStreamFromUri(Uri uri, [NotNullWhen(true)] out Stream? stream);
    }

This will allow the developer to lookup the requested Uri and return a stream as the response.

Once set up, we just need to call LoadUrl("ms-webview-app://myapp/index.htm").

Additional properties

We might need to add additional properties that will allow us to configure the WebView and disable different features that don't make sense in WebView applications:

IsZoomControlEnabled
IsStatusBarEnabled
AreDefaultContextMenusEnabled
AreDefaultScriptDialogsEnabled
IsWebMessageEnabled

I have not done enough research for all the previous properties. I know they are supported in WebView2. Alternatively, we might just add a single property IsWebViewAppEnabled which will configure and lock down the WebView control per platform.

Async methods

The methods in IWebViewDelegate are synchronous. We most probably want to add the following methods:

Task LoadHtmlAsync(string html, string baseUrl);
Task LoadUrlAsync(string url);

So we don't block the UI thread while these load in the background.

Intended Use Case

As detailed in the summary, BlazorWebView is a great example of a use case. Right now its implemented as a standalone control, and is using platform specific WebView controls as it's underlying implementation. An alternative technology to Blazor might be React. Today, there are already many existing projects that have already developed their client side code using TS/JS and react (or Angular, or Vue, etc.) while the backend is developed in C# (using ASP.NET for example). It would be great to offer an option to develop cross platform desktop applications based on the same codebase. The UI will share the same Web client code (with adaptions of course where needed), while all the business logic can run in C# sharing the same logic and libraries used by the application backed code running in the Web server.

In both cases (Blazor, and it's alternatives), the underlying WebView controls can and should be identical

odhanson commented 3 years ago

cc: @Eilon

luismts commented 3 years ago

For the host communication, there is something similar that I used a long time ago but is not getting love right now.

Here an example:

/// <summary>
/// Bind an action to a Javascript function
/// </summary>
WebView webView = new WebView();
webView.AddLocalCallback("test", (str) => Debug.WriteLine(str));
webView.RemoveLocalCallback("test");

You can check it here.

vhugogarcia commented 3 years ago

This gonna be huge and fun!!! Love it.

juanfranblanco commented 2 years ago

I don't know if this fits here, but I would like to be able to use the host to lazy load the .net dlls (if kept locally) and implement some level of caching, that is not dependent on the browser storage.

Eilon commented 2 years ago

@juanfranblanco generally in a .NET MAUI app the DLLs are not loaded into the WebView. That would require using WebAssembly and a .NET runtime/interpreter that can run in the browser (such as what Blazor WASM can do), but that wouldn't usually be used in .NET MAUI. In the case of Blazor and .NET MAUI, the .NET code DLLs are run natively in the process and not in the browser.

As far as lazy loading .NET DLLs in .NET MAUI apps, that would be a general .NET MAUI topic that I think would be similar to how to do it anywhere in .NET.

juanfranblanco commented 2 years ago

@Eilon Yes, I was thinking of the webview hosting an external web assembly app, as opposed to doing the hybrid approach, (what you have created). So in this scenario it will be a normal web assembly app, rendered in the webview but some assemblies lazy loaded / cached from the host. The same principle can be applied to other types of assets like an image, etc. To start with, having the capability to intercept requests / responses like in CEF could do. Then I can easily build from there.

Eilon commented 2 years ago

@juanfranblanco ah got it. I don't think that was one of the original ideas for this issue, but it's certainly interesting. I think the core idea in this issue is that the .NET code runs natively and needs an easy way to interact with the JS code in the web view. This issue offers no particular perspective about what that JS code in the webview does or how it was built. Of course, that JS code could really just be WASM code that is based on .NET (or whatever else).

So, while it's certainly an interesting idea, I think at present it is outside the scope of this issue. But, then again, no one has started any work on this issue, so the scope isn't super clear anyway 😁

juanfranblanco commented 2 years ago

Of course, that JS code could really just be WASM code that is based on .NET (or whatever else). Yes, eventually it could be also a mixture of wasm, .net assemblies, etc.

Eilon commented 1 year ago

Hi everyone, I started an experimental control that you can check out here: https://github.com/dotnet/maui/discussions/12009

Please let me know your feedback!

fairking commented 1 year ago

I had a long discussion with @Eilon about the Hybrid Apps approach. I even had an example which was working fine on Windows, but not on Android. I gave up, and for last 6 months I am working with completely different framework and language and technology. Yes guys, I moved to somewhere else, because I felt nobody cares about hybrid apps in dotnet team. There are few small changes/fixes needed to be done.

I think it could be useful for everyone to look at my example I built long time ago: https://github.com/fairking/napos_maui

The main issue was there (android only): https://github.com/fairking/napos_maui/blob/main/Napos.UI/src/boot/settings.ts

God bless you guys and good luck with dotnet.

akhanalcs commented 1 year ago

@fairking Have you checked Eilon's latest Hybrid app example where he integrates an existing ReactJS app into MAUI? It's very cool! https://github.com/dotnet/maui/discussions/12009#discussioncomment-4482103