-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Blazor Open Links in Browser with Configurability #4645
Conversation
if (uri.Host == "0.0.0.0" && | ||
view is not null && | ||
request is not null && | ||
request.IsRedirect && | ||
request.IsForMainFrame) | ||
{ | ||
view.LoadUrl(uri.ToString()); | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not totally clear to me when/why this code path gets exercised. Could anyone offer insights?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe setting window.location
in JS? Just a guess. Much of this code is likely copied from the non-Blazor WebView, so it could be for scenarios we don't really have in Blazor.
if (uri.Host == "0.0.0.0" && | ||
view is not null && | ||
request is not null && | ||
request.IsRedirect && | ||
request.IsForMainFrame) | ||
{ | ||
view.LoadUrl(uri.ToString()); | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe setting window.location
in JS? Just a guess. Much of this code is likely copied from the non-Blazor WebView, so it could be for scenarios we don't really have in Blazor.
I'm a bit uncertain about The more common requirement, I would think, is allowing specific URL patterns to open internally while others are forced to open externally. Allowing all possible URLs to open internally would be a security issue, even if there are some URLs that you do want to open internally (e.g., you company's "help and support" pages). I was expecting our API to be something more like a virtual method you can override to receive the URL that is being navigated to, and return a flag to say whether (for this specific URL) it should be allowed to open internally. Apologies if this has already been considered and I've missed some context here! |
I think this makes sense. @Eilon had also suggested an event based option for us to consider but I was running into some issues with how the different platforms handle navigation at different stages. I'm leaning towards an implementation which just takes an optional delegate as the arg (instead of ExternalLinkMode). If the delegate is provided then we simply call it when deciding whether or not to open the link within the WebView. How does this sound? |
How does the app developer pass in the delegate? Presumably it's an event on the control? |
During Blazor WebView initialization:
is what I had in mind. |
@TanayParikh - sure, but using what syntax? An event? Events are the canonical pattern for controls. |
The issue with doing an I was proposing just using a non-event delegate function, granted, it wouldn't be the canonical approach. |
Sounds reasonable to me. I agree that We could do it via a plain C# event in which we pass some new myBlazorWebView.OnExternalNavigationStarting += (sender, eventArgs) =>
{
if (ThisLooksLikeATrustworthyUrl(eventArgs.Url))
{
eventArgs.OpenInWebView();
}
}; ... but TBH that seems less ergonomic than your regular-delegate proposal. Maybe something like: // If not set, default to NavigationPolicy.OpenInSystemBrowser
myBlazorWebView.NavigationPolicy = navigationInfo =>
{
return ThisLooksLikeATrustworthyUrl(navigationInfo.Url) ? NavigationPolicy.OpenInWebView : NavigationPolicy.OpenInSystemBrowser;
}; We should check whether the new event/callback fires for all navigations (including ones handled by Blazor's client-side router), or only ones that aren't handled by the client-side router. The API name should account for this, e.g., call it |
Thanks @SteveSandersonMS & @Eilon, I've updated via 3c7c2d0.
We're specifically looking at "external" links (defined by any |
f712ee5
to
f0448be
Compare
src/BlazorWebView/src/SharedSource/ExternalLinkNavigationInfo.cs
Outdated
Show resolved
Hide resolved
src/BlazorWebView/src/SharedSource/ExternalLinkNavigationInfo.cs
Outdated
Show resolved
Hide resolved
src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs
Outdated
Show resolved
Hide resolved
Thanks for the clarification @Eilon, I believe bdfabfd should resolve this. Had to do a sort of awkward workaround using an Please ignore the code comments for now, will update them all once you're happy with this design to minimize churn. Platforms left to do:
|
Thanks for the update, will review soon. I'm out on Monday too but I will try to review before then so you're not blocked. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I think this is looking pretty good (just some fairly small comments). This really hinges on whether this actually works, or not, though 😁 I have no reason to think it wouldn't, but, boy oh boy, this PR gets to deal with all six platforms we support, each with its own unique webview interop, event patterns, launcher APIs, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few more comments.
using Microsoft.Maui; | ||
using Microsoft.Maui.Handlers; | ||
|
||
namespace Microsoft.AspNetCore.Components.WebView.Maui | ||
{ | ||
public partial class BlazorWebViewHandler | ||
{ | ||
public static PropertyMapper<IBlazorWebView, BlazorWebViewHandler> BlazorWebViewMapper = new(ViewHandler.ViewMapper) | ||
private static readonly PropertyMapper<IBlazorWebView, BlazorWebViewHandler> BlazorWebViewMapper = new(ViewMapper) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this change to private deliberate? The mappers are supposed to be public so that derived types can re-use them. Examples: https://github.com/dotnet/maui/blob/main/src/Core/src/Handlers/Button/ButtonHandler.cs#L19-L41
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think making it readonly is good, though interestingly in the one I linked they are not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I logged #4870 to see if they should be readonly
across all of MAUI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated to public static readonly
.
/// <summary> | ||
/// External <see cref="Uri">URI</see> to be navigated to. | ||
/// </summary> | ||
public Uri Uri { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this have a setter? I think not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any harm in having it? Wanted to support the hypothetical use case of redirecting the navigation request based on the existing Uri.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't seem necessary because that isn't the point of this. If someone wants a link to go somewhere else, they should just do that to begin with. Also, the current code doesn't even always work that way with a modified event args URL because in at least some cases it uses the original URL and not the one from the event args that may have been modified:
OpenInExternalBrowser, | ||
|
||
/// <summary> | ||
/// Opens anchor tags <![CDATA[<a>]]> in the WebView. This is not recommended. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still needs some updated comment. Something like:
/// Opens anchor tags <![CDATA[<a>]]> in the WebView. This is not recommended. | |
/// Opens anchor tags <![CDATA[<a>]]> in the WebView. This is not recommended unless the content of the URL is fully trusted. |
@blowdart - any suggestion on how to word this? This is related to the threat model I updated earlier. (We'll later have regular docs too, but I think it's nice to have a reasonable doc comment warning here too.)
Thanks for the review @Eilon. I believe all the feedback should now be addressed, and all 6 platforms should be working. I'll do a quick validation round across all the platforms before final merge. |
/// Used to provide information about a link (<![CDATA[<a>]]>) 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't cref the ExternalNavigationStarting event because this class is in the SharedSource
and is used with ExternalNavigationStarting
in Maui
, WinForms
& WPF
.
We'll need to add some docs for this new functionality (cc/ @guardrex) alongside maybe having this part of the Preview 14 announcement given we changed defaults(?). InstructionsRegister to the Please note, external links are opened in the device default browser, by default. Opening external links within the Blazor WebView is not recommended unless the content is fully trusted. MAUIAdd the event handler to the constructor of the blazorWebView.ExternalNavigationStarting += (sender, externalLinkNavigationEventArgs) =>
{
externalLinkNavigationEventArgs.ExternalLinkNavigationPolicy = ExternalLinkNavigationPolicy.OpenInWebView;
}; WPFAdd the <blazor:BlazorWebView HostPage="wwwroot\index.html" Services="{StaticResource services}" x:Name="blazorWebView" ExternalNavigationStarting="Handle_ExternalNavigationStarting" > Add the event handler in your private void Handle_ExternalNavigationStarting(object sender, ExternalLinkNavigationEventArgs externalLinkNavigationEventArgs)
{
externalLinkNavigationEventArgs.ExternalLinkNavigationPolicy = ExternalLinkNavigationPolicy.OpenInWebView;
} WinformsIn the contructor of the Form containing the blazorWebView.ExternalNavigationStarting += (sender, externalLinkNavigationEventArgs) =>
{
externalLinkNavigationEventArgs.ExternalLinkNavigationPolicy = ExternalLinkNavigationPolicy.OpenInWebView;
}; |
Would this apply to all hrefs in the webview? That seems horrible, because local references would be trusted somewhat. If it was external links it'd be easier to describe. "Allows navigation to external links in the WebView. When true this setting can introduce security concerns and should not be enabled unless you can ensure all external links are fully trusted." Also we now have a habit of naming properties like this as InsecureSomethingOrOther to make it even clearer. OpenInExternalBrowser = false is secure, OpenInExternalBrowser = true potentially isn't. Making it an enum rather than a bool with the option "AllowInsecureOpeningOfLinksInsideWebView" would be clearer. |
Yes, this configurability is specifically designed for external URLs, defined as URLs with a I've updated the enum to |
/// <summary> | ||
/// External <see cref="Uri">URI</see> to be navigated to. | ||
/// </summary> | ||
public Uri Uri { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't seem necessary because that isn't the point of this. If someone wants a link to go somewhere else, they should just do that to begin with. Also, the current code doesn't even always work that way with a modified event args URL because in at least some cases it uses the original URL and not the one from the event args that may have been modified:
src/BlazorWebView/src/SharedSource/ExternalLinkNavigationPolicy.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I approve this message.
ExternalNavigationStarting
event and setting theExternalLinkNavigationPolicy.OpenInWebView
.ExternalLinkNavigationPolicy.CancelNavigation
_blank
target links in external browserStatus:
Fixes: #4357
Fixes: #4338