diff options
author | Jan-Michael Brummer <jan.brummer@tabos.org> | 2019-05-02 09:53:25 +0200 |
---|---|---|
committer | Jan-Michael Brummer <jan.brummer@tabos.org> | 2021-01-17 13:55:36 +0100 |
commit | 5410fec78bc699b593d327e6a98b105f6cef7cd5 (patch) | |
tree | 7e7f379bc6e9677d0ea8015c5c97996d9d49f920 /embed | |
parent | da8757af88925c803be2201784a6821410150608 (diff) | |
download | epiphany-5410fec78bc699b593d327e6a98b105f6cef7cd5.tar.gz |
Add initial WebExtension support
Diffstat (limited to 'embed')
-rw-r--r-- | embed/ephy-web-view.c | 30 | ||||
-rw-r--r-- | embed/ephy-web-view.h | 4 | ||||
-rw-r--r-- | embed/web-process-extension/ephy-web-process-extension-main.c | 4 | ||||
-rw-r--r-- | embed/web-process-extension/ephy-web-process-extension.c | 37 | ||||
-rw-r--r-- | embed/web-process-extension/ephy-web-process-extension.h | 4 | ||||
-rw-r--r-- | embed/web-process-extension/ephy-webextension-api.c | 170 | ||||
-rw-r--r-- | embed/web-process-extension/ephy-webextension-api.h | 37 | ||||
-rw-r--r-- | embed/web-process-extension/meson.build | 1 | ||||
-rw-r--r-- | embed/web-process-extension/resources/epiphany-web-process-extension.gresource.xml | 1 | ||||
-rw-r--r-- | embed/web-process-extension/resources/js/webextensions.js | 115 |
10 files changed, 401 insertions, 2 deletions
diff --git a/embed/ephy-web-view.c b/embed/ephy-web-view.c index 1b5f04c66..814d05989 100644 --- a/embed/ephy-web-view.c +++ b/embed/ephy-web-view.c @@ -67,6 +67,8 @@ #define EPHY_PAGE_TEMPLATE_ERROR "/org/gnome/epiphany/page-templates/error.html" #define EPHY_PAGE_TEMPLATE_ERROR_CSS "/org/gnome/epiphany/page-templates/error.css" +static guint64 web_view_uid = 1; + struct _EphyWebView { WebKitWebView parent_instance; @@ -125,6 +127,8 @@ struct _EphyWebView { char *tls_error_failing_uri; EphyWebViewErrorPage error_page; + + guint64 uid; }; enum { @@ -1427,6 +1431,11 @@ update_security_status_for_committed_load (EphyWebView *view, if (view->loading_error_page) return; + if (g_str_has_prefix (uri, "webextension://")) { + /* Hidden WebExtension webview, ignoring */ + return; + } + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view)); if (EPHY_IS_EMBED_CONTAINER (toplevel)) embed = EPHY_GET_EMBED_FROM_EPHY_WEB_VIEW (view); @@ -2725,6 +2734,19 @@ scale_factor_changed_cb (EphyWebView *web_view, _ephy_web_view_update_icon (web_view); } +GtkWidget * +ephy_web_view_new_with_user_content_manager (WebKitUserContentManager *ucm) +{ + EphyEmbedShell *shell = ephy_embed_shell_get_default (); + + return g_object_new (EPHY_TYPE_WEB_VIEW, + "web-context", ephy_embed_shell_get_web_context (shell), + "user-content-manager", ucm, + "settings", ephy_embed_prefs_get_settings (), + "is-controlled-by-automation", ephy_embed_shell_get_mode (shell) == EPHY_EMBED_SHELL_MODE_AUTOMATION, + NULL); +} + /** * ephy_web_view_load_request: * @view: the #EphyWebView in which to load the request @@ -3771,6 +3793,8 @@ ephy_web_view_init (EphyWebView *web_view) shell = ephy_embed_shell_get_default (); + web_view->uid = web_view_uid++; + web_view->is_blank = TRUE; web_view->ever_committed = FALSE; web_view->document_type = EPHY_WEB_VIEW_DOCUMENT_HTML; @@ -4120,3 +4144,9 @@ ephy_web_view_new_with_related_view (WebKitWebView *related_view) "settings", ephy_embed_prefs_get_settings (), NULL); } + +guint64 +ephy_web_view_get_uid (EphyWebView *web_view) +{ + return web_view->uid; +} diff --git a/embed/ephy-web-view.h b/embed/ephy-web-view.h index 45a58b2d3..0153b4b2f 100644 --- a/embed/ephy-web-view.h +++ b/embed/ephy-web-view.h @@ -182,4 +182,8 @@ void ephy_web_view_show_auth_form_save_request (EphyWebVie gpointer response_data, GDestroyNotify response_destroy); +GtkWidget *ephy_web_view_new_with_user_content_manager (WebKitUserContentManager *ucm); + +guint64 ephy_web_view_get_uid (EphyWebView *web_view); + G_END_DECLS diff --git a/embed/web-process-extension/ephy-web-process-extension-main.c b/embed/web-process-extension/ephy-web-process-extension-main.c index 3a155a13c..8f3298198 100644 --- a/embed/web-process-extension/ephy-web-process-extension-main.c +++ b/embed/web-process-extension/ephy-web-process-extension-main.c @@ -63,8 +63,10 @@ webkit_web_extension_initialize_with_user_data (WebKitWebExtension *webkit_exten static void __attribute__((destructor)) ephy_web_process_extension_shutdown (void) { - if (extension) + if (extension) { + ephy_web_process_extension_deinitialize (extension); g_object_unref (extension); + } ephy_settings_shutdown (); ephy_file_helpers_shutdown (); diff --git a/embed/web-process-extension/ephy-web-process-extension.c b/embed/web-process-extension/ephy-web-process-extension.c index 54392025a..de1cd6302 100644 --- a/embed/web-process-extension/ephy-web-process-extension.c +++ b/embed/web-process-extension/ephy-web-process-extension.c @@ -20,6 +20,7 @@ #include "config.h" #include "ephy-web-process-extension.h" +#include "ephy-webextension-api.h" #include "ephy-debug.h" #include "ephy-file-helpers.h" @@ -56,10 +57,17 @@ struct _EphyWebProcessExtension { gboolean is_private_profile; GHashTable *frames_map; + GHashTable *translation_table; }; G_DEFINE_TYPE (EphyWebProcessExtension, ephy_web_process_extension, G_TYPE_OBJECT) +GHashTable * +ephy_web_process_extension_get_translations (EphyWebProcessExtension *extension) +{ + return extension->translation_table; +} + static void web_page_will_submit_form (WebKitWebPage *web_page, WebKitDOMHTMLFormElement *dom_form, @@ -326,6 +334,18 @@ ephy_web_process_extension_user_message_received_cb (EphyWebProcessExtension *ex return; g_variant_get (parameters, "b", &extension->should_remember_passwords); + } else if (g_strcmp0 (name, "WebExtension.Add") == 0) { + GVariant *parameters; + const char *name; + const char *data; + guint64 length; + + parameters = webkit_user_message_get_parameters (message); + if (!parameters) + return; + + g_variant_get (parameters, "(&s&st)", &name, &data, &length); + webextensions_add_translation (extension, name, data, length); } } @@ -645,6 +665,8 @@ window_object_cleared_cb (WebKitScriptWorld *world, js_context = webkit_frame_get_js_context_for_script_world (frame, world); jsc_context_push_exception_handler (js_context, (JSCExceptionHandler)js_exception_handler, NULL, NULL); + set_up_webextensions (extension, page, js_context); + bytes = g_resources_lookup_data ("/org/gnome/epiphany-web-process-extension/js/ephy.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-process-extension/js/ephy.js", 1); @@ -771,7 +793,12 @@ ephy_web_process_extension_initialize (EphyWebProcessExtension *extension, extension->initialized = TRUE; - extension->script_world = webkit_script_world_new_with_name (guid); + /* Note: An empty guid is used ONLY for WebExtensions which do have an own initialization function */ + if (strlen (guid) > 0) + extension->script_world = webkit_script_world_new_with_name (guid); + else + extension->script_world = webkit_script_world_get_default (); + g_signal_connect (extension->script_world, "window-object-cleared", G_CALLBACK (window_object_cleared_cb), @@ -793,4 +820,12 @@ ephy_web_process_extension_initialize (EphyWebProcessExtension *extension, extension->frames_map = g_hash_table_new_full (g_int64_hash, g_int64_equal, g_free, NULL); + + extension->translation_table = g_hash_table_new (g_str_hash, NULL); +} + +void +ephy_web_process_extension_deinitialize (EphyWebProcessExtension *extension) +{ + g_clear_pointer (&extension->translation_table, g_hash_table_destroy); } diff --git a/embed/web-process-extension/ephy-web-process-extension.h b/embed/web-process-extension/ephy-web-process-extension.h index 45467dcf2..faa577c6f 100644 --- a/embed/web-process-extension/ephy-web-process-extension.h +++ b/embed/web-process-extension/ephy-web-process-extension.h @@ -36,4 +36,8 @@ void ephy_web_process_extension_initialize (EphyWebProcessEx gboolean should_remember_passwords, gboolean is_private_profile); +void ephy_web_process_extension_deinitialize (EphyWebProcessExtension *extension); + +GHashTable *ephy_web_process_extension_get_translations (EphyWebProcessExtension *extension); + G_END_DECLS diff --git a/embed/web-process-extension/ephy-webextension-api.c b/embed/web-process-extension/ephy-webextension-api.c new file mode 100644 index 000000000..281cc4b29 --- /dev/null +++ b/embed/web-process-extension/ephy-webextension-api.c @@ -0,0 +1,170 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright © 2020 Jan-Michael Brummer <jan.brummer@tabos.org> + * + * 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-web-process-extension.h" + +#include <locale.h> +#include <json-glib/json-glib.h> +#include <webkit2/webkit-web-extension.h> +#include <JavaScriptCore/JavaScript.h> + +static char * +js_getmessage (const char *message, + gpointer user_data) +{ + EphyWebProcessExtension *extension = EPHY_WEB_PROCESS_EXTENSION (user_data); + GHashTable *translations = ephy_web_process_extension_get_translations (extension); + JsonObject *translation = NULL; + g_autoptr (JsonObject) name = NULL; + GList *list = NULL; + + if (!extension) + return g_strdup (message); + + list = g_hash_table_get_values (translations); + if (list && list->data) + translation = list->data; + + if (!translation) { + return g_strdup (message); + } + + name = json_object_get_object_member (translation, message); + if (name) { + const char *trans = json_object_get_string_member (name, "message"); + return g_strdup (trans); + } + + return g_strdup (message); +} + +static char * +js_getuilanguage (void) +{ + char *locale = setlocale (LC_MESSAGES, NULL); + + if (locale) { + locale[2] = '\0'; + + return g_strdup (locale); + } + + return g_strdup ("en"); +} + +static char * +js_geturl (const char *path, + gpointer user_data) +{ + return g_strdup_printf ("webextension:///%s", path); +} + +void +set_up_webextensions (EphyWebProcessExtension *extension, + WebKitWebPage *page, + JSCContext *js_context) +{ + g_autoptr (JSCValue) js_browser = NULL; + g_autoptr (JSCValue) js_i18n = NULL; + g_autoptr (JSCValue) js_extension = NULL; + g_autoptr (JSCValue) js_function = NULL; + g_autoptr (GBytes) bytes = NULL; + g_autoptr (JSCValue) result = NULL; + const char *data; + gsize data_size; + static gboolean setup = FALSE; + + if (setup) + return; + + setup = TRUE; + + js_browser = jsc_value_new_object (js_context, NULL, NULL); + jsc_context_set_value (js_context, "browser", js_browser); + + /* i18n */ + js_i18n = jsc_value_new_object (js_context, NULL, NULL); + jsc_value_object_set_property (js_browser, "i18n", js_i18n); + + js_function = jsc_value_new_function (js_context, + "getUILanguage", + G_CALLBACK (js_getuilanguage), extension, NULL, + G_TYPE_STRING, + 0); + jsc_value_object_set_property (js_i18n, "getUILanguage", js_function); + g_clear_object (&js_function); + + js_function = jsc_value_new_function (js_context, + "getMessage", + G_CALLBACK (js_getmessage), extension, NULL, + G_TYPE_STRING, 1, + G_TYPE_STRING); + jsc_value_object_set_property (js_i18n, "getMessage", js_function); + g_clear_object (&js_function); + + /* extension */ + js_extension = jsc_value_new_object (js_context, NULL, NULL); + jsc_value_object_set_property (js_browser, "extension", js_extension); + + js_function = jsc_value_new_function (js_context, + "getURL", + G_CALLBACK (js_geturl), extension, NULL, + G_TYPE_STRING, + 1, + G_TYPE_STRING); + jsc_value_object_set_property (js_extension, "getURL", js_function); + g_clear_object (&js_function); + + bytes = g_resources_lookup_data ("/org/gnome/epiphany-web-process-extension/js/webextensions.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-process-extension/js/webextensions.js", 1); + g_clear_object (&result); +} + +void +webextensions_add_translation (EphyWebProcessExtension *extension, + const char *name, + const char *data, + guint64 length) +{ + GHashTable *translations = ephy_web_process_extension_get_translations (extension); + JsonParser *parser = NULL; + JsonNode *root; + JsonObject *root_object; + g_autoptr (GError) error = NULL; + + g_hash_table_remove (translations, name); + + if (!data || strlen (data) == 0) + return; + + parser = json_parser_new (); + if (json_parser_load_from_data (parser, data, length, &error)) { + root = json_parser_get_root (parser); + g_assert (root); + root_object = json_node_get_object (root); + g_assert (root_object); + + g_hash_table_insert (translations, (char *)name, json_object_ref (root_object)); + } else { + g_warning ("Could not read translation json data: %s. '%s'", error->message, data); + } +} diff --git a/embed/web-process-extension/ephy-webextension-api.h b/embed/web-process-extension/ephy-webextension-api.h new file mode 100644 index 000000000..64e21e30f --- /dev/null +++ b/embed/web-process-extension/ephy-webextension-api.h @@ -0,0 +1,37 @@ + /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright © 2020 Jan-Michael Brummer <jan.brummer@tabos.org> + * + * 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 <glib-object.h> +#include <jsc/jsc.h> + +G_BEGIN_DECLS + +void set_up_webextensions (EphyWebProcessExtension *extension, + WebKitWebPage *page, + JSCContext *js_context); + +void webextensions_add_translation (EphyWebProcessExtension *extension, + const char *name, + const char *data, + guint64 length); + +G_END_DECLS diff --git a/embed/web-process-extension/meson.build b/embed/web-process-extension/meson.build index 398aa5ad1..b50cb4079 100644 --- a/embed/web-process-extension/meson.build +++ b/embed/web-process-extension/meson.build @@ -9,6 +9,7 @@ web_process_extension_sources = [ 'ephy-web-process-extension.c', 'ephy-web-process-extension-main.c', 'ephy-web-overview-model.c', + 'ephy-webextension-api.c', resources ] diff --git a/embed/web-process-extension/resources/epiphany-web-process-extension.gresource.xml b/embed/web-process-extension/resources/epiphany-web-process-extension.gresource.xml index 5c0d16f59..8cdf15d0c 100644 --- a/embed/web-process-extension/resources/epiphany-web-process-extension.gresource.xml +++ b/embed/web-process-extension/resources/epiphany-web-process-extension.gresource.xml @@ -3,5 +3,6 @@ <gresource prefix="/org/gnome/epiphany-web-process-extension"> <file compressed="true">js/ephy.js</file> <file compressed="true">js/overview.js</file> + <file compressed="true">js/webextensions.js</file> </gresource> </gresources> diff --git a/embed/web-process-extension/resources/js/webextensions.js b/embed/web-process-extension/resources/js/webextensions.js new file mode 100644 index 000000000..1ed0ff801 --- /dev/null +++ b/embed/web-process-extension/resources/js/webextensions.js @@ -0,0 +1,115 @@ +'use strict'; + +let promises = []; +let last_promise = 0; + +let tabs_listeners = []; +let page_listeners = []; +let browser_listeners = []; +let runtime_listeners = []; +let runtime_onmessage_listeners = []; +let runtime_onmessageexternal_listeners = []; +let runtime_onconnect_listeners = []; +let windows_onremoved_listeners = []; + +let ephy_message = function (fn, args, cb) { + let 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}); + }); + return promise; +} + +let pageActionOnClicked = function(x) { + for (let listener of page_listeners) + listener.callback(x); +} + +let browserActionClicked = function(x) { + for (let listener of browser_listeners) + listener.callback(x); +} + +let tabsOnUpdated = function(x) { + for (let listener of tabs_listeners) + listener.callback(x); +} + +let runtimeSendMessage = function(x) { + for (let listener of runtime_onmessage_listeners) + listener.callback(x); +} + +let runtimeOnConnect = function(x) { + for (let listener of runtime_onconnect_listeners) + listener.callback(x); +} + +// Browser async API +window.browser.alarms = { + clearAll: function (args, cb) { return ephy_message ('alarms.clearAll', args, cb); }, +}; + +window.browser.windows = { + onRemoved: { + addListener: function (cb) { windows_onremoved_listeners.push({callback: cb}) } + } +}; + +window.browser.tabs = { + create: function (args, cb) { return ephy_message ('tabs.create', args, cb); }, + executeScript: function (...args) { return ephy_message ('tabs.executeScript', args, null); }, + query: function (args, cb) { return ephy_message ('tabs.query', args, cb); }, + get: function (args, cb) { return ephy_message ('tabs.get', args, cb); }, + insertCSS: function (args, cb) { return ephy_message ('tabs.insertCSS', args, cb); }, + removeCSS: function (args, cb) { return ephy_message ('tabs.removeCSS', args, cb); }, + onUpdated: { + addListener: function (cb) { tabs_listeners.push({callback: cb}) } + } +}; + +window.browser.notifications = { + create: function (args, cb) { return ephy_message ('notifications.create', args, cb); }, +}; + +window.browser.runtime = { + getManifest: function (args, cb) { return "[]"; }, + getBrowserInfo: function (args, cb) { return ephy_message ('runtime.getBrowserInfo', args, cb); }, + onInstalled: { + addListener: function (cb) { runtime_listeners.push({callback: cb}); } + }, + onMessage: { + addListener: function (cb) { runtime_onmessage_listeners.push({callback: cb}); } + }, + onMessageExternal: { + addListener: function (cb) { runtime_onmessageexternal_listeners.push({callback: cb}); } + }, + onConnect: { + addListener: function (cb) { runtime_onconnect_listeners.push({callback: cb}); } + }, + connectNative: function (args, cb) { return ephy_message ('runtime.connectNative', args, cb); }, + sendMessage: function (args, cb) { return ephy_message ('runtime.sendMessage', args, cb); }, + openOptionsPage: function (args, cb) { return ephy_message ('runtime.openOptionsPage', args, cb); }, + setUninstallURL: function (args, cb) { return ephy_message ('runtime.setUninstallURL', args, cb); }, +}; + +window.browser.pageAction = { + setIcon: function (args, cb) { return ephy_message ('pageAction.setIcon', args, cb); }, + setTitle: function (args, cb) { return ephy_message ('pageAction.setTitle', args, cb); }, + getTitle: function (args, cb) { return ephy_message ('pageAction.getTitle', args, cb); }, + show: function (args, cb) { return ephy_message ('pageAction.show', args, cb); }, + hide: function (args, cb) { return ephy_message ('pageAction.hide', args, cb); }, + onClicked: { + addListener: function (cb) { page_listeners.push({callback: cb}); } + } +}; + +window.browser.browserAction = { + onClicked: { + addListener: function (cb) { browser_listeners.push({callback: cb}); } + } +}; + +// Compatibility with Chrome +window.chrome = window.browser; + |