From 53f79a6b9aa96442d3d508aacabca4b0c2bfbd4a Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Fri, 11 Feb 2022 17:18:29 -0800 Subject: [PATCH 01/14] Blazor Android Open Links in Browser with Configurability --- .../Android/AndroidWebKitWebViewManager.cs | 3 +- .../src/Maui/Android/BlazorWebChromeClient.cs | 26 +++++++++++++++++ .../Android/BlazorWebViewHandler.Android.cs | 13 +++------ .../src/Maui/Android/WebKitWebViewClient.cs | 28 ++++++++++++++----- src/BlazorWebView/src/Maui/BlazorWebView.cs | 2 ++ .../src/Maui/BlazorWebViewHandler.cs | 5 +++- .../src/Maui/ExternalLinkMode.cs | 8 ++++++ src/BlazorWebView/src/Maui/IBlazorWebView.cs | 1 + 8 files changed, 68 insertions(+), 18 deletions(-) create mode 100644 src/BlazorWebView/src/Maui/Android/BlazorWebChromeClient.cs create mode 100644 src/BlazorWebView/src/Maui/ExternalLinkMode.cs diff --git a/src/BlazorWebView/src/Maui/Android/AndroidWebKitWebViewManager.cs b/src/BlazorWebView/src/Maui/Android/AndroidWebKitWebViewManager.cs index 6f70f0cb7a11..51cdbc9c80ad 100644 --- a/src/BlazorWebView/src/Maui/Android/AndroidWebKitWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Android/AndroidWebKitWebViewManager.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Components.Web; using Microsoft.Extensions.FileProviders; using AWebView = Android.Webkit.WebView; +using AUri = Android.Net.Uri; namespace Microsoft.AspNetCore.Components.WebView.Maui { @@ -18,7 +19,7 @@ public class AndroidWebKitWebViewManager : WebViewManager // 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 static readonly AUri AndroidAppOriginUri = AUri.Parse(AppOrigin)!; private readonly BlazorWebViewHandler _blazorWebViewHandler; private readonly AWebView _webview; diff --git a/src/BlazorWebView/src/Maui/Android/BlazorWebChromeClient.cs b/src/BlazorWebView/src/Maui/Android/BlazorWebChromeClient.cs new file mode 100644 index 000000000000..c42b6fb8c4fa --- /dev/null +++ b/src/BlazorWebView/src/Maui/Android/BlazorWebChromeClient.cs @@ -0,0 +1,26 @@ + +using Android.Content; +using Android.Net; +using Android.OS; +using Android.Webkit; + +namespace Microsoft.AspNetCore.Components.WebView.Maui +{ + class BlazorWebChromeClient : WebChromeClient + { + public override bool OnCreateWindow(Android.Webkit.WebView? view, bool isDialog, bool isUserGesture, Message? resultMsg) + { + if (view?.Context is not null) + { + // Intercept _blank target tags to always open in device browser + // regardless of ExternalLinkMode.OpenInWebview + var requestUrl = view.GetHitTestResult().Extra; + var intent = new Intent(Intent.ActionView, Uri.Parse(requestUrl)); + view.Context.StartActivity(intent); + } + + // We don't actually want to create a new WebView window so we just return false + return false; + } + } +} diff --git a/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs b/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs index 2bb1978e8ace..20e34829be2d 100644 --- a/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs +++ b/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs @@ -1,14 +1,6 @@ using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; using Android.Webkit; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Primitives; using Microsoft.Maui.Handlers; using static Android.Views.ViewGroup; using Path = System.IO.Path; @@ -31,6 +23,9 @@ protected override BlazorAndroidWebView CreateNativeView() #pragma warning restore 618 }; + // To allow overriding ExternalLinkMode.OpenInWebView and open links in browser with a _blank target + blazorAndroidWebView.Settings.SetSupportMultipleWindows(true); + BlazorAndroidWebView.SetWebContentsDebuggingEnabled(enabled: true); if (blazorAndroidWebView.Settings != null) @@ -116,6 +111,6 @@ protected virtual WebViewClient GetWebViewClient() => new WebKitWebViewClient(this); protected virtual WebChromeClient GetWebChromeClient() => - new WebChromeClient(); + new BlazorWebChromeClient(); } } diff --git a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs index a4ce5d1451ac..dff58bc84c0d 100644 --- a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs +++ b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs @@ -1,7 +1,9 @@ using System; +using Android.Content; using Android.Runtime; using Android.Webkit; using AWebView = Android.Webkit.WebView; +using AUri = Android.Net.Uri; namespace Microsoft.AspNetCore.Components.WebView.Maui { @@ -20,25 +22,37 @@ protected WebKitWebViewClient(IntPtr javaReference, JniHandleOwnership transfer) { // This constructor is called whenever the .NET proxy was disposed, and it was recreated by Java. It also // happens when overridden methods are called between execution of this constructor and the one above. - // because of these facts, we have to check - // all methods below for null field references and properties. + // because of these facts, we have to check all methods below for null field references and properties. } public override bool ShouldOverrideUrlLoading(AWebView? view, IWebResourceRequest? request) { - // handle redirects to the app custom scheme by reloading the url in the view. - // otherwise they will be blocked by Android. + // Handle redirects to the app custom scheme by reloading the URL in the view. + // Handle navigation to external URLs using the system browser, unless overriden. var requestUri = request?.Url?.ToString(); - if (requestUri != null && view != null && - request != null && request.IsRedirect && request.IsForMainFrame) + if (requestUri != null) { var uri = new Uri(requestUri); - if (uri.Host == "0.0.0.0") + + if (uri.Host == "0.0.0.0" && + view is not null && + request is not null && + request.IsRedirect && + request.IsForMainFrame) { view.LoadUrl(uri.ToString()); return true; } + else if (uri.Host != "0.0.0.0" && + _webViewHandler is not null && + _webViewHandler.ExternalLinkMode == ExternalLinkMode.OpenInExternalBrowser) + { + var intent = new Intent(Intent.ActionView, AUri.Parse(requestUri)); + _webViewHandler.Context.StartActivity(intent); + return true; + } } + return base.ShouldOverrideUrlLoading(view, request); } diff --git a/src/BlazorWebView/src/Maui/BlazorWebView.cs b/src/BlazorWebView/src/Maui/BlazorWebView.cs index 1021de60926e..49ac0bf7e5df 100644 --- a/src/BlazorWebView/src/Maui/BlazorWebView.cs +++ b/src/BlazorWebView/src/Maui/BlazorWebView.cs @@ -19,6 +19,8 @@ public BlazorWebView() public RootComponentsCollection RootComponents { get; } + public ExternalLinkMode ExternalLinkMode { get; set; } = ExternalLinkMode.OpenInExternalBrowser; + /// public virtual IFileProvider CreateFileProvider(string contentRootDir) { diff --git a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs index ebe9dfe6e724..1f4a954b1705 100644 --- a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs +++ b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Components.WebView.Maui { public partial class BlazorWebViewHandler { - public static PropertyMapper BlazorWebViewMapper = new(ViewHandler.ViewMapper) + public static PropertyMapper BlazorWebViewMapper = new(ViewMapper) { [nameof(IBlazorWebView.HostPage)] = MapHostPage, [nameof(IBlazorWebView.RootComponents)] = MapRootComponents, @@ -28,6 +28,7 @@ public static void MapHostPage(BlazorWebViewHandler handler, IBlazorWebView webV { #if !NETSTANDARD handler.HostPage = webView.HostPage; + handler.ExternalLinkMode = webView.ExternalLinkMode; handler.StartWebViewCoreIfPossible(); #endif } @@ -36,12 +37,14 @@ public static void MapRootComponents(BlazorWebViewHandler handler, IBlazorWebVie { #if !NETSTANDARD handler.RootComponents = webView.RootComponents; + handler.ExternalLinkMode = webView.ExternalLinkMode; handler.StartWebViewCoreIfPossible(); #endif } #if !NETSTANDARD private string? HostPage { get; set; } + internal ExternalLinkMode ExternalLinkMode { get; private set; } private RootComponentsCollection? _rootComponents; private RootComponentsCollection? RootComponents diff --git a/src/BlazorWebView/src/Maui/ExternalLinkMode.cs b/src/BlazorWebView/src/Maui/ExternalLinkMode.cs new file mode 100644 index 000000000000..ddaf57ef704e --- /dev/null +++ b/src/BlazorWebView/src/Maui/ExternalLinkMode.cs @@ -0,0 +1,8 @@ +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 cf97f062b153..7bd1672783f7 100644 --- a/src/BlazorWebView/src/Maui/IBlazorWebView.cs +++ b/src/BlazorWebView/src/Maui/IBlazorWebView.cs @@ -9,6 +9,7 @@ public interface IBlazorWebView : IView string? HostPage { get; set; } RootComponentsCollection RootComponents { get; } JSComponentConfigurationStore JSComponents { get; } + ExternalLinkMode ExternalLinkMode { get; set; } /// /// Creates a file provider for static assets used in the . The default implementation From 817166a5d6a5272edac3939249258aab7326a1ff Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Tue, 15 Feb 2022 13:45:33 -0800 Subject: [PATCH 02/14] Blazor Windows Open Links in Browser with Configurability (#4680) * Blazor Windows Open Links in Browser with Configurability Windows portion of #4338 * TryCreate URI --- .../Windows/BlazorWebViewHandler.Windows.cs | 9 +++- .../src/Maui/Windows/WinUIWebViewManager.cs | 43 ++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs b/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs index 66e44a550b81..818e97aac615 100644 --- a/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs +++ b/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs @@ -58,7 +58,14 @@ private void StartWebViewCoreIfPossible() var fileProvider = VirtualView.CreateFileProvider(contentRootDir); - _webviewManager = new WinUIWebViewManager(NativeView, Services!, ComponentsDispatcher, fileProvider, VirtualView.JSComponents, hostPageRelativePath, contentRootDir); + _webviewManager = new WinUIWebViewManager(NativeView, + Services!, + ComponentsDispatcher, + fileProvider, + VirtualView.JSComponents, + hostPageRelativePath, + contentRootDir, + ExternalLinkMode); if (RootComponents != null) { diff --git a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs index 9a1378614013..f4a948ef0ec3 100644 --- a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs @@ -8,6 +8,7 @@ using Microsoft.Web.WebView2.Core; using Windows.ApplicationModel; using Windows.Storage.Streams; +using Launcher = Windows.System.Launcher; using WebView2Control = Microsoft.UI.Xaml.Controls.WebView2; namespace Microsoft.AspNetCore.Components.WebView.Maui @@ -21,13 +22,25 @@ public class WinUIWebViewManager : WebView2WebViewManager private readonly WebView2Control _webview; private readonly string _hostPageRelativePath; private readonly string _contentRootDir; + private readonly ExternalLinkMode _externalLinkMode; - public WinUIWebViewManager(WebView2Control webview, IServiceProvider services, Dispatcher dispatcher, IFileProvider fileProvider, JSComponentConfigurationStore jsComponents, string hostPageRelativePath, string contentRootDir) + public WinUIWebViewManager( + WebView2Control webview!!, + IServiceProvider services, + Dispatcher dispatcher, + IFileProvider fileProvider, + JSComponentConfigurationStore jsComponents, + string hostPageRelativePath, + string contentRootDir, + ExternalLinkMode externalLinkMode) : base(webview, services, dispatcher, fileProvider, jsComponents, hostPageRelativePath) { _webview = webview; _hostPageRelativePath = hostPageRelativePath; _contentRootDir = contentRootDir; + _externalLinkMode = externalLinkMode; + + _webview.CoreWebView2Initialized += Webview_CoreWebView2Initialized; } protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRequestedEventArgs eventArgs) @@ -102,5 +115,33 @@ protected override void QueueBlazorStart() "); }; } + + 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) + { + if (Uri.TryCreate(args.Uri, UriKind.RelativeOrAbsolute, out var uri) && + uri.Host != "0.0.0.0" && + _externalLinkMode == ExternalLinkMode.OpenInExternalBrowser) + { + _ = Launcher.LaunchUriAsync(uri); + args.Cancel = true; + } + } + + private void CoreWebView2_NewWindowRequested(CoreWebView2 sender, CoreWebView2NewWindowRequestedEventArgs args) + { + // Intercept _blank target tags to always open in device browser + // regardless of ExternalLinkMode.OpenInWebview + if (Uri.TryCreate(args.Uri, UriKind.RelativeOrAbsolute, out var uri)) + { + _ = Launcher.LaunchUriAsync(uri); + args.Handled = true; + } + } } } From 899d90edff9560ae72125a8ee0d355b5267b14aa Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Tue, 15 Feb 2022 16:11:46 -0800 Subject: [PATCH 03/14] PR Feedback (cherry picked from commit 35f637ee0371044f1df1f6ea5092ef69e8a14215) --- 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}")); From 3c7c2d0a54858b76ef2a1daab6d5a877a2f0b9dc Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Thu, 17 Feb 2022 11:18:24 -0800 Subject: [PATCH 04/14] OnExternalNavigationStarting --- Microsoft.Maui.sln | 3 ++- .../src/Maui/Android/WebKitWebViewClient.cs | 24 ++++++++++++------- src/BlazorWebView/src/Maui/BlazorWebView.cs | 11 ++++----- .../src/Maui/BlazorWebViewHandler.cs | 11 +++++---- src/BlazorWebView/src/Maui/IBlazorWebView.cs | 9 +++---- .../src/Maui/Windows/WinUIWebViewManager.cs | 22 +++++++++++------ .../ExternalLinkNavigationInfo.cs | 23 ++++++++++++++++++ ...ode.cs => ExternalLinkNavigationPolicy.cs} | 13 ++++++---- 8 files changed, 79 insertions(+), 37 deletions(-) create mode 100644 src/BlazorWebView/src/SharedSource/ExternalLinkNavigationInfo.cs rename src/BlazorWebView/src/SharedSource/{ExternalLinkMode.cs => ExternalLinkNavigationPolicy.cs} (58%) diff --git a/Microsoft.Maui.sln b/Microsoft.Maui.sln index e5aa887bedfe..82fda1210951 100644 --- a/Microsoft.Maui.sln +++ b/Microsoft.Maui.sln @@ -200,7 +200,8 @@ 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\ExternalLinkNavigationInfo.cs = src\BlazorWebView\src\SharedSource\ExternalLinkNavigationInfo.cs + src\BlazorWebView\src\SharedSource\ExternalLinkNavigationPolicy.cs = src\BlazorWebView\src\SharedSource\ExternalLinkNavigationPolicy.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/WebKitWebViewClient.cs b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs index 3eed7ea74b7f..292a8ea40432 100644 --- a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs +++ b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs @@ -33,10 +33,8 @@ public override bool ShouldOverrideUrlLoading(AWebView? view, IWebResourceReques // Handle redirects to the app custom scheme by reloading the URL in the view. // Handle navigation to external URLs using the system browser, unless overriden. var requestUri = request?.Url?.ToString(); - if (requestUri != null) + if (Uri.TryCreate(requestUri, UriKind.RelativeOrAbsolute, out var uri)) { - var uri = new Uri(requestUri); - if (uri.Host == BlazorWebView.AppHostAddress && view is not null && request is not null && @@ -46,13 +44,21 @@ request is not null && view.LoadUrl(uri.ToString()); return true; } - else if (uri.Host != BlazorWebView.AppHostAddress && - _webViewHandler != null && - _webViewHandler.ExternalLinkMode == ExternalLinkMode.OpenInExternalBrowser) + else if (uri.Host != BlazorWebView.AppHostAddress && _webViewHandler != null) { - var intent = new Intent(Intent.ActionView, AUri.Parse(requestUri)); - _webViewHandler.Context.StartActivity(intent); - return true; + var callbackArgs = new ExternalLinkNavigationInfo(uri); + var externalLinkMode = _webViewHandler.OnExternalNavigationStarting?.Invoke(callbackArgs) ?? ExternalLinkNavigationPolicy.OpenInExternalBrowser; + + if (externalLinkMode == ExternalLinkNavigationPolicy.OpenInExternalBrowser) + { + var intent = new Intent(Intent.ActionView, AUri.Parse(requestUri)); + _webViewHandler.Context.StartActivity(intent); + } + + if (externalLinkMode != ExternalLinkNavigationPolicy.OpenInWebView) + { + return true; + } } } diff --git a/src/BlazorWebView/src/Maui/BlazorWebView.cs b/src/BlazorWebView/src/Maui/BlazorWebView.cs index 6e3228b01ff8..b1f1a7fdf59a 100644 --- a/src/BlazorWebView/src/Maui/BlazorWebView.cs +++ b/src/BlazorWebView/src/Maui/BlazorWebView.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Components.Web; +using System; +using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebView; using Microsoft.Extensions.FileProviders; @@ -21,12 +22,8 @@ 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; + /// + public Func? OnExternalNavigationStarting { get; set; } /// public virtual IFileProvider CreateFileProvider(string contentRootDir) diff --git a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs index d8ed9820d763..f7b64480829a 100644 --- a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs +++ b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Microsoft.AspNetCore.Components.WebView; using Microsoft.Maui; using Microsoft.Maui.Handlers; @@ -11,7 +12,7 @@ public partial class BlazorWebViewHandler { [nameof(IBlazorWebView.HostPage)] = MapHostPage, [nameof(IBlazorWebView.RootComponents)] = MapRootComponents, - [nameof(IBlazorWebView.ExternalLinkMode)] = MapExternalLinkMode, + [nameof(IBlazorWebView.OnExternalNavigationStarting)] = MapOnExternalNavigationStarting, }; public BlazorWebViewHandler() : base(BlazorWebViewMapper) @@ -42,17 +43,17 @@ public static void MapRootComponents(BlazorWebViewHandler handler, IBlazorWebVie #endif } - public static void MapExternalLinkMode(BlazorWebViewHandler handler, IBlazorWebView webView) + public static void MapOnExternalNavigationStarting(BlazorWebViewHandler handler, IBlazorWebView webView) { #if !NETSTANDARD - handler.ExternalLinkMode = webView.ExternalLinkMode; + handler.OnExternalNavigationStarting = webView.OnExternalNavigationStarting; handler.StartWebViewCoreIfPossible(); #endif } #if !NETSTANDARD private string? HostPage { get; set; } - internal ExternalLinkMode ExternalLinkMode { get; private set; } + internal Func? OnExternalNavigationStarting { get; private set; } private RootComponentsCollection? _rootComponents; private RootComponentsCollection? RootComponents diff --git a/src/BlazorWebView/src/Maui/IBlazorWebView.cs b/src/BlazorWebView/src/Maui/IBlazorWebView.cs index 5050ae777886..109243b5624c 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 System; +using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebView; using Microsoft.Extensions.FileProviders; using Microsoft.Maui; @@ -12,10 +13,10 @@ public interface IBlazorWebView : IView JSComponentConfigurationStore JSComponents { get; } /// - /// Specify whether links should be opened in the external - /// system default browser, or within the webview. + /// Allows customizing how external links are opened. + /// Opens external links in the system browser by default. /// - ExternalLinkMode ExternalLinkMode { get; set; } + Func? OnExternalNavigationStarting { get; set; } /// /// Creates a file provider for static assets used in the . The default implementation diff --git a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs index 35d71abb5187..ff908fc09238 100644 --- a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs @@ -105,19 +105,27 @@ protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRe protected override void CoreWebView2_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs args) { - if (Uri.TryCreate(args.Uri, UriKind.RelativeOrAbsolute, out var uri) && - uri.Host != AppHostAddress && - _blazorWebViewHandler.ExternalLinkMode == ExternalLinkMode.OpenInExternalBrowser) + if (Uri.TryCreate(args.Uri, UriKind.RelativeOrAbsolute, out var uri) && uri.Host != AppHostAddress) { - _ = Launcher.LaunchUriAsync(uri); - args.Cancel = true; + var callbackArgs = new ExternalLinkNavigationInfo(uri); + var externalLinkMode = _blazorWebViewHandler.OnExternalNavigationStarting?.Invoke(callbackArgs) ?? ExternalLinkNavigationPolicy.OpenInExternalBrowser; + + if (externalLinkMode == ExternalLinkNavigationPolicy.OpenInExternalBrowser) + { + _ = Launcher.LaunchUriAsync(uri); + } + + if (externalLinkMode != ExternalLinkNavigationPolicy.OpenInWebView) + { + args.Cancel = true; + } } } protected override void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs args) { - // Intercept _blank target tags to always open in device browser - // regardless of ExternalLinkMode.OpenInWebview + // Intercept _blank target tags to always open in device browser. + // The ExternalLinkCallback is not invoked. if (Uri.TryCreate(args.Uri, UriKind.RelativeOrAbsolute, out var uri)) { _ = Launcher.LaunchUriAsync(uri); diff --git a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationInfo.cs b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationInfo.cs new file mode 100644 index 000000000000..dca3244eaa53 --- /dev/null +++ b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationInfo.cs @@ -0,0 +1,23 @@ +using System; + +namespace Microsoft.AspNetCore.Components.WebView +{ + /// + /// Used to provide information about a link (]]>) clicked within a Blazor WebView. + /// + /// `_blank` target links will always open in the default browser + /// thus the OnExternalNavigationStarting won't be called. + /// + public class ExternalLinkNavigationInfo + { + public ExternalLinkNavigationInfo(Uri uri) + { + Uri = uri; + } + + /// + /// External Uri to be navigated to. + /// + public Uri Uri { get; set; } + } +} diff --git a/src/BlazorWebView/src/SharedSource/ExternalLinkMode.cs b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs similarity index 58% rename from src/BlazorWebView/src/SharedSource/ExternalLinkMode.cs rename to src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs index a4fa55b9058c..56e3d6e0863e 100644 --- a/src/BlazorWebView/src/SharedSource/ExternalLinkMode.cs +++ b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs @@ -1,12 +1,12 @@ namespace Microsoft.AspNetCore.Components.WebView { /// - /// Link handling mode for anchor tags ]]> within a Blazor WebView. + /// Link handling policy for anchor tags ]]> within a Blazor WebView. /// /// `_blank` target links will always open in the default browser - /// regardless of this setting. + /// regardless of the policy. /// - public enum ExternalLinkMode + public enum ExternalLinkNavigationPolicy { /// /// Opens anchor tags ]]> in the system default browser. @@ -16,6 +16,11 @@ public enum ExternalLinkMode /// /// Opens anchor tags ]]> in the WebView. This is not recommended. /// - OpenInWebView + OpenInWebView, + + /// + /// Cancels the current navigation attempt to an external link. + /// + CancelNavigation } } From f0448be9f5666728ef4e0bc4122787cf0c4d2be9 Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Thu, 17 Feb 2022 11:24:17 -0800 Subject: [PATCH 05/14] Pranav Points --- src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs index ff908fc09238..be9627101603 100644 --- a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs @@ -115,10 +115,7 @@ protected override void CoreWebView2_NavigationStarting(object sender, CoreWebVi _ = Launcher.LaunchUriAsync(uri); } - if (externalLinkMode != ExternalLinkNavigationPolicy.OpenInWebView) - { - args.Cancel = true; - } + args.Cancel = externalLinkMode != ExternalLinkNavigationPolicy.OpenInWebView; } } From bdfabfd3cf4b7238bb597756d17e3e6705f0c8c0 Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Fri, 18 Feb 2022 16:58:50 -0800 Subject: [PATCH 06/14] Event based approach --- .../Android/BlazorWebViewHandler.Android.cs | 2 +- .../src/Maui/Android/WebKitWebViewClient.cs | 6 +-- src/BlazorWebView/src/Maui/BlazorWebView.cs | 9 +++- .../src/Maui/BlazorWebViewHandler.cs | 8 +-- src/BlazorWebView/src/Maui/IBlazorWebView.cs | 2 +- .../Windows/BlazorWebViewHandler.Windows.cs | 6 +-- .../src/Maui/Windows/WinUIWebViewManager.cs | 34 +----------- .../src/Maui/iOS/BlazorWebViewHandler.iOS.cs | 2 +- .../ExternalLinkNavigationInfo.cs | 9 +++- .../SharedSource/WebView2WebViewManager.cs | 54 +++++++++++++++---- src/BlazorWebView/src/Wpf/BlazorWebView.cs | 32 +++++++++-- 11 files changed, 104 insertions(+), 60 deletions(-) diff --git a/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs b/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs index d66f711514ec..b764cd871381 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(PlatformView, Services!, ComponentsDispatcher, fileProvider, VirtualView.JSComponents, hostPageRelativePath); + _webviewManager = new AndroidWebKitWebViewManager(PlatformView, 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 292a8ea40432..fa734af6a522 100644 --- a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs +++ b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs @@ -47,15 +47,15 @@ request is not null && else if (uri.Host != BlazorWebView.AppHostAddress && _webViewHandler != null) { var callbackArgs = new ExternalLinkNavigationInfo(uri); - var externalLinkMode = _webViewHandler.OnExternalNavigationStarting?.Invoke(callbackArgs) ?? ExternalLinkNavigationPolicy.OpenInExternalBrowser; + _webViewHandler.ExternalNavigationStarting?.Invoke(callbackArgs); - if (externalLinkMode == ExternalLinkNavigationPolicy.OpenInExternalBrowser) + if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenInExternalBrowser) { var intent = new Intent(Intent.ActionView, AUri.Parse(requestUri)); _webViewHandler.Context.StartActivity(intent); } - if (externalLinkMode != ExternalLinkNavigationPolicy.OpenInWebView) + if (callbackArgs.ExternalLinkNavigationPolicy != ExternalLinkNavigationPolicy.OpenInWebView) { return true; } diff --git a/src/BlazorWebView/src/Maui/BlazorWebView.cs b/src/BlazorWebView/src/Maui/BlazorWebView.cs index b1f1a7fdf59a..a5fdc04085ee 100644 --- a/src/BlazorWebView/src/Maui/BlazorWebView.cs +++ b/src/BlazorWebView/src/Maui/BlazorWebView.cs @@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Components.WebView.Maui { public class BlazorWebView : Microsoft.Maui.Controls.View, IBlazorWebView { - internal static readonly string AppHostAddress = "0.0.0.0"; + internal const string AppHostAddress = "0.0.0.0"; private readonly JSComponentConfigurationStore _jSComponents = new(); @@ -23,7 +23,7 @@ public BlazorWebView() public RootComponentsCollection RootComponents { get; } /// - public Func? OnExternalNavigationStarting { get; set; } + public event EventHandler? OnExternalNavigationStarting; /// public virtual IFileProvider CreateFileProvider(string contentRootDir) @@ -31,5 +31,10 @@ public virtual IFileProvider CreateFileProvider(string contentRootDir) // Call into the platform-specific code to get that platform's asset file provider return ((BlazorWebViewHandler)(Handler!)).CreateFileProvider(contentRootDir); } + + internal void ExternalNavigationStarting(ExternalLinkNavigationInfo args) + { + OnExternalNavigationStarting?.Invoke(this, args); + } } } diff --git a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs index f7b64480829a..fb0ec460458c 100644 --- a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs +++ b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs @@ -46,14 +46,16 @@ public static void MapRootComponents(BlazorWebViewHandler handler, IBlazorWebVie public static void MapOnExternalNavigationStarting(BlazorWebViewHandler handler, IBlazorWebView webView) { #if !NETSTANDARD - handler.OnExternalNavigationStarting = webView.OnExternalNavigationStarting; - handler.StartWebViewCoreIfPossible(); + if (webView is BlazorWebView bwv) + { + handler.ExternalNavigationStarting = bwv.ExternalNavigationStarting; + } #endif } #if !NETSTANDARD private string? HostPage { get; set; } - internal Func? OnExternalNavigationStarting { get; private set; } + internal Action? ExternalNavigationStarting; private RootComponentsCollection? _rootComponents; private RootComponentsCollection? RootComponents diff --git a/src/BlazorWebView/src/Maui/IBlazorWebView.cs b/src/BlazorWebView/src/Maui/IBlazorWebView.cs index 109243b5624c..9a2e8a780853 100644 --- a/src/BlazorWebView/src/Maui/IBlazorWebView.cs +++ b/src/BlazorWebView/src/Maui/IBlazorWebView.cs @@ -16,7 +16,7 @@ public interface IBlazorWebView : IView /// Allows customizing how external links are opened. /// Opens external links in the system browser by default. /// - Func? OnExternalNavigationStarting { get; set; } + event EventHandler? OnExternalNavigationStarting; /// /// Creates a file provider for static assets used in the . The default implementation diff --git a/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs b/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs index 387eee90a23e..e60f076f4653 100644 --- a/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs +++ b/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs @@ -58,15 +58,15 @@ private void StartWebViewCoreIfPossible() var fileProvider = VirtualView.CreateFileProvider(contentRootDir); - _webviewManager = new WinUIWebViewManager( - PlatformView, + _webviewManager = new WinUIWebViewManager( + PlatformView, Services!, ComponentsDispatcher, fileProvider, VirtualView.JSComponents, hostPageRelativePath, contentRootDir, - this); + ExternalNavigationStarting); if (RootComponents != null) { diff --git a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs index be9627101603..b9cec6a8dcf7 100644 --- a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs @@ -8,7 +8,6 @@ using Microsoft.Web.WebView2.Core; using Windows.ApplicationModel; using Windows.Storage.Streams; -using Launcher = Windows.System.Launcher; using WebView2Control = Microsoft.UI.Xaml.Controls.WebView2; namespace Microsoft.AspNetCore.Components.WebView.Maui @@ -22,7 +21,6 @@ public class WinUIWebViewManager : WebView2WebViewManager private readonly WebView2Control _webview; private readonly string _hostPageRelativePath; private readonly string _contentRootDir; - private readonly BlazorWebViewHandler _blazorWebViewHandler; public WinUIWebViewManager( WebView2Control webview, @@ -32,13 +30,12 @@ public WinUIWebViewManager( JSComponentConfigurationStore jsComponents, string hostPageRelativePath, string contentRootDir, - BlazorWebViewHandler blazorWebViewHandler) - : base(webview, services, dispatcher, fileProvider, jsComponents, hostPageRelativePath) + Action? externalNavigationStarting) + : base(webview, services, dispatcher, fileProvider, jsComponents, hostPageRelativePath, externalNavigationStarting) { _webview = webview; _hostPageRelativePath = hostPageRelativePath; _contentRootDir = contentRootDir; - _blazorWebViewHandler = blazorWebViewHandler; } protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRequestedEventArgs eventArgs) @@ -103,33 +100,6 @@ protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRe deferral.Complete(); } - protected override void CoreWebView2_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs args) - { - if (Uri.TryCreate(args.Uri, UriKind.RelativeOrAbsolute, out var uri) && uri.Host != AppHostAddress) - { - var callbackArgs = new ExternalLinkNavigationInfo(uri); - var externalLinkMode = _blazorWebViewHandler.OnExternalNavigationStarting?.Invoke(callbackArgs) ?? ExternalLinkNavigationPolicy.OpenInExternalBrowser; - - if (externalLinkMode == ExternalLinkNavigationPolicy.OpenInExternalBrowser) - { - _ = Launcher.LaunchUriAsync(uri); - } - - args.Cancel = externalLinkMode != ExternalLinkNavigationPolicy.OpenInWebView; - } - } - - protected override void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs args) - { - // Intercept _blank target tags to always open in device browser. - // The ExternalLinkCallback is not invoked. - if (Uri.TryCreate(args.Uri, UriKind.RelativeOrAbsolute, out var uri)) - { - _ = Launcher.LaunchUriAsync(uri); - 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 diff --git a/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs b/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs index 7828e5f48755..28ba8310f9df 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 - /// External Uri to be navigated to. + /// External URI to be navigated to. /// public Uri Uri { get; set; } + + /// + /// The policy to use when opening external links from the webview. + /// + /// Defaults to opening links in an external browser. + /// + public ExternalLinkNavigationPolicy ExternalLinkNavigationPolicy { get; set; } = ExternalLinkNavigationPolicy.OpenInExternalBrowser; } } diff --git a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs index c077228cae6b..46cd8f3d2016 100644 --- a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs +++ b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs @@ -13,12 +13,15 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebView; using Microsoft.Extensions.FileProviders; #if WEBVIEW2_WINFORMS +using System.Diagnostics; using Microsoft.Web.WebView2; using Microsoft.Web.WebView2.Core; using WebView2Control = Microsoft.Web.WebView2.WinForms.WebView2; #elif WEBVIEW2_WPF +using System.Diagnostics; using Microsoft.Web.WebView2; using Microsoft.Web.WebView2.Core; using WebView2Control = Microsoft.Web.WebView2.Wpf.WebView2; @@ -27,6 +30,7 @@ using WebView2Control = Microsoft.UI.Xaml.Controls.WebView2; using System.Runtime.InteropServices.WindowsRuntime; using Windows.Storage.Streams; +using Launcher = Windows.System.Launcher; #endif namespace Microsoft.AspNetCore.Components.WebView.WebView2 @@ -47,8 +51,10 @@ public class WebView2WebViewManager : WebViewManager private readonly Task _webviewReadyTask; #if WEBVIEW2_WINFORMS || WEBVIEW2_WPF private protected CoreWebView2Environment _coreWebView2Environment; + private readonly Action _externalNavigationStarting; #elif WEBVIEW2_MAUI private protected CoreWebView2Environment? _coreWebView2Environment; + private readonly Action? _externalNavigationStarting; #endif /// @@ -65,10 +71,17 @@ public WebView2WebViewManager( Dispatcher dispatcher, IFileProvider fileProvider, JSComponentConfigurationStore jsComponents, - string hostPageRelativePath) + string hostPageRelativePath, +#if WEBVIEW2_WINFORMS || WEBVIEW2_WPF + Action externalNavigationStarting +#elif WEBVIEW2_MAUI + Action? externalNavigationStarting +#endif + ) : base(services, dispatcher, new Uri(AppOrigin), fileProvider, jsComponents, hostPageRelativePath) { _webview = webview; + _externalNavigationStarting = externalNavigationStarting; // Unfortunately the CoreWebView2 can only be instantiated asynchronously. // We want the external API to behave as if initalization is synchronous, @@ -163,18 +176,41 @@ 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) + private void CoreWebView2_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs args) { + if (Uri.TryCreate(args.Uri, UriKind.RelativeOrAbsolute, out var uri) && uri.Host != AppHostAddress) + { + var callbackArgs = new ExternalLinkNavigationInfo(uri); + _externalNavigationStarting?.Invoke(callbackArgs); + + if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenInExternalBrowser) + { + LaunchUri(uri); + } + + args.Cancel = callbackArgs.ExternalLinkNavigationPolicy != ExternalLinkNavigationPolicy.OpenInWebView; + } } - /// - /// 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 void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs args) { + // Intercept _blank target tags to always open in device browser. + // The ExternalLinkCallback is not invoked. + if (Uri.TryCreate(args.Uri, UriKind.RelativeOrAbsolute, out var uri)) + { + LaunchUri(uri); + args.Handled = true; + } + } + + private void LaunchUri(Uri uri) + { +#if WEBVIEW2_WINFORMS || WEBVIEW2_WPF + Process.Start(@"C:\Program Files\Internet Explorer\iexplore.exe", uri.ToString()); +#elif WEBVIEW2_MAUI + + _ = Launcher.LaunchUriAsync(uri); +#endif } private protected static string GetHeaderString(IDictionary headers) => diff --git a/src/BlazorWebView/src/Wpf/BlazorWebView.cs b/src/BlazorWebView/src/Wpf/BlazorWebView.cs index 29ebfd1370d8..663f4077db75 100644 --- a/src/BlazorWebView/src/Wpf/BlazorWebView.cs +++ b/src/BlazorWebView/src/Wpf/BlazorWebView.cs @@ -48,9 +48,17 @@ public class BlazorWebView : Control, IAsyncDisposable propertyType: typeof(IServiceProvider), ownerType: typeof(BlazorWebView), typeMetadata: new PropertyMetadata(OnServicesPropertyChanged)); + + /// + /// The backing store for the property. + /// + public static readonly DependencyProperty OnExternalNavigationStartingProperty = DependencyProperty.Register( + name: nameof(OnExternalNavigationStarting), + propertyType: typeof(EventHandler), + ownerType: typeof(BlazorWebView)); #endregion - private const string webViewTemplateChildName = "WebView"; + private const string WebViewTemplateChildName = "WebView"; private WebView2Control _webview; private WebView2WebViewManager _webviewManager; private bool _isDisposed; @@ -67,7 +75,7 @@ public BlazorWebView() Template = new ControlTemplate { - VisualTree = new FrameworkElementFactory(typeof(WebView2Control), webViewTemplateChildName) + VisualTree = new FrameworkElementFactory(typeof(WebView2Control), WebViewTemplateChildName) }; } @@ -98,6 +106,16 @@ public string HostPage public RootComponentsCollection RootComponents => (RootComponentsCollection)GetValue(RootComponentsProperty); + /// + /// Allows customizing how external links are opened. + /// Opens external links in the system browser by default. + /// + public EventHandler OnExternalNavigationStarting + { + get => (EventHandler)GetValue(OnExternalNavigationStartingProperty); + set => SetValue(OnExternalNavigationStartingProperty, value); + } + /// /// Gets or sets an containing services to be used by this control and also by application code. /// This property must be set to a valid value for the Blazor components to start. @@ -131,7 +149,7 @@ public override void OnApplyTemplate() if (_webview == null) { - _webview = (WebView2Control)GetTemplateChild(webViewTemplateChildName); + _webview = (WebView2Control)GetTemplateChild(WebViewTemplateChildName); StartWebViewCoreIfPossible(); } } @@ -171,7 +189,13 @@ private void StartWebViewCoreIfPossible() var fileProvider = CreateFileProvider(contentRootDirFullPath); - _webviewManager = new WebView2WebViewManager(_webview, Services, ComponentsDispatcher, fileProvider, RootComponents.JSComponents, hostPageRelativePath); + _webviewManager = new WebView2WebViewManager(_webview, + Services, + ComponentsDispatcher, + fileProvider, + RootComponents.JSComponents, + hostPageRelativePath, + (args) => OnExternalNavigationStarting?.Invoke(this, args)); foreach (var rootComponent in RootComponents) { // Since the page isn't loaded yet, this will always complete synchronously From 770a8dddf93f47630140af4d679aad8d1c2c230a Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Fri, 18 Feb 2022 17:02:41 -0800 Subject: [PATCH 07/14] Info -> EventArgs --- Microsoft.Maui.sln | 2 +- .../src/Maui/Android/WebKitWebViewClient.cs | 2 +- src/BlazorWebView/src/Maui/BlazorWebView.cs | 4 ++-- src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs | 2 +- src/BlazorWebView/src/Maui/IBlazorWebView.cs | 2 +- .../src/Maui/Windows/WinUIWebViewManager.cs | 2 +- ...ationInfo.cs => ExternalLinkNavigationEventArgs.cs} | 4 ++-- .../src/SharedSource/WebView2WebViewManager.cs | 10 +++++----- src/BlazorWebView/src/Wpf/BlazorWebView.cs | 6 +++--- 9 files changed, 17 insertions(+), 17 deletions(-) rename src/BlazorWebView/src/SharedSource/{ExternalLinkNavigationInfo.cs => ExternalLinkNavigationEventArgs.cs} (87%) diff --git a/Microsoft.Maui.sln b/Microsoft.Maui.sln index 82fda1210951..4f4473863ee5 100644 --- a/Microsoft.Maui.sln +++ b/Microsoft.Maui.sln @@ -200,7 +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\ExternalLinkNavigationInfo.cs = src\BlazorWebView\src\SharedSource\ExternalLinkNavigationInfo.cs + src\BlazorWebView\src\SharedSource\ExternalLinkNavigationEventArgs.cs = src\BlazorWebView\src\SharedSource\ExternalLinkNavigationEventArgs.cs src\BlazorWebView\src\SharedSource\ExternalLinkNavigationPolicy.cs = src\BlazorWebView\src\SharedSource\ExternalLinkNavigationPolicy.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 diff --git a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs index fa734af6a522..d4f336e7189e 100644 --- a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs +++ b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs @@ -46,7 +46,7 @@ request is not null && } else if (uri.Host != BlazorWebView.AppHostAddress && _webViewHandler != null) { - var callbackArgs = new ExternalLinkNavigationInfo(uri); + var callbackArgs = new ExternalLinkNavigationEventArgs(uri); _webViewHandler.ExternalNavigationStarting?.Invoke(callbackArgs); if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenInExternalBrowser) diff --git a/src/BlazorWebView/src/Maui/BlazorWebView.cs b/src/BlazorWebView/src/Maui/BlazorWebView.cs index a5fdc04085ee..0a44bb7a23f1 100644 --- a/src/BlazorWebView/src/Maui/BlazorWebView.cs +++ b/src/BlazorWebView/src/Maui/BlazorWebView.cs @@ -23,7 +23,7 @@ public BlazorWebView() public RootComponentsCollection RootComponents { get; } /// - public event EventHandler? OnExternalNavigationStarting; + public event EventHandler? OnExternalNavigationStarting; /// public virtual IFileProvider CreateFileProvider(string contentRootDir) @@ -32,7 +32,7 @@ public virtual IFileProvider CreateFileProvider(string contentRootDir) return ((BlazorWebViewHandler)(Handler!)).CreateFileProvider(contentRootDir); } - internal void ExternalNavigationStarting(ExternalLinkNavigationInfo args) + internal void ExternalNavigationStarting(ExternalLinkNavigationEventArgs args) { OnExternalNavigationStarting?.Invoke(this, args); } diff --git a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs index fb0ec460458c..36c427d723f9 100644 --- a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs +++ b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs @@ -55,7 +55,7 @@ public static void MapOnExternalNavigationStarting(BlazorWebViewHandler handler, #if !NETSTANDARD private string? HostPage { get; set; } - internal Action? ExternalNavigationStarting; + internal Action? ExternalNavigationStarting; private RootComponentsCollection? _rootComponents; private RootComponentsCollection? RootComponents diff --git a/src/BlazorWebView/src/Maui/IBlazorWebView.cs b/src/BlazorWebView/src/Maui/IBlazorWebView.cs index 9a2e8a780853..ab52bc65fda7 100644 --- a/src/BlazorWebView/src/Maui/IBlazorWebView.cs +++ b/src/BlazorWebView/src/Maui/IBlazorWebView.cs @@ -16,7 +16,7 @@ public interface IBlazorWebView : IView /// Allows customizing how external links are opened. /// Opens external links in the system browser by default. /// - event EventHandler? OnExternalNavigationStarting; + event EventHandler? OnExternalNavigationStarting; /// /// Creates a file provider for static assets used in the . The default implementation diff --git a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs index b9cec6a8dcf7..ff42a2f36195 100644 --- a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs @@ -30,7 +30,7 @@ public WinUIWebViewManager( JSComponentConfigurationStore jsComponents, string hostPageRelativePath, string contentRootDir, - Action? externalNavigationStarting) + Action? externalNavigationStarting) : base(webview, services, dispatcher, fileProvider, jsComponents, hostPageRelativePath, externalNavigationStarting) { _webview = webview; diff --git a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationInfo.cs b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs similarity index 87% rename from src/BlazorWebView/src/SharedSource/ExternalLinkNavigationInfo.cs rename to src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs index 142ced2577ce..9ea8f05ca483 100644 --- a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationInfo.cs +++ b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs @@ -8,9 +8,9 @@ namespace Microsoft.AspNetCore.Components.WebView /// `_blank` target links will always open in the default browser /// thus the OnExternalNavigationStarting won't be called. /// - public class ExternalLinkNavigationInfo + public class ExternalLinkNavigationEventArgs : EventArgs { - public ExternalLinkNavigationInfo(Uri uri) + public ExternalLinkNavigationEventArgs(Uri uri) { Uri = uri; } diff --git a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs index 46cd8f3d2016..c1212b2cb97c 100644 --- a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs +++ b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs @@ -51,10 +51,10 @@ public class WebView2WebViewManager : WebViewManager private readonly Task _webviewReadyTask; #if WEBVIEW2_WINFORMS || WEBVIEW2_WPF private protected CoreWebView2Environment _coreWebView2Environment; - private readonly Action _externalNavigationStarting; + private readonly Action _externalNavigationStarting; #elif WEBVIEW2_MAUI private protected CoreWebView2Environment? _coreWebView2Environment; - private readonly Action? _externalNavigationStarting; + private readonly Action? _externalNavigationStarting; #endif /// @@ -73,9 +73,9 @@ public WebView2WebViewManager( JSComponentConfigurationStore jsComponents, string hostPageRelativePath, #if WEBVIEW2_WINFORMS || WEBVIEW2_WPF - Action externalNavigationStarting + Action externalNavigationStarting #elif WEBVIEW2_MAUI - Action? externalNavigationStarting + Action? externalNavigationStarting #endif ) : base(services, dispatcher, new Uri(AppOrigin), fileProvider, jsComponents, hostPageRelativePath) @@ -180,7 +180,7 @@ private void CoreWebView2_NavigationStarting(object sender, CoreWebView2Navigati { if (Uri.TryCreate(args.Uri, UriKind.RelativeOrAbsolute, out var uri) && uri.Host != AppHostAddress) { - var callbackArgs = new ExternalLinkNavigationInfo(uri); + var callbackArgs = new ExternalLinkNavigationEventArgs(uri); _externalNavigationStarting?.Invoke(callbackArgs); if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenInExternalBrowser) diff --git a/src/BlazorWebView/src/Wpf/BlazorWebView.cs b/src/BlazorWebView/src/Wpf/BlazorWebView.cs index 663f4077db75..9eae036a30ff 100644 --- a/src/BlazorWebView/src/Wpf/BlazorWebView.cs +++ b/src/BlazorWebView/src/Wpf/BlazorWebView.cs @@ -54,7 +54,7 @@ public class BlazorWebView : Control, IAsyncDisposable /// public static readonly DependencyProperty OnExternalNavigationStartingProperty = DependencyProperty.Register( name: nameof(OnExternalNavigationStarting), - propertyType: typeof(EventHandler), + propertyType: typeof(EventHandler), ownerType: typeof(BlazorWebView)); #endregion @@ -110,9 +110,9 @@ public string HostPage /// Allows customizing how external links are opened. /// Opens external links in the system browser by default. /// - public EventHandler OnExternalNavigationStarting + public EventHandler OnExternalNavigationStarting { - get => (EventHandler)GetValue(OnExternalNavigationStartingProperty); + get => (EventHandler)GetValue(OnExternalNavigationStartingProperty); set => SetValue(OnExternalNavigationStartingProperty, value); } From 174a1694d0537766d5fb8fe65a31a5b62a7979c0 Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Wed, 23 Feb 2022 11:56:57 -0800 Subject: [PATCH 08/14] iOS & Mac Catalyst --- .../src/Maui/iOS/IOSWebViewManager.cs | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs b/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs index b064b11b5d64..99fb3aa26686 100644 --- a/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs @@ -181,18 +181,41 @@ public override void DidStartProvisionalNavigation(WKWebView webView, WKNavigati public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action decisionHandler) { + var callbackArgs = new ExternalLinkNavigationEventArgs(new Uri(navigationAction.Request.Url.ToString())); + // TargetFrame is null for navigation to a new window (`_blank`) if (navigationAction.TargetFrame is null) { - // Open in a new browser window - UIApplication.SharedApplication.OpenUrl(navigationAction.Request.Url); + // Open in a new browser window regardless of ExternalLinkNavigationPolicy + callbackArgs.ExternalLinkNavigationPolicy = ExternalLinkNavigationPolicy.OpenInExternalBrowser; + } + else if (callbackArgs.Uri.Host == BlazorWebView.AppHostAddress) + { + callbackArgs.ExternalLinkNavigationPolicy = ExternalLinkNavigationPolicy.OpenInWebView; + } + else + { + _webView.ExternalNavigationStarting?.Invoke(callbackArgs); + } + + var url = new NSUrl(callbackArgs.Uri.ToString()); + + if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenInExternalBrowser) + { + UIApplication.SharedApplication.OpenUrl(url); + } + + if (callbackArgs.ExternalLinkNavigationPolicy != ExternalLinkNavigationPolicy.OpenInWebView) + { + // Cancel any further navigation as we've either opened the link in the external browser + // or canceled the underlying navigation action. decisionHandler(WKNavigationActionPolicy.Cancel); return; } - if (navigationAction.TargetFrame.MainFrame) + if (navigationAction.TargetFrame!.MainFrame) { - _currentUri = navigationAction.Request.Url; + _currentUri = url; } decisionHandler(WKNavigationActionPolicy.Allow); From 8909a1959554ab0b60ffb702f9ea811c2620dc4a Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Wed, 23 Feb 2022 13:04:56 -0800 Subject: [PATCH 09/14] Fix WPF/Winforms Browser Start --- .../src/SharedSource/WebView2WebViewManager.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs index c1212b2cb97c..bedda07c08fb 100644 --- a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs +++ b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs @@ -206,7 +206,12 @@ private void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindo private void LaunchUri(Uri uri) { #if WEBVIEW2_WINFORMS || WEBVIEW2_WPF - Process.Start(@"C:\Program Files\Internet Explorer\iexplore.exe", uri.ToString()); + using (var launchBrowser = new Process()) + { + launchBrowser.StartInfo.UseShellExecute = true; + launchBrowser.StartInfo.FileName = uri.ToString(); + launchBrowser.Start(); + } #elif WEBVIEW2_MAUI _ = Launcher.LaunchUriAsync(uri); From f602372d5d60b0f09dad39475053cd6afd6d4096 Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Wed, 23 Feb 2022 14:21:19 -0800 Subject: [PATCH 10/14] Winforms ExternalNavigationStarting --- src/BlazorWebView/src/WindowsForms/BlazorWebView.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/BlazorWebView/src/WindowsForms/BlazorWebView.cs b/src/BlazorWebView/src/WindowsForms/BlazorWebView.cs index 2df9c81f4e7b..07607012a5c5 100644 --- a/src/BlazorWebView/src/WindowsForms/BlazorWebView.cs +++ b/src/BlazorWebView/src/WindowsForms/BlazorWebView.cs @@ -111,6 +111,10 @@ public IServiceProvider Services } } + [Category("Action")] + [Description("Allows customizing how external links are opened. Opens external links in the system browser by default.")] + public EventHandler ExternalNavigationStarting; + private void OnHostPagePropertyChanged() => StartWebViewCoreIfPossible(); private void OnServicesPropertyChanged() => StartWebViewCoreIfPossible(); @@ -154,7 +158,14 @@ private void StartWebViewCoreIfPossible() var fileProvider = CreateFileProvider(contentRootDirFullPath); - _webviewManager = new WebView2WebViewManager(_webview, Services, ComponentsDispatcher, fileProvider, RootComponents.JSComponents, hostPageRelativePath); + _webviewManager = new WebView2WebViewManager( + _webview, + Services, + ComponentsDispatcher, + fileProvider, + RootComponents.JSComponents, + hostPageRelativePath, + (args) => ExternalNavigationStarting?.Invoke(this, args)); foreach (var rootComponent in RootComponents) { From 78c7ba022434a36f0c9b34b2c40a49db908d30e1 Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Wed, 23 Feb 2022 16:05:23 -0800 Subject: [PATCH 11/14] PR Feedback --- src/BlazorWebView/src/Maui/BlazorWebView.cs | 7 +++---- .../src/Maui/BlazorWebViewHandler.cs | 10 ++++++---- src/BlazorWebView/src/Maui/IBlazorWebView.cs | 3 +-- .../ExternalLinkNavigationEventArgs.cs | 4 ++-- .../ExternalLinkNavigationPolicy.cs | 6 +++--- .../src/SharedSource/WebView2WebViewManager.cs | 7 +++---- src/BlazorWebView/src/Wpf/BlazorWebView.cs | 18 ++++++++++-------- 7 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/BlazorWebView/src/Maui/BlazorWebView.cs b/src/BlazorWebView/src/Maui/BlazorWebView.cs index 0a44bb7a23f1..8fcb8bbd1142 100644 --- a/src/BlazorWebView/src/Maui/BlazorWebView.cs +++ b/src/BlazorWebView/src/Maui/BlazorWebView.cs @@ -1,6 +1,5 @@ using System; using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Components.WebView; using Microsoft.Extensions.FileProviders; namespace Microsoft.AspNetCore.Components.WebView.Maui @@ -23,7 +22,7 @@ public BlazorWebView() public RootComponentsCollection RootComponents { get; } /// - public event EventHandler? OnExternalNavigationStarting; + public event EventHandler? ExternalNavigationStarting; /// public virtual IFileProvider CreateFileProvider(string contentRootDir) @@ -32,9 +31,9 @@ public virtual IFileProvider CreateFileProvider(string contentRootDir) return ((BlazorWebViewHandler)(Handler!)).CreateFileProvider(contentRootDir); } - internal void ExternalNavigationStarting(ExternalLinkNavigationEventArgs args) + internal void NotifyExternalNavigationStarting(ExternalLinkNavigationEventArgs args) { - OnExternalNavigationStarting?.Invoke(this, args); + ExternalNavigationStarting?.Invoke(this, args); } } } diff --git a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs index 36c427d723f9..42e207f7bd12 100644 --- a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs +++ b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs @@ -8,11 +8,13 @@ namespace Microsoft.AspNetCore.Components.WebView.Maui { public partial class BlazorWebViewHandler { - private static readonly PropertyMapper BlazorWebViewMapper = new(ViewMapper) + public static readonly PropertyMapper BlazorWebViewMapper = new(ViewMapper) { + // Note the order of the initialization matters. We must set the ExternalNavigationStarting action before starting the core webview. + [nameof(IBlazorWebView.ExternalNavigationStarting)] = MapNotifyExternalNavigationStarting, + [nameof(IBlazorWebView.HostPage)] = MapHostPage, [nameof(IBlazorWebView.RootComponents)] = MapRootComponents, - [nameof(IBlazorWebView.OnExternalNavigationStarting)] = MapOnExternalNavigationStarting, }; public BlazorWebViewHandler() : base(BlazorWebViewMapper) @@ -43,12 +45,12 @@ public static void MapRootComponents(BlazorWebViewHandler handler, IBlazorWebVie #endif } - public static void MapOnExternalNavigationStarting(BlazorWebViewHandler handler, IBlazorWebView webView) + public static void MapNotifyExternalNavigationStarting(BlazorWebViewHandler handler, IBlazorWebView webView) { #if !NETSTANDARD if (webView is BlazorWebView bwv) { - handler.ExternalNavigationStarting = bwv.ExternalNavigationStarting; + handler.ExternalNavigationStarting = bwv.NotifyExternalNavigationStarting; } #endif } diff --git a/src/BlazorWebView/src/Maui/IBlazorWebView.cs b/src/BlazorWebView/src/Maui/IBlazorWebView.cs index ab52bc65fda7..d566b842ed6b 100644 --- a/src/BlazorWebView/src/Maui/IBlazorWebView.cs +++ b/src/BlazorWebView/src/Maui/IBlazorWebView.cs @@ -1,6 +1,5 @@ using System; using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Components.WebView; using Microsoft.Extensions.FileProviders; using Microsoft.Maui; @@ -16,7 +15,7 @@ public interface IBlazorWebView : IView /// Allows customizing how external links are opened. /// Opens external links in the system browser by default. /// - event EventHandler? OnExternalNavigationStarting; + event EventHandler? ExternalNavigationStarting; /// /// Creates a file provider for static assets used in the . The default implementation diff --git a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs index 9ea8f05ca483..6437edca251f 100644 --- a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs +++ b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs @@ -5,8 +5,8 @@ namespace Microsoft.AspNetCore.Components.WebView /// /// Used to provide information about a link (]]>) clicked within a Blazor WebView. /// - /// `_blank` target links will always open in the default browser - /// thus the OnExternalNavigationStarting won't be called. + /// Anchor tags with target="_blank" will always open in the default + /// browser and the ExternalNavigationStarting event won't be called. /// public class ExternalLinkNavigationEventArgs : EventArgs { diff --git a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs index 56e3d6e0863e..684056f9af24 100644 --- a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs +++ b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs @@ -3,8 +3,8 @@ /// /// Link handling policy for anchor tags ]]> within a Blazor WebView. /// - /// `_blank` target links will always open in the default browser - /// regardless of the policy. + /// Anchor tags with target="_blank" will always open in the default + /// browser and the ExternalNavigationStarting event won't be called. /// public enum ExternalLinkNavigationPolicy { @@ -14,7 +14,7 @@ public enum ExternalLinkNavigationPolicy OpenInExternalBrowser, /// - /// Opens anchor tags ]]> in the WebView. This is not recommended. + /// Opens anchor tags ]]> in the WebView. This is not recommended unless the content of the URL is fully trusted. /// OpenInWebView, diff --git a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs index bedda07c08fb..466221054c44 100644 --- a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs +++ b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs @@ -185,7 +185,7 @@ private void CoreWebView2_NavigationStarting(object sender, CoreWebView2Navigati if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenInExternalBrowser) { - LaunchUri(uri); + LaunchUriInExternalBrowser(uri); } args.Cancel = callbackArgs.ExternalLinkNavigationPolicy != ExternalLinkNavigationPolicy.OpenInWebView; @@ -198,12 +198,12 @@ private void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindo // The ExternalLinkCallback is not invoked. if (Uri.TryCreate(args.Uri, UriKind.RelativeOrAbsolute, out var uri)) { - LaunchUri(uri); + LaunchUriInExternalBrowser(uri); args.Handled = true; } } - private void LaunchUri(Uri uri) + private void LaunchUriInExternalBrowser(Uri uri) { #if WEBVIEW2_WINFORMS || WEBVIEW2_WPF using (var launchBrowser = new Process()) @@ -213,7 +213,6 @@ private void LaunchUri(Uri uri) launchBrowser.Start(); } #elif WEBVIEW2_MAUI - _ = Launcher.LaunchUriAsync(uri); #endif } diff --git a/src/BlazorWebView/src/Wpf/BlazorWebView.cs b/src/BlazorWebView/src/Wpf/BlazorWebView.cs index f3e5d9ed001e..b5b81c700de7 100644 --- a/src/BlazorWebView/src/Wpf/BlazorWebView.cs +++ b/src/BlazorWebView/src/Wpf/BlazorWebView.cs @@ -50,10 +50,10 @@ public class BlazorWebView : Control, IAsyncDisposable typeMetadata: new PropertyMetadata(OnServicesPropertyChanged)); /// - /// The backing store for the property. + /// The backing store for the property. /// - public static readonly DependencyProperty OnExternalNavigationStartingProperty = DependencyProperty.Register( - name: nameof(OnExternalNavigationStarting), + public static readonly DependencyProperty ExternalNavigationStartingProperty = DependencyProperty.Register( + name: nameof(ExternalNavigationStarting), propertyType: typeof(EventHandler), ownerType: typeof(BlazorWebView)); #endregion @@ -110,10 +110,10 @@ public string HostPage /// Allows customizing how external links are opened. /// Opens external links in the system browser by default. /// - public EventHandler OnExternalNavigationStarting + public EventHandler ExternalNavigationStarting { - get => (EventHandler)GetValue(OnExternalNavigationStartingProperty); - set => SetValue(OnExternalNavigationStartingProperty, value); + get => (EventHandler)GetValue(ExternalNavigationStartingProperty); + set => SetValue(ExternalNavigationStartingProperty, value); } /// @@ -189,13 +189,15 @@ private void StartWebViewCoreIfPossible() var fileProvider = CreateFileProvider(contentRootDirFullPath); - _webviewManager = new WebView2WebViewManager(_webview, + _webviewManager = new WebView2WebViewManager( + _webview, Services, ComponentsDispatcher, fileProvider, RootComponents.JSComponents, hostPageRelativePath, - (args) => OnExternalNavigationStarting?.Invoke(this, args)); + (args) => ExternalNavigationStarting?.Invoke(this, args)); + foreach (var rootComponent in RootComponents) { // Since the page isn't loaded yet, this will always complete synchronously From 27be008e874054215bc98759559e8e82a6f6eb98 Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Wed, 23 Feb 2022 16:32:06 -0800 Subject: [PATCH 12/14] Remove ordering dependency during property mapping --- .../src/Maui/BlazorWebViewHandler.cs | 4 +--- .../Maui/Windows/BlazorWebViewHandler.Windows.cs | 2 +- .../src/Maui/Windows/WinUIWebViewManager.cs | 4 ++-- .../src/SharedSource/WebView2WebViewManager.cs | 15 +++++++++++++-- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs index 42e207f7bd12..631587316be9 100644 --- a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs +++ b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs @@ -10,11 +10,9 @@ public partial class BlazorWebViewHandler { public static readonly PropertyMapper BlazorWebViewMapper = new(ViewMapper) { - // Note the order of the initialization matters. We must set the ExternalNavigationStarting action before starting the core webview. - [nameof(IBlazorWebView.ExternalNavigationStarting)] = MapNotifyExternalNavigationStarting, - [nameof(IBlazorWebView.HostPage)] = MapHostPage, [nameof(IBlazorWebView.RootComponents)] = MapRootComponents, + [nameof(IBlazorWebView.ExternalNavigationStarting)] = MapNotifyExternalNavigationStarting, }; public BlazorWebViewHandler() : base(BlazorWebViewMapper) diff --git a/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs b/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs index e60f076f4653..2e8e2eab9d7f 100644 --- a/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs +++ b/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs @@ -66,7 +66,7 @@ private void StartWebViewCoreIfPossible() VirtualView.JSComponents, hostPageRelativePath, contentRootDir, - ExternalNavigationStarting); + this); if (RootComponents != null) { diff --git a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs index ff42a2f36195..cc8b32ce1a50 100644 --- a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs @@ -30,8 +30,8 @@ public WinUIWebViewManager( JSComponentConfigurationStore jsComponents, string hostPageRelativePath, string contentRootDir, - Action? externalNavigationStarting) - : base(webview, services, dispatcher, fileProvider, jsComponents, hostPageRelativePath, externalNavigationStarting) + BlazorWebViewHandler webViewHandler) + : base(webview, services, dispatcher, fileProvider, jsComponents, hostPageRelativePath, webViewHandler) { _webview = webview; _hostPageRelativePath = hostPageRelativePath; diff --git a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs index 466221054c44..69fd9d9ce2ca 100644 --- a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs +++ b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs @@ -26,6 +26,7 @@ using Microsoft.Web.WebView2.Core; using WebView2Control = Microsoft.Web.WebView2.Wpf.WebView2; #elif WEBVIEW2_MAUI +using Microsoft.AspNetCore.Components.WebView.Maui; using Microsoft.Web.WebView2.Core; using WebView2Control = Microsoft.UI.Xaml.Controls.WebView2; using System.Runtime.InteropServices.WindowsRuntime; @@ -54,7 +55,7 @@ public class WebView2WebViewManager : WebViewManager private readonly Action _externalNavigationStarting; #elif WEBVIEW2_MAUI private protected CoreWebView2Environment? _coreWebView2Environment; - private readonly Action? _externalNavigationStarting; + private readonly BlazorWebViewHandler _blazorWebViewHandler; #endif /// @@ -75,13 +76,18 @@ public WebView2WebViewManager( #if WEBVIEW2_WINFORMS || WEBVIEW2_WPF Action externalNavigationStarting #elif WEBVIEW2_MAUI - Action? externalNavigationStarting + BlazorWebViewHandler blazorWebViewHandler #endif ) : base(services, dispatcher, new Uri(AppOrigin), fileProvider, jsComponents, hostPageRelativePath) { _webview = webview; + +#if WEBVIEW2_WINFORMS || WEBVIEW2_WPF _externalNavigationStarting = externalNavigationStarting; +#elif WEBVIEW2_MAUI + _blazorWebViewHandler = blazorWebViewHandler; +#endif // Unfortunately the CoreWebView2 can only be instantiated asynchronously. // We want the external API to behave as if initalization is synchronous, @@ -181,7 +187,12 @@ private void CoreWebView2_NavigationStarting(object sender, CoreWebView2Navigati if (Uri.TryCreate(args.Uri, UriKind.RelativeOrAbsolute, out var uri) && uri.Host != AppHostAddress) { var callbackArgs = new ExternalLinkNavigationEventArgs(uri); + +#if WEBVIEW2_WINFORMS || WEBVIEW2_WPF _externalNavigationStarting?.Invoke(callbackArgs); +#elif WEBVIEW2_MAUI + _blazorWebViewHandler.ExternalNavigationStarting?.Invoke(callbackArgs); +#endif if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenInExternalBrowser) { From d4fb97bc42dff685f8cee4fed537fd6278b3b207 Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Thu, 24 Feb 2022 11:15:52 -0800 Subject: [PATCH 13/14] @blowdart feedback --- .../src/Maui/Android/BlazorWebViewHandler.Android.cs | 3 ++- .../src/Maui/Android/WebKitWebViewClient.cs | 2 +- src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs | 4 ++-- .../src/SharedSource/ExternalLinkNavigationPolicy.cs | 10 ++++++---- .../src/SharedSource/WebView2WebViewManager.cs | 2 +- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs b/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs index b764cd871381..df9fc286cafc 100644 --- a/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs +++ b/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs @@ -1,6 +1,7 @@ using System; using Android.Webkit; using Microsoft.Extensions.FileProviders; +using Microsoft.Maui; using Microsoft.Maui.Handlers; using static Android.Views.ViewGroup; using Path = System.IO.Path; @@ -23,7 +24,7 @@ protected override BlazorAndroidWebView CreatePlatformView() #pragma warning restore 618 }; - // To allow overriding ExternalLinkMode.OpenInWebView and open links in browser with a _blank target + // To allow overriding ExternalLinkMode.InsecureOpenInWebView and open links in browser with a _blank target blazorAndroidWebView.Settings.SetSupportMultipleWindows(true); BlazorAndroidWebView.SetWebContentsDebuggingEnabled(enabled: true); diff --git a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs index d4f336e7189e..74dab83f61fd 100644 --- a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs +++ b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs @@ -55,7 +55,7 @@ request is not null && _webViewHandler.Context.StartActivity(intent); } - if (callbackArgs.ExternalLinkNavigationPolicy != ExternalLinkNavigationPolicy.OpenInWebView) + if (callbackArgs.ExternalLinkNavigationPolicy != ExternalLinkNavigationPolicy.InsecureOpenInWebView) { return true; } diff --git a/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs b/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs index 99fb3aa26686..bcf23d6a2fd2 100644 --- a/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs @@ -191,7 +191,7 @@ public override void DecidePolicy(WKWebView webView, WKNavigationAction navigati } else if (callbackArgs.Uri.Host == BlazorWebView.AppHostAddress) { - callbackArgs.ExternalLinkNavigationPolicy = ExternalLinkNavigationPolicy.OpenInWebView; + callbackArgs.ExternalLinkNavigationPolicy = ExternalLinkNavigationPolicy.InsecureOpenInWebView; } else { @@ -205,7 +205,7 @@ public override void DecidePolicy(WKWebView webView, WKNavigationAction navigati UIApplication.SharedApplication.OpenUrl(url); } - if (callbackArgs.ExternalLinkNavigationPolicy != ExternalLinkNavigationPolicy.OpenInWebView) + if (callbackArgs.ExternalLinkNavigationPolicy != ExternalLinkNavigationPolicy.InsecureOpenInWebView) { // Cancel any further navigation as we've either opened the link in the external browser // or canceled the underlying navigation action. diff --git a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs index 684056f9af24..9c9fee79cc1d 100644 --- a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs +++ b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs @@ -1,7 +1,7 @@ namespace Microsoft.AspNetCore.Components.WebView { /// - /// Link handling policy for anchor tags ]]> within a Blazor WebView. + /// External link handling policy for anchor tags ]]> within a Blazor WebView. /// /// Anchor tags with target="_blank" will always open in the default /// browser and the ExternalNavigationStarting event won't be called. @@ -9,14 +9,16 @@ public enum ExternalLinkNavigationPolicy { /// - /// Opens anchor tags ]]> in the system default browser. + /// Allows navigation to external links using the system default browser. + /// This is the default navigation policy. /// OpenInExternalBrowser, /// - /// Opens anchor tags ]]> in the WebView. This is not recommended unless the content of the URL is fully trusted. + /// Allows navigation to external links within the Blazor WebView. + /// This navigation policy can introduce security concerns and should not be enabled unless you can ensure all external links are fully trusted /// - OpenInWebView, + InsecureOpenInWebView, /// /// Cancels the current navigation attempt to an external link. diff --git a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs index 69fd9d9ce2ca..3dcc70589962 100644 --- a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs +++ b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs @@ -199,7 +199,7 @@ private void CoreWebView2_NavigationStarting(object sender, CoreWebView2Navigati LaunchUriInExternalBrowser(uri); } - args.Cancel = callbackArgs.ExternalLinkNavigationPolicy != ExternalLinkNavigationPolicy.OpenInWebView; + args.Cancel = callbackArgs.ExternalLinkNavigationPolicy != ExternalLinkNavigationPolicy.InsecureOpenInWebView; } } From 20b4f7cceda702f23226fe681fa21b7c672f394e Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Thu, 24 Feb 2022 12:00:24 -0800 Subject: [PATCH 14/14] @Eilon feedback --- .../src/SharedSource/ExternalLinkNavigationEventArgs.cs | 2 +- .../src/SharedSource/ExternalLinkNavigationPolicy.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs index 6437edca251f..cfd0aab807f8 100644 --- a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs +++ b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs @@ -18,7 +18,7 @@ public ExternalLinkNavigationEventArgs(Uri uri) /// /// External URI to be navigated to. /// - public Uri Uri { get; set; } + public Uri Uri { get; } /// /// The policy to use when opening external links from the webview. diff --git a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs index 9c9fee79cc1d..36e1fe4485ee 100644 --- a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs +++ b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs @@ -16,7 +16,7 @@ public enum ExternalLinkNavigationPolicy /// /// Allows navigation to external links within the Blazor WebView. - /// This navigation policy can introduce security concerns and should not be enabled unless you can ensure all external links are fully trusted + /// This navigation policy can introduce security concerns and should not be enabled unless you can ensure all external links are fully trusted. /// InsecureOpenInWebView,