[epiphany/pgriffis/web-extension/load-thread: 2/2] WebExtensions: Redesign file loading




commit 933d4b4269e5d1a2602ae5d6108f6153649a187d
Author: Patrick Griffis <pgriffis igalia com>
Date:   Sat Jun 18 15:32:59 2022 -0500

    WebExtensions: Redesign file loading
    
    This fixes numerous issues with file loading including a handful leaks
    and calling WebKit API from threads.
    
    This also changes resources to be a GHashTable instead of slowly
    iterating over a GList and doing string comparisons.
    
    Overall this makes it more clear what is ran in a thread and the
    simple non-IO tasks like parsing JSON are kept in the main thread.

 src/webextension/ephy-web-extension-manager.c |  76 +++--
 src/webextension/ephy-web-extension.c         | 420 ++++++++++++++------------
 src/webextension/ephy-web-extension.h         |   9 +-
 3 files changed, 270 insertions(+), 235 deletions(-)
---
diff --git a/src/webextension/ephy-web-extension-manager.c b/src/webextension/ephy-web-extension-manager.c
index ad78d8827..d01ec151c 100644
--- a/src/webextension/ephy-web-extension-manager.c
+++ b/src/webextension/ephy-web-extension-manager.c
@@ -120,7 +120,7 @@ ephy_web_extension_manager_remove_from_list (EphyWebExtensionManager *self,
   g_signal_emit (self, signals[CHANGED], 0);
 }
 
-void
+static void
 on_web_extension_loaded (GObject      *source_object,
                          GAsyncResult *result,
                          gpointer      user_data)
@@ -129,9 +129,9 @@ on_web_extension_loaded (GObject      *source_object,
   EphyWebExtension *web_extension;
   EphyWebExtensionManager *self = EPHY_WEB_EXTENSION_MANAGER (user_data);
 
-
   web_extension = ephy_web_extension_load_finished (source_object, result, &error);
   if (!web_extension) {
+    g_warning ("Failed to load extension: %s", error->message);
     return;
   }
 
@@ -143,42 +143,49 @@ on_web_extension_loaded (GObject      *source_object,
 }
 
 static void
-ephy_web_extension_manager_scan_directory (EphyWebExtensionManager *self,
-                                           const char              *extension_dir)
+scan_directory_ready_cb (GFile        *file,
+                         GAsyncResult *result,
+                         gpointer      user_data)
 {
-  g_autoptr (GDir) dir = NULL;
+  EphyWebExtensionManager *self = user_data;
   g_autoptr (GError) error = NULL;
-  const char *directory;
+  g_autoptr (GFileEnumerator) enumerator = NULL;
 
-  if (g_mkdir_with_parents (extension_dir, 0700) != 0)
-    g_warning ("Failed to create %s: %s", extension_dir, g_strerror (errno));
+  enumerator = g_file_enumerate_children_finish (file, result, &error);
 
-  if (!g_file_test (extension_dir, G_FILE_TEST_EXISTS))
-    g_mkdir_with_parents (extension_dir, 0700);
-
-  dir = g_dir_open (extension_dir, 0, &error);
-  if (!dir) {
-    g_warning ("Could not open %s: %s", extension_dir, error->message);
+  if (error) {
+    g_warning ("Failed to scan extensions directory: %s", error->message);
     return;
   }
 
-  errno = 0;
-  while ((directory = g_dir_read_name (dir))) {
-    g_autofree char *filename = NULL;
-    g_autoptr (GFile) file = NULL;
+  while (TRUE) {
+    GFileInfo *info;
+    GFile *child;
 
-    if (errno != 0) {
-      g_warning ("Problem reading %s: %s", extension_dir, g_strerror (errno));
+    if (!g_file_enumerator_iterate (enumerator, &info, &child, NULL, &error)) {
+      g_warning ("Error enumerating extension directory: %s", error->message);
       break;
     }
+    if (!info)
+      break;
 
-    filename = g_build_filename (extension_dir, directory, NULL);
-    file = g_file_new_for_path (filename);
+    ephy_web_extension_load_async (child, info, self->cancellable, on_web_extension_loaded, self);
+  }
+}
 
-    ephy_web_extension_load_async (file, self->cancellable, on_web_extension_loaded, self);
+static void
+ephy_web_extension_manager_scan_directory_async (EphyWebExtensionManager *self,
+                                                 const char              *extension_dir_path)
+{
+  g_autoptr (GFile) extension_dir = g_file_new_for_path (extension_dir_path);
 
-    errno = 0;
-  }
+  g_file_enumerate_children_async (extension_dir,
+                                   G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                   G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                   G_PRIORITY_DEFAULT,
+                                   self->cancellable,
+                                   (GAsyncReadyCallback)scan_directory_ready_cb,
+                                   self);
 }
 
 static void
@@ -239,7 +246,7 @@ ephy_web_extension_manager_constructed (GObject *object)
   self->web_extensions = g_ptr_array_new_full (0, g_object_unref);
   self->user_agent_overrides = create_user_agent_overrides ();
 
-  ephy_web_extension_manager_scan_directory (self, dir);
+  ephy_web_extension_manager_scan_directory_async (self, dir);
 }
 
 static void
@@ -330,6 +337,7 @@ on_new_web_extension_loaded (GObject      *source_object,
 
   ephy_web_extension_manager_add_to_list (self, web_extension);
 }
+
 /**
  * Install a new web web_extension into the local web_extension directory.
  * File should only point to a manifest.json or a .xpi file
@@ -340,11 +348,15 @@ ephy_web_extension_manager_install (EphyWebExtensionManager *self,
 {
   g_autoptr (GFile) target = NULL;
   g_autofree char *basename = NULL;
+  g_autoptr (GFileInfo) file_info = NULL;
   gboolean is_xpi = FALSE;
+  g_autoptr (GError) error = NULL;
 
   basename = g_file_get_basename (file);
   is_xpi = g_str_has_suffix (basename, ".xpi");
 
+  /* FIXME: Make this async. */
+
   if (!is_xpi) {
     g_autoptr (GFile) source = NULL;
 
@@ -354,7 +366,6 @@ ephy_web_extension_manager_install (EphyWebExtensionManager *self,
 
     ephy_copy_directory (g_file_get_path (source), g_file_get_path (target));
   } else {
-    g_autoptr (GError) error = NULL;
     target = g_file_new_build_filename (ephy_default_profile_dir (), "web_extensions", g_file_get_basename 
(file), NULL);
 
     if (!g_file_copy (file, target, G_FILE_COPY_NONE, NULL, NULL, NULL, &error)) {
@@ -365,8 +376,15 @@ ephy_web_extension_manager_install (EphyWebExtensionManager *self,
     }
   }
 
-  if (target)
-    ephy_web_extension_load_async (g_steal_pointer (&target), self->cancellable, 
on_new_web_extension_loaded, self);
+  if (target) {
+    file_info = g_file_query_info (target, G_FILE_ATTRIBUTE_STANDARD_TYPE, 0, self->cancellable, &error);
+    if (!file_info) {
+      g_warning ("Failed to query file info: %s", error->message);
+      return;
+    }
+
+    ephy_web_extension_load_async (g_steal_pointer (&target), file_info, self->cancellable, 
on_new_web_extension_loaded, self);
+  }
 }
 
 void
diff --git a/src/webextension/ephy-web-extension.c b/src/webextension/ephy-web-extension.c
index 74d88f3db..595bedb97 100644
--- a/src/webextension/ephy-web-extension.c
+++ b/src/webextension/ephy-web-extension.c
@@ -74,11 +74,6 @@ typedef struct {
   char *page;
 } WebExtensionOptionsUI;
 
-typedef struct {
-  char *name;
-  GBytes *bytes;
-} WebExtensionResource;
-
 typedef struct {
   char *code;
   WebKitUserStyleSheet *style;
@@ -105,7 +100,7 @@ struct _EphyWebExtension {
   WebExtensionPageAction *page_action;
   WebExtensionBrowserAction *browser_action;
   WebExtensionOptionsUI *options_ui;
-  GList *resources;
+  GHashTable *resources;
   GList *custom_css;
   GHashTable *permissions;
   GPtrArray *host_permissions;
@@ -119,10 +114,6 @@ G_DEFINE_QUARK (web - extension - error - quark, web_extension_error)
 G_DEFINE_TYPE (EphyWebExtension, ephy_web_extension, G_TYPE_OBJECT)
 
 static gboolean is_supported_scheme (const char *scheme);
-static void web_extension_add_resource (EphyWebExtension *self,
-                                        const char       *name,
-                                        gpointer          data,
-                                        guint             len);
 
 static const char *
 get_string_member (JsonObject *object,
@@ -138,14 +129,7 @@ gboolean
 ephy_web_extension_has_resource (EphyWebExtension *self,
                                  const char       *name)
 {
-  for (GList *list = self->resources; list && list->data; list = list->next) {
-    WebExtensionResource *resource = list->data;
-
-    if (g_strcmp0 (resource->name, name) == 0)
-      return TRUE;
-  }
-
-  return FALSE;
+  return g_hash_table_contains (self->resources, name);
 }
 
 gconstpointer
@@ -153,18 +137,17 @@ ephy_web_extension_get_resource (EphyWebExtension *self,
                                  const char       *name,
                                  gsize            *length)
 {
+  GBytes *resource;
   if (length)
     *length = 0;
 
-  for (GList *list = self->resources; list && list->data; list = list->next) {
-    WebExtensionResource *resource = list->data;
-
-    if (g_strcmp0 (resource->name, name) == 0)
-      return g_bytes_get_data (resource->bytes, length);
+  resource = g_hash_table_lookup (self->resources, name);
+  if (!resource) {
+    g_debug ("Could not find web_extension resource: %s\n", name);
+    return NULL;
   }
 
-  g_debug ("Could not find web_extension resource: %s\n", name);
-  return NULL;
+  return g_bytes_get_data (resource, length);
 }
 
 char *
@@ -513,7 +496,6 @@ web_extension_add_content_script (JsonArray *array,
     if (child_array)
       json_array_foreach_element (child_array, web_extension_add_js, content_script);
   }
-  g_ptr_array_add (content_script->js, NULL);
 
   /* Create user scripts so that we can unload them if necessary */
   web_extension_content_script_build (self, content_script);
@@ -527,8 +509,9 @@ generate_background_page (EphyWebExtension *self,
 {
   /* The entry point is always an HTML file, if they list scripts we just generate one.
    * This behavior exactly matches Firefox. */
-  g_autoptr (GString) background_page = g_string_new ("<html><head><meta charset=\"utf-8\"></head><body>");
-  char *path = g_strdup ("_generated_background_page.html");
+  GString *background_page = g_string_new ("<html><head><meta charset=\"utf-8\"></head><body>");
+  const char *path = "_generated_background_page.html";
+  GBytes *bytes;
 
   for (unsigned int i = 0; i < json_array_get_length (scripts); i++) {
     const char *script_file = json_array_get_string_element (scripts, i);
@@ -541,9 +524,12 @@ generate_background_page (EphyWebExtension *self,
   }
   g_string_append (background_page, "</body>");
 
-  web_extension_add_resource (self, path, background_page->str, background_page->len);
+  bytes = g_bytes_new_take (background_page->str, background_page->len);
+  g_string_free (background_page, FALSE);
 
-  return path;
+  g_hash_table_insert (self->resources, g_strdup (path), bytes);
+
+  return g_strdup (path);
 }
 
 static void
@@ -766,14 +752,6 @@ web_extension_add_permission (JsonArray *array,
   g_hash_table_add (self->permissions, g_strdup (permission));
 }
 
-static void
-web_extension_resource_free (WebExtensionResource *resource)
-{
-  g_clear_pointer (&resource->bytes, g_bytes_unref);
-  g_clear_pointer (&resource->name, g_free);
-  g_free (resource);
-}
-
 static void
 ephy_web_extension_dispose (GObject *object)
 {
@@ -791,7 +769,7 @@ ephy_web_extension_dispose (GObject *object)
 
   g_clear_list (&self->icons, (GDestroyNotify)web_extension_icon_free);
   g_clear_list (&self->content_scripts, (GDestroyNotify)web_extension_content_script_free);
-  g_clear_list (&self->resources, (GDestroyNotify)web_extension_resource_free);
+  g_clear_pointer (&self->resources, g_hash_table_unref);
   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_hash_table_unref);
@@ -829,163 +807,47 @@ ephy_web_extension_init (EphyWebExtension *self)
   g_ptr_array_add (self->host_permissions, NULL);
 }
 
-static EphyWebExtension *
-ephy_web_extension_new (void)
-{
-  return g_object_new (EPHY_TYPE_WEB_EXTENSION, NULL);
-}
-
-static void
-web_extension_add_resource (EphyWebExtension *self,
-                            const char       *name,
-                            gpointer          data,
-                            guint             len)
-{
-  WebExtensionResource *resource = g_malloc0 (sizeof (WebExtensionResource));
-
-  resource->name = g_strdup (name);
-  resource->bytes = g_bytes_new (data, len);
-
-  self->resources = g_list_append (self->resources, resource);
-}
-
 static gboolean
-web_extension_read_directory (EphyWebExtension *self,
-                              char             *base,
-                              char             *path)
+ephy_web_extension_parse_manifest (EphyWebExtension  *self,
+                                   GError           **error)
 {
-  g_autoptr (GError) error = NULL;
-  g_autoptr (GDir) dir = NULL;
-  const char *dirent;
-  gboolean ret = TRUE;
-
-  dir = g_dir_open (path, 0, &error);
-  if (!dir) {
-    g_warning ("Could not open web_extension directory: %s", error->message);
-    return FALSE;
-  }
-
-  while ((dirent = g_dir_read_name (dir))) {
-    GFileType type;
-    g_autofree gchar *filename = g_build_filename (path, dirent, NULL);
-    g_autoptr (GFile) file = g_file_new_for_path (filename);
-
-    type = g_file_query_file_type (file, G_FILE_QUERY_INFO_NONE, NULL);
-    if (type == G_FILE_TYPE_DIRECTORY) {
-      web_extension_read_directory (self, base, filename);
-    } else {
-      g_autofree char *data = NULL;
-      gsize len;
-
-      if (g_file_get_contents (filename, &data, &len, NULL))
-        web_extension_add_resource (self, filename + strlen (base) + 1, data, len);
-    }
-  }
-
-  return ret;
-}
-
-static EphyWebExtension *
-ephy_web_extension_load_directory (char *filename)
-{
-  EphyWebExtension *self = ephy_web_extension_new ();
-
-  web_extension_read_directory (self, filename, filename);
-
-  return self;
-}
-
-static EphyWebExtension *
-ephy_web_extension_load_xpi (GFile *target)
-{
-  EphyWebExtension *self = NULL;
-  struct archive *pkg;
-  struct archive_entry *entry;
-  int res;
-
-  pkg = archive_read_new ();
-  archive_read_support_format_zip (pkg);
-
-  res = archive_read_open_filename (pkg, g_file_get_path (target), 10240);
-  if (res == ARCHIVE_OK) {
-    self = ephy_web_extension_new ();
-    self->xpi = TRUE;
-
-    while (archive_read_next_header (pkg, &entry) == ARCHIVE_OK) {
-      int64_t size = archive_entry_size (entry);
-      gsize total_len = 0;
-      g_autofree char *data = NULL;
-
-      data = g_malloc0 (size);
-      total_len = archive_read_data (pkg, data, size);
-
-      if (total_len > 0)
-        web_extension_add_resource (self, archive_entry_pathname (entry), data, total_len);
-    }
-
-    res = archive_read_free (pkg);
-    if (res != ARCHIVE_OK)
-      g_warning ("Error freeing archive: %s", archive_error_string (pkg));
-  } else {
-    g_warning ("Could not open archive %s", archive_error_string (pkg));
-  }
-
-  return self;
-}
-
-EphyWebExtension *
-ephy_web_extension_load (GFile *target)
-{
-  g_autoptr (GError) error = NULL;
-  g_autoptr (GFile) source = g_file_dup (target);
-  g_autoptr (GFile) parent = NULL;
-  g_autoptr (JsonObject) icons_object = NULL;
-  g_autoptr (JsonArray) content_scripts_array = NULL;
-  g_autoptr (JsonObject) background_object = NULL;
-  JsonParser *parser = NULL;
+  g_autoptr (GError) local_error = NULL;
+  g_autoptr (JsonParser) parser = NULL;
+  JsonObject *icons_object = NULL;
+  JsonArray *content_scripts_array = NULL;
+  JsonObject *background_object = NULL;
   JsonNode *root = NULL;
   JsonObject *root_object = NULL;
-  EphyWebExtension *self = NULL;
-  GFileType type;
   gsize length = 0;
-  const unsigned char *manifest;
+  const guchar *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) {
-    g_autofree char *path = g_file_get_path (source);
-    self = ephy_web_extension_load_directory (path);
-  } else
-    self = ephy_web_extension_load_xpi (source);
-
-  if (!self)
-    return NULL;
-
   manifest = ephy_web_extension_get_resource (self, "manifest.json", &length);
-  if (!manifest)
-    return NULL;
+  if (!manifest) {
+    g_set_error (error, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_MANIFEST, "manifest.json not 
found");
+    return FALSE;
+  }
 
   parser = json_parser_new ();
-  if (!json_parser_load_from_data (parser, (char *)manifest, length, &error)) {
-    g_warning ("Could not load web extension manifest: %s", error->message);
-    return NULL;
+  if (!json_parser_load_from_data (parser, (const char *)manifest, length, &local_error)) {
+    g_set_error (error, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_MANIFEST, "Failed to parse 
manifest.json: %s", local_error->message);
+    return FALSE;
   }
 
   root = json_parser_get_root (parser);
-  if (!root) {
-    g_warning ("WebExtension manifest json root is NULL, return NULL.");
-    return NULL;
+  if (!root || json_node_get_node_type (root) != JSON_NODE_OBJECT) {
+    g_set_error (error, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_MANIFEST, "manifest.json invalid");
+    return FALSE;
   }
 
   root_object = json_node_get_object (root);
   if (!root_object) {
-    g_warning ("WebExtension manifest json root is NULL, return NULL.");
-    return NULL;
+    g_set_error (error, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_MANIFEST, "manifest.json invalid");
+    return FALSE;
   }
 
   /* FIXME: Implement i18n: 
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Internationalization#retrieving_localized_strings_in_manifests
 */
   self->manifest = g_strndup ((char *)manifest, length);
-  self->base_location = parent ? g_file_get_path (parent) : g_file_get_path (target);
   self->description = ephy_web_extension_manifest_get_key (self, root_object, "description");
   self->manifest_version = json_object_get_int_member (root_object, "manifest_version");
   self->name = ephy_web_extension_manifest_get_key (self, root_object, "name");
@@ -997,10 +859,10 @@ ephy_web_extension_load (GFile *target)
                                                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);
+    self->local_storage = json_from_string (local_storage_contents, &local_error);
+    if (local_error) {
+      g_warning ("Failed to load extension's local storage JSON: %s", local_error->message);
+      g_clear_error (&local_error);
     }
   }
 
@@ -1026,55 +888,211 @@ ephy_web_extension_load (GFile *target)
   }
 
   if (json_object_has_member (root_object, "page_action")) {
-    g_autoptr (JsonObject) page_action_object = json_object_get_object_member (root_object, "page_action");
+    JsonObject *page_action_object = json_object_get_object_member (root_object, "page_action");
 
     web_extension_add_page_action (page_action_object, self);
   }
 
   if (json_object_has_member (root_object, "browser_action")) {
-    g_autoptr (JsonObject) browser_action_object = json_object_get_object_member (root_object, 
"browser_action");
+    JsonObject *browser_action_object = json_object_get_object_member (root_object, "browser_action");
 
     web_extension_add_browser_action (browser_action_object, self);
   }
 
   if (json_object_has_member (root_object, "options_ui")) {
-    g_autoptr (JsonObject) browser_action_object = json_object_get_object_member (root_object, "options_ui");
+    JsonObject *browser_action_object = json_object_get_object_member (root_object, "options_ui");
 
     web_extension_add_options_ui (browser_action_object, self);
   }
 
   if (json_object_has_member (root_object, "permissions")) {
-    g_autoptr (JsonArray) array = json_object_get_array_member (root_object, "permissions");
+    JsonArray *array = json_object_get_array_member (root_object, "permissions");
 
     json_array_foreach_element (array, web_extension_add_permission, self);
   }
 
-  return self;
+  return TRUE;
 }
 
 EphyWebExtension *
-ephy_web_extension_load_finished (GObject       *unused,
+ephy_web_extension_load_finished (GObject       *source_object,
                                   GAsyncResult  *result,
                                   GError       **error)
 {
-  g_assert (g_task_is_valid (result, unused));
-
   return g_task_propagate_pointer (G_TASK (result), error);
 }
 
 static void
-load_web_extension_thread (GTask        *task,
-                           gpointer     *unused,
-                           GFile        *target,
-                           GCancellable *cancellable)
+load_directory_or_xpi_ready_cb (GFile        *target,
+                                GAsyncResult *result,
+                                gpointer      user_data)
+{
+  g_autoptr (EphyWebExtension) web_extension = NULL;
+  GTask *load_task = user_data;
+  g_autoptr (GError) error = NULL;
+  g_autoptr (GHashTable) resources = NULL;
+  g_autoptr (GFile) parent = NULL;
+  gboolean was_xpi = GPOINTER_TO_UINT (g_task_get_task_data (G_TASK (result)));
+
+  resources = g_task_propagate_pointer (G_TASK (result), &error);
+  if (error) {
+    g_task_return_error (load_task, g_steal_pointer (&error));
+    return;
+  }
+
+  parent = g_file_get_parent (target);
+
+  web_extension = g_object_new (EPHY_TYPE_WEB_EXTENSION, NULL);
+  web_extension->xpi = was_xpi;
+  web_extension->base_location = g_file_get_path (parent);
+  web_extension->resources = g_steal_pointer (&resources);
+
+  if (!ephy_web_extension_parse_manifest (web_extension, &error)) {
+    g_task_return_error (load_task, g_steal_pointer (&error));
+    return;
+  }
+
+  g_task_return_pointer (load_task, g_steal_pointer (&web_extension), g_object_unref);
+}
+
+static gboolean
+load_directory_resources_thread (GFile         *directory,
+                                 GFile         *base_directory,
+                                 GHashTable    *resources,
+                                 GCancellable  *cancellable,
+                                 GError       **error)
+{
+  g_autoptr (GFileEnumerator) enumerator = NULL;
+
+  enumerator = g_file_enumerate_children (directory,
+                                          G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_NAME,
+                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                          cancellable,
+                                          error);
+  if (!enumerator)
+    return FALSE;
+
+  while (TRUE) {
+    GFileInfo *info;
+    GFile *child;
+
+    if (!g_file_enumerator_iterate (enumerator, &info, &child, cancellable, error))
+      return FALSE;
+
+    if (!info)
+      break;
+
+    if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
+      if (!load_directory_resources_thread (child, base_directory, resources, cancellable, error))
+        return FALSE;
+    } else {
+      char *contents;
+      gsize size;
+
+      if (!g_file_get_contents (g_file_peek_path (child), &contents, &size, error))
+        return FALSE;
+
+      g_hash_table_insert (resources,
+                           g_file_get_relative_path (base_directory, child),
+                           g_bytes_new_take (contents, size));
+    }
+  }
+
+  return TRUE;
+}
+
+static void
+load_directory_thread (GTask        *task,
+                       gpointer      source_object,
+                       gpointer      task_data,
+                       GCancellable *cancellable)
+{
+  GFile *target = source_object;
+  g_autoptr (GHashTable) resources = NULL;
+  g_autoptr (GError) error = NULL;
+
+  resources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_bytes_unref);
+
+  if (!load_directory_resources_thread (target, target, resources, g_task_get_cancellable (task), &error)) {
+    g_task_return_error (task, g_steal_pointer (&error));
+    return;
+  }
+
+  g_task_return_pointer (task, g_steal_pointer (&resources), (GDestroyNotify)g_hash_table_unref);
+}
+
+static void
+ephy_web_extension_load_directory_async (GFile *target,
+                                         GTask *load_task)
+{
+  GTask *directory_task = g_task_new (target, g_task_get_cancellable (load_task), 
(GAsyncReadyCallback)load_directory_or_xpi_ready_cb, load_task);
+  g_task_set_task_data (directory_task, GUINT_TO_POINTER (FALSE), NULL);
+  g_task_set_return_on_cancel (directory_task, TRUE);
+  g_task_run_in_thread (directory_task, load_directory_thread);
+}
+
+static void
+load_xpi_thread (GTask        *task,
+                 gpointer      source_object,
+                 gpointer      task_data,
+                 GCancellable *cancellable)
 {
-  EphyWebExtension *self = ephy_web_extension_load (target);
+  GFile *target = source_object;
+  GHashTable *resources;
+  struct archive *pkg;
+  struct archive_entry *entry;
+  int res;
+
+  pkg = archive_read_new ();
+  archive_read_support_format_zip (pkg);
+
+  res = archive_read_open_filename (pkg, g_file_peek_path (target), 10240);
+  if (res != ARCHIVE_OK) {
+    g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_XPI, "Invalid XPI 
archive: %s", archive_error_string (pkg));
 
-  g_task_return_pointer (task, self, NULL);
+    res = archive_read_free (pkg);
+    if (res != ARCHIVE_OK)
+      g_warning ("Error freeing archive: %s", archive_error_string (pkg));
+    return;
+  }
+
+  resources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_bytes_unref);
+
+  while (archive_read_next_header (pkg, &entry) == ARCHIVE_OK) {
+    int64_t size = archive_entry_size (entry);
+    gsize total_len = 0;
+    g_autofree char *data = NULL;
+
+    data = g_malloc0 (size);
+    total_len = archive_read_data (pkg, data, size);
+
+    if (total_len > 0) {
+      g_hash_table_insert (resources,
+                            g_strdup (archive_entry_pathname (entry)),
+                            g_bytes_new_take (g_steal_pointer (&data), total_len));
+    }
+  }
+
+  res = archive_read_free (pkg);
+  if (res != ARCHIVE_OK)
+    g_warning ("Error freeing archive: %s", archive_error_string (pkg));
+
+  g_task_return_pointer (task, resources, (GDestroyNotify)g_hash_table_unref);
+}
+
+static void
+ephy_web_extension_load_xpi_async (GFile *target,
+                                   GTask *load_task)
+{
+  GTask *xpi_task = g_task_new (target, g_task_get_cancellable (load_task), 
(GAsyncReadyCallback)load_directory_or_xpi_ready_cb, load_task);
+  g_task_set_task_data (xpi_task, GUINT_TO_POINTER (TRUE), NULL);
+  g_task_set_return_on_cancel (xpi_task, TRUE);
+  g_task_run_in_thread (xpi_task, load_xpi_thread);
 }
 
 void
 ephy_web_extension_load_async (GFile               *target,
+                               GFileInfo           *info,
                                GCancellable        *cancellable,
                                GAsyncReadyCallback  callback,
                                gpointer             user_data)
@@ -1082,16 +1100,16 @@ ephy_web_extension_load_async (GFile               *target,
   GTask *task;
 
   g_assert (target);
+  g_assert (info);
 
-  task = g_task_new (NULL, cancellable, callback, user_data);
-  g_task_set_priority (task, G_PRIORITY_DEFAULT);
-  g_task_set_task_data (task,
-                        g_file_dup (target),
-                        (GDestroyNotify)g_object_unref);
-  g_task_run_in_thread (task, (GTaskThreadFunc)load_web_extension_thread);
-  g_object_unref (task);
-}
+  task = g_task_new (target, cancellable, callback, user_data);
+  g_task_set_return_on_cancel (task, TRUE);
 
+  if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+    ephy_web_extension_load_directory_async (target, task);
+  else
+    ephy_web_extension_load_xpi_async (target, task);
+}
 
 GdkPixbuf *
 ephy_web_extension_load_pixbuf (EphyWebExtension *self,
diff --git a/src/webextension/ephy-web-extension.h b/src/webextension/ephy-web-extension.h
index d3d8577e9..008217c90 100644
--- a/src/webextension/ephy-web-extension.h
+++ b/src/webextension/ephy-web-extension.h
@@ -55,6 +55,8 @@ typedef enum {
   WEB_EXTENSION_ERROR_INVALID_ARGUMENT = 1001,
   WEB_EXTENSION_ERROR_PERMISSION_DENIED = 1002,
   WEB_EXTENSION_ERROR_NOT_IMPLEMENTED = 1003,
+  WEB_EXTENSION_ERROR_INVALID_MANIFEST = 1004,
+  WEB_EXTENSION_ERROR_INVALID_XPI = 1005,
 } WebExtensionErrorCode;
 
 typedef struct {
@@ -80,16 +82,13 @@ const char            *ephy_web_extension_get_homepage_url                (EphyW
 
 const char            *ephy_web_extension_get_author                      (EphyWebExtension *self);
 
-GList                 *ephy_web_extensions_get                            (void);
-
-EphyWebExtension      *ephy_web_extension_load                            (GFile *file);
-
 void                   ephy_web_extension_load_async                      (GFile               *target,
+                                                                           GFileInfo           *info,
                                                                            GCancellable        *cancellable,
                                                                            GAsyncReadyCallback  callback,
                                                                            gpointer             user_data);
 
-EphyWebExtension      *ephy_web_extension_load_finished                   (GObject       *unused,
+EphyWebExtension      *ephy_web_extension_load_finished                   (GObject       *source_object,
                                                                            GAsyncResult  *result,
                                                                            GError       **error);
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]