diff --git a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs index e1f93fa705ad..debf50d8b5f5 100644 --- a/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs +++ b/src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs @@ -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 @@ -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 && @@ -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?.ResponseContentNotFound(requestUri ?? string.Empty); } - return base.ShouldInterceptRequest(view, request); + return null; } public override void OnPageFinished(AWebView? view, string? url) diff --git a/src/BlazorWebView/src/Maui/BlazorWebView.cs b/src/BlazorWebView/src/Maui/BlazorWebView.cs index d7dbab342c08..87d9f88cbbec 100644 --- a/src/BlazorWebView/src/Maui/BlazorWebView.cs +++ b/src/BlazorWebView/src/Maui/BlazorWebView.cs @@ -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 { @@ -65,6 +66,18 @@ public string StartPath /// public event EventHandler? BlazorWebViewInitialized; + /// + /// 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 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 + /// + /// or + /// method to provide a response to the request. + /// + public event EventHandler? WebResourceRequested; + /// #if ANDROID [System.Runtime.Versioning.SupportedOSPlatform("android23.0")] @@ -108,5 +121,14 @@ void IBlazorWebView.BlazorWebViewInitializing(BlazorWebViewInitializingEventArgs /// void IBlazorWebView.BlazorWebViewInitialized(BlazorWebViewInitializedEventArgs args) => BlazorWebViewInitialized?.Invoke(this, args); + + /// + bool IWebRequestInterceptingWebView.WebResourceRequested(WebResourceRequestedEventArgs args) + { + var platformArgs = new PlatformWebViewWebResourceRequestedEventArgs(args); + var e = new WebViewWebResourceRequestedEventArgs(platformArgs); + WebResourceRequested?.Invoke(this, e); + return e.Handled; + } } } diff --git a/src/BlazorWebView/src/Maui/IBlazorWebView.cs b/src/BlazorWebView/src/Maui/IBlazorWebView.cs index 4907fd66cdde..4fe3b0e85620 100644 --- a/src/BlazorWebView/src/Maui/IBlazorWebView.cs +++ b/src/BlazorWebView/src/Maui/IBlazorWebView.cs @@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Components.WebView.Maui /// /// Defines a contract for a view that renders Blazor content. /// - public interface IBlazorWebView : IView + public interface IBlazorWebView : IView, IWebRequestInterceptingWebView { /// /// Gets the path to the HTML file to render. diff --git a/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj b/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj index 4ffc76f110a4..0e1178780e17 100644 --- a/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj +++ b/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj @@ -33,7 +33,6 @@ - diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt index 7dc5c58110bf..6d3817c808ec 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.WebResourceRequested -> System.EventHandler? diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt index 7dc5c58110bf..6d3817c808ec 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.WebResourceRequested -> System.EventHandler? diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index 7dc5c58110bf..6d3817c808ec 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.WebResourceRequested -> System.EventHandler? diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt index 7dc5c58110bf..6d3817c808ec 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.WebResourceRequested -> System.EventHandler? diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt index 7dc5c58110bf..6d3817c808ec 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.WebResourceRequested -> System.EventHandler? diff --git a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs index 462e809abdcc..0c4a4160fdf6 100644 --- a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs @@ -10,6 +10,7 @@ using Microsoft.Web.WebView2.Core; using Windows.ApplicationModel; using Windows.Storage.Streams; +using Microsoft.Maui.Platform; using WebView2Control = Microsoft.UI.Xaml.Controls.WebView2; namespace Microsoft.AspNetCore.Components.WebView.Maui @@ -20,6 +21,7 @@ namespace Microsoft.AspNetCore.Components.WebView.Maui /// internal class WinUIWebViewManager : WebView2WebViewManager { + private readonly BlazorWebViewHandler _handler; private readonly WebView2Control _webview; private readonly string _hostPageRelativePath; private readonly string _contentRootRelativeToAppRoot; @@ -62,6 +64,7 @@ public WinUIWebViewManager( ILogger logger) : base(webview, services, dispatcher, fileProvider, jsComponents, contentRootRelativeToAppRoot, hostPagePathWithinFileProvider, webViewHandler, logger) { + _handler = webViewHandler; _logger = logger; _webview = webview; _hostPageRelativePath = hostPagePathWithinFileProvider; @@ -71,52 +74,71 @@ public WinUIWebViewManager( /// 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 TryServeFromFolderAsync( @@ -179,7 +201,7 @@ private async Task TryServeFromFolderAsync( } else { - _logger.ReponseContentNotFound(requestUri); + _logger.ResponseContentNotFound(requestUri); } return false; diff --git a/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs b/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs index 8df03fd31281..aff3b291c3cb 100644 --- a/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs +++ b/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs @@ -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; @@ -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()) { - 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"); @@ -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) @@ -303,7 +327,7 @@ private byte[] GetResponseBytes(string? url, out string contentType, out int sta } else { - _webViewHandler?.Logger.ReponseContentNotFound(url); + _webViewHandler?.Logger.ResponseContentNotFound(url); statusCode = 404; contentType = string.Empty; diff --git a/src/BlazorWebView/src/SharedSource/Log.cs b/src/BlazorWebView/src/SharedSource/Log.cs index 290a1054a8bc..8865618cb3dd 100644 --- a/src/BlazorWebView/src/SharedSource/Log.cs +++ b/src/BlazorWebView/src/SharedSource/Log.cs @@ -24,7 +24,7 @@ internal static partial class Log public static partial void ResponseContentBeingSent(this ILogger logger, string requestUri, int statusCode); [LoggerMessage(EventId = 6, Level = LogLevel.Debug, Message = "Response content was not found for web request to URI '{requestUri}'.")] - public static partial void ReponseContentNotFound(this ILogger logger, string requestUri); + public static partial void ResponseContentNotFound(this ILogger logger, string requestUri); [LoggerMessage(EventId = 7, Level = LogLevel.Debug, Message = "Navigation event for URI '{uri}' with URL loading strategy '{urlLoadingStrategy}'.")] public static partial void NavigationEvent(this ILogger logger, Uri uri, UrlLoadingStrategy urlLoadingStrategy); diff --git a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs index 4ff3a97195e1..b169049ed165 100644 --- a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs +++ b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs @@ -259,7 +259,7 @@ private async Task TryInitializeWebView2() }); #endif - _webview.CoreWebView2.AddWebResourceRequestedFilter($"{AppOrigin}*", CoreWebView2WebResourceContext.All); + _webview.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All); _webview.CoreWebView2.WebResourceRequested += async (s, eventArgs) => { @@ -325,7 +325,7 @@ protected virtual Task HandleWebResourceRequest(CoreWebView2WebResourceRequested } else { - _logger.ReponseContentNotFound(requestUri); + _logger.ResponseContentNotFound(requestUri); } #elif WEBVIEW2_MAUI // No-op here because all the work is done in the derived WinUIWebViewManager diff --git a/src/BlazorWebView/samples/WebViewAppShared/NoOpComponent.razor b/src/BlazorWebView/tests/MauiDeviceTests/Components/NoOpComponent.razor similarity index 100% rename from src/BlazorWebView/samples/WebViewAppShared/NoOpComponent.razor rename to src/BlazorWebView/tests/MauiDeviceTests/Components/NoOpComponent.razor diff --git a/src/BlazorWebView/samples/WebViewAppShared/TestComponent1.razor b/src/BlazorWebView/tests/MauiDeviceTests/Components/TestComponent1.razor similarity index 100% rename from src/BlazorWebView/samples/WebViewAppShared/TestComponent1.razor rename to src/BlazorWebView/tests/MauiDeviceTests/Components/TestComponent1.razor diff --git a/src/BlazorWebView/samples/WebViewAppShared/TestComponent1.razor.css b/src/BlazorWebView/tests/MauiDeviceTests/Components/TestComponent1.razor.css similarity index 100% rename from src/BlazorWebView/samples/WebViewAppShared/TestComponent1.razor.css rename to src/BlazorWebView/tests/MauiDeviceTests/Components/TestComponent1.razor.css diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Components/_Imports.razor b/src/BlazorWebView/tests/MauiDeviceTests/Components/_Imports.razor new file mode 100644 index 000000000000..01871b829f84 --- /dev/null +++ b/src/BlazorWebView/tests/MauiDeviceTests/Components/_Imports.razor @@ -0,0 +1,5 @@ +@using System.Net.Http +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.JSInterop diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.Components.cs b/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.Components.cs new file mode 100644 index 000000000000..d5dac9319441 --- /dev/null +++ b/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.Components.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.WebView.Maui; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Microsoft.Maui.MauiBlazorWebView.DeviceTests.Components; +using WebViewAppShared; + +namespace Microsoft.Maui.MauiBlazorWebView.DeviceTests.Elements; + +public partial class BlazorWebViewTests +{ + [Fact] + public async Task BasicRazorComponentClick() + { + EnsureHandlerCreated(additionalCreationActions: appBuilder => + { + appBuilder.Services.AddMauiBlazorWebView(); + }); + + var bwv = new BlazorWebViewWithCustomFiles + { + HostPage = "wwwroot/index.html", + CustomFiles = new Dictionary + { + { "index.html", TestStaticFilesContents.DefaultMauiIndexHtmlContent }, + }, + }; + bwv.RootComponents.Add(new RootComponent { ComponentType = typeof(TestComponent1), Selector = "#app", }); + + await InvokeOnMainThreadAsync(async () => + { + var bwvHandler = CreateHandler(bwv); + var platformWebView = bwvHandler.PlatformView; + await WebViewHelpers.WaitForWebViewReady(platformWebView); + + // Click a button in a Razor component 3 times + await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "0"); + + var c1 = await WebViewHelpers.ExecuteScriptAsync(bwvHandler.PlatformView, "document.getElementById('incrementButton').click()"); + + await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "1"); + + var c2 = await WebViewHelpers.ExecuteScriptAsync(bwvHandler.PlatformView, "document.getElementById('incrementButton').click()"); + + await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "2"); + + var c3 = await WebViewHelpers.ExecuteScriptAsync(bwvHandler.PlatformView, "document.getElementById('incrementButton').click()"); + + await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "3"); + + // And the counter value should increment from 0 to 3. + var actualFinalCounterValue = await WebViewHelpers.ExecuteScriptAsync(bwvHandler.PlatformView, "document.getElementById('counterValue').innerText"); + actualFinalCounterValue = actualFinalCounterValue.Trim('\"'); // some platforms return quoted values, so we trim them + Assert.Equal("3", actualFinalCounterValue); + }); + } +} diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.Logging.cs b/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.Logging.cs new file mode 100644 index 000000000000..a34efd228da2 --- /dev/null +++ b/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.Logging.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.WebView.Maui; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Maui.MauiBlazorWebView.DeviceTests.Components; +using Xunit; + +namespace Microsoft.Maui.MauiBlazorWebView.DeviceTests.Elements; + +public partial class BlazorWebViewTests +{ + [Fact] + public async Task BlazorWebViewLogsRequests() + { + var testLoggerProvider = new TestLoggerProvider(); + EnsureHandlerCreated(additionalCreationActions: appBuilder => + { + appBuilder.Services.AddMauiBlazorWebView(); + appBuilder.Services.AddLogging(c => + { + // Enable maximum logging for BlazorWebView + c.AddFilter("Microsoft.AspNetCore.Components.WebView", LogLevel.Trace); + + c.AddProvider(testLoggerProvider); + }); + }); + + var bwv = new BlazorWebViewWithCustomFiles + { + HostPage = "wwwroot/index.html", + CustomFiles = new Dictionary + { + { "index.html", TestStaticFilesContents.DefaultMauiIndexHtmlContent }, + }, + }; + bwv.RootComponents.Add(new RootComponent { ComponentType = typeof(NoOpComponent), Selector = "#app", }); + + await InvokeOnMainThreadAsync(async () => + { + var bwvHandler = CreateHandler(bwv); + var platformWebView = bwvHandler.PlatformView; + await WebViewHelpers.WaitForWebViewReady(platformWebView); + + // Wait for the no-op component to load + await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "Static"); + }); + + var events = testLoggerProvider.GetEvents(); + + // Here we choose an arbitrary subset of logs to verify. We could check every single one, but + // it's different on each platform, and subject to change in the future. Less is more. + Assert.Equal(1, events.Count(c => c.EventId.Id == 0 && c.LogLevel == LogLevel.Debug && c.EventId.Name == "NavigatingToUri")); + Assert.Equal(1, events.Count(c => c.EventId.Id == 4 && c.LogLevel == LogLevel.Debug && c.EventId.Name == "HandlingWebRequest" && c.Message.Contains("/_framework/blazor.webview.js", System.StringComparison.Ordinal))); + } +} diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.Navigation.cs b/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.Navigation.cs new file mode 100644 index 000000000000..e17648a92353 --- /dev/null +++ b/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.Navigation.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.WebView.Maui; +using Microsoft.Extensions.DependencyInjection; +using WebViewAppShared; + +namespace Microsoft.Maui.MauiBlazorWebView.DeviceTests.Elements; + +public partial class BlazorWebViewTests +{ + [Fact] + public async Task BlazorWebViewUsesStartPath() + { + EnsureHandlerCreated(additionalCreationActions: appBuilder => + { + appBuilder.Services.AddMauiBlazorWebView(); + }); + + var bwv = new BlazorWebViewWithCustomFiles + { + StartPath = "CustomStart/SomeData", + HostPage = "wwwroot/index.html", + CustomFiles = new Dictionary + { + { "index.html", TestStaticFilesContents.DefaultMauiIndexHtmlContent }, + }, + }; + bwv.RootComponents.Add(new RootComponent { ComponentType = typeof(RouterComponent), Selector = "#app", }); + + await InvokeOnMainThreadAsync(async () => + { + var bwvHandler = CreateHandler(bwv); + var platformWebView = bwvHandler.PlatformView; + await WebViewHelpers.WaitForWebViewReady(platformWebView); + + // Wait for the component to load + await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "Found the start path with: 'SomeData'"); + }); + } +} diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.RequestInterception.cs b/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.RequestInterception.cs new file mode 100644 index 000000000000..0961e1ed2678 --- /dev/null +++ b/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.RequestInterception.cs @@ -0,0 +1,464 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.WebView.Maui; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Maui.MauiBlazorWebView.DeviceTests.Components; +using Xunit; + +namespace Microsoft.Maui.MauiBlazorWebView.DeviceTests.Elements; + +public partial class BlazorWebViewTests +{ + [Fact] + public Task RequestsCanBeInterceptedAndCustomDataReturned() => + RunTest(async (blazorWebView, handler) => + { + blazorWebView.WebResourceRequested += (sender, e) => + { + if (e.Uri.PathAndQuery.StartsWith("/api/sample")) + { + // 1. Create the response data + var response = new EchoResponseObject { message = $"Hello real endpoint (param1={e.QueryParameters["param1"]}, param2={e.QueryParameters["param2"]})" }; + var responseData = JsonSerializer.SerializeToUtf8Bytes(response); + var responseLength = responseData.Length.ToString(CultureInfo.InvariantCulture); + + // 2. Create the response + e.SetResponse(200, "OK", "application/json", new MemoryStream(responseData)); + + // 3. Let the app know we are handling it entirely + e.Handled = true; + } + }; + + // Execute JavaScript to make the request and store result in controlDiv + var responseObject = await WebViewHelpers.ExecuteAsyncScriptAndWaitForResult(handler.PlatformView, + """ + const response = await fetch('/api/sample?param1=value1¶m2=value2'); + const jsonData = await response.json(); + return jsonData; + """); + + Assert.NotNull(responseObject); + Assert.Equal("Hello real endpoint (param1=value1, param2=value2)", responseObject.message); + }); + + [Fact] + public Task RequestsCanBeInterceptedAndAsyncCustomDataReturned() => + RunTest(async (blazorWebView, handler) => + { + blazorWebView.WebResourceRequested += (sender, e) => + { + if (e.Uri.PathAndQuery.StartsWith("/api/async-sample")) + { + // 1. Create the response + e.SetResponse(200, "OK", "application/json", GetDataAsync(e.QueryParameters)); + + // 2. Let the app know we are handling it entirely + e.Handled = true; + } + }; + + // Execute JavaScript to make the request and store result in controlDiv + var responseObject = await WebViewHelpers.ExecuteAsyncScriptAndWaitForResult(handler.PlatformView, + """ + const response = await fetch('/api/async-sample?param1=value1¶m2=value2'); + const jsonData = await response.json(); + return jsonData; + """); + + Assert.NotNull(responseObject); + Assert.Equal("Hello real endpoint (param1=value1, param2=value2)", responseObject.message); + + static async Task GetDataAsync(IReadOnlyDictionary queryParams) + { + var response = new EchoResponseObject { message = $"Hello real endpoint (param1={queryParams["param1"]}, param2={queryParams["param2"]})" }; + + var ms = new MemoryStream(); + + await Task.Delay(1000); + await JsonSerializer.SerializeAsync(ms, response); + await Task.Delay(1000); + + ms.Position = 0; + + return ms; + } + }); + + [Theory] +#if !ANDROID // Custom schemes are not supported on Android +#if !WINDOWS // TODO: There seems to be a bug with the implementation in the WASDK version of WebView2 + [InlineData("app://echoservice/")] +#endif +#endif +#if !IOS && !MACCATALYST // Cannot intercept https requests on iOS/MacCatalyst + [InlineData("https://echo.free.beeceptor.com/sample-request")] +#endif + public Task RequestsCanBeInterceptedAndCustomDataReturnedForDifferentHosts(string uriBase) => + RunTest(async (blazorWebView, handler) => + { + blazorWebView.WebResourceRequested += (sender, e) => + { + if (new Uri(uriBase).IsBaseOf(e.Uri) && !e.Method.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase)) + { + // 1. Get the request from the platform args + var name = e.Headers["X-Echo-Name"]; + + // 2. Create the response data + var response = new EchoResponseObject + { + message = $"Hello {name} (param1={e.QueryParameters["param1"]}, param2={e.QueryParameters["param2"]})", + }; + var responseData = JsonSerializer.SerializeToUtf8Bytes(response); + var responseLength = responseData.Length.ToString(CultureInfo.InvariantCulture); + + // 3. Create the response + var headers = new Dictionary + { + ["Content-Length"] = responseLength, + ["Access-Control-Allow-Origin"] = "*", + ["Access-Control-Allow-Headers"] = "*", + ["Access-Control-Allow-Methods"] = "GET", + }; + e.SetResponse(200, "OK", headers, new MemoryStream(responseData)); + + // 4. Let the app know we are handling it entirely + e.Handled = true; + } + }; + + // Execute JavaScript to make the request and store result in controlDiv + var responseObject = await WebViewHelpers.ExecuteAsyncScriptAndWaitForResult(handler.PlatformView, + $$$""" + const response = await fetch('{{{uriBase}}}?param1=value1¶m2=value2', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'X-Test-Header': 'Test Value', + 'X-Echo-Name': 'Matthew' + } + }); + const jsonData = await response.json(); + return jsonData; + """); + + Assert.NotNull(responseObject); + Assert.Equal("Hello Matthew (param1=value1, param2=value2)", responseObject.message); + }); + + [Theory] +#if !ANDROID // Custom schemes are not supported on Android +#if !WINDOWS // TODO: There seems to be a bug with the implementation in the WASDK version of WebView2 + [InlineData("app://echoservice/")] +#endif +#endif +#if !IOS && !MACCATALYST // Cannot intercept https requests on iOS/MacCatalyst + [InlineData("https://echo.free.beeceptor.com/sample-request")] +#endif + public Task RequestsCanBeInterceptedAndHeadersAddedForDifferentHosts(string uriBase) => + RunTest(async (blazorWebView, handler) => + { + const string ExpectedHeaderValue = "My Custom Header Value"; + + blazorWebView.WebResourceRequested += (sender, e) => + { + if (new Uri(uriBase).IsBaseOf(e.Uri) && !e.Method.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase)) + { +#if WINDOWS + // Add the desired header for Windows by modifying the request + e.PlatformArgs.Request.Headers.SetHeader("X-Request-Header", ExpectedHeaderValue); +#elif IOS || MACCATALYST + // We are going to handle this ourselves + e.Handled = true; + + // Intercept the request and add the desired header to a copy of the request + var task = e.PlatformArgs.UrlSchemeTask; + + // Create a mutable copy of the request (this preserves all existing headers and properties) + var request = e.PlatformArgs.Request.MutableCopy() as Foundation.NSMutableUrlRequest; + + // Set the URL to the desired request URL as iOS only allows us to intercept non-https requests + request.Url = new("https://echo.free.beeceptor.com/sample-request"); + + // Add our custom header + var headers = request.Headers.MutableCopy() as Foundation.NSMutableDictionary; + headers[(Foundation.NSString)"X-Request-Header"] = (Foundation.NSString)ExpectedHeaderValue; + request.Headers = headers; + + // Create a session configuration and session to send the request + var configuration = Foundation.NSUrlSessionConfiguration.DefaultSessionConfiguration; + var session = Foundation.NSUrlSession.FromConfiguration(configuration); + + // Create a data task to send the request and get the response + var dataTask = session.CreateDataTask(request, (data, response, error) => + { + if (error is not null) + { + // Handle the error by completing the task with an error response + task.DidFailWithError(error); + return; + } + + if (response is Foundation.NSHttpUrlResponse httpResponse) + { + // Forward the response headers and status + task.DidReceiveResponse(httpResponse); + + // Forward the response body if any + if (data != null) + { + task.DidReceiveData(data); + } + + // Complete the task + task.DidFinish(); + } + else + { + // Fallback for non-HTTP responses or unexpected response type + task.DidFailWithError(new Foundation.NSError(new Foundation.NSString("HybridWebViewError"), -1, null)); + } + }); + + // Start the request + dataTask.Resume(); +#elif ANDROID + // We are going to handle this ourselves + e.Handled = true; + + // Intercept the request and add the desired header to a new request + var request = e.PlatformArgs.Request; + + // Copy the request + var url = new Java.Net.URL(request.Url.ToString()); + var connection = (Java.Net.HttpURLConnection)url.OpenConnection(); + connection.RequestMethod = request.Method; + foreach (var header in request.RequestHeaders) + { + connection.SetRequestProperty(header.Key, header.Value); + } + + // Add our custom header + connection.SetRequestProperty("X-Request-Header", ExpectedHeaderValue); + + // Set the response property + e.PlatformArgs.Response = new global::Android.Webkit.WebResourceResponse( + connection.ContentType, + connection.ContentEncoding ?? "UTF-8", + (int)connection.ResponseCode, + connection.ResponseMessage, + new Dictionary + { + ["Access-Control-Allow-Origin"] = "*", + ["Access-Control-Allow-Headers"] = "*", + ["Access-Control-Allow-Methods"] = "GET", + }, + connection.InputStream); +#endif + } + }; + + // Execute JavaScript to make the request and store result in controlDiv + var responseObject = await WebViewHelpers.ExecuteAsyncScriptAndWaitForResult(handler.PlatformView, + $$$""" + const response = await fetch('{{{uriBase}}}?param1=value1¶m2=value2', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'X-Test-Header': 'Test Value', + 'X-Echo-Name': 'Matthew' + } + }); + const jsonData = await response.json(); + return jsonData; + """); + + Assert.NotNull(responseObject); + Assert.NotNull(responseObject.headers); + Assert.True(responseObject.headers.TryGetValue("X-Request-Header", out var actualHeaderValue)); + Assert.Equal(ExpectedHeaderValue, actualHeaderValue); + }); + + [Theory] +#if !ANDROID // Custom schemes are not supported on Android +#if !WINDOWS // TODO: There seems to be a bug with the implementation in the WASDK version of WebView2 + [InlineData("app://echoservice/")] +#endif +#endif +#if !IOS && !MACCATALYST // Cannot intercept https requests on iOS/MacCatalyst + [InlineData("https://echo.free.beeceptor.com/sample-request")] +#endif + public Task RequestsCanBeInterceptedAndCancelledForDifferentHosts(string uriBase) => + RunTest(async (blazorWebView, handler) => + { + var intercepted = false; + + blazorWebView.WebResourceRequested += (sender, e) => + { + if (new Uri(uriBase).IsBaseOf(e.Uri)) + { + intercepted = true; + + // 1. Create the response + e.SetResponse(403, "Forbidden"); + + // 2. Let the app know we are handling it entirely + e.Handled = true; + } + }; + + // Execute JavaScript to make the request and store result in controlDiv + var result = await WebViewHelpers.ExecuteAsyncScriptAndWaitForResult(handler.PlatformView, + $$$""" + try { + const response = await fetch('{{{uriBase}}}?param1=value1¶m2=value2'); + return (response.status === 403); + } catch (e) { + return true; // Request was cancelled/failed + } + """); + + Assert.True(intercepted, "The request should have been intercepted."); + Assert.True(result); + }); + + [Theory] +#if !ANDROID // Custom schemes are not supported on Android +#if !WINDOWS // TODO: There seems to be a bug with the implementation in the WASDK version of WebView2 + [InlineData("app://echoservice/")] +#endif +#endif +#if !IOS && !MACCATALYST // Cannot intercept https requests on iOS/MacCatalyst + [InlineData("https://echo.free.beeceptor.com/sample-request")] +#endif + public Task RequestsCanBeInterceptedAndCaseInsensitiveHeadersRead(string uriBase) => + RunTest(async (blazorWebView, handler) => + { + var headerValues = new Dictionary(); + + blazorWebView.WebResourceRequested += (sender, e) => + { + if (new Uri(uriBase).IsBaseOf(e.Uri) && !e.Method.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase)) + { + // Should be exactly as set in the JS + try + { headerValues["X-Echo-Name"] = e.Headers["X-Echo-Name"]; } + catch (Exception ex) + { headerValues["X-Echo-Name"] = ex.Message; } + + // Sometimes lowercase is used + try + { headerValues["x-echo-name"] = e.Headers["x-echo-name"]; } + catch (Exception ex) + { headerValues["x-echo-name"] = ex.Message; } + + // This should never actually occur + try + { headerValues["X-ECHO-name"] = e.Headers["X-ECHO-name"]; } + catch (Exception ex) + { headerValues["X-ECHO-name"] = ex.Message; } + + // If the request is for the app:// resources, we return an empty response + // because the tests are not doing anything with the response. + if (e.Uri.Scheme == "app") + { + var headers = new Dictionary + { + ["Content-Type"] = "application/json", + ["Access-Control-Allow-Origin"] = "*", + ["Access-Control-Allow-Headers"] = "*", + ["Access-Control-Allow-Methods"] = "GET", + }; + e.SetResponse(200, "OK", headers, new MemoryStream(Encoding.UTF8.GetBytes("{\"message\":\"test\"}"))); + e.Handled = true; + } + } + }; + + // Execute JavaScript to make the request and store result in controlDiv + var responseObject = await WebViewHelpers.ExecuteAsyncScriptAndWaitForResult(handler.PlatformView, + $$$""" + const response = await fetch('{{{uriBase}}}?param1=value1¶m2=value2', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'X-Test-Header': 'Test Value', + 'X-Echo-Name': 'Matthew' + } + }); + const jsonData = await response.json(); + return jsonData; + """); + + Assert.NotEmpty(headerValues); + Assert.Equal("Matthew", headerValues["X-Echo-Name"]); + Assert.Equal("Matthew", headerValues["x-echo-name"]); + Assert.Equal("Matthew", headerValues["X-ECHO-name"]); + }); + + private async Task RunTest(Func test) + { + EnsureHandlerCreated(builder => + { + builder.Services.AddMauiBlazorWebView(); + }); + + var blazorWebView = new BlazorWebViewWithCustomFiles + { + HostPage = "wwwroot/index.html", + CustomFiles = new Dictionary + { + { "index.html", TestStaticFilesContents.DefaultMauiIndexHtmlContent }, + }, + }; + + blazorWebView.RootComponents.Add(new RootComponent + { + ComponentType = typeof(NoOpComponent), + Selector = "#app" + }); + + // Set up the view to be displayed/parented and run our tests on it + await AttachAndRun(blazorWebView, async handler => + { + var blazorWebViewHandler = handler as BlazorWebViewHandler; + var platformWebView = blazorWebViewHandler.PlatformView; + + await WebViewHelpers.WaitForWebViewReady(platformWebView); + + // Wait for the no-op component to load + await WebViewHelpers.WaitForControlDiv(platformWebView, controlValueToWaitFor: "Static"); + + await test(blazorWebView, blazorWebViewHandler); + }); + } + + public class ResponseObject + { + public string method { get; set; } + public string protocol { get; set; } + public string host { get; set; } + public string path { get; set; } + public string ip { get; set; } + public Dictionary headers { get; set; } + public Dictionary parsedQueryParams { get; set; } + } + + public class EchoResponseObject + { + public string message { get; set; } = string.Empty; + } + + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(EchoResponseObject))] + [JsonSerializable(typeof(ResponseObject))] + internal partial class BlazorWebViewTestContext : JsonSerializerContext + { + } +} diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.Services.cs b/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.Services.cs new file mode 100644 index 000000000000..9a4c8fd9132a --- /dev/null +++ b/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.Services.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.WebView.Maui; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Maui.MauiBlazorWebView.DeviceTests.Components; +using WebViewAppShared; +using Xunit; + +namespace Microsoft.Maui.MauiBlazorWebView.DeviceTests.Elements; + +public partial class BlazorWebViewTests +{ + [Fact] + public async Task BlazorWebViewDispatchGetsScopedServices() + { + EnsureHandlerCreated(additionalCreationActions: appBuilder => + { + appBuilder.Services.AddMauiBlazorWebView(); + appBuilder.Services.AddScoped(); + }); + + var bwv = new BlazorWebViewWithCustomFiles + { + HostPage = "wwwroot/index.html", + CustomFiles = new Dictionary + { + { "index.html", TestStaticFilesContents.DefaultMauiIndexHtmlContent }, + }, + }; + bwv.RootComponents.Add(new RootComponent { ComponentType = typeof(NoOpComponent), Selector = "#app", }); + + await InvokeOnMainThreadAsync(async () => + { + var bwvHandler = CreateHandler(bwv); + var platformWebView = bwvHandler.PlatformView; + await WebViewHelpers.WaitForWebViewReady(platformWebView); + + // Wait for the no-op component to load + await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "Static"); + + // Use BlazorWebView.TryDispatchAsync to access scoped services + var calledWorkItem = await bwv.TryDispatchAsync(async services => + { + var jsInterop = services.GetRequiredService(); + await jsInterop.UpdateControlDiv("some new value"); + }); + + Assert.True(calledWorkItem); + + // Wait for the no-op component to show the new value + await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "some new value"); + }); + } + + [Fact] + public async Task BlazorWebViewDispatchBeforeRunningReturnsFalse() + { + EnsureHandlerCreated(additionalCreationActions: appBuilder => + { + appBuilder.Services.AddMauiBlazorWebView(); + appBuilder.Services.AddScoped(); + }); + + var bwv = new BlazorWebViewWithCustomFiles + { + HostPage = "wwwroot/index.html", + CustomFiles = new Dictionary + { + { "index.html", TestStaticFilesContents.DefaultMauiIndexHtmlContent }, + }, + }; + bwv.RootComponents.Add(new RootComponent { ComponentType = typeof(NoOpComponent), Selector = "#app", }); + + await InvokeOnMainThreadAsync(async () => + { + // Try to dispatch before the MAUI handler is created + var calledWorkItem = await bwv.TryDispatchAsync(_ => throw new NotImplementedException()); + + Assert.False(calledWorkItem); + }); + } + + [Fact] + public async Task BlazorWebViewWithoutDispatchFailsToGetScopedServices() + { + EnsureHandlerCreated(additionalCreationActions: appBuilder => + { + appBuilder.Services.AddMauiBlazorWebView(); + appBuilder.Services.AddScoped(); + }); + + var bwv = new BlazorWebViewWithCustomFiles + { + HostPage = "wwwroot/index.html", + CustomFiles = new Dictionary + { + { "index.html", TestStaticFilesContents.DefaultMauiIndexHtmlContent }, + }, + }; + bwv.RootComponents.Add(new RootComponent { ComponentType = typeof(NoOpComponent), Selector = "#app", }); + + await InvokeOnMainThreadAsync(async () => + { + var bwvHandler = CreateHandler(bwv); + var platformWebView = bwvHandler.PlatformView; + await WebViewHelpers.WaitForWebViewReady(platformWebView); + + // Wait for the no-op component to load + await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "Static"); + + // DON'T use BlazorWebView.Dispatch to access scoped services; instead, just try to get it directly (and fail) + var jsInterop = bwv.Handler.MauiContext.Services.GetRequiredService(); + + // Now when we try to use JSInterop, it will throw an exception + await Assert.ThrowsAsync(async () => + { + await jsInterop.UpdateControlDiv("this will fail!"); + }); + }); + } +} diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.cs b/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.cs index 2184e1c3316e..fbf51c28c5d6 100644 --- a/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.cs +++ b/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.cs @@ -1,254 +1,54 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Components.WebView.Maui; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Logging; using WebViewAppShared; -using Xunit; +using Xunit.Abstractions; -namespace Microsoft.Maui.MauiBlazorWebView.DeviceTests.Elements +namespace Microsoft.Maui.MauiBlazorWebView.DeviceTests.Elements; + +[Category(TestCategory.BlazorWebView)] +public partial class BlazorWebViewTests : Microsoft.Maui.DeviceTests.ControlsHandlerTestBase { - [Category(TestCategory.BlazorWebView)] - public class BlazorWebViewTests : Microsoft.Maui.DeviceTests.ControlsHandlerTestBase + public BlazorWebViewTests(ITestOutputHelper output) { - [Fact] - public async Task BasicRazorComponentClick() - { - EnsureHandlerCreated(additionalCreationActions: appBuilder => - { - appBuilder.Services.AddMauiBlazorWebView(); - }); - - var bwv = new BlazorWebViewWithCustomFiles - { - HostPage = "wwwroot/index.html", - CustomFiles = new Dictionary - { - { "index.html", TestStaticFilesContents.DefaultMauiIndexHtmlContent }, - }, - }; - bwv.RootComponents.Add(new RootComponent { ComponentType = typeof(TestComponent1), Selector = "#app", }); - - await InvokeOnMainThreadAsync(async () => - { - var bwvHandler = CreateHandler(bwv); - var platformWebView = bwvHandler.PlatformView; - await WebViewHelpers.WaitForWebViewReady(platformWebView); - - // Click a button in a Razor component 3 times - await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "0"); - - var c1 = await WebViewHelpers.ExecuteScriptAsync(bwvHandler.PlatformView, "document.getElementById('incrementButton').click()"); - - await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "1"); - - var c2 = await WebViewHelpers.ExecuteScriptAsync(bwvHandler.PlatformView, "document.getElementById('incrementButton').click()"); - - await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "2"); - - var c3 = await WebViewHelpers.ExecuteScriptAsync(bwvHandler.PlatformView, "document.getElementById('incrementButton').click()"); - - await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "3"); - - // And the counter value should increment from 0 to 3. - var actualFinalCounterValue = await WebViewHelpers.ExecuteScriptAsync(bwvHandler.PlatformView, "document.getElementById('counterValue').innerText"); - actualFinalCounterValue = actualFinalCounterValue.Trim('\"'); // some platforms return quoted values, so we trim them - Assert.Equal("3", actualFinalCounterValue); - }); - } - - [Fact] - public async Task BlazorWebViewLogsRequests() - { - var testLoggerProvider = new TestLoggerProvider(); - EnsureHandlerCreated(additionalCreationActions: appBuilder => - { - appBuilder.Services.AddMauiBlazorWebView(); - appBuilder.Services.AddLogging(c => - { - // Enable maximum logging for BlazorWebView - c.AddFilter("Microsoft.AspNetCore.Components.WebView", LogLevel.Trace); - - c.AddProvider(testLoggerProvider); - }); - }); - - var bwv = new BlazorWebViewWithCustomFiles - { - HostPage = "wwwroot/index.html", - CustomFiles = new Dictionary - { - { "index.html", TestStaticFilesContents.DefaultMauiIndexHtmlContent }, - }, - }; - bwv.RootComponents.Add(new RootComponent { ComponentType = typeof(NoOpComponent), Selector = "#app", }); - - await InvokeOnMainThreadAsync(async () => - { - var bwvHandler = CreateHandler(bwv); - var platformWebView = bwvHandler.PlatformView; - await WebViewHelpers.WaitForWebViewReady(platformWebView); - - // Wait for the no-op component to load - await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "Static"); - }); - - var events = testLoggerProvider.GetEvents(); - - // Here we choose an arbitrary subset of logs to verify. We could check every single one, but - // it's different on each platform, and subject to change in the future. Less is more. - Assert.Equal(1, events.Count(c => c.EventId.Id == 0 && c.LogLevel == LogLevel.Debug && c.EventId.Name == "NavigatingToUri")); - Assert.Equal(1, events.Count(c => c.EventId.Id == 4 && c.LogLevel == LogLevel.Debug && c.EventId.Name == "HandlingWebRequest" && c.Message.Contains("/_framework/blazor.webview.js", System.StringComparison.Ordinal))); - } - - - [Fact] - public async Task BlazorWebViewUsesStartPath() - { - EnsureHandlerCreated(additionalCreationActions: appBuilder => - { - appBuilder.Services.AddMauiBlazorWebView(); - }); - - var bwv = new BlazorWebViewWithCustomFiles - { - StartPath = "CustomStart/SomeData", - HostPage = "wwwroot/index.html", - CustomFiles = new Dictionary - { - { "index.html", TestStaticFilesContents.DefaultMauiIndexHtmlContent }, - }, - }; - bwv.RootComponents.Add(new RootComponent { ComponentType = typeof(RouterComponent), Selector = "#app", }); - - await InvokeOnMainThreadAsync(async () => - { - var bwvHandler = CreateHandler(bwv); - var platformWebView = bwvHandler.PlatformView; - await WebViewHelpers.WaitForWebViewReady(platformWebView); - - // Wait for the component to load - await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "Found the start path with: 'SomeData'"); - }); - } - - [Fact] - public async Task BlazorWebViewDispatchGetsScopedServices() - { - EnsureHandlerCreated(additionalCreationActions: appBuilder => - { - appBuilder.Services.AddMauiBlazorWebView(); - appBuilder.Services.AddScoped(); - }); - - var bwv = new BlazorWebViewWithCustomFiles - { - HostPage = "wwwroot/index.html", - CustomFiles = new Dictionary - { - { "index.html", TestStaticFilesContents.DefaultMauiIndexHtmlContent }, - }, - }; - bwv.RootComponents.Add(new RootComponent { ComponentType = typeof(NoOpComponent), Selector = "#app", }); - - await InvokeOnMainThreadAsync(async () => - { - var bwvHandler = CreateHandler(bwv); - var platformWebView = bwvHandler.PlatformView; - await WebViewHelpers.WaitForWebViewReady(platformWebView); - - // Wait for the no-op component to load - await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "Static"); - - // Use BlazorWebView.TryDispatchAsync to access scoped services - var calledWorkItem = await bwv.TryDispatchAsync(async services => - { - var jsInterop = services.GetRequiredService(); - await jsInterop.UpdateControlDiv("some new value"); - }); - - Assert.True(calledWorkItem); + Output = output; + } + + public ITestOutputHelper Output { get; } - // Wait for the no-op component to show the new value - await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "some new value"); - }); - } + sealed class BlazorWebViewWithCustomFiles : BlazorWebView + { + public Dictionary CustomFiles { get; set; } - [Fact] - public async Task BlazorWebViewDispatchBeforeRunningReturnsFalse() + public override IFileProvider CreateFileProvider(string contentRootDir) { - EnsureHandlerCreated(additionalCreationActions: appBuilder => - { - appBuilder.Services.AddMauiBlazorWebView(); - appBuilder.Services.AddScoped(); - }); - - var bwv = new BlazorWebViewWithCustomFiles + if (CustomFiles == null) { - HostPage = "wwwroot/index.html", - CustomFiles = new Dictionary - { - { "index.html", TestStaticFilesContents.DefaultMauiIndexHtmlContent }, - }, - }; - bwv.RootComponents.Add(new RootComponent { ComponentType = typeof(NoOpComponent), Selector = "#app", }); + return null; + } + var inMemoryFiles = new InMemoryStaticFileProvider( + fileContentsMap: CustomFiles, + // The contentRoot is ignored here because in WinForms it would include the absolute physical path to the app's content, which this provider doesn't care about + contentRoot: null); - await InvokeOnMainThreadAsync(async () => - { - // Try to dispatch before the MAUI handler is created - var calledWorkItem = await bwv.TryDispatchAsync(_ => throw new NotImplementedException()); + var baseFileProvider = base.CreateFileProvider(contentRootDir); - Assert.False(calledWorkItem); - }); + return baseFileProvider == null + ? inMemoryFiles + : new CompositeFileProvider(inMemoryFiles, baseFileProvider); } + } - [Fact] - public async Task BlazorWebViewWithoutDispatchFailsToGetScopedServices() - { - EnsureHandlerCreated(additionalCreationActions: appBuilder => - { - appBuilder.Services.AddMauiBlazorWebView(); - appBuilder.Services.AddScoped(); - }); - - var bwv = new BlazorWebViewWithCustomFiles - { - HostPage = "wwwroot/index.html", - CustomFiles = new Dictionary - { - { "index.html", TestStaticFilesContents.DefaultMauiIndexHtmlContent }, - }, - }; - bwv.RootComponents.Add(new RootComponent { ComponentType = typeof(NoOpComponent), Selector = "#app", }); - - await InvokeOnMainThreadAsync(async () => - { - var bwvHandler = CreateHandler(bwv); - var platformWebView = bwvHandler.PlatformView; - await WebViewHelpers.WaitForWebViewReady(platformWebView); - - // Wait for the no-op component to load - await WebViewHelpers.WaitForControlDiv(bwvHandler.PlatformView, controlValueToWaitFor: "Static"); - - // DON'T use BlazorWebView.Dispatch to access scoped services; instead, just try to get it directly (and fail) - var jsInterop = bwv.Handler.MauiContext.Services.GetRequiredService(); - - // Now when we try to use JSInterop, it will throw an exception - await Assert.ThrowsAsync(async () => - { - await jsInterop.UpdateControlDiv("this will fail!"); - }); - }); - } + class TestResponseObject + { + public string message { get; set; } = string.Empty; + } - public static class TestStaticFilesContents - { - public static readonly string DefaultMauiIndexHtmlContent = @" - + static class TestStaticFilesContents + { + public const string DefaultMauiIndexHtmlContent = @" + @@ -271,29 +71,5 @@ An unhandled error has occurred. "; - } - - private sealed class BlazorWebViewWithCustomFiles : BlazorWebView - { - public Dictionary CustomFiles { get; set; } - - public override IFileProvider CreateFileProvider(string contentRootDir) - { - if (CustomFiles == null) - { - return null; - } - var inMemoryFiles = new InMemoryStaticFileProvider( - fileContentsMap: CustomFiles, - // The contentRoot is ignored here because in WinForms it would include the absolute physical path to the app's content, which this provider doesn't care about - contentRoot: null); - - var baseFileProvider = base.CreateFileProvider(contentRootDir); - - return baseFileProvider == null - ? inMemoryFiles - : new CompositeFileProvider(inMemoryFiles, baseFileProvider); - } - } } } diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Platforms/Android/AndroidManifest.xml b/src/BlazorWebView/tests/MauiDeviceTests/Platforms/Android/AndroidManifest.xml index ddf3fbc76cca..b60cfb8af5f7 100644 --- a/src/BlazorWebView/tests/MauiDeviceTests/Platforms/Android/AndroidManifest.xml +++ b/src/BlazorWebView/tests/MauiDeviceTests/Platforms/Android/AndroidManifest.xml @@ -4,4 +4,5 @@ + \ No newline at end of file diff --git a/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Android.cs b/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Android.cs index ef934517c7a4..9fcdf7f5d387 100644 --- a/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Android.cs +++ b/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Android.cs @@ -9,11 +9,16 @@ public static partial class WebViewHelpers { public static async Task WaitForWebViewReady(AWebView webview) { - await Retry(async () => - { - var blazorObject = await ExecuteScriptAsync(webview, "(window.Blazor !== null) && (window.__BlazorStarted === true)"); - return blazorObject == "true"; - }, createExceptionWithTimeoutMS: (int timeoutInMS) => Task.FromResult(new Exception($"Waited {timeoutInMS}ms but couldn't get window.Blazor to be non-null *and* have window.__BlazorStarted to be true."))); + await Retry( + async () => + { + var blazorObject = await ExecuteScriptAsync(webview, "(window.Blazor !== null) && (window.__BlazorStarted === true)"); + return blazorObject == "true"; + }, + timeoutInMS => + { + return Task.FromResult(new Exception($"Waited {timeoutInMS}ms but couldn't get window.Blazor to be non-null *and* have window.__BlazorStarted to be true.")); + }); } public static Task ExecuteScriptAsync(AWebView webview, string script) @@ -23,24 +28,6 @@ public static Task ExecuteScriptAsync(AWebView webview, string script) return jsResult.JsResult; } - public static async Task WaitForControlDiv(AWebView webView, string controlValueToWaitFor) - { - var quotedExpectedValue = "\"" + controlValueToWaitFor + "\""; - var latestControlValue = ""; - - await Retry(async () => - { - latestControlValue = await ExecuteScriptAsync(webView, "document.getElementById('controlDiv').innerText"); - return latestControlValue == quotedExpectedValue; - }, createExceptionWithTimeoutMS: async (int timeoutInMS) => - { - var documentHtmlJavaScriptEncoded = await ExecuteScriptAsync(webView, "document.body.innerHTML"); - var documentHtmlString = System.Text.Json.JsonSerializer.Deserialize(documentHtmlJavaScriptEncoded); - - return new Exception($"Waited {timeoutInMS}ms but couldn't get controlDiv to have value '{controlValueToWaitFor}'. Most recent value was '{latestControlValue}'. document.body.innerHTML = {documentHtmlString}"); - }); - } - class JavascriptResult : Java.Lang.Object, IValueCallback { TaskCompletionSource source; diff --git a/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.NetStandard.cs b/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.NetStandard.cs index 1e1a62374a28..a09421f64eb5 100644 --- a/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.NetStandard.cs +++ b/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.NetStandard.cs @@ -21,6 +21,11 @@ public static Task ExecuteScriptAsync(object platformWebView, string scr { return Task.FromResult(null); } + + public static Task WaitForControlDivToChangeFrom(object platformWebView, string valueToChangeFrom) + { + return Task.FromResult(null); + } } #endif } diff --git a/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Shared.cs b/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Shared.cs index 41ea55d16f89..3b9715f6cf9e 100644 --- a/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Shared.cs +++ b/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Shared.cs @@ -1,5 +1,16 @@ -using System; +#nullable enable +using System; +using System.Text.Json; using System.Threading.Tasks; +#if ANDROID +using PlatformWebView = Android.Webkit.WebView; +#elif IOS || MACCATALYST +using PlatformWebView = WebKit.WKWebView; +#elif WINDOWS +using PlatformWebView = Microsoft.UI.Xaml.Controls.WebView2; +#else +using PlatformWebView = System.Object; +#endif namespace Microsoft.Maui.MauiBlazorWebView.DeviceTests { @@ -21,5 +32,130 @@ private static async Task Retry(Func> tryAction, Func + /// Executes an async JavaScript function body and waits for the result to be stored in controlDiv. + /// This method handles all the boilerplate for script injection and Promise avoidance. + /// + /// The WKWebView instance + /// The body of the async function (without function wrapper) + /// The result stored in controlDiv after the async operation completes + public static async Task ExecuteAsyncScriptAndWaitForResult(PlatformWebView webView, string asyncFunctionBody) + { + // Inject script that executes the async function and stores result in controlDiv + await ExecuteScriptAsync(webView, + $$""" + (function() { + var script = document.createElement('script'); + script.textContent = ` + (async function() { + let result = { + message: 'Failed to run test' + }; + try { + result = await (async function() { + {{asyncFunctionBody}} + })(); + } catch (error) { + result.message = error.message ?? error.toString(); + } + document.getElementById('controlDiv').innerText = JSON.stringify(result); + })(); + `; + document.head.appendChild(script); + return true; + })() + """); + + // Wait for the async operation to complete and get the result + var result = await WaitForControlDivToChangeFrom(webView, "Static"); + + // Deserialize the result from controlDiv + if (TryDeserialize(result, out var value)) + return value; + + // sometimes the result is serialized by the platform, so we need to deserialize it as a string first + if (TryDeserialize(result, out var resultString)) + { + // now try again with the string result + if (TryDeserialize(resultString, out var secondTime)) + return secondTime; + } + + throw new Exception($"Failed to deserialize result from controlDiv: {result}"); + + static bool TryDeserialize(string? result, out TInner? value) + { + if (result is null or "null" or "undefined") + { + value = default; + return false; + } + + try + { + value = JsonSerializer.Deserialize(result); + return true; + } + catch (JsonException) + { + value = default; + } + + return false; + } + } + + public static async Task WaitForControlDiv(PlatformWebView webView, string controlValueToWaitFor) + { + await Retry( + async () => + { + var controlValue = await GetControlDivValue(webView); + + return + controlValue == controlValueToWaitFor || + controlValue == $"\"{controlValueToWaitFor}\""; + }, + timeoutInMS => + { + return Task.FromResult(new Exception($"Waited {timeoutInMS}ms but couldn't get controlDiv to have value '{controlValueToWaitFor}'.")); + }); + } + + public static async Task WaitForControlDivToChangeFrom(PlatformWebView webView, string valueToChangeFrom) + { + string? latestControlValue = null; + + await Retry( + async () => + { + var controlValue = await GetControlDivValue(webView); + latestControlValue = controlValue; + + return + !string.IsNullOrEmpty(controlValue) && + controlValue != "null" && + controlValue != valueToChangeFrom && + controlValue != $"\"{valueToChangeFrom}\""; + }, + timeoutInMS => + { + return Task.FromResult(new Exception($"Waited {timeoutInMS}ms but controlDiv never changed from value '{valueToChangeFrom}'.")); + }); + + // Remove quotes that some platforms add + return latestControlValue; + } + + private static Task GetControlDivValue(PlatformWebView webView) + { + return ExecuteScriptAsync(webView, + """ + (document.getElementById('controlDiv') === null + ? null + : document.getElementById('controlDiv').innerText) + """); + } } } diff --git a/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Windows.cs b/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Windows.cs index dd87c0213bde..f32f55fd5028 100644 --- a/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Windows.cs +++ b/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Windows.cs @@ -12,11 +12,16 @@ public static async Task WaitForWebViewReady(WebView2 wv2) { CoreWebView2 coreWebView2 = null; - await Retry(() => - { - coreWebView2 = wv2.CoreWebView2; - return Task.FromResult(coreWebView2 != null); - }, createExceptionWithTimeoutMS: (int timeoutInMS) => Task.FromResult(new Exception($"Waited {timeoutInMS}ms but couldn't get CoreWebView2 to be available."))); + await Retry( + () => + { + coreWebView2 = wv2.CoreWebView2; + return Task.FromResult(coreWebView2 != null); + }, + timeoutInMS => + { + return Task.FromResult(new Exception($"Waited {timeoutInMS}ms but couldn't get CoreWebView2 to be available.")); + }); var domLoaded = false; var sem = new SemaphoreSlim(1); @@ -35,13 +40,18 @@ await Retry(() => // for that, we inspect an arbitrary custom HTML element attribute to see if we can find it. If we can find it, then surely // the DOM content is loaded, so we can continue with the test. - await Retry(async () => - { - var testHtmlLoadedAttributeValue = await wv2.CoreWebView2.ExecuteScriptAsync("(document.head.attributes['testhtmlloaded']?.value === 'true')"); + await Retry( + async () => + { + var testHtmlLoadedAttributeValue = await wv2.CoreWebView2.ExecuteScriptAsync("(document.head.attributes['testhtmlloaded']?.value === 'true')"); - // If the event didn't fire, AND we couldn't find the custom HTML element attribute, then the test content didn't load - return testHtmlLoadedAttributeValue == "true"; - }, createExceptionWithTimeoutMS: (int timeoutInMS) => Task.FromResult(new Exception($"Waited {timeoutInMS}ms but couldn't get CoreWebView2.DOMContentLoaded to complete."))); + // If the event didn't fire, AND we couldn't find the custom HTML element attribute, then the test content didn't load + return testHtmlLoadedAttributeValue == "true"; + }, + timeoutInMS => + { + return Task.FromResult(new Exception($"Waited {timeoutInMS}ms but couldn't get CoreWebView2.DOMContentLoaded to complete.")); + }); } return; } @@ -50,16 +60,5 @@ public static async Task ExecuteScriptAsync(WebView2 webView2, string sc { return await webView2.CoreWebView2.ExecuteScriptAsync(javaScript: script); } - - public static async Task WaitForControlDiv(WebView2 webView2, string controlValueToWaitFor) - { - var quotedExpectedValue = "\"" + controlValueToWaitFor + "\""; - - await Retry(async () => - { - var controlValue = await ExecuteScriptAsync(webView2, "document.getElementById('controlDiv').innerText"); - return controlValue == quotedExpectedValue; - }, createExceptionWithTimeoutMS: (int timeoutInMS) => Task.FromResult(new Exception($"Waited {timeoutInMS}ms but couldn't get controlDiv to have value '{controlValueToWaitFor}'."))); - } } } diff --git a/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.iOS.cs b/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.iOS.cs index fb11755db9b6..5d6579a586b9 100644 --- a/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.iOS.cs +++ b/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.iOS.cs @@ -8,11 +8,16 @@ public static partial class WebViewHelpers { public static async Task WaitForWebViewReady(WKWebView webview) { - await Retry(async () => - { - var blazorObject = await ExecuteScriptAsync(webview, "(window.Blazor !== null).toString()"); - return blazorObject == "true"; - }, createExceptionWithTimeoutMS: (int timeoutInMS) => Task.FromResult(new Exception($"Waited {timeoutInMS}ms but couldn't get window.Blazor to be non-null."))); + await Retry( + async () => + { + var blazorObject = await ExecuteScriptAsync(webview, "(window.Blazor !== null).toString()"); + return blazorObject == "true"; + }, + timeoutInMS => + { + return Task.FromResult(new Exception($"Waited {timeoutInMS}ms but couldn't get window.Blazor to be non-null.")); + }); } public static async Task ExecuteScriptAsync(WKWebView webview, string script) @@ -20,16 +25,5 @@ public static async Task ExecuteScriptAsync(WKWebView webview, string sc var nsStringResult = await webview.EvaluateJavaScriptAsync(script); return nsStringResult?.ToString(); } - - public static async Task WaitForControlDiv(WKWebView webView, string controlValueToWaitFor) - { - var latestControlValue = ""; - - await Retry(async () => - { - latestControlValue = await ExecuteScriptAsync(webView, "(document.getElementById('controlDiv') === null ? null : document.getElementById('controlDiv').innerText)"); - return latestControlValue == controlValueToWaitFor; - }, createExceptionWithTimeoutMS: (int timeoutInMS) => Task.FromResult(new Exception($"Waited {timeoutInMS}ms but couldn't get controlDiv to have value '{controlValueToWaitFor}'. Most recent value was '{latestControlValue}'."))); - } } } diff --git a/src/Controls/src/Core/HybridWebView/HybridWebView.cs b/src/Controls/src/Core/HybridWebView/HybridWebView.cs index 524c08f7fa2a..2573142aee2a 100644 --- a/src/Controls/src/Core/HybridWebView/HybridWebView.cs +++ b/src/Controls/src/Core/HybridWebView/HybridWebView.cs @@ -66,10 +66,11 @@ void IHybridWebView.RawMessageReceived(string rawMessage) /// public event EventHandler? RawMessageReceived; - bool IHybridWebView.WebResourceRequested(WebResourceRequestedEventArgs args) + /// + 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; } @@ -77,14 +78,14 @@ bool IHybridWebView.WebResourceRequested(WebResourceRequestedEventArgs args) /// /// 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 property to true + /// The event handler can set the 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 - /// - /// or + /// + /// or /// method to provide a response to the request. /// - public event EventHandler? WebResourceRequested; + public event EventHandler? WebResourceRequested; /// /// Sends a raw message to the code running in the web view. Raw messages have no additional processing. diff --git a/src/Controls/src/Core/HybridWebView/HybridWebViewWebResourceRequestedEventArgs.cs b/src/Controls/src/Core/HybridWebView/HybridWebViewWebResourceRequestedEventArgs.cs deleted file mode 100644 index 41b770a34390..000000000000 --- a/src/Controls/src/Core/HybridWebView/HybridWebViewWebResourceRequestedEventArgs.cs +++ /dev/null @@ -1,301 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.Maui.Controls; - -/// -/// Event arguments for the event. -/// -public class HybridWebViewWebResourceRequestedEventArgs -{ - IReadOnlyDictionary? _headers; - IReadOnlyDictionary? _queryParams; - - internal HybridWebViewWebResourceRequestedEventArgs(PlatformHybridWebViewWebResourceRequestedEventArgs platformArgs) - { - PlatformArgs = platformArgs; - Uri = platformArgs.GetRequestUri() is string uri ? new Uri(uri) : throw new InvalidOperationException("Platform web request did not have a request URI."); - Method = platformArgs.GetRequestMethod() ?? throw new InvalidOperationException("Platform web request did not have a request METHOD."); - } - - /// - /// Initializes a new instance of the class - /// with the specified URI and method. - /// - public HybridWebViewWebResourceRequestedEventArgs(Uri uri, string method) - { - Uri = uri; - Method = method; - } - - /// - /// Gets the platform-specific event arguments. - /// - public PlatformHybridWebViewWebResourceRequestedEventArgs? PlatformArgs { get; } - - /// - /// Gets the URI of the requested resource. - /// - public Uri Uri { get; } - - /// - /// Gets the HTTP method used for the request (e.g., GET, POST). - /// - public string Method { get; } - - /// - /// Gets the headers associated with the request. - /// - public IReadOnlyDictionary Headers => - _headers ??= PlatformArgs?.GetRequestHeaders() ?? new Dictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Gets the query parameters from the URI. - /// - public IReadOnlyDictionary QueryParameters => - _queryParams ??= WebUtils.ParseQueryString(Uri, false) ?? new Dictionary(StringComparer.Ordinal); - - /// - /// Gets or sets a value indicating whether the request has been handled. - /// - /// If set to true, the web view will not process the request further and a response - /// must be provided using the - /// - /// or method. - /// If set to false, the web view will continue processing the request as normal. - /// - public bool Handled { get; set; } - - /// - /// Sets the response for the web resource request. - /// - /// This method must be called if the property is set to true. - /// - /// The HTTP status code for the response. - /// The reason phrase for the response. - /// The headers to include in the response. - /// The content of the response as a stream. - public void SetResponse(int code, string reason, IReadOnlyDictionary? headers, Stream? content) - { - _ = PlatformArgs ?? throw new InvalidOperationException("Platform web request was not valid."); - -#if WINDOWS - - // create the response - PlatformArgs.RequestEventArgs.Response = PlatformArgs.Sender.Environment.CreateWebResourceResponse( - content?.AsRandomAccessStream(), - code, - reason, - PlatformHeaders(headers)); - -#elif IOS || MACCATALYST - - // iOS and MacCatalyst will just wait until DidFinish is called - var task = PlatformArgs.UrlSchemeTask; - - // create and send the response headers - task.DidReceiveResponse(new Foundation.NSHttpUrlResponse( - PlatformArgs.Request.Url, - code, - "HTTP/1.1", - PlatformHeaders(headers))); - - // send the data - if (content is not null && Foundation.NSData.FromStream(content) is { } nsdata) - { - task.DidReceiveData(nsdata); - } - - // let the webview know - task.DidFinish(); - -#elif ANDROID - - // Android requires that we return immediately, even if the data is coming later - - // create and send the response headers - var platformHeaders = PlatformHeaders(headers, out var contentType); - PlatformArgs.Response = new global::Android.Webkit.WebResourceResponse( - contentType, - "UTF-8", - code, - reason, - platformHeaders, - content); - -#endif - } - - /// - /// Sets the asynchronous response for the web resource request. - /// - /// This method must be called if the property is set to true. - /// - /// The HTTP status code for the response. - /// The reason phrase for the response. - /// The headers to include in the response. - /// A task that represents the asynchronous operation of getting the response content. - /// - /// This method is not asynchronous and will return immediately. The actual response will be sent when the content task completes. - /// - public void SetResponse(int code, string reason, IReadOnlyDictionary? headers, Task contentTask) => - SetResponseAsync(code, reason, headers, contentTask).FireAndForget(); - -#pragma warning disable CS1998 // Android implememntation does not use async/await - async Task SetResponseAsync(int code, string reason, IReadOnlyDictionary? headers, Task contentTask) -#pragma warning restore CS1998 - { - _ = PlatformArgs ?? throw new InvalidOperationException("Platform web request was not valid."); - -#if WINDOWS - - // Windows uses a deferral to let the webview know that we are going to be async - using var deferral = PlatformArgs.RequestEventArgs.GetDeferral(); - - // get the actual content - var data = await contentTask; - - // create the response - PlatformArgs.RequestEventArgs.Response = PlatformArgs.Sender.Environment.CreateWebResourceResponse( - data?.AsRandomAccessStream(), - code, - reason, - PlatformHeaders(headers)); - - // let the webview know - deferral.Complete(); - -#elif IOS || MACCATALYST - - // iOS and MacCatalyst will just wait until DidFinish is called - var task = PlatformArgs.UrlSchemeTask; - - // create and send the response headers - task.DidReceiveResponse(new Foundation.NSHttpUrlResponse( - PlatformArgs.Request.Url, - code, - "HTTP/1.1", - PlatformHeaders(headers))); - - // get the actual content - var data = await contentTask; - - // send the data - if (data is not null && Foundation.NSData.FromStream(data) is { } nsdata) - { - task.DidReceiveData(nsdata); - } - - // let the webview know - task.DidFinish(); - -#elif ANDROID - - // Android requires that we return immediately, even if the data is coming later - - // get the actual content - var stream = new AsyncStream(contentTask, null); - - // create and send the response headers - var platformHeaders = PlatformHeaders(headers, out var contentType); - PlatformArgs.Response = new global::Android.Webkit.WebResourceResponse( - contentType, - "UTF-8", - code, - reason, - platformHeaders, - stream); - -#endif - } - -#if WINDOWS - static string? PlatformHeaders(IReadOnlyDictionary? headers) - { - if (headers?.Count > 0) - { - var sb = new StringBuilder(); - foreach (var header in headers) - { - sb.AppendLine($"{header.Key}: {header.Value}"); - } - return sb.ToString(); - } - return null; - } -#elif IOS || MACCATALYST - static Foundation.NSMutableDictionary? PlatformHeaders(IReadOnlyDictionary? headers) - { - if (headers?.Count > 0) - { - var dic = new Foundation.NSMutableDictionary(); - foreach (var header in headers) - { - dic.Add((Foundation.NSString)header.Key, (Foundation.NSString)header.Value); - } - return dic; - } - return null; - } -#elif ANDROID - static global::Android.Runtime.JavaDictionary? PlatformHeaders(IReadOnlyDictionary? headers, out string contentType) - { - contentType = "application/octet-stream"; - if (headers?.Count > 0) - { - var dic = new global::Android.Runtime.JavaDictionary(); - foreach (var header in headers) - { - if ("Content-Type".Equals(header.Key, StringComparison.OrdinalIgnoreCase)) - { - contentType = header.Value; - } - - dic.Add(header.Key, header.Value); - } - return dic; - } - return null; - } -#endif -} - -/// -/// Extension methods for the class. -/// -public static class HybridWebViewWebResourceRequestedEventArgsExtensions -{ - /// - /// Sets the response for the web resource request with a status code and reason. - /// - /// The event arguments. - /// The HTTP status code for the response. - /// The reason phrase for the response. - public static void SetResponse(this HybridWebViewWebResourceRequestedEventArgs e, int code, string reason) => - e.SetResponse(code, reason, null, (Stream?)null); - - /// - /// Sets the response for the web resource request with a status code, reason, and content type. - /// - /// The event arguments. - /// The HTTP status code for the response. - /// The reason phrase for the response. - /// The content type of the response. - /// The content of the response as a stream. - public static void SetResponse(this HybridWebViewWebResourceRequestedEventArgs e, int code, string reason, string contentType, Stream? content) => - e.SetResponse(code, reason, new Dictionary { ["Content-Type"] = contentType }, content); - - /// - /// Sets the response for the web resource request with a status code, reason, and content type. - /// - /// The event arguments. - /// The HTTP status code for the response. - /// The reason phrase for the response. - /// The content type of the response. - /// A task that represents the asynchronous operation of getting the response content. - public static void SetResponse(this HybridWebViewWebResourceRequestedEventArgs e, int code, string reason, string contentType, Task contentTask) => - e.SetResponse(code, reason, new Dictionary { ["Content-Type"] = contentType }, contentTask); -} diff --git a/src/Controls/src/Core/HybridWebView/PlatformHybridWebViewWebResourceRequestedEventArgs.cs b/src/Controls/src/Core/HybridWebView/PlatformHybridWebViewWebResourceRequestedEventArgs.cs deleted file mode 100644 index e12e74a97795..000000000000 --- a/src/Controls/src/Core/HybridWebView/PlatformHybridWebViewWebResourceRequestedEventArgs.cs +++ /dev/null @@ -1,209 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace Microsoft.Maui.Controls; - -/// -/// Provides platform-specific information about the event. -/// -public class PlatformHybridWebViewWebResourceRequestedEventArgs -{ -#if WINDOWS - - IReadOnlyDictionary? _headers; - - internal PlatformHybridWebViewWebResourceRequestedEventArgs( - global::Microsoft.Web.WebView2.Core.CoreWebView2 sender, - global::Microsoft.Web.WebView2.Core.CoreWebView2WebResourceRequestedEventArgs eventArgs) - { - Sender = sender; - RequestEventArgs = eventArgs; - } - - internal PlatformHybridWebViewWebResourceRequestedEventArgs(WebResourceRequestedEventArgs args) - : this(args.Sender, args.RequestEventArgs) - { - } - - /// - /// Gets the native view attached to the event. - /// - /// - /// This is only available on Windows. - /// - public global::Microsoft.Web.WebView2.Core.CoreWebView2 Sender { get; } - - /// - /// Gets the native event args attached to the event. - /// - /// - /// This is only available on Windows. - /// - public global::Microsoft.Web.WebView2.Core.CoreWebView2WebResourceRequestedEventArgs RequestEventArgs { get; } - - /// - /// Gets the native request attached to the event. - /// - /// - /// This is only available on Windows. - /// This is equivalent to RequestEventArgs.Request. - /// - public global::Microsoft.Web.WebView2.Core.CoreWebView2WebResourceRequest Request => RequestEventArgs.Request; - - internal string? GetRequestUri() => Request.Uri; - - internal string? GetRequestMethod() => Request.Method; - - internal IReadOnlyDictionary GetRequestHeaders() => - _headers ??= new Dictionary(Request.Headers, StringComparer.OrdinalIgnoreCase); - -#elif IOS || MACCATALYST - - IReadOnlyDictionary? _headers; - - internal PlatformHybridWebViewWebResourceRequestedEventArgs( - global::WebKit.WKWebView sender, - global::WebKit.IWKUrlSchemeTask urlSchemeTask) - { - Sender = sender; - UrlSchemeTask = urlSchemeTask; - } - - internal PlatformHybridWebViewWebResourceRequestedEventArgs(WebResourceRequestedEventArgs args) - : this(args.Sender, args.UrlSchemeTask) - { - } - - /// - /// Gets the native view attached to the event. - /// - /// - /// This is only available on iOS and Mac Catalyst. - /// - public global::WebKit.WKWebView Sender { get; } - - /// - /// Gets the native event args attached to the event. - /// - /// - /// This is only available on iOS and Mac Catalyst. - /// - public global::WebKit.IWKUrlSchemeTask UrlSchemeTask { get; } - - /// - /// Gets the native request attached to the event. - /// - /// - /// This is only available on iOS and Mac Catalyst. - /// This is equivalent to UrlSchemeTask.Request. - /// - public Foundation.NSUrlRequest Request => UrlSchemeTask.Request; - - internal string? GetRequestUri() => Request.Url?.AbsoluteString; - - internal string? GetRequestMethod() => Request.HttpMethod; - - internal IReadOnlyDictionary GetRequestHeaders() => - _headers ??= CreateHeadersDictionary(); - - Dictionary CreateHeadersDictionary() - { - var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); - if (Request.Headers is { } rh) - { - foreach (var key in rh.Keys) - { - if (key is Foundation.NSString keyString) - { - headers[keyString] = rh[keyString].ToString(); - } - } - } - return headers; - } - -#elif ANDROID - - Action _setResponse; - global::Android.Webkit.WebResourceResponse? _response; - IReadOnlyDictionary? _headers; - - internal PlatformHybridWebViewWebResourceRequestedEventArgs( - global::Android.Webkit.WebView sender, - global::Android.Webkit.IWebResourceRequest request, - Action setResponse) - { - Sender = sender; - Request = request; - _setResponse = setResponse; - } - - internal PlatformHybridWebViewWebResourceRequestedEventArgs(WebResourceRequestedEventArgs args) - : this(args.Sender, args.Request, (response) => args.Response = response) - { - } - - /// - /// Gets the native view attached to the event. - /// - /// - /// This is only available on Android. - /// - public global::Android.Webkit.WebView Sender { get; } - - /// - /// Gets the native event args attached to the event. - /// - /// - /// This is only available on Android. - /// - public global::Android.Webkit.IWebResourceRequest Request { get; } - - /// - /// Gets or sets the native response to return to the web view. - /// - /// This property must be set to a valid response if the property is set to true. - /// - /// This is only available on Android. - /// - public global::Android.Webkit.WebResourceResponse? Response - { - get => _response; - set - { - _response = value; - _setResponse(value); - } - } - - internal string? GetRequestUri() => Request.Url?.ToString(); - - internal string? GetRequestMethod() => Request.Method; - - internal IReadOnlyDictionary GetRequestHeaders() => - _headers ??= Request.RequestHeaders is { } rh - ? new Dictionary(rh, StringComparer.OrdinalIgnoreCase) - : new Dictionary(StringComparer.OrdinalIgnoreCase); - -#else - - internal PlatformHybridWebViewWebResourceRequestedEventArgs() - { - } - - internal PlatformHybridWebViewWebResourceRequestedEventArgs(WebResourceRequestedEventArgs args) - { - } - -#pragma warning disable CA1822 // Mark members as static - internal string? GetRequestUri() => null; - - internal string? GetRequestMethod() => null; - - internal IReadOnlyDictionary? GetRequestHeaders() => null; -#pragma warning restore CA1822 // Mark members as static - -#endif -} diff --git a/src/Controls/src/Core/Properties/AssemblyInfo.cs b/src/Controls/src/Core/Properties/AssemblyInfo.cs index ced6d883da0a..2bff1de737af 100644 --- a/src/Controls/src/Core/Properties/AssemblyInfo.cs +++ b/src/Controls/src/Core/Properties/AssemblyInfo.cs @@ -4,6 +4,8 @@ using Microsoft.Maui.Controls.StyleSheets; using Compatibility = Microsoft.Maui.Controls.Compatibility; +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.WebView.Maui")] + [assembly: InternalsVisibleTo("iOSUnitTests")] [assembly: InternalsVisibleTo("Microsoft.Maui.Controls.Compatibility")] [assembly: InternalsVisibleTo("Microsoft.Maui.Controls.Compatibility.Android")] diff --git a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt index 1081545da88d..1b3fc8efc999 100644 --- a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -23,33 +23,33 @@ Microsoft.Maui.Controls.DatePicker.MaximumDate.get -> System.DateTime? Microsoft.Maui.Controls.DatePicker.MinimumDate.get -> System.DateTime? Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task! Microsoft.Maui.Controls.HybridWebView.SetInvokeJavaScriptTarget(T! target) -> void -Microsoft.Maui.Controls.HybridWebView.WebResourceRequested -> System.EventHandler? -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Handled.get -> bool -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Handled.set -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Headers.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.HybridWebViewWebResourceRequestedEventArgs(System.Uri! uri, string! method) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Method.get -> string! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.PlatformArgs.get -> Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs? -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.QueryParameters.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.IO.Stream? content) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.Threading.Tasks.Task! contentTask) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Uri.get -> System.Uri! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions +Microsoft.Maui.Controls.HybridWebView.WebResourceRequested -> System.EventHandler? +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Handled.get -> bool +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Handled.set -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Headers.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.WebViewWebResourceRequestedEventArgs(Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs! platformArgs) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.WebViewWebResourceRequestedEventArgs(System.Uri! uri, string! method) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Method.get -> string! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.PlatformArgs.get -> Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs? +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.QueryParameters.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.IO.Stream? content) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.Threading.Tasks.Task! contentTask) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Uri.get -> System.Uri! Microsoft.Maui.Controls.ICornerElement Microsoft.Maui.Controls.ICornerElement.CornerRadius.get -> Microsoft.Maui.CornerRadius Microsoft.Maui.Controls.ILineHeightElement Microsoft.Maui.Controls.ILineHeightElement.LineHeight.get -> double Microsoft.Maui.Controls.ILineHeightElement.OnLineHeightChanged(double oldValue, double newValue) -> void Microsoft.Maui.Controls.Internals.TextTransformUtilities -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs.Request.get -> Android.Webkit.IWebResourceRequest! -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs.Response.get -> Android.Webkit.WebResourceResponse? -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs.Response.set -> void -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs.Sender.get -> Android.Webkit.WebView! -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason) -> void -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason, string! contentType, System.IO.Stream? content) -> void -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason, string! contentType, System.Threading.Tasks.Task! contentTask) -> void +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs.Request.get -> Android.Webkit.IWebResourceRequest! +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs.Response.get -> Android.Webkit.WebResourceResponse? +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs.Response.set -> void +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs.Sender.get -> Android.Webkit.WebView! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, string! contentType, System.IO.Stream? content) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, string! contentType, System.Threading.Tasks.Task! contentTask) -> void Microsoft.Maui.Controls.ITextAlignmentElement Microsoft.Maui.Controls.ITextAlignmentElement.HorizontalTextAlignment.get -> Microsoft.Maui.TextAlignment Microsoft.Maui.Controls.ITextAlignmentElement.OnHorizontalTextAlignmentPropertyChanged(Microsoft.Maui.TextAlignment oldValue, Microsoft.Maui.TextAlignment newValue) -> void diff --git a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt index f121026d5ff4..04e7c40af39a 100644 --- a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -4,23 +4,23 @@ Microsoft.Maui.Controls.LayoutConstraint.Fixed = Microsoft.Maui.Controls.LayoutC Microsoft.Maui.Controls.LayoutConstraint.HorizontallyFixed = 1 -> Microsoft.Maui.Controls.LayoutConstraint Microsoft.Maui.Controls.LayoutConstraint.None = 0 -> Microsoft.Maui.Controls.LayoutConstraint Microsoft.Maui.Controls.LayoutConstraint.VerticallyFixed = 2 -> Microsoft.Maui.Controls.LayoutConstraint -Microsoft.Maui.Controls.HybridWebView.WebResourceRequested -> System.EventHandler? -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Handled.get -> bool -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Handled.set -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Headers.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.HybridWebViewWebResourceRequestedEventArgs(System.Uri! uri, string! method) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Method.get -> string! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.PlatformArgs.get -> Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs? -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.QueryParameters.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.IO.Stream? content) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.Threading.Tasks.Task! contentTask) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Uri.get -> System.Uri! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs.Request.get -> Foundation.NSUrlRequest! -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs.Sender.get -> WebKit.WKWebView! -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs.UrlSchemeTask.get -> WebKit.IWKUrlSchemeTask! +Microsoft.Maui.Controls.HybridWebView.WebResourceRequested -> System.EventHandler? +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Handled.get -> bool +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Handled.set -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Headers.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.WebViewWebResourceRequestedEventArgs(Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs! platformArgs) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.WebViewWebResourceRequestedEventArgs(System.Uri! uri, string! method) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Method.get -> string! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.PlatformArgs.get -> Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs? +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.QueryParameters.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.IO.Stream? content) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.Threading.Tasks.Task! contentTask) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Uri.get -> System.Uri! +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs.Request.get -> Foundation.NSUrlRequest! +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs.Sender.get -> WebKit.WKWebView! +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs.UrlSchemeTask.get -> WebKit.IWKUrlSchemeTask! Microsoft.Maui.Controls.ShadowTypeConverter Microsoft.Maui.Controls.ShadowTypeConverter.ShadowTypeConverter() -> void *REMOVED*Microsoft.Maui.Controls.DatePicker.Date.get -> System.DateTime @@ -55,9 +55,9 @@ override Microsoft.Maui.Controls.ShadowTypeConverter.ConvertTo(System.ComponentM ~override Microsoft.Maui.Controls.StackLayout.ComputeConstraintForView(Microsoft.Maui.Controls.View view) -> void ~override Microsoft.Maui.Controls.TemplatedPage.ComputeConstraintForView(Microsoft.Maui.Controls.View view) -> void ~override Microsoft.Maui.Controls.TemplatedView.ComputeConstraintForView(Microsoft.Maui.Controls.View view) -> void -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason) -> void -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason, string! contentType, System.IO.Stream? content) -> void -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason, string! contentType, System.Threading.Tasks.Task! contentTask) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, string! contentType, System.IO.Stream? content) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, string! contentType, System.Threading.Tasks.Task! contentTask) -> void ~override Microsoft.Maui.Controls.VerticalStackLayout.ComputeConstraintForView(Microsoft.Maui.Controls.View view) -> void ~static Microsoft.Maui.Controls.Internals.TextTransformUtilities.GetTransformedText(string source, Microsoft.Maui.TextTransform textTransform) -> string ~static Microsoft.Maui.Controls.Internals.TextTransformUtilities.SetPlainText(Microsoft.Maui.Controls.InputView inputView, string platformText) -> void diff --git a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index bf7149d2c42c..1a67e08f5615 100644 --- a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -4,23 +4,23 @@ Microsoft.Maui.Controls.LayoutConstraint.Fixed = Microsoft.Maui.Controls.LayoutC Microsoft.Maui.Controls.LayoutConstraint.HorizontallyFixed = 1 -> Microsoft.Maui.Controls.LayoutConstraint Microsoft.Maui.Controls.LayoutConstraint.None = 0 -> Microsoft.Maui.Controls.LayoutConstraint Microsoft.Maui.Controls.LayoutConstraint.VerticallyFixed = 2 -> Microsoft.Maui.Controls.LayoutConstraint -Microsoft.Maui.Controls.HybridWebView.WebResourceRequested -> System.EventHandler? -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Handled.get -> bool -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Handled.set -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Headers.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.HybridWebViewWebResourceRequestedEventArgs(System.Uri! uri, string! method) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Method.get -> string! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.PlatformArgs.get -> Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs? -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.QueryParameters.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.IO.Stream? content) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.Threading.Tasks.Task! contentTask) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Uri.get -> System.Uri! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs.Request.get -> Foundation.NSUrlRequest! -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs.Sender.get -> WebKit.WKWebView! -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs.UrlSchemeTask.get -> WebKit.IWKUrlSchemeTask! +Microsoft.Maui.Controls.HybridWebView.WebResourceRequested -> System.EventHandler? +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Handled.get -> bool +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Handled.set -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Headers.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.WebViewWebResourceRequestedEventArgs(Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs! platformArgs) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.WebViewWebResourceRequestedEventArgs(System.Uri! uri, string! method) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Method.get -> string! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.PlatformArgs.get -> Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs? +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.QueryParameters.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.IO.Stream? content) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.Threading.Tasks.Task! contentTask) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Uri.get -> System.Uri! +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs.Request.get -> Foundation.NSUrlRequest! +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs.Sender.get -> WebKit.WKWebView! +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs.UrlSchemeTask.get -> WebKit.IWKUrlSchemeTask! Microsoft.Maui.Controls.ShadowTypeConverter Microsoft.Maui.Controls.ShadowTypeConverter.ShadowTypeConverter() -> void *REMOVED*Microsoft.Maui.Controls.DatePicker.Date.get -> System.DateTime @@ -55,9 +55,9 @@ override Microsoft.Maui.Controls.ShadowTypeConverter.ConvertTo(System.ComponentM ~override Microsoft.Maui.Controls.StackLayout.ComputeConstraintForView(Microsoft.Maui.Controls.View view) -> void ~override Microsoft.Maui.Controls.TemplatedPage.ComputeConstraintForView(Microsoft.Maui.Controls.View view) -> void ~override Microsoft.Maui.Controls.TemplatedView.ComputeConstraintForView(Microsoft.Maui.Controls.View view) -> void -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason) -> void -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason, string! contentType, System.IO.Stream? content) -> void -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason, string! contentType, System.Threading.Tasks.Task! contentTask) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, string! contentType, System.IO.Stream? content) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, string! contentType, System.Threading.Tasks.Task! contentTask) -> void ~override Microsoft.Maui.Controls.VerticalStackLayout.ComputeConstraintForView(Microsoft.Maui.Controls.View view) -> void ~static Microsoft.Maui.Controls.Internals.TextTransformUtilities.GetTransformedText(string source, Microsoft.Maui.TextTransform textTransform) -> string ~static Microsoft.Maui.Controls.Internals.TextTransformUtilities.SetPlainText(Microsoft.Maui.Controls.InputView inputView, string platformText) -> void diff --git a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt index bbca6e7b0733..c9374a13eee5 100644 --- a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -23,19 +23,22 @@ Microsoft.Maui.Controls.DatePicker.MaximumDate.get -> System.DateTime? Microsoft.Maui.Controls.DatePicker.MinimumDate.get -> System.DateTime? Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task! Microsoft.Maui.Controls.HybridWebView.SetInvokeJavaScriptTarget(T! target) -> void -Microsoft.Maui.Controls.HybridWebView.WebResourceRequested -> System.EventHandler? -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Handled.get -> bool -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Handled.set -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Headers.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.HybridWebViewWebResourceRequestedEventArgs(System.Uri! uri, string! method) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Method.get -> string! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.PlatformArgs.get -> Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs? -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.QueryParameters.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.IO.Stream? content) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.Threading.Tasks.Task! contentTask) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Uri.get -> System.Uri! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions +Microsoft.Maui.Controls.HybridWebView.WebResourceRequested -> System.EventHandler? +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Handled.get -> bool +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Handled.set -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Headers.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.WebViewWebResourceRequestedEventArgs(Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs! platformArgs) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.WebViewWebResourceRequestedEventArgs(System.Uri! uri, string! method) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Method.get -> string! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.PlatformArgs.get -> Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs? +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.QueryParameters.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.IO.Stream? content) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.Threading.Tasks.Task! contentTask) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, string! contentType, System.IO.Stream? content) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, string! contentType, System.Threading.Tasks.Task! contentTask) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Uri.get -> System.Uri! Microsoft.Maui.Controls.ICornerElement Microsoft.Maui.Controls.ICornerElement.CornerRadius.get -> Microsoft.Maui.CornerRadius Microsoft.Maui.Controls.ILineHeightElement @@ -63,13 +66,10 @@ Microsoft.Maui.Controls.TimeChangedEventArgs.TimeChangedEventArgs(System.TimeSpa ~Microsoft.Maui.Controls.ITextElement.OnTextColorPropertyChanged(Microsoft.Maui.Graphics.Color oldValue, Microsoft.Maui.Graphics.Color newValue) -> void ~Microsoft.Maui.Controls.ITextElement.TextColor.get -> Microsoft.Maui.Graphics.Color ~Microsoft.Maui.Controls.ITextElement.UpdateFormsText(string original, Microsoft.Maui.TextTransform transform) -> string -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs.Request.get -> Microsoft.Web.WebView2.Core.CoreWebView2WebResourceRequest! -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs.RequestEventArgs.get -> Microsoft.Web.WebView2.Core.CoreWebView2WebResourceRequestedEventArgs! -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs.Sender.get -> Microsoft.Web.WebView2.Core.CoreWebView2! -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason) -> void -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason, string! contentType, System.IO.Stream? content) -> void -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason, string! contentType, System.Threading.Tasks.Task! contentTask) -> void +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs.Request.get -> Microsoft.Web.WebView2.Core.CoreWebView2WebResourceRequest! +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs.RequestEventArgs.get -> Microsoft.Web.WebView2.Core.CoreWebView2WebResourceRequestedEventArgs! +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs.Sender.get -> Microsoft.Web.WebView2.Core.CoreWebView2! override Microsoft.Maui.Controls.Handlers.Items.SelectableItemsViewHandler.UpdateItemsLayout() -> void Microsoft.Maui.Controls.ShadowTypeConverter Microsoft.Maui.Controls.ShadowTypeConverter.ShadowTypeConverter() -> void @@ -396,4 +396,4 @@ Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute.Allow.get -> bool Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute.AllowImplicitXmlnsDeclarationAttribute(bool allow = true) -> void ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.Target.get -> string -*REMOVED*~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace) -> void \ No newline at end of file +*REMOVED*~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace) -> void diff --git a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt index c98d17368526..029024c3dd63 100644 --- a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt @@ -23,19 +23,22 @@ Microsoft.Maui.Controls.DatePicker.MaximumDate.get -> System.DateTime? Microsoft.Maui.Controls.DatePicker.MinimumDate.get -> System.DateTime? Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task! Microsoft.Maui.Controls.HybridWebView.SetInvokeJavaScriptTarget(T! target) -> void -Microsoft.Maui.Controls.HybridWebView.WebResourceRequested -> System.EventHandler? -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Handled.get -> bool -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Handled.set -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Headers.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.HybridWebViewWebResourceRequestedEventArgs(System.Uri! uri, string! method) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Method.get -> string! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.PlatformArgs.get -> Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs? -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.QueryParameters.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.IO.Stream? content) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.Threading.Tasks.Task! contentTask) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Uri.get -> System.Uri! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions +Microsoft.Maui.Controls.HybridWebView.WebResourceRequested -> System.EventHandler? +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Handled.get -> bool +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Handled.set -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Headers.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.WebViewWebResourceRequestedEventArgs(Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs! platformArgs) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.WebViewWebResourceRequestedEventArgs(System.Uri! uri, string! method) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Method.get -> string! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.PlatformArgs.get -> Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs? +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.QueryParameters.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.IO.Stream? content) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.Threading.Tasks.Task! contentTask) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, string! contentType, System.IO.Stream? content) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, string! contentType, System.Threading.Tasks.Task! contentTask) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Uri.get -> System.Uri! Microsoft.Maui.Controls.ICornerElement Microsoft.Maui.Controls.ICornerElement.CornerRadius.get -> Microsoft.Maui.CornerRadius Microsoft.Maui.Controls.ILineHeightElement @@ -63,10 +66,7 @@ Microsoft.Maui.Controls.ITextElement.OnCharacterSpacingPropertyChanged(double ol Microsoft.Maui.Controls.ITextElement.OnTextTransformChanged(Microsoft.Maui.TextTransform oldValue, Microsoft.Maui.TextTransform newValue) -> void Microsoft.Maui.Controls.ITextElement.TextTransform.get -> Microsoft.Maui.TextTransform Microsoft.Maui.Controls.ITextElement.TextTransform.set -> void -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason) -> void -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason, string! contentType, System.IO.Stream? content) -> void -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason, string! contentType, System.Threading.Tasks.Task! contentTask) -> void +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs Microsoft.Maui.Controls.ShadowTypeConverter Microsoft.Maui.Controls.ShadowTypeConverter.ShadowTypeConverter() -> void Microsoft.Maui.Controls.StyleableElement.Style.get -> Microsoft.Maui.Controls.Style? diff --git a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt index 6f1e7b974b7d..ed314b04357f 100644 --- a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt @@ -23,19 +23,22 @@ Microsoft.Maui.Controls.DatePicker.MaximumDate.get -> System.DateTime? Microsoft.Maui.Controls.DatePicker.MinimumDate.get -> System.DateTime? Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task! Microsoft.Maui.Controls.HybridWebView.SetInvokeJavaScriptTarget(T! target) -> void -Microsoft.Maui.Controls.HybridWebView.WebResourceRequested -> System.EventHandler? -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Handled.get -> bool -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Handled.set -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Headers.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.HybridWebViewWebResourceRequestedEventArgs(System.Uri! uri, string! method) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Method.get -> string! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.PlatformArgs.get -> Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs? -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.QueryParameters.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.IO.Stream? content) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.Threading.Tasks.Task! contentTask) -> void -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs.Uri.get -> System.Uri! -Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions +Microsoft.Maui.Controls.HybridWebView.WebResourceRequested -> System.EventHandler? +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Handled.get -> bool +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Handled.set -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Headers.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.WebViewWebResourceRequestedEventArgs(Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs! platformArgs) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.WebViewWebResourceRequestedEventArgs(System.Uri! uri, string! method) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Method.get -> string! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.PlatformArgs.get -> Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs? +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.QueryParameters.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.IO.Stream? content) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, System.Collections.Generic.IReadOnlyDictionary? headers, System.Threading.Tasks.Task! contentTask) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, string! contentType, System.IO.Stream? content) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.SetResponse(int code, string! reason, string! contentType, System.Threading.Tasks.Task! contentTask) -> void +Microsoft.Maui.Controls.WebViewWebResourceRequestedEventArgs.Uri.get -> System.Uri! Microsoft.Maui.Controls.ICornerElement Microsoft.Maui.Controls.ICornerElement.CornerRadius.get -> Microsoft.Maui.CornerRadius Microsoft.Maui.Controls.ILineHeightElement @@ -57,7 +60,7 @@ Microsoft.Maui.Controls.LayoutConstraint.Fixed = Microsoft.Maui.Controls.LayoutC Microsoft.Maui.Controls.LayoutConstraint.HorizontallyFixed = 1 -> Microsoft.Maui.Controls.LayoutConstraint Microsoft.Maui.Controls.LayoutConstraint.None = 0 -> Microsoft.Maui.Controls.LayoutConstraint Microsoft.Maui.Controls.LayoutConstraint.VerticallyFixed = 2 -> Microsoft.Maui.Controls.LayoutConstraint -Microsoft.Maui.Controls.PlatformHybridWebViewWebResourceRequestedEventArgs +Microsoft.Maui.Controls.PlatformWebViewWebResourceRequestedEventArgs Microsoft.Maui.Controls.ShadowTypeConverter Microsoft.Maui.Controls.ShadowTypeConverter.ShadowTypeConverter() -> void Microsoft.Maui.Controls.StyleableElement.Style.get -> Microsoft.Maui.Controls.Style? @@ -77,9 +80,6 @@ override Microsoft.Maui.Controls.ShadowTypeConverter.ConvertTo(System.ComponentM ~Microsoft.Maui.Controls.Page.DisplayAlertAsync(string title, string message, string accept, string cancel, Microsoft.Maui.FlowDirection flowDirection) -> System.Threading.Tasks.Task ~Microsoft.Maui.Controls.Page.DisplayAlertAsync(string title, string message, string cancel) -> System.Threading.Tasks.Task ~Microsoft.Maui.Controls.Page.DisplayAlertAsync(string title, string message, string cancel, Microsoft.Maui.FlowDirection flowDirection) -> System.Threading.Tasks.Task -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason) -> void -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason, string! contentType, System.IO.Stream? content) -> void -static Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgsExtensions.SetResponse(this Microsoft.Maui.Controls.HybridWebViewWebResourceRequestedEventArgs! e, int code, string! reason, string! contentType, System.Threading.Tasks.Task! contentTask) -> void static Microsoft.Maui.Controls.ViewExtensions.FadeToAsync(this Microsoft.Maui.Controls.VisualElement! view, double opacity, uint length = 250, Microsoft.Maui.Easing? easing = null) -> System.Threading.Tasks.Task! static Microsoft.Maui.Controls.ViewExtensions.LayoutToAsync(this Microsoft.Maui.Controls.VisualElement! view, Microsoft.Maui.Graphics.Rect bounds, uint length = 250, Microsoft.Maui.Easing? easing = null) -> System.Threading.Tasks.Task! static Microsoft.Maui.Controls.ViewExtensions.RelRotateToAsync(this Microsoft.Maui.Controls.VisualElement! view, double drotation, uint length = 250, Microsoft.Maui.Easing? easing = null) -> System.Threading.Tasks.Task! diff --git a/src/Controls/src/Core/WebRequestInterceptingWebView/PlatformWebViewWebResourceRequestedEventArgs.cs b/src/Controls/src/Core/WebRequestInterceptingWebView/PlatformWebViewWebResourceRequestedEventArgs.cs new file mode 100644 index 000000000000..954b5645693f --- /dev/null +++ b/src/Controls/src/Core/WebRequestInterceptingWebView/PlatformWebViewWebResourceRequestedEventArgs.cs @@ -0,0 +1,367 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Maui.Controls; + +/// +/// Provides platform-specific information about the event. +/// +public class PlatformWebViewWebResourceRequestedEventArgs +{ +#if WINDOWS + + IReadOnlyDictionary? _headers; + + internal PlatformWebViewWebResourceRequestedEventArgs( + global::Microsoft.Web.WebView2.Core.CoreWebView2 sender, + global::Microsoft.Web.WebView2.Core.CoreWebView2WebResourceRequestedEventArgs eventArgs) + { + Sender = sender; + RequestEventArgs = eventArgs; + } + + internal PlatformWebViewWebResourceRequestedEventArgs(WebResourceRequestedEventArgs args) + : this(args.Sender, args.RequestEventArgs) + { + } + + /// + /// Gets the native view attached to the event. + /// + /// + /// This is only available on Windows. + /// + public global::Microsoft.Web.WebView2.Core.CoreWebView2 Sender { get; } + + /// + /// Gets the native event args attached to the event. + /// + /// + /// This is only available on Windows. + /// + public global::Microsoft.Web.WebView2.Core.CoreWebView2WebResourceRequestedEventArgs RequestEventArgs { get; } + + /// + /// Gets the native request attached to the event. + /// + /// + /// This is only available on Windows. + /// This is equivalent to RequestEventArgs.Request. + /// + public global::Microsoft.Web.WebView2.Core.CoreWebView2WebResourceRequest Request => RequestEventArgs.Request; + + internal string? GetRequestUri() => Request.Uri; + + internal string? GetRequestMethod() => Request.Method; + + internal IReadOnlyDictionary GetRequestHeaders() => + _headers ??= new Dictionary(Request.Headers, StringComparer.OrdinalIgnoreCase); + + static string? ToPlatformHeaders(IReadOnlyDictionary? headers) + { + if (headers?.Count > 0) + { + var sb = new StringBuilder(); + foreach (var header in headers) + { + sb.AppendLine($"{header.Key}: {header.Value}"); + } + return sb.ToString(); + } + return null; + } + + internal void SetResponse(int code, string reason, IReadOnlyDictionary? headers, Stream? content) + { + // create the response + RequestEventArgs.Response = Sender.Environment.CreateWebResourceResponse( + content?.AsRandomAccessStream(), + code, + reason, + ToPlatformHeaders(headers)); + } + + internal async Task SetResponseAsync(int code, string reason, IReadOnlyDictionary? headers, Task contentTask) + { + // Windows uses a deferral to let the webview know that we are going to be async + using var deferral = RequestEventArgs.GetDeferral(); + + // get the actual content + var data = await contentTask; + + // create the response + RequestEventArgs.Response = Sender.Environment.CreateWebResourceResponse( + data?.AsRandomAccessStream(), + code, + reason, + ToPlatformHeaders(headers)); + + // let the webview know + deferral.Complete(); + } + +#elif IOS || MACCATALYST + + IReadOnlyDictionary? _headers; + + internal PlatformWebViewWebResourceRequestedEventArgs( + global::WebKit.WKWebView sender, + global::WebKit.IWKUrlSchemeTask urlSchemeTask) + { + Sender = sender; + UrlSchemeTask = urlSchemeTask; + } + + internal PlatformWebViewWebResourceRequestedEventArgs(WebResourceRequestedEventArgs args) + : this(args.Sender, args.UrlSchemeTask) + { + } + + /// + /// Gets the native view attached to the event. + /// + /// + /// This is only available on iOS and Mac Catalyst. + /// + public global::WebKit.WKWebView Sender { get; } + + /// + /// Gets the native event args attached to the event. + /// + /// + /// This is only available on iOS and Mac Catalyst. + /// + public global::WebKit.IWKUrlSchemeTask UrlSchemeTask { get; } + + /// + /// Gets the native request attached to the event. + /// + /// + /// This is only available on iOS and Mac Catalyst. + /// This is equivalent to UrlSchemeTask.Request. + /// + public Foundation.NSUrlRequest Request => UrlSchemeTask.Request; + + internal string? GetRequestUri() => Request.Url?.AbsoluteString; + + internal string? GetRequestMethod() => Request.HttpMethod; + + internal IReadOnlyDictionary GetRequestHeaders() => + _headers ??= CreateHeadersDictionary(); + + Dictionary CreateHeadersDictionary() + { + var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (Request.Headers is { } rh) + { + foreach (var key in rh.Keys) + { + if (key is Foundation.NSString keyString) + { + headers[keyString] = rh[keyString].ToString(); + } + } + } + return headers; + } + + static Foundation.NSMutableDictionary? ToPlatformHeaders(IReadOnlyDictionary? headers) + { + if (headers?.Count > 0) + { + var dic = new Foundation.NSMutableDictionary(); + foreach (var header in headers) + { + dic.Add((Foundation.NSString)header.Key, (Foundation.NSString)header.Value); + } + return dic; + } + return null; + } + + internal void SetResponse(int code, string reason, IReadOnlyDictionary? headers, Stream? content) + { + // create and send the response headers + UrlSchemeTask.DidReceiveResponse(new Foundation.NSHttpUrlResponse( + Request.Url, + code, + "HTTP/1.1", + ToPlatformHeaders(headers))); + + // send the data + if (content is not null && Foundation.NSData.FromStream(content) is { } nsdata) + { + UrlSchemeTask.DidReceiveData(nsdata); + } + + // let the webview know + UrlSchemeTask.DidFinish(); + } + + internal async Task SetResponseAsync(int code, string reason, IReadOnlyDictionary? headers, Task contentTask) + { + // iOS and MacCatalyst will just wait until DidFinish is called + + // create and send the response headers + UrlSchemeTask.DidReceiveResponse(new Foundation.NSHttpUrlResponse( + Request.Url, + code, + "HTTP/1.1", + ToPlatformHeaders(headers))); + + // get the actual content + var data = await contentTask; + + // send the data + if (data is not null && Foundation.NSData.FromStream(data) is { } nsdata) + { + UrlSchemeTask.DidReceiveData(nsdata); + } + + // let the webview know + UrlSchemeTask.DidFinish(); + } + +#elif ANDROID + + Action _setResponse; + global::Android.Webkit.WebResourceResponse? _response; + IReadOnlyDictionary? _headers; + + internal PlatformWebViewWebResourceRequestedEventArgs( + global::Android.Webkit.WebView sender, + global::Android.Webkit.IWebResourceRequest request, + Action setResponse) + { + Sender = sender; + Request = request; + _setResponse = setResponse; + } + + internal PlatformWebViewWebResourceRequestedEventArgs(WebResourceRequestedEventArgs args) + : this(args.Sender, args.Request, (response) => args.Response = response) + { + } + + /// + /// Gets the native view attached to the event. + /// + /// + /// This is only available on Android. + /// + public global::Android.Webkit.WebView Sender { get; } + + /// + /// Gets the native event args attached to the event. + /// + /// + /// This is only available on Android. + /// + public global::Android.Webkit.IWebResourceRequest Request { get; } + + /// + /// Gets or sets the native response to return to the web view. + /// + /// This property must be set to a valid response if the property is set to true. + /// + /// This is only available on Android. + /// + public global::Android.Webkit.WebResourceResponse? Response + { + get => _response; + set + { + _response = value; + _setResponse(value); + } + } + + internal string? GetRequestUri() => Request.Url?.ToString(); + + internal string? GetRequestMethod() => Request.Method; + + internal IReadOnlyDictionary GetRequestHeaders() => + _headers ??= Request.RequestHeaders is { } rh + ? new Dictionary(rh, StringComparer.OrdinalIgnoreCase) + : new Dictionary(StringComparer.OrdinalIgnoreCase); + + static global::Android.Runtime.JavaDictionary? ToPlatformHeaders(IReadOnlyDictionary? headers, out string contentType) + { + contentType = "application/octet-stream"; + if (headers?.Count > 0) + { + var dic = new global::Android.Runtime.JavaDictionary(); + foreach (var header in headers) + { + if ("Content-Type".Equals(header.Key, StringComparison.OrdinalIgnoreCase)) + { + contentType = header.Value; + } + + dic.Add(header.Key, header.Value); + } + return dic; + } + return null; + } + + internal void SetResponse(int code, string reason, IReadOnlyDictionary? headers, Stream? content) + { + // Android requires that we return immediately, even if the data is coming later + + // create and send the response headers + var platformHeaders = ToPlatformHeaders(headers, out var contentType); + Response = new global::Android.Webkit.WebResourceResponse( + contentType, + "UTF-8", + code, + reason, + platformHeaders, + content); + } + + internal Task SetResponseAsync(int code, string reason, IReadOnlyDictionary? headers, Task contentTask) + { + // Android requires that we return immediately, even if the data is coming later + + // get the actual content + var stream = new AsyncStream(contentTask, null); + + // create and send the response headers + var platformHeaders = ToPlatformHeaders(headers, out var contentType); + Response = new global::Android.Webkit.WebResourceResponse( + contentType, + "UTF-8", + code, + reason, + platformHeaders, + stream); + + return Task.CompletedTask; + } + +#else + + internal PlatformWebViewWebResourceRequestedEventArgs(WebResourceRequestedEventArgs args) + { + } + +#pragma warning disable CA1822 // Mark members as static + internal string? GetRequestUri() => null; + + internal string? GetRequestMethod() => null; + + internal IReadOnlyDictionary? GetRequestHeaders() => null; + + internal void SetResponse(int code, string reason, IReadOnlyDictionary? headers, Stream? content) { } + + internal Task SetResponseAsync(int code, string reason, IReadOnlyDictionary? headers, Task contentTask) => Task.CompletedTask; +#pragma warning restore CA1822 // Mark members as static + +#endif +} diff --git a/src/Controls/src/Core/WebRequestInterceptingWebView/WebViewWebResourceRequestedEventArgs.cs b/src/Controls/src/Core/WebRequestInterceptingWebView/WebViewWebResourceRequestedEventArgs.cs new file mode 100644 index 000000000000..c7101a931486 --- /dev/null +++ b/src/Controls/src/Core/WebRequestInterceptingWebView/WebViewWebResourceRequestedEventArgs.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Maui.Controls; + +/// +/// Event arguments for the event. +/// +public class WebViewWebResourceRequestedEventArgs +{ + IReadOnlyDictionary? _headers; + IReadOnlyDictionary? _queryParams; + + public WebViewWebResourceRequestedEventArgs(PlatformWebViewWebResourceRequestedEventArgs platformArgs) + { + PlatformArgs = platformArgs; + Uri = platformArgs.GetRequestUri() is string uri ? new Uri(uri) : throw new InvalidOperationException("Platform web request did not have a request URI."); + Method = platformArgs.GetRequestMethod() ?? throw new InvalidOperationException("Platform web request did not have a request METHOD."); + } + + /// + /// Initializes a new instance of the class + /// with the specified URI and method. + /// + public WebViewWebResourceRequestedEventArgs(Uri uri, string method) + { + Uri = uri; + Method = method; + } + + /// + /// Gets the platform-specific event arguments. + /// + public PlatformWebViewWebResourceRequestedEventArgs? PlatformArgs { get; } + + /// + /// Gets the URI of the requested resource. + /// + public Uri Uri { get; } + + /// + /// Gets the HTTP method used for the request (e.g., GET, POST). + /// + public string Method { get; } + + /// + /// Gets the headers associated with the request. + /// + public IReadOnlyDictionary Headers => + _headers ??= PlatformArgs?.GetRequestHeaders() ?? new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Gets the query parameters from the URI. + /// + public IReadOnlyDictionary QueryParameters => + _queryParams ??= WebUtils.ParseQueryString(Uri, false) ?? new Dictionary(StringComparer.Ordinal); + + /// + /// Gets or sets a value indicating whether the request has been handled. + /// + /// If set to true, the web view will not process the request further and a response + /// must be provided using the + /// + /// or method. + /// If set to false, the web view will continue processing the request as normal. + /// + public bool Handled { get; set; } + + /// + /// Sets the response for the web resource request. + /// + /// This method must be called if the property is set to true. + /// + /// The HTTP status code for the response. + /// The reason phrase for the response. + /// The headers to include in the response. + /// The content of the response as a stream. + public void SetResponse(int code, string reason, IReadOnlyDictionary? headers, Stream? content) => + PlatformArgs?.SetResponse(code, reason, headers, content); + + /// + /// Sets the asynchronous response for the web resource request. + /// + /// This method must be called if the property is set to true. + /// + /// The HTTP status code for the response. + /// The reason phrase for the response. + /// The headers to include in the response. + /// A task that represents the asynchronous operation of getting the response content. + /// + /// This method is not asynchronous and will return immediately. The actual response will be sent when the content task completes. + /// + public void SetResponse(int code, string reason, IReadOnlyDictionary? headers, Task contentTask) => + PlatformArgs?.SetResponseAsync(code, reason, headers, contentTask).FireAndForget(); + + /// + /// Sets the response for the web resource request with a status code and reason. + /// + /// The HTTP status code for the response. + /// The reason phrase for the response. + public void SetResponse(int code, string reason) => + PlatformArgs?.SetResponse(code, reason, null, null); + + /// + /// Sets the response for the web resource request with a status code, reason, and content type. + /// + /// The HTTP status code for the response. + /// The reason phrase for the response. + /// The content type of the response. + /// The content of the response as a stream. + public void SetResponse(int code, string reason, string contentType, Stream? content) => + PlatformArgs?.SetResponse(code, reason, new Dictionary { ["Content-Type"] = contentType }, content); + + /// + /// Sets the response for the web resource request with a status code, reason, and content type. + /// + /// The HTTP status code for the response. + /// The reason phrase for the response. + /// The content type of the response. + /// A task that represents the asynchronous operation of getting the response content. + public void SetResponse(int code, string reason, string contentType, Task contentTask) => + PlatformArgs?.SetResponseAsync(code, reason, new Dictionary { ["Content-Type"] = contentType }, contentTask).FireAndForget(); +} diff --git a/src/Controls/tests/DeviceTests/Elements/HybridWebView/HybridWebViewTests.cs b/src/Controls/tests/DeviceTests/Elements/HybridWebView/HybridWebViewTests.cs index 90d9616750bb..c1266651b653 100644 --- a/src/Controls/tests/DeviceTests/Elements/HybridWebView/HybridWebViewTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/HybridWebView/HybridWebViewTests.cs @@ -710,10 +710,14 @@ public Task RequestsCanBeInterceptedAndHeadersAddedForDifferentHosts(string uriB public Task RequestsCanBeInterceptedAndCancelledForDifferentHosts(string uriBase, string function) => RunTest(async (hybridWebView) => { + var intercepted = false; + hybridWebView.WebResourceRequested += (sender, e) => { if (new Uri(uriBase).IsBaseOf(e.Uri)) { + intercepted = true; + // 1. Create the response e.SetResponse(403, "Forbidden"); @@ -726,6 +730,8 @@ await Assert.ThrowsAsync(() => hybridWebView.InvokeJavaScriptAsync( function, HybridWebViewTestContext.Default.ResponseObject)); + + Assert.True(intercepted, "Request was not intercepted"); }); diff --git a/src/Core/src/Core/IHybridWebView.cs b/src/Core/src/Core/IHybridWebView.cs index 6713f7b2b01c..7af0535c3147 100644 --- a/src/Core/src/Core/IHybridWebView.cs +++ b/src/Core/src/Core/IHybridWebView.cs @@ -5,7 +5,7 @@ namespace Microsoft.Maui { - public interface IHybridWebView : IView + public interface IHybridWebView : IView, IWebRequestInterceptingWebView { /// /// Specifies the file within the that should be served as the default file. The @@ -65,16 +65,5 @@ public interface IHybridWebView : IView JsonTypeInfo returnTypeJsonTypeInfo, object?[]? paramValues = null, JsonTypeInfo?[]? paramJsonTypeInfos = null); - - /// - /// Invoked when a web resource is requested. This event can be used to intercept requests and provide custom responses. - /// - /// The event arguments containing the request details. - /// true if the request was handled; otherwise, false. -#if NETSTANDARD - bool WebResourceRequested(WebResourceRequestedEventArgs args); -#else - bool WebResourceRequested(WebResourceRequestedEventArgs args) => false; -#endif } } diff --git a/src/Core/src/Core/IWebRequestInterceptingWebView.cs b/src/Core/src/Core/IWebRequestInterceptingWebView.cs new file mode 100644 index 000000000000..c9097df8cea4 --- /dev/null +++ b/src/Core/src/Core/IWebRequestInterceptingWebView.cs @@ -0,0 +1,15 @@ +namespace Microsoft.Maui; + +public interface IWebRequestInterceptingWebView : IView +{ + /// + /// Invoked when a web resource is requested. This event can be used to intercept requests and provide custom responses. + /// + /// The event arguments containing the request details. + /// true if the request was handled; otherwise, false. +#if NETSTANDARD + bool WebResourceRequested(WebResourceRequestedEventArgs args); +#else + bool WebResourceRequested(WebResourceRequestedEventArgs args) => false; +#endif +} diff --git a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs index 44c963724e2c..4a911be52902 100644 --- a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs +++ b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs @@ -109,20 +109,9 @@ private async void OnWebResourceRequested(CoreWebView2 sender, CoreWebView2WebRe logger?.LogDebug("Intercepting request for {Url}.", url); // 1. First check if the app wants to modify or override the request. + if (WebRequestInterceptingWebView.TryInterceptResponseStream(this, sender, eventArgs, url, logger)) { - // 1.a. First, create the event args - var platformArgs = new WebResourceRequestedEventArgs(sender, eventArgs); - - // 1.b. Trigger the event for the app - var handled = VirtualView.WebResourceRequested(platformArgs); - - // 1.c. If the app reported that it completed the request, then we do nothing more - if (handled) - { - logger?.LogDebug("Request for {Url} was handled by the user.", url); - - return; - } + return; } // 2. If this is an app request, then assume the request is for a local resource. @@ -156,6 +145,7 @@ private async void OnWebResourceRequested(CoreWebView2 sender, CoreWebView2WebRe // 2.e. Notify WebView2 that the deferred (async) operation is complete and we set a response. deferral.Complete(); + return; } // 3. If the request is not handled by the app nor is it a local source, then we let the WebView2 diff --git a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cs b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cs index 908472a0c562..6c7297e97d88 100644 --- a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cs +++ b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cs @@ -39,6 +39,7 @@ protected override WKWebView CreatePlatformView() config.DefaultWebpagePreferences!.AllowsContentJavaScript = true; config.UserContentController.AddScriptMessageHandler(new WebViewScriptMessageHandler(this), ScriptMessageHandlerName); + // iOS WKWebView doesn't allow handling 'http'/'https' schemes, so we use the fake 'app' scheme config.SetUrlSchemeHandler(new SchemeHandler(this), urlScheme: "app"); @@ -161,20 +162,9 @@ public async void StartUrlSchemeTask(WKWebView webView, IWKUrlSchemeTask urlSche logger?.LogDebug("Intercepting request for {Url}.", url); // 1. First check if the app wants to modify or override the request. + if (WebRequestInterceptingWebView.TryInterceptResponseStream(Handler, webView, urlSchemeTask, url, logger)) { - // 1.a. First, create the event args - var platformArgs = new WebResourceRequestedEventArgs(webView, urlSchemeTask); - - // 1.b. Trigger the event for the app - var handled = Handler.VirtualView.WebResourceRequested(platformArgs); - - // 1.c. If the app reported that it completed the request, then we do nothing more - if (handled) - { - logger?.LogDebug("Request for {Url} was handled by the user.", url); - - return; - } + return; } // 2. If this is an app request, then assume the request is for a local resource. diff --git a/src/Core/src/Platform/Android/MauiHybridWebViewClient.cs b/src/Core/src/Platform/Android/MauiHybridWebViewClient.cs index 46679e015bc5..86e57c35940f 100644 --- a/src/Core/src/Platform/Android/MauiHybridWebViewClient.cs +++ b/src/Core/src/Platform/Android/MauiHybridWebViewClient.cs @@ -40,14 +40,14 @@ public MauiHybridWebViewClient(HybridWebViewHandler handler) if (view is not null && request is not null && !string.IsNullOrEmpty(url)) { // 1. Check if the app wants to modify or override the request - var response = TryInterceptResponseStream(view, request, url, logger); + var response = WebRequestInterceptingWebView.TryInterceptResponseStream(Handler, view, request, url, logger); if (response is not null) { return response; } // 2. Check if the request is for a local resource - response = GetResponseStream(view, request, url, logger); + response = GetResponse(url, logger); if (response is not null) { return response; @@ -60,31 +60,7 @@ public MauiHybridWebViewClient(HybridWebViewHandler handler) return base.ShouldInterceptRequest(view, request); } - private WebResourceResponse? TryInterceptResponseStream(AWebView view, IWebResourceRequest request, string url, ILogger? logger) - { - if (Handler is null || Handler is IViewHandler ivh && ivh.VirtualView is null) - { - return null; - } - - // 1. First, create the event args - var platformArgs = new WebResourceRequestedEventArgs(view, request); - - // 2. Trigger the event for the app - var handled = Handler.VirtualView.WebResourceRequested(platformArgs); - - // 3. If the app reported that it completed the request, then we do nothing more - if (handled) - { - logger?.LogDebug("Request for {Url} was handled by the user.", url); - - return platformArgs.Response; - } - - return null; - } - - private WebResourceResponse? GetResponseStream(AWebView view, IWebResourceRequest request, string fullUrl, ILogger? logger) + private WebResourceResponse? GetResponse(string fullUrl, ILogger? logger) { if (Handler is null || Handler is IViewHandler ivh && ivh.VirtualView is null) { diff --git a/src/Core/src/Platform/Android/WebRequestInterceptingWebView.cs b/src/Core/src/Platform/Android/WebRequestInterceptingWebView.cs new file mode 100644 index 000000000000..c685e76f68eb --- /dev/null +++ b/src/Core/src/Platform/Android/WebRequestInterceptingWebView.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Android.Webkit; +using Microsoft.Extensions.Logging; +using AWebView = Android.Webkit.WebView; + +namespace Microsoft.Maui.Platform; + +internal static class WebRequestInterceptingWebView +{ + internal static WebResourceResponse? TryInterceptResponseStream(IViewHandler? handler, AWebView view, IWebResourceRequest request, string url, ILogger? logger) + { + if (handler is null || handler.VirtualView is not IWebRequestInterceptingWebView interceptingWebView) + { + return null; + } + + // 1. First, create the event args + var platformArgs = new WebResourceRequestedEventArgs(view, request); + + // 2. Trigger the event for the app + var handled = interceptingWebView.WebResourceRequested(platformArgs); + + // 3. If the app reported that it completed the request, then we do nothing more + if (handled) + { + logger?.LogDebug("Request for {Url} was handled by the user.", url); + + return platformArgs.Response; + } + + return null; + } +} diff --git a/src/Core/src/Platform/Windows/WebRequestInterceptingWebView.cs b/src/Core/src/Platform/Windows/WebRequestInterceptingWebView.cs new file mode 100644 index 000000000000..0071ddae66bb --- /dev/null +++ b/src/Core/src/Platform/Windows/WebRequestInterceptingWebView.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Web.WebView2.Core; + +namespace Microsoft.Maui.Platform; + +internal static class WebRequestInterceptingWebView +{ + internal static bool TryInterceptResponseStream(IViewHandler? handler, CoreWebView2 sender, CoreWebView2WebResourceRequestedEventArgs eventArgs, string url, ILogger? logger) + { + if (handler is null || handler.VirtualView is not IWebRequestInterceptingWebView interceptingWebView) + { + return false; + } + + // 1. First, create the event args + var platformArgs = new WebResourceRequestedEventArgs(sender, eventArgs); + + // 2. Trigger the event for the app + var handled = interceptingWebView.WebResourceRequested(platformArgs); + + // 3. If the app reported that it completed the request, then we do nothing more + if (handled) + { + logger?.LogDebug("Request for {Url} was handled by the user.", url); + + return true; + } + + return false; + } +} diff --git a/src/Core/src/Platform/iOS/WebRequestInterceptingWebView.cs b/src/Core/src/Platform/iOS/WebRequestInterceptingWebView.cs new file mode 100644 index 000000000000..f3a4acdbb2c8 --- /dev/null +++ b/src/Core/src/Platform/iOS/WebRequestInterceptingWebView.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Logging; +using WebKit; + +namespace Microsoft.Maui.Platform; + +internal static class WebRequestInterceptingWebView +{ + internal static bool TryInterceptResponseStream(IViewHandler? handler, WKWebView webView, IWKUrlSchemeTask urlSchemeTask, string url, ILogger? logger) + { + if (handler is null || handler.VirtualView is not IWebRequestInterceptingWebView interceptingWebView) + { + return false; + } + + // 1. First, create the event args + var platformArgs = new WebResourceRequestedEventArgs(webView, urlSchemeTask); + + // 2. Trigger the event for the app + var handled = interceptingWebView.WebResourceRequested(platformArgs); + + // 3. If the app reported that it completed the request, then we do nothing more + if (handled) + { + logger?.LogDebug("Request for {Url} was handled by the user.", url); + + return true; + } + + return false; + } +} diff --git a/src/Core/src/Primitives/WebResourceRequestedEventArgs.cs b/src/Core/src/Primitives/WebResourceRequestedEventArgs.cs index c6b55e9868e8..e9498efe3299 100644 --- a/src/Core/src/Primitives/WebResourceRequestedEventArgs.cs +++ b/src/Core/src/Primitives/WebResourceRequestedEventArgs.cs @@ -1,7 +1,7 @@ namespace Microsoft.Maui; /// -/// Provides platform-specific information for the event. +/// Provides platform-specific information for the event. /// public class WebResourceRequestedEventArgs { diff --git a/src/Core/src/Properties/AssemblyInfo.cs b/src/Core/src/Properties/AssemblyInfo.cs index eb1db1dee35d..53fff1cfc6db 100644 --- a/src/Core/src/Properties/AssemblyInfo.cs +++ b/src/Core/src/Properties/AssemblyInfo.cs @@ -1,5 +1,7 @@ using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.WebView.Maui")] + [assembly: InternalsVisibleTo("iOSUnitTests")] [assembly: InternalsVisibleTo("Microsoft.Maui.Controls")] [assembly: InternalsVisibleTo("Microsoft.Maui.Controls.Compatibility")] diff --git a/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt index 99a78cc53360..c3249d4d45d4 100644 --- a/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -57,12 +57,13 @@ Microsoft.Maui.IHybridWebView.InvokeJavaScriptType.set -> void Microsoft.Maui.IHybridWebView.RawMessageReceived(string! rawMessage) -> void Microsoft.Maui.IHybridWebView.SendRawMessage(string! rawMessage) -> void Microsoft.Maui.IHybridWebView.SetInvokeJavaScriptTarget(T! target) -> void -Microsoft.Maui.IHybridWebView.WebResourceRequested(Microsoft.Maui.WebResourceRequestedEventArgs! args) -> bool Microsoft.Maui.ITitleBar Microsoft.Maui.ITitleBar.PassthroughElements.get -> System.Collections.Generic.IList! Microsoft.Maui.ITitleBar.Subtitle.get -> string? Microsoft.Maui.ITitleBar.Title.get -> string? Microsoft.Maui.IWebView.ProcessTerminated(Microsoft.Maui.WebProcessTerminatedEventArgs! args) -> void +Microsoft.Maui.IWebRequestInterceptingWebView +Microsoft.Maui.IWebRequestInterceptingWebView.WebResourceRequested(Microsoft.Maui.WebResourceRequestedEventArgs! args) -> bool *REMOVED*Microsoft.Maui.IWindow.Content.get -> Microsoft.Maui.IView! Microsoft.Maui.IWindow.Content.get -> Microsoft.Maui.IView? Microsoft.Maui.Platform.MauiAppCompatEditText diff --git a/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt index f648c87286d6..e3ecdf19356b 100644 --- a/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -56,12 +56,13 @@ Microsoft.Maui.IHybridWebView.InvokeJavaScriptType.set -> void Microsoft.Maui.IHybridWebView.RawMessageReceived(string! rawMessage) -> void Microsoft.Maui.IHybridWebView.SendRawMessage(string! rawMessage) -> void Microsoft.Maui.IHybridWebView.SetInvokeJavaScriptTarget(T! target) -> void -Microsoft.Maui.IHybridWebView.WebResourceRequested(Microsoft.Maui.WebResourceRequestedEventArgs! args) -> bool Microsoft.Maui.ITitleBar Microsoft.Maui.ITitleBar.PassthroughElements.get -> System.Collections.Generic.IList! Microsoft.Maui.ITitleBar.Subtitle.get -> string? Microsoft.Maui.ITitleBar.Title.get -> string? Microsoft.Maui.IWebView.ProcessTerminated(Microsoft.Maui.WebProcessTerminatedEventArgs! args) -> void +Microsoft.Maui.IWebRequestInterceptingWebView +Microsoft.Maui.IWebRequestInterceptingWebView.WebResourceRequested(Microsoft.Maui.WebResourceRequestedEventArgs! args) -> bool *REMOVED*Microsoft.Maui.IWindow.Content.get -> Microsoft.Maui.IView! Microsoft.Maui.IWindow.Content.get -> Microsoft.Maui.IView? Microsoft.Maui.Platform.IAutoSizableCALayer diff --git a/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index 87db7dec54e5..016cf73134e9 100644 --- a/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -56,13 +56,14 @@ Microsoft.Maui.IHybridWebView.InvokeJavaScriptType.set -> void Microsoft.Maui.IHybridWebView.RawMessageReceived(string! rawMessage) -> void Microsoft.Maui.IHybridWebView.SendRawMessage(string! rawMessage) -> void Microsoft.Maui.IHybridWebView.SetInvokeJavaScriptTarget(T! target) -> void -Microsoft.Maui.IHybridWebView.WebResourceRequested(Microsoft.Maui.WebResourceRequestedEventArgs! args) -> bool Microsoft.Maui.ITitleBar Microsoft.Maui.ITitleBar.PassthroughElements.get -> System.Collections.Generic.IList! Microsoft.Maui.ITitleBar.Subtitle.get -> string? Microsoft.Maui.ITitleBar.Title.get -> string? Microsoft.Maui.IWindow.TitleBar.get -> Microsoft.Maui.ITitleBar? Microsoft.Maui.IWebView.ProcessTerminated(Microsoft.Maui.WebProcessTerminatedEventArgs! args) -> void +Microsoft.Maui.IWebRequestInterceptingWebView +Microsoft.Maui.IWebRequestInterceptingWebView.WebResourceRequested(Microsoft.Maui.WebResourceRequestedEventArgs! args) -> bool *REMOVED*Microsoft.Maui.IWindow.Content.get -> Microsoft.Maui.IView! Microsoft.Maui.IWindow.Content.get -> Microsoft.Maui.IView? Microsoft.Maui.Platform.IAutoSizableCALayer diff --git a/src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt index 61eee164a6e3..c83a772e4f43 100644 --- a/src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -56,7 +56,6 @@ Microsoft.Maui.IHybridWebView.InvokeJavaScriptType.set -> void Microsoft.Maui.IHybridWebView.RawMessageReceived(string! rawMessage) -> void Microsoft.Maui.IHybridWebView.SendRawMessage(string! rawMessage) -> void Microsoft.Maui.IHybridWebView.SetInvokeJavaScriptTarget(T! target) -> void -Microsoft.Maui.IHybridWebView.WebResourceRequested(Microsoft.Maui.WebResourceRequestedEventArgs! args) -> bool Microsoft.Maui.ITitleBar Microsoft.Maui.ITitleBar.PassthroughElements.get -> System.Collections.Generic.IList! Microsoft.Maui.ITitleBar.Subtitle.get -> string? @@ -74,6 +73,8 @@ Microsoft.Maui.TextAlignment.Justify = 3 -> Microsoft.Maui.TextAlignment Microsoft.Maui.WebProcessTerminatedEventArgs Microsoft.Maui.WebProcessTerminatedEventArgs.CoreWebView2ProcessFailedEventArgs.get -> Microsoft.Web.WebView2.Core.CoreWebView2ProcessFailedEventArgs! Microsoft.Maui.WebProcessTerminatedEventArgs.Sender.get -> Microsoft.Web.WebView2.Core.CoreWebView2! +Microsoft.Maui.IWebRequestInterceptingWebView +Microsoft.Maui.IWebRequestInterceptingWebView.WebResourceRequested(Microsoft.Maui.WebResourceRequestedEventArgs! args) -> bool Microsoft.Maui.WebResourceRequestedEventArgs Microsoft.Maui.WebResourceRequestedEventArgs.RequestEventArgs.get -> Microsoft.Web.WebView2.Core.CoreWebView2WebResourceRequestedEventArgs! Microsoft.Maui.WebResourceRequestedEventArgs.Sender.get -> Microsoft.Web.WebView2.Core.CoreWebView2! diff --git a/src/Core/src/PublicAPI/net/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net/PublicAPI.Unshipped.txt index 58500097e73a..68d1ef06ac05 100644 --- a/src/Core/src/PublicAPI/net/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net/PublicAPI.Unshipped.txt @@ -56,12 +56,13 @@ Microsoft.Maui.IHybridWebView.InvokeJavaScriptType.set -> void Microsoft.Maui.IHybridWebView.RawMessageReceived(string! rawMessage) -> void Microsoft.Maui.IHybridWebView.SendRawMessage(string! rawMessage) -> void Microsoft.Maui.IHybridWebView.SetInvokeJavaScriptTarget(T! target) -> void -Microsoft.Maui.IHybridWebView.WebResourceRequested(Microsoft.Maui.WebResourceRequestedEventArgs! args) -> bool Microsoft.Maui.ITitleBar Microsoft.Maui.ITitleBar.PassthroughElements.get -> System.Collections.Generic.IList! Microsoft.Maui.ITitleBar.Subtitle.get -> string? Microsoft.Maui.ITitleBar.Title.get -> string? Microsoft.Maui.IWebView.ProcessTerminated(Microsoft.Maui.WebProcessTerminatedEventArgs! args) -> void +Microsoft.Maui.IWebRequestInterceptingWebView +Microsoft.Maui.IWebRequestInterceptingWebView.WebResourceRequested(Microsoft.Maui.WebResourceRequestedEventArgs! args) -> bool *REMOVED*Microsoft.Maui.IWindow.Content.get -> Microsoft.Maui.IView! Microsoft.Maui.IWindow.Content.get -> Microsoft.Maui.IView? Microsoft.Maui.TextAlignment.Justify = 3 -> Microsoft.Maui.TextAlignment diff --git a/src/Core/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt index 58500097e73a..eda3c7eab4bc 100644 --- a/src/Core/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt @@ -56,7 +56,8 @@ Microsoft.Maui.IHybridWebView.InvokeJavaScriptType.set -> void Microsoft.Maui.IHybridWebView.RawMessageReceived(string! rawMessage) -> void Microsoft.Maui.IHybridWebView.SendRawMessage(string! rawMessage) -> void Microsoft.Maui.IHybridWebView.SetInvokeJavaScriptTarget(T! target) -> void -Microsoft.Maui.IHybridWebView.WebResourceRequested(Microsoft.Maui.WebResourceRequestedEventArgs! args) -> bool +Microsoft.Maui.IWebRequestInterceptingWebView +Microsoft.Maui.IWebRequestInterceptingWebView.WebResourceRequested(Microsoft.Maui.WebResourceRequestedEventArgs! args) -> bool Microsoft.Maui.ITitleBar Microsoft.Maui.ITitleBar.PassthroughElements.get -> System.Collections.Generic.IList! Microsoft.Maui.ITitleBar.Subtitle.get -> string? diff --git a/src/Core/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 58500097e73a..eda3c7eab4bc 100644 --- a/src/Core/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -56,7 +56,8 @@ Microsoft.Maui.IHybridWebView.InvokeJavaScriptType.set -> void Microsoft.Maui.IHybridWebView.RawMessageReceived(string! rawMessage) -> void Microsoft.Maui.IHybridWebView.SendRawMessage(string! rawMessage) -> void Microsoft.Maui.IHybridWebView.SetInvokeJavaScriptTarget(T! target) -> void -Microsoft.Maui.IHybridWebView.WebResourceRequested(Microsoft.Maui.WebResourceRequestedEventArgs! args) -> bool +Microsoft.Maui.IWebRequestInterceptingWebView +Microsoft.Maui.IWebRequestInterceptingWebView.WebResourceRequested(Microsoft.Maui.WebResourceRequestedEventArgs! args) -> bool Microsoft.Maui.ITitleBar Microsoft.Maui.ITitleBar.PassthroughElements.get -> System.Collections.Generic.IList! Microsoft.Maui.ITitleBar.Subtitle.get -> string?