From 6162785d997fcfa4efaf6ec83670b2fab8cca6bd Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Fri, 28 Aug 2020 09:49:46 +1000 Subject: [PATCH 073/124] GearyWebExtension: Add support for sending messages from JS to client Define a vala-backed JS class in the extension and make that available to pages when they are registered. Add some helper JS to PageState for defining message sending functions. Listen for these in Components.WebView and dispatch to the registered callback for it. --- .../components/components-web-view.vala | 55 ++++++++++++++++ .../web-process/web-process-extension.vala | 63 +++++++++++++++++++ ui/components-web-view.js | 9 +++ 3 files changed, 127 insertions(+) diff --git a/src/client/components/components-web-view.vala b/src/client/components/components-web-view.vala index 2b373170..c746c441 100644 --- a/src/client/components/components-web-view.vala +++ b/src/client/components/components-web-view.vala @@ -198,6 +198,24 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface { /** Delegate for UserContentManager message callbacks. */ public delegate void JavaScriptMessageHandler(WebKit.JavascriptResult js_result); + /** + * Delegate for message handler callbacks. + * + * @see register_message_callback + */ + protected delegate void MessageCallback(GLib.Variant? parameters); + + // Work around for not being able to put delegates in a Gee collection. + private class MessageCallable { + + public unowned MessageCallback handler; + + public MessageCallable(MessageCallback handler) { + this.handler = handler; + } + + } + /** * Determines if the view's content has been fully loaded. * @@ -263,6 +281,8 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface { private Gee.List registered_message_handlers = new Gee.LinkedList(); + private Gee.Map message_handlers = + new Gee.HashMap(); private double webkit_reported_height = 0; @@ -359,6 +379,7 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface { this.user_content_manager.disconnect(id); } this.registered_message_handlers.clear(); + this.message_handlers.clear(); base.destroy(); } @@ -568,6 +589,14 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface { } } + /** + * Registers a callback for a specific WebKit user message. + */ + protected void register_message_callback(string name, + MessageCallback handler) { + this.message_handlers.set(name, new MessageCallable(handler)); + } + private void init(Application.Configuration config) { // XXX get the allow prefix from the extension somehow @@ -595,6 +624,8 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface { SELECTION_CHANGED, on_selection_changed ); + this.user_message_received.connect(this.on_message_received); + // Manage zoom level, ensure it's sane config.bind(Application.Configuration.CONVERSATION_VIEWER_ZOOM_KEY, this, "zoom_level"); if (this.zoom_level < ZOOM_MIN) { @@ -803,6 +834,30 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface { } } + private bool on_message_received(WebKit.UserMessage message) { + if (message.name == MESSAGE_EXCEPTION_NAME) { + var detail = new GLib.VariantDict(message.parameters); + var name = detail.lookup_value("name", GLib.VariantType.STRING) as string; + var log_message = detail.lookup_value("message", GLib.VariantType.STRING) as string; + warning( + "Error sending message from JS: %s: %s", + name ?? "unknown", + log_message ?? "unknown" + ); + } else if (this.message_handlers.has_key(message.name)) { + debug( + "Message received: %s(%s)", + message.name, + message.parameters != null ? message.parameters.print(true) : "" + ); + MessageCallable callback = this.message_handlers.get(message.name); + callback.handler(message.parameters); + } else { + warning("Message with unknown handler received: %s", message.name); + } + return true; + } + } // XXX this needs to be moved into the libsoup bindings diff --git a/src/client/web-process/web-process-extension.vala b/src/client/web-process/web-process-extension.vala index 89d9a1e3..31f2b0f0 100644 --- a/src/client/web-process/web-process-extension.vala +++ b/src/client/web-process/web-process-extension.vala @@ -38,6 +38,8 @@ public class GearyWebExtension : Object { private const string[] ALLOWED_SCHEMES = { "cid", "geary", "data", "blob" }; + private const string EXTENSION_CLASS_VAR = "_GearyWebExtension"; + private const string EXTENSION_CLASS_SEND = "send"; private const string REMOTE_LOAD_VAR = "_gearyAllowRemoteResourceLoads"; private WebKit.WebExtension extension; @@ -180,6 +182,25 @@ public class GearyWebExtension : Object { WebKit.WebPage page) { WebKit.Frame frame = page.get_main_frame(); JSC.Context context = frame.get_js_context(); + + var extension_class = context.register_class( + this.get_type().name(), + null, + null, + null + ); + extension_class.add_method( + EXTENSION_CLASS_SEND, + (instance, values) => { + return this.on_page_send_message(page, values); + }, + GLib.Type.NONE + ); + context.set_value( + EXTENSION_CLASS_VAR, + new JSC.Value.object(context, extension_class, extension_class) + ); + context.set_value( REMOTE_LOAD_VAR, new JSC.Value.boolean(context, false) @@ -259,4 +280,46 @@ public class GearyWebExtension : Object { return true; } + private bool on_page_send_message(WebKit.WebPage page, + GLib.GenericArray args) { + WebKit.UserMessage? message = null; + if (args.length > 0) { + var name = args.get(0).to_string(); + GLib.Variant? parameters = null; + if (args.length > 1) { + JSC.Value param_value = args.get(1); + try { + int len = Util.JS.to_int32( + param_value.object_get_property("length") + ); + if (len == 1) { + parameters = Util.JS.value_to_variant( + param_value.object_get_property_at_index(0) + ); + } else if (len > 1) { + parameters = Util.JS.value_to_variant(param_value); + } + } catch (Util.JS.Error err) { + message = to_exception_message( + this.get_type().name(), err.message + ); + } + } + if (message == null) { + message = new WebKit.UserMessage(name, parameters); + } + } + if (message == null) { + var log_message = "Not enough parameters for JS call to %s.%s()".printf( + EXTENSION_CLASS_VAR, + EXTENSION_CLASS_SEND + ); + debug(log_message); + message = to_exception_message(this.get_type().name(), log_message); + } + + page.send_message_to_view.begin(message, null); + return true; + } + } diff --git a/ui/components-web-view.js b/ui/components-web-view.js index 0f932a19..35e82dfc 100644 --- a/ui/components-web-view.js +++ b/ui/components-web-view.js @@ -200,3 +200,12 @@ PageState.prototype = { throw this.testResult; } }; + +let MessageSender = function(name) { + return function() { + // Since typeof(arguments) == 'object', convert to an array so + // that Components.WebView.MessageCallback callbacks get + // arrays or tuples rather than dicts as arguments + _GearyWebExtension.send(name, Array.from(arguments)); + }; +}; -- 2.29.2