Codebase list epiphany-browser / 5410fec
Add initial WebExtension support Jan-Michael Brummer 3 years ago
50 changed file(s) with 4211 addition(s) and 48 deletion(s). Raw diff Collapse all Expand all
1010 # Translators: Do NOT translate or transliterate this text (this is an icon file name)!
1111 Icon=@icon@
1212 Categories=Network;GNOME;GTK;WebBrowser;
13 MimeType=text/html;text/xml;application/xhtml+xml;x-scheme-handler/http;x-scheme-handler/https;multipart/related;application/x-mimearchive;message/rfc822;
13 MimeType=text/html;text/xml;application/xhtml+xml;x-scheme-handler/http;x-scheme-handler/https;multipart/related;application/x-mimearchive;message/rfc822;application/x-xpinstall;
1414 Actions=new-window;Incognito;
1515 # Translators: Do NOT translate or transliterate this text (these are enum types)!
1616 X-Purism-FormFactor=Workstation;Mobile;
244244 <summary>Enable immediately switch to new open tab</summary>
245245 <description>Whether to automatically switch to a new open tab.</description>
246246 </key>
247 <key type="b" name="enable-webextensions">
248 <default>false</default>
249 <summary>Enable WebExtensions</summary>
250 <description>Whether to enable WebExtensions. WebExtensions is a cross-browser system for extensions.</description>
251 </key>
252 <key type="as" name="webextensions-active">
253 <default>[]</default>
254 <summary>Active WebExtensions</summary>
255 <description>Indicates which WebExtensions are set to active.</description>
256 </key>
247257 </schema>
248258 <schema id="org.gnome.Epiphany.webapp">
249259 <key type="as" name="additional-urls">
6666 #define EPHY_PAGE_TEMPLATE_ERROR "/org/gnome/epiphany/page-templates/error.html"
6767 #define EPHY_PAGE_TEMPLATE_ERROR_CSS "/org/gnome/epiphany/page-templates/error.css"
6868
69 static guint64 web_view_uid = 1;
70
6971 struct _EphyWebView {
7072 WebKitWebView parent_instance;
7173
124126 char *tls_error_failing_uri;
125127
126128 EphyWebViewErrorPage error_page;
129
130 guint64 uid;
127131 };
128132
129133 enum {
14251429
14261430 if (view->loading_error_page)
14271431 return;
1432
1433 if (g_str_has_prefix (uri, "webextension://")) {
1434 /* Hidden WebExtension webview, ignoring */
1435 return;
1436 }
14281437
14291438 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
14301439 if (EPHY_IS_EMBED_CONTAINER (toplevel))
27242733 _ephy_web_view_update_icon (web_view);
27252734 }
27262735
2736 GtkWidget *
2737 ephy_web_view_new_with_user_content_manager (WebKitUserContentManager *ucm)
2738 {
2739 EphyEmbedShell *shell = ephy_embed_shell_get_default ();
2740
2741 return g_object_new (EPHY_TYPE_WEB_VIEW,
2742 "web-context", ephy_embed_shell_get_web_context (shell),
2743 "user-content-manager", ucm,
2744 "settings", ephy_embed_prefs_get_settings (),
2745 "is-controlled-by-automation", ephy_embed_shell_get_mode (shell) == EPHY_EMBED_SHELL_MODE_AUTOMATION,
2746 NULL);
2747 }
2748
27272749 /**
27282750 * ephy_web_view_load_request:
27292751 * @view: the #EphyWebView in which to load the request
37703792
37713793 shell = ephy_embed_shell_get_default ();
37723794
3795 web_view->uid = web_view_uid++;
3796
37733797 web_view->is_blank = TRUE;
37743798 web_view->ever_committed = FALSE;
37753799 web_view->document_type = EPHY_WEB_VIEW_DOCUMENT_HTML;
41194143 "settings", ephy_embed_prefs_get_settings (),
41204144 NULL);
41214145 }
4146
4147 guint64
4148 ephy_web_view_get_uid (EphyWebView *web_view)
4149 {
4150 return web_view->uid;
4151 }
181181 gpointer response_data,
182182 GDestroyNotify response_destroy);
183183
184 GtkWidget *ephy_web_view_new_with_user_content_manager (WebKitUserContentManager *ucm);
185
186 guint64 ephy_web_view_get_uid (EphyWebView *web_view);
187
184188 G_END_DECLS
6262 static void __attribute__((destructor))
6363 ephy_web_process_extension_shutdown (void)
6464 {
65 if (extension)
65 if (extension) {
66 ephy_web_process_extension_deinitialize (extension);
6667 g_object_unref (extension);
68 }
6769
6870 ephy_settings_shutdown ();
6971 ephy_file_helpers_shutdown ();
1919
2020 #include "config.h"
2121 #include "ephy-web-process-extension.h"
22 #include "ephy-webextension-api.h"
2223
2324 #include "ephy-debug.h"
2425 #include "ephy-file-helpers.h"
5556 gboolean is_private_profile;
5657
5758 GHashTable *frames_map;
59 GHashTable *translation_table;
5860 };
5961
6062 G_DEFINE_TYPE (EphyWebProcessExtension, ephy_web_process_extension, G_TYPE_OBJECT)
63
64 GHashTable *
65 ephy_web_process_extension_get_translations (EphyWebProcessExtension *extension)
66 {
67 return extension->translation_table;
68 }
6169
6270 static void
6371 web_page_will_submit_form (WebKitWebPage *web_page,
325333 return;
326334
327335 g_variant_get (parameters, "b", &extension->should_remember_passwords);
336 } else if (g_strcmp0 (name, "WebExtension.Add") == 0) {
337 GVariant *parameters;
338 const char *name;
339 const char *data;
340 guint64 length;
341
342 parameters = webkit_user_message_get_parameters (message);
343 if (!parameters)
344 return;
345
346 g_variant_get (parameters, "(&s&st)", &name, &data, &length);
347 webextensions_add_translation (extension, name, data, length);
328348 }
329349 }
330350
643663
644664 js_context = webkit_frame_get_js_context_for_script_world (frame, world);
645665 jsc_context_push_exception_handler (js_context, (JSCExceptionHandler)js_exception_handler, NULL, NULL);
666
667 set_up_webextensions (extension, page, js_context);
646668
647669 bytes = g_resources_lookup_data ("/org/gnome/epiphany-web-process-extension/js/ephy.js", G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
648670 data = g_bytes_get_data (bytes, &data_size);
770792
771793 extension->initialized = TRUE;
772794
773 extension->script_world = webkit_script_world_new_with_name (guid);
795 /* Note: An empty guid is used ONLY for WebExtensions which do have an own initialization function */
796 if (strlen (guid) > 0)
797 extension->script_world = webkit_script_world_new_with_name (guid);
798 else
799 extension->script_world = webkit_script_world_get_default ();
800
774801 g_signal_connect (extension->script_world,
775802 "window-object-cleared",
776803 G_CALLBACK (window_object_cleared_cb),
792819
793820 extension->frames_map = g_hash_table_new_full (g_int64_hash, g_int64_equal,
794821 g_free, NULL);
795 }
822
823 extension->translation_table = g_hash_table_new (g_str_hash, NULL);
824 }
825
826 void
827 ephy_web_process_extension_deinitialize (EphyWebProcessExtension *extension)
828 {
829 g_clear_pointer (&extension->translation_table, g_hash_table_destroy);
830 }
3535 gboolean should_remember_passwords,
3636 gboolean is_private_profile);
3737
38 void ephy_web_process_extension_deinitialize (EphyWebProcessExtension *extension);
39
40 GHashTable *ephy_web_process_extension_get_translations (EphyWebProcessExtension *extension);
41
3842 G_END_DECLS
0 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
1 /*
2 * Copyright © 2020 Jan-Michael Brummer <jan.brummer@tabos.org>
3 *
4 * This file is part of Epiphany.
5 *
6 * Epiphany is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Epiphany is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "config.h"
21 #include "ephy-web-process-extension.h"
22
23 #include <locale.h>
24 #include <json-glib/json-glib.h>
25 #include <webkit2/webkit-web-extension.h>
26 #include <JavaScriptCore/JavaScript.h>
27
28 static char *
29 js_getmessage (const char *message,
30 gpointer user_data)
31 {
32 EphyWebProcessExtension *extension = EPHY_WEB_PROCESS_EXTENSION (user_data);
33 GHashTable *translations = ephy_web_process_extension_get_translations (extension);
34 JsonObject *translation = NULL;
35 g_autoptr (JsonObject) name = NULL;
36 GList *list = NULL;
37
38 if (!extension)
39 return g_strdup (message);
40
41 list = g_hash_table_get_values (translations);
42 if (list && list->data)
43 translation = list->data;
44
45 if (!translation) {
46 return g_strdup (message);
47 }
48
49 name = json_object_get_object_member (translation, message);
50 if (name) {
51 const char *trans = json_object_get_string_member (name, "message");
52 return g_strdup (trans);
53 }
54
55 return g_strdup (message);
56 }
57
58 static char *
59 js_getuilanguage (void)
60 {
61 char *locale = setlocale (LC_MESSAGES, NULL);
62
63 if (locale) {
64 locale[2] = '\0';
65
66 return g_strdup (locale);
67 }
68
69 return g_strdup ("en");
70 }
71
72 static char *
73 js_geturl (const char *path,
74 gpointer user_data)
75 {
76 return g_strdup_printf ("webextension:///%s", path);
77 }
78
79 void
80 set_up_webextensions (EphyWebProcessExtension *extension,
81 WebKitWebPage *page,
82 JSCContext *js_context)
83 {
84 g_autoptr (JSCValue) js_browser = NULL;
85 g_autoptr (JSCValue) js_i18n = NULL;
86 g_autoptr (JSCValue) js_extension = NULL;
87 g_autoptr (JSCValue) js_function = NULL;
88 g_autoptr (GBytes) bytes = NULL;
89 g_autoptr (JSCValue) result = NULL;
90 const char *data;
91 gsize data_size;
92 static gboolean setup = FALSE;
93
94 if (setup)
95 return;
96
97 setup = TRUE;
98
99 js_browser = jsc_value_new_object (js_context, NULL, NULL);
100 jsc_context_set_value (js_context, "browser", js_browser);
101
102 /* i18n */
103 js_i18n = jsc_value_new_object (js_context, NULL, NULL);
104 jsc_value_object_set_property (js_browser, "i18n", js_i18n);
105
106 js_function = jsc_value_new_function (js_context,
107 "getUILanguage",
108 G_CALLBACK (js_getuilanguage), extension, NULL,
109 G_TYPE_STRING,
110 0);
111 jsc_value_object_set_property (js_i18n, "getUILanguage", js_function);
112 g_clear_object (&js_function);
113
114 js_function = jsc_value_new_function (js_context,
115 "getMessage",
116 G_CALLBACK (js_getmessage), extension, NULL,
117 G_TYPE_STRING, 1,
118 G_TYPE_STRING);
119 jsc_value_object_set_property (js_i18n, "getMessage", js_function);
120 g_clear_object (&js_function);
121
122 /* extension */
123 js_extension = jsc_value_new_object (js_context, NULL, NULL);
124 jsc_value_object_set_property (js_browser, "extension", js_extension);
125
126 js_function = jsc_value_new_function (js_context,
127 "getURL",
128 G_CALLBACK (js_geturl), extension, NULL,
129 G_TYPE_STRING,
130 1,
131 G_TYPE_STRING);
132 jsc_value_object_set_property (js_extension, "getURL", js_function);
133 g_clear_object (&js_function);
134
135 bytes = g_resources_lookup_data ("/org/gnome/epiphany-web-process-extension/js/webextensions.js", G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
136 data = g_bytes_get_data (bytes, &data_size);
137 result = jsc_context_evaluate_with_source_uri (js_context, data, data_size, "resource:///org/gnome/epiphany-web-process-extension/js/webextensions.js", 1);
138 g_clear_object (&result);
139 }
140
141 void
142 webextensions_add_translation (EphyWebProcessExtension *extension,
143 const char *name,
144 const char *data,
145 guint64 length)
146 {
147 GHashTable *translations = ephy_web_process_extension_get_translations (extension);
148 JsonParser *parser = NULL;
149 JsonNode *root;
150 JsonObject *root_object;
151 g_autoptr (GError) error = NULL;
152
153 g_hash_table_remove (translations, name);
154
155 if (!data || strlen (data) == 0)
156 return;
157
158 parser = json_parser_new ();
159 if (json_parser_load_from_data (parser, data, length, &error)) {
160 root = json_parser_get_root (parser);
161 g_assert (root);
162 root_object = json_node_get_object (root);
163 g_assert (root_object);
164
165 g_hash_table_insert (translations, (char *)name, json_object_ref (root_object));
166 } else {
167 g_warning ("Could not read translation json data: %s. '%s'", error->message, data);
168 }
169 }
0 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
1 /*
2 * Copyright © 2020 Jan-Michael Brummer <jan.brummer@tabos.org>
3 *
4 * This file is part of Epiphany.
5 *
6 * Epiphany is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Epiphany is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #pragma once
21
22 #include <glib-object.h>
23 #include <jsc/jsc.h>
24
25 G_BEGIN_DECLS
26
27 void set_up_webextensions (EphyWebProcessExtension *extension,
28 WebKitWebPage *page,
29 JSCContext *js_context);
30
31 void webextensions_add_translation (EphyWebProcessExtension *extension,
32 const char *name,
33 const char *data,
34 guint64 length);
35
36 G_END_DECLS
88 'ephy-web-process-extension.c',
99 'ephy-web-process-extension-main.c',
1010 'ephy-web-overview-model.c',
11 'ephy-webextension-api.c',
1112 resources
1213 ]
1314
22 <gresource prefix="/org/gnome/epiphany-web-process-extension">
33 <file compressed="true">js/ephy.js</file>
44 <file compressed="true">js/overview.js</file>
5 <file compressed="true">js/webextensions.js</file>
56 </gresource>
67 </gresources>
0 'use strict';
1
2 let promises = [];
3 let last_promise = 0;
4
5 let tabs_listeners = [];
6 let page_listeners = [];
7 let browser_listeners = [];
8 let runtime_listeners = [];
9 let runtime_onmessage_listeners = [];
10 let runtime_onmessageexternal_listeners = [];
11 let runtime_onconnect_listeners = [];
12 let windows_onremoved_listeners = [];
13
14 let ephy_message = function (fn, args, cb) {
15 let promise = new Promise (function (resolve, reject) {
16 window.webkit.messageHandlers.epiphany.postMessage ({fn: fn, args: args, promise: last_promise});
17 last_promise = promises.push({resolve: resolve, reject: reject});
18 });
19 return promise;
20 }
21
22 let pageActionOnClicked = function(x) {
23 for (let listener of page_listeners)
24 listener.callback(x);
25 }
26
27 let browserActionClicked = function(x) {
28 for (let listener of browser_listeners)
29 listener.callback(x);
30 }
31
32 let tabsOnUpdated = function(x) {
33 for (let listener of tabs_listeners)
34 listener.callback(x);
35 }
36
37 let runtimeSendMessage = function(x) {
38 for (let listener of runtime_onmessage_listeners)
39 listener.callback(x);
40 }
41
42 let runtimeOnConnect = function(x) {
43 for (let listener of runtime_onconnect_listeners)
44 listener.callback(x);
45 }
46
47 // Browser async API
48 window.browser.alarms = {
49 clearAll: function (args, cb) { return ephy_message ('alarms.clearAll', args, cb); },
50 };
51
52 window.browser.windows = {
53 onRemoved: {
54 addListener: function (cb) { windows_onremoved_listeners.push({callback: cb}) }
55 }
56 };
57
58 window.browser.tabs = {
59 create: function (args, cb) { return ephy_message ('tabs.create', args, cb); },
60 executeScript: function (...args) { return ephy_message ('tabs.executeScript', args, null); },
61 query: function (args, cb) { return ephy_message ('tabs.query', args, cb); },
62 get: function (args, cb) { return ephy_message ('tabs.get', args, cb); },
63 insertCSS: function (args, cb) { return ephy_message ('tabs.insertCSS', args, cb); },
64 removeCSS: function (args, cb) { return ephy_message ('tabs.removeCSS', args, cb); },
65 onUpdated: {
66 addListener: function (cb) { tabs_listeners.push({callback: cb}) }
67 }
68 };
69
70 window.browser.notifications = {
71 create: function (args, cb) { return ephy_message ('notifications.create', args, cb); },
72 };
73
74 window.browser.runtime = {
75 getManifest: function (args, cb) { return "[]"; },
76 getBrowserInfo: function (args, cb) { return ephy_message ('runtime.getBrowserInfo', args, cb); },
77 onInstalled: {
78 addListener: function (cb) { runtime_listeners.push({callback: cb}); }
79 },
80 onMessage: {
81 addListener: function (cb) { runtime_onmessage_listeners.push({callback: cb}); }
82 },
83 onMessageExternal: {
84 addListener: function (cb) { runtime_onmessageexternal_listeners.push({callback: cb}); }
85 },
86 onConnect: {
87 addListener: function (cb) { runtime_onconnect_listeners.push({callback: cb}); }
88 },
89 connectNative: function (args, cb) { return ephy_message ('runtime.connectNative', args, cb); },
90 sendMessage: function (args, cb) { return ephy_message ('runtime.sendMessage', args, cb); },
91 openOptionsPage: function (args, cb) { return ephy_message ('runtime.openOptionsPage', args, cb); },
92 setUninstallURL: function (args, cb) { return ephy_message ('runtime.setUninstallURL', args, cb); },
93 };
94
95 window.browser.pageAction = {
96 setIcon: function (args, cb) { return ephy_message ('pageAction.setIcon', args, cb); },
97 setTitle: function (args, cb) { return ephy_message ('pageAction.setTitle', args, cb); },
98 getTitle: function (args, cb) { return ephy_message ('pageAction.getTitle', args, cb); },
99 show: function (args, cb) { return ephy_message ('pageAction.show', args, cb); },
100 hide: function (args, cb) { return ephy_message ('pageAction.hide', args, cb); },
101 onClicked: {
102 addListener: function (cb) { page_listeners.push({callback: cb}); }
103 }
104 };
105
106 window.browser.browserAction = {
107 onClicked: {
108 addListener: function (cb) { browser_listeners.push({callback: cb}); }
109 }
110 };
111
112 // Compatibility with Chrome
113 window.chrome = window.browser;
114
853853
854854 g_free (command);
855855 }
856
857 void
858 ephy_copy_directory (const char *source,
859 const char *target)
860 {
861 g_autoptr (GError) error = NULL;
862 GFileType type;
863 g_autoptr (GFile) src_file = g_file_new_for_path (source);
864 g_autoptr (GFile) dest_file = g_file_new_for_path (target);
865
866 type = g_file_query_file_type (src_file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL);
867
868 if (type == G_FILE_TYPE_DIRECTORY) {
869 g_autoptr (GFileEnumerator) enumerator = NULL;
870 g_autoptr (GFileInfo) info = NULL;
871
872 if (!g_file_make_directory_with_parents (dest_file, NULL, &error)) {
873 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
874 g_warning ("Could not create target directory for webextension: %s", error->message);
875 return;
876 }
877
878 g_error_free (error);
879 }
880
881 if (!g_file_copy_attributes (src_file, dest_file, G_FILE_COPY_NONE, NULL, &error)) {
882 g_warning ("Could not copy file attributes for webextension: %s", error->message);
883 return;
884 }
885
886 enumerator = g_file_enumerate_children (src_file, G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error);
887 if (!enumerator) {
888 g_warning ("Could not create file enumberator for webextensions: %s", error->message);
889 return;
890 }
891
892 for (info = g_file_enumerator_next_file (enumerator, NULL, NULL); info != NULL; info = g_file_enumerator_next_file (enumerator, NULL, NULL)) {
893 ephy_copy_directory (
894 g_build_filename (source, g_file_info_get_name (info), NULL),
895 g_build_filename (target, g_file_info_get_name (info), NULL));
896 }
897 } else if (type == G_FILE_TYPE_REGULAR) {
898 if (!g_file_copy (src_file, dest_file, G_FILE_COPY_NONE, NULL, NULL, NULL, &error)) {
899 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
900 g_warning ("Could not copy file for webextensions: %s", error->message);
901 return;
902 }
903 }
904 }
905 }
8686 gboolean ephy_file_browse_to (GFile *file,
8787 guint32 user_time);
8888
89 void ephy_copy_directory (const char *source,
90 const char *target);
91
8992 G_END_DECLS
116116 #define EPHY_PREFS_WEB_HARDWARE_ACCELERATION_POLICY "hardware-acceleration-policy"
117117 #define EPHY_PREFS_WEB_ASK_ON_DOWNLOAD "ask-on-download"
118118 #define EPHY_PREFS_WEB_SWITCH_TO_NEW_TAB "switch-to-new-tab"
119 #define EPHY_PREFS_WEB_ENABLE_WEBEXTENSIONS "enable-webextensions"
120 #define EPHY_PREFS_WEB_WEBEXTENSIONS_ACTIVE "webextensions-active"
119121
120122 static const char * const ephy_prefs_web_schema[] = {
121123 EPHY_PREFS_WEB_FONT_MIN_SIZE,
145147 EPHY_PREFS_WEB_HARDWARE_ACCELERATION_POLICY,
146148 EPHY_PREFS_WEB_ASK_ON_DOWNLOAD,
147149 EPHY_PREFS_WEB_SWITCH_TO_NEW_TAB,
150 EPHY_PREFS_WEB_ENABLE_WEBEXTENSIONS,
148151 };
149152
150153 #define EPHY_PREFS_SCHEMA "org.gnome.Epiphany"
315315 }
316316
317317 char **
318 ephy_strv_append (const char * const *strv,
319 const char *str)
320 {
321 char **new_strv;
322 char **n;
323 const char * const *s;
324 guint len;
325
326 if (g_strv_contains (strv, str))
327 return g_strdupv ((char **)strv);
328
329 /* Needs room for one more string than before, plus one for trailing NULL. */
330 len = g_strv_length ((char **)strv);
331 new_strv = g_malloc ((len + 1 + 1) * sizeof (char *));
332 n = new_strv;
333 s = strv;
334
335 while (*s != NULL) {
336 *n = g_strdup (*s);
337 n++;
338 s++;
339 }
340 new_strv[len] = g_strdup (str);
341 new_strv[len + 1] = NULL;
342
343 return new_strv;
344 }
345
346 char **
347318 ephy_strv_remove (const char * const *strv,
348319 const char *str)
349320 {
4848 char *ephy_string_remove_trailing (char *string,
4949 char ch);
5050
51 char **ephy_strv_append (const char * const *strv,
52 const char *str);
5351 char **ephy_strv_remove (const char * const *strv,
5452 const char *str);
5553
5656 GtkOverlay parent_instance;
5757
5858 GtkWidget *url_entry;
59 GtkWidget *button_box;
60 GtkWidget *page_action_box;
5961 GtkWidget *bookmark;
6062 GtkWidget *bookmark_event_box;
6163 GtkWidget *reader_mode;
989991 ephy_location_entry_construct_contents (EphyLocationEntry *entry)
990992 {
991993 GtkWidget *event;
992 GtkWidget *box;
993994 GtkStyleContext *context;
994995 DzlShortcutController *controller;
995996
10291030 gtk_overlay_add_overlay (GTK_OVERLAY (entry), event);
10301031
10311032 /* Button Box */
1032 box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1033 gtk_container_add (GTK_CONTAINER (event), box);
1034 g_signal_connect (G_OBJECT (box), "size-allocate", G_CALLBACK (button_box_size_allocated_cb), entry);
1035 gtk_widget_set_halign (box, GTK_ALIGN_END);
1036 gtk_widget_set_valign (box, GTK_ALIGN_CENTER);
1037 gtk_widget_show (box);
1038
1039 context = gtk_widget_get_style_context (box);
1033 entry->button_box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
1034 gtk_container_add (GTK_CONTAINER (event), entry->button_box);
1035 gtk_box_set_homogeneous (GTK_BOX (entry->button_box), FALSE);
1036 g_signal_connect (G_OBJECT (entry->button_box), "size-allocate", G_CALLBACK (button_box_size_allocated_cb), entry);
1037 gtk_button_box_set_layout (GTK_BUTTON_BOX (entry->button_box), GTK_BUTTONBOX_EXPAND);
1038 gtk_widget_set_valign (entry->button_box, GTK_ALIGN_CENTER);
1039 gtk_widget_set_halign (entry->button_box, GTK_ALIGN_END);
1040 gtk_widget_set_margin_end (entry->button_box, 5);
1041 gtk_widget_show (entry->button_box);
1042
1043 /* Page action box */
1044 entry->page_action_box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
1045 gtk_box_set_homogeneous (GTK_BOX (entry->page_action_box), FALSE);
1046 gtk_widget_show (entry->page_action_box);
1047 gtk_button_box_set_layout (GTK_BUTTON_BOX (entry->page_action_box), GTK_BUTTONBOX_EXPAND);
1048 gtk_widget_set_valign (entry->page_action_box, GTK_ALIGN_CENTER);
1049 gtk_widget_set_halign (entry->page_action_box, GTK_ALIGN_END);
1050 gtk_box_pack_start (GTK_BOX (entry->button_box), entry->page_action_box, FALSE, FALSE, 0);
1051
1052 context = gtk_widget_get_style_context (entry->button_box);
10401053 gtk_style_context_add_class (context, "entry_icon_box");
10411054
10421055 /* Bookmark */
10471060 gtk_widget_show (entry->bookmark);
10481061 g_signal_connect (G_OBJECT (entry->bookmark_event_box), "button_press_event", G_CALLBACK (bookmark_icon_button_press_event_cb), entry);
10491062 gtk_container_add (GTK_CONTAINER (entry->bookmark_event_box), entry->bookmark);
1050 gtk_box_pack_end (GTK_BOX (box), entry->bookmark_event_box, FALSE, FALSE, 0);
1063 gtk_box_pack_end (GTK_BOX (entry->button_box), entry->bookmark_event_box, FALSE, FALSE, 6);
10511064
10521065 context = gtk_widget_get_style_context (entry->bookmark);
10531066 gtk_style_context_add_class (context, "entry_icon");
10651078 gtk_widget_set_valign (entry->reader_mode, GTK_ALIGN_CENTER);
10661079 gtk_widget_show (entry->reader_mode);
10671080 gtk_container_add (GTK_CONTAINER (entry->reader_mode_event_box), entry->reader_mode);
1068 gtk_box_pack_end (GTK_BOX (box), entry->reader_mode_event_box, FALSE, FALSE, 0);
1081 gtk_box_pack_end (GTK_BOX (entry->button_box), entry->reader_mode_event_box, FALSE, FALSE, 6);
10691082
10701083 context = gtk_widget_get_style_context (entry->reader_mode);
10711084 gtk_style_context_add_class (context, "entry_icon");
15041517 else
15051518 dzl_suggestion_entry_set_position_func (DZL_SUGGESTION_ENTRY (entry->url_entry), position_func, NULL, NULL);
15061519 }
1520
1521 void
1522 ephy_location_entry_page_action_add (EphyLocationEntry *entry,
1523 GtkWidget *action)
1524 {
1525 GtkStyleContext *context;
1526
1527 context = gtk_widget_get_style_context (action);
1528 gtk_style_context_add_class (context, "entry_icon");
1529
1530 gtk_box_pack_end (GTK_BOX (entry->page_action_box), action, FALSE, FALSE, 6);
1531 }
1532
1533 static
1534 void clear_page_actions (GtkWidget *child,
1535 gpointer user_data)
1536 {
1537 EphyLocationEntry *entry = EPHY_LOCATION_ENTRY (user_data);
1538 GtkStyleContext *context;
1539
1540 context = gtk_widget_get_style_context (child);
1541
1542 gtk_style_context_remove_class (context, "entry_icon");
1543
1544 gtk_container_remove (GTK_CONTAINER (entry->page_action_box), child);
1545 }
1546
1547 void
1548 ephy_location_entry_page_action_clear (EphyLocationEntry *entry)
1549 {
1550 gtk_container_foreach (GTK_CONTAINER (entry->page_action_box), clear_page_actions, entry);
1551 }
7878 void ephy_location_entry_set_progress (EphyLocationEntry *entry,
7979 gdouble progress,
8080 gboolean loading);
81 void ephy_location_entry_page_action_add (EphyLocationEntry *entry,
82 GtkWidget *action);
83
84 void ephy_location_entry_page_action_clear (EphyLocationEntry *entry);
8185
8286 void ephy_location_entry_set_mobile_popdown (EphyLocationEntry *entry,
8387 gboolean mobile_popdown);
7171 conf.set_quoted('GSB_API_KEY', gsb_api_key)
7272 conf.set10('ENABLE_GSB', gsb_api_key != '')
7373
74 glib_requirement = '>= 2.61.2'
74 glib_requirement = '>= 2.64.0'
7575 gtk_requirement = '>= 3.24.0'
7676 nettle_requirement = '>= 3.4'
77 webkitgtk_requirement = '>= 2.29.3'
77 webkitgtk_requirement = '>= 2.31.1'
7878
7979 cairo_dep = dependency('cairo', version: '>= 1.2')
8080 gcr_dep = dependency('gcr-3', version: '>= 3.5.5')
8989 hogweed_dep = dependency('hogweed', version: nettle_requirement)
9090 iso_codes_dep = dependency('iso-codes', version: '>= 0.35')
9191 json_glib_dep = dependency('json-glib-1.0', version: '>= 1.2.4')
92 libarchive_dep = dependency('libarchive')
9293 libdazzle_dep = dependency('libdazzle-1.0', version: '>= 3.37.1')
9394 libhandy_dep = dependency('libhandy-1', version: '>= 1.0.0')
9495 libsecret_dep = dependency('libsecret-1', version: '>= 0.19.0')
3838 GtkWidget *downloads_popover;
3939 GtkWidget *downloads_icon;
4040 GtkWidget *downloads_progress;
41 GtkWidget *browser_action_box;
4142
4243 guint downloads_button_attention_timeout_id;
4344 };
241242 gtk_widget_class_bind_template_child (widget_class,
242243 EphyActionBarEnd,
243244 downloads_progress);
245 gtk_widget_class_bind_template_child (widget_class,
246 EphyActionBarEnd,
247 browser_action_box);
244248 }
245249
246250 static void
318322 {
319323 return action_bar_end->downloads_revealer;
320324 }
325
326 void
327 ephy_action_bar_end_add_browser_action (EphyActionBarEnd *action_bar_end,
328 GtkWidget *action)
329 {
330 gtk_container_add (GTK_CONTAINER (action_bar_end->browser_action_box), action);
331 }
3333 gboolean show);
3434 GtkWidget *ephy_action_bar_end_get_downloads_revealer (EphyActionBarEnd *action_bar_end);
3535
36 void ephy_action_bar_end_add_browser_action (EphyActionBarEnd *action_bar_end,
37 GtkWidget *action);
38
3639 G_END_DECLS
304304 gtk_image_new_from_icon_name ("open-menu",
305305 GTK_ICON_SIZE_LARGE_TOOLBAR));
306306 }
307 g_settings_bind (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_ENABLE_WEBEXTENSIONS, gtk_builder_get_object (builder, "extensions-button"), "visible", G_SETTINGS_BIND_DEFAULT);
307308
308309 gtk_menu_button_set_popover (GTK_MENU_BUTTON (button), page_menu_popover);
309310 g_object_unref (builder);
457458
458459 gtk_label_set_label (GTK_LABEL (header_bar->zoom_level_label), zoom_level);
459460 }
461
462 void
463 ephy_header_bar_add_browser_action (EphyHeaderBar *header_bar,
464 GtkWidget *action)
465 {
466 ephy_action_bar_end_add_browser_action (header_bar->action_bar_end, action);
467 }
4848 void ephy_header_bar_set_zoom_level (EphyHeaderBar *header_bar,
4949 gdouble zoom);
5050
51 void ephy_header_bar_add_browser_action (EphyHeaderBar *header_bar,
52 GtkWidget *action);
53
5154 G_END_DECLS
6060 EphyBookmarksManager *bookmarks_manager;
6161 EphyHistoryManager *history_manager;
6262 EphyOpenTabsManager *open_tabs_manager;
63 EphyWebExtensionManager *web_extension_manager;
6364 GNetworkMonitor *network_monitor;
6465 GtkWidget *history_dialog;
6566 GtkWidget *firefox_sync_dialog;
15341535 {
15351536 return shell->startup_finished;
15361537 }
1538
1539 EphyWebExtensionManager *
1540 ephy_shell_get_web_extension_manager (EphyShell *shell)
1541 {
1542 g_assert (EPHY_IS_SHELL (shell));
1543
1544 if (shell->web_extension_manager == NULL)
1545 shell->web_extension_manager = ephy_web_extension_manager_new ();
1546
1547 return shell->web_extension_manager;
1548 }
1549
1550
1551 /* Helper functions: better place for this? */
1552 EphyWebView *
1553 ephy_shell_get_web_view (EphyShell *shell,
1554 guint64 id)
1555 {
1556 GList *windows;
1557 GtkWindow *window;
1558 GtkWidget *notebook;
1559
1560 windows = gtk_application_get_windows (GTK_APPLICATION (shell));
1561
1562 for (GList *list = windows; list && list->data; list = list->next) {
1563 window = GTK_WINDOW (list->data);
1564 notebook = ephy_window_get_notebook (EPHY_WINDOW (window));
1565
1566 for (int i = 0; i < gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)); i++) {
1567 GtkWidget *page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), i);
1568 EphyWebView *web_view = ephy_embed_get_web_view (EPHY_EMBED (page));
1569
1570 if (ephy_web_view_get_uid (web_view) == id)
1571 return web_view;
1572 }
1573 }
1574
1575 return NULL;
1576 }
1577
1578 EphyWebView *
1579 ephy_shell_get_active_web_view (EphyShell *shell)
1580 {
1581 GtkWindow *window;
1582 GtkWidget *notebook;
1583 GtkWidget *page;
1584 gint page_num;
1585
1586 window = gtk_application_get_active_window (GTK_APPLICATION (shell));
1587 if (!window)
1588 return NULL;
1589
1590 notebook = ephy_window_get_notebook (EPHY_WINDOW (window));
1591
1592 page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
1593 page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), page_num);
1594
1595 return ephy_embed_get_web_view (EPHY_EMBED (page));
1596 }
2929 #include "ephy-password-manager.h"
3030 #include "ephy-session.h"
3131 #include "ephy-sync-service.h"
32 #include "ephy-web-extension-manager.h"
3233 #include "ephy-window.h"
3334
3435 #include <webkit2/webkit2.h>
129130
130131 gboolean ephy_shell_startup_finished (EphyShell *shell);
131132
133 EphyWebExtensionManager *ephy_shell_get_web_extension_manager (EphyShell *shell);
134
135 EphyWebView *ephy_shell_get_web_view (EphyShell *shell,
136 guint64 id);
137
138 EphyWebView *ephy_shell_get_active_web_view (EphyShell *shell);
139
132140 G_END_DECLS
0 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
1 /*
2 * Copyright © 2019-2020 Jan-Michael Brummer <jan.brummer@tabos.org>
3 *
4 * This file is part of Epiphany.
5 *
6 * Epiphany is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Epiphany is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "config.h"
21
22 #include "ephy-file-helpers.h"
23 #include "ephy-shell.h"
24 #include "ephy-web-extension.h"
25 #include "ephy-web-extension-dialog.h"
26 #include "ephy-web-extension-manager.h"
27
28 #include <gtk/gtk.h>
29
30 struct _EphyWebExtensionDialog {
31 HdyWindow parent_instance;
32
33 EphyWebExtensionManager *web_extension_manager;
34
35 GtkWidget *listbox;
36 GtkWidget *add_button;
37 GtkWidget *remove_button;
38 };
39
40 G_DEFINE_TYPE (EphyWebExtensionDialog, ephy_web_extension_dialog, HDY_TYPE_WINDOW)
41
42 static void
43 clear_listbox (GtkWidget *listbox)
44 {
45 GList *children, *iter;
46
47 children = gtk_container_get_children (GTK_CONTAINER (listbox));
48
49 for (iter = children; iter && iter->data; iter = g_list_next (iter))
50 gtk_widget_destroy (GTK_WIDGET (iter->data));
51
52 g_list_free (children);
53 }
54
55 static void
56 on_remove_button_clicked (GtkButton *button,
57 gpointer user_data)
58 {
59 EphyWebExtensionDialog *self = EPHY_WEB_EXTENSION_DIALOG (user_data);
60 GtkWidget *dialog = NULL;
61 GtkListBoxRow *row;
62 GtkWidget *widget;
63 gint res;
64
65 row = gtk_list_box_get_selected_row (GTK_LIST_BOX (self->listbox));
66 if (!row)
67 return;
68
69 dialog = gtk_message_dialog_new (GTK_WINDOW (self),
70 GTK_DIALOG_MODAL | GTK_DIALOG_USE_HEADER_BAR,
71 GTK_MESSAGE_QUESTION,
72 GTK_BUTTONS_NONE,
73 _("Do you really want to remove this extension?"));
74 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
75 _("_Cancel"),
76 GTK_RESPONSE_CANCEL,
77 _("_Remove"),
78 GTK_RESPONSE_OK,
79 NULL);
80
81 widget = gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
82 gtk_style_context_add_class (gtk_widget_get_style_context (widget), GTK_STYLE_CLASS_DESTRUCTIVE_ACTION);
83
84 res = gtk_dialog_run (GTK_DIALOG (dialog));
85 if (res == GTK_RESPONSE_OK) {
86 EphyWebExtension *web_extension = g_object_get_data (G_OBJECT (row), "web_extension");
87
88 g_assert (web_extension);
89 ephy_web_extension_manager_uninstall (self->web_extension_manager, web_extension);
90 }
91
92 gtk_widget_destroy (dialog);
93 }
94
95 static void
96 toggle_state_set_cb (GtkSwitch *widget,
97 gboolean state,
98 gpointer user_data)
99 {
100 EphyWebExtensionManager *manager = ephy_shell_get_web_extension_manager (ephy_shell_get_default ());
101 EphyWebExtension *web_extension = EPHY_WEB_EXTENSION (user_data);
102
103 ephy_web_extension_manager_set_active (manager, web_extension, state);
104 }
105
106 static GtkWidget *
107 create_row (EphyWebExtensionDialog *self,
108 EphyWebExtension *web_extension)
109 {
110 GtkWidget *row;
111 GtkWidget *sub_row;
112 GtkWidget *image;
113 GtkWidget *toggle;
114 GtkWidget *button;
115 GtkWidget *homepage;
116 GtkWidget *author;
117 GtkWidget *version;
118 g_autoptr (GdkPixbuf) icon = NULL;
119 EphyWebExtensionManager *manager = ephy_shell_get_web_extension_manager (ephy_shell_get_default ());
120
121 row = hdy_expander_row_new ();
122 g_object_set_data (G_OBJECT (row), "web_extension", web_extension);
123
124 /* Tooltip */
125 gtk_widget_set_tooltip_text (GTK_WIDGET (row), ephy_web_extension_get_name (web_extension));
126
127 /* Icon */
128 icon = ephy_web_extension_get_icon (web_extension, 48);
129 image = icon ? gtk_image_new_from_pixbuf (icon) : gtk_image_new_from_icon_name ("application-x-addon-symbolic", GTK_ICON_SIZE_DIALOG);
130 hdy_expander_row_add_prefix (HDY_EXPANDER_ROW (row), image);
131
132 /* Titles */
133 hdy_preferences_row_set_title (HDY_PREFERENCES_ROW (row), ephy_web_extension_get_name (web_extension));
134 hdy_expander_row_set_subtitle (HDY_EXPANDER_ROW (row), ephy_web_extension_get_description (web_extension));
135 hdy_expander_row_set_show_enable_switch (HDY_EXPANDER_ROW (row), FALSE);
136
137 toggle = gtk_switch_new ();
138 gtk_switch_set_active (GTK_SWITCH (toggle), ephy_web_extension_manager_is_active (manager, web_extension));
139 g_signal_connect (toggle, "state-set", G_CALLBACK (toggle_state_set_cb), web_extension);
140 gtk_widget_set_valign (toggle, GTK_ALIGN_CENTER);
141 hdy_expander_row_add_action (HDY_EXPANDER_ROW (row), toggle);
142
143 /* Author */
144 if (ephy_web_extension_get_author (web_extension)) {
145 sub_row = hdy_action_row_new ();
146 gtk_container_add (GTK_CONTAINER (row), sub_row);
147 hdy_preferences_row_set_title (HDY_PREFERENCES_ROW (sub_row), _("Author"));
148 author = gtk_label_new (ephy_web_extension_get_author (web_extension));
149 gtk_label_set_line_wrap (GTK_LABEL (author), TRUE);
150 gtk_container_add (GTK_CONTAINER (sub_row), author);
151 }
152
153 /* Version */
154 sub_row = hdy_action_row_new ();
155 gtk_container_add (GTK_CONTAINER (row), sub_row);
156 hdy_preferences_row_set_title (HDY_PREFERENCES_ROW (sub_row), _("Version"));
157 version = gtk_label_new (ephy_web_extension_get_version (web_extension));
158 gtk_container_add (GTK_CONTAINER (sub_row), version);
159
160 /* Homepage url */
161 if (ephy_web_extension_get_homepage_url (web_extension)) {
162 sub_row = hdy_action_row_new ();
163 gtk_container_add (GTK_CONTAINER (row), sub_row);
164 hdy_preferences_row_set_title (HDY_PREFERENCES_ROW (sub_row), _("Homepage"));
165 homepage = gtk_link_button_new_with_label (ephy_web_extension_get_homepage_url (web_extension), _("Open"));
166 gtk_container_add (GTK_CONTAINER (sub_row), homepage);
167 }
168
169 /* Remove button */
170 sub_row = hdy_action_row_new ();
171 gtk_container_add (GTK_CONTAINER (row), sub_row);
172
173 button = gtk_button_new_with_label (_("Remove"));
174 gtk_widget_set_valign (GTK_WIDGET (button), GTK_ALIGN_CENTER);
175 dzl_gtk_widget_add_style_class (button, GTK_STYLE_CLASS_DESTRUCTIVE_ACTION);
176 g_signal_connect (button, "clicked", G_CALLBACK (on_remove_button_clicked), self);
177 gtk_widget_set_tooltip_text (button, _("Remove selected WebExtension"));
178 gtk_container_add (GTK_CONTAINER (sub_row), button);
179
180 gtk_widget_show_all (GTK_WIDGET (row));
181
182 return GTK_WIDGET (row);
183 }
184
185 static void
186 ephy_web_extension_dialog_refresh_listbox (EphyWebExtensionDialog *self)
187 {
188 GList *extensions = ephy_web_extension_manager_get_web_extensions (self->web_extension_manager);
189
190 clear_listbox (self->listbox);
191
192 for (GList *tmp = extensions; tmp && tmp->data; tmp = tmp->next) {
193 EphyWebExtension *web_extension = tmp->data;
194 GtkWidget *row;
195
196 row = create_row (self, web_extension);
197 gtk_list_box_insert (GTK_LIST_BOX (self->listbox), row, -1);
198 }
199 }
200
201 static void
202 on_add_button_clicked (GtkButton *button,
203 gpointer user_data)
204 {
205 EphyWebExtensionDialog *self = EPHY_WEB_EXTENSION_DIALOG (user_data);
206 GtkWidget *dialog = NULL;
207 GtkFileFilter *filter;
208 gint res;
209
210 dialog = gtk_file_chooser_dialog_new (_("Open File (manifest.json/xpi)"),
211 GTK_WINDOW (self),
212 GTK_FILE_CHOOSER_ACTION_OPEN,
213 _("_Cancel"),
214 GTK_RESPONSE_CANCEL,
215 _("_Open"),
216 GTK_RESPONSE_ACCEPT,
217 NULL);
218
219 filter = gtk_file_filter_new ();
220 gtk_file_filter_set_name (GTK_FILE_FILTER (filter), "WebExtensions");
221 gtk_file_filter_add_mime_type (GTK_FILE_FILTER (filter), "application/json");
222 gtk_file_filter_add_mime_type (GTK_FILE_FILTER (filter), "application/x-xpinstall");
223 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), g_steal_pointer (&filter));
224
225 res = gtk_dialog_run (GTK_DIALOG (dialog));
226 if (res == GTK_RESPONSE_ACCEPT) {
227 g_autoptr (GFile) file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
228
229 ephy_web_extension_manager_install (self->web_extension_manager, file);
230 }
231
232 gtk_widget_destroy (dialog);
233 }
234
235 static void
236 on_web_extension_manager_changed (EphyWebExtensionManager *manager,
237 gpointer user_data)
238 {
239 EphyWebExtensionDialog *self = EPHY_WEB_EXTENSION_DIALOG (user_data);
240
241 ephy_web_extension_dialog_refresh_listbox (self);
242 }
243
244 static void
245 ephy_web_extension_dialog_dispose (GObject *object)
246 {
247 EphyWebExtensionDialog *self = EPHY_WEB_EXTENSION_DIALOG (object);
248
249 g_clear_weak_pointer (&self->web_extension_manager);
250
251 G_OBJECT_CLASS (ephy_web_extension_dialog_parent_class)->dispose (object);
252 }
253
254 static void
255 ephy_web_extension_dialog_class_init (EphyWebExtensionDialogClass *klass)
256 {
257 GObjectClass *object_class = G_OBJECT_CLASS (klass);
258 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
259
260 object_class->dispose = ephy_web_extension_dialog_dispose;
261
262 gtk_widget_class_set_template_from_resource (widget_class,
263 "/org/gnome/epiphany/gtk/web-extensions-dialog.ui");
264
265 gtk_widget_class_bind_template_child (widget_class, EphyWebExtensionDialog, listbox);
266
267 gtk_widget_class_bind_template_callback (widget_class, on_add_button_clicked);
268 }
269
270 static void
271 ephy_web_extension_dialog_init (EphyWebExtensionDialog *self)
272 {
273 EphyWebExtensionManager *manager;
274
275 gtk_widget_init_template (GTK_WIDGET (self));
276
277 manager = ephy_shell_get_web_extension_manager (ephy_shell_get_default ());
278 g_assert (manager != NULL);
279
280 g_set_weak_pointer (&self->web_extension_manager, manager);
281 g_signal_connect_object (self->web_extension_manager, "changed", G_CALLBACK (on_web_extension_manager_changed), self, 0);
282
283 ephy_web_extension_dialog_refresh_listbox (self);
284 }
285
286 GtkWidget *
287 ephy_web_extension_dialog_new (void)
288 {
289 return g_object_new (EPHY_TYPE_WEB_EXTENSION_DIALOG, NULL);
290 }
0 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
1 /*
2 * Copyright © 2019-2020 Jan-Michael Brummer <jan.brummer@tabos.org>
3 *
4 * This file is part of Epiphany.
5 *
6 * Epiphany is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Epiphany is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #pragma once
21
22 #include <gtk/gtk.h>
23
24 #include "ephy-window.h"
25
26 G_BEGIN_DECLS
27
28 #define EPHY_TYPE_WEB_EXTENSION_DIALOG (ephy_web_extension_dialog_get_type ())
29
30 G_DECLARE_FINAL_TYPE (EphyWebExtensionDialog, ephy_web_extension_dialog, EPHY, WEB_EXTENSION_DIALOG, HdyWindow)
31
32 GtkWidget *ephy_web_extension_dialog_new (void);
33
34 G_END_DECLS
116116 { "win.location-search", {"<Primary>K", NULL} },
117117 { "win.home", { "<alt>Home", NULL } },
118118 { "win.content", { "Escape", NULL } },
119 { "win.extensions", { NULL } },
119120
120121 /* Toggle actions */
121122 { "win.browse-with-caret", { "F7", NULL } },
858859 { "page-source", window_cmd_page_source },
859860 { "toggle-inspector", window_cmd_toggle_inspector },
860861 { "toggle-reader-mode", window_cmd_toggle_reader_mode },
862 { "extensions", window_cmd_extensions },
861863
862864 { "select-all", window_cmd_select_all },
863865
12671269
12681270 gtk_window_set_title (GTK_WINDOW (window),
12691271 ephy_embed_get_title (embed));
1272 }
1273
1274 static void
1275 sync_tab_page_action (EphyWebView *view,
1276 GParamSpec *pspec,
1277 EphyWindow *window)
1278 {
1279 EphyWebExtensionManager *manager;
1280
1281 manager = ephy_shell_get_web_extension_manager (ephy_shell_get_default ());
1282 ephy_web_extension_manager_update_location_entry (manager, window);
12701283 }
12711284
12721285 static gboolean
24182431 sync_tab_popup_windows (view, NULL, window);
24192432
24202433 sync_tab_zoom (web_view, NULL, window);
2434 sync_tab_page_action (view, NULL, window);
24212435
24222436 title_widget = ephy_header_bar_get_title_widget (EPHY_HEADER_BAR (window->header_bar));
24232437
39453959 window->mouse_gesture_controller = ephy_mouse_gesture_controller_new (window);
39463960
39473961 ephy_window_set_chrome (window, chrome);
3962
3963 ephy_web_extension_manager_install_actions (ephy_shell_get_web_extension_manager (ephy_shell_get_default ()), window);
39483964 }
39493965
39503966 static void
88 enums = gnome.mkenums_simple('ephy-type-builtins',
99 sources: types_headers
1010 )
11
12 subdir('webextension')
1113
1214 libephymain_sources = [
1315 'bookmarks/ephy-add-bookmark-popover.c',
4244 'ephy-suggestion-model.c',
4345 'ephy-tab-header-bar.c',
4446 'ephy-tab-label.c',
47 'ephy-web-extension-dialog.c',
4548 'ephy-window.c',
4649 'popup-commands.c',
4750 'preferences/clear-data-view.c',
5760 'preferences/webapp-additional-urls-dialog.c',
5861 'synced-tabs-dialog.c',
5962 'window-commands.c',
63 ephywebextension_src,
6064 compile_schemas,
6165 enums
6266 ]
6973 ephywidgets_dep,
7074 gdk_dep,
7175 gvdb_dep,
76 libarchive_dep,
7277 libhandy_dep
7378 ]
7479
7580 libephymain_includes = include_directories(
7681 '.',
82 '..',
7783 'bookmarks',
7884 'preferences',
85 'webextension',
86 'webextension/api',
7987 )
8088
8189 libephymain = shared_library('ephymain',
4242 <file preprocess="xml-stripblanks" compressed="true">gtk/shortcuts-dialog.ui</file>
4343 <file preprocess="xml-stripblanks" compressed="true">gtk/tab-label.ui</file>
4444 <file preprocess="xml-stripblanks" compressed="true">gtk/webapp-additional-urls-dialog.ui</file>
45 <file preprocess="xml-stripblanks" compressed="true">gtk/web-extensions-dialog.ui</file>
4546 </gresource>
4647 <gresource prefix="/org/gnome/Epiphany/icons">
4748 <file compressed="true" alias="scalable/actions/ephy-download-symbolic.svg" preprocess="xml-stripblanks">ephy-download-symbolic.svg</file>
11 <interface>
22 <template class="EphyActionBarEnd" parent="GtkBox">
33 <property name="spacing">6</property>
4 <child>
5 <object class="GtkButtonBox" id="browser_action_box">
6 <property name="visible">True</property>
7 <property name="valign">center</property>
8 <property name="halign">end</property>
9 <property name="homogeneous">False</property>
10 <property name="layout-style">expand</property>
11 </object>
12 <packing>
13 <property name="pack-type">start</property>
14 </packing>
15 </child>
416 <child>
517 <object class="GtkMenuButton" id="bookmarks_button">
618 <property name="visible">True</property>
267267 <property name="can_focus">True</property>
268268 <property name="text" translatable="yes">Open Appli_cation Manager</property>
269269 <property name="action-name">win.open-application-manager</property>
270 <property name="visible">True</property>
271 </object>
272 </child>
273 <child>
274 <object class="GtkModelButton" id="extensions-button">
275 <property name="can_focus">True</property>
276 <property name="text" translatable="yes">_Extensions</property>
277 <property name="action-name">win.extensions</property>
270278 <property name="visible">True</property>
271279 </object>
272280 </child>
0 <?xml version="1.0" encoding="UTF-8"?>
1 <!-- Generated with glade 3.38.0 -->
2 <interface>
3 <requires lib="gtk+" version="3.20"/>
4 <template class="EphyWebExtensionDialog" parent="HdyWindow">
5 <property name="can-focus">False</property>
6 <property name="modal">True</property>
7 <property name="window-position">center-on-parent</property>
8 <property name="default-width">640</property>
9 <property name="default-height">400</property>
10 <property name="destroy-with_parent">True</property>
11 <property name="type-hint">dialog</property>
12 <child>
13 <object class="GtkBox">
14 <property name="visible">True</property>
15 <property name="orientation">vertical</property>
16 <child>
17 <object class="HdyHeaderBar">
18 <property name="visible">True</property>
19 <property name="decoration-layout">:close</property>
20 <property name="show-close-button">True</property>
21 <property name="title" translatable="yes">Extensions</property>
22 <child>
23 <object class="GtkButton" id="add_button">
24 <property name="visible">True</property>
25 <property name="can-focus">True</property>
26 <property name="receives-default">True</property>
27 <property name="label" translatable="yes">Add…</property>
28 <signal name="clicked" handler="on_add_button_clicked" object="EphyWebExtensionDialog" swapped="no"/>
29 </object>
30 </child>
31 </object>
32 </child>
33 <child>
34 <object class="GtkScrolledWindow">
35 <property name="visible">True</property>
36 <property name="can_focus">True</property>
37 <property name="vexpand">True</property>
38 <child>
39 <object class="HdyClamp">
40 <property name="visible">True</property>
41 <property name="can-focus">False</property>
42 <property name="margin_start">6</property>
43 <property name="margin_end">6</property>
44 <property name="maximum_size">1024</property>
45 <child>
46 <object class="GtkListBox" id="listbox">
47 <property name="visible">True</property>
48 <property name="can_focus">False</property>
49 <property name="margin_top">6</property>
50 <property name="margin_bottom">6</property>
51 <property name="valign">start</property>
52 <style>
53 <class name="content"/>
54 </style>
55 </object>
56 </child>
57 </object>
58 </child>
59 </object>
60 </child>
61 </object>
62 </child>
63 </template>
64 </interface>
0 https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API
1
2 https://github.com/mdn/webextensions-examples
3
4
5 # Working extensions
6
7 - Borderify
8 - Apply CSS
9 - Page to extension messaging
10
11 # QUESTIONS
12 - Should we use **self** as current module parameter name for consistency or name it like module?
13 - Clear definition if get/set functions should be used instead of direct struct access
14 - Enfore g_auto free functions implementation?
15 - Alignment in header files
16 - Should every function of a file has a certain prefix or only non static functions?
17 - EphyWebExtensionManager as a singleton?
18
19 # PLAN
20
21 ## First release
22 Feature set:
23 - Un/Load/Enable/Disable xpi and extracted extensions
24 - Works for existing and new views
25 - Manifest file:
26 - initial content_scripts
27 - initial background page
28 - initial background scripts
29 - API:
30 - notifications:
31 - create
32 - pageaction:
33 - setIcon
34 - setTitle
35 - show
36 - getTitle
37 - tabs:
38 - insertCSS
39 - removeCSS
40 - initial query
41
42 - Test extensions:
43 - apply-css
44 - borderify
45
46 ## Second release
47 Feature set:
48 - API:
49 - i18n:
50 - getMessage
51 - getUILanguage
52 - runtime:
53 - sendMessage
54 - onMessage.addListener
55
56 - Test extensions:
57 - notify-link-clicks-i18n
58
0 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
1 /*
2 * Copyright © 2019-2020 Jan-Michael Brummer <jan.brummer@tabos.org>
3 *
4 * This file is part of Epiphany.
5 *
6 * Epiphany is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Epiphany is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "config.h"
21
22 #include "ephy-notification.h"
23 #include "ephy-web-extension.h"
24
25 #include "notifications.h"
26
27 static char *
28 notifications_handler_create (EphyWebExtension *self,
29 char *name,
30 JSCValue *args)
31 {
32 g_autofree char *title_str = NULL;
33 g_autofree char *message_str = NULL;
34 g_autoptr (JSCValue) title = NULL;
35 g_autoptr (JSCValue) message = NULL;
36 EphyNotification *notify;
37
38 title = jsc_value_object_get_property (args, "title");
39 title_str = jsc_value_to_string (title);
40
41 message = jsc_value_object_get_property (args, "message");
42 message_str = jsc_value_to_string (message);
43
44 notify = ephy_notification_new (g_strdup (title_str), g_strdup (message_str));
45 ephy_notification_show (notify);
46
47 return NULL;
48 }
49
50 static EphyWebExtensionApiHandler notifications_handlers[] = {
51 {"create", notifications_handler_create},
52 {NULL, NULL},
53 };
54
55 char *
56 ephy_web_extension_api_notifications_handler (EphyWebExtension *self,
57 char *name,
58 JSCValue *args)
59 {
60 guint idx;
61
62 for (idx = 0; idx < G_N_ELEMENTS (notifications_handlers); idx++) {
63 EphyWebExtensionApiHandler handler = notifications_handlers[idx];
64
65 if (g_strcmp0 (handler.name, name) == 0)
66 return handler.execute (self, name, args);
67 }
68
69 g_warning ("%s(): '%s' not implemented by Epiphany!", __FUNCTION__, name);
70
71 return NULL;
72 }
0 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
1 /*
2 * Copyright © 2019-2020 Jan-Michael Brummer <jan.brummer@tabos.org>
3 *
4 * This file is part of Epiphany.
5 *
6 * Epiphany is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Epiphany is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20
21 #pragma once
22
23 #include "ephy-web-extension.h"
24
25 G_BEGIN_DECLS
26
27 char *ephy_web_extension_api_notifications_handler (EphyWebExtension *self,
28 char *name,
29 JSCValue *args);
30
31 G_END_DECLS
0 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
1 /*
2 * Copyright © 2019-2020 Jan-Michael Brummer <jan.brummer@tabos.org>
3 *
4 * This file is part of Epiphany.
5 *
6 * Epiphany is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Epiphany is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "config.h"
21
22 #include "ephy-shell.h"
23 #include "ephy-web-extension.h"
24 #include "ephy-window.h"
25
26 #include "pageaction.h"
27
28 static GtkWidget *
29 pageaction_get_action (EphyWebExtension *self,
30 JSCValue *args)
31 {
32 EphyWebView *web_view = NULL;
33 EphyShell *shell = ephy_shell_get_default ();
34 EphyWebExtensionManager *manager = ephy_shell_get_web_extension_manager (shell);
35 g_autoptr (JSCValue) tab_id = NULL;
36 gint32 nr;
37
38 if (jsc_value_object_has_property (args, "tabId")) {
39 tab_id = jsc_value_object_get_property (args, "tabId");
40 nr = jsc_value_to_int32 (tab_id);
41 web_view = ephy_shell_get_web_view (shell, nr);
42 if (!web_view) {
43 LOG ("%s(): Invalid tabId '%d', abort\n", __FUNCTION__, nr);
44 return NULL;
45 }
46 }
47
48 return ephy_web_extension_manager_get_page_action (manager, self, web_view);
49 }
50
51 static char *
52 pageaction_handler_seticon (EphyWebExtension *self,
53 char *name,
54 JSCValue *args)
55 {
56 GtkWidget *action;
57 g_autoptr (JSCValue) path = NULL;
58 g_autoptr (GdkPixbuf) pixbuf = NULL;
59
60 action = pageaction_get_action (self, args);
61 if (!action)
62 return NULL;
63
64 path = jsc_value_object_get_property (args, "path");
65 pixbuf = ephy_web_extension_load_pixbuf (self, jsc_value_to_string (path));
66
67 gtk_image_set_from_pixbuf (GTK_IMAGE (gtk_bin_get_child (GTK_BIN (action))), pixbuf);
68
69 return NULL;
70 }
71
72 static char *
73 pageaction_handler_settitle (EphyWebExtension *self,
74 char *name,
75 JSCValue *args)
76 {
77 GtkWidget *action;
78 g_autoptr (JSCValue) title = NULL;
79
80 action = pageaction_get_action (self, args);
81 if (!action)
82 return NULL;
83
84 title = jsc_value_object_get_property (args, "title");
85 gtk_widget_set_tooltip_text (action, jsc_value_to_string (title));
86
87 return NULL;
88 }
89
90 static char *
91 pageaction_handler_gettitle (EphyWebExtension *self,
92 char *name,
93 JSCValue *args)
94 {
95 GtkWidget *action;
96 g_autofree char *title = NULL;
97
98 action = pageaction_get_action (self, args);
99 if (!action)
100 return NULL;
101
102 title = gtk_widget_get_tooltip_text (action);
103
104 return g_strdup_printf ("\"%s\"", title ? title : "");
105 }
106
107 static char *
108 pageaction_handler_show (EphyWebExtension *self,
109 char *name,
110 JSCValue *args)
111 {
112 GtkWidget *action;
113
114 action = pageaction_get_action (self, args);
115 if (!action)
116 return NULL;
117
118 gtk_widget_set_visible (action, TRUE);
119
120 return NULL;
121 }
122
123 static char *
124 pageaction_handler_hide (EphyWebExtension *self,
125 char *name,
126 JSCValue *args)
127 {
128 GtkWidget *action;
129
130 action = pageaction_get_action (self, args);
131 if (!action)
132 return NULL;
133
134 gtk_widget_set_visible (action, FALSE);
135
136 return NULL;
137 }
138
139 static EphyWebExtensionApiHandler pageaction_handlers[] = {
140 {"setIcon", pageaction_handler_seticon},
141 {"setTitle", pageaction_handler_settitle},
142 {"getTitle", pageaction_handler_gettitle},
143 {"show", pageaction_handler_show},
144 {"hide", pageaction_handler_hide},
145 {NULL, NULL},
146 };
147
148 char *
149 ephy_web_extension_api_pageaction_handler (EphyWebExtension *self,
150 char *name,
151 JSCValue *args)
152 {
153 guint idx;
154
155 for (idx = 0; idx < G_N_ELEMENTS (pageaction_handlers); idx++) {
156 EphyWebExtensionApiHandler handler = pageaction_handlers[idx];
157
158 if (g_strcmp0 (handler.name, name) == 0)
159 return handler.execute (self, name, args);
160 }
161
162 g_warning ("%s(): '%s' not implemented by Epiphany!", __FUNCTION__, name);
163
164 return NULL;
165 }
0 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
1 /*
2 * Copyright © 2019-2020 Jan-Michael Brummer <jan.brummer@tabos.org>
3 *
4 * This file is part of Epiphany.
5 *
6 * Epiphany is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Epiphany is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20
21 #pragma once
22
23 #include "ephy-web-extension.h"
24
25 G_BEGIN_DECLS
26
27 char *ephy_web_extension_api_pageaction_handler (EphyWebExtension *self,
28 char *name,
29 JSCValue *args);
30
31 G_END_DECLS
0 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
1 /*
2 * Copyright © 2019-2020 Jan-Michael Brummer <jan.brummer@tabos.org>
3 *
4 * This file is part of Epiphany.
5 *
6 * Epiphany is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Epiphany is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "config.h"
21
22 #include "runtime.h"
23
24 #include "ephy-web-extension-manager.h"
25
26 #include "ephy-embed-utils.h"
27 #include "ephy-shell.h"
28
29 static char *
30 runtime_handler_get_browser_info (EphyWebExtension *self,
31 char *name,
32 JSCValue *args)
33 {
34 g_autoptr (JsonBuilder) builder = json_builder_new ();
35 g_autoptr (JsonNode) root = NULL;
36
37 json_builder_begin_object (builder);
38 json_builder_set_member_name (builder, "name");
39 json_builder_add_string_value (builder, "GNOME Web (Epiphany)");
40 json_builder_end_object (builder);
41
42 root = json_builder_get_root (builder);
43
44 return json_to_string (root, FALSE);
45 }
46
47 static char *
48 runtime_handler_send_message (EphyWebExtension *self,
49 char *name,
50 JSCValue *args)
51 {
52 EphyShell *shell = ephy_shell_get_default ();
53 EphyWebExtensionManager *manager = ephy_shell_get_web_extension_manager (shell);
54 WebKitWebView *view = WEBKIT_WEB_VIEW (ephy_web_extension_manager_get_background_web_view (manager, self));
55 g_autofree char *script = NULL;
56
57 script = g_strdup_printf ("runtimeSendMessage(%s);", jsc_value_to_json (args, 2));
58 webkit_web_view_run_javascript_in_world (view, script, ephy_embed_shell_get_guid (EPHY_EMBED_SHELL (shell)), NULL, NULL, NULL);
59
60 return NULL;
61 }
62
63 static char *
64 runtime_handler_open_options_page (EphyWebExtension *self,
65 char *name,
66 JSCValue *args)
67 {
68 const char *data = ephy_web_extension_get_option_ui_page (self);
69
70 if (data) {
71 EphyEmbed *embed;
72 EphyShell *shell = ephy_shell_get_default ();
73 WebKitWebView *web_view;
74 GtkWindow *window = gtk_application_get_active_window (GTK_APPLICATION (shell));
75
76 embed = ephy_shell_new_tab (shell,
77 EPHY_WINDOW (window),
78 NULL,
79 EPHY_NEW_TAB_JUMP);
80
81 web_view = EPHY_GET_WEBKIT_WEB_VIEW_FROM_EMBED (embed);
82 webkit_web_view_load_html (web_view, data, NULL);
83 }
84
85 return NULL;
86 }
87
88 static char *
89 runtime_handler_set_uninstall_url (EphyWebExtension *self,
90 char *name,
91 JSCValue *args)
92 {
93 return NULL;
94 }
95
96 static EphyWebExtensionApiHandler runtime_handlers[] = {
97 {"getBrowserInfo", runtime_handler_get_browser_info},
98 {"sendMessage", runtime_handler_send_message},
99 {"openOptionsPage", runtime_handler_open_options_page},
100 {"setUninstallURL", runtime_handler_set_uninstall_url},
101 {NULL, NULL},
102 };
103
104 char *
105 ephy_web_extension_api_runtime_handler (EphyWebExtension *self,
106 char *name,
107 JSCValue *args)
108 {
109 guint idx;
110
111 for (idx = 0; idx < G_N_ELEMENTS (runtime_handlers); idx++) {
112 EphyWebExtensionApiHandler handler = runtime_handlers[idx];
113
114 if (g_strcmp0 (handler.name, name) == 0)
115 return handler.execute (self, name, args);
116 }
117
118 g_warning ("%s(): '%s' not implemented by Epiphany!", __FUNCTION__, name);
119
120 return NULL;
121 }
0 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
1 /*
2 * Copyright © 2019-2020 Jan-Michael Brummer <jan.brummer@tabos.org>
3 *
4 * This file is part of Epiphany.
5 *
6 * Epiphany is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Epiphany is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20
21 #pragma once
22
23 #include "ephy-web-extension.h"
24
25 G_BEGIN_DECLS
26
27 char *ephy_web_extension_api_runtime_handler (EphyWebExtension *self,
28 char *name,
29 JSCValue *args);
30
31 G_END_DECLS
0 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
1 /*
2 * Copyright © 2019-2020 Jan-Michael Brummer <jan.brummer@tabos.org>
3 *
4 * This file is part of Epiphany.
5 *
6 * Epiphany is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Epiphany is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "config.h"
21
22 #include "ephy-shell.h"
23 #include "ephy-window.h"
24
25 #include "tabs.h"
26
27 static void
28 add_web_view_to_json (JsonBuilder *builder,
29 EphyWebView *web_view)
30 {
31 json_builder_begin_object (builder);
32 json_builder_set_member_name (builder, "url");
33 json_builder_add_string_value (builder, ephy_web_view_get_address (web_view));
34 json_builder_set_member_name (builder, "id");
35 json_builder_add_int_value (builder, ephy_web_view_get_uid (web_view));
36 json_builder_end_object (builder);
37 }
38
39 static char *
40 tabs_handler_query (EphyWebExtension *self,
41 char *name,
42 JSCValue *args)
43 {
44 g_autoptr (JsonBuilder) builder = json_builder_new ();
45 g_autoptr (JsonNode) root = NULL;
46 EphyShell *shell = ephy_shell_get_default ();
47 GtkWindow *window;
48 GtkWidget *notebook;
49 gboolean current_window = TRUE;
50 gboolean active = TRUE;
51
52 if (jsc_value_object_has_property (args, "active")) {
53 g_autoptr (JSCValue) value = NULL;
54
55 value = jsc_value_object_get_property (args, "active");
56 active = jsc_value_to_boolean (value);
57 }
58
59 if (jsc_value_object_has_property (args, "currentWindow")) {
60 g_autoptr (JSCValue) value = NULL;
61
62 value = jsc_value_object_get_property (args, "currentWindow");
63 current_window = jsc_value_to_boolean (value);
64 }
65
66 if (current_window) {
67 window = gtk_application_get_active_window (GTK_APPLICATION (shell));
68 notebook = ephy_window_get_notebook (EPHY_WINDOW (window));
69
70 json_builder_begin_array (builder);
71
72 if (active) {
73 GtkWidget *page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook)));
74 EphyWebView *tmp_webview = ephy_embed_get_web_view (EPHY_EMBED (page));
75
76 add_web_view_to_json (builder, tmp_webview);
77 } else {
78 for (int i = 0; i < gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)); i++) {
79 GtkWidget *page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), i);
80 EphyWebView *tmp_webview = ephy_embed_get_web_view (EPHY_EMBED (page));
81
82 add_web_view_to_json (builder, tmp_webview);
83 }
84 }
85
86 json_builder_end_array (builder);
87 }
88
89 root = json_builder_get_root (builder);
90
91 return json_to_string (root, FALSE);
92 }
93
94 static char *
95 tabs_handler_insert_css (EphyWebExtension *self,
96 char *name,
97 JSCValue *args)
98 {
99 EphyShell *shell = ephy_shell_get_default ();
100 WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager (WEBKIT_WEB_VIEW (ephy_shell_get_active_web_view (shell)));
101 WebKitUserStyleSheet *css = NULL;
102 g_autoptr (JSCValue) code = NULL;
103
104 code = jsc_value_object_get_property (args, "code");
105 css = ephy_web_extension_add_custom_css (self, jsc_value_to_string (code));
106
107 if (css)
108 webkit_user_content_manager_add_style_sheet (ucm, css);
109
110 return NULL;
111 }
112
113 static char *
114 tabs_handler_remove_css (EphyWebExtension *self,
115 char *name,
116 JSCValue *args)
117 {
118 EphyShell *shell = ephy_shell_get_default ();
119 JSCValue *code;
120 WebKitUserStyleSheet *css = NULL;
121 WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager (WEBKIT_WEB_VIEW (ephy_shell_get_active_web_view (shell)));
122
123 code = jsc_value_object_get_property (args, "code");
124 css = ephy_web_extension_get_custom_css (self, jsc_value_to_string (code));
125 if (css)
126 webkit_user_content_manager_remove_style_sheet (ucm, css);
127
128 return NULL;
129 }
130
131 static char *
132 tabs_handler_get (EphyWebExtension *self,
133 char *name,
134 JSCValue *args)
135 {
136 EphyShell *shell = ephy_shell_get_default ();
137 g_autoptr (JsonBuilder) builder = json_builder_new ();
138 g_autoptr (JsonNode) root = NULL;
139 EphyWebView *tmp_webview = ephy_shell_get_active_web_view (shell);
140
141 add_web_view_to_json (builder, tmp_webview);
142 root = json_builder_get_root (builder);
143
144 return json_to_string (root, FALSE);
145 }
146
147 static char *
148 tabs_handler_execute_script (EphyWebExtension *self,
149 char *name,
150 JSCValue *args)
151 {
152 g_autoptr (JSCValue) code_value = NULL;
153 g_autoptr (JSCValue) obj = NULL;
154 EphyShell *shell = ephy_shell_get_default ();
155
156 if (jsc_value_is_array (args)) {
157 obj = jsc_value_object_get_property_at_index (args, 1);
158 } else {
159 obj = args;
160 }
161
162 code_value = jsc_value_object_get_property (obj, "code");
163 if (code_value) {
164 g_autofree char *code = jsc_value_to_string (code_value);
165 webkit_web_view_run_javascript_in_world (WEBKIT_WEB_VIEW (ephy_shell_get_active_web_view (shell)),
166 code,
167 ephy_embed_shell_get_guid (ephy_embed_shell_get_default ()),
168 NULL,
169 NULL,
170 NULL);
171 }
172
173 return NULL;
174 }
175
176 static EphyWebExtensionApiHandler tabs_handlers[] = {
177 {"query", tabs_handler_query},
178 {"insertCSS", tabs_handler_insert_css},
179 {"removeCSS", tabs_handler_remove_css},
180 {"get", tabs_handler_get},
181 {"executeScript", tabs_handler_execute_script},
182 {NULL, NULL},
183 };
184
185 char *
186 ephy_web_extension_api_tabs_handler (EphyWebExtension *self,
187 char *name,
188 JSCValue *args)
189 {
190 guint idx;
191
192 for (idx = 0; idx < G_N_ELEMENTS (tabs_handlers); idx++) {
193 EphyWebExtensionApiHandler handler = tabs_handlers[idx];
194
195 if (g_strcmp0 (handler.name, name) == 0)
196 return handler.execute (self, name, args);
197 }
198
199 g_warning ("%s(): '%s' not implemented by Epiphany!", __FUNCTION__, name);
200
201 return NULL;
202 }
0 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
1 /*
2 * Copyright © 2019-2020 Jan-Michael Brummer <jan.brummer@tabos.org>
3 *
4 * This file is part of Epiphany.
5 *
6 * Epiphany is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Epiphany is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20
21 #pragma once
22
23 #include "ephy-web-extension.h"
24
25 #include <webkit2/webkit2.h>
26
27 G_BEGIN_DECLS
28
29 char *ephy_web_extension_api_tabs_handler (EphyWebExtension *self,
30 char *name,
31 JSCValue *value);
32
33 G_END_DECLS
0 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
1 /*
2 * Copyright © 2019-2020 Jan-Michael Brummer <jan.brummer@tabos.org>
3 *
4 * This file is part of Epiphany.
5 *
6 * Epiphany is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Epiphany is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "config.h"
21
22 #include "ephy-debug.h"
23 #include "ephy-embed-shell.h"
24 #include "ephy-embed-prefs.h"
25 #include "ephy-embed-utils.h"
26 #include "ephy-file-helpers.h"
27 #include "ephy-header-bar.h"
28 #include "ephy-location-entry.h"
29 #include "ephy-notification.h"
30 #include "ephy-settings.h"
31 #include "ephy-shell.h"
32 #include "ephy-string.h"
33 #include "ephy-web-extension.h"
34 #include "ephy-web-extension-manager.h"
35 #include "ephy-web-view.h"
36
37 #include "api/notifications.h"
38 #include "api/pageaction.h"
39 #include "api/runtime.h"
40 #include "api/tabs.h"
41
42 #include <json-glib/json-glib.h>
43
44 struct _EphyWebExtensionManager {
45 GObject parent_instance;
46
47 GCancellable *cancellable;
48 GList *web_extensions;
49 GHashTable *page_action_map;
50 GHashTable *browser_action_map;
51 GHashTable *background_web_views;
52 };
53
54 G_DEFINE_TYPE (EphyWebExtensionManager, ephy_web_extension_manager, G_TYPE_OBJECT)
55
56 EphyWebExtensionApiHandler api_handlers[] = {
57 {"notifications", ephy_web_extension_api_notifications_handler},
58 {"pageAction", ephy_web_extension_api_pageaction_handler},
59 {"runtime", ephy_web_extension_api_runtime_handler},
60 {"tabs", ephy_web_extension_api_tabs_handler},
61 {NULL, NULL},
62 };
63
64 enum {
65 CHANGED,
66 LAST_SIGNAL
67 };
68
69 static guint signals[LAST_SIGNAL];
70
71 static void
72 ephy_web_extension_manager_add_to_list (EphyWebExtensionManager *self,
73 EphyWebExtension *web_extension)
74 {
75 self->web_extensions = g_list_append (self->web_extensions, g_object_ref (web_extension));
76
77 g_signal_emit (self, signals[CHANGED], 0);
78 }
79
80 static void
81 ephy_web_extension_manager_remove_from_list (EphyWebExtensionManager *self,
82 EphyWebExtension *web_extension)
83 {
84 self->web_extensions = g_list_remove (self->web_extensions, web_extension);
85 g_object_unref (web_extension);
86
87 g_signal_emit (self, signals[CHANGED], 0);
88 }
89
90 void
91 on_web_extension_loaded (GObject *source_object,
92 GAsyncResult *result,
93 gpointer user_data)
94 {
95 g_autoptr (GError) error = NULL;
96 EphyWebExtension *web_extension;
97 EphyWebExtensionManager *self = EPHY_WEB_EXTENSION_MANAGER (user_data);
98
99
100 web_extension = ephy_web_extension_load_finished (source_object, result, &error);
101 if (!web_extension) {
102 return;
103 }
104
105 ephy_web_extension_manager_add_to_list (self, web_extension);
106 g_object_unref (web_extension);
107
108 if (ephy_web_extension_manager_is_active (self, web_extension))
109 ephy_web_extension_manager_set_active (self, web_extension, TRUE);
110 }
111
112 static void
113 ephy_web_extension_manager_scan_directory (EphyWebExtensionManager *self,
114 const char *extension_dir)
115 {
116 g_autoptr (GDir) dir = NULL;
117 g_autoptr (GError) error = NULL;
118 const char *directory;
119
120 if (g_mkdir_with_parents (extension_dir, 0700) != 0)
121 g_warning ("Failed to create %s: %s", extension_dir, g_strerror (errno));
122
123 if (!g_file_test (extension_dir, G_FILE_TEST_EXISTS))
124 g_mkdir_with_parents (extension_dir, 0700);
125
126 dir = g_dir_open (extension_dir, 0, &error);
127 if (!dir) {
128 g_warning ("Could not open %s: %s", extension_dir, error->message);
129 return;
130 }
131
132 errno = 0;
133 while ((directory = g_dir_read_name (dir))) {
134 g_autofree char *filename = NULL;
135 g_autoptr (GFile) file = NULL;
136
137 if (errno != 0) {
138 g_warning ("Problem reading %s: %s", extension_dir, g_strerror (errno));
139 break;
140 }
141
142 filename = g_build_filename (extension_dir, directory, NULL);
143 file = g_file_new_for_path (filename);
144
145 ephy_web_extension_load_async (file, self->cancellable, on_web_extension_loaded, self);
146
147 errno = 0;
148 }
149 }
150
151 static void
152 ephy_web_extension_manager_constructed (GObject *object)
153 {
154 EphyWebExtensionManager *self = EPHY_WEB_EXTENSION_MANAGER (object);
155 g_autofree char *dir = g_build_filename (ephy_default_profile_dir (), "web_extensions", NULL);
156
157 self->background_web_views = g_hash_table_new (NULL, NULL);
158 self->page_action_map = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_hash_table_destroy);
159 self->browser_action_map = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)gtk_widget_destroy);
160 self->web_extensions = NULL;
161
162 ephy_web_extension_manager_scan_directory (self, dir);
163 }
164
165 static void
166 ephy_web_extension_manager_dispose (GObject *object)
167 {
168 EphyWebExtensionManager *self = EPHY_WEB_EXTENSION_MANAGER (object);
169
170 g_clear_pointer (&self->background_web_views, g_hash_table_destroy);
171 g_clear_pointer (&self->page_action_map, g_hash_table_destroy);
172 g_list_free_full (g_steal_pointer (&self->web_extensions), g_object_unref);
173 }
174
175 static void
176 ephy_web_extension_manager_class_init (EphyWebExtensionManagerClass *klass)
177 {
178 GObjectClass *object_class = G_OBJECT_CLASS (klass);
179
180 object_class->constructed = ephy_web_extension_manager_constructed;
181 object_class->dispose = ephy_web_extension_manager_dispose;
182
183 signals[CHANGED] =
184 g_signal_new ("changed",
185 G_OBJECT_CLASS_TYPE (object_class),
186 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
187 0, NULL, NULL, NULL,
188 G_TYPE_NONE, 0);
189 }
190
191 static void
192 ephy_web_extension_manager_init (EphyWebExtensionManager *self)
193 {
194 }
195
196 EphyWebExtensionManager *ephy_web_extension_manager_new (void)
197 {
198 return g_object_new (EPHY_TYPE_WEB_EXTENSION_MANAGER, NULL);
199 }
200
201 GList *
202 ephy_web_extension_manager_get_web_extensions (EphyWebExtensionManager *self)
203 {
204 return self->web_extensions;
205 }
206
207 /**
208 * Installs/Adds all web_extensions to new EphyWindow.
209 */
210 void
211 ephy_web_extension_manager_install_actions (EphyWebExtensionManager *self,
212 EphyWindow *window)
213 {
214 for (GList *list = self->web_extensions; list && list->data; list = list->next)
215 ephy_web_extension_manager_add_web_extension_to_window (self, list->data, window);
216 }
217
218 void
219 on_new_web_extension_loaded (GObject *source_object,
220 GAsyncResult *result,
221 gpointer user_data)
222 {
223 g_autoptr (GError) error = NULL;
224 EphyWebExtension *web_extension;
225 EphyWebExtensionManager *self = EPHY_WEB_EXTENSION_MANAGER (user_data);
226
227 web_extension = ephy_web_extension_load_finished (source_object, result, &error);
228 if (!web_extension) {
229 return;
230 }
231
232 ephy_web_extension_manager_add_to_list (self, web_extension);
233 }
234 /**
235 * Install a new web web_extension into the local web_extension directory.
236 * File should only point to a manifest.json or a .xpi file
237 */
238 void
239 ephy_web_extension_manager_install (EphyWebExtensionManager *self,
240 GFile *file)
241 {
242 g_autoptr (GFile) target = NULL;
243 g_autofree char *basename = NULL;
244 gboolean is_xpi = FALSE;
245
246 basename = g_file_get_basename (file);
247 is_xpi = g_str_has_suffix (basename, ".xpi");
248
249 if (!is_xpi) {
250 g_autoptr (GFile) source = NULL;
251
252 /* Get parent directory */
253 source = g_file_get_parent (file);
254 target = g_file_new_build_filename (ephy_default_profile_dir (), "web_extensions", g_file_get_basename (source), NULL);
255
256 ephy_copy_directory (g_file_get_path (source), g_file_get_path (target));
257 } else {
258 g_autoptr (GError) error = NULL;
259 target = g_file_new_build_filename (ephy_default_profile_dir (), "web_extensions", g_file_get_basename (file), NULL);
260
261 if (!g_file_copy (file, target, G_FILE_COPY_NONE, NULL, NULL, NULL, &error)) {
262 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
263 g_warning ("Could not copy file for web_extensions: %s", error->message);
264 return;
265 }
266 }
267 }
268
269 if (target)
270 ephy_web_extension_load_async (g_steal_pointer (&target), self->cancellable, on_new_web_extension_loaded, self);
271 }
272
273 void
274 ephy_web_extension_manager_uninstall (EphyWebExtensionManager *self,
275 EphyWebExtension *web_extension)
276 {
277 if (ephy_web_extension_manager_is_active (self, web_extension))
278 ephy_web_extension_manager_set_active (self, web_extension, FALSE);
279
280 ephy_web_extension_remove (web_extension);
281 ephy_web_extension_manager_remove_from_list (self, web_extension);
282 }
283
284 void
285 ephy_web_extension_manager_update_location_entry (EphyWebExtensionManager *self,
286 EphyWindow *window)
287 {
288 GtkWidget *title_widget;
289 EphyLocationEntry *lentry;
290 GtkWidget *notebook = ephy_window_get_notebook (EPHY_WINDOW (window));
291 int current_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
292 GtkWidget *page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), current_page);
293 EphyWebView *web_view;
294
295 if (!page)
296 return;
297
298 web_view = ephy_embed_get_web_view (EPHY_EMBED (page));
299 title_widget = GTK_WIDGET (ephy_header_bar_get_title_widget (EPHY_HEADER_BAR (ephy_window_get_header_bar (window))));
300 if (!EPHY_IS_LOCATION_ENTRY (title_widget))
301 return;
302
303 lentry = EPHY_LOCATION_ENTRY (title_widget);
304
305 ephy_location_entry_page_action_clear (lentry);
306
307 for (GList *list = ephy_web_extension_manager_get_web_extensions (self); list && list->data; list = list->next) {
308 EphyWebExtension *web_extension = EPHY_WEB_EXTENSION (list->data);
309 GtkWidget *action = ephy_web_extension_manager_get_page_action (self, web_extension, web_view);
310
311 if (action)
312 ephy_location_entry_page_action_add (lentry, action);
313 }
314 }
315
316 EphyWebView *
317 ephy_web_extension_manager_get_background_web_view (EphyWebExtensionManager *self,
318 EphyWebExtension *web_extension)
319 {
320 return g_hash_table_lookup (self->background_web_views, web_extension);
321 }
322
323 static void
324 ephy_web_extension_manager_set_background_web_view (EphyWebExtensionManager *self,
325 EphyWebExtension *web_extension,
326 EphyWebView *web_view)
327 {
328 g_hash_table_insert (self->background_web_views, web_extension, web_view);
329 }
330
331 static gboolean
332 page_action_clicked (GtkWidget *event_box,
333 GdkEventButton *event,
334 gpointer user_data)
335 {
336 EphyWebExtension *web_extension = EPHY_WEB_EXTENSION (user_data);
337 EphyShell *shell = ephy_shell_get_default ();
338 EphyWebView *view = EPHY_WEB_VIEW (ephy_shell_get_active_web_view (shell));
339 g_autoptr (JsonBuilder) builder = json_builder_new ();
340 g_autoptr (JsonNode) root = NULL;
341 g_autofree char *json = NULL;
342 g_autofree char *script = NULL;
343 EphyWebExtensionManager *self = ephy_shell_get_web_extension_manager (shell);
344 WebKitWebView *web_view = WEBKIT_WEB_VIEW (ephy_web_extension_manager_get_background_web_view (self, web_extension));
345
346 json_builder_begin_object (builder);
347 json_builder_set_member_name (builder, "url");
348 json_builder_add_string_value (builder, ephy_web_view_get_address (view));
349 json_builder_set_member_name (builder, "id");
350 json_builder_add_int_value (builder, ephy_web_view_get_uid (view));
351 json_builder_end_object (builder);
352
353 root = json_builder_get_root (builder);
354
355 json = json_to_string (root, FALSE);
356
357 script = g_strdup_printf ("pageActionOnClicked(%s);", json);
358 webkit_web_view_run_javascript_in_world (web_view,
359 script,
360 ephy_embed_shell_get_guid (EPHY_EMBED_SHELL (shell)),
361 NULL,
362 NULL,
363 NULL);
364
365 return GDK_EVENT_STOP;
366 }
367
368 static GtkWidget *
369 create_page_action_widget (EphyWebExtensionManager *self,
370 EphyWebExtension *web_extension)
371 {
372 GtkWidget *image;
373 GtkWidget *event_box;
374
375 /* Create new event box with page action */
376 event_box = gtk_event_box_new ();
377 image = gtk_image_new ();
378 gtk_container_add (GTK_CONTAINER (event_box), image);
379 g_signal_connect_object (event_box, "button_press_event", G_CALLBACK (page_action_clicked), web_extension, 0);
380 gtk_widget_show_all (event_box);
381
382 return g_object_ref (event_box);
383 }
384
385 static void
386 ephy_web_extension_handle_background_script_message (WebKitUserContentManager *ucm,
387 WebKitJavascriptResult *js_result,
388 gpointer user_data)
389 {
390 EphyWebExtension *web_extension = EPHY_WEB_EXTENSION (user_data);
391 JSCValue *value = webkit_javascript_result_get_js_value (js_result);
392 EphyWebExtensionManager *self = ephy_shell_get_web_extension_manager (ephy_shell_get_default ());
393 WebKitWebView *web_view = WEBKIT_WEB_VIEW (ephy_web_extension_manager_get_background_web_view (self, web_extension));
394 g_autofree char *name_str = NULL;
395 g_autoptr (JSCValue) name = NULL;
396 g_autoptr (JSCValue) promise = NULL;
397 g_auto (GStrv) split = NULL;
398 GPtrArray *permissions = ephy_web_extension_get_permissions (web_extension);
399 unsigned int idx;
400
401 if (!jsc_value_is_object (value))
402 return;
403
404 if (!jsc_value_object_has_property (value, "promise"))
405 return;
406
407 promise = jsc_value_object_get_property (value, "promise");
408 if (!jsc_value_is_number (promise))
409 return;
410
411 name = jsc_value_object_get_property (value, "fn");
412 if (!name)
413 return;
414
415 name_str = jsc_value_to_string (name);
416 LOG ("%s(): Called for %s, function %s\n", __FUNCTION__, ephy_web_extension_get_name (web_extension), name_str);
417
418 split = g_strsplit (name_str, ".", 2);
419 if (g_strv_length (split) != 2) {
420 g_warning ("Invalid function call, aborting: %s", name_str);
421 return;
422 }
423
424 for (idx = 0; idx < G_N_ELEMENTS (api_handlers); idx++) {
425 EphyWebExtensionApiHandler handler = api_handlers[idx];
426
427 if (!g_ptr_array_find (permissions, split[0], NULL)) {
428 LOG ("%s(): Requested api is not part of the permissions, aborting\n", __FUNCTION__);
429 /* TODO: Permissions are not working yet */
430 /*return; */
431 }
432
433 if (g_strcmp0 (handler.name, split[0]) == 0) {
434 g_autofree char *ret = NULL;
435 g_autofree char *script = NULL;
436 g_autoptr (JSCValue) args = jsc_value_object_get_property (value, "args");
437
438 ret = handler.execute (web_extension, split[1], args);
439 script = g_strdup_printf ("promises[%.f].resolve(%s);", jsc_value_to_double (promise), ret ? ret : "");
440 webkit_web_view_run_javascript_in_world (web_view, script, ephy_embed_shell_get_guid (ephy_embed_shell_get_default ()), NULL, NULL, NULL);
441
442 return;
443 }
444 }
445
446 g_warning ("%s(): '%s' not implemented by Epiphany!", __FUNCTION__, name_str);
447 }
448
449 static void
450 add_content_scripts (EphyWebExtension *web_extension,
451 EphyWebView *web_view)
452 {
453 GList *content_scripts = ephy_web_extension_get_content_scripts (web_extension);
454 WebKitUserContentManager *ucm;
455
456 if (!content_scripts)
457 return;
458
459 ucm = webkit_web_view_get_user_content_manager (WEBKIT_WEB_VIEW (web_view));
460 g_signal_connect_object (ucm, "script-message-received", G_CALLBACK (ephy_web_extension_handle_background_script_message), web_extension, 0);
461 webkit_user_content_manager_register_script_message_handler_in_world (ucm, "epiphany", ephy_embed_shell_get_guid (ephy_embed_shell_get_default ()));
462
463 for (GList *list = content_scripts; list && list->data; list = list->next) {
464 GList *js_list = ephy_web_extension_get_content_script_js (web_extension, list->data);
465
466 for (GList *tmp_list = js_list; tmp_list && tmp_list->data; tmp_list = tmp_list->next) {
467 webkit_user_content_manager_add_script (WEBKIT_USER_CONTENT_MANAGER (ucm), tmp_list->data);
468 }
469 }
470 }
471
472 static void
473 remove_content_scripts (EphyWebExtension *self,
474 EphyWebView *web_view)
475 {
476 GList *content_scripts = ephy_web_extension_get_content_scripts (self);
477 WebKitUserContentManager *ucm;
478
479 if (!content_scripts)
480 return;
481
482 ucm = webkit_web_view_get_user_content_manager (WEBKIT_WEB_VIEW (web_view));
483
484 for (GList *list = content_scripts; list && list->data; list = list->next) {
485 GList *js_list = ephy_web_extension_get_content_script_js (self, list->data);
486
487 for (GList *tmp_list = js_list; tmp_list && tmp_list->data; tmp_list = tmp_list->next)
488 webkit_user_content_manager_remove_script (WEBKIT_USER_CONTENT_MANAGER (ucm), tmp_list->data);
489 }
490
491 g_signal_handlers_disconnect_by_func (ucm, G_CALLBACK (ephy_web_extension_handle_background_script_message), self);
492 }
493
494 static void
495 remove_custom_css (EphyWebExtension *self,
496 EphyWebView *web_view)
497 {
498 GList *custom_css = ephy_web_extension_get_custom_css_list (self);
499 GList *list;
500 WebKitUserContentManager *ucm;
501
502 if (!custom_css)
503 return;
504
505 ucm = webkit_web_view_get_user_content_manager (WEBKIT_WEB_VIEW (web_view));
506
507 for (list = custom_css; list && list->data; list = list->next)
508 webkit_user_content_manager_remove_style_sheet (WEBKIT_USER_CONTENT_MANAGER (ucm), ephy_web_extension_custom_css_style (self, list->data));
509 }
510
511 static void
512 update_translations (EphyWebExtension *web_extension)
513 {
514 /* TODO: Use current locale and fallback to default web_extension locale if necessary */
515 g_autofree char *path = g_strdup_printf ("_locales/%s/messages.json", "en");
516 g_autofree char *data = NULL;
517 gint length = 0;
518
519 data = ephy_web_extension_get_resource_as_string (web_extension, path);
520 if (data)
521 length = strlen (data);
522
523 webkit_web_context_send_message_to_all_extensions (ephy_embed_shell_get_web_context (ephy_embed_shell_get_default ()),
524 webkit_user_message_new ("WebExtension.Add",
525 g_variant_new ("(sst)", ephy_web_extension_get_name (web_extension), data ? (char *)data : "", length)));
526 }
527
528 static void
529 ephy_web_extension_manager_add_web_extension_to_webview (EphyWebExtensionManager *self,
530 EphyWebExtension *web_extension,
531 EphyWindow *window,
532 EphyWebView *web_view)
533 {
534 GtkWidget *title_widget = GTK_WIDGET (ephy_header_bar_get_title_widget (EPHY_HEADER_BAR (ephy_window_get_header_bar (window))));
535 EphyLocationEntry *lentry = NULL;
536
537 if (EPHY_IS_LOCATION_ENTRY (title_widget)) {
538 lentry = EPHY_LOCATION_ENTRY (title_widget);
539
540 if (lentry && ephy_web_extension_has_page_action (web_extension)) {
541 GtkWidget *page_action = create_page_action_widget (self, web_extension);
542 GHashTable *table;
543
544 table = g_hash_table_lookup (self->page_action_map, web_extension);
545 if (!table) {
546 table = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)gtk_widget_destroy);
547 g_hash_table_insert (self->page_action_map, web_extension, table);
548 }
549
550 g_hash_table_insert (table, web_view, g_steal_pointer (&page_action));
551 }
552 }
553
554 update_translations (web_extension);
555 add_content_scripts (web_extension, web_view);
556 }
557
558 static void
559 page_added_cb (GtkNotebook *notebook,
560 GtkWidget *child,
561 guint page_num,
562 gpointer user_data)
563 {
564 EphyWebExtension *web_extension = EPHY_WEB_EXTENSION (user_data);
565 EphyWebView *web_view = ephy_embed_get_web_view (EPHY_EMBED (child));
566 EphyWindow *window = EPHY_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (notebook)));
567 EphyWebExtensionManager *self = ephy_shell_get_web_extension_manager (ephy_shell_get_default ());
568
569
570 ephy_web_extension_manager_add_web_extension_to_webview (self, web_extension, window, web_view);
571 ephy_web_extension_manager_update_location_entry (self, window);
572 }
573
574 static void
575 web_extension_cb (WebKitURISchemeRequest *request,
576 gpointer user_data)
577 {
578 EphyWebExtension *web_extension = EPHY_WEB_EXTENSION (user_data);
579 const char *path;
580 const unsigned char *data;
581 gsize length;
582 g_autoptr (GInputStream) stream = NULL;
583
584 path = webkit_uri_scheme_request_get_path (request);
585
586 data = ephy_web_extension_get_resource (web_extension, path + 1, &length);
587 if (!data)
588 return;
589
590 stream = g_memory_input_stream_new_from_data (data, length, NULL);
591 webkit_uri_scheme_request_finish (request, stream, length, NULL);
592 }
593
594 static void
595 init_web_extension_api (WebKitWebContext *web_context,
596 EphyWebExtension *web_extension)
597 {
598 g_autoptr (GVariant) user_data = NULL;
599
600 #if DEVELOPER_MODE
601 webkit_web_context_set_web_extensions_directory (web_context, BUILD_ROOT "/embed/web-process-extension");
602 #else
603 webkit_web_context_set_web_extensions_directory (web_context, EPHY_WEB_PROCESS_EXTENSIONS_DIR);
604 #endif
605
606 user_data = g_variant_new ("(smsbb)",
607 "",
608 ephy_profile_dir_is_default () ? NULL : ephy_profile_dir (),
609 FALSE,
610 FALSE);
611 webkit_web_context_set_web_extensions_initialization_user_data (web_context, g_steal_pointer (&user_data));
612 }
613
614 static GtkWidget *
615 create_web_extensions_webview (EphyWebExtension *web_extension,
616 gboolean custom_web_context)
617 {
618 WebKitUserContentManager *ucm;
619 WebKitWebContext *web_context;
620 WebKitSettings *settings;
621 GtkWidget *web_view;
622
623 /* Create an own ucm so new scripts/css are only applied to this web_view */
624 ucm = webkit_user_content_manager_new ();
625 g_signal_connect_object (ucm, "script-message-received", G_CALLBACK (ephy_web_extension_handle_background_script_message), web_extension, 0);
626
627 if (!custom_web_context) {
628 /* Get webcontext and register web_extension scheme */
629 webkit_user_content_manager_register_script_message_handler_in_world (ucm,
630 "epiphany",
631 ephy_embed_shell_get_guid (ephy_embed_shell_get_default ()));
632 web_context = ephy_embed_shell_get_web_context (ephy_embed_shell_get_default ());
633 webkit_web_context_register_uri_scheme (web_context, "webextension", web_extension_cb, web_extension, NULL);
634 webkit_security_manager_register_uri_scheme_as_secure (webkit_web_context_get_security_manager (web_context),
635 "webextension");
636 web_view = ephy_web_view_new_with_user_content_manager (ucm);
637 } else {
638 webkit_user_content_manager_register_script_message_handler (ucm, "epiphany");
639 web_context = webkit_web_context_new ();
640 webkit_web_context_register_uri_scheme (web_context, "webextension", web_extension_cb, web_extension, NULL);
641 g_signal_connect_object (web_context, "initialize-web_extensions", G_CALLBACK (init_web_extension_api), web_extension, 0);
642 webkit_security_manager_register_uri_scheme_as_secure (webkit_web_context_get_security_manager (web_context),
643 "webextension");
644 web_view = g_object_new (EPHY_TYPE_WEB_VIEW,
645 "web-context", web_context,
646 "user-content-manager", ucm,
647 "settings", ephy_embed_prefs_get_settings (),
648 NULL);
649 }
650
651 settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (web_view));
652 webkit_settings_set_enable_write_console_messages_to_stdout (settings, TRUE);
653
654 update_translations (web_extension);
655
656 return web_view;
657 }
658
659 static GtkWidget *
660 create_browser_popup (EphyWebExtension *web_extension)
661 {
662 GtkWidget *web_view;
663 GtkWidget *popover;
664 g_autofree char *data = NULL;
665 g_autofree char *base_uri = NULL;
666 g_autofree char *dir_name = NULL;
667 const char *popup;
668
669 popover = gtk_popover_new (NULL);
670
671 web_view = create_web_extensions_webview (web_extension, TRUE);
672
673 gtk_widget_set_hexpand (web_view, TRUE);
674 gtk_widget_set_vexpand (web_view, TRUE);
675
676 popup = ephy_web_extension_get_browser_popup (web_extension);
677 dir_name = g_path_get_dirname (popup);
678 base_uri = g_strdup_printf ("webextension:///%s/", dir_name);
679 data = ephy_web_extension_get_resource_as_string (web_extension, popup);
680 webkit_web_view_load_html (WEBKIT_WEB_VIEW (web_view), (char *)data, base_uri);
681 gtk_container_add (GTK_CONTAINER (popover), web_view);
682 gtk_widget_show_all (web_view);
683
684 return popover;
685 }
686
687 static gboolean
688 on_browser_action_clicked (GtkWidget *event_box,
689 gpointer user_data)
690 {
691 EphyShell *shell = ephy_shell_get_default ();
692 EphyWebExtension *web_extension = EPHY_WEB_EXTENSION (user_data);
693 EphyWebExtensionManager *self = ephy_shell_get_web_extension_manager (ephy_shell_get_default ());
694 g_autofree char *script = NULL;
695 WebKitWebView *web_view = NULL;
696 gboolean own_web_view = !!ephy_web_extension_background_web_view_get_page (web_extension);
697
698 if (!own_web_view)
699 web_view = WEBKIT_WEB_VIEW (ephy_web_extension_manager_get_background_web_view (self, web_extension));
700 else
701 web_view = WEBKIT_WEB_VIEW (ephy_shell_get_active_web_view (shell));
702
703 script = g_strdup_printf ("browserActionClicked();");
704
705 webkit_web_view_run_javascript_in_world (web_view,
706 script,
707 ephy_embed_shell_get_guid (ephy_embed_shell_get_default ()),
708 NULL,
709 NULL,
710 NULL);
711
712 return GDK_EVENT_STOP;
713 }
714
715
716 GtkWidget *
717 create_browser_action (EphyWebExtension *web_extension)
718 {
719 GtkWidget *button;
720 GtkWidget *image;
721 GtkWidget *popover;
722
723 if (ephy_web_extension_get_browser_popup (web_extension)) {
724 button = gtk_menu_button_new ();
725 image = gtk_image_new_from_pixbuf (ephy_web_extension_browser_action_get_icon (web_extension, 16));
726 popover = create_browser_popup (web_extension);
727 gtk_menu_button_set_popover (GTK_MENU_BUTTON (button), popover);
728
729 gtk_button_set_image (GTK_BUTTON (button), image);
730 gtk_widget_set_visible (button, TRUE);
731 } else {
732 GdkPixbuf *pixbuf = ephy_web_extension_browser_action_get_icon (web_extension, 16);
733
734 button = gtk_button_new ();
735
736 if (pixbuf)
737 image = gtk_image_new_from_pixbuf (pixbuf);
738 else
739 image = gtk_image_new_from_icon_name ("application-x-addon-symbolic", GTK_ICON_SIZE_BUTTON);
740
741 g_signal_connect_object (button, "clicked", G_CALLBACK (on_browser_action_clicked), web_extension, 0);
742 gtk_button_set_image (GTK_BUTTON (button), image);
743 gtk_widget_set_visible (button, TRUE);
744 }
745
746 return button;
747 }
748
749 void
750 ephy_web_extension_manager_add_web_extension_to_window (EphyWebExtensionManager *self,
751 EphyWebExtension *web_extension,
752 EphyWindow *window)
753 {
754 GtkWidget *notebook = ephy_window_get_notebook (EPHY_WINDOW (window));
755
756 if (!ephy_web_extension_manager_is_active (self, web_extension))
757 return;
758
759 /* Add page actions and add content script */
760 for (int i = 0; i < gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)); i++) {
761 GtkWidget *page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), i);
762 EphyWebView *web_view = ephy_embed_get_web_view (EPHY_EMBED (page));
763
764 ephy_web_extension_manager_add_web_extension_to_webview (self, web_extension, window, web_view);
765 }
766
767 if (ephy_web_extension_has_browser_action (web_extension)) {
768 GtkWidget *browser_action_widget = create_browser_action (web_extension);
769 ephy_header_bar_add_browser_action (EPHY_HEADER_BAR (ephy_window_get_header_bar (window)), browser_action_widget);
770 g_hash_table_insert (self->browser_action_map, web_extension, browser_action_widget);
771 }
772
773 ephy_web_extension_manager_update_location_entry (self, window);
774 g_signal_connect_object (notebook, "page-added", G_CALLBACK (page_added_cb), web_extension, 0);
775 }
776
777 static gboolean
778 remove_page_action (gpointer key,
779 gpointer value,
780 gpointer user_data)
781 {
782 return TRUE;
783 }
784
785 void
786 ephy_web_extension_manager_remove_web_extension_from_webview (EphyWebExtensionManager *self,
787 EphyWebExtension *web_extension,
788 EphyWindow *window,
789 EphyWebView *web_view)
790 {
791 GtkWidget *title_widget = GTK_WIDGET (ephy_header_bar_get_title_widget (EPHY_HEADER_BAR (ephy_window_get_header_bar (window))));
792 EphyLocationEntry *lentry = NULL;
793
794 if (EPHY_IS_LOCATION_ENTRY (title_widget))
795 lentry = EPHY_LOCATION_ENTRY (title_widget);
796
797 g_hash_table_foreach_remove (self->page_action_map, remove_page_action, web_view);
798
799 if (lentry)
800 ephy_location_entry_page_action_clear (lentry);
801
802 remove_content_scripts (web_extension, web_view);
803 remove_custom_css (web_extension, web_view);
804 }
805
806 void
807 ephy_web_extension_manager_remove_web_extension_from_window (EphyWebExtensionManager *self,
808 EphyWebExtension *web_extension,
809 EphyWindow *window)
810 {
811 GtkWidget *notebook = ephy_window_get_notebook (EPHY_WINDOW (window));
812 GtkWidget *browser_action_widget;
813
814 if (ephy_web_extension_manager_is_active (self, web_extension))
815 return;
816
817 for (int i = 0; i < gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)); i++) {
818 GtkWidget *page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), i);
819 EphyWebView *web_view = ephy_embed_get_web_view (EPHY_EMBED (page));
820
821 ephy_web_extension_manager_remove_web_extension_from_webview (self, web_extension, window, web_view);
822 }
823
824 browser_action_widget = g_hash_table_lookup (self->browser_action_map, web_extension);
825 if (browser_action_widget) {
826 g_hash_table_remove (self->browser_action_map, web_extension);
827 }
828
829 ephy_web_extension_manager_update_location_entry (self, window);
830
831 g_signal_handlers_disconnect_by_data (notebook, web_extension);
832 }
833
834 gboolean
835 ephy_web_extension_manager_is_active (EphyWebExtensionManager *self,
836 EphyWebExtension *web_extension)
837 {
838 g_auto (GStrv) web_extensions_active = g_settings_get_strv (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_WEBEXTENSIONS_ACTIVE);
839
840 return g_strv_contains ((const char * const *)web_extensions_active, ephy_web_extension_get_name (web_extension));
841 }
842
843 static void
844 run_background_script (EphyWebExtensionManager *self,
845 EphyWebExtension *web_extension)
846 {
847 WebKitUserContentManager *ucm;
848 GtkWidget *background;
849 g_autofree char *base_uri = NULL;
850 const char *page;
851
852 if (!ephy_web_extension_has_background_web_view (web_extension) || ephy_web_extension_manager_get_background_web_view (self, web_extension))
853 return;
854
855 page = ephy_web_extension_background_web_view_get_page (web_extension);
856
857 /* Create new background web_view */
858 background = create_web_extensions_webview (web_extension, page != NULL);
859 ephy_web_extension_manager_set_background_web_view (self, web_extension, EPHY_WEB_VIEW (background));
860
861 if (page) {
862 g_autofree char *data = ephy_web_extension_get_resource_as_string (web_extension, page);
863
864 base_uri = g_strdup_printf ("webextension://%s/%s/", ephy_web_extension_get_guid (web_extension), g_path_get_dirname (page));
865 webkit_web_view_load_html (WEBKIT_WEB_VIEW (background), (char *)data, base_uri);
866 } else {
867 GPtrArray *scripts = ephy_web_extension_background_web_view_get_scripts (web_extension);
868
869 ucm = webkit_web_view_get_user_content_manager (WEBKIT_WEB_VIEW (background));
870
871 base_uri = g_strdup_printf ("webextension://%s/", ephy_web_extension_get_guid (web_extension));
872 for (unsigned int i = 0; i < scripts->len; i++) {
873 char *script_file = g_ptr_array_index (scripts, i);
874 g_autofree char *data = NULL;
875 WebKitUserScript *user_script;
876
877 data = ephy_web_extension_get_resource_as_string (web_extension, script_file);
878 user_script = webkit_user_script_new_for_world (data,
879 WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
880 WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END,
881 ephy_embed_shell_get_guid (ephy_embed_shell_get_default ()),
882 NULL,
883 NULL);
884
885 webkit_user_content_manager_add_script (ucm, user_script);
886 }
887 webkit_web_view_load_html (WEBKIT_WEB_VIEW (background), "<body></body>", base_uri);
888 }
889 }
890
891 static GPtrArray *
892 strv_to_ptr_array (char **strv)
893 {
894 GPtrArray *array = g_ptr_array_new ();
895
896 for (char **str = strv; *str; ++str) {
897 g_ptr_array_add (array, g_strdup (*str));
898 }
899
900 return array;
901 }
902
903 static gboolean
904 extension_equal (gconstpointer a,
905 gconstpointer b)
906 {
907 return g_strcmp0 (a, b) == 0;
908 }
909
910 void
911 ephy_web_extension_manager_set_active (EphyWebExtensionManager *self,
912 EphyWebExtension *web_extension,
913 gboolean active)
914 {
915 g_auto (GStrv) web_extensions_active = g_settings_get_strv (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_WEBEXTENSIONS_ACTIVE);
916 EphyShell *shell = ephy_shell_get_default ();
917 GList *windows = gtk_application_get_windows (GTK_APPLICATION (shell));
918 GList *list;
919 g_autoptr (GPtrArray) array = strv_to_ptr_array (web_extensions_active);
920 const char *name = ephy_web_extension_get_name (web_extension);
921 gboolean found;
922 guint idx;
923
924 /* Update settings */
925 found = g_ptr_array_find_with_equal_func (array, name, extension_equal, &idx);
926 if (active) {
927 if (!found)
928 g_ptr_array_add (array, (gpointer)name);
929 } else {
930 if (found)
931 g_ptr_array_remove_index (array, idx);
932 }
933
934 g_ptr_array_add (array, NULL);
935
936 g_settings_set_strv (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_WEBEXTENSIONS_ACTIVE, (const gchar * const *)array->pdata);
937
938 /* Update window web_extension state */
939 for (list = windows; list && list->data; list = list->next) {
940 EphyWindow *window = EPHY_WINDOW (list->data);
941
942 if (active)
943 ephy_web_extension_manager_add_web_extension_to_window (self, web_extension, window);
944 else
945 ephy_web_extension_manager_remove_web_extension_from_window (self, web_extension, window);
946 }
947
948 if (active) {
949 if (ephy_web_extension_has_background_web_view (web_extension))
950 run_background_script (self, web_extension);
951 }
952 }
953
954 GtkWidget *
955 ephy_web_extension_manager_get_page_action (EphyWebExtensionManager *self,
956 EphyWebExtension *web_extension,
957 EphyWebView *web_view)
958 {
959 GHashTable *table;
960 GtkWidget *ret = NULL;
961
962 table = g_hash_table_lookup (self->page_action_map, web_extension);
963 if (table)
964 ret = g_hash_table_lookup (table, web_view);
965
966 return ret;
967 }
0 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
1 /*
2 * Copyright © 2019-2020 Jan-Michael Brummer <jan.brummer@tabos.org>
3 *
4 * This file is part of Epiphany.
5 *
6 * Epiphany is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Epiphany is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20
21 #pragma once
22
23 G_BEGIN_DECLS
24
25 #include <glib.h>
26
27 #include "ephy-web-extension.h"
28
29 #define EPHY_TYPE_WEB_EXTENSION_MANAGER (ephy_web_extension_manager_get_type ())
30
31 G_DECLARE_FINAL_TYPE (EphyWebExtensionManager, ephy_web_extension_manager, EPHY, WEB_EXTENSION_MANAGER, GObject)
32
33 EphyWebExtensionManager *ephy_web_extension_manager_new (void);
34
35 GList *ephy_web_extension_manager_get_web_extensions (EphyWebExtensionManager *self);
36
37 void ephy_web_extension_manager_install_actions (EphyWebExtensionManager *self,
38 EphyWindow *window);
39
40 void ephy_web_extension_manager_install (EphyWebExtensionManager *self,
41 GFile *file);
42
43 void ephy_web_extension_manager_uninstall (EphyWebExtensionManager *self,
44 EphyWebExtension *web_extension);
45
46 void ephy_web_extension_manager_update_location_entry (EphyWebExtensionManager *self,
47 EphyWindow *window);
48
49 void ephy_web_extension_manager_add_web_extension_to_window (EphyWebExtensionManager *self,
50 EphyWebExtension *web_extension,
51 EphyWindow *window);
52
53 void ephy_web_extension_manager_remove_web_extension_from_window (EphyWebExtensionManager *self,
54 EphyWebExtension *web_extension,
55 EphyWindow *window);
56
57 gboolean ephy_web_extension_manager_is_active (EphyWebExtensionManager *self,
58 EphyWebExtension *web_extension);
59
60 void ephy_web_extension_manager_set_active (EphyWebExtensionManager *self,
61 EphyWebExtension *web_extension,
62 gboolean active);
63
64 GtkWidget *ephy_web_extension_manager_get_page_action (EphyWebExtensionManager *self,
65 EphyWebExtension *web_extension,
66 EphyWebView *web_view);
67
68 EphyWebView *ephy_web_extension_manager_get_background_web_view (EphyWebExtensionManager *self,
69 EphyWebExtension *web_extension);
70
71 G_END_DECLS
0 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
1 /*
2 * Copyright © 2019-2020 Jan-Michael Brummer <jan.brummer@tabos.org>
3 *
4 * This file is part of Epiphany.
5 *
6 * Epiphany is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Epiphany is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /**
21 * - Load a web_extension as described at https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/
22 * - Prepare the internal structure so that they can be easily applied to its destination (webview/browser) with the help of extension manager.
23 */
24
25 #include "config.h"
26
27 #include "ephy-embed-shell.h"
28 #include "ephy-file-helpers.h"
29 #include "ephy-shell.h"
30 #include "ephy-string.h"
31 #include "ephy-web-extension.h"
32 #include "ephy-window.h"
33
34 #include <archive.h>
35 #include <archive_entry.h>
36 #include <glib/gstdio.h>
37 #include <json-glib/json-glib.h>
38
39 typedef struct {
40 gint64 size;
41 char *file;
42 GdkPixbuf *pixbuf;
43 } WebExtensionIcon;
44
45 typedef struct {
46 GPtrArray *allow_list;
47 GPtrArray *block_list;
48 GPtrArray *js;
49
50 WebKitUserContentInjectedFrames injected_frames;
51 WebKitUserScriptInjectionTime injection_time;
52 GList *user_scripts;
53 } WebExtensionContentScript;
54
55 typedef struct {
56 GList *default_icons;
57 GtkWidget *widget;
58 } WebExtensionPageAction;
59
60 typedef struct {
61 char *title;
62 GList *default_icons;
63 char *popup;
64 } WebExtensionBrowserAction;
65
66 typedef struct {
67 GPtrArray *scripts;
68 char *page;
69 } WebExtensionBackground;
70
71 typedef struct {
72 char *page;
73 } WebExtensionOptionsUI;
74
75 typedef struct {
76 char *name;
77 GBytes *bytes;
78 } WebExtensionResource;
79
80 typedef struct {
81 char *code;
82 WebKitUserStyleSheet *style;
83 } WebExtensionCustomCSS;
84
85 struct _EphyWebExtension {
86 GObject parent_instance;
87
88 gboolean xpi;
89 char *base_location;
90 char *manifest;
91
92 char *description;
93 gint64 manifest_version;
94 char *guid;
95 char *author;
96 char *name;
97 char *version;
98 char *homepage_url;
99 GList *icons;
100 GList *content_scripts;
101 WebExtensionBackground *background;
102 GHashTable *page_action_map;
103 WebExtensionPageAction *page_action;
104 WebExtensionBrowserAction *browser_action;
105 WebExtensionOptionsUI *options_ui;
106 GList *resources;
107 GList *custom_css;
108 GPtrArray *permissions;
109 GCancellable *cancellable;
110 };
111
112 G_DEFINE_TYPE (EphyWebExtension, ephy_web_extension, G_TYPE_OBJECT)
113
114 gboolean
115 ephy_web_extension_has_resource (EphyWebExtension *self,
116 const char *name)
117 {
118 for (GList *list = self->resources; list && list->data; list = list->next) {
119 WebExtensionResource *resource = list->data;
120
121 if (g_strcmp0 (resource->name, name) == 0)
122 return TRUE;
123 }
124
125 return FALSE;
126 }
127
128 gconstpointer
129 ephy_web_extension_get_resource (EphyWebExtension *self,
130 const char *name,
131 gsize *length)
132 {
133 if (length)
134 *length = 0;
135
136 for (GList *list = self->resources; list && list->data; list = list->next) {
137 WebExtensionResource *resource = list->data;
138
139 if (g_strcmp0 (resource->name, name) == 0)
140 return g_bytes_get_data (resource->bytes, length);
141 }
142
143 g_debug ("Could not find web_extension resource: %s\n", name);
144 return NULL;
145 }
146
147 char *
148 ephy_web_extension_get_resource_as_string (EphyWebExtension *self,
149 const char *name)
150 {
151 gsize len;
152 gconstpointer data = ephy_web_extension_get_resource (self, name, &len);
153 g_autofree char *out = NULL;
154
155 if (data && len) {
156 out = g_malloc0 (len + 1);
157 memcpy (out, data, len);
158 }
159
160 return g_steal_pointer (&out);
161 }
162
163 static WebExtensionIcon *
164 web_extension_icon_new (EphyWebExtension *self,
165 const char *file,
166 gint64 size)
167 {
168 WebExtensionIcon *icon = NULL;
169 g_autoptr (GInputStream) stream = NULL;
170 g_autoptr (GError) error = NULL;
171 g_autoptr (GdkPixbuf) pixbuf = NULL;
172 const unsigned char *data = NULL;
173 gsize length;
174
175 data = ephy_web_extension_get_resource (self, file, &length);
176 if (!data) {
177 if (!self->xpi) {
178 g_autofree char *path = NULL;
179 path = g_build_filename (self->base_location, file, NULL);
180 pixbuf = gdk_pixbuf_new_from_file (path, NULL);
181 }
182 } else {
183 stream = g_memory_input_stream_new_from_data (data, length, NULL);
184 pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, &error);
185 }
186
187 if (!pixbuf) {
188 g_warning ("Could not read web_extension icon: %s", error ? error->message : "");
189 return NULL;
190 }
191
192 icon = g_malloc0 (sizeof (WebExtensionIcon));
193 icon->file = g_strdup (file);
194 icon->size = size;
195 icon->pixbuf = g_steal_pointer (&pixbuf);
196
197 return icon;
198 }
199
200 static void
201 web_extension_icon_free (WebExtensionIcon *icon)
202 {
203 g_clear_pointer (&icon->file, g_free);
204 g_clear_object (&icon->pixbuf);
205 g_free (icon);
206 }
207
208 static WebExtensionContentScript *
209 web_extension_content_script_new (WebKitUserContentInjectedFrames injected_frames,
210 WebKitUserScriptInjectionTime injection_time)
211 {
212 WebExtensionContentScript *content_script = g_malloc0 (sizeof (WebExtensionContentScript));
213
214 content_script->injected_frames = injected_frames;
215 content_script->injection_time = injection_time;
216 content_script->allow_list = g_ptr_array_new_full (1, g_free);
217 content_script->block_list = g_ptr_array_new_full (1, g_free);
218 content_script->js = g_ptr_array_new_full (1, g_free);
219
220 return content_script;
221 }
222
223 static void
224 web_extension_content_script_free (WebExtensionContentScript *content_script)
225 {
226 g_clear_pointer (&content_script->allow_list, g_ptr_array_unref);
227 g_clear_pointer (&content_script->block_list, g_ptr_array_unref);
228 g_clear_pointer (&content_script->js, g_ptr_array_unref);
229 g_clear_list (&content_script->user_scripts, (GDestroyNotify)webkit_user_script_unref);
230 g_free (content_script);
231 }
232
233 static WebExtensionOptionsUI *
234 web_extension_options_ui_new (const char *page)
235 {
236 WebExtensionOptionsUI *options_ui = g_malloc0 (sizeof (WebExtensionOptionsUI));
237
238 options_ui->page = g_strdup (page);
239
240 return options_ui;
241 }
242
243 static void
244 web_extension_options_ui_free (WebExtensionOptionsUI *options_ui)
245 {
246 g_clear_pointer (&options_ui->page, g_free);
247 g_free (options_ui);
248 }
249
250 static WebExtensionBackground *
251 web_extension_background_new (void)
252 {
253 WebExtensionBackground *background = g_malloc0 (sizeof (WebExtensionBackground));
254
255 background->scripts = g_ptr_array_new_full (1, g_free);
256
257 return background;
258 }
259
260 static void
261 web_extension_background_free (WebExtensionBackground *background)
262 {
263 g_clear_pointer (&background->scripts, g_ptr_array_unref);
264 g_clear_pointer (&background->page, g_free);
265 g_free (background);
266 }
267
268 static void
269 web_extension_add_icon (JsonObject *object,
270 const char *member_name,
271 JsonNode *member_node,
272 gpointer user_data)
273 {
274 EphyWebExtension *self = EPHY_WEB_EXTENSION (user_data);
275 WebExtensionIcon *icon;
276 const char *file = json_node_get_string (member_node);
277 gint64 size;
278
279 size = g_ascii_strtoll (member_name, NULL, 0);
280 if (size == 0) {
281 LOG ("Skipping %s as web extension icon as size is 0", file);
282 return;
283 }
284
285 icon = web_extension_icon_new (self, file, size);
286
287 if (icon)
288 self->icons = g_list_append (self->icons, icon);
289 }
290
291 static void
292 web_extension_add_browser_icons (JsonObject *object,
293 const char *member_name,
294 JsonNode *member_node,
295 gpointer user_data)
296 {
297 EphyWebExtension *self = EPHY_WEB_EXTENSION (user_data);
298 WebExtensionIcon *icon;
299 const char *file = json_node_get_string (member_node);
300 gint64 size;
301
302 size = g_ascii_strtoll (member_name, NULL, 0);
303 if (size == 0) {
304 LOG ("Skipping %s as web extension browser icon as size is 0", file);
305 return;
306 }
307 icon = web_extension_icon_new (self, file, size);
308
309 if (icon)
310 self->browser_action->default_icons = g_list_append (self->browser_action->default_icons, icon);
311 }
312
313 GdkPixbuf *
314 ephy_web_extension_get_icon (EphyWebExtension *self,
315 gint64 size)
316 {
317 WebExtensionIcon *icon_fallback = NULL;
318
319 for (GList *list = self->icons; list && list->data; list = list->next) {
320 WebExtensionIcon *icon = list->data;
321
322 if (icon->size == size)
323 return gdk_pixbuf_scale_simple (icon->pixbuf, size, size, GDK_INTERP_BILINEAR);
324
325 if (!icon_fallback || icon->size > icon_fallback->size)
326 icon_fallback = icon;
327 }
328
329 /* Fallback */
330 if (icon_fallback && icon_fallback->pixbuf)
331 return gdk_pixbuf_scale_simple (icon_fallback->pixbuf, size, size, GDK_INTERP_BILINEAR);
332
333 return NULL;
334 }
335
336 const char *
337 ephy_web_extension_get_name (EphyWebExtension *self)
338 {
339 return self->name;
340 }
341
342 const char *
343 ephy_web_extension_get_version (EphyWebExtension *self)
344 {
345 return self->version;
346 }
347
348 const char *
349 ephy_web_extension_get_description (EphyWebExtension *self)
350 {
351 return self->description;
352 }
353
354 const char *
355 ephy_web_extension_get_homepage_url (EphyWebExtension *self)
356 {
357 return self->homepage_url;
358 }
359
360 const char *
361 ephy_web_extension_get_author (EphyWebExtension *self)
362 {
363 return self->author;
364 }
365
366 const char *
367 ephy_web_extension_get_manifest (EphyWebExtension *self)
368 {
369 return self->manifest;
370 }
371
372 const char *
373 ephy_web_extension_get_base_location (EphyWebExtension *self)
374 {
375 return self->base_location;
376 }
377
378 static void
379 web_extension_add_allow_list (JsonArray *array,
380 guint index,
381 JsonNode *element_node,
382 gpointer user_data)
383 {
384 WebExtensionContentScript *content_script = user_data;
385
386 g_ptr_array_add (content_script->allow_list, g_strdup (json_node_get_string (element_node)));
387 }
388
389 static void
390 web_extension_add_block_list (JsonArray *array,
391 guint index,
392 JsonNode *element_node,
393 gpointer user_data)
394 {
395 WebExtensionContentScript *content_script = user_data;
396
397 g_ptr_array_add (content_script->block_list, g_strdup (json_node_get_string (element_node)));
398 }
399
400 static void
401 web_extension_add_js (JsonArray *array,
402 guint index_,
403 JsonNode *element_node,
404 gpointer user_data)
405 {
406 WebExtensionContentScript *content_script = user_data;
407
408 g_ptr_array_add (content_script->js, g_strdup (json_node_get_string (element_node)));
409 }
410
411 static void
412 web_extension_content_script_build (EphyWebExtension *self,
413 WebExtensionContentScript *content_script)
414 {
415 if (!content_script->js)
416 return;
417
418 for (guint i = 0; i < content_script->js->len; i++) {
419 WebKitUserScript *user_script;
420 char *js_data;
421
422 js_data = ephy_web_extension_get_resource_as_string (self, g_ptr_array_index (content_script->js, i));
423 if (!js_data)
424 continue;
425
426 user_script = webkit_user_script_new_for_world (js_data,
427 content_script->injected_frames,
428 content_script->injection_time,
429 ephy_embed_shell_get_guid (ephy_embed_shell_get_default ()),
430 (const char * const *)content_script->allow_list->pdata,
431 (const char * const *)content_script->block_list->pdata);
432
433 content_script->user_scripts = g_list_append (content_script->user_scripts, user_script);
434 g_free (js_data);
435 }
436 }
437
438 static void
439 web_extension_add_content_script (JsonArray *array,
440 guint index_,
441 JsonNode *element_node,
442 gpointer user_data)
443 {
444 EphyWebExtension *self = EPHY_WEB_EXTENSION (user_data);
445 WebKitUserContentInjectedFrames injected_frames = WEBKIT_USER_CONTENT_INJECT_TOP_FRAME;
446 WebKitUserScriptInjectionTime injection_time = WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END;
447 WebExtensionContentScript *content_script;
448 JsonObject *object = json_node_get_object (element_node);
449 JsonArray *child_array;
450 const char *run_at;
451 gboolean all_frames;
452
453 /* TODO: The default value is "document_idle", which in WebKit term is document_end */
454 run_at = json_object_get_string_member_with_default (object, "run_at", "document_idle");
455 if (strcmp (run_at, "document_start") == 0) {
456 injection_time = WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START;
457 } else if (strcmp (run_at, "document_end") == 0) {
458 injection_time = WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END;
459 } else if (strcmp (run_at, "document_idle") == 0) {
460 g_warning ("run_at: document_idle not supported by WebKit, falling back to document_end");
461 injection_time = WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END;
462 } else {
463 g_warning ("Unhandled run_at '%s' in web_extension, ignoring.", run_at);
464 return;
465 }
466
467 /* all_frames */
468 all_frames = json_object_get_boolean_member_with_default (object, "all_frames", FALSE);
469 injected_frames = all_frames ? WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES : WEBKIT_USER_CONTENT_INJECT_TOP_FRAME;
470
471 content_script = web_extension_content_script_new (injected_frames, injection_time);
472 if (json_object_has_member (object, "matches")) {
473 child_array = json_object_get_array_member (object, "matches");
474 json_array_foreach_element (child_array, web_extension_add_allow_list, content_script);
475 }
476 g_ptr_array_add (content_script->allow_list, NULL);
477
478 if (json_object_has_member (object, "exclude_matches")) {
479 child_array = json_object_get_array_member (object, "exclude_matches");
480 json_array_foreach_element (child_array, web_extension_add_block_list, content_script);
481 }
482 g_ptr_array_add (content_script->block_list, NULL);
483
484 if (json_object_has_member (object, "js")) {
485 child_array = json_object_get_array_member (object, "js");
486 if (child_array)
487 json_array_foreach_element (child_array, web_extension_add_js, content_script);
488 }
489 g_ptr_array_add (content_script->js, NULL);
490
491 /* Create user scripts so that we can unload them if necessary */
492 web_extension_content_script_build (self, content_script);
493
494 self->content_scripts = g_list_append (self->content_scripts, content_script);
495 }
496
497 static void
498 web_extension_add_scripts (JsonArray *array,
499 guint index_,
500 JsonNode *element_node,
501 gpointer user_data)
502 {
503 EphyWebExtension *self = EPHY_WEB_EXTENSION (user_data);
504
505 g_ptr_array_add (self->background->scripts, g_strdup (json_node_get_string (element_node)));
506 }
507
508 static void
509 web_extension_add_background (JsonObject *object,
510 const char *member_name,
511 JsonNode *member_node,
512 gpointer user_data)
513 {
514 /* https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/background
515 * Limitations:
516 * - persistent with false is not supported yet.
517 */
518 EphyWebExtension *self = EPHY_WEB_EXTENSION (user_data);
519 JsonArray *child_array;
520
521 if (!json_object_has_member (object, "scripts") && !json_object_has_member (object, "page") && !json_object_has_member (object, "persistent")) {
522 g_warning ("Invalid background section, it must be either scripts, page or persistent entry.");
523 return;
524 }
525
526 if (!self->background)
527 self->background = web_extension_background_new ();
528
529 if (json_object_has_member (object, "scripts")) {
530 child_array = json_object_get_array_member (object, "scripts");
531 json_array_foreach_element (child_array, web_extension_add_scripts, self);
532 } else if (!self->background->page && json_object_has_member (object, "page")) {
533 self->background->page = g_strdup (json_object_get_string_member (object, "page"));
534 } else if (json_object_has_member (object, "persistent")) {
535 LOG ("persistent background setting is not handled in Epiphany");
536 }
537 }
538
539 static void
540 web_extension_add_page_action (JsonObject *object,
541 gpointer user_data)
542 {
543 EphyWebExtension *self = EPHY_WEB_EXTENSION (user_data);
544 WebExtensionPageAction *page_action = g_malloc0 (sizeof (WebExtensionPageAction));
545
546 self->page_action = page_action;
547
548 if (json_object_has_member (object, "default_icon")) {
549 WebExtensionIcon *icon = g_malloc (sizeof (WebExtensionIcon));
550 const char *default_icon = json_object_get_string_member (object, "default_icon");
551 g_autofree char *path = NULL;
552
553 icon->size = -1;
554 icon->file = g_strdup (default_icon);
555
556 path = g_build_filename (self->base_location, icon->file, NULL);
557 icon->pixbuf = gdk_pixbuf_new_from_file (path, NULL);
558
559 self->page_action->default_icons = g_list_append (self->page_action->default_icons, icon);
560 }
561 }
562
563 static void
564 web_extension_page_action_free (WebExtensionPageAction *page_action)
565 {
566 g_clear_list (&page_action->default_icons, (GDestroyNotify)web_extension_icon_free);
567 g_free (page_action);
568 }
569
570 /* TODO: Load translation for current locale during init */
571 static char *
572 web_extension_get_translation (EphyWebExtension *self,
573 const char *locale,
574 const char *key)
575 {
576 g_autoptr (JsonParser) parser = NULL;
577 g_autoptr (GError) error = NULL;
578 g_autofree char *path = g_strdup_printf ("_locales/%s/messages.json", locale);
579 JsonNode *root = NULL;
580 JsonObject *root_object = NULL;
581 JsonObject *name = NULL;
582 const unsigned char *data = NULL;
583 gsize length;
584
585 if (!ephy_web_extension_has_resource (self, path))
586 return NULL;
587
588 data = ephy_web_extension_get_resource (self, path, &length);
589
590 parser = json_parser_new ();
591 if (!json_parser_load_from_data (parser, (char *)data, length, &error)) {
592 g_warning ("Could not load WebExtension translation: %s", error->message);
593 return NULL;
594 }
595
596 root = json_parser_get_root (parser);
597 if (!root) {
598 g_warning ("WebExtension translation root is NULL, return NULL.");
599 return NULL;
600 }
601
602 root_object = json_node_get_object (root);
603 if (!root_object) {
604 g_warning ("WebExtension translation root object is NULL, return NULL.");
605 return NULL;
606 }
607
608 name = json_object_get_object_member (root_object, key);
609 if (name)
610 return g_strdup (json_object_get_string_member (name, "message"));
611
612 return NULL;
613 }
614
615 char *
616 ephy_web_extension_manifest_get_key (EphyWebExtension *self,
617 JsonObject *object,
618 char *key)
619 {
620 char *value = NULL;
621
622 if (json_object_has_member (object, key)) {
623 g_autofree char *ret = g_strdup (json_object_get_string_member (object, key));
624
625 /* Translation are requested with a unique string, e.g.:
626 * __MSG_unique_name__ but stored as unique_name in messages.json.
627 * Let's check for this prefix and suffix and extract the unique name
628 */
629 if (g_str_has_prefix (ret, "__MSG_") && g_str_has_suffix (ret, "__")) {
630 /* FIXME: Set current locale */
631 g_autofree char *locale = g_strdup ("en");
632
633 /* Remove trailing __ */
634 ret[strlen (ret) - 2] = '\0';
635 value = web_extension_get_translation (self, locale, ret + strlen ("__MSG_"));
636 } else {
637 value = g_strdup (ret);
638 }
639 }
640
641 return value;
642 }
643
644 static void
645 web_extension_add_browser_action (JsonObject *object,
646 gpointer user_data)
647 {
648 EphyWebExtension *self = EPHY_WEB_EXTENSION (user_data);
649 WebExtensionBrowserAction *browser_action = g_malloc0 (sizeof (WebExtensionBrowserAction));
650
651 g_clear_object (&self->browser_action);
652 self->browser_action = browser_action;
653
654 if (json_object_has_member (object, "default_title")) {
655 self->browser_action->title = ephy_web_extension_manifest_get_key (self, object, "default_title");
656 }
657
658 if (json_object_has_member (object, "default_icon")) {
659 /* defaullt_icon can be Object or String */
660 JsonNode *icon_node = json_object_get_member (object, "default_icon");
661
662 if (json_node_get_node_type (icon_node) == JSON_NODE_OBJECT) {
663 JsonObject *icon_object = json_object_get_object_member (object, "default_icon");
664 json_object_foreach_member (icon_object, web_extension_add_browser_icons, self);
665 } else {
666 const char *default_icon = json_object_get_string_member (object, "default_icon");
667 WebExtensionIcon *icon = web_extension_icon_new (self, default_icon, -1);
668
669 self->browser_action->default_icons = g_list_append (self->browser_action->default_icons, icon);
670 }
671 }
672
673 if (json_object_has_member (object, "default_popup"))
674 self->browser_action->popup = g_strdup (json_object_get_string_member (object, "default_popup"));
675 }
676
677 static void
678 web_extension_browser_action_free (WebExtensionBrowserAction *browser_action)
679 {
680 g_clear_pointer (&browser_action->title, g_free);
681 g_clear_pointer (&browser_action->popup, g_free);
682 g_clear_list (&browser_action->default_icons, (GDestroyNotify)web_extension_icon_free);
683 g_free (browser_action);
684 }
685
686 static void
687 web_extension_add_options_ui (JsonObject *object,
688 gpointer user_data)
689 {
690 EphyWebExtension *self = EPHY_WEB_EXTENSION (user_data);
691 const char *page = json_object_get_string_member (object, "page");
692 WebExtensionOptionsUI *options_ui = web_extension_options_ui_new (page);
693
694 g_clear_pointer (&self->options_ui, web_extension_options_ui_free);
695 self->options_ui = options_ui;
696 }
697
698 static void
699 web_extension_add_permission (JsonArray *array,
700 guint index_,
701 JsonNode *element_node,
702 gpointer user_data)
703 {
704 EphyWebExtension *self = EPHY_WEB_EXTENSION (user_data);
705
706 g_ptr_array_add (self->permissions, g_strdup (json_node_get_string (element_node)));
707 }
708
709 static void
710 web_extension_resource_free (WebExtensionResource *resource)
711 {
712 g_clear_pointer (&resource->bytes, g_bytes_unref);
713 g_clear_pointer (&resource->name, g_free);
714 g_free (resource);
715 }
716
717 static void
718 ephy_web_extension_dispose (GObject *object)
719 {
720 EphyWebExtension *self = EPHY_WEB_EXTENSION (object);
721
722 g_clear_pointer (&self->base_location, g_free);
723 g_clear_pointer (&self->manifest, g_free);
724 g_clear_pointer (&self->guid, g_free);
725 g_clear_pointer (&self->description, g_free);
726 g_clear_pointer (&self->author, g_free);
727 g_clear_pointer (&self->name, g_free);
728 g_clear_pointer (&self->version, g_free);
729 g_clear_pointer (&self->homepage_url, g_free);
730
731 g_clear_list (&self->icons, (GDestroyNotify)web_extension_icon_free);
732 g_clear_list (&self->content_scripts, (GDestroyNotify)web_extension_content_script_free);
733 g_clear_list (&self->resources, (GDestroyNotify)web_extension_resource_free);
734 g_clear_pointer (&self->background, web_extension_background_free);
735 g_clear_pointer (&self->options_ui, web_extension_options_ui_free);
736 g_clear_pointer (&self->permissions, g_ptr_array_unref);
737
738 g_clear_pointer (&self->page_action, web_extension_page_action_free);
739 g_clear_pointer (&self->browser_action, web_extension_browser_action_free);
740 g_clear_list (&self->custom_css, (GDestroyNotify)webkit_user_style_sheet_unref);
741
742 g_hash_table_destroy (self->page_action_map);
743
744 G_OBJECT_CLASS (ephy_web_extension_parent_class)->dispose (object);
745 }
746
747 static void
748 ephy_web_extension_class_init (EphyWebExtensionClass *klass)
749 {
750 GObjectClass *object_class = G_OBJECT_CLASS (klass);
751
752 object_class->dispose = ephy_web_extension_dispose;
753 }
754
755 static void
756 ephy_web_extension_init (EphyWebExtension *self)
757 {
758 self->page_action_map = g_hash_table_new (NULL, NULL);
759 self->permissions = g_ptr_array_new_full (1, g_free);
760
761 self->guid = g_uuid_string_random ();
762 }
763
764 static EphyWebExtension *
765 ephy_web_extension_new (void)
766 {
767 return g_object_new (EPHY_TYPE_WEB_EXTENSION, NULL);
768 }
769
770 static void
771 web_extension_add_resource (EphyWebExtension *self,
772 const char *name,
773 gpointer data,
774 guint len)
775 {
776 WebExtensionResource *resource = g_malloc0 (sizeof (WebExtensionResource));
777
778 resource->name = g_strdup (name);
779 resource->bytes = g_bytes_new (data, len);
780
781 self->resources = g_list_append (self->resources, resource);
782 }
783
784 static gboolean
785 web_extension_read_directory (EphyWebExtension *self,
786 char *base,
787 char *path)
788 {
789 g_autoptr (GError) error = NULL;
790 g_autoptr (GDir) dir = NULL;
791 const char *dirent;
792 gboolean ret = TRUE;
793
794 dir = g_dir_open (path, 0, &error);
795 if (!dir) {
796 g_warning ("Could not open web_extension directory: %s", error->message);
797 return FALSE;
798 }
799
800 while ((dirent = g_dir_read_name (dir))) {
801 GFileType type;
802 g_autofree gchar *filename = g_build_filename (path, dirent, NULL);
803 g_autoptr (GFile) file = g_file_new_for_path (filename);
804
805 type = g_file_query_file_type (file, G_FILE_QUERY_INFO_NONE, NULL);
806 if (type == G_FILE_TYPE_DIRECTORY) {
807 web_extension_read_directory (self, base, filename);
808 } else {
809 g_autofree char *data = NULL;
810 gsize len;
811
812 if (g_file_get_contents (filename, &data, &len, NULL))
813 web_extension_add_resource (self, filename + strlen (base) + 1, data, len);
814 }
815 }
816
817 return ret;
818 }
819
820 static EphyWebExtension *
821 ephy_web_extension_load_directory (char *filename)
822 {
823 EphyWebExtension *self = ephy_web_extension_new ();
824
825 web_extension_read_directory (self, filename, filename);
826
827 return self;
828 }
829
830 static EphyWebExtension *
831 ephy_web_extension_load_xpi (GFile *target)
832 {
833 EphyWebExtension *self = NULL;
834 struct archive *pkg;
835 struct archive_entry *entry;
836 int res;
837
838 pkg = archive_read_new ();
839 archive_read_support_format_zip (pkg);
840
841 res = archive_read_open_filename (pkg, g_file_get_path (target), 10240);
842 if (res == ARCHIVE_OK) {
843 self = ephy_web_extension_new ();
844 self->xpi = TRUE;
845
846 while (archive_read_next_header (pkg, &entry) == ARCHIVE_OK) {
847 int64_t size = archive_entry_size (entry);
848 gsize total_len = 0;
849 g_autofree char *data = NULL;
850
851 data = g_malloc0 (size);
852 total_len = archive_read_data (pkg, data, size);
853
854 if (total_len > 0)
855 web_extension_add_resource (self, archive_entry_pathname (entry), data, total_len);
856 }
857
858 res = archive_read_free (pkg);
859 if (res != ARCHIVE_OK)
860 g_warning ("Error freeing archive: %s", archive_error_string (pkg));
861 } else {
862 g_warning ("Could not open archive %s", archive_error_string (pkg));
863 }
864
865 return self;
866 }
867
868 EphyWebExtension *
869 ephy_web_extension_load (GFile *target)
870 {
871 g_autoptr (GError) error = NULL;
872 g_autoptr (GFile) source = g_file_dup (target);
873 g_autoptr (GFile) parent = NULL;
874 g_autoptr (JsonObject) icons_object = NULL;
875 g_autoptr (JsonArray) content_scripts_array = NULL;
876 g_autoptr (JsonObject) background_object = NULL;
877 JsonParser *parser = NULL;
878 JsonNode *root = NULL;
879 JsonObject *root_object = NULL;
880 EphyWebExtension *self = NULL;
881 GFileType type;
882 gsize length = 0;
883 const unsigned char *manifest;
884
885 type = g_file_query_file_type (source, G_FILE_QUERY_INFO_NONE, NULL);
886 if (type == G_FILE_TYPE_DIRECTORY) {
887 g_autofree char *path = g_file_get_path (source);
888 self = ephy_web_extension_load_directory (path);
889 } else
890 self = ephy_web_extension_load_xpi (source);
891
892 if (!self)
893 return NULL;
894
895 manifest = ephy_web_extension_get_resource (self, "manifest.json", &length);
896 if (!manifest)
897 return NULL;
898
899 parser = json_parser_new ();
900 if (!json_parser_load_from_data (parser, (char *)manifest, length, &error)) {
901 g_warning ("Could not load web extension manifest: %s", error->message);
902 return NULL;
903 }
904
905 root = json_parser_get_root (parser);
906 if (!root) {
907 g_warning ("WebExtension manifest json root is NULL, return NULL.");
908 return NULL;
909 }
910
911 root_object = json_node_get_object (root);
912 if (!root_object) {
913 g_warning ("WebExtension manifest json root is NULL, return NULL.");
914 return NULL;
915 }
916
917 self->manifest = g_strndup ((char *)manifest, length);
918 self->base_location = parent ? g_file_get_path (parent) : g_file_get_path (target);
919 self->description = ephy_web_extension_manifest_get_key (self, root_object, "description");
920 self->manifest_version = json_object_get_int_member (root_object, "manifest_version");
921 self->name = ephy_web_extension_manifest_get_key (self, root_object, "name");
922 self->version = ephy_web_extension_manifest_get_key (self, root_object, "version");
923 self->homepage_url = ephy_web_extension_manifest_get_key (self, root_object, "homepage_url");
924 self->author = ephy_web_extension_manifest_get_key (self, root_object, "author");
925
926 if (json_object_has_member (root_object, "icons")) {
927 icons_object = json_object_get_object_member (root_object, "icons");
928
929 json_object_foreach_member (icons_object, web_extension_add_icon, self);
930 }
931
932 if (json_object_has_member (root_object, "content_scripts")) {
933 content_scripts_array = json_object_get_array_member (root_object, "content_scripts");
934
935 json_array_foreach_element (content_scripts_array, web_extension_add_content_script, self);
936 }
937
938 if (json_object_has_member (root_object, "background")) {
939 background_object = json_object_get_object_member (root_object, "background");
940
941 json_object_foreach_member (background_object, web_extension_add_background, self);
942 }
943 if (self->background)
944 g_ptr_array_add (self->background->scripts, NULL);
945
946 if (json_object_has_member (root_object, "page_action")) {
947 g_autoptr (JsonObject) page_action_object = json_object_get_object_member (root_object, "page_action");
948
949 web_extension_add_page_action (page_action_object, self);
950 }
951
952 if (json_object_has_member (root_object, "browser_action")) {
953 g_autoptr (JsonObject) browser_action_object = json_object_get_object_member (root_object, "browser_action");
954
955 web_extension_add_browser_action (browser_action_object, self);
956 }
957
958 if (json_object_has_member (root_object, "options_ui")) {
959 g_autoptr (JsonObject) browser_action_object = json_object_get_object_member (root_object, "options_ui");
960
961 web_extension_add_options_ui (browser_action_object, self);
962 }
963
964 if (json_object_has_member (root_object, "permissions")) {
965 g_autoptr (JsonArray) array = json_object_get_array_member (root_object, "permissions");
966
967 json_array_foreach_element (array, web_extension_add_permission, self);
968 }
969 if (self->permissions)
970 g_ptr_array_add (self->permissions, NULL);
971
972 return self;
973 }
974
975 EphyWebExtension *
976 ephy_web_extension_load_finished (GObject *unused,
977 GAsyncResult *result,
978 GError **error)
979 {
980 g_assert (g_task_is_valid (result, unused));
981
982 return g_task_propagate_pointer (G_TASK (result), error);
983 }
984
985 static void
986 load_web_extension_thread (GTask *task,
987 gpointer *unused,
988 GFile *target,
989 GCancellable *cancellable)
990 {
991 EphyWebExtension *self = ephy_web_extension_load (target);
992
993 g_task_return_pointer (task, self, NULL);
994 }
995
996 void
997 ephy_web_extension_load_async (GFile *target,
998 GCancellable *cancellable,
999 GAsyncReadyCallback callback,
1000 gpointer user_data)
1001 {
1002 GTask *task;
1003
1004 g_assert (target);
1005
1006 task = g_task_new (NULL, cancellable, callback, user_data);
1007 g_task_set_priority (task, G_PRIORITY_DEFAULT);
1008 g_task_set_task_data (task,
1009 g_file_dup (target),
1010 (GDestroyNotify)g_object_unref);
1011 g_task_run_in_thread (task, (GTaskThreadFunc)load_web_extension_thread);
1012 g_object_unref (task);
1013 }
1014
1015
1016 GdkPixbuf *
1017 ephy_web_extension_load_pixbuf (EphyWebExtension *self,
1018 char *file)
1019 {
1020 g_autofree gchar *path = NULL;
1021
1022 path = g_build_filename (self->base_location, file, NULL);
1023
1024 return gdk_pixbuf_new_from_file (path, NULL);
1025 }
1026
1027 void
1028 ephy_web_extension_remove (EphyWebExtension *self)
1029 {
1030 g_autoptr (GError) error = NULL;
1031
1032 if (!self->xpi) {
1033 if (!ephy_file_delete_dir_recursively (self->base_location, &error))
1034 g_warning ("Could not delete web_extension from %s: %s", self->base_location, error->message);
1035 } else {
1036 g_unlink (self->base_location);
1037 }
1038 }
1039
1040 gboolean
1041 ephy_web_extension_has_page_action (EphyWebExtension *self)
1042 {
1043 return !!self->page_action;
1044 }
1045
1046 gboolean
1047 ephy_web_extension_has_browser_action (EphyWebExtension *self)
1048 {
1049 return !!self->browser_action;
1050 }
1051
1052 gboolean
1053 ephy_web_extension_has_background_web_view (EphyWebExtension *self)
1054 {
1055 return !!self->background;
1056 }
1057
1058 const char *
1059 ephy_web_extension_background_web_view_get_page (EphyWebExtension *self)
1060 {
1061 return self->background->page;
1062 }
1063
1064 GPtrArray *
1065 ephy_web_extension_background_web_view_get_scripts (EphyWebExtension *self)
1066 {
1067 return self->background->scripts;
1068 }
1069
1070 GList *
1071 ephy_web_extension_get_content_scripts (EphyWebExtension *self)
1072 {
1073 return self->content_scripts;
1074 }
1075
1076 GList *
1077 ephy_web_extension_get_content_script_js (EphyWebExtension *self,
1078 gpointer content_script)
1079 {
1080 WebExtensionContentScript *script = content_script;
1081 return script->user_scripts;
1082 }
1083
1084 GdkPixbuf *
1085 ephy_web_extension_browser_action_get_icon (EphyWebExtension *self,
1086 int size)
1087 {
1088 WebExtensionIcon *icon_fallback = NULL;
1089
1090 if (!self->browser_action || !self->browser_action->default_icons)
1091 return NULL;
1092
1093 for (GList *list = self->browser_action->default_icons; list && list->data; list = list->next) {
1094 WebExtensionIcon *icon = list->data;
1095
1096 if (icon->size == size)
1097 return gdk_pixbuf_copy (icon->pixbuf);
1098
1099 if (!icon_fallback || icon->size > icon_fallback->size)
1100 icon_fallback = icon;
1101 }
1102
1103 /* Fallback */
1104 if (icon_fallback)
1105 return gdk_pixbuf_scale_simple (icon_fallback->pixbuf, size, size, GDK_INTERP_BILINEAR);
1106
1107 return NULL;
1108 }
1109
1110 const char *
1111 ephy_web_extension_get_browser_popup (EphyWebExtension *self)
1112 {
1113 return self->browser_action->popup;
1114 }
1115
1116 const char *
1117 ephy_web_extension_browser_action_get_tooltip (EphyWebExtension *self)
1118 {
1119 return self->browser_action->title;
1120 }
1121
1122 WebExtensionCustomCSS *web_extension_custom_css_new (EphyWebExtension *self,
1123 const char *code)
1124
1125 {
1126 WebExtensionCustomCSS *css = g_malloc0 (sizeof (WebExtensionCustomCSS));
1127
1128 css->code = g_strdup (code);
1129 css->style = webkit_user_style_sheet_new (css->code, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WEBKIT_USER_STYLE_LEVEL_USER, NULL, NULL);
1130
1131 self->custom_css = g_list_append (self->custom_css, css);
1132
1133 return css;
1134 }
1135
1136 WebKitUserStyleSheet *
1137 ephy_web_extension_get_custom_css (EphyWebExtension *self,
1138 const char *code)
1139 {
1140 WebExtensionCustomCSS *css = NULL;
1141
1142 for (GList *list = self->custom_css; list && list->data; list = list->data) {
1143 css = list->data;
1144
1145 if (strcmp (css->code, code) == 0)
1146 return css->style;
1147 }
1148
1149 return NULL;
1150 }
1151
1152 WebKitUserStyleSheet *
1153 ephy_web_extension_add_custom_css (EphyWebExtension *self,
1154 const char *code)
1155 {
1156 WebKitUserStyleSheet *style;
1157 WebExtensionCustomCSS *css = NULL;
1158
1159 style = ephy_web_extension_get_custom_css (self, code);
1160 if (style)
1161 return style;
1162
1163 css = web_extension_custom_css_new (self, code);
1164
1165 return css->style;
1166 }
1167
1168 GList *
1169 ephy_web_extension_get_custom_css_list (EphyWebExtension *self)
1170 {
1171 return self->custom_css;
1172 }
1173
1174 WebKitUserStyleSheet *
1175 ephy_web_extension_custom_css_style (EphyWebExtension *self,
1176 gpointer custom_css)
1177 {
1178 WebExtensionCustomCSS *css = custom_css;
1179
1180 return css->style;
1181 }
1182
1183 char *
1184 ephy_web_extension_get_option_ui_page (EphyWebExtension *self)
1185 {
1186 if (!self->options_ui)
1187 return NULL;
1188
1189 return ephy_web_extension_get_resource_as_string (self, self->options_ui->page);
1190 }
1191
1192 const char *
1193 ephy_web_extension_get_guid (EphyWebExtension *self)
1194 {
1195 return self->guid;
1196 }
1197
1198 GPtrArray *
1199 ephy_web_extension_get_permissions (EphyWebExtension *self)
1200 {
1201 return self->permissions;
1202 }
0 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
1 /*
2 * Copyright © 2019-2020 Jan-Michael Brummer <jan.brummer@tabos.org>
3 *
4 * This file is part of Epiphany.
5 *
6 * Epiphany is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Epiphany is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20
21 #pragma once
22
23 #include "ephy-debug.h"
24 #include "ephy-window.h"
25
26 #include <gdk-pixbuf/gdk-pixbuf.h>
27 #include <gio/gio.h>
28 #include <string.h>
29 #include <webkit2/webkit2.h>
30
31 G_BEGIN_DECLS
32
33 #define EPHY_TYPE_WEB_EXTENSION (ephy_web_extension_get_type ())
34
35 G_DECLARE_FINAL_TYPE (EphyWebExtension, ephy_web_extension, EPHY, WEB_EXTENSION, GObject)
36
37 typedef char *(*executeHandler)(EphyWebExtension *web_extension,
38 char *name,
39 JSCValue *args);
40
41 typedef struct {
42 char *name;
43 executeHandler execute;
44 } EphyWebExtensionApiHandler;
45
46 GdkPixbuf *ephy_web_extension_get_icon (EphyWebExtension *self,
47 gint64 size);
48
49 const char *ephy_web_extension_get_name (EphyWebExtension *self);
50
51 const char *ephy_web_extension_get_version (EphyWebExtension *self);
52
53 const char *ephy_web_extension_get_description (EphyWebExtension *self);
54
55 const char *ephy_web_extension_get_homepage_url (EphyWebExtension *self);
56
57 const char *ephy_web_extension_get_author (EphyWebExtension *self);
58
59 GList *ephy_web_extensions_get (void);
60
61 EphyWebExtension *ephy_web_extension_load (GFile *file);
62
63 void ephy_web_extension_load_async (GFile *target,
64 GCancellable *cancellable,
65 GAsyncReadyCallback callback,
66 gpointer user_data);
67
68 EphyWebExtension *ephy_web_extension_load_finished (GObject *unused,
69 GAsyncResult *result,
70 GError **error);
71
72 GdkPixbuf *ephy_web_extension_load_pixbuf (EphyWebExtension *self,
73 char *file);
74
75 gboolean ephy_web_extension_has_page_action (EphyWebExtension *self);
76
77 gboolean ephy_web_extension_has_browser_action (EphyWebExtension *self);
78
79 gboolean ephy_web_extension_has_background_web_view (EphyWebExtension *self);
80
81 void ephy_web_extension_remove (EphyWebExtension *self);
82
83 const char *ephy_web_extension_get_manifest (EphyWebExtension *self);
84
85 const char *ephy_web_extension_background_web_view_get_page (EphyWebExtension *self);
86
87 GdkPixbuf *ephy_web_extension_browser_action_get_icon (EphyWebExtension *self,
88 int size);
89
90 const char *ephy_web_extension_browser_action_get_tooltip (EphyWebExtension *self);
91
92 const char *ephy_web_extension_get_browser_popup (EphyWebExtension *self);
93
94 GPtrArray *ephy_web_extension_background_web_view_get_scripts (EphyWebExtension *self);
95
96 GList *ephy_web_extension_get_content_scripts (EphyWebExtension *self);
97
98 GList *ephy_web_extension_get_content_script_js (EphyWebExtension *self,
99 gpointer content_script);
100
101 const char *ephy_web_extension_get_base_location (EphyWebExtension *self);
102
103 gconstpointer ephy_web_extension_get_resource (EphyWebExtension *self,
104 const char *name,
105 gsize *length);
106
107 char *ephy_web_extension_get_resource_as_string (EphyWebExtension *self,
108 const char *name);
109
110 WebKitUserStyleSheet *ephy_web_extension_add_custom_css (EphyWebExtension *self,
111 const char *code);
112
113 WebKitUserStyleSheet *ephy_web_extension_get_custom_css (EphyWebExtension *self,
114 const char *code);
115
116 GList *ephy_web_extension_get_custom_css_list (EphyWebExtension *self);
117
118 WebKitUserStyleSheet *ephy_web_extension_custom_css_style (EphyWebExtension *self,
119 gpointer custom_css);
120
121 char *ephy_web_extension_get_option_ui_page (EphyWebExtension *self);
122
123 const char *ephy_web_extension_get_guid (EphyWebExtension *self);
124
125 GPtrArray *ephy_web_extension_get_permissions (EphyWebExtension *self);
126
127 G_END_DECLS
128
0 ephywebextension_src = [
1 'webextension/api/notifications.c',
2 'webextension/api/pageaction.c',
3 'webextension/api/runtime.c',
4 'webextension/api/tabs.c',
5 'webextension/ephy-web-extension-manager.c',
6 'webextension/ephy-web-extension.c',
7 ]
5454 #include "ephy-string.h"
5555 #include "ephy-view-source-handler.h"
5656 #include "ephy-web-app-utils.h"
57 #include "ephy-web-extension-dialog.h"
5758 #include "ephy-zoom.h"
5859
5960 #include <gio/gio.h>
30763077
30773078 g_simple_action_set_state (action, g_variant_new_boolean (mute));
30783079 }
3080
3081 void
3082 window_cmd_extensions (GSimpleAction *action,
3083 GVariant *parameter,
3084 gpointer user_data)
3085 {
3086 EphyWindow *window = EPHY_WINDOW (user_data);
3087 GtkWidget *dialog;
3088
3089 dialog = ephy_web_extension_dialog_new ();
3090 gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (window));
3091 gtk_widget_show_all (dialog);
3092 }
244244 void window_cmd_import_passwords (GSimpleAction *action,
245245 GVariant *parameter,
246246 gpointer user_data);
247 void window_cmd_extensions (GSimpleAction *action,
248 GVariant *parameter,
249 gpointer user_data);
247250
248251 G_END_DECLS