Skip to content

Commit

Permalink
Wire up WebView2 to window life cycle (dotnet#13206) Fixes dotnet#10436
Browse files Browse the repository at this point in the history
  • Loading branch information
PureWeen authored and TJ Lambert committed Feb 21, 2023
1 parent 037b195 commit be9a0bd
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 44 deletions.
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;
}

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

0 comments on commit be9a0bd

Please sign in to comment.