[epiphany/pgriffis/web-extension-storage: 3/5] WebExtension: Initial implementation of storage.local




commit 1c1a4d669064480306ca2f0e5c978904ead4c1cf
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
    
    Part-of: <https://gitlab.gnome.org/GNOME/epiphany/-/merge_requests/1117>

 .../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]