Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d558728
Add support for fallback URL in intent-schemed URI
MackinnonBuck Mar 15, 2022
607669c
Merge branch 'main' into mbuck/non-http-link-handling
MackinnonBuck Mar 17, 2022
24f0018
Remove support for intent fallback URL
MackinnonBuck Mar 17, 2022
1139d32
Update WebKitWebViewClient.cs
MackinnonBuck Mar 17, 2022
3fea471
Ignore URI parsing errors.
MackinnonBuck Mar 17, 2022
37d6091
Merge branch 'main' into mbuck/non-http-link-handling
MackinnonBuck Mar 22, 2022
afcf60a
OpenInExternalBrowser -> OpenExternally.
MackinnonBuck Mar 22, 2022
34f61af
Refactored URL loading for Android and Windows.
MackinnonBuck Mar 23, 2022
fa9a3da
Improved WinUI navigation fallback logic.
MackinnonBuck Mar 24, 2022
0fe6ed9
Update UriExtensions.cs
MackinnonBuck Mar 24, 2022
1fd5be5
Update UrlLoadingEventArgs
MackinnonBuck Mar 24, 2022
bcac49a
Fix for iOS
MackinnonBuck Mar 24, 2022
0049fbe
Cleanup, improved comments.
MackinnonBuck Mar 24, 2022
1d4eb5e
Unify fallback behavior between Windows & Android
MackinnonBuck Mar 25, 2022
148c350
Improved fallback navigation for iOS
MackinnonBuck Mar 25, 2022
efa9983
Delete UriExtensions.cs
MackinnonBuck Mar 25, 2022
51b688d
Merge branch 'main' into mbuck/non-http-link-handling
MackinnonBuck Mar 28, 2022
be51e32
Revert URL fallback behavior for WinUI/Android
MackinnonBuck Mar 28, 2022
decd336
Update WinUIWebViewManager.cs
MackinnonBuck Mar 28, 2022
69b429a
Revert URL fallback behavior for iOS
MackinnonBuck Mar 28, 2022
d29c7a3
Correct out-of-date XML documentation
MackinnonBuck Mar 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Microsoft.Maui.sln
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,10 @@ 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
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks.Droid", "src\Core\tests\Benchmarks.Droid\Benchmarks.Droid.csproj", "{5B56A734-D53C-4635-A53E-F889FCFCDD66}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public override bool OnCreateWindow(Android.Webkit.WebView? view, bool isDialog,
if (view?.Context is not null)
{
// Intercept _blank target <a> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 UrlLoadingStrategy.OpenInWebView and open links in browser with a _blank target
blazorAndroidWebView.Settings.SetSupportMultipleWindows(true);

BlazorAndroidWebView.SetWebContentsDebuggingEnabled(enabled: DeveloperTools.Enabled);
Expand Down
56 changes: 28 additions & 28 deletions src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
using System;
using System.IO;
using Android.Content;
using Android.Runtime;
using Android.Webkit;
using Java.Net;
using AWebView = Android.Webkit.WebView;
using AUri = Android.Net.Uri;

namespace Microsoft.AspNetCore.Components.WebView.Maui
{
Expand Down Expand Up @@ -32,40 +31,41 @@ 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;
}

// 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);

if (callbackArgs.UrlLoadingStrategy == UrlLoadingStrategy.OpenExternally)
{
if (uri.Host == BlazorWebView.AppHostAddress &&
view is not null &&
request is not null &&
request.IsRedirect &&
request.IsForMainFrame)
try
{
var intent = Intent.ParseUri(uri.OriginalString, IntentUriType.Scheme);
_webViewHandler.Context.StartActivity(intent);
}
catch (URISyntaxException)
{
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 (uri.Host != BlazorWebView.AppHostAddress && _webViewHandler != null)
catch (ActivityNotFoundException)
{
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);
}

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)
Expand Down
6 changes: 3 additions & 3 deletions src/BlazorWebView/src/Maui/BlazorWebView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public BlazorWebView()
public RootComponentsCollection RootComponents { get; }

/// <inheritdoc/>
public event EventHandler<ExternalLinkNavigationEventArgs>? ExternalNavigationStarting;
public event EventHandler<UrlLoadingEventArgs>? UrlLoading;

/// <inheritdoc/>
public virtual IFileProvider CreateFileProvider(string contentRootDir)
Expand All @@ -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);
}
}
}
10 changes: 5 additions & 5 deletions src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public partial class BlazorWebViewHandler
{
[nameof(IBlazorWebView.HostPage)] = MapHostPage,
[nameof(IBlazorWebView.RootComponents)] = MapRootComponents,
[nameof(IBlazorWebView.ExternalNavigationStarting)] = MapNotifyExternalNavigationStarting,
[nameof(IBlazorWebView.UrlLoading)] = MapNotifyUrlLoading,
};

/// <summary>
Expand Down Expand Up @@ -63,23 +63,23 @@ public static void MapRootComponents(BlazorWebViewHandler handler, IBlazorWebVie
}

/// <summary>
/// Maps the <see cref="BlazorWebView.NotifyExternalNavigationStarting"/> property to the specified handler.
/// Maps the <see cref="BlazorWebView.NotifyUrlLoading"/> property to the specified handler.
/// </summary>
/// <param name="handler">The <see cref="BlazorWebViewHandler"/>.</param>
/// <param name="webView">The <see cref="IBlazorWebView"/>.</param>
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<ExternalLinkNavigationEventArgs>? ExternalNavigationStarting;
internal Action<UrlLoadingEventArgs>? UrlLoading;

private RootComponentsCollection? _rootComponents;
private RootComponentsCollection? RootComponents
Expand Down
6 changes: 3 additions & 3 deletions src/BlazorWebView/src/Maui/IBlazorWebView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ public interface IBlazorWebView : IView
JSComponentConfigurationStore JSComponents { get; }

/// <summary>
/// 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.
/// </summary>
event EventHandler<ExternalLinkNavigationEventArgs>? ExternalNavigationStarting;
event EventHandler<UrlLoadingEventArgs>? UrlLoading;

/// <summary>
/// Creates a file provider for static assets used in the <see cref="BlazorWebView"/>. The default implementation
Expand Down
2 changes: 1 addition & 1 deletion src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public partial class BlazorWebViewHandler : ViewHandler<IBlazorWebView, WKWebVie
private IOSWebViewManager? _webviewManager;

internal const string AppOrigin = "app://" + BlazorWebView.AppHostAddress + "/";
private static readonly Uri AppOriginUri = new(AppOrigin);
internal static readonly Uri AppOriginUri = new(AppOrigin);
private const string BlazorInitScript = @"
window.__receiveMessageCallbacks = [];
window.__dispatchMessageCallback = function(message) {
Expand Down
27 changes: 13 additions & 14 deletions src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,31 +203,30 @@ public override void DidStartProvisionalNavigation(WKWebView webView, WKNavigati

public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action<WKNavigationActionPolicy> decisionHandler)
{
var callbackArgs = new ExternalLinkNavigationEventArgs(new Uri(navigationAction.Request.Url.ToString()));
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 ExternalLinkNavigationPolicy
callbackArgs.ExternalLinkNavigationPolicy = ExternalLinkNavigationPolicy.OpenInExternalBrowser;
}
else if (callbackArgs.Uri.Host == BlazorWebView.AppHostAddress)
{
callbackArgs.ExternalLinkNavigationPolicy = ExternalLinkNavigationPolicy.InsecureOpenInWebView;
// Open in a new browser window regardless of UrlLoadingStrategy
strategy = UrlLoadingStrategy.OpenExternally;
}
else
{
_webView.ExternalNavigationStarting?.Invoke(callbackArgs);
// 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.Uri.ToString());

if (callbackArgs.ExternalLinkNavigationPolicy == ExternalLinkNavigationPolicy.OpenInExternalBrowser)
if (strategy == UrlLoadingStrategy.OpenExternally)
{
UIApplication.SharedApplication.OpenUrl(url);
UIApplication.SharedApplication.OpenUrl(requestUrl);
}

if (callbackArgs.ExternalLinkNavigationPolicy != ExternalLinkNavigationPolicy.InsecureOpenInWebView)
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.
Expand All @@ -237,7 +236,7 @@ public override void DecidePolicy(WKWebView webView, WKNavigationAction navigati

if (navigationAction.TargetFrame!.MainFrame)
{
_currentUri = url;
_currentUri = requestUrl;
}

decisionHandler(WKNavigationActionPolicy.Allow);
Expand Down

This file was deleted.

28 changes: 0 additions & 28 deletions src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs

This file was deleted.

45 changes: 45 additions & 0 deletions src/BlazorWebView/src/SharedSource/UrlLoadingEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;

namespace Microsoft.AspNetCore.Components.WebView
{
/// <summary>
/// Used to provide information about a link (<![CDATA[<a>]]>) clicked within a Blazor WebView.
/// <para>
/// Anchor tags with target="_blank" will always open in the default
/// browser and the UrlLoading event won't be called.
/// </para>
/// </summary>
public class UrlLoadingEventArgs : EventArgs
{
internal static UrlLoadingEventArgs CreateWithDefaultLoadingStrategy(Uri urlToLoad, Uri appOriginUri)
{
var strategy = appOriginUri.IsBaseOf(urlToLoad) ?
UrlLoadingStrategy.OpenInWebView :
UrlLoadingStrategy.OpenExternally;

return new(urlToLoad, strategy);
}

private UrlLoadingEventArgs(Uri url, UrlLoadingStrategy urlLoadingStrategy)
{
Url = url;
UrlLoadingStrategy = urlLoadingStrategy;
}

/// <summary>
/// Gets the <see cref="Url">URL</see> to be loaded.
/// </summary>
public Uri Url { get; }

/// <summary>
/// The policy to use when loading links from the webview.
/// Defaults to <see cref="UrlLoadingStrategy.OpenExternally"/> unless <see cref="Url"/> has a host
/// matching the app origin, in which case the default becomes <see cref="UrlLoadingStrategy.OpenInWebView"/>.
/// <para>
/// This value should not be changed to <see cref="UrlLoadingStrategy.OpenInWebView"/> for external links
/// unless you can ensure they are fully trusted.
/// </para>
/// </summary>
public UrlLoadingStrategy UrlLoadingStrategy { get; set; }
}
}
31 changes: 31 additions & 0 deletions src/BlazorWebView/src/SharedSource/UrlLoadingStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace Microsoft.AspNetCore.Components.WebView
{
/// <summary>
/// URL loading strategy for anchor tags <![CDATA[<a>]]> within a Blazor WebView.
///
/// Anchor tags with target="_blank" will always open in the default
/// browser and the UrlLoading event won't be called.
/// </summary>
public enum UrlLoadingStrategy
{
/// <summary>
/// Allows loading URLs using an app determined by the system.
/// This is the default strategy for URLs with an external host.
/// </summary>
OpenExternally,

/// <summary>
/// Allows loading URLs within the Blazor WebView.
/// This is the default strategy for URLs with a host matching the app origin.
/// <para>
/// This strategy should not be used for external links unless you can ensure they are fully trusted.
/// </para>
/// </summary>
OpenInWebView,

/// <summary>
/// Cancels the current URL loading attempt.
/// </summary>
CancelLoad
}
}
Loading