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

Wire up WebView2 to window life cycle #13206

Merged
merged 1 commit into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
Padding="12"
Spacing="6">

<WebView
HeightRequest="300"
WidthRequest="300"
Source="https://github.com/dotnet/maui" />

<Label Text="Current Window Frame:" />
<Label Text="{Binding Window.X, StringFormat='X = {0:0.00}'}" />
<Label Text="{Binding Window.Y, StringFormat='Y = {0:0.00}'}" />
Expand Down
72 changes: 53 additions & 19 deletions src/Core/src/Handlers/WebView/WebViewHandler.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Web.WebView2.Core;
using Windows.Web.Http;
Expand All @@ -12,6 +13,7 @@ public partial class WebViewHandler : ViewHandler<IWebView, WebView2>
{
WebNavigationEvent _eventState;
readonly HashSet<string> _loadedCookies = new HashSet<string>();
Window? _window;

protected override WebView2 CreatePlatformView() => new MauiWebView();

Expand All @@ -24,21 +26,54 @@ internal WebNavigationEvent CurrentNavigationEvent
protected override void ConnectHandler(WebView2 platformView)
{
platformView.CoreWebView2Initialized += OnCoreWebView2Initialized;

base.ConnectHandler(platformView);

if (platformView.IsLoaded)
OnLoaded();
else
platformView.Loaded += OnWebViewLoaded;
}

protected override void DisconnectHandler(WebView2 platformView)
void OnWebViewLoaded(object sender, UI.Xaml.RoutedEventArgs e)
{
OnLoaded();
}

void OnLoaded()
{
_window = MauiContext!.GetPlatformWindow();
_window.Closed += OnWindowClosed;

This comment was marked as outdated.

This comment was marked as off-topic.

}
Comment on lines +42 to +46
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the real fix here, cleaning up after the native window closes.


private void OnWindowClosed(object sender, UI.Xaml.WindowEventArgs args)
{
if (platformView.CoreWebView2 != null)
Disconnect(PlatformView);
}

void Disconnect(WebView2 platformView)
{
if (_window is not null)
{
_window.Closed -= OnWindowClosed;
_window = null;
}

if (platformView.CoreWebView2 is not null)
{
platformView.CoreWebView2.HistoryChanged -= OnHistoryChanged;
platformView.CoreWebView2.NavigationStarting -= OnNavigationStarting;
platformView.CoreWebView2.NavigationCompleted -= OnNavigationCompleted;
platformView.CoreWebView2.Stop();
}

platformView.Loaded -= OnWebViewLoaded;
platformView.CoreWebView2Initialized -= OnCoreWebView2Initialized;
platformView.Close();
}

protected override void DisconnectHandler(WebView2 platformView)
{
DisconnectHandler(platformView);
base.DisconnectHandler(platformView);
}

Expand Down Expand Up @@ -66,7 +101,7 @@ void OnNavigationCompleted(CoreWebView2 sender, CoreWebView2NavigationCompletedE

void OnNavigationStarting(CoreWebView2 sender, CoreWebView2NavigationStartingEventArgs args)
{
if (Uri.TryCreate(args.Uri, UriKind.Absolute, out Uri? uri) && uri != null)
if (Uri.TryCreate(args.Uri, UriKind.Absolute, out Uri? uri) && uri is not null)
{
bool cancel = VirtualView.Navigating(CurrentNavigationEvent, uri.AbsoluteUri);

Expand Down Expand Up @@ -126,7 +161,7 @@ void NavigationSucceeded(CoreWebView2 sender, CoreWebView2NavigationCompletedEve
{
var uri = sender.Source;

if (uri != null)
if (uri is not null)
SendNavigated(uri, CurrentNavigationEvent, WebNavigationResult.Success);

if (VirtualView is null)
Expand All @@ -145,12 +180,11 @@ void NavigationFailed(CoreWebView2 sender, CoreWebView2NavigationCompletedEventA

void SendNavigated(string url, WebNavigationEvent evnt, WebNavigationResult result)
{
if (VirtualView != null)
if (VirtualView is not null)
{
SyncPlatformCookiesToVirtualView(url);

VirtualView.Navigated(evnt, url, result);

PlatformView?.UpdateCanGoBackForward(VirtualView);
}

Expand All @@ -161,12 +195,12 @@ void SyncPlatformCookiesToVirtualView(string url)
{
var myCookieJar = VirtualView.Cookies;

if (myCookieJar == null)
if (myCookieJar is null)
return;

var uri = CreateUriForCookies(url);

if (uri == null)
if (uri is null)
return;

var cookies = myCookieJar.GetCookies(uri);
Expand All @@ -180,7 +214,7 @@ void SyncPlatformCookiesToVirtualView(string url)
var httpCookie = platformCookies
.FirstOrDefault(x => x.Name == cookie.Name);

if (httpCookie == null)
if (httpCookie is null)
cookie.Expired = true;
else
cookie.Value = httpCookie.Value;
Expand All @@ -193,18 +227,18 @@ void SyncPlatformCookies(string url)
{
var uri = CreateUriForCookies(url);

if (uri == null)
if (uri is null)
return;

var myCookieJar = VirtualView.Cookies;

if (myCookieJar == null)
if (myCookieJar is null)
return;

InitialCookiePreloadIfNecessary(url);
var cookies = myCookieJar.GetCookies(uri);

if (cookies == null)
if (cookies is null)
return;

var retrieveCurrentWebCookies = GetCookiesFromPlatformStore(url);
Expand All @@ -222,7 +256,7 @@ void SyncPlatformCookies(string url)

foreach (HttpCookie cookie in retrieveCurrentWebCookies)
{
if (cookies[cookie.Name] != null)
if (cookies[cookie.Name] is not null)
continue;

filter.CookieManager.DeleteCookie(cookie);
Expand All @@ -233,7 +267,7 @@ void InitialCookiePreloadIfNecessary(string url)
{
var myCookieJar = VirtualView.Cookies;

if (myCookieJar == null)
if (myCookieJar is null)
return;

var uri = new Uri(url);
Expand All @@ -243,12 +277,12 @@ void InitialCookiePreloadIfNecessary(string url)

var cookies = myCookieJar.GetCookies(uri);

if (cookies != null)
if (cookies is not null)
{
var existingCookies = GetCookiesFromPlatformStore(url);
foreach (HttpCookie cookie in existingCookies)
{
if (cookies[cookie.Name] == null)
if (cookies[cookie.Name] is null)
myCookieJar.SetCookies(uri, cookie.ToString());
}
}
Expand All @@ -265,7 +299,7 @@ HttpCookieCollection GetCookiesFromPlatformStore(string url)

Uri? CreateUriForCookies(string url)
{
if (url == null)
if (url is null)
return null;

Uri? uri;
Expand All @@ -290,7 +324,7 @@ public static void MapEvaluateJavaScriptAsync(IWebViewHandler handler, IWebView
{
if (arg is EvaluateJavaScriptAsyncRequest request)
{
if (handler.PlatformView == null)
if (handler.PlatformView is null)
{
request.SetCanceled();
return;
Expand Down
2 changes: 1 addition & 1 deletion src/Core/tests/DeviceTests.Shared/Stubs/ContextStub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public object GetService(Type serviceType)
return _windowManager ??= new NavigationRootManager(MauiProgramDefaults.DefaultWindow);

if (serviceType == typeof(UI.Xaml.Window))
return MauiProgramDefaults.DefaultWindow;
return _services.GetService(serviceType) ?? MauiProgramDefaults.DefaultWindow;
#endif
if (serviceType == typeof(IDispatcher))
return _services.GetService(serviceType) ?? TestDispatcher.Current;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,46 @@
using Microsoft.UI.Xaml.Controls;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Maui.Controls;
using Microsoft.Maui.DeviceTests.Stubs;
using Microsoft.UI.Xaml.Controls;
using Xunit;

namespace Microsoft.Maui.DeviceTests
{
public partial class WebViewHandlerTests
{
[Fact(DisplayName = "Closing Window With WebView Doesnt Crash")]
public async Task ClosingWindowWithWebViewDoesntCrash()
{
EnsureHandlerCreated(builder =>
{
builder.Services.AddSingleton(typeof(UI.Xaml.Window), (services) => new UI.Xaml.Window());
});

var webView = new WebViewStub()
{
Source = new UrlWebViewSourceStub { Url = "https://dotnet.microsoft.com/" }
};

var handler = await CreateHandlerAsync(webView);

await InvokeOnMainThreadAsync(async () =>
{
TaskCompletionSource navigationComplete = new TaskCompletionSource();
handler.PlatformView.NavigationCompleted += (_, _) =>
{
navigationComplete?.SetResult();
navigationComplete = null;
};

await handler.PlatformView.AttachAndRun(async () =>
{
await handler.PlatformView.OnLoadedAsync();
await navigationComplete.Task;
}, MauiContext);
});
}

WebView2 GetNativeWebView(WebViewHandler webViewHandler) =>
webViewHandler.PlatformView;

Expand Down
47 changes: 24 additions & 23 deletions src/TestUtils/src/DeviceTests/AssertionExtensions.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,25 +93,25 @@ public static WColor ColorAtPoint(this CanvasBitmap bitmap, int x, int y, bool i
: WColor.FromArgb(255, pixel.R, pixel.G, pixel.B);
}

public static Task AttachAndRun(this FrameworkElement view, Action action) =>
view.AttachAndRun(window => action());
public static Task AttachAndRun(this FrameworkElement view, Action action, IMauiContext? mauiContext = null) =>
view.AttachAndRun(window => action(), mauiContext);

public static Task AttachAndRun(this FrameworkElement view, Action<Window> action) =>
public static Task AttachAndRun(this FrameworkElement view, Action<Window> action, IMauiContext? mauiContext = null) =>
view.AttachAndRun((window) =>
{
action(window);
return Task.FromResult(true);
});
}, mauiContext);

public static Task<T> AttachAndRun<T>(this FrameworkElement view, Func<T> action) =>
view.AttachAndRun(window => action());
public static Task<T> AttachAndRun<T>(this FrameworkElement view, Func<T> action, IMauiContext? mauiContext = null) =>
view.AttachAndRun(window => action(), mauiContext);

public static Task<T> AttachAndRun<T>(this FrameworkElement view, Func<Window, T> action) =>
public static Task<T> AttachAndRun<T>(this FrameworkElement view, Func<Window, T> action, IMauiContext? mauiContext = null) =>
view.AttachAndRun((window) =>
{
var result = action(window);
return Task.FromResult(result);
});
}, mauiContext);

public static Task AttachAndRun(this FrameworkElement view, Func<Task> action) =>
view.AttachAndRun(window => action());
Expand All @@ -132,7 +132,7 @@ public static Task AttachAndRun(this FrameworkElement view, Func<Window, Task> a
public static Task<T> AttachAndRun<T>(this FrameworkElement view, Func<Task<T>> action) =>
view.AttachAndRun(window => action());

public static async Task<T> AttachAndRun<T>(this FrameworkElement view, Func<Window, Task<T>> action)
public static async Task<T> AttachAndRun<T>(this FrameworkElement view, Func<Window, Task<T>> action, IMauiContext? mauiContext = null)
{
if (view.Parent is Border wrapper)
view = wrapper;
Expand All @@ -156,26 +156,27 @@ public static async Task<T> AttachAndRun<T>(this FrameworkElement view, Func<Win

// attach to the UI
Grid grid;
var window = new Window
var window = (mauiContext?.Services != null) ?
Extensions.DependencyInjection.ActivatorUtilities.GetServiceOrCreateInstance<Window>(mauiContext.Services) : new Window();

window.Content = new Grid
{
Content = new Grid
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Children =
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Children =
(grid = new Grid
{
(grid = new Grid
Width = view.Width,
Height = view.Height,
Children =
{
Width = view.Width,
Height = view.Height,
Children =
{
view
}
})
}
view
}
})
}
};

window.Activate();

// wait for element to be loaded
Expand Down