summaryrefslogtreecommitdiff
path: root/embed
diff options
context:
space:
mode:
authorJan-Michael Brummer <jan.brummer@tabos.org>2019-05-02 09:53:25 +0200
committerJan-Michael Brummer <jan.brummer@tabos.org>2021-01-17 13:55:36 +0100
commit5410fec78bc699b593d327e6a98b105f6cef7cd5 (patch)
tree7e7f379bc6e9677d0ea8015c5c97996d9d49f920 /embed
parentda8757af88925c803be2201784a6821410150608 (diff)
downloadepiphany-5410fec78bc699b593d327e6a98b105f6cef7cd5.tar.gz
Add initial WebExtension support
Diffstat (limited to 'embed')
-rw-r--r--embed/ephy-web-view.c30
-rw-r--r--embed/ephy-web-view.h4
-rw-r--r--embed/web-process-extension/ephy-web-process-extension-main.c4
-rw-r--r--embed/web-process-extension/ephy-web-process-extension.c37
-rw-r--r--embed/web-process-extension/ephy-web-process-extension.h4
-rw-r--r--embed/web-process-extension/ephy-webextension-api.c170
-rw-r--r--embed/web-process-extension/ephy-webextension-api.h37
-rw-r--r--embed/web-process-extension/meson.build1
-rw-r--r--embed/web-process-extension/resources/epiphany-web-process-extension.gresource.xml1
-rw-r--r--embed/web-process-extension/resources/js/webextensions.js115
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;
+