Skip to content
Open
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
10 changes: 10 additions & 0 deletions src/Avalonia.Controls.WebView.Core/IWebViewAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,16 @@ internal interface IWebViewAdapterWithInputRedirect : IWebViewAdapter
event Action<RoutedEventArgs> Input;
}

internal interface IWebViewAdapterWithProcessTermination : IWebViewAdapter
{
/// <summary>
/// WebContentProcessTerminated dispatches after the web content process backing the webview terminates,
/// for example after a crash or after the operating system reclaims its memory. The native control stays
/// alive but renders nothing until the page is reloaded.
/// </summary>
event EventHandler? WebContentProcessTerminated;
}

internal interface IWebViewAdapterWithCommands
{
void Copy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ internal unsafe class WKNavigationDelegate : NSManagedObjectBase
s_willPresentNotification = &OnDidFinishNavigation;
private static readonly delegate* unmanaged[Cdecl]<IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, void>
s_decidePolicyForNavigationAction = &OnDecidePolicyForNavigationAction;
private static readonly delegate* unmanaged[Cdecl]<IntPtr, IntPtr, IntPtr, void>
s_webContentProcessDidTerminate = &OnWebContentProcessDidTerminate;

static WKNavigationDelegate()
{
Expand All @@ -31,6 +33,10 @@ static WKNavigationDelegate()
result = Libobjc.class_addMethod(delegateClass, didReceiveNotificationResponse, s_decidePolicyForNavigationAction, "v@:@@@");
Debug.Assert(result == 1);

var webContentProcessDidTerminateSel = Libobjc.sel_getUid("webViewWebContentProcessDidTerminate:");
result = Libobjc.class_addMethod(delegateClass, webContentProcessDidTerminateSel, s_webContentProcessDidTerminate, "v@:@");
Debug.Assert(result == 1);

result = RegisterManagedMembers(delegateClass) ? 1 : 0;
Debug.Assert(result == 1);

Expand All @@ -45,6 +51,7 @@ public WKNavigationDelegate() : base(s_class)

public event EventHandler? DidFinishNavigation;
public event EventHandler<DecidePolicyNavigationEventArgs>? DecidePolicyNavigation;
public event EventHandler? WebContentProcessDidTerminate;

[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnDidFinishNavigation(IntPtr self, IntPtr sel, IntPtr webView, IntPtr navigation)
Expand All @@ -53,6 +60,13 @@ private static void OnDidFinishNavigation(IntPtr self, IntPtr sel, IntPtr webVie
managed?.DidFinishNavigation?.Invoke(managed, EventArgs.Empty);
}

[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnWebContentProcessDidTerminate(IntPtr self, IntPtr sel, IntPtr webView)
{
var managed = ReadManagedSelf<WKNavigationDelegate>(self);
managed?.WebContentProcessDidTerminate?.Invoke(managed, EventArgs.Empty);
}

private static readonly IntPtr s_actionRequest = Libobjc.sel_getUid("request");
private static readonly IntPtr s_navigationType = Libobjc.sel_getUid("navigationType");
private static readonly IntPtr s_targetFrame = Libobjc.sel_getUid("targetFrame");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ namespace Avalonia.Controls.Macios;
[SupportedOSPlatform("macos")]
[SupportedOSPlatform("ios")]
internal class MaciosWebViewAdapter : IWebViewAdapterWithFocus, IWebViewAdapterWithInputRedirect,
IWebViewAdapterWithCookieManager, IWebViewAdapterWithCommands, IWebViewWithPrint, IAppleWKWebViewPlatformHandle
IWebViewAdapterWithCookieManager, IWebViewAdapterWithCommands, IWebViewAdapterWithProcessTermination,
IWebViewWithPrint, IAppleWKWebViewPlatformHandle
{
private const string DefaultPostAvWebViewMessageName = "postAvWebViewMessage";

Expand Down Expand Up @@ -73,6 +74,7 @@ public MaciosWebViewAdapter(AppleWKWebViewEnvironmentRequestedEventArgs options)
_navDelegate = new WKNavigationDelegate();
_navDelegate.DidFinishNavigation += OnDelegateOnDidFinishNavigation;
_navDelegate.DecidePolicyNavigation += OnDelegateOnDecidePolicyNavigation;
_navDelegate.WebContentProcessDidTerminate += OnDelegateOnWebContentProcessDidTerminate;

_webView = new WKWebView(_config) { NavigationDelegate = _navDelegate };
_webView.Opaque = false;
Expand Down Expand Up @@ -157,6 +159,7 @@ public Color DefaultBackground
public event EventHandler<WebResourceRequestedEventArgs>? WebResourceRequested;
public event EventHandler? GotFocus;
public event EventHandler<IWebViewAdapterWithFocus.LostFocusDirection>? LostFocus;
public event EventHandler? WebContentProcessTerminated;
public event Action<RoutedEventArgs>? Input;
public bool GoBack() => _webView.GoBack() != default;
public bool GoForward() => _webView.GoForward() != default;
Expand Down Expand Up @@ -203,6 +206,7 @@ public void Dispose()
_scriptHandler.DidReceiveScriptMessage -= OnScriptHandlerOnDidReceiveScriptMessage;
_navDelegate.DidFinishNavigation -= OnDelegateOnDidFinishNavigation;
_navDelegate.DecidePolicyNavigation -= OnDelegateOnDecidePolicyNavigation;
_navDelegate.WebContentProcessDidTerminate -= OnDelegateOnWebContentProcessDidTerminate;
_webView.PerformKeyEquivalent -= WebViewOnPerformKeyEquivalent;
_webView.BecomeFirstResponder -= OnWebViewOnBecomeFirstResponder;
_webView.ResignFirstResponder -= OnWebViewOnResignFirstResponder;
Expand Down Expand Up @@ -297,6 +301,11 @@ private async void OnDelegateOnDidFinishNavigation(object? sender, EventArgs arg
NavigationCompleted?.Invoke(this, new WebViewNavigationCompletedEventArgs { Request = Uri.TryCreate(url!.AbsoluteString, UriKind.Absolute, out var uri) ? uri : null, IsSuccess = true });
}

private void OnDelegateOnWebContentProcessDidTerminate(object? sender, EventArgs args)
{
WebContentProcessTerminated?.Invoke(this, EventArgs.Empty);
}

private void OnWebViewOnResignFirstResponder(object? o, EventArgs eventArgs)
{
LostFocus?.Invoke(this, IWebViewAdapterWithFocus.LostFocusDirection.Unknown);
Expand Down
20 changes: 20 additions & 0 deletions src/Avalonia.Controls.WebView/NativeWebDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,17 @@ public bool Move(int x, int y)
public event EventHandler<Core.WebViewAdapterEventArgs>? AdapterDestroyed;
public event EventHandler<Core.WebViewEnvironmentRequestedEventArgs>? EnvironmentRequested;

/// <summary>
/// WebContentProcessTerminated dispatches after the web content process backing the webview terminates,
/// for example after a crash or after the operating system reclaims its memory. The dialog stays open
/// but renders nothing until the page is reloaded, so handle this event to <see cref="Navigate"/> again.
/// </summary>
/// <remarks>
/// Raised by WebKit-based adapters (WKWebView on macOS and iOS). Other platforms do not raise this
/// event yet.
/// </remarks>
public event EventHandler? WebContentProcessTerminated;

/// <inheritdoc/>
public Core.NativeWebViewCommandManager? TryGetCommandManager() => TryGetAdapter() switch
{
Expand Down Expand Up @@ -465,6 +476,8 @@ private void DialogImplOnAdapterDestroyed(object? sender, Core.WebViewAdapterEve
adapter.WebMessageReceived -= WebViewAdapterOnWebMessageReceived;
adapter.WebResourceRequested -= WebViewAdapterOnWebResourceRequested;
adapter.NewWindowRequested -= WebViewAdapterOnNewWindowRequested;
if (adapter is Core.IWebViewAdapterWithProcessTermination withProcessTermination)
withProcessTermination.WebContentProcessTerminated -= WebViewAdapterOnWebContentProcessTerminated;
_dialogInitialized = false;
AdapterDestroyed?.Invoke(this, e);
}
Expand Down Expand Up @@ -492,6 +505,8 @@ private void DialogImplOnAdapterCreated(object? sender, Core.WebViewAdapterEvent
adapter.WebResourceRequested += WebViewAdapterOnWebResourceRequested;
if (_newWindowRequested is not null)
adapter.NewWindowRequested += WebViewAdapterOnNewWindowRequested;
if (adapter is Core.IWebViewAdapterWithProcessTermination withProcessTermination)
withProcessTermination.WebContentProcessTerminated += WebViewAdapterOnWebContentProcessTerminated;
if (_initialSource is Uri url)
adapter.Source = url;
else if (_initialSource is ValueTuple<string, Uri?> pair)
Expand Down Expand Up @@ -528,5 +543,10 @@ private void WebViewAdapterOnNewWindowRequested(object? sender, Core.WebViewNewW
{
_newWindowRequested?.Invoke(this, e);
}

private void WebViewAdapterOnWebContentProcessTerminated(object? sender, EventArgs e)
{
WebContentProcessTerminated?.Invoke(this, e);
}
}
}
27 changes: 27 additions & 0 deletions src/Avalonia.Controls.WebView/NativeWebView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,18 @@ public event EventHandler<Core.WebResourceRequestedEventArgs>? WebResourceReques
/// <inheritdoc/>
public event EventHandler<Core.WebViewAdapterEventArgs>? AdapterDestroyed;

/// <summary>
/// WebContentProcessTerminated dispatches after the web content process backing the webview terminates,
/// for example after a crash or after the operating system reclaims its memory. The native control stays
/// alive but renders nothing until the page is reloaded, so handle this event to
/// <see cref="Refresh"/> or <see cref="Navigate"/> again.
/// </summary>
/// <remarks>
/// Raised by WebKit-based adapters (WKWebView on macOS and iOS). Other platforms do not raise this
/// event yet.
/// </remarks>
public event EventHandler? WebContentProcessTerminated;

/// <inheritdoc/>
public Uri Source
{
Expand Down Expand Up @@ -428,6 +440,12 @@ private void WebViewAdapterOnNewWindowRequested(object? sender, Core.WebViewNewW
_newWindowRequested?.Invoke(this, e);
}

private void WebViewAdapterOnWebContentProcessTerminated(object? sender, EventArgs e)
{
Core.WebViewDispatcher.VerifyAccess();
WebContentProcessTerminated?.Invoke(this, e);
}

#if AVALONIA
protected override async void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
Expand Down Expand Up @@ -563,6 +581,10 @@ private void ControlHostImplOnAdapterDeinitialized(object? sender, Core.IWebView
{
withInput.Input -= WithInputOnInput;
}
if (adapter is Core.IWebViewAdapterWithProcessTermination withProcessTermination)
{
withProcessTermination.WebContentProcessTerminated -= WebViewAdapterOnWebContentProcessTerminated;
}

AdapterDestroyed?.Invoke(this, new Core.WebViewAdapterEventArgs(adapter));
}
Expand Down Expand Up @@ -595,6 +617,11 @@ private void ControlHostImplOnAdapterCreated(object? sender, Core.IWebViewAdapte
withInput.Input += WithInputOnInput;
}

if (adapter is Core.IWebViewAdapterWithProcessTermination withProcessTermination)
{
withProcessTermination.WebContentProcessTerminated += WebViewAdapterOnWebContentProcessTerminated;
}

if (_initialSource is Uri url)
{
adapter.Navigate(url);
Expand Down