Add initial WebExtension support
Jan-Michael Brummer
3 years ago
10 | 10 | # Translators: Do NOT translate or transliterate this text (this is an icon file name)! |
11 | 11 | Icon=@icon@ |
12 | 12 | 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; | |
14 | 14 | Actions=new-window;Incognito; |
15 | 15 | # Translators: Do NOT translate or transliterate this text (these are enum types)! |
16 | 16 | X-Purism-FormFactor=Workstation;Mobile; |
244 | 244 | <summary>Enable immediately switch to new open tab</summary> |
245 | 245 | <description>Whether to automatically switch to a new open tab.</description> |
246 | 246 | </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> | |
247 | 257 | </schema> |
248 | 258 | <schema id="org.gnome.Epiphany.webapp"> |
249 | 259 | <key type="as" name="additional-urls"> |
66 | 66 | #define EPHY_PAGE_TEMPLATE_ERROR "/org/gnome/epiphany/page-templates/error.html" |
67 | 67 | #define EPHY_PAGE_TEMPLATE_ERROR_CSS "/org/gnome/epiphany/page-templates/error.css" |
68 | 68 | |
69 | static guint64 web_view_uid = 1; | |
70 | ||
69 | 71 | struct _EphyWebView { |
70 | 72 | WebKitWebView parent_instance; |
71 | 73 | |
124 | 126 | char *tls_error_failing_uri; |
125 | 127 | |
126 | 128 | EphyWebViewErrorPage error_page; |
129 | ||
130 | guint64 uid; | |
127 | 131 | }; |
128 | 132 | |
129 | 133 | enum { |
1425 | 1429 | |
1426 | 1430 | if (view->loading_error_page) |
1427 | 1431 | return; |
1432 | ||
1433 | if (g_str_has_prefix (uri, "webextension://")) { | |
1434 | /* Hidden WebExtension webview, ignoring */ | |
1435 | return; | |
1436 | } | |
1428 | 1437 | |
1429 | 1438 | toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view)); |
1430 | 1439 | if (EPHY_IS_EMBED_CONTAINER (toplevel)) |
2724 | 2733 | _ephy_web_view_update_icon (web_view); |
2725 | 2734 | } |
2726 | 2735 | |
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 | ||
2727 | 2749 | /** |
2728 | 2750 | * ephy_web_view_load_request: |
2729 | 2751 | * @view: the #EphyWebView in which to load the request |
3770 | 3792 | |
3771 | 3793 | shell = ephy_embed_shell_get_default (); |
3772 | 3794 | |
3795 | web_view->uid = web_view_uid++; | |
3796 | ||
3773 | 3797 | web_view->is_blank = TRUE; |
3774 | 3798 | web_view->ever_committed = FALSE; |
3775 | 3799 | web_view->document_type = EPHY_WEB_VIEW_DOCUMENT_HTML; |
4119 | 4143 | "settings", ephy_embed_prefs_get_settings (), |
4120 | 4144 | NULL); |
4121 | 4145 | } |
4146 | ||
4147 | guint64 | |
4148 | ephy_web_view_get_uid (EphyWebView *web_view) | |
4149 | { | |
4150 | return web_view->uid; | |
4151 | } |
181 | 181 | gpointer response_data, |
182 | 182 | GDestroyNotify response_destroy); |
183 | 183 | |
184 | GtkWidget *ephy_web_view_new_with_user_content_manager (WebKitUserContentManager *ucm); | |
185 | ||
186 | guint64 ephy_web_view_get_uid (EphyWebView *web_view); | |
187 | ||
184 | 188 | G_END_DECLS |
62 | 62 | static void __attribute__((destructor)) |
63 | 63 | ephy_web_process_extension_shutdown (void) |
64 | 64 | { |
65 | if (extension) | |
65 | if (extension) { | |
66 | ephy_web_process_extension_deinitialize (extension); | |
66 | 67 | g_object_unref (extension); |
68 | } | |
67 | 69 | |
68 | 70 | ephy_settings_shutdown (); |
69 | 71 | ephy_file_helpers_shutdown (); |
19 | 19 | |
20 | 20 | #include "config.h" |
21 | 21 | #include "ephy-web-process-extension.h" |
22 | #include "ephy-webextension-api.h" | |
22 | 23 | |
23 | 24 | #include "ephy-debug.h" |
24 | 25 | #include "ephy-file-helpers.h" |
55 | 56 | gboolean is_private_profile; |
56 | 57 | |
57 | 58 | GHashTable *frames_map; |
59 | GHashTable *translation_table; | |
58 | 60 | }; |
59 | 61 | |
60 | 62 | 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 | } | |
61 | 69 | |
62 | 70 | static void |
63 | 71 | web_page_will_submit_form (WebKitWebPage *web_page, |
325 | 333 | return; |
326 | 334 | |
327 | 335 | 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); | |
328 | 348 | } |
329 | 349 | } |
330 | 350 | |
643 | 663 | |
644 | 664 | js_context = webkit_frame_get_js_context_for_script_world (frame, world); |
645 | 665 | jsc_context_push_exception_handler (js_context, (JSCExceptionHandler)js_exception_handler, NULL, NULL); |
666 | ||
667 | set_up_webextensions (extension, page, js_context); | |
646 | 668 | |
647 | 669 | bytes = g_resources_lookup_data ("/org/gnome/epiphany-web-process-extension/js/ephy.js", G_RESOURCE_LOOKUP_FLAGS_NONE, NULL); |
648 | 670 | data = g_bytes_get_data (bytes, &data_size); |
770 | 792 | |
771 | 793 | extension->initialized = TRUE; |
772 | 794 | |
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 | ||
774 | 801 | g_signal_connect (extension->script_world, |
775 | 802 | "window-object-cleared", |
776 | 803 | G_CALLBACK (window_object_cleared_cb), |
792 | 819 | |
793 | 820 | extension->frames_map = g_hash_table_new_full (g_int64_hash, g_int64_equal, |
794 | 821 | 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 | } |
35 | 35 | gboolean should_remember_passwords, |
36 | 36 | gboolean is_private_profile); |
37 | 37 | |
38 | void ephy_web_process_extension_deinitialize (EphyWebProcessExtension *extension); | |
39 | ||
40 | GHashTable *ephy_web_process_extension_get_translations (EphyWebProcessExtension *extension); | |
41 | ||
38 | 42 | 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 |
8 | 8 | 'ephy-web-process-extension.c', |
9 | 9 | 'ephy-web-process-extension-main.c', |
10 | 10 | 'ephy-web-overview-model.c', |
11 | 'ephy-webextension-api.c', | |
11 | 12 | resources |
12 | 13 | ] |
13 | 14 |
2 | 2 | <gresource prefix="/org/gnome/epiphany-web-process-extension"> |
3 | 3 | <file compressed="true">js/ephy.js</file> |
4 | 4 | <file compressed="true">js/overview.js</file> |
5 | <file compressed="true">js/webextensions.js</file> | |
5 | 6 | </gresource> |
6 | 7 | </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 |
853 | 853 | |
854 | 854 | g_free (command); |
855 | 855 | } |
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 | } |
86 | 86 | gboolean ephy_file_browse_to (GFile *file, |
87 | 87 | guint32 user_time); |
88 | 88 | |
89 | void ephy_copy_directory (const char *source, | |
90 | const char *target); | |
91 | ||
89 | 92 | G_END_DECLS |
116 | 116 | #define EPHY_PREFS_WEB_HARDWARE_ACCELERATION_POLICY "hardware-acceleration-policy" |
117 | 117 | #define EPHY_PREFS_WEB_ASK_ON_DOWNLOAD "ask-on-download" |
118 | 118 | #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" | |
119 | 121 | |
120 | 122 | static const char * const ephy_prefs_web_schema[] = { |
121 | 123 | EPHY_PREFS_WEB_FONT_MIN_SIZE, |
145 | 147 | EPHY_PREFS_WEB_HARDWARE_ACCELERATION_POLICY, |
146 | 148 | EPHY_PREFS_WEB_ASK_ON_DOWNLOAD, |
147 | 149 | EPHY_PREFS_WEB_SWITCH_TO_NEW_TAB, |
150 | EPHY_PREFS_WEB_ENABLE_WEBEXTENSIONS, | |
148 | 151 | }; |
149 | 152 | |
150 | 153 | #define EPHY_PREFS_SCHEMA "org.gnome.Epiphany" |
315 | 315 | } |
316 | 316 | |
317 | 317 | 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 ** | |
347 | 318 | ephy_strv_remove (const char * const *strv, |
348 | 319 | const char *str) |
349 | 320 | { |
48 | 48 | char *ephy_string_remove_trailing (char *string, |
49 | 49 | char ch); |
50 | 50 | |
51 | char **ephy_strv_append (const char * const *strv, | |
52 | const char *str); | |
53 | 51 | char **ephy_strv_remove (const char * const *strv, |
54 | 52 | const char *str); |
55 | 53 |
56 | 56 | GtkOverlay parent_instance; |
57 | 57 | |
58 | 58 | GtkWidget *url_entry; |
59 | GtkWidget *button_box; | |
60 | GtkWidget *page_action_box; | |
59 | 61 | GtkWidget *bookmark; |
60 | 62 | GtkWidget *bookmark_event_box; |
61 | 63 | GtkWidget *reader_mode; |
989 | 991 | ephy_location_entry_construct_contents (EphyLocationEntry *entry) |
990 | 992 | { |
991 | 993 | GtkWidget *event; |
992 | GtkWidget *box; | |
993 | 994 | GtkStyleContext *context; |
994 | 995 | DzlShortcutController *controller; |
995 | 996 | |
1029 | 1030 | gtk_overlay_add_overlay (GTK_OVERLAY (entry), event); |
1030 | 1031 | |
1031 | 1032 | /* 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); | |
1040 | 1053 | gtk_style_context_add_class (context, "entry_icon_box"); |
1041 | 1054 | |
1042 | 1055 | /* Bookmark */ |
1047 | 1060 | gtk_widget_show (entry->bookmark); |
1048 | 1061 | g_signal_connect (G_OBJECT (entry->bookmark_event_box), "button_press_event", G_CALLBACK (bookmark_icon_button_press_event_cb), entry); |
1049 | 1062 | 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); | |
1051 | 1064 | |
1052 | 1065 | context = gtk_widget_get_style_context (entry->bookmark); |
1053 | 1066 | gtk_style_context_add_class (context, "entry_icon"); |
1065 | 1078 | gtk_widget_set_valign (entry->reader_mode, GTK_ALIGN_CENTER); |
1066 | 1079 | gtk_widget_show (entry->reader_mode); |
1067 | 1080 | 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); | |
1069 | 1082 | |
1070 | 1083 | context = gtk_widget_get_style_context (entry->reader_mode); |
1071 | 1084 | gtk_style_context_add_class (context, "entry_icon"); |
1504 | 1517 | else |
1505 | 1518 | dzl_suggestion_entry_set_position_func (DZL_SUGGESTION_ENTRY (entry->url_entry), position_func, NULL, NULL); |
1506 | 1519 | } |
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 | } |
78 | 78 | void ephy_location_entry_set_progress (EphyLocationEntry *entry, |
79 | 79 | gdouble progress, |
80 | 80 | 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); | |
81 | 85 | |
82 | 86 | void ephy_location_entry_set_mobile_popdown (EphyLocationEntry *entry, |
83 | 87 | gboolean mobile_popdown); |
71 | 71 | conf.set_quoted('GSB_API_KEY', gsb_api_key) |
72 | 72 | conf.set10('ENABLE_GSB', gsb_api_key != '') |
73 | 73 | |
74 | glib_requirement = '>= 2.61.2' | |
74 | glib_requirement = '>= 2.64.0' | |
75 | 75 | gtk_requirement = '>= 3.24.0' |
76 | 76 | nettle_requirement = '>= 3.4' |
77 | webkitgtk_requirement = '>= 2.29.3' | |
77 | webkitgtk_requirement = '>= 2.31.1' | |
78 | 78 | |
79 | 79 | cairo_dep = dependency('cairo', version: '>= 1.2') |
80 | 80 | gcr_dep = dependency('gcr-3', version: '>= 3.5.5') |
89 | 89 | hogweed_dep = dependency('hogweed', version: nettle_requirement) |
90 | 90 | iso_codes_dep = dependency('iso-codes', version: '>= 0.35') |
91 | 91 | json_glib_dep = dependency('json-glib-1.0', version: '>= 1.2.4') |
92 | libarchive_dep = dependency('libarchive') | |
92 | 93 | libdazzle_dep = dependency('libdazzle-1.0', version: '>= 3.37.1') |
93 | 94 | libhandy_dep = dependency('libhandy-1', version: '>= 1.0.0') |
94 | 95 | libsecret_dep = dependency('libsecret-1', version: '>= 0.19.0') |
38 | 38 | GtkWidget *downloads_popover; |
39 | 39 | GtkWidget *downloads_icon; |
40 | 40 | GtkWidget *downloads_progress; |
41 | GtkWidget *browser_action_box; | |
41 | 42 | |
42 | 43 | guint downloads_button_attention_timeout_id; |
43 | 44 | }; |
241 | 242 | gtk_widget_class_bind_template_child (widget_class, |
242 | 243 | EphyActionBarEnd, |
243 | 244 | downloads_progress); |
245 | gtk_widget_class_bind_template_child (widget_class, | |
246 | EphyActionBarEnd, | |
247 | browser_action_box); | |
244 | 248 | } |
245 | 249 | |
246 | 250 | static void |
318 | 322 | { |
319 | 323 | return action_bar_end->downloads_revealer; |
320 | 324 | } |
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 | } |
33 | 33 | gboolean show); |
34 | 34 | GtkWidget *ephy_action_bar_end_get_downloads_revealer (EphyActionBarEnd *action_bar_end); |
35 | 35 | |
36 | void ephy_action_bar_end_add_browser_action (EphyActionBarEnd *action_bar_end, | |
37 | GtkWidget *action); | |
38 | ||
36 | 39 | G_END_DECLS |
304 | 304 | gtk_image_new_from_icon_name ("open-menu", |
305 | 305 | GTK_ICON_SIZE_LARGE_TOOLBAR)); |
306 | 306 | } |
307 | g_settings_bind (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_ENABLE_WEBEXTENSIONS, gtk_builder_get_object (builder, "extensions-button"), "visible", G_SETTINGS_BIND_DEFAULT); | |
307 | 308 | |
308 | 309 | gtk_menu_button_set_popover (GTK_MENU_BUTTON (button), page_menu_popover); |
309 | 310 | g_object_unref (builder); |
457 | 458 | |
458 | 459 | gtk_label_set_label (GTK_LABEL (header_bar->zoom_level_label), zoom_level); |
459 | 460 | } |
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 | } |
48 | 48 | void ephy_header_bar_set_zoom_level (EphyHeaderBar *header_bar, |
49 | 49 | gdouble zoom); |
50 | 50 | |
51 | void ephy_header_bar_add_browser_action (EphyHeaderBar *header_bar, | |
52 | GtkWidget *action); | |
53 | ||
51 | 54 | G_END_DECLS |
60 | 60 | EphyBookmarksManager *bookmarks_manager; |
61 | 61 | EphyHistoryManager *history_manager; |
62 | 62 | EphyOpenTabsManager *open_tabs_manager; |
63 | EphyWebExtensionManager *web_extension_manager; | |
63 | 64 | GNetworkMonitor *network_monitor; |
64 | 65 | GtkWidget *history_dialog; |
65 | 66 | GtkWidget *firefox_sync_dialog; |
1534 | 1535 | { |
1535 | 1536 | return shell->startup_finished; |
1536 | 1537 | } |
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 | } |
29 | 29 | #include "ephy-password-manager.h" |
30 | 30 | #include "ephy-session.h" |
31 | 31 | #include "ephy-sync-service.h" |
32 | #include "ephy-web-extension-manager.h" | |
32 | 33 | #include "ephy-window.h" |
33 | 34 | |
34 | 35 | #include <webkit2/webkit2.h> |
129 | 130 | |
130 | 131 | gboolean ephy_shell_startup_finished (EphyShell *shell); |
131 | 132 | |
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 | ||
132 | 140 | 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 |
116 | 116 | { "win.location-search", {"<Primary>K", NULL} }, |
117 | 117 | { "win.home", { "<alt>Home", NULL } }, |
118 | 118 | { "win.content", { "Escape", NULL } }, |
119 | { "win.extensions", { NULL } }, | |
119 | 120 | |
120 | 121 | /* Toggle actions */ |
121 | 122 | { "win.browse-with-caret", { "F7", NULL } }, |
858 | 859 | { "page-source", window_cmd_page_source }, |
859 | 860 | { "toggle-inspector", window_cmd_toggle_inspector }, |
860 | 861 | { "toggle-reader-mode", window_cmd_toggle_reader_mode }, |
862 | { "extensions", window_cmd_extensions }, | |
861 | 863 | |
862 | 864 | { "select-all", window_cmd_select_all }, |
863 | 865 | |
1267 | 1269 | |
1268 | 1270 | gtk_window_set_title (GTK_WINDOW (window), |
1269 | 1271 | 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); | |
1270 | 1283 | } |
1271 | 1284 | |
1272 | 1285 | static gboolean |
2418 | 2431 | sync_tab_popup_windows (view, NULL, window); |
2419 | 2432 | |
2420 | 2433 | sync_tab_zoom (web_view, NULL, window); |
2434 | sync_tab_page_action (view, NULL, window); | |
2421 | 2435 | |
2422 | 2436 | title_widget = ephy_header_bar_get_title_widget (EPHY_HEADER_BAR (window->header_bar)); |
2423 | 2437 | |
3945 | 3959 | window->mouse_gesture_controller = ephy_mouse_gesture_controller_new (window); |
3946 | 3960 | |
3947 | 3961 | 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); | |
3948 | 3964 | } |
3949 | 3965 | |
3950 | 3966 | static void |
8 | 8 | enums = gnome.mkenums_simple('ephy-type-builtins', |
9 | 9 | sources: types_headers |
10 | 10 | ) |
11 | ||
12 | subdir('webextension') | |
11 | 13 | |
12 | 14 | libephymain_sources = [ |
13 | 15 | 'bookmarks/ephy-add-bookmark-popover.c', |
42 | 44 | 'ephy-suggestion-model.c', |
43 | 45 | 'ephy-tab-header-bar.c', |
44 | 46 | 'ephy-tab-label.c', |
47 | 'ephy-web-extension-dialog.c', | |
45 | 48 | 'ephy-window.c', |
46 | 49 | 'popup-commands.c', |
47 | 50 | 'preferences/clear-data-view.c', |
57 | 60 | 'preferences/webapp-additional-urls-dialog.c', |
58 | 61 | 'synced-tabs-dialog.c', |
59 | 62 | 'window-commands.c', |
63 | ephywebextension_src, | |
60 | 64 | compile_schemas, |
61 | 65 | enums |
62 | 66 | ] |
69 | 73 | ephywidgets_dep, |
70 | 74 | gdk_dep, |
71 | 75 | gvdb_dep, |
76 | libarchive_dep, | |
72 | 77 | libhandy_dep |
73 | 78 | ] |
74 | 79 | |
75 | 80 | libephymain_includes = include_directories( |
76 | 81 | '.', |
82 | '..', | |
77 | 83 | 'bookmarks', |
78 | 84 | 'preferences', |
85 | 'webextension', | |
86 | 'webextension/api', | |
79 | 87 | ) |
80 | 88 | |
81 | 89 | libephymain = shared_library('ephymain', |
42 | 42 | <file preprocess="xml-stripblanks" compressed="true">gtk/shortcuts-dialog.ui</file> |
43 | 43 | <file preprocess="xml-stripblanks" compressed="true">gtk/tab-label.ui</file> |
44 | 44 | <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> | |
45 | 46 | </gresource> |
46 | 47 | <gresource prefix="/org/gnome/Epiphany/icons"> |
47 | 48 | <file compressed="true" alias="scalable/actions/ephy-download-symbolic.svg" preprocess="xml-stripblanks">ephy-download-symbolic.svg</file> |
1 | 1 | <interface> |
2 | 2 | <template class="EphyActionBarEnd" parent="GtkBox"> |
3 | 3 | <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> | |
4 | 16 | <child> |
5 | 17 | <object class="GtkMenuButton" id="bookmarks_button"> |
6 | 18 | <property name="visible">True</property> |
267 | 267 | <property name="can_focus">True</property> |
268 | 268 | <property name="text" translatable="yes">Open Appli_cation Manager</property> |
269 | 269 | <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> | |
270 | 278 | <property name="visible">True</property> |
271 | 279 | </object> |
272 | 280 | </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 | ] |
54 | 54 | #include "ephy-string.h" |
55 | 55 | #include "ephy-view-source-handler.h" |
56 | 56 | #include "ephy-web-app-utils.h" |
57 | #include "ephy-web-extension-dialog.h" | |
57 | 58 | #include "ephy-zoom.h" |
58 | 59 | |
59 | 60 | #include <gio/gio.h> |
3076 | 3077 | |
3077 | 3078 | g_simple_action_set_state (action, g_variant_new_boolean (mute)); |
3078 | 3079 | } |
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 | } |