[epiphany/pgriffis/web-extension-storage] WebExtension: Initial implementation of storage.local
- From: Patrick Griffis <pgriffis src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [epiphany/pgriffis/web-extension-storage] WebExtension: Initial implementation of storage.local
- Date: Tue, 24 May 2022 16:33:30 +0000 (UTC)
commit b68b8ac23dd5527c03e11773c6dda2dbdeddd263
Author: Patrick Griffis <pgriffis igalia com>
Date: Mon May 23 22:59:41 2022 -0500
WebExtension: Initial implementation of storage.local
This implements a simple JSON backend to storage.local.
Remaining tasks:
- Make it a non-blocking async API
- Implement onChanged event
- Implement storage.sync using Firefox sync.
- Expose API to Content Scripts
.../resources/js/webextensions-common.js | 27 ++-
src/webextension/api/storage.c | 209 +++++++++++++++++++++
src/webextension/api/storage.h | 35 ++++
src/webextension/ephy-web-extension-manager.c | 2 +
src/webextension/ephy-web-extension.c | 60 +++++-
src/webextension/ephy-web-extension.h | 9 +
src/webextension/meson.build | 1 +
7 files changed, 327 insertions(+), 16 deletions(-)
---
diff --git a/embed/web-process-extension/resources/js/webextensions-common.js
b/embed/web-process-extension/resources/js/webextensions-common.js
index 3cab4497c..f4417b3e3 100644
--- a/embed/web-process-extension/resources/js/webextensions-common.js
+++ b/embed/web-process-extension/resources/js/webextensions-common.js
@@ -48,25 +48,22 @@ window.browser.runtime = {
},
};
+
+window.browser.storage = {
+ local: {
+ get: function (keys) {
+ return ephy_message ('storage.local.get', keys);
},
- hasListener: function (cb) {
- return !!runtime_onmessage_listeners.find(l => l.callback === cb);
- }
- },
- onConnect: {
- addListener: function (cb) {
- runtime_onconnect_listeners.push({callback: cb});
+ set: function (keys) {
+ return ephy_message ('storage.local.set', keys);
},
- removeListener: function (cb) {
- runtime_onconnect_listeners = runtime_onconnect_listeners.filter(l => l.callback !== cb);
+ remove: function (keys) {
+ return ephy_message ('storage.local.remove', keys);
},
- hasListener: function (cb) {
- return !!runtime_onconnect_listeners.find(l => l.callback === cb);
+ clear: function () {
+ return ephy_message ('storage.local.clear');
}
- },
- sendMessage: function (args, cb) {
- return ephy_message ('runtime.sendMessage', args, cb);
- },
+ }
};
// Compatibility with Chrome
diff --git a/src/webextension/api/storage.c b/src/webextension/api/storage.c
new file mode 100644
index 000000000..4055497b3
--- /dev/null
+++ b/src/webextension/api/storage.c
@@ -0,0 +1,209 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright © 2019-2020 Jan-Michael Brummer <jan brummer tabos org>
+ * Copyright © 2022 Igalia S.L.
+ *
+ * This file is part of Epiphany.
+ *
+ * Epiphany is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "ephy-embed-utils.h"
+#include "ephy-shell.h"
+#include "ephy-window.h"
+
+#include "storage.h"
+
+static JsonNode *
+node_from_value_property (JSCValue *object,
+ const char *property)
+{
+ g_autoptr (JSCValue) property_value = jsc_value_object_get_property (object, property);
+ g_autofree char *value_json = jsc_value_to_json (property_value, 0);
+ JsonNode *json = json_from_string (value_json, NULL);
+ g_assert (json);
+ return json;
+}
+
+static GStrv
+strv_from_value (JSCValue *array)
+{
+ g_autoptr (GStrvBuilder) builder = g_strv_builder_new ();
+
+ for (guint i = 0;; i++) {
+ g_autoptr (JSCValue) value = jsc_value_object_get_property_at_index (array, i);
+ g_autofree char *str = NULL;
+
+ if (jsc_value_is_undefined (value))
+ break;
+
+ str = jsc_value_to_string (value);
+ g_strv_builder_add (builder, str);
+ }
+
+ return g_strv_builder_end (builder);
+}
+
+static char *
+storage_handler_local_set (EphyWebExtension *self,
+ char *name,
+ JSCValue *args)
+{
+ JsonNode *local_storage = ephy_web_extension_get_local_storage (self);
+ JsonObject *local_storage_obj = json_node_get_object (local_storage);
+ g_auto (GStrv) keys = NULL;
+
+ if (!jsc_value_is_object (args))
+ return NULL;
+
+ keys = jsc_value_object_enumerate_properties (args);
+
+ for (guint i = 0; keys[i]; i++)
+ json_object_set_member (local_storage_obj, keys[i], node_from_value_property (args, keys[i]));
+
+ /* FIXME: Implement storage.onChanged */
+ /* FIXME: Async IO */
+ ephy_web_extension_save_local_storage (self);
+
+ return NULL;
+}
+
+static char *
+storage_handler_local_get (EphyWebExtension *self,
+ char *name,
+ JSCValue *args)
+{
+ JsonNode *local_storage = ephy_web_extension_get_local_storage (self);
+ JsonObject *local_storage_obj = json_node_get_object (local_storage);
+ g_autoptr (JsonBuilder) builder = NULL;
+ g_autoptr (JsonNode) root = NULL;
+ g_auto (GStrv) keys = NULL;
+
+ if (jsc_value_is_null (args))
+ return json_to_string (local_storage, FALSE);
+
+ builder = json_builder_new ();
+ json_builder_begin_object (builder);
+
+ if (jsc_value_is_string (args)) {
+ g_autofree char *key = jsc_value_to_string (args);
+ JsonNode *member = json_object_get_member (local_storage_obj, key);
+ if (member) {
+ json_builder_set_member_name (builder, key);
+ json_builder_add_value (builder, member);
+ }
+ goto end_get;
+ }
+
+ if (jsc_value_is_array (args)) {
+ keys = strv_from_value (args);
+ for (guint i = 0; keys[i]; i++) {
+ const char *key = keys[i];
+ JsonNode *member = json_object_get_member (local_storage_obj, key);
+ if (member) {
+ json_builder_set_member_name (builder, key);
+ json_builder_add_value (builder, member);
+ }
+ }
+ goto end_get;
+ }
+
+ if (jsc_value_is_object (args)) {
+ keys = jsc_value_object_enumerate_properties (args);
+ for (guint i = 0; keys[i]; i++) {
+ const char *key = keys[i];
+ JsonNode *member = json_object_get_member (local_storage_obj, key);
+ json_builder_set_member_name (builder, key);
+ if (!member)
+ member = node_from_value_property (args, key);
+ json_builder_add_value (builder, member);
+ }
+ goto end_get;
+ }
+
+end_get:
+ json_builder_end_object (builder);
+ root = json_builder_get_root (builder);
+ return json_to_string (root, FALSE);
+}
+
+static char *
+storage_handler_local_remove (EphyWebExtension *self,
+ char *name,
+ JSCValue *args)
+{
+ JsonNode *local_storage = ephy_web_extension_get_local_storage (self);
+ JsonObject *local_storage_obj = json_node_get_object (local_storage);
+
+ if (jsc_value_is_string (args)) {
+ g_autofree char *key = jsc_value_to_string (args);
+ json_object_remove_member (local_storage_obj, key);
+ goto end_remove;
+ }
+
+ if (jsc_value_is_array (args)) {
+ g_auto (GStrv) keys = strv_from_value (args);
+ for (guint i = 0; keys[i]; i++) {
+ json_object_remove_member (local_storage_obj, keys[i]);
+ }
+ goto end_remove;
+ }
+
+end_remove:
+ ephy_web_extension_save_local_storage (self);
+ return NULL;
+}
+
+static char *
+storage_handler_local_clear (EphyWebExtension *self,
+ char *name,
+ JSCValue *args)
+{
+ ephy_web_extension_clear_local_storage (self);
+ ephy_web_extension_save_local_storage (self);
+ return NULL;
+}
+
+static EphyWebExtensionApiHandler storage_handlers[] = {
+ {"local.set", storage_handler_local_set},
+ {"local.get", storage_handler_local_get},
+ {"local.remove", storage_handler_local_remove},
+ {"local.clear", storage_handler_local_clear},
+};
+
+char *
+ephy_web_extension_api_storage_handler (EphyWebExtension *self,
+ char *name,
+ JSCValue *args)
+{
+ guint idx;
+
+ if (!ephy_web_extension_has_permission (self, "storage")) {
+ g_warning ("Extension %s tried to use storage without permission.", ephy_web_extension_get_name (self));
+ return NULL;
+ }
+
+ for (idx = 0; idx < G_N_ELEMENTS (storage_handlers); idx++) {
+ EphyWebExtensionApiHandler handler = storage_handlers[idx];
+
+ if (g_strcmp0 (handler.name, name) == 0)
+ return handler.execute (self, name, args);
+ }
+
+ g_warning ("%s(): '%s' not implemented by Epiphany!", __FUNCTION__, name);
+
+ return NULL;
+}
diff --git a/src/webextension/api/storage.h b/src/webextension/api/storage.h
new file mode 100644
index 000000000..18ff87de9
--- /dev/null
+++ b/src/webextension/api/storage.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright © 2019-2020 Jan-Michael Brummer <jan brummer tabos org>
+ * Copyright © 2022 Igalia S.L.
+ *
+ * This file is part of Epiphany.
+ *
+ * Epiphany is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+
+#include "ephy-web-extension.h"
+
+#include <webkit2/webkit2.h>
+
+G_BEGIN_DECLS
+
+char *ephy_web_extension_api_storage_handler (EphyWebExtension *self,
+ char *name,
+ JSCValue *value);
+
+G_END_DECLS
diff --git a/src/webextension/ephy-web-extension-manager.c b/src/webextension/ephy-web-extension-manager.c
index 65f3a75bd..d48b5630c 100644
--- a/src/webextension/ephy-web-extension-manager.c
+++ b/src/webextension/ephy-web-extension-manager.c
@@ -38,6 +38,7 @@
#include "api/notifications.h"
#include "api/pageaction.h"
#include "api/runtime.h"
+#include "api/storage.h"
#include "api/tabs.h"
#include <json-glib/json-glib.h>
@@ -58,6 +59,7 @@ EphyWebExtensionApiHandler api_handlers[] = {
{"notifications", ephy_web_extension_api_notifications_handler},
{"pageAction", ephy_web_extension_api_pageaction_handler},
{"runtime", ephy_web_extension_api_runtime_handler},
+ {"storage", ephy_web_extension_api_storage_handler},
{"tabs", ephy_web_extension_api_tabs_handler},
{NULL, NULL},
};
diff --git a/src/webextension/ephy-web-extension.c b/src/webextension/ephy-web-extension.c
index 82eb061fe..1b9e5c479 100644
--- a/src/webextension/ephy-web-extension.c
+++ b/src/webextension/ephy-web-extension.c
@@ -108,6 +108,8 @@ struct _EphyWebExtension {
GList *custom_css;
GPtrArray *permissions;
GCancellable *cancellable;
+ char *local_storage_path;
+ JsonNode *local_storage;
};
G_DEFINE_TYPE (EphyWebExtension, ephy_web_extension, G_TYPE_OBJECT)
@@ -728,6 +730,7 @@ ephy_web_extension_dispose (GObject *object)
g_clear_pointer (&self->name, g_free);
g_clear_pointer (&self->version, g_free);
g_clear_pointer (&self->homepage_url, g_free);
+ g_clear_pointer (&self->local_storage_path, g_free);
g_clear_list (&self->icons, (GDestroyNotify)web_extension_icon_free);
g_clear_list (&self->content_scripts, (GDestroyNotify)web_extension_content_script_free);
@@ -735,6 +738,7 @@ ephy_web_extension_dispose (GObject *object)
g_clear_pointer (&self->background, web_extension_background_free);
g_clear_pointer (&self->options_ui, web_extension_options_ui_free);
g_clear_pointer (&self->permissions, g_ptr_array_unref);
+ g_clear_pointer (&self->local_storage, json_node_unref);
g_clear_pointer (&self->page_action, web_extension_page_action_free);
g_clear_pointer (&self->browser_action, web_extension_browser_action_free);
@@ -884,6 +888,7 @@ ephy_web_extension_load (GFile *target)
GFileType type;
gsize length = 0;
const unsigned char *manifest;
+ g_autofree char *local_storage_contents = NULL;
type = g_file_query_file_type (source, G_FILE_QUERY_INFO_NONE, NULL);
if (type == G_FILE_TYPE_DIRECTORY) {
@@ -927,6 +932,20 @@ ephy_web_extension_load (GFile *target)
self->homepage_url = ephy_web_extension_manifest_get_key (self, root_object, "homepage_url");
self->author = ephy_web_extension_manifest_get_key (self, root_object, "author");
+ self->local_storage_path = g_build_filename (ephy_config_dir (), "web_extensions",
+ g_path_get_basename (self->base_location),
"local-storage.json", NULL);
+
+ if (g_file_get_contents (self->local_storage_path, &local_storage_contents, NULL, NULL)) {
+ self->local_storage = json_from_string (local_storage_contents, &error);
+ if (error) {
+ g_warning ("Failed to load extension's local storage JSON: %s", error->message);
+ g_clear_error (&error);
+ }
+ }
+
+ if (!self->local_storage)
+ self->local_storage = json_node_init_object (json_node_alloc (), json_object_new ());
+
if (json_object_has_member (root_object, "icons")) {
icons_object = json_object_get_object_member (root_object, "icons");
@@ -1346,7 +1365,7 @@ ephy_web_extension_has_permission_internal (EphyWebExtension *self,
/* https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns */
- for (uint i = 0; i < self->permissions->len; i++) {
+ for (guint i = 0; i < self->permissions->len; i++) {
const char *permission = g_ptr_array_index (self->permissions, i);
if (is_user_interaction && is_active_tab && strcmp (permission, "activeTab") == 0)
@@ -1377,3 +1396,42 @@ ephy_web_extension_has_tab_or_host_permission (EphyWebExtension *self,
{
return ephy_web_extension_has_permission_internal (self, web_view, is_user_interaction, TRUE);
}
+
+gboolean
+ephy_web_extension_has_permission (EphyWebExtension *self,
+ const char *permission)
+{
+ for (guint i = 0; i < self->permissions->len; i++) {
+ if (strcmp (g_ptr_array_index (self->permissions, i), permission) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+JsonNode *
+ephy_web_extension_get_local_storage (EphyWebExtension *self)
+{
+ return self->local_storage;
+}
+
+void
+ephy_web_extension_save_local_storage (EphyWebExtension *self)
+{
+ g_autoptr (GError) error = NULL;
+ g_autofree char *json = NULL;
+ g_autofree char *parent_dir = NULL;
+
+ parent_dir = g_path_get_dirname (self->local_storage_path);
+ g_mkdir_with_parents (parent_dir, 0755);
+
+ json = json_to_string (self->local_storage, TRUE);
+ if (!g_file_set_contents (self->local_storage_path, json, -1, &error))
+ g_warning ("Failed to write %s: %s", self->local_storage_path, error->message);
+}
+
+void
+ephy_web_extension_clear_local_storage (EphyWebExtension *self)
+{
+ self->local_storage = json_node_init_object (self->local_storage, json_object_new ());
+}
diff --git a/src/webextension/ephy-web-extension.h b/src/webextension/ephy-web-extension.h
index 1a7584c46..625488c16 100644
--- a/src/webextension/ephy-web-extension.h
+++ b/src/webextension/ephy-web-extension.h
@@ -133,5 +133,14 @@ gboolean ephy_web_extension_has_host_permission (EphyW
EphyWebView *web_view,
gboolean
is_user_interaction);
+gboolean ephy_web_extension_has_permission (EphyWebExtension *self,
+ const char *permission);
+
+JsonNode *ephy_web_extension_get_local_storage (EphyWebExtension *self);
+
+void ephy_web_extension_save_local_storage (EphyWebExtension *self);
+
+void ephy_web_extension_clear_local_storage (EphyWebExtension *self);
+
G_END_DECLS
diff --git a/src/webextension/meson.build b/src/webextension/meson.build
index 921cc68bc..5656e0b6e 100644
--- a/src/webextension/meson.build
+++ b/src/webextension/meson.build
@@ -2,6 +2,7 @@ ephywebextension_src = [
'webextension/api/notifications.c',
'webextension/api/pageaction.c',
'webextension/api/runtime.c',
+ 'webextension/api/storage.c',
'webextension/api/tabs.c',
'webextension/ephy-web-extension-manager.c',
'webextension/ephy-web-extension.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]