Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BlazorWebView: Allow for WPF and WinForms WebView2 Custom Startup Settings and Fixed Distribution Mode #5086

Closed
1 change: 1 addition & 0 deletions Microsoft.Maui.sln
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SharedSource", "SharedSourc
src\BlazorWebView\src\SharedSource\ExternalLinkNavigationPolicy.cs = src\BlazorWebView\src\SharedSource\ExternalLinkNavigationPolicy.cs
src\BlazorWebView\src\SharedSource\QueryStringHelper.cs = src\BlazorWebView\src\SharedSource\QueryStringHelper.cs
src\BlazorWebView\src\SharedSource\WebView2WebViewManager.cs = src\BlazorWebView\src\SharedSource\WebView2WebViewManager.cs
src\BlazorWebView\src\SharedSource\WebViewInitEventArgs.cs = src\BlazorWebView\src\SharedSource\WebViewInitEventArgs.cs
EndProjectSection
EndProject
Global
Expand Down
8 changes: 8 additions & 0 deletions src/BlazorWebView/samples/BlazorWinFormsApp/Form1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Windows.Forms;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebView.WebView2;
using Microsoft.AspNetCore.Components.WebView.WindowsForms;
using Microsoft.Extensions.DependencyInjection;
using WebViewAppShared;
Expand Down Expand Up @@ -37,6 +38,7 @@ public Form1()
blazorWebView1.Services = services1.BuildServiceProvider();
blazorWebView1.RootComponents.Add<Main>("#app");
blazorWebView1.RootComponents.RegisterForJavaScript<MyDynamicComponent>("my-dynamic-root-component");
blazorWebView1.WebViewInitialize = blazorWebView_WebViewInitialize;

customFilesBlazorWebView.HostPage = @"wwwroot\customindex.html";
customFilesBlazorWebView.Services = services2.BuildServiceProvider();
Expand All @@ -51,6 +53,12 @@ private void button1_Click(object sender, EventArgs e)
caption: "Counter");
}

private void blazorWebView_WebViewInitialize(object sender, WebViewInitializeEventArgs args)
{
// Pressing F12 will result in infinite load animation / not found error.
args.CoreWebView2EnvironmentOptions.AdditionalBrowserArguments = "--custom-devtools-frontend=http://invalid-url";
}

private void _webViewActionButton_Click(object sender, EventArgs e)
{
blazorWebView1.WebView.CoreWebView2.ExecuteScriptAsync("alert('hello from native UI')");
Expand Down
2 changes: 1 addition & 1 deletion src/BlazorWebView/samples/BlazorWpfApp/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

<TabControl>
<TabItem Header="BlazorWebView">
<blazor:BlazorWebView HostPage="wwwroot\index.html" Services="{StaticResource services1}" x:Name="blazorWebView1">
<blazor:BlazorWebView HostPage="wwwroot\index.html" Services="{StaticResource services1}" x:Name="blazorWebView1" InitializingWebView="OnInitializingWebView">
<blazor:BlazorWebView.RootComponents>
<blazor:RootComponent Selector="#app" ComponentType="{x:Type local:Main}" />
</blazor:BlazorWebView.RootComponents>
Expand Down
10 changes: 10 additions & 0 deletions src/BlazorWebView/samples/BlazorWpfApp/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Windows;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebView.WebView2;
using Microsoft.Extensions.DependencyInjection;
using WebViewAppShared;

Expand Down Expand Up @@ -52,5 +53,14 @@ private void WebViewAlertButton_Click(object sender, RoutedEventArgs e)
{
blazorWebView1.WebView.CoreWebView2.ExecuteScriptAsync("alert('hello from native UI')");
}

private void OnInitializingWebView(object sender, WebViewInitializeEventArgs e)
{
// Pressing F12 will result in infinite load animation / not found error.
e.CoreWebView2EnvironmentOptions.AdditionalBrowserArguments = "--custom-devtools-frontend=http://invalid-url";

// Would show FPS Counter
//e.CoreWebView2EnvironmentOptions.AdditionalBrowserArguments = "--show-fps-counter --ui-show-fps-counter";
}
}
}
36 changes: 28 additions & 8 deletions src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public class WebView2WebViewManager : WebViewManager
#if WEBVIEW2_WINFORMS || WEBVIEW2_WPF
private protected CoreWebView2Environment _coreWebView2Environment;
private readonly Action<ExternalLinkNavigationEventArgs> _externalNavigationStarting;
private readonly Action<WebViewInitializeEventArgs> _webView2Initialize;
private readonly BlazorWebViewDeveloperTools _developerTools;

/// <summary>
Expand All @@ -75,14 +76,16 @@ public class WebView2WebViewManager : WebViewManager
/// <param name="jsComponents">Describes configuration for adding, removing, and updating root components from JavaScript code.</param>
/// <param name="hostPageRelativePath">Path to the host page within the <paramref name="fileProvider"/>.</param>
/// <param name="externalNavigationStarting">Callback invoked when external navigation starts.</param>
/// <param name="webView2Initialize">Callback when the webview is initialized.</param>
public WebView2WebViewManager(
WebView2Control webview!!,
IServiceProvider services,
Dispatcher dispatcher,
IFileProvider fileProvider,
JSComponentConfigurationStore jsComponents,
string hostPageRelativePath,
Action<ExternalLinkNavigationEventArgs> externalNavigationStarting)
Action<ExternalLinkNavigationEventArgs> externalNavigationStarting,
Action<WebViewInitializeEventArgs> webView2Initialize)
: base(services, dispatcher, new Uri(AppOrigin), fileProvider, jsComponents, hostPageRelativePath)

{
Expand All @@ -104,6 +107,7 @@ public WebView2WebViewManager(

_webview = webview;
_externalNavigationStarting = externalNavigationStarting;
_webView2Initialize = webView2Initialize;
_developerTools = services.GetRequiredService<BlazorWebViewDeveloperTools>();

// Unfortunately the CoreWebView2 can only be instantiated asynchronously.
Expand Down Expand Up @@ -169,18 +173,34 @@ protected override void SendMessage(string message)

private async Task InitializeWebView2()
{
_coreWebView2Environment = await CoreWebView2Environment.CreateAsync()
#if WEBVIEW2_MAUI
.AsTask()
CoreWebView2EnvironmentOptions options = new CoreWebView2EnvironmentOptions();
#if (WEBVIEW2_WPF)
var args = new WebViewInitializeEventArgs(options, Microsoft.AspNetCore.Components.WebView.Wpf.BlazorWebView.InitializingWebViewEvent);
#else
var args = new WebViewInitializeEventArgs(options);
#endif

#if (WEBVIEW2_WINFORMS || WEBVIEW2_WPF)
_webView2Initialize(args);
#endif
.ConfigureAwait(true);
await _webview.EnsureCoreWebView2Async();

#if WEBVIEW2_MAUI
var developerTools = _blazorWebViewHandler.DeveloperTools;
#elif WEBVIEW2_WINFORMS || WEBVIEW2_WPF
var developerTools = _blazorWebViewHandler.DeveloperTools;

_coreWebView2Environment = await CoreWebView2Environment.CreateAsync()
.AsTask();

await _webview.EnsureCoreWebView2Async();
#else
var developerTools = _developerTools;

_coreWebView2Environment = await CoreWebView2Environment.CreateAsync(
args.CoreWebView2BrowserExecutableFolder, args.CoreWebView2UserDataFolder, args.CoreWebView2EnvironmentOptions)
.ConfigureAwait(true);

await _webview.EnsureCoreWebView2Async(_coreWebView2Environment);
#endif

ApplyDefaultWebViewSettings(developerTools);

_webview.CoreWebView2.AddWebResourceRequestedFilter($"{AppOrigin}*", CoreWebView2WebResourceContext.All);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Windows;
using Microsoft.Web.WebView2.Core;

namespace Microsoft.AspNetCore.Components.WebView.WebView2
{
/// <summary>
/// Event arguments for the InitializingWebView event.
/// </summary>
public sealed class WebViewInitializeEventArgs
#if (WEBVIEW2_WPF)
: RoutedEventArgs
#else
: EventArgs
#endif
{
#if(WEBVIEW2_WPF)
/// <summary>
/// Creates the event args for the WebView2 initalization.
/// </summary>
/// <param name="coreWebView2EnvironmentOptions">The options</param>
/// <param name="routedEvent">The <see cref="RoutedEvent"/> source. </param>
internal WebViewInitializeEventArgs(CoreWebView2EnvironmentOptions coreWebView2EnvironmentOptions
, RoutedEvent routedEvent)
: base(routedEvent)
#else
/// <summary>
/// Creates the event args for the WebView2 initalization.
/// </summary>
/// <param name="coreWebView2EnvironmentOptions">The options</param>
internal WebViewInitializeEventArgs(CoreWebView2EnvironmentOptions coreWebView2EnvironmentOptions)
#endif

{
CoreWebView2EnvironmentOptions = coreWebView2EnvironmentOptions;
}

/// <summary>
/// Options used to create WebView2 Environment.
/// As a browser process may be shared among WebViews, WebView creation fails if
/// the specified options does not match the options of the WebViews that are currently
/// running in the shared browser process.
/// </summary>
public CoreWebView2EnvironmentOptions CoreWebView2EnvironmentOptions { get; }

/// <summary>
/// The relative path to the folder that contains a custom version of WebView2 Runtime.
/// To use a fixed version of the WebView2 Runtime, pass the folder path that contains
/// the fixed version of the WebView2 Runtime to browserExecutableFolder. BrowserExecutableFolder
/// supports both relative (to the application's executable) and absolute file paths.
/// To create WebView2 controls that use the installed version of the WebView2 Runtime
/// that exists on user machines, pass a null or empty string to browserExecutableFolder.
/// In this scenario, the API tries to find a compatible version of the WebView2
/// Runtime that is installed on the user machine (first at the machine level, and
/// then per user) using the selected channel preference. The path of fixed version
/// of the WebView2 Runtime should not contain \Edge\Application\. When such a path
/// is used, the API fails with ERROR_NOT_SUPPORTED.
/// </summary>
public string CoreWebView2BrowserExecutableFolder { get; set; } = null;

/// <summary>
/// The user data folder location for WebView2.
/// The path is either an absolute file path or a relative file path that is interpreted
/// as relative to the compiled code for the current process. The default user data
/// folder {Executable File Name}.WebView2 is created in the same directory next
/// to the compiled code for the app. WebView2 creation fails if the compiled code
/// is running in a directory in which the process does not have permission to create
/// a new directory. The app is responsible to clean up the associated user data
/// folder when it is done.
/// </summary>
public string CoreWebView2UserDataFolder { get; set; } = null;

#if (WEBVIEW2_WPF)

/// <inheritdoc />
protected override void InvokeEventHandler(Delegate genericHandler, object genericTarget)
{
var handler = (EventHandler<WebViewInitializeEventArgs>)genericHandler;
handler(genericTarget, this);
}
#endif
}
}
11 changes: 10 additions & 1 deletion src/BlazorWebView/src/WindowsForms/BlazorWebView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ public IServiceProvider Services
[Description("Allows customizing how external links are opened. Opens external links in the system browser by default.")]
public EventHandler<ExternalLinkNavigationEventArgs> ExternalNavigationStarting;

/// <summary>
/// Allows customizing the webview start parameters.
/// </summary>
[Category("Action")]
[Description("Allows customizing the webview start parameters.")]
public EventHandler<WebViewInitializeEventArgs> WebViewInitialize;

private void OnHostPagePropertyChanged() => StartWebViewCoreIfPossible();

private void OnServicesPropertyChanged() => StartWebViewCoreIfPossible();
Expand Down Expand Up @@ -170,7 +177,9 @@ private void StartWebViewCoreIfPossible()
fileProvider,
RootComponents.JSComponents,
hostPageRelativePath,
(args) => ExternalNavigationStarting?.Invoke(this, args));
(args) => ExternalNavigationStarting?.Invoke(this, args),
(args) => WebViewInitialize?.Invoke(this, args)
);

foreach (var rootComponent in RootComponents)
{
Expand Down
30 changes: 29 additions & 1 deletion src/BlazorWebView/src/Wpf/BlazorWebView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ public class BlazorWebView : Control, IAsyncDisposable
ownerType: typeof(BlazorWebView));
#endregion

#region Routed event definitions

/// <summary>
/// The backing event for the <see cref="InitializingWebViewEvent" /> event.
/// </summary>
public static readonly RoutedEvent InitializingWebViewEvent = EventManager.RegisterRoutedEvent(
name: "InitializingWebView",
routingStrategy: RoutingStrategy.Bubble,
handlerType: typeof(EventHandler<WebViewInitializeEventArgs>),
ownerType: typeof(BlazorWebView));

#endregion

private const string WebViewTemplateChildName = "WebView";
private WebView2Control _webview;
private WebView2WebViewManager _webviewManager;
Expand Down Expand Up @@ -197,7 +210,8 @@ private void StartWebViewCoreIfPossible()
fileProvider,
RootComponents.JSComponents,
hostPageRelativePath,
(args) => ExternalNavigationStarting?.Invoke(this, args));
(args) => ExternalNavigationStarting?.Invoke(this, args),
RaiseInitializingWebViewEvent);

foreach (var rootComponent in RootComponents)
{
Expand All @@ -207,6 +221,20 @@ private void StartWebViewCoreIfPossible()
_webviewManager.Navigate("/");
}

/// <summary>
/// Is fired before the enviroment of the WebView is set.
/// </summary>
public event EventHandler<WebViewInitializeEventArgs> InitializingWebView
{
add { AddHandler(InitializingWebViewEvent, value); }
remove { RemoveHandler(InitializingWebViewEvent, value); }
}

internal void RaiseInitializingWebViewEvent(WebViewInitializeEventArgs args)
{
RaiseEvent(args);
}

private WpfDispatcher ComponentsDispatcher { get; }

private void HandleRootComponentsCollectionChanged(object sender, NotifyCollectionChangedEventArgs eventArgs)
Expand Down