[epiphany/pgriffis/web-extension/menus: 4/4] WebExtensions: Implement menus API
- From: Marge Bot <marge-bot src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [epiphany/pgriffis/web-extension/menus: 4/4] WebExtensions: Implement menus API
- Date: Tue, 28 Jun 2022 16:33:02 +0000 (UTC)
commit 38675bd699cba84751ea5db4659333aed821e212
Author: Patrick Griffis <pgriffis igalia com>
Date: Sat Jun 11 21:09:02 2022 -0500
WebExtensions: Implement menus API
Part-of: <https://gitlab.gnome.org/GNOME/epiphany/-/merge_requests/1154>
.../ephy-web-process-extension.c | 15 +-
embed/web-process-extension/resources/js/ephy.js | 11 +
.../resources/js/webextensions.js | 10 +
src/ephy-shell.c | 10 +
src/ephy-window.c | 8 +-
src/webextension/README.md | 22 +
src/webextension/api/menus.c | 707 +++++++++++++++++++++
src/webextension/api/menus.h | 43 ++
src/webextension/ephy-json-utils.c | 30 +
src/webextension/ephy-json-utils.h | 6 +
src/webextension/ephy-web-extension-manager.c | 49 ++
src/webextension/ephy-web-extension-manager.h | 11 +
src/webextension/ephy-web-extension.c | 47 +-
src/webextension/ephy-web-extension.h | 17 +-
src/webextension/meson.build | 1 +
15 files changed, 963 insertions(+), 24 deletions(-)
---
diff --git a/embed/web-process-extension/ephy-web-process-extension.c
b/embed/web-process-extension/ephy-web-process-extension.c
index d0983555a..01afd379a 100644
--- a/embed/web-process-extension/ephy-web-process-extension.c
+++ b/embed/web-process-extension/ephy-web-process-extension.c
@@ -194,6 +194,8 @@ web_page_context_menu (WebKitWebPage *web_page,
{
EphyWebProcessExtension *extension;
g_autofree char *string = NULL;
+ gboolean is_editable;
+ gboolean is_password;
GVariantBuilder builder;
WebKitFrame *frame;
g_autoptr (JSCContext) js_context = NULL;
@@ -209,12 +211,19 @@ web_page_context_menu (WebKitWebPage *web_page,
js_value = jsc_context_evaluate (js_context, "window.getSelection().toString();", -1);
if (!jsc_value_is_null (js_value) && !jsc_value_is_undefined (js_value))
string = jsc_value_to_string (js_value);
+ g_object_unref (js_value);
- if (!string || *string == '\0')
- return FALSE;
+ js_value = jsc_context_evaluate (js_context, "contextMenuElementIsEditable;", -1);
+ is_editable = jsc_value_to_boolean (js_value);
+ g_object_unref (js_value);
+
+ js_value = jsc_context_evaluate (js_context, "contextMenuElementIsPassword;", -1);
+ is_password = jsc_value_to_boolean (js_value);
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
- g_variant_builder_add (&builder, "{sv}", "SelectedText", g_variant_new_string (g_strstrip (string)));
+ g_variant_builder_add (&builder, "{sv}", "SelectedText", g_variant_new_string (string ? g_strstrip
(string) : ""));
+ g_variant_builder_add (&builder, "{sv}", "IsEditable", g_variant_new_boolean (is_editable));
+ g_variant_builder_add (&builder, "{sv}", "IsPassword", g_variant_new_boolean (is_password));
webkit_context_menu_set_user_data (context_menu,
g_variant_builder_end (&builder));
diff --git a/embed/web-process-extension/resources/js/ephy.js
b/embed/web-process-extension/resources/js/ephy.js
index 38b806f7a..ce553dca4 100644
--- a/embed/web-process-extension/resources/js/ephy.js
+++ b/embed/web-process-extension/resources/js/ephy.js
@@ -1,5 +1,7 @@
'use strict';
+/* exported contextMenuElementIsPassword, contextMenuElementIsEditable */
+
// eslint-disable-next-line no-var
var Ephy = {};
@@ -766,3 +768,12 @@ Ephy.FormManager = class FormManager
return formAuth;
}
};
+
+let contextMenuElementIsEditable = false;
+let contextMenuElementIsPassword = false;
+
+window.document.addEventListener('contextmenu', (event) => {
+ // isContentEditable is always false, in practice this seems functional enough.
+ contextMenuElementIsEditable = event.target.tagName.toLowerCase() === 'input';
+ contextMenuElementIsPassword = event.target.type === 'password';
+});
\ No newline at end of file
diff --git a/embed/web-process-extension/resources/js/webextensions.js
b/embed/web-process-extension/resources/js/webextensions.js
index 12ffe25cd..de6454ab6 100644
--- a/embed/web-process-extension/resources/js/webextensions.js
+++ b/embed/web-process-extension/resources/js/webextensions.js
@@ -155,3 +155,13 @@ window.browser.downloads = {
onChanged: new EphyEventListener (),
};
+window.browser.menus = {
+ create: function (...args) { return ephy_message ('menus.create', args); },
+ remove: function (...args) { return ephy_message ('menus.remove', args); },
+ removeAll: function (...args) { return ephy_message ('menus.removeAll', args); },
+ update: function (...args) { return ephy_message ('menus.update', args); },
+ onClicked: new EphyEventListener (),
+};
+
+// Chrome compat.
+window.browser.contextMenus = window.browser.menus;
diff --git a/src/ephy-shell.c b/src/ephy-shell.c
index 3c8c4464c..5d428ea20 100644
--- a/src/ephy-shell.c
+++ b/src/ephy-shell.c
@@ -361,6 +361,15 @@ webextension_action (GSimpleAction *action,
ephy_web_extension_manager_handle_notifications_action (manager, parameter);
}
+static void
+webextension_context_menu_action (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ EphyWebExtensionManager *manager = ephy_web_extension_manager_get_default ();
+ ephy_web_extension_manager_handle_context_menu_action (manager, parameter);
+}
+
static GActionEntry app_entries[] = {
{ "new-window", new_window, NULL, NULL, NULL },
{ "new-incognito", new_incognito_window, NULL, NULL, NULL },
@@ -378,6 +387,7 @@ static GActionEntry app_entries[] = {
{ "launch-app", launch_app, "s", NULL, NULL },
{ "notification-clicked", notification_clicked, "t", NULL, NULL },
{ "webextension-notification", webextension_action, "(ssi)", NULL, NULL },
+ { "webextension-context-menu", webextension_context_menu_action, "(sss)", NULL, NULL },
};
static GActionEntry non_incognito_extra_app_entries[] = {
diff --git a/src/ephy-window.c b/src/ephy-window.c
index 650adce80..c1521ffee 100644
--- a/src/ephy-window.c
+++ b/src/ephy-window.c
@@ -1490,6 +1490,7 @@ populate_context_menu (WebKitWebView *web_view,
WebKitHitTestResult *hit_test_result,
EphyWindow *window)
{
+ EphyWebExtensionManager *extension_manager = ephy_web_extension_manager_get_default ();
WebKitContextMenuItem *input_methods_item = NULL;
WebKitContextMenuItem *insert_emoji_item = NULL;
WebKitContextMenuItem *copy_image_item = NULL;
@@ -1565,7 +1566,7 @@ populate_context_menu (WebKitWebView *web_view,
}
parse_context_menu_user_data (context_menu, &selected_text);
- if (selected_text) {
+ if (selected_text && *selected_text) {
if (g_uri_is_valid (selected_text, G_URI_FLAGS_NONE, NULL)) {
GVariant *value;
@@ -1807,6 +1808,11 @@ populate_context_menu (WebKitWebView *web_view,
webkit_context_menu_item_new_from_stock_action
(WEBKIT_CONTEXT_MENU_ACTION_INSPECT_ELEMENT));
}
+ ephy_web_extension_manager_append_context_menu (extension_manager, web_view,
+ context_menu, hit_test_result,
+ event->type == GDK_BUTTON_PRESS ? ((GdkEventButton
*)event)->state : 0,
+ is_downloadable_audio, is_downloadable_video);
+
return GDK_EVENT_PROPAGATE;
}
diff --git a/src/webextension/README.md b/src/webextension/README.md
index 3bfc12a76..9918e8d4f 100644
--- a/src/webextension/README.md
+++ b/src/webextension/README.md
@@ -105,6 +105,28 @@ This is partially implemented. Message passing including replies works including
- onMessage
- lastError
+#### menus
+
+Notes:
+
+- `contextMenus` is aliased to this API and the same permissions work for both.
+
+Limitations:
+
+- Menus are only shown on web pages and not extension views, tabs, etc. (TODO)
+- Radio and Checkbox menus aren't properly supported yet. (TODO)
+- Commands are not yet supported. (TODO)
+- update() isn't supported yet. (TODO)
+- Frames are not properly handled.
+- Icons are not supported.
+
+APIs:
+
+- create()
+- remove()
+- removeAll()
+- onClicked
+
#### notifications
Limitations:
diff --git a/src/webextension/api/menus.c b/src/webextension/api/menus.c
new file mode 100644
index 000000000..eb0ef209c
--- /dev/null
+++ b/src/webextension/api/menus.c
@@ -0,0 +1,707 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright 2022 Igalia S.L.
+ *
+ * This file is part of Epiphany.
+ *
+ * Epiphany is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "ephy-json-utils.h"
+#include "ephy-web-view.h"
+#include "ephy-shell.h"
+
+#include "tabs.h"
+#include "menus.h"
+
+typedef enum {
+ MENU_CONTEXT_AUDIO = 1 << 0,
+ MENU_CONTEXT_BOOKMARK = 1 << 1,
+ MENU_CONTEXT_BROWSER_ACTION = 1 << 2,
+ MENU_CONTEXT_EDITABLE = 1 << 3,
+ MENU_CONTEXT_FRAME = 1 << 4,
+ MENU_CONTEXT_IMAGE = 1 << 5,
+ MENU_CONTEXT_LINK = 1 << 6,
+ MENU_CONTEXT_PAGE = 1 << 7,
+ MENU_CONTEXT_PASSWORD = 1 << 8,
+ MENU_CONTEXT_SELECTION = 1 << 9,
+ MENU_CONTEXT_TAB = 1 << 10,
+ MENU_CONTEXT_TOOLS_MENU = 1 << 11,
+ MENU_CONTEXT_VIDEO = 1 << 12,
+ MENU_CONTEXT_PAGE_ACTION = 1 << 13,
+} MenuContext;
+
+typedef enum {
+ MENU_TYPE_NORMAL,
+ MENU_TYPE_CHECKBOX,
+ MENU_TYPE_RADIO,
+ MENU_TYPE_SEPARATOR,
+} MenuType;
+
+typedef enum {
+ VIEW_TYPE_ANY = 0,
+ VIEW_TYPE_TAB = 1 << 0,
+ VIEW_TYPE_POPUP = 1 << 1,
+ VIEW_TYPE_SIDEBAR = 1 << 2,
+} ViewType;
+
+typedef enum {
+ COMMAND_NONE,
+ COMMAND_BROWSER_ACTION,
+ COMMAND_PAGE_ACTION,
+ COMMAND_SIDEBAR_ACTION,
+} Command;
+
+typedef struct {
+ char *id;
+ char *parent_id;
+ char *title;
+ GHashTable *children;
+ GStrv document_url_patterns;
+ GStrv target_url_patterns;
+ MenuType menu_type;
+ ViewType view_type;
+ Command command;
+ MenuContext contexts;
+ gboolean checked;
+ gboolean enabled;
+ gboolean visible;
+} MenuItem;
+
+static GStrv
+get_strv_property (JsonObject *object,
+ const char *name)
+{
+ JsonNode *node = json_object_get_member (object, name);
+ JsonArray *json_array;
+ GPtrArray *array;
+ GStrv ret;
+
+ if (!node || json_node_get_node_type (node) != JSON_NODE_ARRAY)
+ return NULL;
+
+ json_array = json_node_get_array (node);
+ array = g_ptr_array_new ();
+
+ for (guint i = 0; i < json_array_get_length (json_array); i++) {
+ const char *string = ephy_json_array_get_string (json_array, i);
+ if (!string)
+ continue;
+
+ g_ptr_array_add (array, g_strdup (string));
+ }
+
+ if (array->len == 0) {
+ g_ptr_array_free (array, TRUE);
+ return NULL;
+ }
+
+ g_ptr_array_add (array, NULL);
+ ret = (GStrv)array->pdata;
+ g_ptr_array_free (array, FALSE);
+ return ret;
+}
+
+static Command
+get_command_property (JsonObject *object)
+{
+ JsonNode *node = json_object_get_member (object, "command");
+ const char *command;
+
+ command = ephy_json_node_to_string (node);
+ if (!command)
+ return COMMAND_NONE;
+ if (strcmp (command, "_execute_browser_action") == 0)
+ return COMMAND_BROWSER_ACTION;
+ if (strcmp (command, "_execute_page_action") == 0)
+ return COMMAND_PAGE_ACTION;
+ if (strcmp (command, "_execute_sidebar_action") == 0)
+ return COMMAND_SIDEBAR_ACTION;
+
+ return COMMAND_NONE;
+}
+
+static MenuType
+get_menu_type_property (JsonObject *object)
+{
+ JsonNode *node = json_object_get_member (object, "type");
+ const char *menu_type;
+
+ menu_type = ephy_json_node_to_string (node);
+ if (!menu_type)
+ return MENU_TYPE_NORMAL;
+ if (strcmp (menu_type, "normal") == 0)
+ return MENU_TYPE_NORMAL;
+ if (strcmp (menu_type, "checkbox") == 0)
+ return MENU_TYPE_CHECKBOX;
+ if (strcmp (menu_type, "radio") == 0)
+ return MENU_TYPE_RADIO;
+ if (strcmp (menu_type, "separator") == 0)
+ return MENU_TYPE_SEPARATOR;
+
+ return MENU_TYPE_NORMAL;
+}
+
+static ViewType
+get_view_type_property (JsonObject *object)
+{
+ JsonNode *node = json_object_get_member (object, "viewTypes");
+ JsonArray *view_type_array;
+ ViewType view_type_flags = 0;
+
+ if (!node || json_node_get_node_type (node) != JSON_NODE_ARRAY)
+ return VIEW_TYPE_ANY;
+
+ view_type_array = json_node_get_array (node);
+ for (guint i = 0; i < json_array_get_length (view_type_array); i++) {
+ const char *view_type = ephy_json_array_get_string (view_type_array, i);
+ if (!view_type)
+ continue;
+
+ if (strcmp (view_type, "tab") == 0)
+ view_type_flags |= VIEW_TYPE_TAB;
+ else if (strcmp (view_type, "popup") == 0)
+ view_type_flags |= VIEW_TYPE_POPUP;
+ else if (strcmp (view_type, "sidebar") == 0)
+ view_type_flags |= VIEW_TYPE_SIDEBAR;
+ }
+
+ if (view_type_flags == 0)
+ return VIEW_TYPE_ANY;
+
+ return view_type_flags;
+}
+
+typedef struct {
+ const char *name;
+ MenuContext value;
+} ContextMap;
+
+static const ContextMap context_map[] = {
+ { "all", MENU_CONTEXT_AUDIO | MENU_CONTEXT_BROWSER_ACTION | MENU_CONTEXT_EDITABLE | MENU_CONTEXT_FRAME |
+ MENU_CONTEXT_IMAGE | MENU_CONTEXT_LINK | MENU_CONTEXT_PAGE | MENU_CONTEXT_PASSWORD |
+ MENU_CONTEXT_SELECTION | MENU_CONTEXT_VIDEO | MENU_CONTEXT_PAGE_ACTION },
+ { "audio", MENU_CONTEXT_AUDIO },
+ { "bookmark", MENU_CONTEXT_BOOKMARK },
+ { "browser_action", MENU_CONTEXT_BROWSER_ACTION },
+ { "editable", MENU_CONTEXT_EDITABLE },
+ { "frame", MENU_CONTEXT_FRAME },
+ { "image", MENU_CONTEXT_IMAGE },
+ { "link", MENU_CONTEXT_LINK },
+ { "page", MENU_CONTEXT_PAGE },
+ { "password", MENU_CONTEXT_PASSWORD },
+ { "selection", MENU_CONTEXT_SELECTION },
+ { "tab", MENU_CONTEXT_TAB },
+ { "tools_menu", MENU_CONTEXT_TOOLS_MENU },
+ { "video", MENU_CONTEXT_VIDEO },
+ { "page_action", MENU_CONTEXT_PAGE_ACTION },
+};
+
+static MenuContext
+get_contexts_property (JsonObject *object)
+{
+ JsonNode *node = json_object_get_member (object, "contexts");
+ JsonArray *contexts_array;
+ MenuContext context_flags = 0;
+
+ if (!node || !JSON_NODE_HOLDS_ARRAY (node))
+ return MENU_CONTEXT_PAGE;
+
+ contexts_array = json_node_get_array (node);
+ for (guint i = 0; i < json_array_get_length (contexts_array); i++) {
+ const char *context = ephy_json_array_get_string (contexts_array, i);
+
+ if (!context)
+ continue;
+
+ for (guint j = 0; j < G_N_ELEMENTS (context_map); j++) {
+ if (strcmp (context, context_map[j].name) == 0) {
+ context_flags |= context_map[j].value;
+ break;
+ }
+ }
+ }
+
+ if (context_flags == 0)
+ return MENU_CONTEXT_PAGE;
+
+ return context_flags;
+}
+
+static void
+menu_item_free (MenuItem *item)
+{
+ if (item) {
+ g_hash_table_unref (item->children);
+ g_free (item->id);
+ g_free (item->parent_id);
+ g_free (item->title);
+ g_strfreev (item->document_url_patterns);
+ g_strfreev (item->target_url_patterns);
+ g_free (item);
+ }
+}
+
+static MenuItem *
+menu_item_new (JsonObject *object)
+{
+ MenuItem *item = g_new0 (MenuItem, 1);
+
+ item->id = g_strdup (ephy_json_object_get_string (object, "id"));
+ item->parent_id = g_strdup (ephy_json_object_get_string (object, "parentId"));
+ item->title = g_strdup (ephy_json_object_get_string (object, "title"));
+ item->command = get_command_property (object);
+ item->contexts = get_contexts_property (object);
+ item->menu_type = get_menu_type_property (object);
+ item->view_type = get_view_type_property (object);
+ item->document_url_patterns = get_strv_property (object, "documentUrlPatterns");
+ item->target_url_patterns = get_strv_property (object, "targetUrlPatterns");
+ item->checked = json_object_get_boolean_member_with_default (object, "checked", FALSE);
+ item->enabled = json_object_get_boolean_member_with_default (object, "enabled", TRUE);
+ item->visible = json_object_get_boolean_member_with_default (object, "visible", TRUE);
+ item->children = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)menu_item_free);
+
+ return item;
+}
+
+static gboolean
+insert_menu_item (GHashTable *menus,
+ MenuItem *item)
+{
+ MenuItem *parent;
+ MenuItem *next_item;
+ GHashTableIter iter;
+
+ if (!item->parent_id) {
+ g_hash_table_replace (menus, item->id, item);
+ return TRUE;
+ }
+
+ parent = g_hash_table_lookup (menus, item->parent_id);
+ if (parent) {
+ g_hash_table_replace (parent->children, item->id, item);
+ return TRUE;
+ }
+
+ g_hash_table_iter_init (&iter, menus);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&next_item)) {
+ if (insert_menu_item (next_item->children, item))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static GHashTable *
+get_menus (EphyWebExtension *extension)
+{
+ GHashTable *menus = g_object_get_data (G_OBJECT (extension), "menus");
+ if (menus)
+ return menus;
+
+ menus = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)menu_item_free);
+ g_object_set_data_full (G_OBJECT (extension), "menus", menus, (GDestroyNotify)g_hash_table_destroy);
+ return menus;
+}
+
+static void
+menus_handler_create (EphyWebExtension *self,
+ char *name,
+ JsonArray *args,
+ WebKitWebView *web_view,
+ GTask *task)
+{
+ JsonObject *create_properties = ephy_json_array_get_object (args, 0);
+ MenuItem *item;
+ GHashTable *menus = get_menus (self);
+
+ if (!create_properties) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_ARGUMENT,
"menus.create(): Missing createProperties");
+ return;
+ }
+
+ item = menu_item_new (create_properties);
+
+ if (!item->id || (!item->title && item->menu_type != MENU_TYPE_SEPARATOR)) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_ARGUMENT,
"menus.create(): createProperties is missing an id or title");
+ menu_item_free (item);
+ return;
+ }
+
+ if (!insert_menu_item (menus, item)) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_ARGUMENT,
"menus.create(): parentId not found");
+ menu_item_free (item);
+ return;
+ }
+
+ g_task_return_pointer (task, g_strdup_printf ("\"%s\"", item->id), g_free);
+}
+
+static gboolean
+menus_remove_by_id (GHashTable *menus,
+ const char *menu_id)
+{
+ GHashTableIter iter;
+ MenuItem *next_item;
+
+ if (!menus)
+ return FALSE;
+
+ if (g_hash_table_remove (menus, menu_id))
+ return TRUE;
+
+ g_hash_table_iter_init (&iter, menus);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&next_item)) {
+ if (menus_remove_by_id (next_item->children, menu_id))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+menus_handler_remove (EphyWebExtension *self,
+ char *name,
+ JsonArray *args,
+ WebKitWebView *web_view,
+ GTask *task)
+{
+ const char *menu_id = ephy_json_array_get_string (args, 0);
+
+ if (!menu_id) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_ARGUMENT,
"menus.remove(): Missing menuId");
+ return;
+ }
+
+ if (!menus_remove_by_id (get_menus (self), menu_id)) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_ARGUMENT,
"menus.remove(): Failed to find menuId '%s'", menu_id);
+ return;
+ }
+
+ g_task_return_pointer (task, NULL, NULL);
+}
+
+static void
+menus_handler_remove_all (EphyWebExtension *self,
+ char *name,
+ JsonArray *args,
+ WebKitWebView *web_view,
+ GTask *task)
+{
+ g_object_set_data (G_OBJECT (self), "menus", NULL);
+ g_task_return_pointer (task, NULL, NULL);
+}
+
+static char *
+format_label (const char *label,
+ const char *selected_text)
+{
+ GString *str = g_string_new (label);
+ /* FIXME: Handle & character. */
+ /* Documented here:
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/menus/create */
+ g_string_replace (str, "%s", selected_text, 1);
+ return g_string_free (str, FALSE);
+}
+
+static char *
+create_tabdata (EphyWebExtension *web_extension,
+ WebKitWebView *web_view)
+{
+ g_autoptr (JsonNode) tab_node = NULL;
+
+ if (!EPHY_IS_WEB_VIEW (web_view))
+ return g_strdup ("undefined");
+
+ tab_node = ephy_web_extension_api_tabs_create_tab_object (web_extension, EPHY_WEB_VIEW (web_view));
+ return json_to_string (tab_node, FALSE);
+}
+
+static void
+parse_context_menu_user_data (WebKitContextMenu *context_menu,
+ const char **selected_text,
+ gboolean *is_editable,
+ gboolean *is_password)
+{
+ GVariantDict dict;
+
+ g_variant_dict_init (&dict, webkit_context_menu_get_user_data (context_menu));
+ g_variant_dict_lookup (&dict, "SelectedText", "&s", selected_text);
+ g_variant_dict_lookup (&dict, "IsEditable", "b", is_editable);
+ g_variant_dict_lookup (&dict, "IsPassword", "b", is_password);
+}
+
+static void
+builder_add_modifier_array (JsonBuilder *builder,
+ GdkModifierType state)
+{
+ json_builder_begin_array (builder);
+ if (state & GDK_CONTROL_MASK)
+ json_builder_add_string_value (builder, "Ctrl");
+ if (state & GDK_SHIFT_MASK)
+ json_builder_add_string_value (builder, "Shift");
+ if (state & GDK_MOD1_MASK)
+ json_builder_add_string_value (builder, "Alt");
+ json_builder_end_array (builder);
+}
+
+static char *
+create_onclickdata (EphyWebExtension *web_extension,
+ WebKitWebView *web_view,
+ WebKitContextMenu *context_menu,
+ GdkModifierType modifiers,
+ WebKitHitTestResult *hit_test_result,
+ MenuItem *menu_item,
+ const char *selected_text,
+ gboolean is_editable,
+ gboolean is_audio,
+ gboolean is_video)
+{
+ g_autoptr (JsonBuilder) builder = json_builder_new ();
+ g_autoptr (JsonNode) root = NULL;
+
+ json_builder_begin_object (builder);
+ json_builder_set_member_name (builder, "menuItemId");
+ json_builder_add_string_value (builder, menu_item->id);
+ if (menu_item->parent_id) {
+ json_builder_set_member_name (builder, "parentMenuItemId");
+ json_builder_add_string_value (builder, menu_item->parent_id);
+ }
+ if (selected_text) {
+ json_builder_set_member_name (builder, "selectionText");
+ json_builder_add_string_value (builder, selected_text);
+ }
+ json_builder_set_member_name (builder, "button");
+ json_builder_add_int_value (builder, GDK_BUTTON_SECONDARY);
+ json_builder_set_member_name (builder, "modifiers");
+ builder_add_modifier_array (builder, modifiers);
+ if (webkit_hit_test_result_context_is_link (hit_test_result)) {
+ json_builder_set_member_name (builder, "linkUrl");
+ json_builder_add_string_value (builder, webkit_hit_test_result_get_link_uri (hit_test_result));
+ if (webkit_hit_test_result_get_link_title (hit_test_result)) {
+ json_builder_set_member_name (builder, "linkTitle");
+ json_builder_add_string_value (builder, webkit_hit_test_result_get_link_title (hit_test_result));
+ }
+ }
+ if (webkit_hit_test_result_context_is_image (hit_test_result)) {
+ json_builder_set_member_name (builder, "mediaType");
+ json_builder_add_string_value (builder, "image");
+ }
+ if (is_audio || is_video) {
+ json_builder_set_member_name (builder, "mediaType");
+ json_builder_add_string_value (builder, is_audio ? "audio" : "video");
+ }
+ json_builder_set_member_name (builder, "editable");
+ json_builder_add_boolean_value (builder, is_editable);
+ json_builder_set_member_name (builder, "pageUrl");
+ json_builder_add_string_value (builder, webkit_web_view_get_uri (web_view));
+ json_builder_end_object (builder);
+
+ root = json_builder_get_root (builder);
+ return json_to_string (root, FALSE);
+}
+
+static gboolean
+item_applies_to_context (MenuContext context,
+ gboolean is_audio,
+ gboolean is_video,
+ gboolean is_editable,
+ gboolean is_password,
+ gboolean has_selection,
+ WebKitHitTestResult *hit_test_result)
+{
+ /* Only pages are currently supported. */
+ if ((context & MENU_CONTEXT_PAGE))
+ return TRUE;
+ if (is_password && (context & MENU_CONTEXT_PASSWORD))
+ return TRUE;
+ if (is_audio && (context & MENU_CONTEXT_AUDIO))
+ return TRUE;
+ if (is_editable && (context & MENU_CONTEXT_EDITABLE))
+ return TRUE;
+ if (webkit_hit_test_result_context_is_image (hit_test_result) && (context & MENU_CONTEXT_IMAGE))
+ return TRUE;
+ if (has_selection && (context & MENU_CONTEXT_SELECTION))
+ return TRUE;
+ if (webkit_hit_test_result_context_is_link (hit_test_result) && (context & MENU_CONTEXT_LINK))
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+rules_match_uri (GStrv rules,
+ GUri *uri)
+{
+ if (!rules || !*rules)
+ return TRUE;
+
+ if (!uri)
+ return FALSE;
+
+ for (guint i = 0; rules[i]; i++) {
+ if (ephy_web_extension_rule_matches_uri (rules[i], uri))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static WebKitContextMenuItem *
+create_context_menu_item (GHashTable *menus,
+ const char *name,
+ EphyWebExtension *self,
+ WebKitWebView *web_view,
+ GdkModifierType modifiers,
+ WebKitContextMenu *context_menu,
+ WebKitHitTestResult *hit_test_result,
+ GAction *action,
+ gboolean is_audio,
+ gboolean is_video,
+ gboolean is_editable,
+ gboolean is_password,
+ const char *selected_text,
+ const char *tab_data,
+ GUri *document_uri,
+ GUri *target_uri)
+{
+ GHashTableIter iter;
+ MenuItem *item;
+ GList *menu_items = NULL;
+
+ g_hash_table_iter_init (&iter, menus);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&item)) {
+ g_autofree char *label = NULL;
+ g_autofree char *onclickdata = NULL;
+ WebKitContextMenuItem *menu_item;
+
+ if (!item->visible)
+ continue;
+
+ if (!rules_match_uri (item->document_url_patterns, document_uri) ||
+ !rules_match_uri (item->target_url_patterns, target_uri))
+ continue;
+
+ if (!item_applies_to_context (item->contexts, is_audio, is_video,
+ is_editable, is_password,
+ selected_text && *selected_text,
+ hit_test_result))
+ continue;
+
+ if (item->view_type != VIEW_TYPE_ANY && !(item->view_type & VIEW_TYPE_TAB))
+ continue;
+
+ /* FIXME: We don't properly support checked and radio items but we still show them for now. */
+ if (item->menu_type == MENU_TYPE_SEPARATOR) {
+ menu_item = webkit_context_menu_item_new_separator ();
+ } else if (g_hash_table_size (item->children)) {
+ label = format_label (item->title, selected_text ? selected_text : "");
+ menu_item = create_context_menu_item (item->children, label, self, web_view, modifiers, context_menu,
hit_test_result, action,
+ is_audio, is_video, is_editable, is_password, selected_text,
tab_data,
+ document_uri, target_uri);
+
+ /* If the menus root is an item with the same name as the extension we use it as the root.
+ * otherwise we create a new root below. */
+ if (!item->parent_id && g_hash_table_size (menus) == 1 && strcmp (item->title, name) == 0) {
+ return menu_item;
+ }
+ } else {
+ label = format_label (item->title, selected_text ? selected_text : "");
+ onclickdata = create_onclickdata (self, web_view, context_menu, modifiers, hit_test_result,
+ item, selected_text, is_editable, is_audio, is_video);
+ menu_item = webkit_context_menu_item_new_from_gaction (action, label,
+ g_variant_new ("(sss)",
+ ephy_web_extension_get_guid
(self),
+ onclickdata,
+ tab_data));
+ }
+
+ menu_items = g_list_append (menu_items, menu_item);
+ }
+
+ return webkit_context_menu_item_new_with_submenu (name, webkit_context_menu_new_with_items (menu_items));
+}
+
+WebKitContextMenuItem *
+ephy_web_extension_api_menus_create_context_menu (EphyWebExtension *self,
+ WebKitWebView *web_view,
+ WebKitContextMenu *context_menu,
+ WebKitHitTestResult *hit_test_result,
+ GdkModifierType modifiers,
+ gboolean is_audio,
+ gboolean is_video)
+{
+ GHashTable *menus = g_object_get_data (G_OBJECT (self), "menus");
+ GAction *action;
+ g_autofree char *tab_data = NULL;
+ const char *selected_text;
+ gboolean is_editable;
+ gboolean is_password;
+ GUri *document_uri;
+ GUri *target_uri = NULL;
+
+ if (!menus)
+ return NULL;
+
+ parse_context_menu_user_data (context_menu, &selected_text, &is_editable, &is_password);
+ tab_data = create_tabdata (self, web_view);
+ action = g_action_map_lookup_action (G_ACTION_MAP (ephy_shell_get_default ()),
"webextension-context-menu");
+ g_assert (action);
+
+ document_uri = g_uri_parse (webkit_web_view_get_uri (web_view), G_URI_FLAGS_ENCODED_PATH |
G_URI_FLAGS_ENCODED_QUERY | G_URI_FLAGS_SCHEME_NORMALIZE, NULL);
+ if (webkit_hit_test_result_get_link_uri (hit_test_result))
+ target_uri = g_uri_parse (webkit_hit_test_result_get_link_uri (hit_test_result),
G_URI_FLAGS_ENCODED_PATH | G_URI_FLAGS_ENCODED_QUERY | G_URI_FLAGS_SCHEME_NORMALIZE, NULL);
+
+ return create_context_menu_item (menus, ephy_web_extension_get_short_name (self), self, web_view,
modifiers,
+ context_menu, hit_test_result, action, is_audio, is_video, is_editable,
+ is_password, selected_text, tab_data, document_uri, target_uri);
+}
+
+static EphyWebExtensionApiHandler menus_handlers[] = {
+ {"create", menus_handler_create},
+ {"remove", menus_handler_remove},
+ {"remove_all", menus_handler_remove_all},
+};
+
+void
+ephy_web_extension_api_menus_handler (EphyWebExtension *self,
+ char *name,
+ JSCValue *args,
+ WebKitWebView *web_view,
+ GTask *task)
+{
+ /* Temporary conversion as we migrate to json-glib here. */
+ g_autofree char *json = jsc_value_to_json (args, 0);
+ g_autoptr (JsonNode) json_node = json_from_string (json, NULL);
+ JsonArray *json_args = json_node_get_array (json_node);
+ json_array_seal (json_args);
+
+ /* We slightly differ from Firefox here that either permission works for either API but they are
identical. */
+ if (!ephy_web_extension_has_permission (self, "menus") && !ephy_web_extension_has_permission (self,
"contextMenus")) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_PERMISSION_DENIED, "Permission
Denied");
+ return;
+ }
+
+ for (guint idx = 0; idx < G_N_ELEMENTS (menus_handlers); idx++) {
+ EphyWebExtensionApiHandler handler = menus_handlers[idx];
+
+ if (g_strcmp0 (handler.name, name) == 0) {
+ handler.execute (self, name, json_args, web_view, task);
+ return;
+ }
+ }
+
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_NOT_IMPLEMENTED, "Not
Implemented");
+}
diff --git a/src/webextension/api/menus.h b/src/webextension/api/menus.h
new file mode 100644
index 000000000..419570f76
--- /dev/null
+++ b/src/webextension/api/menus.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2022 Igalia S.L.
+ *
+ * This file is part of Epiphany.
+ *
+ * Epiphany is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ephy-web-extension.h"
+
+#include <json-glib/json-glib.h>
+#include <webkit2/webkit2.h>
+
+G_BEGIN_DECLS
+
+void ephy_web_extension_api_menus_handler (EphyWebExtension *self,
+ char *name,
+ JSCValue *value,
+ WebKitWebView *web_view,
+ GTask *task);
+
+WebKitContextMenuItem *ephy_web_extension_api_menus_create_context_menu (EphyWebExtension *self,
+ WebKitWebView
*web_view,
+ WebKitContextMenu
*context_menu,
+ WebKitHitTestResult
*hit_test_result,
+ GdkModifierType
modifiers,
+ gboolean
is_audio,
+ gboolean
is_video);
+
+G_END_DECLS
diff --git a/src/webextension/ephy-json-utils.c b/src/webextension/ephy-json-utils.c
index 68d6a3e3d..1f1cbccb8 100644
--- a/src/webextension/ephy-json-utils.c
+++ b/src/webextension/ephy-json-utils.c
@@ -196,3 +196,33 @@ ephy_json_node_to_string (JsonNode *node)
return json_node_get_string (node);
}
+
+/**
+ * ephy_json_array_get_string:
+ * @array: A #JsonArray
+ *
+ * This safely gets a string from an array.
+ *
+ * Returns: (transfer none): A string or %NULL if not a string.
+ */
+const char *
+ephy_json_array_get_string (JsonArray *array,
+ guint index)
+{
+ return ephy_json_node_to_string (json_array_get_element (array, index));
+}
+
+/**
+ * ephy_json_array_get_object:
+ * @array: A #JsonArray
+ *
+ * This safely gets an object from an array.
+ *
+ * Returns: (transfer none): An object or %NULL if not a object.
+ */
+JsonObject *
+ephy_json_array_get_object (JsonArray *array,
+ guint index)
+{
+ return ephy_json_node_get_object (json_array_get_element (array, index));
+}
diff --git a/src/webextension/ephy-json-utils.h b/src/webextension/ephy-json-utils.h
index 967978a1b..04c9f42f5 100644
--- a/src/webextension/ephy-json-utils.h
+++ b/src/webextension/ephy-json-utils.h
@@ -44,3 +44,9 @@ JsonObject *ephy_json_object_get_object (JsonObject *object
JsonObject *ephy_json_node_get_object (JsonNode *node);
const char *ephy_json_node_to_string (JsonNode *node);
+
+const char *ephy_json_array_get_string (JsonArray *array,
+ guint index);
+
+JsonObject *ephy_json_array_get_object (JsonArray *array,
+ guint index);
diff --git a/src/webextension/ephy-web-extension-manager.c b/src/webextension/ephy-web-extension-manager.c
index dae10db98..1a9975ecc 100644
--- a/src/webextension/ephy-web-extension-manager.c
+++ b/src/webextension/ephy-web-extension-manager.c
@@ -38,6 +38,7 @@
#include "api/alarms.h"
#include "api/cookies.h"
#include "api/downloads.h"
+#include "api/menus.h"
#include "api/notifications.h"
#include "api/pageaction.h"
#include "api/runtime.h"
@@ -72,6 +73,7 @@ EphyWebExtensionAsyncApiHandler api_handlers[] = {
{"alarms", ephy_web_extension_api_alarms_handler},
{"cookies", ephy_web_extension_api_cookies_handler},
{"downloads", ephy_web_extension_api_downloads_handler},
+ {"menus", ephy_web_extension_api_menus_handler},
{"notifications", ephy_web_extension_api_notifications_handler},
{"pageAction", ephy_web_extension_api_pageaction_handler},
{"runtime", ephy_web_extension_api_runtime_handler},
@@ -468,6 +470,33 @@ ephy_web_extension_manager_set_background_web_view (EphyWebExtensionManager *sel
g_hash_table_insert (self->background_web_views, web_extension, web_view);
}
+void
+ephy_web_extension_manager_append_context_menu (EphyWebExtensionManager *self,
+ WebKitWebView *web_view,
+ WebKitContextMenu *context_menu,
+ WebKitHitTestResult *hit_test_result,
+ GdkModifierType modifiers,
+ gboolean is_audio,
+ gboolean is_video)
+{
+ gboolean inserted_separator = FALSE;
+
+ for (guint i = 0; i < self->web_extensions->len; i++) {
+ EphyWebExtension *extension = g_ptr_array_index (self->web_extensions, i);
+ WebKitContextMenuItem *item;
+
+ item = ephy_web_extension_api_menus_create_context_menu (extension, web_view, context_menu,
+ hit_test_result, modifiers, is_audio, is_video);
+ if (item) {
+ if (!inserted_separator) {
+ webkit_context_menu_append (context_menu, webkit_context_menu_item_new_separator ());
+ inserted_separator = TRUE;
+ }
+ webkit_context_menu_append (context_menu, item);
+ }
+ }
+}
+
static gboolean
page_action_clicked (GtkWidget *event_box,
GdkEventButton *event,
@@ -1359,6 +1388,26 @@ ephy_web_extension_manager_handle_notifications_action (EphyWebExtensionManager
}
}
+void
+ephy_web_extension_manager_handle_context_menu_action (EphyWebExtensionManager *self,
+ GVariant *params)
+{
+ EphyWebExtension *web_extension;
+ const char *extension_guid;
+ const char *onclickdata;
+ const char *tabdata;
+ g_autofree char *json = NULL;
+
+ g_variant_get (params, "(&s&s&s)", &extension_guid, &onclickdata, &tabdata);
+
+ web_extension = ephy_web_extension_manager_get_extension_by_guid (self, extension_guid);
+ if (!web_extension)
+ return;
+
+ json = g_strconcat (onclickdata, ", ", tabdata, NULL);
+ ephy_web_extension_manager_emit_in_extension_views (self, web_extension, "menus.onClicked", json);
+}
+
static void
handle_message_reply (EphyWebExtension *web_extension,
JSCValue *args)
diff --git a/src/webextension/ephy-web-extension-manager.h b/src/webextension/ephy-web-extension-manager.h
index 92a88f5da..dc66a6991 100644
--- a/src/webextension/ephy-web-extension-manager.h
+++ b/src/webextension/ephy-web-extension-manager.h
@@ -74,6 +74,9 @@ WebKitWebView *ephy_web_extension_manager_get_background_web_view
void ephy_web_extension_manager_handle_notifications_action (EphyWebExtensionManager
*self,
GVariant
*params);
+void ephy_web_extension_manager_handle_context_menu_action (EphyWebExtensionManager
*self,
+ GVariant
*params);
+
void ephy_web_extension_manager_emit_in_extension_views (EphyWebExtensionManager
*self,
EphyWebExtension
*web_extension,
const char
*name,
@@ -108,4 +111,12 @@ void ephy_web_extension_manager_foreach_extension
EphyWebExtensionForeachFunc func,
gpointer
user_data);
+void ephy_web_extension_manager_append_context_menu (EphyWebExtensionManager
*self,
+ WebKitWebView
*web_view,
+ WebKitContextMenu
*context_menu,
+ WebKitHitTestResult
*hit_test_result,
+ GdkModifierType
modifiers,
+ gboolean
is_audio,
+ gboolean
is_video);
+
G_END_DECLS
diff --git a/src/webextension/ephy-web-extension.c b/src/webextension/ephy-web-extension.c
index da364676e..07ce43548 100644
--- a/src/webextension/ephy-web-extension.c
+++ b/src/webextension/ephy-web-extension.c
@@ -87,6 +87,7 @@ struct _EphyWebExtension {
char *guid;
char *author;
char *name;
+ char *short_name;
char *version;
char *homepage_url;
GList *icons;
@@ -306,6 +307,12 @@ ephy_web_extension_get_name (EphyWebExtension *self)
return self->name;
}
+const char *
+ephy_web_extension_get_short_name (EphyWebExtension *self)
+{
+ return self->short_name ? self->short_name : self->name;
+}
+
const char *
ephy_web_extension_get_version (EphyWebExtension *self)
{
@@ -756,6 +763,7 @@ ephy_web_extension_dispose (GObject *object)
g_clear_pointer (&self->description, g_free);
g_clear_pointer (&self->author, g_free);
g_clear_pointer (&self->name, g_free);
+ g_clear_pointer (&self->short_name, g_free);
g_clear_pointer (&self->version, g_free);
g_clear_pointer (&self->homepage_url, g_free);
g_clear_pointer (&self->local_storage_path, g_free);
@@ -849,6 +857,7 @@ ephy_web_extension_parse_manifest (EphyWebExtension *self,
self->description = ephy_web_extension_manifest_get_localized_string (self, root_object, "description");
self->name = ephy_web_extension_manifest_get_localized_string (self, root_object, "name");
+ self->short_name = ephy_web_extension_manifest_get_localized_string (self, root_object, "short_name");
self->version = ephy_web_extension_manifest_get_localized_string (self, root_object, "version");
self->homepage_url = ephy_web_extension_manifest_get_localized_string (self, root_object, "homepage_url");
self->author = ephy_web_extension_manifest_get_localized_string (self, root_object, "author");
@@ -1421,44 +1430,44 @@ is_default_port (const char *scheme,
return FALSE;
}
-static gboolean
-permission_matches_uri (const char *permission,
- GUri *uri)
+gboolean
+ephy_web_extension_rule_matches_uri (const char *rule,
+ GUri *uri)
{
- g_autoptr (GUri) permission_uri = NULL;
+ g_autoptr (GUri) rule_uri = NULL;
g_autoptr (GError) error = NULL;
- g_autofree char *permission_path_and_query = NULL;
+ g_autofree char *rule_path_and_query = NULL;
g_autofree char *uri_path_and_query = NULL;
- const char *permission_scheme;
- int permission_port;
+ const char *rule_scheme;
+ int rule_port;
- permission_uri = parse_uri_with_wildcard_scheme (permission, &error);
+ rule_uri = parse_uri_with_wildcard_scheme (rule, &error);
if (error) {
- g_message ("Failed to parse permission '%s' as URI: %s", permission, error->message);
+ g_warning ("Failed to parse rule '%s' as URI: %s", rule, error->message);
return FALSE;
}
- permission_scheme = g_uri_get_scheme (permission_uri);
- permission_port = g_uri_get_port (permission_uri);
+ rule_scheme = g_uri_get_scheme (rule_uri);
+ rule_port = g_uri_get_port (rule_uri);
/* Ports are forbidden, however GUri normalizes these to the default. */
- if (permission_port != -1 && !is_default_port (permission_scheme, permission_port))
+ if (rule_port != -1 && !is_default_port (rule_scheme, rule_port))
return FALSE;
/* Empty paths are forbidden. */
- if (strcmp (g_uri_get_path (permission_uri), "") == 0)
+ if (strcmp (g_uri_get_path (rule_uri), "") == 0)
return FALSE;
- if (!scheme_matches (permission_scheme, g_uri_get_scheme (uri)))
+ if (!scheme_matches (rule_scheme, g_uri_get_scheme (uri)))
return FALSE;
- if (!host_matches (g_uri_get_host (permission_uri), g_uri_get_host (uri)))
+ if (!host_matches (g_uri_get_host (rule_uri), g_uri_get_host (uri)))
return FALSE;
- permission_path_and_query = join_path_and_query (permission_uri);
+ rule_path_and_query = join_path_and_query (rule_uri);
uri_path_and_query = join_path_and_query (uri);
- if (!path_matches (permission_path_and_query, uri_path_and_query))
+ if (!path_matches (rule_path_and_query, uri_path_and_query))
return FALSE;
return TRUE;
@@ -1487,7 +1496,7 @@ ephy_web_extension_has_permission_internal (EphyWebExtension *self,
g_assert (host); /* WebKitGTK shouldn't ever expose an invalid URI. */
for (guint i = 0; i < self->host_permissions->len - 1; i++) {
const char *permission = g_ptr_array_index (self->host_permissions, i);
- if (permission_matches_uri (permission, host))
+ if (ephy_web_extension_rule_matches_uri (permission, host))
return TRUE;
}
@@ -1520,7 +1529,7 @@ ephy_web_extension_has_host_permission (EphyWebExtension *self,
for (guint i = 0; i < self->host_permissions->len - 1; i++) {
const char *permission = g_ptr_array_index (self->host_permissions, i);
- if (permission_matches_uri (permission, uri))
+ if (ephy_web_extension_rule_matches_uri (permission, uri))
return TRUE;
}
diff --git a/src/webextension/ephy-web-extension.h b/src/webextension/ephy-web-extension.h
index 5155ef3b0..6339b1b29 100644
--- a/src/webextension/ephy-web-extension.h
+++ b/src/webextension/ephy-web-extension.h
@@ -47,6 +47,12 @@ typedef char *(*executeHandler)(EphyWebExtension *web_extension,
WebKitWebView *web_view,
GError **error);
+typedef void (*EphyApiExecuteFunc)(EphyWebExtension *web_extension,
+ char *name,
+ JsonArray *args,
+ WebKitWebView *web_view,
+ GTask *task);
+
extern GQuark web_extension_error_quark (void);
#define WEB_EXTENSION_ERROR web_extension_error_quark ()
@@ -60,6 +66,11 @@ typedef enum {
WEB_EXTENSION_ERROR_INVALID_HOST = 1006,
} WebExtensionErrorCode;
+typedef struct {
+ char *name;
+ EphyApiExecuteFunc execute;
+} EphyWebExtensionApiHandler;
+
typedef struct {
char *name;
executeTaskHandler execute;
@@ -75,6 +86,8 @@ GdkPixbuf *ephy_web_extension_get_icon (EphyW
const char *ephy_web_extension_get_name (EphyWebExtension *self);
+const char *ephy_web_extension_get_short_name (EphyWebExtension *self);
+
const char *ephy_web_extension_get_version (EphyWebExtension *self);
const char *ephy_web_extension_get_description (EphyWebExtension *self);
@@ -168,7 +181,9 @@ void ephy_web_extension_save_local_storage (EphyW
void ephy_web_extension_clear_local_storage (EphyWebExtension *self);
char *ephy_web_extension_create_sender_object (EphyWebExtension *self,
- WebKitWebView *web_view);
+ WebKitWebView *web_view);
+gboolean ephy_web_extension_rule_matches_uri (const char *rule,
+ GUri *uri);
gboolean ephy_web_extension_has_web_accessible_resource (EphyWebExtension *self,
const char *path);
diff --git a/src/webextension/meson.build b/src/webextension/meson.build
index 0ed2fceaf..006ea40c3 100644
--- a/src/webextension/meson.build
+++ b/src/webextension/meson.build
@@ -3,6 +3,7 @@ ephywebextension_src = [
'webextension/api/api-utils.c',
'webextension/api/cookies.c',
'webextension/api/downloads.c',
+ 'webextension/api/menus.c',
'webextension/api/notifications.c',
'webextension/api/pageaction.c',
'webextension/api/runtime.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]