From d55872899477b7ff8a11c73f65e509d8d6a3a077 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Tue, 15 Mar 2022 13:53:44 -0700 Subject: [PATCH 01/18] Add support for fallback URL in intent-schemed URI Also fixed crashing issues when a scheme has no handler. This is consistent with other platforms. --- .../src/Maui/Android/WebKitWebViewClient.cs | 85 +++++++++++++++++-- 1 file changed, 80 insertions(+), 5 deletions(-) diff --git a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs index 8f9bfc6ce82c..216f0aa4927b 100644 --- a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs +++ b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs @@ -1,10 +1,12 @@ using System; using System.IO; +using System.Web; using Android.Content; using Android.Runtime; using Android.Webkit; using AWebView = Android.Webkit.WebView; using AUri = Android.Net.Uri; +using Java.Net; namespace Microsoft.AspNetCore.Components.WebView.Maui { @@ -35,13 +37,33 @@ 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 (request?.Url is { } url) + { + if (url.Scheme is "http" or "https") + { + if (TryOverrideHttpUrlLoading(view, request, url.ToString())) + { + return true; + } + } + else + { + TryStartActivityFromUri(url.ToString(), uriHasIntentScheme: url.Scheme == "intent"); + return true; + } + } + + return base.ShouldOverrideUrlLoading(view, request); + } + + private bool TryOverrideHttpUrlLoading(AWebView? view, IWebResourceRequest? request, string? requestUri) + { if (Uri.TryCreate(requestUri, UriKind.RelativeOrAbsolute, out var uri)) { if (uri.Host == BlazorWebView.AppHostAddress && - view is not null && - request is not null && - request.IsRedirect && + view is not null && + request is not null && + request.IsRedirect && request.IsForMainFrame) { view.LoadUrl(uri.ToString()); @@ -65,7 +87,60 @@ request is not null && } } - return base.ShouldOverrideUrlLoading(view, request); + return false; + } + + private void TryStartActivityFromUri(string? uriString, bool uriHasIntentScheme) + { + if (_webViewHandler is null || uriString is null) + { + return; + } + + try + { + var intent = Intent.ParseUri(uriString, IntentUriType.Scheme); + + if (intent is not null) + { + _webViewHandler.Context.StartActivity(intent); + } + } + catch (URISyntaxException) + { + // No-op. The URI was malformed. + return; + } + catch (ActivityNotFoundException) + { + if (!uriHasIntentScheme) + { + // No-op. This behavior is consistent with other platforms when a URI with an unknown scheme is clicked. + return; + } + + // Chrome for Android allows specifying a fallback URL in case an intent cannot be resolved. + // Since the Android WebView cannot handle "intent"-schemed URIs, we add our own support for the fallback + // URL here. + // See: https://developer.chrome.com/docs/multidevice/android/intents/ + + const string FallbackUrlQueryParameterPrefix = "S.browser_fallback_url="; + + var fallbackUrlQueryParameterPrefixLocation = uriString.LastIndexOf(FallbackUrlQueryParameterPrefix); + + if (fallbackUrlQueryParameterPrefixLocation != -1) + { + var fallbackUrlQueryParameterValueStart = fallbackUrlQueryParameterPrefixLocation + FallbackUrlQueryParameterPrefix.Length; + var fallbackUrlQueryParameterValueEnd = uriString.IndexOf(';', fallbackUrlQueryParameterValueStart); + + if (fallbackUrlQueryParameterValueEnd != -1) + { + var encodedFallbackUrlString = uriString[fallbackUrlQueryParameterValueStart..fallbackUrlQueryParameterValueEnd]; + var fallbackUrlString = HttpUtility.UrlDecode(encodedFallbackUrlString); + TryStartActivityFromUri(fallbackUrlString, uriHasIntentScheme: false); + } + } + } } public override WebResourceResponse? ShouldInterceptRequest(AWebView? view, IWebResourceRequest? request) From 24f00181b5917938bd6a5553e092ed899c1118e1 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Thu, 17 Mar 2022 12:04:17 -0700 Subject: [PATCH 02/18] Remove support for intent fallback URL --- .../src/Maui/Android/WebKitWebViewClient.cs | 104 ++++-------------- .../src/Maui/Extensions/UriExtensions.cs | 19 ++-- 2 files changed, 31 insertions(+), 92 deletions(-) diff --git a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs index 4bb55ed156df..07e030bcef72 100644 --- a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs +++ b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs @@ -37,47 +37,38 @@ 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. - if (request?.Url is { } url) + var requestUri = request?.Url?.ToString(); + if (Uri.TryCreate(requestUri, UriKind.RelativeOrAbsolute, out var uri)) { - if (url.Scheme is "http" or "https") + if (AppOriginUri.IsBaseOfPage(uri)) { - if (TryOverrideHttpUrlLoading(view, request, url.ToString())) + if (view is not null && + request is not null && + request.IsRedirect && + request.IsForMainFrame) { + view.LoadUrl(uri.ToString()); return true; } } - else - { - TryStartActivityFromUri(url.ToString(), uriHasIntentScheme: url.Scheme == "intent"); - return true; - } - } - - return base.ShouldOverrideUrlLoading(view, request); - } - - private bool TryOverrideHttpUrlLoading(AWebView? view, IWebResourceRequest? request, string? requestUri) - { - if (Uri.TryCreate(requestUri, UriKind.RelativeOrAbsolute, out var uri)) - { - if (uri.Host == BlazorWebView.AppHostAddress && - view is not null && - request is not null && - request.IsRedirect && - request.IsForMainFrame) - { - view.LoadUrl(uri.ToString()); - return true; - } - else if (uri.Host != BlazorWebView.AppHostAddress && _webViewHandler != null) + else if (_webViewHandler != null) { var callbackArgs = new ExternalLinkNavigationEventArgs(uri); _webViewHandler.ExternalNavigationStarting?.Invoke(callbackArgs); if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenInExternalBrowser) { - var intent = new Intent(Intent.ActionView, AUri.Parse(requestUri)); - _webViewHandler.Context.StartActivity(intent); + var intent = Intent.ParseUri(requestUri, IntentUriType.Scheme); + + try + { + _webViewHandler.Context.StartActivity(intent); + } + catch (ActivityNotFoundException) + { + // Do nothing. This is consistent with the behavior on other platforms when a URL with + // unknown scheme is clicked. + } } if (callbackArgs.ExternalLinkNavigationPolicy != ExternalLinkNavigationPolicy.InsecureOpenInWebView) @@ -87,60 +78,7 @@ request is not null && } } - return false; - } - - private void TryStartActivityFromUri(string? uriString, bool uriHasIntentScheme) - { - if (_webViewHandler is null || uriString is null) - { - return; - } - - try - { - var intent = Intent.ParseUri(uriString, IntentUriType.Scheme); - - if (intent is not null) - { - _webViewHandler.Context.StartActivity(intent); - } - } - catch (URISyntaxException) - { - // No-op. The URI was malformed. - return; - } - catch (ActivityNotFoundException) - { - if (!uriHasIntentScheme) - { - // No-op. This behavior is consistent with other platforms when a URI with an unknown scheme is clicked. - return; - } - - // Chrome for Android allows specifying a fallback URL in case an intent cannot be resolved. - // Since the Android WebView cannot handle "intent"-schemed URIs, we add our own support for the fallback - // URL here. - // See: https://developer.chrome.com/docs/multidevice/android/intents/ - - const string FallbackUrlQueryParameterPrefix = "S.browser_fallback_url="; - - var fallbackUrlQueryParameterPrefixLocation = uriString.LastIndexOf(FallbackUrlQueryParameterPrefix); - - if (fallbackUrlQueryParameterPrefixLocation != -1) - { - var fallbackUrlQueryParameterValueStart = fallbackUrlQueryParameterPrefixLocation + FallbackUrlQueryParameterPrefix.Length; - var fallbackUrlQueryParameterValueEnd = uriString.IndexOf(';', fallbackUrlQueryParameterValueStart); - - if (fallbackUrlQueryParameterValueEnd != -1) - { - var encodedFallbackUrlString = uriString[fallbackUrlQueryParameterValueStart..fallbackUrlQueryParameterValueEnd]; - var fallbackUrlString = HttpUtility.UrlDecode(encodedFallbackUrlString); - TryStartActivityFromUri(fallbackUrlString, uriHasIntentScheme: false); - } - } - } + return base.ShouldOverrideUrlLoading(view, request); } public override WebResourceResponse? ShouldInterceptRequest(AWebView? view, IWebResourceRequest? request) diff --git a/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs b/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs index 3767e19237c5..bab76e71e5c3 100644 --- a/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs +++ b/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs @@ -6,15 +6,16 @@ namespace Microsoft.AspNetCore.Components.WebView.Maui internal static class UriExtensions { internal static bool IsBaseOfPage(this Uri baseUri, string? uriString) - { - if (Path.HasExtension(uriString)) - { - // If the path ends in a file extension, it's not referring to a page. - return false; - } + => !Path.HasExtension(uriString) + && Uri.TryCreate(uriString, UriKind.RelativeOrAbsolute, out var uri) + && IsBaseOfPageCore(baseUri, uri); - var uri = new Uri(uriString!); - return baseUri.IsBaseOf(uri); - } + internal static bool IsBaseOfPage(this Uri baseUri, Uri uri) + => !Path.HasExtension(uri.OriginalString) + && IsBaseOfPageCore(baseUri, uri); + + private static bool IsBaseOfPageCore(Uri baseUri, Uri uri) + => (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) + && baseUri.IsBaseOf(uri); } } From 1139d32b2085f85e1c7ca20c65f12794de5cc5c8 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Thu, 17 Mar 2022 12:20:02 -0700 Subject: [PATCH 03/18] Update WebKitWebViewClient.cs --- src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs index 07e030bcef72..9a1011c64b1d 100644 --- a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs +++ b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs @@ -1,12 +1,8 @@ using System; -using System.IO; -using System.Web; using Android.Content; using Android.Runtime; using Android.Webkit; using AWebView = Android.Webkit.WebView; -using AUri = Android.Net.Uri; -using Java.Net; namespace Microsoft.AspNetCore.Components.WebView.Maui { From 3fea471acb25048eb092251f07074282cbb813fb Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Thu, 17 Mar 2022 12:30:53 -0700 Subject: [PATCH 04/18] Ignore URI parsing errors. --- .../src/Maui/Android/WebKitWebViewClient.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs index 9a1011c64b1d..91116dab1e5f 100644 --- a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs +++ b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs @@ -2,6 +2,7 @@ using Android.Content; using Android.Runtime; using Android.Webkit; +using Java.Net; using AWebView = Android.Webkit.WebView; namespace Microsoft.AspNetCore.Components.WebView.Maui @@ -54,16 +55,20 @@ request is not null && if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenInExternalBrowser) { - var intent = Intent.ParseUri(requestUri, IntentUriType.Scheme); - try { + var intent = Intent.ParseUri(requestUri, IntentUriType.Scheme); _webViewHandler.Context.StartActivity(intent); } + catch (URISyntaxException) + { + // This can occur if there is a problem with the URI formatting given its specified scheme. + // Other platforms will silently ignore formatting issues, so we do the same here. + } catch (ActivityNotFoundException) { - // Do nothing. This is consistent with the behavior on other platforms when a URL with - // unknown scheme is clicked. + // Do nothing if there is no activity to handle the intent. This is consistent with the + // behavior on other platforms when a URL with an unknown scheme is clicked. } } From afcf60a1317b73523fcf46f8c2b84ae4b5d4015d Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Tue, 22 Mar 2022 14:04:24 -0700 Subject: [PATCH 05/18] OpenInExternalBrowser -> OpenExternally. --- src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs | 2 +- src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs | 4 ++-- .../src/SharedSource/ExternalLinkNavigationEventArgs.cs | 2 +- .../src/SharedSource/ExternalLinkNavigationPolicy.cs | 4 ++-- src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs index 91116dab1e5f..a97758e43d5a 100644 --- a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs +++ b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs @@ -53,7 +53,7 @@ request is not null && var callbackArgs = new ExternalLinkNavigationEventArgs(uri); _webViewHandler.ExternalNavigationStarting?.Invoke(callbackArgs); - if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenInExternalBrowser) + if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenExternally) { try { diff --git a/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs b/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs index 807fa0cb3cd0..85cb214eeb6b 100644 --- a/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs @@ -209,7 +209,7 @@ public override void DecidePolicy(WKWebView webView, WKNavigationAction navigati if (navigationAction.TargetFrame is null) { // Open in a new browser window regardless of ExternalLinkNavigationPolicy - callbackArgs.ExternalLinkNavigationPolicy = ExternalLinkNavigationPolicy.OpenInExternalBrowser; + callbackArgs.ExternalLinkNavigationPolicy = ExternalLinkNavigationPolicy.OpenExternally; } else if (callbackArgs.Uri.Host == BlazorWebView.AppHostAddress) { @@ -222,7 +222,7 @@ public override void DecidePolicy(WKWebView webView, WKNavigationAction navigati var url = new NSUrl(callbackArgs.Uri.ToString()); - if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenInExternalBrowser) + if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenExternally) { UIApplication.SharedApplication.OpenUrl(url); } diff --git a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs index 2b9ddd74f409..bb65189ea838 100644 --- a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs +++ b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs @@ -30,6 +30,6 @@ public ExternalLinkNavigationEventArgs(Uri uri) /// /// Defaults to opening links in an external browser. /// - public ExternalLinkNavigationPolicy ExternalLinkNavigationPolicy { get; set; } = ExternalLinkNavigationPolicy.OpenInExternalBrowser; + public ExternalLinkNavigationPolicy ExternalLinkNavigationPolicy { get; set; } = ExternalLinkNavigationPolicy.OpenExternally; } } diff --git a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs index 36e1fe4485ee..fe5deb12ca01 100644 --- a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs +++ b/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs @@ -9,10 +9,10 @@ public enum ExternalLinkNavigationPolicy { /// - /// Allows navigation to external links using the system default browser. + /// Allows navigation to external links using an app determined by the system. /// This is the default navigation policy. /// - OpenInExternalBrowser, + OpenExternally, /// /// Allows navigation to external links within the Blazor WebView. diff --git a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs index 9130017a8d43..b15b772e5327 100644 --- a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs +++ b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs @@ -262,7 +262,7 @@ private void CoreWebView2_NavigationStarting(object sender, CoreWebView2Navigati _blazorWebViewHandler.ExternalNavigationStarting?.Invoke(callbackArgs); #endif - if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenInExternalBrowser) + if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenExternally) { LaunchUriInExternalBrowser(uri); } From 34f61af729b2571fa0ecde93d4ce505b7f5f54fc Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Wed, 23 Mar 2022 15:58:53 -0700 Subject: [PATCH 06/18] Refactored URL loading for Android and Windows. iOS is still TODO. --- Microsoft.Maui.sln | 26 ++++---- .../Android/BlazorWebViewHandler.Android.cs | 2 +- .../src/Maui/Android/WebKitWebViewClient.cs | 66 +++++++------------ src/BlazorWebView/src/Maui/BlazorWebView.cs | 6 +- .../src/Maui/BlazorWebViewHandler.cs | 10 +-- src/BlazorWebView/src/Maui/IBlazorWebView.cs | 2 +- .../src/Maui/iOS/BlazorWebViewHandler.iOS.cs | 2 +- .../src/Maui/iOS/IOSWebViewManager.cs | 17 +++-- .../ExternalLinkNavigationPolicy.cs | 28 -------- ...ionEventArgs.cs => UrlLoadingEventArgs.cs} | 22 ++++--- .../src/SharedSource/UrlLoadingStrategy.cs | 30 +++++++++ .../SharedSource/WebView2WebViewManager.cs | 22 ++++--- .../src/WindowsForms/BlazorWebView.cs | 4 +- src/BlazorWebView/src/Wpf/BlazorWebView.cs | 16 ++--- 14 files changed, 125 insertions(+), 128 deletions(-) delete mode 100644 src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs rename src/BlazorWebView/src/SharedSource/{ExternalLinkNavigationEventArgs.cs => UrlLoadingEventArgs.cs} (50%) create mode 100644 src/BlazorWebView/src/SharedSource/UrlLoadingStrategy.cs diff --git a/Microsoft.Maui.sln b/Microsoft.Maui.sln index 4f4473863ee5..e98b32c0449a 100644 --- a/Microsoft.Maui.sln +++ b/Microsoft.Maui.sln @@ -200,24 +200,13 @@ 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\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\UrlLoadingStrategy.cs = src\BlazorWebView\src\SharedSource\UrlLoadingStrategy.cs src\BlazorWebView\src\SharedSource\WebView2WebViewManager.cs = src\BlazorWebView\src\SharedSource\WebView2WebViewManager.cs + src\BlazorWebView\src\SharedSource\UrlLoadingEventArgs.cs = src\BlazorWebView\src\SharedSource\UrlLoadingEventArgs.cs EndProjectSection EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - src\Compatibility\ControlGallery\src\Issues.Shared\Compatibility.ControlGallery.Issues.Shared.projitems*{0a39a74b-6f7a-4d41-84f2-b0ccdce899df}*SharedItemsImports = 5 - src\Compatibility\ControlGallery\src\UITests.Shared\Compatibility.UITests.projitems*{0a39a74b-6f7a-4d41-84f2-b0ccdce899df}*SharedItemsImports = 5 - src\Compatibility\ControlGallery\src\Issues.Shared\Compatibility.ControlGallery.Issues.Shared.projitems*{772e2531-2270-429a-9afb-4aa0104aef27}*SharedItemsImports = 5 - src\Compatibility\ControlGallery\src\Issues.Shared\Compatibility.ControlGallery.Issues.Shared.projitems*{a34ebe01-25bf-4e69-a2dc-2288dc625541}*SharedItemsImports = 5 - src\Compatibility\ControlGallery\src\UITests.Shared\Compatibility.UITests.projitems*{a34ebe01-25bf-4e69-a2dc-2288dc625541}*SharedItemsImports = 5 - src\Compatibility\ControlGallery\src\Issues.Shared\Compatibility.ControlGallery.Issues.Shared.projitems*{ae2513cb-4e5e-4e5c-8237-88954d4c9433}*SharedItemsImports = 13 - src\Compatibility\ControlGallery\src\UITests.Shared\Compatibility.UITests.projitems*{e175485b-3c8c-47d7-8dd5-f7fed627eb25}*SharedItemsImports = 13 - src\Compatibility\ControlGallery\src\Issues.Shared\Compatibility.ControlGallery.Issues.Shared.projitems*{eadd8100-b3ae-4a31-92c4-267a64a1c6eb}*SharedItemsImports = 5 - src\Compatibility\ControlGallery\src\UITests.Shared\Compatibility.UITests.projitems*{eadd8100-b3ae-4a31-92c4-267a64a1c6eb}*SharedItemsImports = 5 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU @@ -608,4 +597,15 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0B8ABEAD-D2B5-4370-A187-62B5ABE4EE50} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + src\Compatibility\ControlGallery\src\Issues.Shared\Compatibility.ControlGallery.Issues.Shared.projitems*{0a39a74b-6f7a-4d41-84f2-b0ccdce899df}*SharedItemsImports = 5 + src\Compatibility\ControlGallery\src\UITests.Shared\Compatibility.UITests.projitems*{0a39a74b-6f7a-4d41-84f2-b0ccdce899df}*SharedItemsImports = 5 + src\Compatibility\ControlGallery\src\Issues.Shared\Compatibility.ControlGallery.Issues.Shared.projitems*{772e2531-2270-429a-9afb-4aa0104aef27}*SharedItemsImports = 5 + src\Compatibility\ControlGallery\src\Issues.Shared\Compatibility.ControlGallery.Issues.Shared.projitems*{a34ebe01-25bf-4e69-a2dc-2288dc625541}*SharedItemsImports = 5 + src\Compatibility\ControlGallery\src\UITests.Shared\Compatibility.UITests.projitems*{a34ebe01-25bf-4e69-a2dc-2288dc625541}*SharedItemsImports = 5 + src\Compatibility\ControlGallery\src\Issues.Shared\Compatibility.ControlGallery.Issues.Shared.projitems*{ae2513cb-4e5e-4e5c-8237-88954d4c9433}*SharedItemsImports = 13 + src\Compatibility\ControlGallery\src\UITests.Shared\Compatibility.UITests.projitems*{e175485b-3c8c-47d7-8dd5-f7fed627eb25}*SharedItemsImports = 13 + src\Compatibility\ControlGallery\src\Issues.Shared\Compatibility.ControlGallery.Issues.Shared.projitems*{eadd8100-b3ae-4a31-92c4-267a64a1c6eb}*SharedItemsImports = 5 + src\Compatibility\ControlGallery\src\UITests.Shared\Compatibility.UITests.projitems*{eadd8100-b3ae-4a31-92c4-267a64a1c6eb}*SharedItemsImports = 5 + EndGlobalSection EndGlobal diff --git a/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs b/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs index 2f28827a8f56..a1b76ceaf0b5 100644 --- a/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs +++ b/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs @@ -26,7 +26,7 @@ protected override BlazorAndroidWebView CreatePlatformView() #pragma warning restore 618 }; - // To allow overriding ExternalLinkMode.InsecureOpenInWebView and open links in browser with a _blank target + // To allow overriding ExternalLinkMode.OpenInWebView and open links in browser with a _blank target blazorAndroidWebView.Settings.SetSupportMultipleWindows(true); BlazorAndroidWebView.SetWebContentsDebuggingEnabled(enabled: DeveloperTools.Enabled); diff --git a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs index a97758e43d5a..e70fe82f8e9d 100644 --- a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs +++ b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs @@ -31,55 +31,39 @@ protected WebKitWebViewClient(IntPtr javaReference, JniHandleOwnership transfer) } public override bool ShouldOverrideUrlLoading(AWebView? view, IWebResourceRequest? request) + => ShouldOverrideUrlLoadingCore(request) || base.ShouldOverrideUrlLoading(view, request); + + private bool ShouldOverrideUrlLoadingCore(IWebResourceRequest? request) { - // 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 (Uri.TryCreate(requestUri, UriKind.RelativeOrAbsolute, out var uri)) + if (_webViewHandler is null || !Uri.TryCreate(request?.Url?.ToString(), UriKind.RelativeOrAbsolute, out var uri)) + { + return false; + } + + var callbackArgs = UrlLoadingEventArgs.CreateWithDefaultLoadingStrategy(uri, AppOriginUri); + _webViewHandler.UrlLoading?.Invoke(callbackArgs); + + if (callbackArgs.UrlLoadingStrategy == UrlLoadingStrategy.OpenExternally) { - if (AppOriginUri.IsBaseOfPage(uri)) + try + { + var intent = Intent.ParseUri(uri.OriginalString, IntentUriType.Scheme); + _webViewHandler.Context.StartActivity(intent); + } + catch (URISyntaxException) { - if (view is not null && - request is not null && - request.IsRedirect && - request.IsForMainFrame) - { - view.LoadUrl(uri.ToString()); - return true; - } + // This can occur if there is a problem with the URI formatting given its specified scheme. + // Other platforms will silently ignore formatting issues, so we do the same here. } - else if (_webViewHandler != null) + catch (ActivityNotFoundException) { - var callbackArgs = new ExternalLinkNavigationEventArgs(uri); - _webViewHandler.ExternalNavigationStarting?.Invoke(callbackArgs); - - if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenExternally) - { - try - { - var intent = Intent.ParseUri(requestUri, IntentUriType.Scheme); - _webViewHandler.Context.StartActivity(intent); - } - catch (URISyntaxException) - { - // This can occur if there is a problem with the URI formatting given its specified scheme. - // Other platforms will silently ignore formatting issues, so we do the same here. - } - catch (ActivityNotFoundException) - { - // Do nothing if there is no activity to handle the intent. This is consistent with the - // behavior on other platforms when a URL with an unknown scheme is clicked. - } - } - - if (callbackArgs.ExternalLinkNavigationPolicy != ExternalLinkNavigationPolicy.InsecureOpenInWebView) - { - return true; - } + // Do nothing if there is no activity to handle the intent. This is consistent with the + // behavior on other platforms when a URL with an unknown scheme is clicked. } + return true; } - return base.ShouldOverrideUrlLoading(view, request); + return callbackArgs.UrlLoadingStrategy != UrlLoadingStrategy.OpenInWebView; } public override WebResourceResponse? ShouldInterceptRequest(AWebView? view, IWebResourceRequest? request) diff --git a/src/BlazorWebView/src/Maui/BlazorWebView.cs b/src/BlazorWebView/src/Maui/BlazorWebView.cs index 9512cde3674e..db366066b22f 100644 --- a/src/BlazorWebView/src/Maui/BlazorWebView.cs +++ b/src/BlazorWebView/src/Maui/BlazorWebView.cs @@ -34,7 +34,7 @@ public BlazorWebView() public RootComponentsCollection RootComponents { get; } /// - public event EventHandler? ExternalNavigationStarting; + public event EventHandler? UrlLoading; /// public virtual IFileProvider CreateFileProvider(string contentRootDir) @@ -43,9 +43,9 @@ public virtual IFileProvider CreateFileProvider(string contentRootDir) return ((BlazorWebViewHandler)(Handler!)).CreateFileProvider(contentRootDir); } - internal void NotifyExternalNavigationStarting(ExternalLinkNavigationEventArgs args) + internal void NotifyUrlLoading(UrlLoadingEventArgs args) { - ExternalNavigationStarting?.Invoke(this, args); + UrlLoading?.Invoke(this, args); } } } diff --git a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs index 142e974d308e..7563a6593188 100644 --- a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs +++ b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs @@ -16,7 +16,7 @@ public partial class BlazorWebViewHandler { [nameof(IBlazorWebView.HostPage)] = MapHostPage, [nameof(IBlazorWebView.RootComponents)] = MapRootComponents, - [nameof(IBlazorWebView.ExternalNavigationStarting)] = MapNotifyExternalNavigationStarting, + [nameof(IBlazorWebView.UrlLoading)] = MapNotifyUrlLoading, }; /// @@ -63,23 +63,23 @@ public static void MapRootComponents(BlazorWebViewHandler handler, IBlazorWebVie } /// - /// Maps the property to the specified handler. + /// Maps the property to the specified handler. /// /// The . /// The . - public static void MapNotifyExternalNavigationStarting(BlazorWebViewHandler handler, IBlazorWebView webView) + public static void MapNotifyUrlLoading(BlazorWebViewHandler handler, IBlazorWebView webView) { #if !NETSTANDARD if (webView is BlazorWebView bwv) { - handler.ExternalNavigationStarting = bwv.NotifyExternalNavigationStarting; + handler.UrlLoading = bwv.NotifyUrlLoading; } #endif } #if !NETSTANDARD private string? HostPage { get; set; } - internal Action? ExternalNavigationStarting; + internal Action? UrlLoading; private RootComponentsCollection? _rootComponents; private RootComponentsCollection? RootComponents diff --git a/src/BlazorWebView/src/Maui/IBlazorWebView.cs b/src/BlazorWebView/src/Maui/IBlazorWebView.cs index 2edc8a22350e..542ffc3c8536 100644 --- a/src/BlazorWebView/src/Maui/IBlazorWebView.cs +++ b/src/BlazorWebView/src/Maui/IBlazorWebView.cs @@ -29,7 +29,7 @@ public interface IBlazorWebView : IView /// Allows customizing how external links are opened. /// Opens external links in the system browser by default. /// - event EventHandler? ExternalNavigationStarting; + event EventHandler? UrlLoading; /// /// Creates a file provider for static assets used in the . The default implementation diff --git a/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs b/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs index 4f7486491d9f..9183c94543cd 100644 --- a/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs +++ b/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs @@ -20,7 +20,7 @@ public partial class BlazorWebViewHandler : ViewHandler decisionHandler) { - var callbackArgs = new ExternalLinkNavigationEventArgs(new Uri(navigationAction.Request.Url.ToString())); + var uri = new Uri(navigationAction.Request.Url.ToString()); + var callbackArgs = UrlLoadingEventArgs.CreateWithDefaultLoadingStrategy(uri, BlazorWebViewHandler.AppOriginUri); + + // TODO: Test this on iOS, see how the logic directly below should be updated. // TargetFrame is null for navigation to a new window (`_blank`) if (navigationAction.TargetFrame is null) { - // Open in a new browser window regardless of ExternalLinkNavigationPolicy - callbackArgs.ExternalLinkNavigationPolicy = ExternalLinkNavigationPolicy.OpenExternally; + // Open in a new browser window regardless of UrlLoadingStrategy + callbackArgs.UrlLoadingStrategy = UrlLoadingStrategy.OpenExternally; } else if (callbackArgs.Uri.Host == BlazorWebView.AppHostAddress) { - callbackArgs.ExternalLinkNavigationPolicy = ExternalLinkNavigationPolicy.InsecureOpenInWebView; + callbackArgs.UrlLoadingStrategy = UrlLoadingStrategy.OpenInWebView; } else { - _webView.ExternalNavigationStarting?.Invoke(callbackArgs); + _webView.UrlLoading?.Invoke(callbackArgs); } var url = new NSUrl(callbackArgs.Uri.ToString()); - if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenExternally) + if (callbackArgs.UrlLoadingStrategy == UrlLoadingStrategy.OpenExternally) { UIApplication.SharedApplication.OpenUrl(url); } - if (callbackArgs.ExternalLinkNavigationPolicy != ExternalLinkNavigationPolicy.InsecureOpenInWebView) + if (callbackArgs.UrlLoadingStrategy != UrlLoadingStrategy.OpenInWebView) { // 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 deleted file mode 100644 index fe5deb12ca01..000000000000 --- a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Microsoft.AspNetCore.Components.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. - /// - public enum ExternalLinkNavigationPolicy - { - /// - /// Allows navigation to external links using an app determined by the system. - /// This is the default navigation policy. - /// - OpenExternally, - - /// - /// 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. - /// - InsecureOpenInWebView, - - /// - /// Cancels the current navigation attempt to an external link. - /// - CancelNavigation - } -} diff --git a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs b/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs similarity index 50% rename from src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs rename to src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs index bb65189ea838..561157acc03d 100644 --- a/src/BlazorWebView/src/SharedSource/ExternalLinkNavigationEventArgs.cs +++ b/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs @@ -6,18 +6,24 @@ namespace Microsoft.AspNetCore.Components.WebView /// Used to provide information about a link (]]>) clicked within a Blazor WebView. /// /// Anchor tags with target="_blank" will always open in the default - /// browser and the ExternalNavigationStarting event won't be called. + /// browser and the UrlLoading event won't be called. /// /// - public class ExternalLinkNavigationEventArgs : EventArgs + public class UrlLoadingEventArgs : EventArgs { - /// - /// Initializes a new instance of . - /// - /// The external URI to be navigated to. - public ExternalLinkNavigationEventArgs(Uri uri) + internal static UrlLoadingEventArgs CreateWithDefaultLoadingStrategy(Uri urlToLoad, Uri appOriginUri) + { + var strategy = appOriginUri.IsBaseOf(urlToLoad) ? + UrlLoadingStrategy.OpenInWebView : + UrlLoadingStrategy.OpenExternally; + + return new(urlToLoad, strategy); + } + + private UrlLoadingEventArgs(Uri uri, UrlLoadingStrategy urlLoadingStrategy) { Uri = uri; + UrlLoadingStrategy = urlLoadingStrategy; } /// @@ -30,6 +36,6 @@ public ExternalLinkNavigationEventArgs(Uri uri) /// /// Defaults to opening links in an external browser. /// - public ExternalLinkNavigationPolicy ExternalLinkNavigationPolicy { get; set; } = ExternalLinkNavigationPolicy.OpenExternally; + public UrlLoadingStrategy UrlLoadingStrategy { get; set; } = UrlLoadingStrategy.OpenExternally; } } diff --git a/src/BlazorWebView/src/SharedSource/UrlLoadingStrategy.cs b/src/BlazorWebView/src/SharedSource/UrlLoadingStrategy.cs new file mode 100644 index 000000000000..52abddd25272 --- /dev/null +++ b/src/BlazorWebView/src/SharedSource/UrlLoadingStrategy.cs @@ -0,0 +1,30 @@ +namespace Microsoft.AspNetCore.Components.WebView +{ + /// + /// URL loading strategy for anchor tags ]]> within a Blazor WebView. + /// + /// Anchor tags with target="_blank" will always open in the default + /// browser and the UrlLoading event won't be called. + /// + public enum UrlLoadingStrategy + { + /// + /// Allows loading URLs using an app determined by the system. + /// This is the default strategy for URLs with an external host. + /// + OpenExternally, + + /// + /// Allows loading URLs within the Blazor WebView. + /// This is the default strategy for URLs with a host matching the app origin. + /// This strategy can introduce security concerns and should not be used for external links + /// unless you can ensure they are fully trusted. + /// + OpenInWebView, + + /// + /// Cancels the current URL loading attempt. + /// + CancelLoad + } +} diff --git a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs index b15b772e5327..335aeac0492a 100644 --- a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs +++ b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs @@ -57,12 +57,14 @@ public class WebView2WebViewManager : WebViewManager /// protected static readonly string AppOrigin = $"https://{AppHostAddress}/"; + private static readonly Uri AppOriginUri = new(AppOrigin); + private readonly WebView2Control _webview; private readonly Task _webviewReadyTask; #if WEBVIEW2_WINFORMS || WEBVIEW2_WPF private protected CoreWebView2Environment _coreWebView2Environment; - private readonly Action _externalNavigationStarting; + private readonly Action _urlLoading; private readonly BlazorWebViewDeveloperTools _developerTools; /// @@ -74,7 +76,7 @@ public class WebView2WebViewManager : WebViewManager /// Provides static content to the webview. /// Describes configuration for adding, removing, and updating root components from JavaScript code. /// Path to the host page within the . - /// Callback invoked when external navigation starts. + /// Callback invoked when a url is about to load. public WebView2WebViewManager( WebView2Control webview!!, IServiceProvider services, @@ -82,7 +84,7 @@ public WebView2WebViewManager( IFileProvider fileProvider, JSComponentConfigurationStore jsComponents, string hostPageRelativePath, - Action externalNavigationStarting) + Action urlLoading) : base(services, dispatcher, new Uri(AppOrigin), fileProvider, jsComponents, hostPageRelativePath) { @@ -103,7 +105,7 @@ public WebView2WebViewManager( #endif _webview = webview; - _externalNavigationStarting = externalNavigationStarting; + _urlLoading = urlLoading; _developerTools = services.GetRequiredService(); // Unfortunately the CoreWebView2 can only be instantiated asynchronously. @@ -252,22 +254,22 @@ protected virtual void QueueBlazorStart() private void CoreWebView2_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs args) { - if (Uri.TryCreate(args.Uri, UriKind.RelativeOrAbsolute, out var uri) && uri.Host != AppHostAddress) + if (Uri.TryCreate(args.Uri, UriKind.RelativeOrAbsolute, out var uri)) { - var callbackArgs = new ExternalLinkNavigationEventArgs(uri); + var callbackArgs = UrlLoadingEventArgs.CreateWithDefaultLoadingStrategy(uri, AppOriginUri); #if WEBVIEW2_WINFORMS || WEBVIEW2_WPF - _externalNavigationStarting?.Invoke(callbackArgs); + _urlLoading?.Invoke(callbackArgs); #elif WEBVIEW2_MAUI - _blazorWebViewHandler.ExternalNavigationStarting?.Invoke(callbackArgs); + _blazorWebViewHandler.UrlLoading?.Invoke(callbackArgs); #endif - if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenExternally) + if (callbackArgs.UrlLoadingStrategy == UrlLoadingStrategy.OpenExternally) { LaunchUriInExternalBrowser(uri); } - args.Cancel = callbackArgs.ExternalLinkNavigationPolicy != ExternalLinkNavigationPolicy.InsecureOpenInWebView; + args.Cancel = callbackArgs.UrlLoadingStrategy != UrlLoadingStrategy.OpenInWebView; } } diff --git a/src/BlazorWebView/src/WindowsForms/BlazorWebView.cs b/src/BlazorWebView/src/WindowsForms/BlazorWebView.cs index 6ba6bf487c3b..a977485bb9de 100644 --- a/src/BlazorWebView/src/WindowsForms/BlazorWebView.cs +++ b/src/BlazorWebView/src/WindowsForms/BlazorWebView.cs @@ -118,7 +118,7 @@ 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; + public EventHandler UrlLoading; private void OnHostPagePropertyChanged() => StartWebViewCoreIfPossible(); @@ -170,7 +170,7 @@ private void StartWebViewCoreIfPossible() fileProvider, RootComponents.JSComponents, hostPageRelativePath, - (args) => ExternalNavigationStarting?.Invoke(this, args)); + (args) => UrlLoading?.Invoke(this, args)); foreach (var rootComponent in RootComponents) { diff --git a/src/BlazorWebView/src/Wpf/BlazorWebView.cs b/src/BlazorWebView/src/Wpf/BlazorWebView.cs index b04de9b7df42..629b112147a1 100644 --- a/src/BlazorWebView/src/Wpf/BlazorWebView.cs +++ b/src/BlazorWebView/src/Wpf/BlazorWebView.cs @@ -51,11 +51,11 @@ 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 ExternalNavigationStartingProperty = DependencyProperty.Register( - name: nameof(ExternalNavigationStarting), - propertyType: typeof(EventHandler), + public static readonly DependencyProperty UrlLoadingProperty = DependencyProperty.Register( + name: nameof(UrlLoading), + propertyType: typeof(EventHandler), ownerType: typeof(BlazorWebView)); #endregion @@ -111,10 +111,10 @@ public string HostPage /// Allows customizing how external links are opened. /// Opens external links in the system browser by default. /// - public EventHandler ExternalNavigationStarting + public EventHandler UrlLoading { - get => (EventHandler)GetValue(ExternalNavigationStartingProperty); - set => SetValue(ExternalNavigationStartingProperty, value); + get => (EventHandler)GetValue(UrlLoadingProperty); + set => SetValue(UrlLoadingProperty, value); } /// @@ -197,7 +197,7 @@ private void StartWebViewCoreIfPossible() fileProvider, RootComponents.JSComponents, hostPageRelativePath, - (args) => ExternalNavigationStarting?.Invoke(this, args)); + (args) => UrlLoading?.Invoke(this, args)); foreach (var rootComponent in RootComponents) { From fa9a3da9a3cef470ec465e36abaa9e3bbd3b7079 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Wed, 23 Mar 2022 17:23:02 -0700 Subject: [PATCH 07/18] Improved WinUI navigation fallback logic. Page URLs with patterns that make them look like files are now supported. Still need to decide how to approach this on the other platforms. --- .../src/Maui/Windows/WinUIWebViewManager.cs | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs index 0bd1e6948fb8..f7f7d13328fa 100644 --- a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs @@ -87,27 +87,20 @@ protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRe var uri = new Uri(requestUri); if (new Uri(AppOrigin).IsBaseOf(uri)) { + var hasRequestBeenHandled = false; var relativePath = new Uri(AppOrigin).MakeRelativeUri(uri).ToString(); - // If the path does not end in a file extension (or is empty), it's most likely referring to a page, - // in which case we should allow falling back on the host page. - if (allowFallbackOnHostPage && !Path.HasExtension(relativePath)) + if (Path.HasExtension(relativePath)) { - relativePath = _hostPageRelativePath; + // This looks like a file, so we'll attempt to fetch content using the existing relative path. + hasRequestBeenHandled = await TryHandleWinUIStorageWebResourceRequestAsync(eventArgs, relativePath); } - relativePath = Path.Combine(_contentRootDir, relativePath.Replace('/', '\\')); - var winUIItem = await Package.Current.InstalledLocation.TryGetItemAsync(relativePath); - if (winUIItem != null) + if (!hasRequestBeenHandled && allowFallbackOnHostPage) { - statusCode = 200; - statusMessage = "OK"; - var contentType = StaticContentProvider.GetResponseContentTypeOrDefault(relativePath); - headers = StaticContentProvider.GetResponseHeaders(contentType); - var headerString = GetHeaderString(headers); - var stream = await Package.Current.InstalledLocation.OpenStreamForReadAsync(relativePath); - - eventArgs.Response = _coreWebView2Environment!.CreateWebResourceResponse(stream.AsRandomAccessStream(), statusCode, statusMessage, headerString); + // Either no content was found at the path, or we didn't attempt to load content because of the path format. + // Since we're allowed to fall back on the host page, try to do so. + hasRequestBeenHandled = await TryHandleWinUIStorageWebResourceRequestAsync(eventArgs, _hostPageRelativePath); } } } @@ -116,6 +109,28 @@ protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRe deferral.Complete(); } + private async Task TryHandleWinUIStorageWebResourceRequestAsync(CoreWebView2WebResourceRequestedEventArgs eventArgs, string relativePath) + { + var path = Path.Combine(_contentRootDir, relativePath.Replace('/', '\\')); + var winUIItem = await Package.Current.InstalledLocation.TryGetItemAsync(path); + + if (winUIItem is null) + { + return false; + } + + var statusCode = 200; + var statusMessage = "OK"; + var contentType = StaticContentProvider.GetResponseContentTypeOrDefault(path); + var headers = StaticContentProvider.GetResponseHeaders(contentType); + var headerString = GetHeaderString(headers); + var stream = await Package.Current.InstalledLocation.OpenStreamForReadAsync(path); + + eventArgs.Response = _coreWebView2Environment!.CreateWebResourceResponse(stream.AsRandomAccessStream(), statusCode, statusMessage, headerString); + + return true; + } + /// protected override void QueueBlazorStart() { From 0fe6ed98ab7a6cc85d38d0808c1761daac493a2e Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Wed, 23 Mar 2022 17:31:24 -0700 Subject: [PATCH 08/18] Update UriExtensions.cs --- .../src/Maui/Extensions/UriExtensions.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs b/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs index bab76e71e5c3..f537b8d3626b 100644 --- a/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs +++ b/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs @@ -6,16 +6,15 @@ namespace Microsoft.AspNetCore.Components.WebView.Maui internal static class UriExtensions { internal static bool IsBaseOfPage(this Uri baseUri, string? uriString) - => !Path.HasExtension(uriString) - && Uri.TryCreate(uriString, UriKind.RelativeOrAbsolute, out var uri) - && IsBaseOfPageCore(baseUri, uri); + { + if (Path.HasExtension(uriString)) + { + // If the path ends in a file extension, it's not referring to a page. + return false; + } - internal static bool IsBaseOfPage(this Uri baseUri, Uri uri) - => !Path.HasExtension(uri.OriginalString) - && IsBaseOfPageCore(baseUri, uri); - - private static bool IsBaseOfPageCore(Uri baseUri, Uri uri) - => (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) - && baseUri.IsBaseOf(uri); + var uri = new Uri(uriString!); + return baseUri.IsBaseOf(uri); + } } -} +} \ No newline at end of file From 1fd5be5d36972a5a6f07a89b081615914711f371 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Thu, 24 Mar 2022 11:34:18 -0700 Subject: [PATCH 09/18] Update UrlLoadingEventArgs --- .../src/Maui/Extensions/UriExtensions.cs | 2 +- .../src/Maui/iOS/IOSWebViewManager.cs | 4 ++-- .../src/SharedSource/UrlLoadingEventArgs.cs | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs b/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs index f537b8d3626b..3767e19237c5 100644 --- a/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs +++ b/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs @@ -17,4 +17,4 @@ internal static bool IsBaseOfPage(this Uri baseUri, string? uriString) return baseUri.IsBaseOf(uri); } } -} \ No newline at end of file +} diff --git a/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs b/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs index e295de09cbb4..9de600e7052a 100644 --- a/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs @@ -214,7 +214,7 @@ public override void DecidePolicy(WKWebView webView, WKNavigationAction navigati // Open in a new browser window regardless of UrlLoadingStrategy callbackArgs.UrlLoadingStrategy = UrlLoadingStrategy.OpenExternally; } - else if (callbackArgs.Uri.Host == BlazorWebView.AppHostAddress) + else if (callbackArgs.Url.Host == BlazorWebView.AppHostAddress) { callbackArgs.UrlLoadingStrategy = UrlLoadingStrategy.OpenInWebView; } @@ -223,7 +223,7 @@ public override void DecidePolicy(WKWebView webView, WKNavigationAction navigati _webView.UrlLoading?.Invoke(callbackArgs); } - var url = new NSUrl(callbackArgs.Uri.ToString()); + var url = new NSUrl(callbackArgs.Url.ToString()); if (callbackArgs.UrlLoadingStrategy == UrlLoadingStrategy.OpenExternally) { diff --git a/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs b/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs index 561157acc03d..9ae18517b955 100644 --- a/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs +++ b/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs @@ -20,22 +20,22 @@ internal static UrlLoadingEventArgs CreateWithDefaultLoadingStrategy(Uri urlToLo return new(urlToLoad, strategy); } - private UrlLoadingEventArgs(Uri uri, UrlLoadingStrategy urlLoadingStrategy) + private UrlLoadingEventArgs(Uri url, UrlLoadingStrategy urlLoadingStrategy) { - Uri = uri; + Url = url; UrlLoadingStrategy = urlLoadingStrategy; } /// - /// Gets the external URI to be navigated to. + /// Gets the URL to be loaded. /// - public Uri Uri { get; } + public Uri Url { get; } /// - /// The policy to use when opening external links from the webview. - /// - /// Defaults to opening links in an external browser. + /// The policy to use when loading links from the webview. + /// Defaults to unless has a host + /// matching the app origin, in which case the default becomes . /// - public UrlLoadingStrategy UrlLoadingStrategy { get; set; } = UrlLoadingStrategy.OpenExternally; + public UrlLoadingStrategy UrlLoadingStrategy { get; set; } } } From bcac49a5fc855fd4ed5f155ac18f4eef29cfe29c Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Thu, 24 Mar 2022 14:39:02 -0700 Subject: [PATCH 10/18] Fix for iOS --- .../src/Maui/iOS/IOSWebViewManager.cs | 26 ++++++++----------- .../src/SharedSource/UrlLoadingEventArgs.cs | 2 +- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs b/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs index 9de600e7052a..a961616a837c 100644 --- a/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs @@ -203,34 +203,30 @@ public override void DidStartProvisionalNavigation(WKWebView webView, WKNavigati public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action decisionHandler) { - var uri = new Uri(navigationAction.Request.Url.ToString()); - var callbackArgs = UrlLoadingEventArgs.CreateWithDefaultLoadingStrategy(uri, BlazorWebViewHandler.AppOriginUri); - - // TODO: Test this on iOS, see how the logic directly below should be updated. + var requestUrl = navigationAction.Request.Url; + UrlLoadingStrategy strategy; // TargetFrame is null for navigation to a new window (`_blank`) if (navigationAction.TargetFrame is null) { // Open in a new browser window regardless of UrlLoadingStrategy - callbackArgs.UrlLoadingStrategy = UrlLoadingStrategy.OpenExternally; - } - else if (callbackArgs.Url.Host == BlazorWebView.AppHostAddress) - { - callbackArgs.UrlLoadingStrategy = UrlLoadingStrategy.OpenInWebView; + strategy = UrlLoadingStrategy.OpenExternally; } else { + // Invoke the UrlLoading event to allow overriding the default link handling behavior + var uri = new Uri(requestUrl.ToString()); + var callbackArgs = UrlLoadingEventArgs.CreateWithDefaultLoadingStrategy(uri, BlazorWebViewHandler.AppOriginUri); _webView.UrlLoading?.Invoke(callbackArgs); + strategy = callbackArgs.UrlLoadingStrategy; } - var url = new NSUrl(callbackArgs.Url.ToString()); - - if (callbackArgs.UrlLoadingStrategy == UrlLoadingStrategy.OpenExternally) + if (strategy == UrlLoadingStrategy.OpenExternally) { - UIApplication.SharedApplication.OpenUrl(url); + UIApplication.SharedApplication.OpenUrl(requestUrl); } - if (callbackArgs.UrlLoadingStrategy != UrlLoadingStrategy.OpenInWebView) + if (strategy != UrlLoadingStrategy.OpenInWebView) { // Cancel any further navigation as we've either opened the link in the external browser // or canceled the underlying navigation action. @@ -240,7 +236,7 @@ public override void DecidePolicy(WKWebView webView, WKNavigationAction navigati if (navigationAction.TargetFrame!.MainFrame) { - _currentUri = url; + _currentUri = requestUrl; } decisionHandler(WKNavigationActionPolicy.Allow); diff --git a/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs b/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs index 9ae18517b955..080ae2d1c2b4 100644 --- a/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs +++ b/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs @@ -13,7 +13,7 @@ public class UrlLoadingEventArgs : EventArgs { internal static UrlLoadingEventArgs CreateWithDefaultLoadingStrategy(Uri urlToLoad, Uri appOriginUri) { - var strategy = appOriginUri.IsBaseOf(urlToLoad) ? + var strategy = urlToLoad.Host == appOriginUri.Host ? UrlLoadingStrategy.OpenInWebView : UrlLoadingStrategy.OpenExternally; From 0049fbece1e2724c4d45a20f34c725f3a41eeafb Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Thu, 24 Mar 2022 16:09:31 -0700 Subject: [PATCH 11/18] Cleanup, improved comments. --- src/BlazorWebView/src/Maui/Android/BlazorWebChromeClient.cs | 2 +- .../src/Maui/Android/BlazorWebViewHandler.Android.cs | 2 +- src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs | 2 ++ src/BlazorWebView/src/Maui/IBlazorWebView.cs | 4 ++-- src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs | 4 ++++ src/BlazorWebView/src/SharedSource/UrlLoadingStrategy.cs | 5 +++-- src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs | 2 +- 7 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/BlazorWebView/src/Maui/Android/BlazorWebChromeClient.cs b/src/BlazorWebView/src/Maui/Android/BlazorWebChromeClient.cs index 8b387d2e1177..f06bc2efe49a 100644 --- a/src/BlazorWebView/src/Maui/Android/BlazorWebChromeClient.cs +++ b/src/BlazorWebView/src/Maui/Android/BlazorWebChromeClient.cs @@ -18,7 +18,7 @@ public override bool OnCreateWindow(Android.Webkit.WebView? view, bool isDialog, if (view?.Context is not null) { // Intercept _blank target tags to always open in device browser - // regardless of ExternalLinkMode.OpenInWebview + // regardless of UrlLoadingStrategy.OpenInWebview var requestUrl = view.GetHitTestResult().Extra; var intent = new Intent(Intent.ActionView, Uri.Parse(requestUrl)); view.Context.StartActivity(intent); diff --git a/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs b/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs index a1b76ceaf0b5..800ec6f49714 100644 --- a/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs +++ b/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs @@ -26,7 +26,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 UrlLoadingStrategy.OpenInWebView and open links in browser with a _blank target blazorAndroidWebView.Settings.SetSupportMultipleWindows(true); BlazorAndroidWebView.SetWebContentsDebuggingEnabled(enabled: DeveloperTools.Enabled); diff --git a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs index e70fe82f8e9d..caf991765e2a 100644 --- a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs +++ b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs @@ -40,6 +40,8 @@ private bool ShouldOverrideUrlLoadingCore(IWebResourceRequest? request) return false; } + // This method never gets called for navigation to a new window ('_blank'), + // so we know we can safely invoke the UrlLoading event. var callbackArgs = UrlLoadingEventArgs.CreateWithDefaultLoadingStrategy(uri, AppOriginUri); _webViewHandler.UrlLoading?.Invoke(callbackArgs); diff --git a/src/BlazorWebView/src/Maui/IBlazorWebView.cs b/src/BlazorWebView/src/Maui/IBlazorWebView.cs index 542ffc3c8536..6bf941b9ddd2 100644 --- a/src/BlazorWebView/src/Maui/IBlazorWebView.cs +++ b/src/BlazorWebView/src/Maui/IBlazorWebView.cs @@ -26,8 +26,8 @@ public interface IBlazorWebView : IView JSComponentConfigurationStore JSComponents { get; } /// - /// Allows customizing how external links are opened. - /// Opens external links in the system browser by default. + /// Allows customizing how links are opened. + /// By default, opens internal links in the webview and external links in an external app. /// event EventHandler? UrlLoading; diff --git a/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs b/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs index 080ae2d1c2b4..ee321d0f8149 100644 --- a/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs +++ b/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs @@ -35,6 +35,10 @@ private UrlLoadingEventArgs(Uri url, UrlLoadingStrategy urlLoadingStrategy) /// The policy to use when loading links from the webview. /// Defaults to unless has a host /// matching the app origin, in which case the default becomes . + /// + /// This value should not be changed to for external links + /// unless you can ensure they are fully trusted. + /// /// public UrlLoadingStrategy UrlLoadingStrategy { get; set; } } diff --git a/src/BlazorWebView/src/SharedSource/UrlLoadingStrategy.cs b/src/BlazorWebView/src/SharedSource/UrlLoadingStrategy.cs index 52abddd25272..ded4d608d11a 100644 --- a/src/BlazorWebView/src/SharedSource/UrlLoadingStrategy.cs +++ b/src/BlazorWebView/src/SharedSource/UrlLoadingStrategy.cs @@ -17,8 +17,9 @@ public enum UrlLoadingStrategy /// /// Allows loading URLs within the Blazor WebView. /// This is the default strategy for URLs with a host matching the app origin. - /// This strategy can introduce security concerns and should not be used for external links - /// unless you can ensure they are fully trusted. + /// + /// This strategy should not be used for external links unless you can ensure they are fully trusted. + /// /// OpenInWebView, diff --git a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs index 335aeac0492a..25768d1eaf8d 100644 --- a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs +++ b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs @@ -85,7 +85,7 @@ public WebView2WebViewManager( JSComponentConfigurationStore jsComponents, string hostPageRelativePath, Action urlLoading) - : base(services, dispatcher, new Uri(AppOrigin), fileProvider, jsComponents, hostPageRelativePath) + : base(services, dispatcher, AppOriginUri, fileProvider, jsComponents, hostPageRelativePath) { #if WEBVIEW2_WINFORMS From 1d4eb5ec1e77ef1d8ed351ed74356f7de484ba84 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Fri, 25 Mar 2022 13:46:21 -0700 Subject: [PATCH 12/18] Unify fallback behavior between Windows & Android --- .../src/Maui/Android/WebKitWebViewClient.cs | 26 +++++++++++-------- .../src/Maui/Windows/WinUIWebViewManager.cs | 4 +-- .../src/SharedSource/UrlLoadingEventArgs.cs | 2 +- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs index caf991765e2a..4694800718b6 100644 --- a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs +++ b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs @@ -75,18 +75,22 @@ private bool ShouldOverrideUrlLoadingCore(IWebResourceRequest? request) throw new ArgumentNullException(nameof(request)); } - var requestUri = request?.Url?.ToString(); - var allowFallbackOnHostPage = AppOriginUri.IsBaseOfPage(requestUri); - requestUri = QueryStringHelper.RemovePossibleQueryString(requestUri); - - if (requestUri != null && - _webViewHandler != null && - _webViewHandler.WebviewManager != null && - _webViewHandler.WebviewManager.TryGetResponseContentInternal(requestUri, allowFallbackOnHostPage, out var statusCode, out var statusMessage, out var content, out var headers)) + var requestUriString = QueryStringHelper.RemovePossibleQueryString(request?.Url?.ToString()); + + if (Uri.TryCreate(requestUriString, UriKind.RelativeOrAbsolute, out var requestUri)) { - var contentType = headers["Content-Type"]; + // We explicitly disallow fallback for framework files because StaticContentProvider prioritizes host page + // fallback over attempting to fetch framework files. + var allowFallbackOnHostPage = !requestUri.AbsolutePath.StartsWith("/_framework/", StringComparison.Ordinal); + + if (_webViewHandler != null && + _webViewHandler.WebviewManager != null && + _webViewHandler.WebviewManager.TryGetResponseContentInternal(requestUriString, allowFallbackOnHostPage, out var statusCode, out var statusMessage, out var content, out var headers)) + { + var contentType = headers["Content-Type"]; - return new WebResourceResponse(contentType, "UTF-8", statusCode, statusMessage, headers, content); + return new WebResourceResponse(contentType, "UTF-8", statusCode, statusMessage, headers, content); + } } return base.ShouldInterceptRequest(view, request); @@ -97,7 +101,7 @@ public override void OnPageFinished(AWebView? view, string? url) base.OnPageFinished(view, url); // TODO: How do we know this runs only once? - if (view != null && AppOriginUri.IsBaseOfPage(url)) + if (view != null && Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri) && AppOriginUri.IsBaseOf(uri)) { // Startup scripts must run in OnPageFinished. If scripts are run earlier they will have no lasting // effect because once the page content loads all the document state gets reset. diff --git a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs index f7f7d13328fa..7f63dc8990cd 100644 --- a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs @@ -90,7 +90,7 @@ protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRe var hasRequestBeenHandled = false; var relativePath = new Uri(AppOrigin).MakeRelativeUri(uri).ToString(); - if (Path.HasExtension(relativePath)) + if (!string.IsNullOrWhiteSpace(relativePath)) { // This looks like a file, so we'll attempt to fetch content using the existing relative path. hasRequestBeenHandled = await TryHandleWinUIStorageWebResourceRequestAsync(eventArgs, relativePath); @@ -100,7 +100,7 @@ protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRe { // Either no content was found at the path, or we didn't attempt to load content because of the path format. // Since we're allowed to fall back on the host page, try to do so. - hasRequestBeenHandled = await TryHandleWinUIStorageWebResourceRequestAsync(eventArgs, _hostPageRelativePath); + _ = await TryHandleWinUIStorageWebResourceRequestAsync(eventArgs, _hostPageRelativePath); } } } diff --git a/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs b/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs index ee321d0f8149..5bb0141a3491 100644 --- a/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs +++ b/src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs @@ -13,7 +13,7 @@ public class UrlLoadingEventArgs : EventArgs { internal static UrlLoadingEventArgs CreateWithDefaultLoadingStrategy(Uri urlToLoad, Uri appOriginUri) { - var strategy = urlToLoad.Host == appOriginUri.Host ? + var strategy = appOriginUri.IsBaseOf(urlToLoad) ? UrlLoadingStrategy.OpenInWebView : UrlLoadingStrategy.OpenExternally; From 148c3507f30db653138669028c95958a24487f98 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Fri, 25 Mar 2022 13:53:18 -0700 Subject: [PATCH 13/18] Improved fallback navigation for iOS --- .../src/Maui/iOS/BlazorWebViewHandler.iOS.cs | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs b/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs index 9183c94543cd..88b7f030191b 100644 --- a/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs +++ b/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs @@ -191,26 +191,31 @@ public void StartUrlSchemeTask(WKWebView webView, IWKUrlSchemeTask urlSchemeTask private byte[] GetResponseBytes(string url, out string contentType, out int statusCode) { - var allowFallbackOnHostPage = AppOriginUri.IsBaseOfPage(url); url = QueryStringHelper.RemovePossibleQueryString(url); - if (_webViewHandler._webviewManager!.TryGetResponseContentInternal(url, allowFallbackOnHostPage, out statusCode, out var statusMessage, out var content, out var headers)) + + if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var requestUri)) { - statusCode = 200; - using var ms = new MemoryStream(); + // We explicitly disallow fallback for framework files because StaticContentProvider prioritizes host page + // fallback over attempting to fetch framework files. + var allowFallbackOnHostPage = !requestUri.AbsolutePath.StartsWith("/_framework/", StringComparison.Ordinal); - content.CopyTo(ms); - content.Dispose(); + if (_webViewHandler._webviewManager!.TryGetResponseContentInternal(url, allowFallbackOnHostPage, out statusCode, out var statusMessage, out var content, out var headers)) + { + statusCode = 200; + using var ms = new MemoryStream(); - contentType = headers["Content-Type"]; + content.CopyTo(ms); + content.Dispose(); - return ms.ToArray(); - } - else - { - statusCode = 404; - contentType = string.Empty; - return Array.Empty(); + contentType = headers["Content-Type"]; + + return ms.ToArray(); + } } + + statusCode = 404; + contentType = string.Empty; + return Array.Empty(); } [Export("webView:stopURLSchemeTask:")] From efa9983acf6de90ae3273caaef2f121d9df99e14 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Fri, 25 Mar 2022 14:07:32 -0700 Subject: [PATCH 14/18] Delete UriExtensions.cs --- .../src/Maui/Extensions/UriExtensions.cs | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs diff --git a/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs b/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs deleted file mode 100644 index 3767e19237c5..000000000000 --- a/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.IO; - -namespace Microsoft.AspNetCore.Components.WebView.Maui -{ - internal static class UriExtensions - { - internal static bool IsBaseOfPage(this Uri baseUri, string? uriString) - { - if (Path.HasExtension(uriString)) - { - // If the path ends in a file extension, it's not referring to a page. - return false; - } - - var uri = new Uri(uriString!); - return baseUri.IsBaseOf(uri); - } - } -} From be51e3241e3ac74cbad3ccd9204c5a1fdfc6372c Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Mon, 28 Mar 2022 10:50:57 -0700 Subject: [PATCH 15/18] Revert URL fallback behavior for WinUI/Android --- .../src/Maui/Android/WebKitWebViewClient.cs | 26 +++++----- .../src/Maui/Extensions/UriExtensions.cs | 20 ++++++++ .../src/Maui/Windows/WinUIWebViewManager.cs | 47 +++++++------------ 3 files changed, 47 insertions(+), 46 deletions(-) create mode 100644 src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs diff --git a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs index 4694800718b6..caf991765e2a 100644 --- a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs +++ b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs @@ -75,22 +75,18 @@ private bool ShouldOverrideUrlLoadingCore(IWebResourceRequest? request) throw new ArgumentNullException(nameof(request)); } - var requestUriString = QueryStringHelper.RemovePossibleQueryString(request?.Url?.ToString()); - - if (Uri.TryCreate(requestUriString, UriKind.RelativeOrAbsolute, out var requestUri)) + var requestUri = request?.Url?.ToString(); + var allowFallbackOnHostPage = AppOriginUri.IsBaseOfPage(requestUri); + requestUri = QueryStringHelper.RemovePossibleQueryString(requestUri); + + if (requestUri != null && + _webViewHandler != null && + _webViewHandler.WebviewManager != null && + _webViewHandler.WebviewManager.TryGetResponseContentInternal(requestUri, allowFallbackOnHostPage, out var statusCode, out var statusMessage, out var content, out var headers)) { - // We explicitly disallow fallback for framework files because StaticContentProvider prioritizes host page - // fallback over attempting to fetch framework files. - var allowFallbackOnHostPage = !requestUri.AbsolutePath.StartsWith("/_framework/", StringComparison.Ordinal); - - if (_webViewHandler != null && - _webViewHandler.WebviewManager != null && - _webViewHandler.WebviewManager.TryGetResponseContentInternal(requestUriString, allowFallbackOnHostPage, out var statusCode, out var statusMessage, out var content, out var headers)) - { - var contentType = headers["Content-Type"]; + var contentType = headers["Content-Type"]; - return new WebResourceResponse(contentType, "UTF-8", statusCode, statusMessage, headers, content); - } + return new WebResourceResponse(contentType, "UTF-8", statusCode, statusMessage, headers, content); } return base.ShouldInterceptRequest(view, request); @@ -101,7 +97,7 @@ public override void OnPageFinished(AWebView? view, string? url) base.OnPageFinished(view, url); // TODO: How do we know this runs only once? - if (view != null && Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri) && AppOriginUri.IsBaseOf(uri)) + if (view != null && AppOriginUri.IsBaseOfPage(url)) { // Startup scripts must run in OnPageFinished. If scripts are run earlier they will have no lasting // effect because once the page content loads all the document state gets reset. diff --git a/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs b/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs new file mode 100644 index 000000000000..3767e19237c5 --- /dev/null +++ b/src/BlazorWebView/src/Maui/Extensions/UriExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.IO; + +namespace Microsoft.AspNetCore.Components.WebView.Maui +{ + internal static class UriExtensions + { + internal static bool IsBaseOfPage(this Uri baseUri, string? uriString) + { + if (Path.HasExtension(uriString)) + { + // If the path ends in a file extension, it's not referring to a page. + return false; + } + + var uri = new Uri(uriString!); + return baseUri.IsBaseOf(uri); + } + } +} diff --git a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs index 7f63dc8990cd..ffee96805449 100644 --- a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs @@ -87,20 +87,27 @@ protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRe var uri = new Uri(requestUri); if (new Uri(AppOrigin).IsBaseOf(uri)) { - var hasRequestBeenHandled = false; var relativePath = new Uri(AppOrigin).MakeRelativeUri(uri).ToString(); - if (!string.IsNullOrWhiteSpace(relativePath)) + // If the path does not end in a file extension (or is empty), it's most likely referring to a page, + // in which case we should allow falling back on the host page. + if (allowFallbackOnHostPage && !Path.HasExtension(relativePath)) { - // This looks like a file, so we'll attempt to fetch content using the existing relative path. - hasRequestBeenHandled = await TryHandleWinUIStorageWebResourceRequestAsync(eventArgs, relativePath); + relativePath = _hostPageRelativePath; } + relativePath = Path.Combine(_contentRootDir, relativePath.Replace('/', '\\')); - if (!hasRequestBeenHandled && allowFallbackOnHostPage) + var winUIItem = await Package.Current.InstalledLocation.TryGetItemAsync(relativePath); + if (winUIItem != null) { - // Either no content was found at the path, or we didn't attempt to load content because of the path format. - // Since we're allowed to fall back on the host page, try to do so. - _ = await TryHandleWinUIStorageWebResourceRequestAsync(eventArgs, _hostPageRelativePath); + statusCode = 200; + statusMessage = "OK"; + var contentType = StaticContentProvider.GetResponseContentTypeOrDefault(relativePath); + headers = StaticContentProvider.GetResponseHeaders(contentType); + var headerString = GetHeaderString(headers); + var stream = await Package.Current.InstalledLocation.OpenStreamForReadAsync(relativePath); + + eventArgs.Response = _coreWebView2Environment!.CreateWebResourceResponse(stream.AsRandomAccessStream(), statusCode, statusMessage, headerString); } } } @@ -109,28 +116,6 @@ protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRe deferral.Complete(); } - private async Task TryHandleWinUIStorageWebResourceRequestAsync(CoreWebView2WebResourceRequestedEventArgs eventArgs, string relativePath) - { - var path = Path.Combine(_contentRootDir, relativePath.Replace('/', '\\')); - var winUIItem = await Package.Current.InstalledLocation.TryGetItemAsync(path); - - if (winUIItem is null) - { - return false; - } - - var statusCode = 200; - var statusMessage = "OK"; - var contentType = StaticContentProvider.GetResponseContentTypeOrDefault(path); - var headers = StaticContentProvider.GetResponseHeaders(contentType); - var headerString = GetHeaderString(headers); - var stream = await Package.Current.InstalledLocation.OpenStreamForReadAsync(path); - - eventArgs.Response = _coreWebView2Environment!.CreateWebResourceResponse(stream.AsRandomAccessStream(), statusCode, statusMessage, headerString); - - return true; - } - /// protected override void QueueBlazorStart() { @@ -143,4 +128,4 @@ protected override void QueueBlazorStart() }; } } -} +} \ No newline at end of file From decd336488282dba743242926b1d63202a53642c Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Mon, 28 Mar 2022 10:52:02 -0700 Subject: [PATCH 16/18] Update WinUIWebViewManager.cs --- src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs index ffee96805449..0bd1e6948fb8 100644 --- a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs @@ -128,4 +128,4 @@ protected override void QueueBlazorStart() }; } } -} \ No newline at end of file +} From 69b429aeef3cae9f5816b8e1d1e092898335a81c Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Mon, 28 Mar 2022 11:09:10 -0700 Subject: [PATCH 17/18] Revert URL fallback behavior for iOS --- .../src/Maui/iOS/BlazorWebViewHandler.iOS.cs | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs b/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs index 88b7f030191b..9183c94543cd 100644 --- a/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs +++ b/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs @@ -191,31 +191,26 @@ public void StartUrlSchemeTask(WKWebView webView, IWKUrlSchemeTask urlSchemeTask private byte[] GetResponseBytes(string url, out string contentType, out int statusCode) { + var allowFallbackOnHostPage = AppOriginUri.IsBaseOfPage(url); url = QueryStringHelper.RemovePossibleQueryString(url); - - if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var requestUri)) + if (_webViewHandler._webviewManager!.TryGetResponseContentInternal(url, allowFallbackOnHostPage, out statusCode, out var statusMessage, out var content, out var headers)) { - // We explicitly disallow fallback for framework files because StaticContentProvider prioritizes host page - // fallback over attempting to fetch framework files. - var allowFallbackOnHostPage = !requestUri.AbsolutePath.StartsWith("/_framework/", StringComparison.Ordinal); - - if (_webViewHandler._webviewManager!.TryGetResponseContentInternal(url, allowFallbackOnHostPage, out statusCode, out var statusMessage, out var content, out var headers)) - { - statusCode = 200; - using var ms = new MemoryStream(); + statusCode = 200; + using var ms = new MemoryStream(); - content.CopyTo(ms); - content.Dispose(); + content.CopyTo(ms); + content.Dispose(); - contentType = headers["Content-Type"]; + contentType = headers["Content-Type"]; - return ms.ToArray(); - } + return ms.ToArray(); + } + else + { + statusCode = 404; + contentType = string.Empty; + return Array.Empty(); } - - statusCode = 404; - contentType = string.Empty; - return Array.Empty(); } [Export("webView:stopURLSchemeTask:")] From d29c7a335c8c2ffed399e48a3c805a48fd34270d Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Wed, 30 Mar 2022 08:50:08 -0700 Subject: [PATCH 18/18] Correct out-of-date XML documentation --- src/BlazorWebView/src/Wpf/BlazorWebView.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BlazorWebView/src/Wpf/BlazorWebView.cs b/src/BlazorWebView/src/Wpf/BlazorWebView.cs index 629b112147a1..252ba3a9e3b1 100644 --- a/src/BlazorWebView/src/Wpf/BlazorWebView.cs +++ b/src/BlazorWebView/src/Wpf/BlazorWebView.cs @@ -108,8 +108,8 @@ public string HostPage (RootComponentsCollection)GetValue(RootComponentsProperty); /// - /// Allows customizing how external links are opened. - /// Opens external links in the system browser by default. + /// Allows customizing how links are opened. + /// By default, opens internal links in the webview and external links in an external app. /// public EventHandler UrlLoading {