Add 5 verified files from decompiled production code
Changes: - GtkWebViewHandler.cs - New native WebKit handler - GtkWebViewProxy.cs - New proxy for WebView positioning - WebViewHandler.cs - Fixed navigation event handling - PageHandler.cs - Added MapBackgroundColor - SkiaView.cs - Made Arrange() virtual Also adds CLAUDE.md (instructions) and MERGE_TRACKING.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
233
Handlers/GtkWebViewHandler.cs
Normal file
233
Handlers/GtkWebViewHandler.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Platform.Linux.Native;
|
||||
using Microsoft.Maui.Platform.Linux.Services;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for WebView using native GTK WebKitGTK widget.
|
||||
/// </summary>
|
||||
public class GtkWebViewHandler : ViewHandler<IWebView, GtkWebViewProxy>
|
||||
{
|
||||
private GtkWebViewPlatformView? _platformWebView;
|
||||
private bool _isRegisteredWithHost;
|
||||
private SKRect _lastBounds;
|
||||
|
||||
public static IPropertyMapper<IWebView, GtkWebViewHandler> Mapper = new PropertyMapper<IWebView, GtkWebViewHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(IWebView.Source)] = MapSource,
|
||||
};
|
||||
|
||||
public static CommandMapper<IWebView, GtkWebViewHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
{
|
||||
[nameof(IWebView.GoBack)] = MapGoBack,
|
||||
[nameof(IWebView.GoForward)] = MapGoForward,
|
||||
[nameof(IWebView.Reload)] = MapReload,
|
||||
};
|
||||
|
||||
public GtkWebViewHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public GtkWebViewHandler(IPropertyMapper? mapper = null, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override GtkWebViewProxy CreatePlatformView()
|
||||
{
|
||||
_platformWebView = new GtkWebViewPlatformView();
|
||||
return new GtkWebViewProxy(this, _platformWebView);
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(GtkWebViewProxy platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
if (_platformWebView != null)
|
||||
{
|
||||
_platformWebView.NavigationStarted += OnNavigationStarted;
|
||||
_platformWebView.NavigationCompleted += OnNavigationCompleted;
|
||||
}
|
||||
Console.WriteLine("[GtkWebViewHandler] ConnectHandler - WebView ready");
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(GtkWebViewProxy platformView)
|
||||
{
|
||||
if (_platformWebView != null)
|
||||
{
|
||||
_platformWebView.NavigationStarted -= OnNavigationStarted;
|
||||
_platformWebView.NavigationCompleted -= OnNavigationCompleted;
|
||||
UnregisterFromHost();
|
||||
_platformWebView.Dispose();
|
||||
_platformWebView = null;
|
||||
}
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnNavigationStarted(object? sender, string uri)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] Navigation started: {uri}");
|
||||
try
|
||||
{
|
||||
GLibNative.IdleAdd(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (VirtualView is IWebViewController controller)
|
||||
{
|
||||
var args = new Microsoft.Maui.Controls.WebNavigatingEventArgs(
|
||||
WebNavigationEvent.NewPage, null, uri);
|
||||
controller.SendNavigating(args);
|
||||
Console.WriteLine("[GtkWebViewHandler] Sent Navigating event to VirtualView");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] Error in SendNavigating: {ex.Message}");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] Error dispatching navigation started: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNavigationCompleted(object? sender, (string Url, bool Success) e)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] Navigation completed: {e.Url} (Success: {e.Success})");
|
||||
try
|
||||
{
|
||||
GLibNative.IdleAdd(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (VirtualView is IWebViewController controller)
|
||||
{
|
||||
var result = e.Success ? WebNavigationResult.Success : WebNavigationResult.Failure;
|
||||
var args = new Microsoft.Maui.Controls.WebNavigatedEventArgs(
|
||||
WebNavigationEvent.NewPage, null, e.Url, result);
|
||||
controller.SendNavigated(args);
|
||||
|
||||
bool canGoBack = _platformWebView?.CanGoBack() ?? false;
|
||||
bool canGoForward = _platformWebView?.CanGoForward() ?? false;
|
||||
controller.CanGoBack = canGoBack;
|
||||
controller.CanGoForward = canGoForward;
|
||||
Console.WriteLine($"[GtkWebViewHandler] Sent Navigated, CanGoBack={canGoBack}, CanGoForward={canGoForward}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] Error in SendNavigated: {ex.Message}");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] Error dispatching navigation completed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal void RegisterWithHost(SKRect bounds)
|
||||
{
|
||||
if (_platformWebView == null)
|
||||
return;
|
||||
|
||||
var hostService = GtkHostService.Instance;
|
||||
if (hostService.HostWindow == null || hostService.WebViewManager == null)
|
||||
{
|
||||
Console.WriteLine("[GtkWebViewHandler] Warning: GTK host not initialized, cannot register WebView");
|
||||
return;
|
||||
}
|
||||
|
||||
int x = (int)bounds.Left;
|
||||
int y = (int)bounds.Top;
|
||||
int width = (int)bounds.Width;
|
||||
int height = (int)bounds.Height;
|
||||
|
||||
if (width <= 0 || height <= 0)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] Skipping invalid bounds: {bounds}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isRegisteredWithHost)
|
||||
{
|
||||
hostService.HostWindow.AddWebView(_platformWebView.Widget, x, y, width, height);
|
||||
_isRegisteredWithHost = true;
|
||||
Console.WriteLine($"[GtkWebViewHandler] Registered WebView at ({x}, {y}) size {width}x{height}");
|
||||
}
|
||||
else if (bounds != _lastBounds)
|
||||
{
|
||||
hostService.HostWindow.MoveResizeWebView(_platformWebView.Widget, x, y, width, height);
|
||||
Console.WriteLine($"[GtkWebViewHandler] Updated WebView to ({x}, {y}) size {width}x{height}");
|
||||
}
|
||||
|
||||
_lastBounds = bounds;
|
||||
}
|
||||
|
||||
private void UnregisterFromHost()
|
||||
{
|
||||
if (_isRegisteredWithHost && _platformWebView != null)
|
||||
{
|
||||
var hostService = GtkHostService.Instance;
|
||||
if (hostService.HostWindow != null)
|
||||
{
|
||||
hostService.HostWindow.RemoveWebView(_platformWebView.Widget);
|
||||
Console.WriteLine("[GtkWebViewHandler] Unregistered WebView from host");
|
||||
}
|
||||
_isRegisteredWithHost = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapSource(GtkWebViewHandler handler, IWebView webView)
|
||||
{
|
||||
if (handler._platformWebView == null)
|
||||
return;
|
||||
|
||||
var source = webView.Source;
|
||||
Console.WriteLine($"[GtkWebViewHandler] MapSource: {source?.GetType().Name ?? "null"}");
|
||||
|
||||
if (source is UrlWebViewSource urlSource)
|
||||
{
|
||||
var url = urlSource.Url;
|
||||
if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
handler._platformWebView.Navigate(url);
|
||||
}
|
||||
}
|
||||
else if (source is HtmlWebViewSource htmlSource)
|
||||
{
|
||||
var html = htmlSource.Html;
|
||||
if (!string.IsNullOrEmpty(html))
|
||||
{
|
||||
handler._platformWebView.LoadHtml(html, htmlSource.BaseUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapGoBack(GtkWebViewHandler handler, IWebView webView, object? args)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] MapGoBack called, CanGoBack={handler._platformWebView?.CanGoBack()}");
|
||||
handler._platformWebView?.GoBack();
|
||||
}
|
||||
|
||||
public static void MapGoForward(GtkWebViewHandler handler, IWebView webView, object? args)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] MapGoForward called, CanGoForward={handler._platformWebView?.CanGoForward()}");
|
||||
handler._platformWebView?.GoForward();
|
||||
}
|
||||
|
||||
public static void MapReload(GtkWebViewHandler handler, IWebView webView, object? args)
|
||||
{
|
||||
Console.WriteLine("[GtkWebViewHandler] MapReload called");
|
||||
handler._platformWebView?.Reload();
|
||||
}
|
||||
}
|
||||
83
Handlers/GtkWebViewProxy.cs
Normal file
83
Handlers/GtkWebViewProxy.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Platform;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Proxy view that bridges SkiaView layout to GTK WebView positioning.
|
||||
/// </summary>
|
||||
public class GtkWebViewProxy : SkiaView
|
||||
{
|
||||
private readonly GtkWebViewHandler _handler;
|
||||
private readonly GtkWebViewPlatformView _platformView;
|
||||
|
||||
public GtkWebViewPlatformView PlatformView => _platformView;
|
||||
public bool CanGoBack => _platformView.CanGoBack();
|
||||
public bool CanGoForward => _platformView.CanGoForward();
|
||||
|
||||
public GtkWebViewProxy(GtkWebViewHandler handler, GtkWebViewPlatformView platformView)
|
||||
{
|
||||
_handler = handler;
|
||||
_platformView = platformView;
|
||||
}
|
||||
|
||||
public override void Arrange(SKRect bounds)
|
||||
{
|
||||
base.Arrange(bounds);
|
||||
var windowBounds = TransformToWindow(bounds);
|
||||
_handler.RegisterWithHost(windowBounds);
|
||||
}
|
||||
|
||||
private SKRect TransformToWindow(SKRect localBounds)
|
||||
{
|
||||
float x = localBounds.Left;
|
||||
float y = localBounds.Top;
|
||||
|
||||
for (var parent = Parent; parent != null; parent = parent.Parent)
|
||||
{
|
||||
x += parent.Bounds.Left;
|
||||
y += parent.Bounds.Top;
|
||||
}
|
||||
|
||||
return new SKRect(x, y, x + localBounds.Width, y + localBounds.Height);
|
||||
}
|
||||
|
||||
public override void Draw(SKCanvas canvas)
|
||||
{
|
||||
// Draw transparent placeholder - actual WebView is rendered by GTK
|
||||
using var paint = new SKPaint
|
||||
{
|
||||
Color = new SKColor(0, 0, 0, 0),
|
||||
Style = SKPaintStyle.Fill
|
||||
};
|
||||
canvas.DrawRect(Bounds, paint);
|
||||
}
|
||||
|
||||
public void Navigate(string url)
|
||||
{
|
||||
_platformView.Navigate(url);
|
||||
}
|
||||
|
||||
public void LoadHtml(string html, string? baseUrl = null)
|
||||
{
|
||||
_platformView.LoadHtml(html, baseUrl);
|
||||
}
|
||||
|
||||
public void GoBack()
|
||||
{
|
||||
_platformView.GoBack();
|
||||
}
|
||||
|
||||
public void GoForward()
|
||||
{
|
||||
_platformView.GoForward();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
_platformView.Reload();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ public partial class PageHandler : ViewHandler<Page, SkiaPage>
|
||||
[nameof(Page.BackgroundImageSource)] = MapBackgroundImageSource,
|
||||
[nameof(Page.Padding)] = MapPadding,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(VisualElement.BackgroundColor)] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<Page, PageHandler> CommandMapper =
|
||||
@@ -101,6 +102,18 @@ public partial class PageHandler : ViewHandler<Page, SkiaPage>
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(PageHandler handler, Page page)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
var backgroundColor = page.BackgroundColor;
|
||||
if (backgroundColor != null && backgroundColor != Colors.Transparent)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = backgroundColor.ToSKColor();
|
||||
Console.WriteLine($"[PageHandler] MapBackgroundColor: {backgroundColor}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -54,29 +54,63 @@ public partial class WebViewHandler : ViewHandler<IWebView, SkiaWebView>
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnNavigating(object? sender, WebNavigatingEventArgs e)
|
||||
private void OnNavigating(object? sender, Microsoft.Maui.Platform.WebNavigatingEventArgs e)
|
||||
{
|
||||
// Forward to virtual view if needed
|
||||
IWebView virtualView = VirtualView;
|
||||
IWebViewController? controller = virtualView as IWebViewController;
|
||||
if (controller != null)
|
||||
{
|
||||
var args = new Microsoft.Maui.Controls.WebNavigatingEventArgs(
|
||||
WebNavigationEvent.NewPage,
|
||||
null,
|
||||
e.Url);
|
||||
controller.SendNavigating(args);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNavigated(object? sender, WebNavigatedEventArgs e)
|
||||
private void OnNavigated(object? sender, Microsoft.Maui.Platform.WebNavigatedEventArgs e)
|
||||
{
|
||||
// Forward to virtual view if needed
|
||||
IWebView virtualView = VirtualView;
|
||||
IWebViewController? controller = virtualView as IWebViewController;
|
||||
if (controller != null)
|
||||
{
|
||||
WebNavigationResult result = e.Success ? WebNavigationResult.Success : WebNavigationResult.Failure;
|
||||
var args = new Microsoft.Maui.Controls.WebNavigatedEventArgs(
|
||||
WebNavigationEvent.NewPage,
|
||||
null,
|
||||
e.Url,
|
||||
result);
|
||||
controller.SendNavigated(args);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapSource(WebViewHandler handler, IWebView webView)
|
||||
{
|
||||
if (handler.PlatformView == null) return;
|
||||
Console.WriteLine("[WebViewHandler] MapSource called");
|
||||
if (handler.PlatformView == null)
|
||||
{
|
||||
Console.WriteLine("[WebViewHandler] PlatformView is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
var source = webView.Source;
|
||||
Console.WriteLine($"[WebViewHandler] Source type: {source?.GetType().Name ?? "null"}");
|
||||
|
||||
if (source is UrlWebViewSource urlSource)
|
||||
{
|
||||
Console.WriteLine($"[WebViewHandler] Loading URL: {urlSource.Url}");
|
||||
handler.PlatformView.Source = urlSource.Url ?? "";
|
||||
}
|
||||
else if (source is HtmlWebViewSource htmlSource)
|
||||
{
|
||||
Console.WriteLine($"[WebViewHandler] Loading HTML ({htmlSource.Html?.Length ?? 0} chars)");
|
||||
Console.WriteLine($"[WebViewHandler] HTML preview: {htmlSource.Html?.Substring(0, Math.Min(100, htmlSource.Html?.Length ?? 0))}...");
|
||||
handler.PlatformView.Html = htmlSource.Html ?? "";
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("[WebViewHandler] Unknown source type or null");
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapGoBack(WebViewHandler handler, IWebView webView, object? args)
|
||||
|
||||
Reference in New Issue
Block a user