[epiphany/pgriffis/web-extension-gtask: 7/15] WebExtensions: Rewrite message handling to be async friendly




commit f9aa9a979077a3e1d8c51b8fafec598ee92ef69c
Author: Patrick Griffis <pgriffis igalia com>
Date:   Tue May 24 17:03:09 2022 -0500

    WebExtensions: Rewrite message handling to be async friendly
    
    Part-of: <https://gitlab.gnome.org/GNOME/epiphany/-/merge_requests/1119>

 .../web-process-extension/ephy-webextension-api.c  | 111 ++++++++++++++++++++-
 .../resources/js/webextensions-common.js           |  14 +--
 src/webextension/ephy-web-extension-manager.c      |  80 +++++++--------
 3 files changed, 148 insertions(+), 57 deletions(-)
---
diff --git a/embed/web-process-extension/ephy-webextension-api.c 
b/embed/web-process-extension/ephy-webextension-api.c
index 3b9cb67d7..bdcf38cda 100644
--- a/embed/web-process-extension/ephy-webextension-api.c
+++ b/embed/web-process-extension/ephy-webextension-api.c
@@ -136,6 +136,101 @@ ephy_web_extension_extension_get (void)
   return EPHY_WEB_EXTENSION_EXTENSION (g_once (&once_init, ephy_web_extension_extension_create_instance, 
NULL));
 }
 
+typedef struct {
+  JSCValue *resolve_callback;
+  JSCValue *reject_callback;
+} EphyMessageData;
+
+static void
+ephy_message_data_free (EphyMessageData *data)
+{
+  g_object_unref (data->reject_callback);
+  g_object_unref (data->resolve_callback);
+  g_free (data);
+}
+
+static EphyMessageData *
+ephy_message_data_new (JSCValue *resolve_callback,
+                       JSCValue *reject_callback)
+{
+  EphyMessageData *data = g_new (EphyMessageData, 1);
+  data->resolve_callback = g_object_ref (resolve_callback);
+  data->reject_callback = g_object_ref (reject_callback);
+  return data;
+}
+
+static void
+on_send_message_finish (WebKitWebExtension *extension,
+                        GAsyncResult       *result,
+                        gpointer            user_data)
+{
+  GTask *task = user_data;
+  g_autoptr (GError) error = NULL;
+  g_autoptr (WebKitUserMessage) response = NULL;
+  GVariant *params;
+
+  response = webkit_web_extension_send_message_to_context_finish (extension, result, &error);
+
+  if (error) {
+    g_task_return_error (task, g_steal_pointer (&error));
+  } else if (strcmp (webkit_user_message_get_name (response), "error") == 0) {
+    params = webkit_user_message_get_parameters (response);
+    g_assert (params);
+    error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, "%s", g_variant_get_string (params, NULL));
+    g_task_return_error (task, g_steal_pointer (&error));
+  } else {
+    params = webkit_user_message_get_parameters (response);
+    g_assert (params);
+    g_task_return_pointer (task, g_variant_dup_string (params, NULL), g_free);
+  }
+
+  g_object_unref (task);
+}
+
+static void
+on_ephy_message_finish (EphyWebExtensionExtension *extension,
+                        GAsyncResult              *result,
+                        gpointer                   user_data)
+{
+  g_autoptr (GError) error = NULL;
+  EphyMessageData *message_data = g_task_get_task_data (G_TASK (result));
+  g_autofree char *json = g_task_propagate_pointer (G_TASK (result), &error);
+  g_autoptr (JSCValue) value = jsc_value_new_from_json (jsc_value_get_context 
(message_data->resolve_callback), json);
+  g_autoptr (JSCValue) ret = NULL;
+
+  if (error) {
+    ret = jsc_value_function_call (message_data->reject_callback, G_TYPE_STRING, error->message, 
G_TYPE_NONE);
+  } else {
+    ret = jsc_value_function_call (message_data->resolve_callback, JSC_TYPE_VALUE, value, G_TYPE_NONE);
+  }
+}
+
+static void
+ephy_send_message (const char *function_name,
+                   JSCValue   *function_args,
+                   JSCValue   *resolve_callback,
+                   JSCValue   *reject_callback,
+                   gpointer    user_data)
+{
+  EphyWebExtensionExtension *extension = user_data;
+  WebKitUserMessage *message;
+  EphyMessageData *data;
+  GTask *task;
+  char *args_json;
+
+  /* TODO: If function_args is list and last arg is callable, treat it as `chrome` API. */
+  /* TODO: Check for valid types for args, resolve, reject. */
+
+  task = g_task_new (extension, NULL, (GAsyncReadyCallback)on_ephy_message_finish, NULL);
+  data = ephy_message_data_new (resolve_callback, reject_callback);
+  g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify)ephy_message_data_free);
+
+  args_json = jsc_value_to_json (function_args, 0);
+  message = webkit_user_message_new (function_name, g_variant_new_take_string (args_json));
+
+  webkit_web_extension_send_message_to_context (extension->extension, message, NULL, 
(GAsyncReadyCallback)on_send_message_finish, task);
+}
+
 static void
 window_object_cleared_cb (WebKitScriptWorld         *world,
                           WebKitWebPage             *page,
@@ -157,6 +252,19 @@ window_object_cleared_cb (WebKitScriptWorld         *world,
   js_browser = jsc_context_get_value (js_context, "browser");
   g_assert (!jsc_value_is_object (js_browser));
 
+  js_function = jsc_value_new_function (js_context,
+                                        "ephy_send_message",
+                                        G_CALLBACK (ephy_send_message),
+                                        extension, NULL,
+                                        G_TYPE_NONE,
+                                        4,
+                                        G_TYPE_STRING,
+                                        JSC_TYPE_VALUE,
+                                        JSC_TYPE_VALUE,
+                                        JSC_TYPE_VALUE);
+  jsc_context_set_value (js_context, "ephy_send_message", js_function);
+  g_clear_object (&js_function);
+
   bytes = g_resources_lookup_data ("/org/gnome/epiphany-web-extension/js/webextensions-common.js", 
G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
   data = g_bytes_get_data (bytes, &data_size);
   result = jsc_context_evaluate_with_source_uri (js_context, data, data_size, 
"resource:///org/gnome/epiphany-web-extension/js/webextensions-common.js", 1);
@@ -213,14 +321,13 @@ ephy_web_extension_extension_initialize (EphyWebExtensionExtension *extension,
 
   extension->initialized = TRUE;
   extension->guid = g_strdup (guid);
+  extension->extension = g_object_ref (wk_extension);
 
   g_signal_connect (webkit_script_world_get_default (),
                     "window-object-cleared",
                     G_CALLBACK (window_object_cleared_cb),
                     extension);
 
-  extension->extension = g_object_ref (wk_extension);
-
   ephy_web_extension_extension_update_translations (extension, translations);
 
   g_signal_connect_swapped (extension->extension, "page-created",
diff --git a/embed/web-process-extension/resources/js/webextensions-common.js 
b/embed/web-process-extension/resources/js/webextensions-common.js
index f4417b3e3..5c2820d56 100644
--- a/embed/web-process-extension/resources/js/webextensions-common.js
+++ b/embed/web-process-extension/resources/js/webextensions-common.js
@@ -1,12 +1,10 @@
 'use strict';
 
-/* exported runtimeSendMessage, runtimeOnConnect, ephy_message */
+/* exported ephy_message */
+/* global ephy_send_message */
 
 window.browser = {};
 
-const promises = [];
-let last_promise = 0;
-
 class EphyEventListener {
     constructor () {
         this._listeners = [];
@@ -30,12 +28,10 @@ class EphyEventListener {
     }
 }
 
-const ephy_message = function (fn, args, cb) {
-    const promise = new Promise (function (resolve, reject) {
-        window.webkit.messageHandlers.epiphany.postMessage ({fn: fn, args: args, promise: last_promise});
-        last_promise = promises.push({resolve: resolve, reject: reject});
+const ephy_message = function (fn, ...args) {
+    return new Promise (function (resolve, reject) {
+        ephy_send_message (fn, args, resolve, reject);
     });
-    return promise;
 };
 
 window.browser.runtime = {
diff --git a/src/webextension/ephy-web-extension-manager.c b/src/webextension/ephy-web-extension-manager.c
index d51473e07..2ea6b95e1 100644
--- a/src/webextension/ephy-web-extension-manager.c
+++ b/src/webextension/ephy-web-extension-manager.c
@@ -438,66 +438,61 @@ create_page_action_widget (EphyWebExtensionManager *self,
   return g_object_ref (event_box);
 }
 
-typedef struct {
-  EphyWebExtension *web_extension;
-  WebKitWebView *web_view;
-} ScriptMessageData;
-
 static void
-ephy_web_extension_handle_script_message (WebKitUserContentManager *ucm,
-                                          WebKitJavascriptResult   *js_result,
-                                          gpointer                  user_data)
+respond_with_error (WebKitUserMessage *message,
+                    const char        *error)
+{
+  WebKitUserMessage *reply = webkit_user_message_new ("error", g_variant_new_string (error));
+  webkit_user_message_send_reply (message, reply);
+}
+
+static gboolean
+ephy_web_extension_handle_user_message (WebKitWebContext  *context,
+                                        WebKitUserMessage *message,
+                                        gpointer           user_data)
 {
-  ScriptMessageData *data = user_data;
-  EphyWebExtension *web_extension = data->web_extension;
-  WebKitWebView *web_view = data->web_view;
-  JSCValue *value = webkit_javascript_result_get_js_value (js_result);
-  g_autofree char *name_str = NULL;
-  g_autoptr (JSCValue) name = NULL;
-  g_autoptr (JSCValue) promise = NULL;
+  EphyWebExtension *web_extension = user_data;
+  g_autoptr (JSCContext) js_context = NULL;
+  g_autoptr (JSCValue) args = NULL;
+  const char *name = webkit_user_message_get_name (message);
   g_auto (GStrv) split = NULL;
-  unsigned int idx;
 
-  if (!jsc_value_is_object (value))
-    return;
 
-  if (!jsc_value_object_has_property (value, "promise"))
-    return;
 
-  promise = jsc_value_object_get_property (value, "promise");
-  if (!jsc_value_is_number (promise))
-    return;
-
-  name = jsc_value_object_get_property (value, "fn");
-  if (!name)
-    return;
+  js_context = jsc_context_new ();
+  args = jsc_value_new_from_json (js_context, g_variant_get_string (webkit_user_message_get_parameters 
(message), NULL));
 
-  name_str = jsc_value_to_string (name);
-  LOG ("%s(): Called for %s, function %s\n", __FUNCTION__, ephy_web_extension_get_name (web_extension), 
name_str);
+  LOG ("%s(): Called for %s, function %s (%s)\n", __FUNCTION__, ephy_web_extension_get_name (web_extension), 
name, g_variant_get_string (webkit_user_message_get_parameters (message), NULL));
 
-  split = g_strsplit (name_str, ".", 2);
+  split = g_strsplit (name, ".", 2);
   if (g_strv_length (split) != 2) {
-    g_warning ("Invalid function call, aborting: %s", name_str);
-    return;
+    respond_with_error (message, "Invalid function name");
+    return TRUE;
   }
 
-  for (idx = 0; idx < G_N_ELEMENTS (api_handlers); idx++) {
+  for (guint idx = 0; idx < G_N_ELEMENTS (api_handlers); idx++) {
     EphyWebExtensionApiHandler handler = api_handlers[idx];
 
     if (g_strcmp0 (handler.name, split[0]) == 0) {
       g_autofree char *ret = NULL;
       g_autofree char *script = NULL;
-      g_autoptr (JSCValue) args = jsc_value_object_get_property (value, "args");
+      WebKitUserMessage *reply;
 
       ret = handler.execute (web_extension, split[1], args);
-      script = g_strdup_printf ("promises[%.f].resolve(%s);", jsc_value_to_double (promise), ret ? ret : "");
-      webkit_web_view_run_javascript (web_view, script, NULL, NULL, NULL);
+      if (ret) {
+        reply = webkit_user_message_new ("", g_variant_new_take_string (g_steal_pointer (&ret)));
+      } else {
+        reply = webkit_user_message_new ("", g_variant_new_string ("{}"));
+      }
 
-      return;
+      webkit_user_message_send_reply (message, reply);
+      return TRUE;
     }
   }
 
-  g_warning ("%s(): '%s' not implemented by Epiphany!", __FUNCTION__, name_str);
+  g_warning ("%s(): '%s' not implemented by Epiphany!", __FUNCTION__, name);
+  respond_with_error (message, "Not implemented");
+  return TRUE;
 }
 
 static void
@@ -688,7 +683,6 @@ create_web_extensions_webview (EphyWebExtension *web_extension)
   WebKitSettings *settings;
   WebKitWebContext *web_context;
   GtkWidget *web_view;
-  ScriptMessageData *data;
 
   /* Create an own ucm so new scripts/css are only applied to this web_view */
   ucm = webkit_user_content_manager_new ();
@@ -699,6 +693,7 @@ create_web_extensions_webview (EphyWebExtension *web_extension)
                                                          "ephy-webextension");
 
   g_signal_connect_object (web_context, "initialize-web-extensions", G_CALLBACK (init_web_extension_api), 
web_extension, 0);
+  g_signal_connect (web_context, "user-message-received", G_CALLBACK 
(ephy_web_extension_handle_user_message), web_extension);
 
   web_view = g_object_new (WEBKIT_TYPE_WEB_VIEW,
                            "web-context", web_context,
@@ -710,13 +705,6 @@ create_web_extensions_webview (EphyWebExtension *web_extension)
   settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (web_view));
   webkit_settings_set_enable_write_console_messages_to_stdout (settings, TRUE);
 
-  data = g_new0 (ScriptMessageData, 1);
-  data->web_extension = web_extension;
-  data->web_view = WEBKIT_WEB_VIEW (web_view);
-
-  g_signal_connect_data (ucm, "script-message-received::epiphany", G_CALLBACK 
(ephy_web_extension_handle_script_message), data, (GClosureNotify)g_free, 0);
-  webkit_user_content_manager_register_script_message_handler (ucm, "epiphany");
-
   return web_view;
 }
 


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