[geary/mjog/558-webkit-shared-process-redux: 6/16] GearyWebExtension: Add support for sending messages from JS to client




commit a9d02c59e0c1b75fb2896ac9f7bfcb95451e6dbe
Author: Michael Gratton <mike vee net>
Date:   Fri Aug 28 09:49:46 2020 +1000

    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.

 src/client/components/components-web-view.vala    | 55 ++++++++++++++++++++
 src/client/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 2b3731702..c746c4410 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<ulong> registered_message_handlers =
         new Gee.LinkedList<ulong>();
+    private Gee.Map<string,MessageCallable> message_handlers =
+        new Gee.HashMap<string,MessageCallable>();
 
     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 89d9a1e34..31f2b0f0f 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<JSC.Value> 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 0f932a19c..35e82dfcb 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));
+    };
+};


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]