From bf2f380f568abbae6e8f6a5cf2387e2cf6dfe7b5 Mon Sep 17 00:00:00 2001 From: logikonline Date: Fri, 16 Jan 2026 05:40:49 +0000 Subject: [PATCH] WebView completed --- Handlers/WebViewHandler.cs | 65 ++++++++++++++++++++++++++++ Views/SkiaWebView.cs | 88 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) diff --git a/Handlers/WebViewHandler.cs b/Handlers/WebViewHandler.cs index a2300ec..ee46621 100644 --- a/Handlers/WebViewHandler.cs +++ b/Handlers/WebViewHandler.cs @@ -15,6 +15,7 @@ public partial class WebViewHandler : ViewHandler public static IPropertyMapper Mapper = new PropertyMapper(ViewHandler.ViewMapper) { [nameof(IWebView.Source)] = MapSource, + [nameof(IWebView.UserAgent)] = MapUserAgent, }; public static CommandMapper CommandMapper = new(ViewHandler.ViewCommandMapper) @@ -22,6 +23,8 @@ public partial class WebViewHandler : ViewHandler [nameof(IWebView.GoBack)] = MapGoBack, [nameof(IWebView.GoForward)] = MapGoForward, [nameof(IWebView.Reload)] = MapReload, + [nameof(IWebView.Eval)] = MapEval, + [nameof(IWebView.EvaluateJavaScriptAsync)] = MapEvaluateJavaScriptAsync, }; public WebViewHandler() : base(Mapper, CommandMapper) @@ -127,4 +130,66 @@ public partial class WebViewHandler : ViewHandler { handler.PlatformView?.Reload(); } + + public static void MapUserAgent(WebViewHandler handler, IWebView webView) + { + if (handler.PlatformView != null && !string.IsNullOrEmpty(webView.UserAgent)) + { + handler.PlatformView.UserAgent = webView.UserAgent; + } + } + + public static void MapEval(WebViewHandler handler, IWebView webView, object? args) + { + if (args is string script) + { + handler.PlatformView?.Eval(script); + } + } + + public static void MapEvaluateJavaScriptAsync(WebViewHandler handler, IWebView webView, object? args) + { + // Handle EvaluateJavaScriptAsyncRequest from Microsoft.Maui.Platform namespace + if (args is EvaluateJavaScriptAsyncRequest request) + { + var result = handler.PlatformView?.EvaluateJavaScriptAsync(request.Script); + if (result != null) + { + result.ContinueWith(t => + { + request.SetResult(t.Result); + }); + } + else + { + request.SetResult(null); + } + } + else if (args is string script) + { + // Direct script string + handler.PlatformView?.EvaluateJavaScriptAsync(script); + } + } +} + +/// +/// Request object for async JavaScript evaluation (matches Microsoft.Maui.Platform.EvaluateJavaScriptAsyncRequest). +/// +public class EvaluateJavaScriptAsyncRequest +{ + public string Script { get; } + private readonly System.Threading.Tasks.TaskCompletionSource _tcs = new(); + + public EvaluateJavaScriptAsyncRequest(string script) + { + Script = script; + } + + public System.Threading.Tasks.Task Task => _tcs.Task; + + public void SetResult(string? result) + { + _tcs.TrySetResult(result); + } } diff --git a/Views/SkiaWebView.cs b/Views/SkiaWebView.cs index c191310..04f38c9 100644 --- a/Views/SkiaWebView.cs +++ b/Views/SkiaWebView.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Net; using System.Runtime.InteropServices; using SkiaSharp; @@ -33,6 +34,9 @@ public class SkiaWebView : SkiaView private delegate void WebKitSettingsSetEnableJavascriptDelegate(IntPtr settings, bool enabled); private delegate void WebKitSettingsSetHardwareAccelerationPolicyDelegate(IntPtr settings, int policy); private delegate void WebKitSettingsSetEnableWebglDelegate(IntPtr settings, bool enabled); + private delegate void WebKitSettingsSetUserAgentDelegate(IntPtr settings, [MarshalAs(UnmanagedType.LPStr)] string userAgent); + private delegate IntPtr WebKitSettingsGetUserAgentDelegate(IntPtr settings); + private delegate void WebKitWebViewRunJavascriptDelegate(IntPtr webView, [MarshalAs(UnmanagedType.LPStr)] string script, IntPtr cancellable, IntPtr callback, IntPtr userData); #endregion @@ -109,6 +113,9 @@ public class SkiaWebView : SkiaView private static WebKitSettingsSetEnableJavascriptDelegate? _webkitSetJavascript; private static WebKitSettingsSetHardwareAccelerationPolicyDelegate? _webkitSetHardwareAcceleration; private static WebKitSettingsSetEnableWebglDelegate? _webkitSetWebgl; + private static WebKitSettingsSetUserAgentDelegate? _webkitSetUserAgent; + private static WebKitSettingsGetUserAgentDelegate? _webkitGetUserAgent; + private static WebKitWebViewRunJavascriptDelegate? _webkitRunJavascript; private static readonly List _activeWebViews = new(); private static readonly Dictionary _webViewInstances = new(); @@ -127,6 +134,8 @@ public class SkiaWebView : SkiaView private bool _isEmbedded; private bool _isProperlyReparented; private bool _javascriptEnabled = true; + private string? _userAgent; + private CookieContainer _cookies = new(); private double _loadProgress; private SKRect _lastBounds; private int _lastMainX; @@ -447,6 +456,38 @@ public class SkiaWebView : SkiaView public double LoadProgress => _loadProgress; + public string? UserAgent + { + get + { + if (!string.IsNullOrEmpty(_userAgent)) + return _userAgent; + if (_webView == IntPtr.Zero || _webkitGetSettings == null || _webkitGetUserAgent == null) + return null; + var settings = _webkitGetSettings(_webView); + if (settings == IntPtr.Zero) return null; + var ptr = _webkitGetUserAgent(settings); + return ptr != IntPtr.Zero ? Marshal.PtrToStringAnsi(ptr) : null; + } + set + { + _userAgent = value; + UpdateUserAgentSetting(); + } + } + + /// + /// Gets or sets the cookie container for this WebView. + /// Note: Cookies set here are available for .NET code but are not automatically + /// synchronized with WebKitGTK's internal cookie storage. For full cookie + /// integration, use JavaScript-based cookie operations. + /// + public CookieContainer Cookies + { + get => _cookies; + set => _cookies = value ?? new CookieContainer(); + } + public static bool IsSupported => InitializeWebKit(); #endregion @@ -534,6 +575,9 @@ public class SkiaWebView : SkiaView _webkitSetJavascript = LoadFunction("webkit_settings_set_enable_javascript"); _webkitSetHardwareAcceleration = LoadFunction("webkit_settings_set_hardware_acceleration_policy"); _webkitSetWebgl = LoadFunction("webkit_settings_set_enable_webgl"); + _webkitSetUserAgent = LoadFunction("webkit_settings_set_user_agent"); + _webkitGetUserAgent = LoadFunction("webkit_settings_get_user_agent"); + _webkitRunJavascript = LoadFunction("webkit_web_view_run_javascript"); Console.WriteLine($"[WebView] Using {_webkitLib}"); return _webkitWebViewNew != null; @@ -663,6 +707,7 @@ public class SkiaWebView : SkiaView ConfigureWebKitSettings(); UpdateJavaScriptSetting(); + UpdateUserAgentSetting(); _isInitialized = true; lock (_activeWebViews) @@ -740,6 +785,18 @@ public class SkiaWebView : SkiaView } } + private void UpdateUserAgentSetting() + { + if (_webView == IntPtr.Zero || _webkitGetSettings == null || _webkitSetUserAgent == null) return; + if (string.IsNullOrEmpty(_userAgent)) return; + + var settings = _webkitGetSettings(_webView); + if (settings != IntPtr.Zero) + { + _webkitSetUserAgent(settings, _userAgent); + } + } + #endregion #region Navigation @@ -802,6 +859,37 @@ public class SkiaWebView : SkiaView _webkitStopLoading?.Invoke(_webView); } + /// + /// Evaluates JavaScript in the WebView. This is a fire-and-forget operation. + /// For MAUI compatibility - use EvaluateJavaScriptAsync for async results. + /// + public void Eval(string script) + { + if (_webView == IntPtr.Zero || _webkitRunJavascript == null || string.IsNullOrEmpty(script)) return; + _webkitRunJavascript(_webView, script, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + } + + /// + /// Evaluates JavaScript in the WebView asynchronously. + /// Note: WebKitGTK async result handling is complex - this implementation + /// executes the script but returns null. Full async result support would + /// require GAsyncReadyCallback integration. + /// + public System.Threading.Tasks.Task EvaluateJavaScriptAsync(string script) + { + if (_webView == IntPtr.Zero || _webkitRunJavascript == null || string.IsNullOrEmpty(script)) + { + return System.Threading.Tasks.Task.FromResult(null); + } + + // Execute the JavaScript + _webkitRunJavascript(_webView, script, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + + // Note: Full async result handling would require GAsyncReadyCallback + // For now, we execute and return null (script side effects still work) + return System.Threading.Tasks.Task.FromResult(null); + } + #endregion #region Event Processing