Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 36 additions & 4 deletions src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using Android.Runtime;
using Android.Webkit;
using Java.Net;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Platform;
using AWebView = Android.Webkit.WebView;

namespace Microsoft.AspNetCore.Components.WebView.Maui
Expand Down Expand Up @@ -83,10 +85,40 @@ private bool ShouldOverrideUrlLoadingCore(IWebResourceRequest? request)
}

var requestUri = request?.Url?.ToString();

var logger = _webViewHandler?.Logger;

logger?.LogDebug("Intercepting request for {Url}.", requestUri);

if (view is not null && request is not null && !string.IsNullOrEmpty(requestUri))
{
// 1. Check if the app wants to modify or override the request
var response = WebRequestInterceptingWebView.TryInterceptResponseStream(_webViewHandler, view, request, requestUri, logger);
if (response is not null)
{
return response;
}

// 2. Check if the request is for a Blazor resource
response = GetResponse(requestUri, _webViewHandler?.Logger);
if (response is not null)
{
return response;
}
}

// 3. Otherwise, we let the request go through as is
logger?.LogDebug("Request for {Url} was not handled.", requestUri);

return base.ShouldInterceptRequest(view, request);
}

private WebResourceResponse? GetResponse(string requestUri, ILogger? logger)
{
var allowFallbackOnHostPage = AppOriginUri.IsBaseOfPage(requestUri);
requestUri = QueryStringHelper.RemovePossibleQueryString(requestUri);

_webViewHandler?.Logger.HandlingWebRequest(requestUri);
logger?.HandlingWebRequest(requestUri);

if (requestUri != null &&
_webViewHandler != null &&
Expand All @@ -95,16 +127,16 @@ private bool ShouldOverrideUrlLoadingCore(IWebResourceRequest? request)
{
var contentType = headers["Content-Type"];

_webViewHandler?.Logger.ResponseContentBeingSent(requestUri, statusCode);
logger?.ResponseContentBeingSent(requestUri, statusCode);

return new WebResourceResponse(contentType, "UTF-8", statusCode, statusMessage, headers, content);
}
else
{
_webViewHandler?.Logger.ReponseContentNotFound(requestUri ?? string.Empty);
logger?.ReponseContentNotFound(requestUri ?? string.Empty);
}

return base.ShouldInterceptRequest(view, request);
return null;
}

public override void OnPageFinished(AWebView? view, string? url)
Expand Down
22 changes: 22 additions & 0 deletions src/BlazorWebView/src/Maui/BlazorWebView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.FileProviders;
using Microsoft.Maui.Controls;
using Microsoft.Maui;

namespace Microsoft.AspNetCore.Components.WebView.Maui
{
Expand Down Expand Up @@ -65,6 +66,18 @@ public string StartPath
/// </summary>
public event EventHandler<BlazorWebViewInitializedEventArgs>? BlazorWebViewInitialized;

/// <summary>
/// Raised when a web resource is requested. This event allows the application to intercept the request and provide a
/// custom response.
/// The event handler can set the <see cref="WebViewWebResourceRequestedEventArgs.Handled"/> property to true
/// to indicate that the request has been handled and no further processing is needed. If the event handler does set this
/// property to true, it must also call the
/// <see cref="WebViewWebResourceRequestedEventArgs.SetResponse(int, string, System.Collections.Generic.IReadOnlyDictionary{string, string}?, System.IO.Stream?)"/>
/// or <see cref="WebViewWebResourceRequestedEventArgs.SetResponse(int, string, System.Collections.Generic.IReadOnlyDictionary{string, string}?, System.Threading.Tasks.Task{System.IO.Stream?})"/>
/// method to provide a response to the request.
/// </summary>
public event EventHandler<WebViewWebResourceRequestedEventArgs>? WebResourceRequested;

/// <inheritdoc />
#if ANDROID
[System.Runtime.Versioning.SupportedOSPlatform("android23.0")]
Expand Down Expand Up @@ -108,5 +121,14 @@ void IBlazorWebView.BlazorWebViewInitializing(BlazorWebViewInitializingEventArgs
/// <inheritdoc />
void IBlazorWebView.BlazorWebViewInitialized(BlazorWebViewInitializedEventArgs args) =>
BlazorWebViewInitialized?.Invoke(this, args);

/// <inheritdoc />
bool IWebRequestInterceptingWebView.WebResourceRequested(WebResourceRequestedEventArgs args)
{
var platformArgs = new PlatformWebViewWebResourceRequestedEventArgs(args);
var e = new WebViewWebResourceRequestedEventArgs(platformArgs);
WebResourceRequested?.Invoke(this, e);
return e.Handled;
}
}
}
2 changes: 1 addition & 1 deletion src/BlazorWebView/src/Maui/IBlazorWebView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Components.WebView.Maui
/// <summary>
/// Defines a contract for a view that renders Blazor content.
/// </summary>
public interface IBlazorWebView : IView
public interface IBlazorWebView : IView, IWebRequestInterceptingWebView
{
/// <summary>
/// Gets the path to the HTML file to render.
Expand Down
99 changes: 60 additions & 39 deletions src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace Microsoft.AspNetCore.Components.WebView.Maui
/// </summary>
internal class WinUIWebViewManager : WebView2WebViewManager
{
private readonly BlazorWebViewHandler _handler;
private readonly WebView2Control _webview;
private readonly string _hostPageRelativePath;
private readonly string _contentRootRelativeToAppRoot;
Expand Down Expand Up @@ -62,6 +63,7 @@ public WinUIWebViewManager(
ILogger logger)
: base(webview, services, dispatcher, fileProvider, jsComponents, contentRootRelativeToAppRoot, hostPagePathWithinFileProvider, webViewHandler, logger)
{
_handler = webViewHandler;
_logger = logger;
_webview = webview;
_hostPageRelativePath = hostPagePathWithinFileProvider;
Expand All @@ -71,52 +73,71 @@ public WinUIWebViewManager(
/// <inheritdoc />
protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRequestedEventArgs eventArgs)
{
// Unlike server-side code, we get told exactly why the browser is making the request,
// so we can be smarter about fallback. We can ensure that 'fetch' requests never result
// in fallback, for example.
var allowFallbackOnHostPage =
eventArgs.ResourceContext == CoreWebView2WebResourceContext.Document ||
eventArgs.ResourceContext == CoreWebView2WebResourceContext.Other; // e.g., dev tools requesting page source
var url = eventArgs.Request.Uri;

// Get a deferral object so that WebView2 knows there's some async stuff going on. We call Complete() at the end of this method.
using var deferral = eventArgs.GetDeferral();
_logger.LogDebug("Intercepting request for {Url}.", url);

var requestUri = QueryStringHelper.RemovePossibleQueryString(eventArgs.Request.Uri);

_logger.HandlingWebRequest(requestUri);

var uri = new Uri(requestUri);
var relativePath = AppOriginUri.IsBaseOf(uri) ? AppOriginUri.MakeRelativeUri(uri).ToString() : null;

// Check if the uri is _framework/blazor.modules.json is a special case as the built-in file provider
// brings in a default implementation.
if (relativePath != null &&
string.Equals(relativePath, "_framework/blazor.modules.json", StringComparison.Ordinal) &&
await TryServeFromFolderAsync(eventArgs, allowFallbackOnHostPage: false, requestUri, relativePath))
{
_logger.ResponseContentBeingSent(requestUri, 200);
}
else if (TryGetResponseContent(requestUri, allowFallbackOnHostPage, out var statusCode, out var statusMessage, out var content, out var headers)
&& statusCode != 404)
// 1. First check if the app wants to modify or override the request.
if (WebRequestInterceptingWebView.TryInterceptResponseStream(_handler, _webview.CoreWebView2, eventArgs, url, _logger))
{
// First, call into WebViewManager to see if it has a framework file for this request. It will
// fall back to an IFileProvider, but on WinUI it's always a NullFileProvider, so that will never
// return a file.
var headerString = GetHeaderString(headers);
_logger.ResponseContentBeingSent(requestUri, statusCode);
eventArgs.Response = _coreWebView2Environment!.CreateWebResourceResponse(content.AsRandomAccessStream(), statusCode, statusMessage, headerString);
return;
}
else if (relativePath != null)

// 2. If this is an app request, then assume the request is for a Blazor resource.
var requestUri = QueryStringHelper.RemovePossibleQueryString(url);
if (new Uri(requestUri) is Uri uri)
{
await TryServeFromFolderAsync(
eventArgs,
allowFallbackOnHostPage,
requestUri,
relativePath);
// Unlike server-side code, we get told exactly why the browser is making the request,
// so we can be smarter about fallback. We can ensure that 'fetch' requests never result
// in fallback, for example.
var allowFallbackOnHostPage =
eventArgs.ResourceContext == CoreWebView2WebResourceContext.Document ||
eventArgs.ResourceContext == CoreWebView2WebResourceContext.Other; // e.g., dev tools requesting page source

// Get a deferral object so that WebView2 knows there's some async stuff going on. We call Complete() at the end of this method.
using var deferral = eventArgs.GetDeferral();

_logger.HandlingWebRequest(requestUri);

var relativePath = AppOriginUri.IsBaseOf(uri) ? AppOriginUri.MakeRelativeUri(uri).ToString() : null;

// Check if the uri is _framework/blazor.modules.json is a special case as the built-in file provider
// brings in a default implementation.
if (relativePath != null &&
string.Equals(relativePath, "_framework/blazor.modules.json", StringComparison.Ordinal) &&
await TryServeFromFolderAsync(eventArgs, allowFallbackOnHostPage: false, requestUri, relativePath))
{
_logger.ResponseContentBeingSent(requestUri, 200);
}
else if (TryGetResponseContent(requestUri, allowFallbackOnHostPage, out var statusCode, out var statusMessage, out var content, out var headers)
&& statusCode != 404)
{
// First, call into WebViewManager to see if it has a framework file for this request. It will
// fall back to an IFileProvider, but on WinUI it's always a NullFileProvider, so that will never
// return a file.
var headerString = GetHeaderString(headers);
_logger.ResponseContentBeingSent(requestUri, statusCode);
eventArgs.Response = _coreWebView2Environment!.CreateWebResourceResponse(content.AsRandomAccessStream(), statusCode, statusMessage, headerString);
}
else if (relativePath != null)
{
await TryServeFromFolderAsync(
eventArgs,
allowFallbackOnHostPage,
requestUri,
relativePath);
}

// Notify WebView2 that the deferred (async) operation is complete and we set a response.
deferral.Complete();
return;
}

// Notify WebView2 that the deferred (async) operation is complete and we set a response.
deferral.Complete();
// 3. If the request is not handled by the app nor is it a local source, then we let the WebView2
// handle the request as it would normally do. This means that it will try to load the resource
// from the internet or from the local cache.

_logger.LogDebug("Request for {Url} was not handled.", url);
}

private async Task<bool> TryServeFromFolderAsync(
Expand Down
28 changes: 26 additions & 2 deletions src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.Maui;
using Microsoft.Maui.Dispatching;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using UIKit;
using WebKit;
using RectangleF = CoreGraphics.CGRect;
Expand Down Expand Up @@ -259,12 +260,29 @@ public SchemeHandler(BlazorWebViewHandler webViewHandler)
[SupportedOSPlatform("ios11.0")]
public void StartUrlSchemeTask(WKWebView webView, IWKUrlSchemeTask urlSchemeTask)
{
var responseBytes = GetResponseBytes(urlSchemeTask.Request.Url?.AbsoluteString ?? "", out var contentType, statusCode: out var statusCode);
var url = urlSchemeTask.Request.Url.AbsoluteString;
if (string.IsNullOrEmpty(url))
{
return;
}

var logger = _webViewHandler.Logger;

logger?.LogDebug("Intercepting request for {Url}.", url);

// 1. First check if the app wants to modify or override the request.
if (WebRequestInterceptingWebView.TryInterceptResponseStream(_webViewHandler, webView, urlSchemeTask, url, logger))
{
return;
}

// 2. If this is an app request, then assume the request is for a Blazor resource.
var responseBytes = GetResponseBytes(url, out var contentType, statusCode: out var statusCode);
if (statusCode == 200)
{
using (var dic = new NSMutableDictionary<NSString, NSString>())
{
dic.Add((NSString)"Content-Length", (NSString)(responseBytes.Length.ToString(CultureInfo.InvariantCulture)));
dic.Add((NSString)"Content-Length", (NSString)responseBytes.Length.ToString(CultureInfo.InvariantCulture));
dic.Add((NSString)"Content-Type", (NSString)contentType);
// Disable local caching. This will prevent user scripts from executing correctly.
dic.Add((NSString)"Cache-Control", (NSString)"no-cache, max-age=0, must-revalidate, no-store");
Expand All @@ -278,6 +296,12 @@ public void StartUrlSchemeTask(WKWebView webView, IWKUrlSchemeTask urlSchemeTask
urlSchemeTask.DidReceiveData(NSData.FromArray(responseBytes));
urlSchemeTask.DidFinish();
}

// 3. If the request is not handled by the app nor is it a local source, then we let the WKWebView
// handle the request as it would normally do. This means that it will try to load the resource
// from the internet or from the local cache.

logger?.LogDebug("Request for {Url} was not handled.", url);
}

private byte[] GetResponseBytes(string? url, out string contentType, out int statusCode)
Expand Down
15 changes: 8 additions & 7 deletions src/Controls/src/Core/HybridWebView/HybridWebView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,25 +66,26 @@ void IHybridWebView.RawMessageReceived(string rawMessage)
/// </summary>
public event EventHandler<HybridWebViewRawMessageReceivedEventArgs>? RawMessageReceived;

bool IHybridWebView.WebResourceRequested(WebResourceRequestedEventArgs args)
/// <inheritdoc/>
bool IWebRequestInterceptingWebView.WebResourceRequested(WebResourceRequestedEventArgs args)
{
var platformArgs = new PlatformHybridWebViewWebResourceRequestedEventArgs(args);
var e = new HybridWebViewWebResourceRequestedEventArgs(platformArgs);
var platformArgs = new PlatformWebViewWebResourceRequestedEventArgs(args);
var e = new WebViewWebResourceRequestedEventArgs(platformArgs);
WebResourceRequested?.Invoke(this, e);
return e.Handled;
}

/// <summary>
/// Raised when a web resource is requested. This event allows the application to intercept the request and provide a
/// custom response.
/// The event handler can set the <see cref="HybridWebViewWebResourceRequestedEventArgs.Handled"/> property to true
/// The event handler can set the <see cref="WebViewWebResourceRequestedEventArgs.Handled"/> property to true
/// to indicate that the request has been handled and no further processing is needed. If the event handler does set this
/// property to true, it must also call the
/// <see cref="HybridWebViewWebResourceRequestedEventArgs.SetResponse(int, string, System.Collections.Generic.IReadOnlyDictionary{string, string}?, System.IO.Stream?)"/>
/// or <see cref="HybridWebViewWebResourceRequestedEventArgs.SetResponse(int, string, System.Collections.Generic.IReadOnlyDictionary{string, string}?, System.Threading.Tasks.Task{System.IO.Stream?})"/>
/// <see cref="WebViewWebResourceRequestedEventArgs.SetResponse(int, string, System.Collections.Generic.IReadOnlyDictionary{string, string}?, System.IO.Stream?)"/>
/// or <see cref="WebViewWebResourceRequestedEventArgs.SetResponse(int, string, System.Collections.Generic.IReadOnlyDictionary{string, string}?, System.Threading.Tasks.Task{System.IO.Stream?})"/>
/// method to provide a response to the request.
/// </summary>
public event EventHandler<HybridWebViewWebResourceRequestedEventArgs>? WebResourceRequested;
public event EventHandler<WebViewWebResourceRequestedEventArgs>? WebResourceRequested;

/// <summary>
/// Sends a raw message to the code running in the web view. Raw messages have no additional processing.
Expand Down
Loading
Loading