-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
.NET MAUI BlazorWebView control for Android, iOS/MacCatalyst, and Win…
…UI (#654) * BlazorWebView control for .NET MAUI: - Supports Android, iOS, and WinUI - Samples updates to show simple Blazor demo * Revert more sample changes for cleanup * Update samples * Shorten folder names * Code cleanup * Sample cleanup * Update non-net6 project/SLN * Revert "Update non-net6 project/SLN" This reverts commit cc6be87. * Skip Blazor for non-.NET6 samples * Update BlazorPage.cs * Rebase on 'main' and update sample to match * Added BlazorWebView project to the winui sln * Remove scrollview for now Co-authored-by: Jonathan Dick <[email protected]>
- Loading branch information
Showing
36 changed files
with
1,677 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
src/BlazorWebView/src/core/Android/AndroidWebKitWebViewManager.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
using Android.Webkit; | ||
using AWebView = Android.Webkit.WebView; | ||
using Microsoft.Extensions.FileProviders; | ||
using System; | ||
using System.IO; | ||
|
||
namespace Microsoft.AspNetCore.Components.WebView.Maui | ||
{ | ||
/// <summary> | ||
/// An implementation of <see cref="WebViewManager"/> that uses the Android WebKit WebView browser control | ||
/// to render web content. | ||
/// </summary> | ||
public class AndroidWebKitWebViewManager : WebViewManager | ||
{ | ||
// Using an IP address means that WebView doesn't wait for any DNS resolution, | ||
// making it substantially faster. Note that this isn't real HTTP traffic, since | ||
// we intercept all the requests within this origin. | ||
private const string AppOrigin = "https://0.0.0.0/"; | ||
private static readonly Android.Net.Uri AndroidAppOriginUri = Android.Net.Uri.Parse(AppOrigin)!; | ||
private readonly BlazorWebViewHandler _blazorWebViewHandler; | ||
private readonly AWebView _webview; | ||
|
||
/// <summary> | ||
/// Constructs an instance of <see cref="AndroidWebKitWebViewManager"/>. | ||
/// </summary> | ||
/// <param name="webview">A wrapper to access platform-specific WebView APIs.</param> | ||
/// <param name="services">A service provider containing services to be used by this class and also by application code.</param> | ||
/// <param name="dispatcher">A <see cref="Dispatcher"/> instance that can marshal calls to the required thread or sync context.</param> | ||
/// <param name="fileProvider">Provides static content to the webview.</param> | ||
/// <param name="hostPageRelativePath">Path to the host page within the <paramref name="fileProvider"/>.</param> | ||
public AndroidWebKitWebViewManager(BlazorWebViewHandler blazorMauiWebViewHandler, AWebView webview, IServiceProvider services, Dispatcher dispatcher, IFileProvider fileProvider, string hostPageRelativePath) | ||
: base(services, dispatcher, new Uri(AppOrigin), fileProvider, hostPageRelativePath) | ||
{ | ||
_blazorWebViewHandler = blazorMauiWebViewHandler ?? throw new ArgumentNullException(nameof(blazorMauiWebViewHandler)); | ||
_webview = webview ?? throw new ArgumentNullException(nameof(webview)); | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected override void NavigateCore(Uri absoluteUri) | ||
{ | ||
_webview.LoadUrl(absoluteUri.AbsoluteUri); | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected override void SendMessage(string message) | ||
{ | ||
_webview.PostWebMessage(new WebMessage(message), AndroidAppOriginUri); | ||
} | ||
|
||
internal bool TryGetResponseContentInternal(string uri, bool allowFallbackOnHostPage, out int statusCode, out string statusMessage, out Stream content, out string headers) => | ||
TryGetResponseContent(uri, allowFallbackOnHostPage, out statusCode, out statusMessage, out content, out headers); | ||
//internal bool TryGetResponseContentInternal(string uri, bool allowFallbackOnHostPage, out int statusCode, out string statusMessage, out Stream content, out IDictionary<string, string> headers) => | ||
// TryGetResponseContent(uri, allowFallbackOnHostPage, out statusCode, out statusMessage, out content, out headers); | ||
|
||
internal void SetUpMessageChannel() | ||
{ | ||
var nativeToJsPorts = _webview.CreateWebMessageChannel(); | ||
|
||
var nativeToJs = new BlazorWebMessageCallback(message => | ||
{ | ||
MessageReceived(new Uri(AppOrigin), message!); | ||
}); | ||
|
||
var destPort = new[] { nativeToJsPorts[1] }; | ||
|
||
nativeToJsPorts[0].SetWebMessageCallback(nativeToJs); | ||
|
||
_webview.PostWebMessage(new WebMessage("capturePort", destPort), AndroidAppOriginUri); | ||
} | ||
|
||
private class BlazorWebMessageCallback : WebMessagePort.WebMessageCallback | ||
{ | ||
private readonly Action<string?> _onMessageReceived; | ||
|
||
public BlazorWebMessageCallback(Action<string?> onMessageReceived) | ||
{ | ||
_onMessageReceived = onMessageReceived ?? throw new ArgumentNullException(nameof(onMessageReceived)); | ||
} | ||
|
||
public override void OnMessage(WebMessagePort? port, WebMessage? message) | ||
{ | ||
if (message is null) | ||
{ | ||
throw new ArgumentNullException(nameof(message)); | ||
} | ||
|
||
_onMessageReceived(message.Data); | ||
} | ||
} | ||
} | ||
} |
121 changes: 121 additions & 0 deletions
121
src/BlazorWebView/src/core/Android/BlazorWebViewHandler.Android.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
using Android.Webkit; | ||
using static Android.Views.ViewGroup; | ||
using AWebView = Android.Webkit.WebView; | ||
using Microsoft.Extensions.FileProviders; | ||
using Microsoft.Maui.Handlers; | ||
using System; | ||
using System.Collections.ObjectModel; | ||
using Path = System.IO.Path; | ||
|
||
namespace Microsoft.AspNetCore.Components.WebView.Maui | ||
{ | ||
public partial class BlazorWebViewHandler : ViewHandler<IBlazorWebView, AWebView> | ||
{ | ||
private WebViewClient? _webViewClient; | ||
private WebChromeClient? _webChromeClient; | ||
private AndroidWebKitWebViewManager? _webviewManager; | ||
internal AndroidWebKitWebViewManager? WebviewManager => _webviewManager; | ||
|
||
protected override AWebView CreateNativeView() | ||
{ | ||
var aWebView = new AWebView(Context!) | ||
{ | ||
#pragma warning disable 618 // This can probably be replaced with LinearLayout(LayoutParams.MatchParent, LayoutParams.MatchParent); just need to test that theory | ||
LayoutParameters = new Android.Widget.AbsoluteLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent, 0, 0) | ||
#pragma warning restore 618 | ||
}; | ||
|
||
if (aWebView.Settings != null) | ||
{ | ||
aWebView.Settings.JavaScriptEnabled = true; | ||
aWebView.Settings.DomStorageEnabled = true; | ||
} | ||
|
||
_webViewClient = GetWebViewClient(); | ||
aWebView.SetWebViewClient(_webViewClient); | ||
|
||
_webChromeClient = GetWebChromeClient(); | ||
aWebView.SetWebChromeClient(_webChromeClient); | ||
|
||
return aWebView; | ||
} | ||
|
||
protected override void DisconnectHandler(AWebView nativeView) | ||
{ | ||
nativeView.StopLoading(); | ||
|
||
_webViewClient?.Dispose(); | ||
_webChromeClient?.Dispose(); | ||
} | ||
|
||
private bool RequiredStartupPropertiesSet => | ||
//_webview != null && | ||
HostPage != null && | ||
Services != null; | ||
|
||
private string? HostPage { get; set; } | ||
private ObservableCollection<RootComponent>? RootComponents { get; set; } | ||
private new IServiceProvider? Services { get; set; } | ||
|
||
private void StartWebViewCoreIfPossible() | ||
{ | ||
if (!RequiredStartupPropertiesSet || | ||
false)//_webviewManager != null) | ||
{ | ||
return; | ||
} | ||
if (NativeView == null) | ||
{ | ||
throw new InvalidOperationException($"Can't start {nameof(BlazorWebView)} without native web view instance."); | ||
} | ||
|
||
var resourceAssembly = RootComponents?[0]?.ComponentType?.Assembly; | ||
if (resourceAssembly == null) | ||
{ | ||
throw new InvalidOperationException($"Can't start {nameof(BlazorWebView)} without a component type assembly."); | ||
} | ||
|
||
// We assume the host page is always in the root of the content directory, because it's | ||
// unclear there's any other use case. We can add more options later if so. | ||
var contentRootDir = Path.GetDirectoryName(HostPage) ?? string.Empty; | ||
var hostPageRelativePath = Path.GetRelativePath(contentRootDir, HostPage!); | ||
var fileProvider = new ManifestEmbeddedFileProvider(resourceAssembly, root: contentRootDir); | ||
|
||
_webviewManager = new AndroidWebKitWebViewManager(this, NativeView, Services!, MauiDispatcher.Instance, fileProvider, hostPageRelativePath); | ||
if (RootComponents != null) | ||
{ | ||
foreach (var rootComponent in RootComponents) | ||
{ | ||
// Since the page isn't loaded yet, this will always complete synchronously | ||
_ = rootComponent.AddToWebViewManagerAsync(_webviewManager); | ||
} | ||
} | ||
|
||
_webviewManager.Navigate("/"); | ||
} | ||
|
||
protected virtual WebViewClient GetWebViewClient() => | ||
new WebKitWebViewClient(this); | ||
|
||
protected virtual WebChromeClient GetWebChromeClient() => | ||
new WebChromeClient(); | ||
|
||
public static void MapHostPage(BlazorWebViewHandler handler, IBlazorWebView webView) | ||
{ | ||
handler.HostPage = webView.HostPage; | ||
handler.StartWebViewCoreIfPossible(); | ||
} | ||
|
||
public static void MapRootComponents(BlazorWebViewHandler handler, IBlazorWebView webView) | ||
{ | ||
handler.RootComponents = webView.RootComponents; | ||
handler.StartWebViewCoreIfPossible(); | ||
} | ||
|
||
public static void MapServices(BlazorWebViewHandler handler, IBlazorWebView webView) | ||
{ | ||
handler.Services = webView.Services; | ||
handler.StartWebViewCoreIfPossible(); | ||
} | ||
} | ||
} |
Oops, something went wrong.