Skip to content

Commit 077719f

Browse files
authored
Merge pull request #72 from AvaloniaUI/fixes/wpe-script-callbacks
Fixes/wpe script callbacks
2 parents d2e05d0 + 9fc9e48 commit 077719f

8 files changed

Lines changed: 88 additions & 41 deletions

File tree

src/Avalonia.Controls.WebView.Core/Android/AndroidWebViewAdapter.cs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ namespace Avalonia.Controls.Android;
3030
internal class AndroidWebViewAdapter : IWebViewAdapterWithFocus, IWebViewAdapterWithInputRedirect,
3131
IWebViewAdapterWithCookieManager, IAndroidWebViewPlatformHandle, IWebViewWithPrintWithOptions
3232
{
33-
private const string PostAvWebViewMessageName = "postAvWebViewMessage";
3433
private static bool s_canSetDataDirectorySuffix = true;
3534
private readonly JavaScriptInterface _jsInterface;
3635
private WebView? _webView;
@@ -93,7 +92,7 @@ public AndroidWebViewAdapter(global::Android.Content.Context parentContext, Andr
9392
_webView.Settings.LoadWithOverviewMode = true;
9493
_webView.Settings.UseWideViewPort = true;
9594
}
96-
_webView.AddJavascriptInterface(_jsInterface, PostAvWebViewMessageName);
95+
_webView.AddJavascriptInterface(_jsInterface, WebViewHelper.PostAvWebViewMessageName);
9796
_webView.SetWebViewClient(new AvaloniaWebViewClient(this));
9897
_webView.SetWebChromeClient(new WebChromeClient());
9998

@@ -661,14 +660,7 @@ public override void OnPageFinished(WebView? view, string? url)
661660
return;
662661

663662
adapter._webView.EvaluateJavascript(
664-
"""
665-
function invokeCSharpAction(data)
666-
{
667-
var message = typeof data === 'object' ? JSON.stringify(data) : data;
668-
postAvWebViewMessage.postMessage(message);
669-
}
670-
"""
671-
, null);
663+
WebViewHelper.BuildInvokeCSharpActionScript(WebViewHelper.PostAvWebViewMessageName, stringify: true), null);
672664

673665
if (!_lastNavigationCompleted)
674666
{

src/Avalonia.Controls.WebView.Core/Gtk/GtkWebViewAdapter.cs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ namespace Avalonia.Controls.Gtk;
1616

1717
internal abstract class GtkWebViewAdapter : IWebViewAdapterWithFocus, IGtkWebViewPlatformHandle, IWebViewWithPrintWithOptions
1818
{
19-
private const string PostAvWebViewMessageName = "postAvWebViewMessage";
20-
2119
internal enum WebKitLoadEvent
2220
{
2321
Started,
@@ -111,17 +109,11 @@ protected GtkWebViewAdapter(GtkWebViewEnvironmentRequestedEventArgs args)
111109
_webViewHandle = webkit_web_view_new_with_context(context);
112110

113111
var contentManager = webkit_web_view_get_user_content_manager(WebViewHandle);
114-
_scriptMessageReceivedSignal = new GtkSignal(contentManager, $"script-message-received::{PostAvWebViewMessageName}", s_scriptMessageReceivedCallback, this);
115-
webkit_user_content_manager_register_script_message_handler(contentManager, PostAvWebViewMessageName);
112+
_scriptMessageReceivedSignal = new GtkSignal(contentManager, $"script-message-received::{WebViewHelper.PostAvWebViewMessageName}", s_scriptMessageReceivedCallback, this);
113+
webkit_user_content_manager_register_script_message_handler(contentManager, WebViewHelper.PostAvWebViewMessageName);
116114

117115
var script = webkit_user_script_new(
118-
$$"""
119-
function invokeCSharpAction(data)
120-
{
121-
var message = typeof data === 'object' ? JSON.stringify(data) : data;
122-
window.webkit.messageHandlers.{{PostAvWebViewMessageName}}.postMessage(message);
123-
}
124-
""",
116+
WebViewHelper.BuildWebKitInvokeCSharpActionScript(),
125117
0, 0, IntPtr.Zero, IntPtr.Zero);
126118
webkit_user_content_manager_add_script(contentManager, script);
127119

src/Avalonia.Controls.WebView.Core/Linux/Interop/WpeInterop.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,27 @@ public static partial void webkit_web_view_set_background_color(
137137
[LibraryImport(LibWpeWebKit)]
138138
public static partial IntPtr webkit_web_view_get_user_content_manager(IntPtr webView);
139139

140+
[LibraryImport(LibWpeWebKit)]
141+
public static partial IntPtr webkit_user_content_manager_get_type();
142+
143+
[LibraryImport(LibWpeWebKit)]
144+
public static partial IntPtr webkit_user_content_manager_new();
145+
140146
[LibraryImport(LibWpeWebKit, StringMarshalling = StringMarshalling.Utf8)]
141147
[return: MarshalAs(UnmanagedType.Bool)]
142148
public static partial bool webkit_user_content_manager_register_script_message_handler(
143149
IntPtr manager, string name, string? worldName);
144150

151+
[LibraryImport(LibWpeWebKit)]
152+
public static partial void webkit_user_content_manager_add_script(IntPtr manager, IntPtr userScript);
153+
154+
[LibraryImport(LibWpeWebKit, StringMarshalling = StringMarshalling.Utf8)]
155+
public static partial IntPtr webkit_user_script_new(
156+
string source, int injectedFrames, int injectionTime, IntPtr allowList, IntPtr blockList);
157+
158+
[LibraryImport(LibWpeWebKit)]
159+
public static partial void webkit_user_script_unref(IntPtr userScript);
160+
145161
[LibraryImport(LibWpeWebKit, StringMarshalling = StringMarshalling.Utf8)]
146162
public static partial void webkit_settings_set_user_agent(IntPtr settings, string? userAgent);
147163

src/Avalonia.Controls.WebView.Core/Linux/WpeWebViewAdapter.cs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ internal sealed unsafe class WpeWebViewAdapter
3333
private IntPtr _exportable;
3434
private IntPtr _viewBackend;
3535
private IntPtr _networkSession;
36+
private IntPtr _userContentManager;
3637
private IntPtr _cookieManager;
3738
private bool _exportableOwnedByWebKit; // true when webkit_web_view_backend_new took ownership
3839
private PixelSize _currentSize;
@@ -231,15 +232,32 @@ private void Initialize(LinuxWpeWebViewEnvironmentRequestedEventArgs args, Contr
231232
else
232233
_networkSession = WpeInterop.webkit_network_session_get_default();
233234

235+
_userContentManager = WpeInterop.webkit_user_content_manager_new();
236+
if (_userContentManager == IntPtr.Zero)
237+
throw new InvalidOperationException("webkit_user_content_manager_new failed.");
238+
234239
var webViewType = WpeInterop.webkit_web_view_get_type();
235240
var wkBackendType = WpeInterop.webkit_web_view_backend_get_type();
236241
var networkSessionType = WpeInterop.webkit_network_session_get_type();
237-
var keys = new[] { "backend", "network-session" };
238-
var values = new[] { new GValue(wkBackendType, wkBackend), new GValue(networkSessionType, _networkSession) };
239-
_webView = WpeInterop.g_object_new_with_properties(webViewType, 2, keys, values);
242+
var contentManagerType = WpeInterop.webkit_user_content_manager_get_type();
243+
var keys = new[] { "backend", "network-session", "user-content-manager" };
244+
var values = new[]
245+
{
246+
new GValue(wkBackendType, wkBackend),
247+
new GValue(networkSessionType, _networkSession),
248+
new GValue(contentManagerType, _userContentManager)
249+
};
250+
_webView = WpeInterop.g_object_new_with_properties(webViewType, 3, keys, values);
240251
if (_webView == IntPtr.Zero)
241252
throw new InvalidOperationException("webkit_web_view_new failed.");
242253

254+
var viewContentManager = WpeInterop.webkit_web_view_get_user_content_manager(_webView);
255+
if (viewContentManager != IntPtr.Zero && viewContentManager != _userContentManager)
256+
{
257+
WpeInterop.g_object_unref(_userContentManager);
258+
_userContentManager = WpeInterop.g_object_ref(viewContentManager);
259+
}
260+
243261
// 5. Start GLib pump (WebKit needs it for internal IPC)
244262
WpeGLibIntegration.Start();
245263

@@ -258,11 +276,17 @@ private void Initialize(LinuxWpeWebViewEnvironmentRequestedEventArgs args, Contr
258276
ConnectSignal(_webView, "decide-policy", Marshal.GetFunctionPointerForDelegate(_decidePolicyCallback), selfPtr);
259277
ConnectSignal(_webView, "create", Marshal.GetFunctionPointerForDelegate(_createCallback), selfPtr);
260278

261-
// 8. Register invokeCSharpAction message handler
262-
var contentManager = WpeInterop.webkit_web_view_get_user_content_manager(_webView);
263-
WpeInterop.webkit_user_content_manager_register_script_message_handler(contentManager, "invokeCSharpAction", null);
264-
ConnectSignal(contentManager, "script-message-received::invokeCSharpAction",
279+
// 8. Register the message handler, connecting before registering so that no message can arrive unhandled.
280+
ConnectSignal(_userContentManager, $"script-message-received::{WebViewHelper.PostAvWebViewMessageName}",
265281
Marshal.GetFunctionPointerForDelegate(_scriptMessageCallback), selfPtr);
282+
WpeInterop.webkit_user_content_manager_register_script_message_handler(
283+
_userContentManager, WebViewHelper.PostAvWebViewMessageName, null);
284+
285+
// Inject the invokeCSharpAction wrapper into all frames at document start.
286+
var bridgeScript = WpeInterop.webkit_user_script_new(
287+
WebViewHelper.BuildWebKitInvokeCSharpActionScript(), 0, 0, IntPtr.Zero, IntPtr.Zero);
288+
WpeInterop.webkit_user_content_manager_add_script(_userContentManager, bridgeScript);
289+
WpeInterop.webkit_user_script_unref(bridgeScript);
266290

267291
// 9. Apply settings
268292
var settings = WpeInterop.webkit_web_view_get_settings(_webView);
@@ -1019,6 +1043,12 @@ public void Dispose()
10191043
_webView = IntPtr.Zero;
10201044
}
10211045

1046+
if (_userContentManager != IntPtr.Zero)
1047+
{
1048+
WpeInterop.g_object_unref(_userContentManager);
1049+
_userContentManager = IntPtr.Zero;
1050+
}
1051+
10221052
if (!_exportableOwnedByWebKit && _exportable != IntPtr.Zero)
10231053
{
10241054
WpeInterop.wpe_view_backend_exportable_fdo_destroy(_exportable);

src/Avalonia.Controls.WebView.Core/Macios/MaciosWebViewAdapter.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ namespace Avalonia.Controls.Macios;
2121
internal class MaciosWebViewAdapter : IWebViewAdapterWithFocus, IWebViewAdapterWithInputRedirect,
2222
IWebViewAdapterWithCookieManager, IWebViewAdapterWithCommands, IWebViewWithPrint, IAppleWKWebViewPlatformHandle
2323
{
24-
private const string DefaultPostAvWebViewMessageName = "postAvWebViewMessage";
25-
2624
private readonly string _scriptHandlerMessageName;
2725
private readonly NSString _scriptHandlerMessageNameNative;
2826
private readonly WKWebViewConfiguration _config;
@@ -40,7 +38,7 @@ public MaciosWebViewAdapter(AppleWKWebViewEnvironmentRequestedEventArgs options)
4038
_scriptHandler = new WKScriptMessageHandler();
4139
_scriptHandler.DidReceiveScriptMessage += OnScriptHandlerOnDidReceiveScriptMessage;
4240

43-
_scriptHandlerMessageName = options.ScriptHandlerMessageName ?? DefaultPostAvWebViewMessageName;
41+
_scriptHandlerMessageName = options.ScriptHandlerMessageName ?? WebViewHelper.PostAvWebViewMessageName;
4442
_scriptHandlerMessageNameNative = NSString.Create(_scriptHandlerMessageName);
4543
_config = new WKWebViewConfiguration { JavaScriptEnabled = true };
4644
_config.AddScriptMessageHandler(_scriptHandler, _scriptHandlerMessageNameNative);
@@ -297,7 +295,7 @@ private void OnDelegateOnDecidePolicyNavigation(object? _, WKNavigationDelegate.
297295

298296
private async void OnDelegateOnDidFinishNavigation(object? sender, EventArgs args)
299297
{
300-
_ = await InvokeScript($"function invokeCSharpAction(data){{window.webkit.messageHandlers.{_scriptHandlerMessageName}.postMessage(data);}}");
298+
_ = await InvokeScript(WebViewHelper.BuildWebKitInvokeCSharpActionScript(_scriptHandlerMessageName, stringify: true));
301299

302300
using var url = _webView.Url;
303301
NavigationCompleted?.Invoke(this, new WebViewNavigationCompletedEventArgs { Request = Uri.TryCreate(url!.AbsoluteString, UriKind.Absolute, out var uri) ? uri : null, IsSuccess = true });

src/Avalonia.Controls.WebView.Core/WebViewHelper.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,33 @@ namespace Avalonia.Controls;
44

55
internal static class WebViewHelper
66
{
7+
internal const string PostAvWebViewMessageName = "postAvWebViewMessage";
8+
79
public static Uri EmptyPage { get; } = new("about:blank");
810

11+
internal static string BuildWebKitInvokeCSharpActionScript(
12+
string messageName = PostAvWebViewMessageName, bool stringify = true) =>
13+
BuildInvokeCSharpActionScript("window.webkit.messageHandlers." + messageName, stringify: stringify);
14+
15+
/// <param name="postObject">Target object to send message to.</param>
16+
/// <param name="postMethod">Method on the <see cref="postObject"/> that should be invoked to pass the message.</param>
17+
/// <param name="stringify">
18+
/// Defines if post data should be JSON serialized,
19+
/// some backends do that automatically when marshall objects to the C# handlers.
20+
/// </param>
21+
internal static string BuildInvokeCSharpActionScript(string postObject,
22+
string postMethod = "postMessage", bool stringify = true)
23+
{
24+
return stringify ?
25+
"function invokeCSharpAction(data){" +
26+
"var message = typeof data === 'object' ? JSON.stringify(data) : data;" +
27+
$"{postObject}.{postMethod}(message);" +
28+
"}" :
29+
"function invokeCSharpAction(data){" +
30+
$"{postObject}.{postMethod}(data);" +
31+
"}";
32+
}
33+
934
internal static bool IsAnchorNavigation(Uri? currentUrl, Uri? newUrl)
1035
{
1136
if (currentUrl is null || newUrl is null)

src/Avalonia.Controls.WebView.Core/Win/WebView1/WebViewCallbacks.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,8 @@ async void InitScript()
4545
{
4646
try
4747
{
48-
var initScript =
49-
"""
50-
window.invokeCSharpAction = function(data) {
51-
var message = typeof data === 'object' ? JSON.stringify(data) : data;
52-
window.external.notify(message);
53-
};
54-
""";
55-
await adapter.InvokeScript(initScript);
48+
await adapter.InvokeScript(WebViewHelper.BuildInvokeCSharpActionScript(
49+
"window.external", postMethod: "notify", stringify: true));
5650
}
5751
catch (Exception ex)
5852
{

src/Avalonia.Controls.WebView.Core/Win/WebView2/WebView2BaseAdapter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ public async Task InitializeAsync(WindowsWebView2EnvironmentRequestedEventArgs e
297297
var addScriptCompletion = new AddScriptToExecuteOnDocumentCreatedCompletedHandler();
298298
var webView = TryGetWebView2() ?? throw new InvalidOperationException("WebView2 is not initialized.");
299299
webView.AddScriptToExecuteOnDocumentCreated(
300-
"function invokeCSharpAction(data){window.chrome.webview.postMessage(data);}", addScriptCompletion);
300+
WebViewHelper.BuildInvokeCSharpActionScript("window.chrome.webview", stringify: false), addScriptCompletion);
301301
_ = await addScriptCompletion.Result.Task;
302302

303303
controller.SetIsVisible(1);

0 commit comments

Comments
 (0)