[epiphany/pgriffis/web-extension/commands: 2/4] WebExtensions: Add initial commands API




commit f3b9700b91cf81a086d072addcfcf60229c10abe
Author: Jamie Murphy <hello jamiethalacker dev>
Date:   Thu Jun 30 19:50:33 2022 -0700

    WebExtensions: Add initial commands API
    
    Part-of: <https://gitlab.gnome.org/GNOME/epiphany/-/merge_requests/1165>

 .../resources/js/webextensions.js                  |   7 +
 src/webextension/api/commands.c                    | 296 +++++++++++++++++++++
 src/webextension/api/commands.h                    |  36 +++
 src/webextension/ephy-web-extension-manager.c      |   2 +
 src/webextension/ephy-web-extension.c              | 136 ++++++++++
 src/webextension/ephy-web-extension.h              |  14 +
 src/webextension/meson.build                       |   1 +
 7 files changed, 492 insertions(+)
---
diff --git a/embed/web-process-extension/resources/js/webextensions.js 
b/embed/web-process-extension/resources/js/webextensions.js
index de6454ab6..b63bb1078 100644
--- a/embed/web-process-extension/resources/js/webextensions.js
+++ b/embed/web-process-extension/resources/js/webextensions.js
@@ -13,6 +13,13 @@ window.browser.alarms = {
     onAlarm: new EphyEventListener (),
 };
 
+window.browser.commands = {
+    getAll: function (...args) { return ephy_message ('commands.getAll', args); },
+    reset: function (...args) { return ephy_message ('commands.reset', args); },
+    update: function (...args) { return ephy_message ('commands.update', args); },
+    onCommand: new EphyEventListener (),
+};
+
 window.browser.windows = {
     onRemoved: new EphyEventListener (),
 };
diff --git a/src/webextension/api/commands.c b/src/webextension/api/commands.c
new file mode 100644
index 000000000..fde025f72
--- /dev/null
+++ b/src/webextension/api/commands.c
@@ -0,0 +1,296 @@
+/*
+ *  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-shell.h"
+
+#include "api-utils.h"
+#include "commands.h"
+
+/* Local command struct. */
+typedef struct {
+  EphyWebExtension *web_extension; /* Parent object */
+  char *name;
+  char *accelerator;
+  char *description;
+} Command;
+
+static void
+command_destroy (Command *cmd)
+{
+  /* g_clear_pointer (&cmd->name); */
+  g_free (cmd);
+}
+
+static void
+on_command_activated (GAction  *action,
+                      GVariant *parameter,
+                      gpointer  user_data)
+{
+  EphyWebExtensionManager *manager = ephy_web_extension_manager_get_default ();
+  Command *cmd = user_data;
+  JsonNode *node;
+  node = json_node_init_string (json_node_alloc (), cmd->name);
+
+  ephy_web_extension_manager_emit_in_extension_views (manager,
+                                                      cmd->web_extension,
+                                                      "commands.onCommand",
+                                                      json_to_string (node, FALSE));
+}
+
+static void
+setup_actions (Command *cmd)
+{
+  GSimpleAction *action = g_simple_action_new (cmd->name, NULL);
+  g_signal_connect (action, "activate", G_CALLBACK (on_command_activated), cmd);
+  g_action_map_add_action (G_ACTION_MAP (ephy_shell_get_default ()), (GAction *)action);
+  gtk_application_set_accels_for_action (GTK_APPLICATION (ephy_shell_get_default ()),
+                                         g_strdup_printf ("app.%s", cmd->name),
+                                         (const char *[]) {
+    cmd->accelerator,
+    NULL,
+  });
+}
+
+static Command *
+create_command (EphyWebExtension *self,
+                guint             cmd)
+{
+  char *shortcut;
+  char *suggested_key;
+  char *description;
+  Command *command = g_new0 (Command, 1);
+
+  ephy_web_extension_get_command_data_from_index (self,
+                                                  cmd,
+                                                  &shortcut,
+                                                  &suggested_key,
+                                                  &description);
+
+  command->web_extension = self;
+  command->name = g_strdup (shortcut);
+  command->accelerator = g_strdup (suggested_key);
+  command->description = g_strdup (description);
+
+  setup_actions (command);
+
+  return command;
+}
+
+static GHashTable *
+get_commands (EphyWebExtension *self)
+{
+  GList *commands = ephy_web_extension_get_commands (self);
+  Command *cmd = NULL;
+
+  GHashTable *cmds = g_object_get_data (G_OBJECT (self), "commands");
+  if (cmds)
+    return cmds;
+
+  cmds = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)command_destroy);
+  g_object_set_data_full (G_OBJECT (self), "commands", cmds, (GDestroyNotify)g_hash_table_destroy);
+
+  for (GList *list = commands; list && list->data; list = list->next) {
+    cmd = create_command (self, g_list_index (list, list->data));
+
+    g_hash_table_replace (cmds, cmd->name, cmd);
+  }
+  return cmds;
+}
+
+static JsonNode *
+command_to_node (Command *cmd)
+{
+  JsonNode *node;
+  JsonObject *obj;
+
+  if (!cmd)
+    return NULL;
+
+  node = json_node_init_object (json_node_alloc (), json_object_new ());
+  obj = json_node_get_object (node);
+  json_object_set_string_member (obj, "name", cmd->name);
+  json_object_set_string_member (obj, "shortcut", cmd->accelerator);
+  json_object_set_string_member (obj, "description", cmd->description);
+
+  return node;
+}
+
+char *
+create_accelerator (char *orig_string)
+{
+  char **accelerator_keys = NULL;
+  char *accelerator = "";
+
+  if (strchr (orig_string, '<') != NULL || strchr (orig_string, '>') != NULL)
+    return orig_string;
+
+  accelerator_keys = g_strsplit ((const gchar *)orig_string, "+", 0);
+
+  for (int i = 0; accelerator_keys[i]; i++) {
+    /* We have to use 2 here, as F# keys are treated like normal keys. */
+    if (strlen (accelerator_keys[i]) > 3) {
+      accelerator = g_strdup_printf ("%s<%s>", accelerator, accelerator_keys[i]);
+    } else {
+      accelerator = g_strdup_printf ("%s%s", accelerator, accelerator_keys[i]);
+    }
+  }
+
+  return accelerator;
+}
+
+static char *
+commands_handler_get_all (EphyWebExtension  *self,
+                          char              *name,
+                          JSCValue          *args,
+                          WebKitWebView     *web_view,
+                          GError           **error)
+{
+  GHashTable *commands = get_commands (self);
+  g_autoptr (JsonNode) node = json_node_init_array (json_node_alloc (), json_array_new ());
+  JsonArray *rel = json_node_get_array (node);
+  GHashTableIter iter;
+  Command *cmd;
+
+  g_hash_table_iter_init (&iter, commands);
+  while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&cmd))
+    json_array_add_element (rel, command_to_node (cmd));
+
+  return json_to_string (node, FALSE);
+}
+
+static char *
+commands_handler_reset (EphyWebExtension  *self,
+                        char              *name,
+                        JSCValue          *args,
+                        WebKitWebView     *web_view,
+                        GError           **error)
+{
+  GHashTable *commands = get_commands (self);
+  g_autoptr (JSCValue) name_value = jsc_value_object_get_property_at_index (args, 0);
+  g_autofree char *name_str = NULL;
+  Command *cmd = NULL;
+  char *shortcut;
+  char *suggested_key;
+  char *description;
+
+  if (!jsc_value_is_string (name_value))
+    name_str = g_strdup ("");
+  else
+    name_str = jsc_value_to_string (name_value);
+
+  if (g_hash_table_lookup (commands, name_str)) {
+    cmd = g_hash_table_lookup (commands, name_str);
+    ephy_web_extension_get_command_data_from_name (self,
+                                                   name_str,
+                                                   &shortcut,
+                                                   &suggested_key,
+                                                   &description);
+
+    cmd->name = g_strdup (shortcut);
+    cmd->accelerator = g_strdup (suggested_key);
+    cmd->description = g_strdup (description);
+  }
+
+  return NULL;
+}
+
+static char *
+commands_handler_update (EphyWebExtension  *self,
+                         char              *name,
+                         JSCValue          *args,
+                         WebKitWebView     *web_view,
+                         GError           **error)
+{
+  GHashTable *commands = get_commands (self);
+  g_autoptr (JSCValue) obj = jsc_value_object_get_property_at_index (args, 0);
+  Command *cmd = NULL;
+  g_autofree char *name_str = NULL;
+  g_autofree char *desc_str = NULL;
+  g_autofree char *shortcut_str = NULL;
+
+  if (!jsc_value_is_object (obj))
+    return NULL;
+  else {
+    if (!jsc_value_object_has_property (obj, "name"))
+      return NULL;
+    else
+      name_str = jsc_value_to_string (jsc_value_object_get_property (obj, "name"));
+
+    if (jsc_value_object_has_property (obj, "description"))
+      desc_str = jsc_value_to_string (jsc_value_object_get_property (obj, "description"));
+
+    if (jsc_value_object_has_property (obj, "shortcut")) {
+      shortcut_str = jsc_value_to_string (jsc_value_object_get_property (obj, "shortcut"));
+      shortcut_str = create_accelerator (shortcut_str);
+    }
+  }
+
+  if (g_hash_table_lookup (commands, name_str)) {
+    cmd = g_hash_table_lookup (commands, name_str);
+    cmd->name = g_strdup (name_str);
+    cmd->accelerator = g_strdup (shortcut_str);
+    cmd->description = g_strdup (desc_str);
+    gtk_application_set_accels_for_action (GTK_APPLICATION (ephy_shell_get_default ()),
+                                           g_strdup_printf ("app.%s", cmd->name),
+                                           (const char *[]) {
+      cmd->accelerator,
+      NULL,
+    });
+  }
+
+  return NULL;
+}
+
+static EphyWebExtensionSyncApiHandler commands_handlers[] = {
+  {"getAll", commands_handler_get_all},
+  {"reset", commands_handler_reset},
+  {"update", commands_handler_update}
+};
+
+void
+ephy_web_extension_api_commands_handler (EphyWebExtension *self,
+                                         char             *name,
+                                         JSCValue         *args,
+                                         WebKitWebView    *web_view,
+                                         GTask            *task)
+{
+  g_autoptr (GError) error = NULL;
+
+  for (guint idx = 0; idx < G_N_ELEMENTS (commands_handlers); idx++) {
+    EphyWebExtensionSyncApiHandler handler = commands_handlers[idx];
+    char *ret;
+
+    if (g_strcmp0 (handler.name, name) == 0) {
+      ret = handler.execute (self, name, args, web_view, &error);
+
+      if (error)
+        g_task_return_error (task, g_steal_pointer (&error));
+      else
+        g_task_return_pointer (task, ret, g_free);
+
+      return;
+    }
+  }
+
+  g_warning ("%s(): '%s' not implemented by Epiphany!", __FUNCTION__, name);
+  error = g_error_new_literal (WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_NOT_IMPLEMENTED, "Not Implemented");
+  g_task_return_error (task, g_steal_pointer (&error));
+}
diff --git a/src/webextension/api/commands.h b/src/webextension/api/commands.h
new file mode 100644
index 000000000..0cb315883
--- /dev/null
+++ b/src/webextension/api/commands.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  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
+
+void ephy_web_extension_api_commands_handler (EphyWebExtension *self,
+                                              char            *name,
+                                              JSCValue        *value,
+                                              WebKitWebView   *web_view,
+                                              GTask           *task);
+
+G_END_DECLS
diff --git a/src/webextension/ephy-web-extension-manager.c b/src/webextension/ephy-web-extension-manager.c
index 4ca8033d2..40ded52c9 100644
--- a/src/webextension/ephy-web-extension-manager.c
+++ b/src/webextension/ephy-web-extension-manager.c
@@ -36,6 +36,7 @@
 #include "ephy-web-view.h"
 
 #include "api/alarms.h"
+#include "api/commands.h"
 #include "api/cookies.h"
 #include "api/downloads.h"
 #include "api/menus.h"
@@ -71,6 +72,7 @@ G_DEFINE_TYPE (EphyWebExtensionManager, ephy_web_extension_manager, G_TYPE_OBJEC
 
 EphyWebExtensionApiHandler api_handlers[] = {
   {"alarms", ephy_web_extension_api_alarms_handler},
+  {"commands", ephy_web_extension_api_commands_handler},
   {"cookies", ephy_web_extension_api_cookies_handler},
   {"downloads", ephy_web_extension_api_downloads_handler},
   {"menus", ephy_web_extension_api_menus_handler},
diff --git a/src/webextension/ephy-web-extension.c b/src/webextension/ephy-web-extension.c
index 9ddb28081..6cc89f69a 100644
--- a/src/webextension/ephy-web-extension.c
+++ b/src/webextension/ephy-web-extension.c
@@ -75,6 +75,12 @@ typedef struct {
   WebKitUserStyleSheet *style;
 } WebExtensionCustomCSS;
 
+typedef struct {
+  char *shortcut;
+  char *suggested_key;
+  char *description;
+} WebExtensionCommand;
+
 struct _EphyWebExtension {
   GObject parent_instance;
 
@@ -105,6 +111,7 @@ struct _EphyWebExtension {
   char *local_storage_path;
   JsonNode *local_storage;
   GHashTable *web_accessible_resources;
+  GList *commands;
 };
 
 G_DEFINE_QUARK (web - extension - error - quark, web_extension_error)
@@ -752,6 +759,131 @@ web_extension_add_web_accessible_resource (JsonArray *array,
   g_hash_table_add (self->web_accessible_resources, g_strdup (web_accessible_resource));
 }
 
+char *
+ephy_web_extension_get_command_accelerator (JsonObject *suggested_key_object)
+{
+  const char *orig_string = NULL;
+  char **accelerator_keys = NULL;
+  char *accelerator = "";
+
+  if (!ephy_json_object_get_string (suggested_key_object, "default") &&
+      !ephy_json_object_get_string (suggested_key_object, "linux")) {
+    return NULL;
+  } else if (ephy_json_object_get_string (suggested_key_object, "default")) {
+    orig_string = ephy_json_object_get_string (suggested_key_object, "default");
+  } else if (ephy_json_object_get_string (suggested_key_object, "linux")) {
+    orig_string = ephy_json_object_get_string (suggested_key_object, "linux");
+  }
+
+  accelerator_keys = g_strsplit ((const gchar *)orig_string, "+", 0);
+
+  for (int i = 0; accelerator_keys[i]; i++) {
+    /* We have to use 2 here, as F# keys are treated like normal keys. */
+    if (strlen (accelerator_keys[i]) > 2) {
+      accelerator = g_strdup_printf ("%s<%s>", accelerator, accelerator_keys[i]);
+    } else {
+      accelerator = g_strdup_printf ("%s%s", accelerator, accelerator_keys[i]);
+    }
+  }
+
+  return accelerator;
+}
+
+static void
+web_extension_parse_commands (EphyWebExtension *self,
+                              JsonObject       *object)
+{
+  JsonNode *node = NULL;
+  JsonObject *cmd_object = NULL;
+  JsonObject *key_object = NULL;
+  WebExtensionCommand *cmd = g_malloc0 (sizeof (WebExtensionCommand));
+  const char *description;
+  const char *shortcut;
+  const char *suggested_key;
+
+  for (GList *list = json_object_get_members (object); list && list->data; list = list->next) {
+    node = json_object_get_member (object, (gchar *)list->data);
+    cmd_object = ephy_json_node_get_object (node);
+    key_object = ephy_json_object_get_object (cmd_object, "suggested_key");
+
+    if (!cmd_object) {
+      LOG ("Skipping command as value is invalid");
+      return;
+    }
+
+    description = ephy_json_object_get_string (cmd_object, "description");
+
+    shortcut = (gchar *)list->data;
+    if (!shortcut) {
+      LOG ("Skipping command as value is invalid");
+      return;
+    }
+
+    cmd->description = g_strdup (description);
+    cmd->shortcut = g_strdup (shortcut);
+
+    suggested_key = ephy_web_extension_get_command_accelerator (key_object);
+
+    if (!g_strcmp0 (suggested_key, "") == 0)
+      cmd->suggested_key = g_strdup (suggested_key);
+
+    self->commands = g_list_append (self->commands, cmd);
+  }
+}
+
+void *
+ephy_web_extension_get_command_data_from_index (EphyWebExtension  *self,
+                                                guint              command,
+                                                char             **shortcut,
+                                                char             **suggested_key,
+                                                char             **description)
+{
+  WebExtensionCommand *cmd = g_list_nth_data (self->commands, command);
+
+  *shortcut = strdup (cmd->shortcut);
+  *suggested_key = strdup (cmd->suggested_key);
+  *description = strdup (cmd->description);
+
+  return NULL;
+}
+
+void *
+ephy_web_extension_get_command_data_from_name (EphyWebExtension  *self,
+                                               const char        *name,
+                                               char             **shortcut,
+                                               char             **suggested_key,
+                                               char             **description)
+{
+  WebExtensionCommand *cmd = NULL;
+
+  for (GList *list = self->commands; list && list->data; list = list->next) {
+    cmd = list->data;
+
+    if (strcmp (cmd->shortcut, name) == 0) {
+      *shortcut = strdup (cmd->shortcut);
+      *suggested_key = strdup (cmd->suggested_key);
+      *description = strdup (cmd->description);
+    }
+  }
+
+  return NULL;
+}
+
+GList *
+ephy_web_extension_get_commands (EphyWebExtension *self)
+{
+  return self->commands;
+}
+
+static void
+web_extension_commands_free (WebExtensionCommand *cmd)
+{
+  g_clear_pointer (&cmd->shortcut, g_free);
+  g_clear_pointer (&cmd->suggested_key, g_free);
+  g_clear_pointer (&cmd->description, g_free);
+  g_free (cmd);
+}
+
 static void
 ephy_web_extension_dispose (GObject *object)
 {
@@ -781,6 +913,7 @@ ephy_web_extension_dispose (GObject *object)
   g_clear_pointer (&self->page_action, web_extension_page_action_free);
   g_clear_pointer (&self->browser_action, web_extension_browser_action_free);
   g_clear_list (&self->custom_css, (GDestroyNotify)webkit_user_style_sheet_unref);
+  g_clear_list (&self->commands, (GDestroyNotify)web_extension_commands_free);
 
   g_hash_table_destroy (self->page_action_map);
 
@@ -906,6 +1039,9 @@ ephy_web_extension_parse_manifest (EphyWebExtension  *self,
   if ((child_array = ephy_json_object_get_array (root_object, "web_accessible_resources")))
     json_array_foreach_element (child_array, web_extension_add_web_accessible_resource, self);
 
+  if ((child_object = ephy_json_object_get_object (root_object, "commands")))
+    web_extension_parse_commands (self, child_object);
+
   return TRUE;
 }
 
diff --git a/src/webextension/ephy-web-extension.h b/src/webextension/ephy-web-extension.h
index 8fbd140bc..7b280fa8b 100644
--- a/src/webextension/ephy-web-extension.h
+++ b/src/webextension/ephy-web-extension.h
@@ -148,6 +148,20 @@ const char            *ephy_web_extension_get_option_ui_page              (EphyW
 
 const char            *ephy_web_extension_get_guid                        (EphyWebExtension *self);
 
+GList                 *ephy_web_extension_get_commands                    (EphyWebExtension *self);
+
+void                  *ephy_web_extension_get_command_data_from_index     (EphyWebExtension    *self,
+                                                                           guint                command,
+                                                                           char               **shortcut,
+                                                                           char               
**suggested_key,
+                                                                           char               **description);
+
+void                  *ephy_web_extension_get_command_data_from_name      (EphyWebExtension    *self,
+                                                                           const char          *name,
+                                                                           char               **shortcut,
+                                                                           char               
**suggested_key,
+                                                                           char               **description);
+
 gboolean               ephy_web_extension_has_tab_or_host_permission      (EphyWebExtension *self,
                                                                            EphyWebView      *web_view,
                                                                            gboolean          
is_user_interaction);
diff --git a/src/webextension/meson.build b/src/webextension/meson.build
index 3d7fda2df..c4ad72c50 100644
--- a/src/webextension/meson.build
+++ b/src/webextension/meson.build
@@ -1,5 +1,6 @@
 ephywebextension_src = [
   'webextension/api/alarms.c',
+  'webextension/api/commands.c',
   'webextension/api/cookies.c',
   'webextension/api/downloads.c',
   'webextension/api/menus.c',


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