From 35f637ee0371044f1df1f6ea5092ef69e8a14215 Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Tue, 15 Feb 2022 16:11:46 -0800 Subject: [PATCH] PR Feedback --- Microsoft.Maui.sln | 1 + .../Android/AndroidWebKitWebViewManager.cs | 8 ++-- .../Android/BlazorWebViewHandler.Android.cs | 2 +- .../src/Maui/Android/WebKitWebViewClient.cs | 19 ++++---- src/BlazorWebView/src/Maui/BlazorWebView.cs | 11 ++++- .../src/Maui/BlazorWebViewHandler.cs | 12 ++++- .../src/Maui/ExternalLinkMode.cs | 8 ---- src/BlazorWebView/src/Maui/IBlazorWebView.cs | 6 +++ .../Windows/BlazorWebViewHandler.Windows.cs | 2 +- .../src/Maui/Windows/WinUIWebViewManager.cs | 46 ++++++++----------- .../src/Maui/iOS/BlazorWebViewHandler.iOS.cs | 2 +- .../src/Maui/iOS/IOSWebViewManager.cs | 12 ++--- .../src/SharedSource/ExternalLinkMode.cs | 21 +++++++++ .../SharedSource/WebView2WebViewManager.cs | 31 +++++++++++-- 14 files changed, 116 insertions(+), 65 deletions(-) delete mode 100644 src/BlazorWebView/src/Maui/ExternalLinkMode.cs create mode 100644 src/BlazorWebView/src/SharedSource/ExternalLinkMode.cs diff --git a/Microsoft.Maui.sln b/Microsoft.Maui.sln index 4f5ce334b08a..e5aa887bedfe 100644 --- a/Microsoft.Maui.sln +++ b/Microsoft.Maui.sln @@ -200,6 +200,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiBlazorWebView.DeviceTes EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SharedSource", "SharedSource", "{4F2926C8-43AB-4328-A735-D9EAD699F81D}" ProjectSection(SolutionItems) = preProject + src\BlazorWebView\src\SharedSource\ExternalLinkMode.cs = src\BlazorWebView\src\SharedSource\ExternalLinkMode.cs src\BlazorWebView\src\SharedSource\QueryStringHelper.cs = src\BlazorWebView\src\SharedSource\QueryStringHelper.cs src\BlazorWebView\src\SharedSource\WebView2WebViewManager.cs = src\BlazorWebView\src\SharedSource\WebView2WebViewManager.cs EndProjectSection diff --git a/src/BlazorWebView/src/Maui/Android/AndroidWebKitWebViewManager.cs b/src/BlazorWebView/src/Maui/Android/AndroidWebKitWebViewManager.cs index 51cdbc9c80ad..d602ded634ec 100644 --- a/src/BlazorWebView/src/Maui/Android/AndroidWebKitWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Android/AndroidWebKitWebViewManager.cs @@ -18,9 +18,8 @@ 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 string AppOrigin = $"https://{BlazorWebView.AppHostAddress}/"; private static readonly AUri AndroidAppOriginUri = AUri.Parse(AppOrigin)!; - private readonly BlazorWebViewHandler _blazorWebViewHandler; private readonly AWebView _webview; /// @@ -31,11 +30,10 @@ public class AndroidWebKitWebViewManager : WebViewManager /// A instance that can marshal calls to the required thread or sync context. /// Provides static content to the webview. /// Path to the host page within the . - public AndroidWebKitWebViewManager(BlazorWebViewHandler blazorMauiWebViewHandler, AWebView webview, IServiceProvider services, Dispatcher dispatcher, IFileProvider fileProvider, JSComponentConfigurationStore jsComponents, string hostPageRelativePath) + public AndroidWebKitWebViewManager(AWebView webview!!, IServiceProvider services, Dispatcher dispatcher, IFileProvider fileProvider, JSComponentConfigurationStore jsComponents, string hostPageRelativePath) : base(services, dispatcher, new Uri(AppOrigin), fileProvider, jsComponents, hostPageRelativePath) { - _blazorWebViewHandler = blazorMauiWebViewHandler ?? throw new ArgumentNullException(nameof(blazorMauiWebViewHandler)); - _webview = webview ?? throw new ArgumentNullException(nameof(webview)); + _webview = webview; } /// diff --git a/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs b/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs index 20e34829be2d..c0f881e2070d 100644 --- a/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs +++ b/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs @@ -88,7 +88,7 @@ private void StartWebViewCoreIfPossible() var fileProvider = VirtualView.CreateFileProvider(contentRootDir); - _webviewManager = new AndroidWebKitWebViewManager(this, NativeView, Services!, ComponentsDispatcher, fileProvider, VirtualView.JSComponents, hostPageRelativePath); + _webviewManager = new AndroidWebKitWebViewManager(NativeView, Services!, ComponentsDispatcher, fileProvider, VirtualView.JSComponents, hostPageRelativePath); if (RootComponents != null) { diff --git a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs index dff58bc84c0d..3eed7ea74b7f 100644 --- a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs +++ b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs @@ -9,13 +9,16 @@ namespace Microsoft.AspNetCore.Components.WebView.Maui { internal class WebKitWebViewClient : WebViewClient { - private const string AppOrigin = "https://0.0.0.0/"; + // 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 static readonly string AppOrigin = $"https://{BlazorWebView.AppHostAddress}/"; private readonly BlazorWebViewHandler? _webViewHandler; - public WebKitWebViewClient(BlazorWebViewHandler webViewHandler) + public WebKitWebViewClient(BlazorWebViewHandler webViewHandler!!) { - _webViewHandler = webViewHandler ?? throw new ArgumentNullException(nameof(webViewHandler)); + _webViewHandler = webViewHandler; } protected WebKitWebViewClient(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) @@ -34,7 +37,7 @@ public override bool ShouldOverrideUrlLoading(AWebView? view, IWebResourceReques { var uri = new Uri(requestUri); - if (uri.Host == "0.0.0.0" && + if (uri.Host == BlazorWebView.AppHostAddress && view is not null && request is not null && request.IsRedirect && @@ -43,8 +46,8 @@ request is not null && view.LoadUrl(uri.ToString()); return true; } - else if (uri.Host != "0.0.0.0" && - _webViewHandler is not null && + else if (uri.Host != BlazorWebView.AppHostAddress && + _webViewHandler != null && _webViewHandler.ExternalLinkMode == ExternalLinkMode.OpenInExternalBrowser) { var intent = new Intent(Intent.ActionView, AUri.Parse(requestUri)); @@ -181,9 +184,9 @@ private class JavaScriptValueCallback : Java.Lang.Object, IValueCallback { private readonly Action _callback; - public JavaScriptValueCallback(Action callback) + public JavaScriptValueCallback(Action callback!!) { - _callback = callback ?? throw new ArgumentNullException(nameof(callback)); + _callback = callback; } public void OnReceiveValue(Java.Lang.Object? value) diff --git a/src/BlazorWebView/src/Maui/BlazorWebView.cs b/src/BlazorWebView/src/Maui/BlazorWebView.cs index 49ac0bf7e5df..6e3228b01ff8 100644 --- a/src/BlazorWebView/src/Maui/BlazorWebView.cs +++ b/src/BlazorWebView/src/Maui/BlazorWebView.cs @@ -1,11 +1,13 @@ -using System; -using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebView; using Microsoft.Extensions.FileProviders; namespace Microsoft.AspNetCore.Components.WebView.Maui { public class BlazorWebView : Microsoft.Maui.Controls.View, IBlazorWebView { + internal static readonly string AppHostAddress = "0.0.0.0"; + private readonly JSComponentConfigurationStore _jSComponents = new(); public BlazorWebView() @@ -19,6 +21,11 @@ public BlazorWebView() public RootComponentsCollection RootComponents { get; } + /// + /// Specify whether links should be opened in the external + /// system default browser, or within the webview. + /// + /// Defaults to opening links in the system default browser. public ExternalLinkMode ExternalLinkMode { get; set; } = ExternalLinkMode.OpenInExternalBrowser; /// diff --git a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs index 1f4a954b1705..d8ed9820d763 100644 --- a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs +++ b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs @@ -1,4 +1,5 @@ using System.Linq; +using Microsoft.AspNetCore.Components.WebView; using Microsoft.Maui; using Microsoft.Maui.Handlers; @@ -6,10 +7,11 @@ namespace Microsoft.AspNetCore.Components.WebView.Maui { public partial class BlazorWebViewHandler { - public static PropertyMapper BlazorWebViewMapper = new(ViewMapper) + private static readonly PropertyMapper BlazorWebViewMapper = new(ViewMapper) { [nameof(IBlazorWebView.HostPage)] = MapHostPage, [nameof(IBlazorWebView.RootComponents)] = MapRootComponents, + [nameof(IBlazorWebView.ExternalLinkMode)] = MapExternalLinkMode, }; public BlazorWebViewHandler() : base(BlazorWebViewMapper) @@ -28,7 +30,6 @@ public static void MapHostPage(BlazorWebViewHandler handler, IBlazorWebView webV { #if !NETSTANDARD handler.HostPage = webView.HostPage; - handler.ExternalLinkMode = webView.ExternalLinkMode; handler.StartWebViewCoreIfPossible(); #endif } @@ -37,6 +38,13 @@ public static void MapRootComponents(BlazorWebViewHandler handler, IBlazorWebVie { #if !NETSTANDARD handler.RootComponents = webView.RootComponents; + handler.StartWebViewCoreIfPossible(); +#endif + } + + public static void MapExternalLinkMode(BlazorWebViewHandler handler, IBlazorWebView webView) + { +#if !NETSTANDARD handler.ExternalLinkMode = webView.ExternalLinkMode; handler.StartWebViewCoreIfPossible(); #endif diff --git a/src/BlazorWebView/src/Maui/ExternalLinkMode.cs b/src/BlazorWebView/src/Maui/ExternalLinkMode.cs deleted file mode 100644 index ddaf57ef704e..000000000000 --- a/src/BlazorWebView/src/Maui/ExternalLinkMode.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Microsoft.AspNetCore.Components.WebView.Maui -{ - public enum ExternalLinkMode - { - OpenInExternalBrowser, - OpenInWebView - } -} diff --git a/src/BlazorWebView/src/Maui/IBlazorWebView.cs b/src/BlazorWebView/src/Maui/IBlazorWebView.cs index 7bd1672783f7..5050ae777886 100644 --- a/src/BlazorWebView/src/Maui/IBlazorWebView.cs +++ b/src/BlazorWebView/src/Maui/IBlazorWebView.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebView; using Microsoft.Extensions.FileProviders; using Microsoft.Maui; @@ -9,6 +10,11 @@ public interface IBlazorWebView : IView string? HostPage { get; set; } RootComponentsCollection RootComponents { get; } JSComponentConfigurationStore JSComponents { get; } + + /// + /// Specify whether links should be opened in the external + /// system default browser, or within the webview. + /// ExternalLinkMode ExternalLinkMode { get; set; } /// diff --git a/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs b/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs index 818e97aac615..378b5aeeaaa9 100644 --- a/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs +++ b/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs @@ -65,7 +65,7 @@ private void StartWebViewCoreIfPossible() VirtualView.JSComponents, hostPageRelativePath, contentRootDir, - ExternalLinkMode); + this); if (RootComponents != null) { diff --git a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs index f4a948ef0ec3..35d71abb5187 100644 --- a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs @@ -22,25 +22,23 @@ public class WinUIWebViewManager : WebView2WebViewManager private readonly WebView2Control _webview; private readonly string _hostPageRelativePath; private readonly string _contentRootDir; - private readonly ExternalLinkMode _externalLinkMode; + private readonly BlazorWebViewHandler _blazorWebViewHandler; public WinUIWebViewManager( - WebView2Control webview!!, + WebView2Control webview, IServiceProvider services, Dispatcher dispatcher, IFileProvider fileProvider, JSComponentConfigurationStore jsComponents, string hostPageRelativePath, string contentRootDir, - ExternalLinkMode externalLinkMode) + BlazorWebViewHandler blazorWebViewHandler) : base(webview, services, dispatcher, fileProvider, jsComponents, hostPageRelativePath) { _webview = webview; _hostPageRelativePath = hostPageRelativePath; _contentRootDir = contentRootDir; - _externalLinkMode = externalLinkMode; - - _webview.CoreWebView2Initialized += Webview_CoreWebView2Initialized; + _blazorWebViewHandler = blazorWebViewHandler; } protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRequestedEventArgs eventArgs) @@ -105,35 +103,18 @@ protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRe deferral.Complete(); } - protected override void QueueBlazorStart() - { - // In .NET MAUI we use autostart='false' for the Blazor script reference, so we start it up manually in this event - _webview.CoreWebView2.DOMContentLoaded += async (_, __) => - { - await _webview.CoreWebView2!.ExecuteScriptAsync(@" - Blazor.start(); - "); - }; - } - - private void Webview_CoreWebView2Initialized(WebView2Control sender, UI.Xaml.Controls.CoreWebView2InitializedEventArgs args) - { - _webview.CoreWebView2.NavigationStarting += CoreWebView2_NavigationStarting; - _webview.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested; - } - - private void CoreWebView2_NavigationStarting(CoreWebView2 sender, CoreWebView2NavigationStartingEventArgs args) + protected override void CoreWebView2_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs args) { if (Uri.TryCreate(args.Uri, UriKind.RelativeOrAbsolute, out var uri) && - uri.Host != "0.0.0.0" && - _externalLinkMode == ExternalLinkMode.OpenInExternalBrowser) + uri.Host != AppHostAddress && + _blazorWebViewHandler.ExternalLinkMode == ExternalLinkMode.OpenInExternalBrowser) { _ = Launcher.LaunchUriAsync(uri); args.Cancel = true; } } - private void CoreWebView2_NewWindowRequested(CoreWebView2 sender, CoreWebView2NewWindowRequestedEventArgs args) + protected override void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs args) { // Intercept _blank target tags to always open in device browser // regardless of ExternalLinkMode.OpenInWebview @@ -143,5 +124,16 @@ private void CoreWebView2_NewWindowRequested(CoreWebView2 sender, CoreWebView2Ne args.Handled = true; } } + + protected override void QueueBlazorStart() + { + // In .NET MAUI we use autostart='false' for the Blazor script reference, so we start it up manually in this event + _webview.CoreWebView2.DOMContentLoaded += async (_, __) => + { + await _webview.CoreWebView2!.ExecuteScriptAsync(@" + Blazor.start(); + "); + }; + } } } diff --git a/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs b/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs index 28f1dfc8a448..f792d2f73ce1 100644 --- a/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs +++ b/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs @@ -22,7 +22,7 @@ public partial class BlazorWebViewHandler : ViewHandler + /// Link handling mode for anchor tags ]]> within a Blazor WebView. + /// + /// `_blank` target links will always open in the default browser + /// regardless of this setting. + /// + public enum ExternalLinkMode + { + /// + /// Opens anchor tags ]]> in the system default browser. + /// + OpenInExternalBrowser, + + /// + /// Opens anchor tags ]]> in the WebView. This is not recommended. + /// + OpenInWebView + } +} diff --git a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs index 67c0caa51161..c077228cae6b 100644 --- a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs +++ b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs @@ -40,7 +40,8 @@ public class WebView2WebViewManager : WebViewManager // Using an IP address means that WebView2 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 protected const string AppOrigin = "https://0.0.0.0/"; + internal static readonly string AppHostAddress = "0.0.0.0"; + protected static readonly string AppOrigin = $"https://{AppHostAddress}/"; private readonly WebView2Control _webview; private readonly Task _webviewReadyTask; @@ -58,10 +59,16 @@ public class WebView2WebViewManager : WebViewManager /// A instance that can marshal calls to the required thread or sync context. /// Provides static content to the webview. /// Path to the host page within the . - public WebView2WebViewManager(WebView2Control webview, IServiceProvider services, Dispatcher dispatcher, IFileProvider fileProvider, JSComponentConfigurationStore jsComponents, string hostPageRelativePath) + public WebView2WebViewManager( + WebView2Control webview!!, + IServiceProvider services, + Dispatcher dispatcher, + IFileProvider fileProvider, + JSComponentConfigurationStore jsComponents, + string hostPageRelativePath) : base(services, dispatcher, new Uri(AppOrigin), fileProvider, jsComponents, hostPageRelativePath) { - _webview = webview ?? throw new ArgumentNullException(nameof(webview)); + _webview = webview; // Unfortunately the CoreWebView2 can only be instantiated asynchronously. // We want the external API to behave as if initalization is synchronous, @@ -94,11 +101,15 @@ private async Task InitializeWebView2() ApplyDefaultWebViewSettings(); _webview.CoreWebView2.AddWebResourceRequestedFilter($"{AppOrigin}*", CoreWebView2WebResourceContext.All); + _webview.CoreWebView2.WebResourceRequested += async (s, eventArgs) => { await HandleWebResourceRequest(eventArgs); }; + _webview.CoreWebView2.NavigationStarting += CoreWebView2_NavigationStarting; + _webview.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested; + // The code inside blazor.webview.js is meant to be agnostic to specific webview technologies, // so the following is an adaptor from blazor.webview.js conventions to WebView2 APIs await _webview.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(@" @@ -152,6 +163,20 @@ protected virtual void QueueBlazorStart() { } + /// + /// Override this method to manage opening links in the webview. Not all platforms require this. + /// + protected virtual void CoreWebView2_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs args) + { + } + + /// + /// Override this method to manage opening a new window in the webview. Not all platforms require this. + /// + protected virtual void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs args) + { + } + private protected static string GetHeaderString(IDictionary headers) => string.Join(Environment.NewLine, headers.Select(kvp => $"{kvp.Key}: {kvp.Value}"));