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)); + }; +};