[epiphany/pgriffis/web-extension/runtime-send-message: 4/4] WIP: Implement messages replies to runtime.sendMessage()
- From: Patrick Griffis <pgriffis src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [epiphany/pgriffis/web-extension/runtime-send-message: 4/4] WIP: Implement messages replies to runtime.sendMessage()
- Date: Sun, 29 May 2022 17:20:08 +0000 (UTC)
commit f692ceec2b63b77c1c3519f17c11b5120bf1e138
Author: Patrick Griffis <pgriffis igalia com>
Date: Sun May 29 12:18:55 2022 -0500
WIP: Implement messages replies to runtime.sendMessage()
This is a rough draft and currently causes a crash.
.../web-process-extension/ephy-webextension-api.c | 2 +-
.../resources/js/webextensions-common.js | 20 +++
src/webextension/api/runtime.c | 47 ++++--
src/webextension/ephy-web-extension-manager.c | 171 +++++++++++++++++++--
src/webextension/ephy-web-extension-manager.h | 9 ++
5 files changed, 221 insertions(+), 28 deletions(-)
---
diff --git a/embed/web-process-extension/ephy-webextension-api.c
b/embed/web-process-extension/ephy-webextension-api.c
index 2448376f5..362848f9b 100644
--- a/embed/web-process-extension/ephy-webextension-api.c
+++ b/embed/web-process-extension/ephy-webextension-api.c
@@ -208,7 +208,7 @@ ephy_send_message (const char *function_name,
return; /* Can't reject in this case. */
if (!jsc_value_is_array (function_args) || !jsc_value_is_function (resolve_callback)) {
- g_autoptr (JSCValue) ret = jsc_value_function_call (reject_callback, G_TYPE_STRING, "Invalid Arguments",
G_TYPE_NONE);
+ g_autoptr (JSCValue) ret = jsc_value_function_call (reject_callback, G_TYPE_STRING,
"ephy_send_message(): Invalid Arguments", G_TYPE_NONE);
return;
}
diff --git a/embed/web-process-extension/resources/js/webextensions-common.js
b/embed/web-process-extension/resources/js/webextensions-common.js
index c8194fd26..8aaf54819 100644
--- a/embed/web-process-extension/resources/js/webextensions-common.js
+++ b/embed/web-process-extension/resources/js/webextensions-common.js
@@ -26,6 +26,26 @@ class EphyEventListener {
for (const listener of this._listeners)
listener.callback (data);
}
+
+ _emit_with_reply (message, sender, message_guid) {
+ let handled = false;
+ const reply_callback = function (reply_message) {
+ ephy_message ('runtime._sendMessageReply', [message_guid, reply_message]);
+ };
+
+ for (const listener of this._listeners) {
+ const ret = listener.callback (message, sender, reply_callback);
+ if (typeof ret === 'object' && typeof ret.then === 'function') {
+ ret.then(x => { reply_callback(x); }).catch(x => { reply_callback(); })
+ handled = true;
+ } else if (ret === true) {
+ // We expect listener.callback to call `reply_callback`.
+ handled = true;
+ }
+ }
+
+ return handled;
+ }
}
const ephy_message = function (fn, args) {
diff --git a/src/webextension/api/runtime.c b/src/webextension/api/runtime.c
index e6295542d..1e2657342 100644
--- a/src/webextension/api/runtime.c
+++ b/src/webextension/api/runtime.c
@@ -58,14 +58,15 @@ is_empty_object (JSCValue *value)
return FALSE;
}
-static char *
-runtime_handler_send_message (EphyWebExtension *self,
- char *name,
- JSCValue *args,
- const char *context_guid,
- GError **error)
+static void
+runtime_handler_send_message (EphyWebExtension *self,
+ char *name,
+ JSCValue *args,
+ const char *context_guid,
+ GTask *task)
{
EphyWebExtensionManager *manager = ephy_web_extension_manager_get_default ();
+ g_autoptr (GError) error = NULL;
g_autoptr (JSCValue) last_value = NULL;
g_autoptr (JSCValue) message = NULL;
g_autofree char *json = NULL;
@@ -75,22 +76,24 @@ runtime_handler_send_message (EphyWebExtension *self,
last_value = jsc_value_object_get_property_at_index (args, 2);
if (!jsc_value_is_undefined (last_value)) {
/* We don't actually support sending to external extensions yet. */
- g_set_error_literal (error, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_NOT_IMPLEMENTED, "extensionId is
not supported");
- return NULL;
+ error = g_error_new_literal (WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_NOT_IMPLEMENTED, "extensionId is
not supported");
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
}
last_value = jsc_value_object_get_property_at_index (args, 1);
if (jsc_value_is_undefined (last_value) || jsc_value_is_null (last_value) || is_empty_object (last_value))
{
message = jsc_value_object_get_property_at_index (args, 0);
} else {
- g_set_error_literal (error, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_NOT_IMPLEMENTED, "extensionId is
not supported");
- return NULL;
+ error = g_error_new_literal (WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_NOT_IMPLEMENTED, "extensionId is
not supported");
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
}
json = jsc_value_to_json (message, 0);
- ephy_web_extension_manager_emit_in_extension_views_except_self (manager, self, "runtime.onMessage", json,
context_guid);
+ ephy_web_extension_manager_emit_in_extension_views_with_reply (manager, self, "runtime.onMessage", json,
context_guid, "{}", task);
- return NULL;
+ return;
}
static char *
@@ -120,12 +123,15 @@ runtime_handler_open_options_page (EphyWebExtension *self,
return NULL;
}
-static EphyWebExtensionSyncApiHandler runtime_handlers[] = {
+static EphyWebExtensionSyncApiHandler runtime_sync_handlers[] = {
{"getBrowserInfo", runtime_handler_get_browser_info},
- {"sendMessage", runtime_handler_send_message},
{"openOptionsPage", runtime_handler_open_options_page},
};
+static EphyWebExtensionAsyncApiHandler runtime_async_handlers[] = {
+ {"sendMessage", runtime_handler_send_message},
+};
+
void
ephy_web_extension_api_runtime_handler (EphyWebExtension *self,
char *name,
@@ -136,8 +142,8 @@ ephy_web_extension_api_runtime_handler (EphyWebExtension *self,
g_autoptr (GError) error = NULL;
guint idx;
- for (idx = 0; idx < G_N_ELEMENTS (runtime_handlers); idx++) {
- EphyWebExtensionSyncApiHandler handler = runtime_handlers[idx];
+ for (idx = 0; idx < G_N_ELEMENTS (runtime_sync_handlers); idx++) {
+ EphyWebExtensionSyncApiHandler handler = runtime_sync_handlers[idx];
char *ret;
if (g_strcmp0 (handler.name, name) == 0) {
@@ -152,6 +158,15 @@ ephy_web_extension_api_runtime_handler (EphyWebExtension *self,
}
}
+ for (idx = 0; idx < G_N_ELEMENTS (runtime_async_handlers); idx++) {
+ EphyWebExtensionAsyncApiHandler handler = runtime_async_handlers[idx];
+
+ if (g_strcmp0 (handler.name, name) == 0) {
+ handler.execute (self, name, args, context_guid, task);
+ return;
+ }
+ }
+
g_warning ("%s(): '%s' not implemented by Epiphany!", __FUNCTION__, name);
error = g_error_new_literal (WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_NOT_IMPLEMENTED, "Not Implemented");
g_task_return_error (task, g_steal_pointer (&error));
diff --git a/src/webextension/ephy-web-extension-manager.c b/src/webextension/ephy-web-extension-manager.c
index ef31b12d4..a376ea003 100644
--- a/src/webextension/ephy-web-extension-manager.c
+++ b/src/webextension/ephy-web-extension-manager.c
@@ -44,6 +44,8 @@
#include <json-glib/json-glib.h>
+static void handle_message_reply (EphyWebExtension *web_extension, JSCValue *args);
+
struct _EphyWebExtensionManager {
GObject parent_instance;
@@ -54,6 +56,8 @@ struct _EphyWebExtensionManager {
GHashTable *background_web_views;
GHashTable *popup_web_views;
+
+ GHashTable *pending_messages;
};
G_DEFINE_TYPE (EphyWebExtensionManager, ephy_web_extension_manager, G_TYPE_OBJECT)
@@ -209,6 +213,7 @@ ephy_web_extension_manager_constructed (GObject *object)
self->popup_web_views = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
(GDestroyNotify)g_ptr_array_free);
self->page_action_map = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_hash_table_destroy);
self->browser_action_map = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)destroy_widget_list);
+ self->pending_messages = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
(GDestroyNotify)g_hash_table_destroy);
self->web_extensions = NULL;
ephy_web_extension_manager_scan_directory (self, dir);
@@ -222,6 +227,7 @@ ephy_web_extension_manager_dispose (GObject *object)
g_clear_pointer (&self->background_web_views, g_hash_table_destroy);
g_clear_pointer (&self->popup_web_views, g_hash_table_destroy);
g_clear_pointer (&self->page_action_map, g_hash_table_destroy);
+ g_clear_pointer (&self->pending_messages, g_hash_table_destroy);
g_list_free_full (g_steal_pointer (&self->web_extensions), g_object_unref);
}
@@ -516,7 +522,13 @@ ephy_web_extension_handle_user_message (WebKitWebContext *context,
js_context = jsc_context_new ();
args = jsc_value_new_from_json (js_context, g_variant_get_string (webkit_user_message_get_parameters
(message), NULL));
- 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));
+ g_message ("%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));
+
+ /* Private API for message replies handled by the manager. */
+ if (strcmp (name, "runtime._sendMessageReply") == 0) {
+ handle_message_reply (web_extension, args);
+ return TRUE;
+ }
split = g_strsplit (name, ".", 2);
if (g_strv_length (split) != 2) {
@@ -1147,6 +1159,45 @@ ephy_web_extension_manager_get_page_action (EphyWebExtensionManager *self,
return ret;
}
+static void
+handle_message_reply (EphyWebExtension *web_extension,
+ JSCValue *args)
+{
+ EphyWebExtensionManager *manager = ephy_web_extension_manager_get_default ();
+ GHashTable *pending_messages = g_hash_table_lookup (manager->pending_messages, web_extension);
+ GTask *pending_task;
+ g_autofree char *message_guid = NULL;
+ g_autoptr (JSCValue) message_guid_value = NULL;
+ g_autoptr (JSCValue) reply_value = NULL;
+
+ g_message ("handle_message_reply");
+
+ message_guid_value = jsc_value_object_get_property_at_index (args, 0);
+ if (!jsc_value_is_string (message_guid_value)) {
+ g_debug ("Received invalid message reply");
+ return;
+ }
+
+ message_guid = jsc_value_to_string (message_guid_value);
+ pending_task = g_hash_table_lookup (pending_messages, message_guid);
+ if (!pending_task) {
+ g_debug ("Received message not found in pending replies");
+ return;
+ }
+
+ reply_value = jsc_value_object_get_property_at_index (args, 1);
+ g_task_return_pointer (pending_task, jsc_value_to_json (reply_value, 0), g_free);
+ g_object_ref (pending_task); /* Final unref in on_web_extension_api_handler_finish() */
+ g_hash_table_remove (pending_messages, message_guid);
+}
+
+typedef struct {
+ EphyWebExtension *web_extension;
+ const char *message_guid; /* Owned by manager->pending_messages. */
+ guint pending_view_responses;
+ gboolean handled;
+} PendingMessageReplyTracker;
+
static const char *
get_guid_for_view (WebKitWebView *view)
{
@@ -1154,24 +1205,103 @@ get_guid_for_view (WebKitWebView *view)
return g_object_get_data (G_OBJECT (context), "guid");
}
+static void
+on_extension_emit_ready (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EphyWebExtensionManager *manager = ephy_web_extension_manager_get_default ();
+ PendingMessageReplyTracker *tracker = user_data;
+ GHashTable *pending_messages;
+ g_autoptr (GError) error = NULL;
+ g_autoptr (WebKitJavascriptResult) js_result = NULL;
+ g_autoptr (JSCValue) js_value = NULL;
+
+ js_result = webkit_web_view_run_javascript_finish (WEBKIT_WEB_VIEW (source),
+ result,
+ &error);
+
+ if (error) {
+ g_warning ("%s", error->message);
+ } else {
+ js_value = webkit_javascript_result_get_js_value (js_result);
+ if (jsc_value_to_boolean (js_value))
+ tracker->handled = TRUE;
+ }
+
+ /* Once all views have been notified it will either be handled by one of them, in which case
+ * handle_message_reply() finishes the task, or we finish it here with an empty response. */
+ /* FIXME: A race condition is possible where a view is destroyed before it responds. */
+ tracker->pending_view_responses--;
+ if (tracker->pending_view_responses == 0) {
+ if (!tracker->handled) {
+ GTask *pending_task;
+ pending_messages = g_hash_table_lookup (manager->pending_messages, tracker->web_extension);
+ pending_task = g_hash_table_lookup (pending_messages, tracker->message_guid);
+ g_assert (pending_task);
+ g_task_return_pointer (pending_task, NULL, NULL);
+ g_object_ref (pending_task); /* Final unref in on_web_extension_api_handler_finish() */
+ g_hash_table_remove (pending_messages, tracker->message_guid);
+ }
+ g_free (tracker);
+ }
+}
+
static void
ephy_web_extension_manager_emit_in_extension_views_internal (EphyWebExtensionManager *self,
EphyWebExtension *web_extension,
const char *name,
- const char *json,
- const char *exception_guid)
+ const char *message_json,
+ const char *exception_guid,
+ const char *sender_json,
+ GTask *reply_task)
{
WebKitWebView *background_view = ephy_web_extension_manager_get_background_web_view (self, web_extension);
GPtrArray *popup_views = g_hash_table_lookup (self->popup_web_views, web_extension);
- g_autofree char *script = g_strdup_printf ("window.browser.%s._emit(%s);", name, json);
+ g_autofree char *script = NULL;
+ PendingMessageReplyTracker *tracker = NULL;
+ guint pending_views = 0;
+
+ /* The `runtime.sendMessage()` API emits `runtime.onMessage` and waits for a reply.
+ * The way this is implemented is:
+ * - All API handlers can be async: Returning a Promise backed by GTask (@reply_task).
+ * - Instead of completing the GTask we store it for each message waiting on replies.
+ * - We then call every extension view and track if any of them handled it (see webextensions-common.js).
+ * - If none handled it we complete with an empty message.
+ * - Otherwise we wait for our private `runtime._sendMessageReply` API call.
+ * - The first `runtime._sendMessageReply` call wins and completes the GTask with its data.
+ */
+ if (reply_task) {
+ char *message_guid = g_dbus_generate_guid ();
+ GHashTable *pending_messages = g_hash_table_lookup (self->pending_messages, web_extension);
+
+ tracker = g_new0 (PendingMessageReplyTracker, 1);
+ tracker->web_extension = web_extension;
+
+ if (!pending_messages) {
+ pending_messages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
(GDestroyNotify)g_object_unref);
+ g_hash_table_insert (self->pending_messages, web_extension, pending_messages);
+ }
+
+ script = g_strdup_printf ("window.browser.%s._emit_with_reply(%s, %s, '%s');", name, message_json,
sender_json, message_guid);
+
+ if (!g_hash_table_replace (pending_messages, message_guid, reply_task))
+ g_warning ("Duplicate message GUID");
+ }
+ else
+ script = g_strdup_printf ("window.browser.%s._emit(%s);", name, message_json);
+
+ g_message ("SCRIPT: %s", script);
if (background_view) {
- if (g_strcmp0 (get_guid_for_view (background_view), exception_guid) != 0)
+ if (g_strcmp0 (get_guid_for_view (background_view), exception_guid) != 0) {
webkit_web_view_run_javascript (background_view,
script,
NULL,
- NULL,
- NULL);
+ on_extension_emit_ready,
+ tracker);
+ pending_views++;
+ }
}
if (popup_views) {
@@ -1183,10 +1313,14 @@ ephy_web_extension_manager_emit_in_extension_views_internal (EphyWebExtensionMan
webkit_web_view_run_javascript (popup_view,
script,
NULL,
- NULL,
- NULL);
+ on_extension_emit_ready,
+ tracker);
+ pending_views++;
}
}
+
+ if (tracker)
+ tracker->pending_view_responses = pending_views;
}
void
@@ -1195,7 +1329,7 @@ ephy_web_extension_manager_emit_in_extension_views (EphyWebExtensionManager *sel
const char *name,
const char *json)
{
- ephy_web_extension_manager_emit_in_extension_views_internal (self, web_extension, name, json, NULL);
+ ephy_web_extension_manager_emit_in_extension_views_internal (self, web_extension, name, json, NULL, NULL,
NULL);
}
void
@@ -1206,5 +1340,20 @@ ephy_web_extension_manager_emit_in_extension_views_except_self (EphyWebExtension
const char *context_guid)
{
g_assert (context_guid);
- ephy_web_extension_manager_emit_in_extension_views_internal (self, web_extension, name, json,
context_guid);
+ ephy_web_extension_manager_emit_in_extension_views_internal (self, web_extension, name, json,
context_guid, NULL, NULL);
+}
+
+void
+ephy_web_extension_manager_emit_in_extension_views_with_reply (EphyWebExtensionManager *self,
+ EphyWebExtension *web_extension,
+ const char *name,
+ const char *json,
+ const char *context_guid,
+ const char *sender_json,
+ GTask *reply_task)
+{
+ g_assert (context_guid);
+ g_assert (sender_json);
+ g_assert (reply_task);
+ ephy_web_extension_manager_emit_in_extension_views_internal (self, web_extension, name, json,
context_guid, sender_json, reply_task);
}
diff --git a/src/webextension/ephy-web-extension-manager.h b/src/webextension/ephy-web-extension-manager.h
index 70bbf7e07..ef54ba5da 100644
--- a/src/webextension/ephy-web-extension-manager.h
+++ b/src/webextension/ephy-web-extension-manager.h
@@ -81,4 +81,13 @@ void ephy_web_extension_manager_emit_in_extension_views_exce
const char
*json,
const char
*context_guid);
+void ephy_web_extension_manager_emit_in_extension_views_with_reply
+ (EphyWebExtensionManager
*self,
+ EphyWebExtension
*web_extension,
+ const char
*name,
+ const char
*json,
+ const char
*context_guid,
+ const char
*sender_json,
+ GTask
*reply_task);
+
G_END_DECLS
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]