[epiphany/pgriffis/web-extension-alarms: 1/4] WebExtensions: Add complete implementation of alarms
- From: Patrick Griffis <pgriffis src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [epiphany/pgriffis/web-extension-alarms: 1/4] WebExtensions: Add complete implementation of alarms
- Date: Sun, 29 May 2022 02:27:28 +0000 (UTC)
commit dd572e55cde31979a9980d4c53fb2ff418ce7d91
Author: Patrick Griffis <pgriffis igalia com>
Date: Fri May 27 13:19:09 2022 -0500
WebExtensions: Add complete implementation of alarms
.../resources/js/webextensions.js | 5 +
src/webextension/api/alarms.c | 329 +++++++++++++++++++++
src/webextension/api/alarms.h | 35 +++
src/webextension/ephy-web-extension-manager.c | 67 +++++
src/webextension/meson.build | 1 +
5 files changed, 437 insertions(+)
---
diff --git a/embed/web-process-extension/resources/js/webextensions.js
b/embed/web-process-extension/resources/js/webextensions.js
index cc0dca1f8..dea92ae4b 100644
--- a/embed/web-process-extension/resources/js/webextensions.js
+++ b/embed/web-process-extension/resources/js/webextensions.js
@@ -5,7 +5,12 @@
// Browser async API
window.browser.alarms = {
+ clear: function (...args) { return ephy_message ('alarms.clear', args); },
clearAll: function (...args) { return ephy_message ('alarms.clearAll', args); },
+ create: function (...args) { return ephy_message ('alarms.create', args); },
+ get: function (...args) { return ephy_message ('alarms.get', args); },
+ getAll: function (...args) { return ephy_message ('alarms.getAll', args); },
+ onAlarm: new EphyEventListener (),
};
window.browser.windows = {
diff --git a/src/webextension/api/alarms.c b/src/webextension/api/alarms.c
new file mode 100644
index 000000000..0765bd17c
--- /dev/null
+++ b/src/webextension/api/alarms.c
@@ -0,0 +1,329 @@
+/* -*- 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/>.
+ */
+
+#include "config.h"
+
+#include <time.h>
+
+#include "ephy-embed-utils.h"
+#include "ephy-shell.h"
+#include "ephy-window.h"
+
+#include "storage.h"
+
+typedef struct {
+ EphyWebExtension *web_extension; /* Parent object */
+ char *name;
+ guint repeat_interval_ms;
+ double scheduled_time;
+ double repeat_interval_minutes;
+ guint timeout_id;
+} Alarm;
+
+static void
+alarm_destroy (Alarm *alarm)
+{
+ g_clear_handle_id (&alarm->timeout_id, g_source_remove);
+ g_clear_pointer (&alarm->name, g_free);
+ g_free (alarm);
+}
+
+static GHashTable *
+get_alarms (EphyWebExtension *extension)
+{
+ GHashTable *alarms = g_object_get_data (G_OBJECT (extension), "alarms");
+ if (alarms)
+ return alarms;
+
+ alarms = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)alarm_destroy);
+ g_object_set_data_full (G_OBJECT (extension), "alarms", alarms, (GDestroyNotify)g_hash_table_destroy);
+ return alarms;
+}
+
+static gdouble
+get_double_property (JSCValue *object,
+ const char *name)
+{
+ g_autoptr (JSCValue) value = jsc_value_object_get_property (object, name);
+ if (jsc_value_is_number (value))
+ return jsc_value_to_double (value);
+ return 0.0;
+}
+
+static guint64
+time_now_ms (void)
+{
+ struct timespec spec;
+ clock_gettime (CLOCK_REALTIME, &spec);
+ return (spec.tv_sec * 1000) + (spec.tv_nsec / 1.0e6);
+}
+
+static guint
+timestamp_to_ms (double timestamp)
+{
+ guint64 now_ms = time_now_ms ();
+
+ if (now_ms > timestamp)
+ return 0;
+
+ return (guint)(timestamp - now_ms);
+}
+
+static guint
+minutes_to_ms (double minutes)
+{
+ return (guint)(minutes * 60000);
+}
+
+static JsonNode *
+alarm_to_node (Alarm *alarm)
+{
+ JsonNode *node;
+ JsonObject *obj;
+
+ if (!alarm)
+ 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", alarm->name);
+ json_object_set_double_member (obj, "scheduledTime", alarm->scheduled_time);
+ if (alarm->repeat_interval_ms)
+ json_object_set_double_member (obj, "periodInMinutes", alarm->repeat_interval_minutes);
+
+ return node;
+}
+
+static char *
+alarm_to_json (Alarm *alarm)
+{
+ g_autoptr (JsonNode) node = alarm_to_node (alarm);
+ return json_to_string (node, FALSE);
+}
+
+static void
+emit_alarm (Alarm *alarm)
+{
+ EphyWebExtensionManager *manager = ephy_web_extension_manager_get_default ();
+ g_autofree char *json = alarm_to_json (alarm);
+
+ ephy_web_extension_manager_emit_in_extension_views (manager, alarm->web_extension, "alarms.onAlarm", json);
+}
+
+static gboolean
+on_alarm_repeat (gpointer user_data)
+{
+ emit_alarm (user_data);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+on_alarm_start (gpointer user_data)
+{
+ GHashTable *alarms;
+ Alarm *alarm = user_data;
+ alarm->timeout_id = 0;
+
+ /* Remove, but don't free, ourselves before we call the extension. */
+ if (!alarm->repeat_interval_ms) {
+ alarms = get_alarms (alarm->web_extension);
+ g_hash_table_steal (alarms, alarm->name);
+ }
+
+ emit_alarm (alarm);
+
+ if (alarm->repeat_interval_ms) {
+ alarm->timeout_id = g_timeout_add (alarm->repeat_interval_ms, on_alarm_repeat, alarm);
+ alarm->scheduled_time = (double)(time_now_ms () + alarm->repeat_interval_ms);
+ } else {
+ alarm_destroy (alarm);
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+static char *
+alarms_handler_create (EphyWebExtension *self,
+ char *name,
+ JSCValue *args,
+ GError **error)
+{
+ g_autoptr (JSCValue) alarm_name = NULL;
+ g_autoptr (JSCValue) alarm_info = NULL;
+ GHashTable *alarms = get_alarms (self);
+ Alarm *alarm;
+ double delay_in_minutes = 0.0;
+ double period_in_minutes = 0.0;
+ double when = 0.0;
+ g_autofree char *name_str = NULL;
+
+ /* This takes two optional args, name:str, info:obj */
+ alarm_name = jsc_value_object_get_property_at_index (args, 0);
+ if (jsc_value_is_string (alarm_name)) {
+ name_str = jsc_value_to_string (alarm_name);
+ alarm_info = jsc_value_object_get_property_at_index (args, 1);
+ } else {
+ name_str = g_strdup ("");
+ alarm_info = g_steal_pointer (&alarm_name);
+ }
+
+ if (jsc_value_is_object (alarm_info)) {
+ delay_in_minutes = get_double_property (alarm_info, "delayInMinutes");
+ period_in_minutes = get_double_property (alarm_info, "periodInMinutes");
+ when = get_double_property (alarm_info, "when");
+ }
+
+ if (delay_in_minutes && when) {
+ g_set_error (error, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_ARGUMENT, "Both 'when' and
'delayInMinutes' cannot be set");
+ return NULL;
+ }
+
+ alarm = g_new0 (Alarm, 1);
+ alarm->repeat_interval_ms = minutes_to_ms (period_in_minutes);
+ alarm->web_extension = self;
+ alarm->name = g_steal_pointer (&name_str);
+
+ if (delay_in_minutes) {
+ alarm->timeout_id = g_timeout_add (minutes_to_ms (delay_in_minutes), on_alarm_start, alarm);
+ alarm->scheduled_time = (double)(time_now_ms () + minutes_to_ms (delay_in_minutes));
+ } else if (when) {
+ alarm->timeout_id = g_timeout_add (timestamp_to_ms (when), on_alarm_start, alarm);
+ alarm->scheduled_time = when;
+ } else {
+ alarm->timeout_id = g_idle_add (on_alarm_start, alarm);
+ alarm->scheduled_time = (double)time_now_ms ();
+ }
+
+ g_hash_table_replace (alarms, alarm->name, alarm);
+
+ return NULL;
+}
+
+static char *
+alarms_handler_clear (EphyWebExtension *self,
+ char *name,
+ JSCValue *args,
+ GError **error)
+{
+ GHashTable *alarms = get_alarms (self);
+ g_autoptr (JSCValue) name_value = jsc_value_object_get_property_at_index (args, 0);
+ g_autofree char *name_str = NULL;
+
+ if (!jsc_value_is_string (name_value))
+ name_str = g_strdup ("");
+ else
+ name_str = jsc_value_to_string (name_value);
+
+ if (g_hash_table_remove (alarms, name_str))
+ return g_strdup ("true");
+
+ return g_strdup ("false");
+}
+
+static char *
+alarms_handler_clear_all (EphyWebExtension *self,
+ char *name,
+ JSCValue *args,
+ GError **error)
+{
+ GHashTable *alarms = get_alarms (self);
+
+ if (g_hash_table_size (alarms) == 0)
+ return g_strdup ("false");
+
+ g_hash_table_remove_all (alarms);
+ return g_strdup ("true");
+}
+
+static char *
+alarms_handler_get (EphyWebExtension *self,
+ char *name,
+ JSCValue *args,
+ GError **error)
+{
+ GHashTable *alarms = get_alarms (self);
+ g_autoptr (JSCValue) name_value = jsc_value_object_get_property_at_index (args, 0);
+ g_autofree char *name_str = NULL;
+ Alarm *alarm;
+
+ if (!jsc_value_is_string (name_value))
+ name_str = g_strdup ("");
+ else
+ name_str = jsc_value_to_string (name_value);
+
+ alarm = g_hash_table_lookup (alarms, name_str);
+ return alarm_to_json (alarm);
+}
+
+static char *
+alarms_handler_get_all (EphyWebExtension *self,
+ char *name,
+ JSCValue *args,
+ GError **error)
+{
+ GHashTable *alarms = get_alarms (self);
+ g_autoptr (JsonNode) node = json_node_init_array (json_node_alloc (), json_array_new ());
+ JsonArray *array = json_node_get_array (node);
+ GHashTableIter iter;
+ Alarm *alarm;
+
+ g_hash_table_iter_init (&iter, alarms);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&alarm))
+ json_array_add_element (array, alarm_to_node (alarm));
+
+ return json_to_string (node, FALSE);
+}
+
+static EphyWebExtensionSyncApiHandler alarms_handlers[] = {
+ {"clear", alarms_handler_clear},
+ {"clearAll", alarms_handler_clear_all},
+ {"create", alarms_handler_create},
+ {"get", alarms_handler_get},
+ {"getAll", alarms_handler_get_all},
+};
+
+void
+ephy_web_extension_api_alarms_handler (EphyWebExtension *self,
+ char *name,
+ JSCValue *args,
+ GTask *task)
+{
+ g_autoptr (GError) error = NULL;
+
+ for (guint idx = 0; idx < G_N_ELEMENTS (alarms_handlers); idx++) {
+ EphyWebExtensionSyncApiHandler handler = alarms_handlers[idx];
+ char *ret;
+
+ if (g_strcmp0 (handler.name, name) == 0) {
+ ret = handler.execute (self, name, args, &error);
+
+ if (error)
+ g_task_return_error (task, g_steal_pointer (&error));
+ else
+ g_task_return_pointer (task, ret, g_free);
+
+ return;
+ }
+ }
+
+ 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/alarms.h b/src/webextension/api/alarms.h
new file mode 100644
index 000000000..9c8d03c19
--- /dev/null
+++ b/src/webextension/api/alarms.h
@@ -0,0 +1,35 @@
+/* -*- 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_alarms_handler (EphyWebExtension *self,
+ char *name,
+ JSCValue *value,
+ GTask *task);
+
+G_END_DECLS
diff --git a/src/webextension/ephy-web-extension-manager.c b/src/webextension/ephy-web-extension-manager.c
index e30b2eee9..a1cae22cd 100644
--- a/src/webextension/ephy-web-extension-manager.c
+++ b/src/webextension/ephy-web-extension-manager.c
@@ -35,6 +35,7 @@
#include "ephy-web-extension-manager.h"
#include "ephy-web-view.h"
+#include "api/alarms.h"
#include "api/notifications.h"
#include "api/pageaction.h"
#include "api/runtime.h"
@@ -50,12 +51,15 @@ struct _EphyWebExtensionManager {
GList *web_extensions;
GHashTable *page_action_map;
GHashTable *browser_action_map;
+
GHashTable *background_web_views;
+ GHashTable *popup_web_views;
};
G_DEFINE_TYPE (EphyWebExtensionManager, ephy_web_extension_manager, G_TYPE_OBJECT)
EphyWebExtensionAsyncApiHandler api_handlers[] = {
+ {"alarms", ephy_web_extension_api_alarms_handler},
{"notifications", ephy_web_extension_api_notifications_handler},
{"pageAction", ephy_web_extension_api_pageaction_handler},
{"runtime", ephy_web_extension_api_runtime_handler},
@@ -202,6 +206,7 @@ ephy_web_extension_manager_constructed (GObject *object)
g_autofree char *dir = g_build_filename (ephy_default_profile_dir (), "web_extensions", NULL);
self->background_web_views = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
(GDestroyNotify)gtk_widget_destroy);
+ self->popup_web_views = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
(GDestroyNotify)g_ptr_array_free);
self->page_action_map = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_hash_table_destroy);
self->browser_action_map = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)destroy_widget_list);
self->web_extensions = NULL;
@@ -215,6 +220,7 @@ ephy_web_extension_manager_dispose (GObject *object)
EphyWebExtensionManager *self = EPHY_WEB_EXTENSION_MANAGER (object);
g_clear_pointer (&self->background_web_views, g_hash_table_destroy);
+ g_clear_pointer (&self->popup_web_views, g_hash_table_destroy);
g_clear_pointer (&self->page_action_map, g_hash_table_destroy);
g_list_free_full (g_steal_pointer (&self->web_extensions), g_object_unref);
}
@@ -776,9 +782,37 @@ on_popup_load_changed (WebKitWebView *web_view,
gtk_widget_show (GTK_WIDGET (web_view));
}
+static void
+on_popup_view_destroyed (GtkWidget *widget,
+ gpointer user_data)
+{
+ EphyWebExtension *web_extension = user_data;
+ EphyWebExtensionManager *manager = ephy_web_extension_manager_get_default ();
+ GPtrArray *popup_views = g_hash_table_lookup (manager->popup_web_views, web_extension);
+
+ g_assert (g_ptr_array_remove_fast (popup_views, widget));
+}
+
+static void
+ephy_web_extension_manager_register_popup_view (EphyWebExtensionManager *manager,
+ EphyWebExtension *web_extension,
+ GtkWidget *web_view)
+{
+ GPtrArray *popup_views = g_hash_table_lookup (manager->popup_web_views, web_extension);
+
+ if (!popup_views) {
+ popup_views = g_ptr_array_new ();
+ g_hash_table_insert (manager->popup_web_views, web_extension, popup_views);
+ }
+
+ g_ptr_array_add (popup_views, web_view);
+ g_signal_connect (web_view, "destroy", G_CALLBACK (on_popup_view_destroyed), web_extension);
+}
+
static GtkWidget *
create_browser_popup (EphyWebExtension *web_extension)
{
+ EphyWebExtensionManager *manager = ephy_web_extension_manager_get_default ();
GtkWidget *web_view;
g_autofree char *data = NULL;
g_autofree char *base_uri = NULL;
@@ -791,6 +825,8 @@ create_browser_popup (EphyWebExtension *web_extension)
web_view = create_web_extensions_webview (web_extension);
gtk_widget_hide (web_view); /* Shown in on_popup_load_changed. */
+ ephy_web_extension_manager_register_popup_view (manager, web_extension, web_view);
+
popup = ephy_web_extension_get_browser_popup (web_extension);
base_uri = create_base_uri_for_resource_path (web_extension, popup);
data = ephy_web_extension_get_resource_as_string (web_extension, popup);
@@ -1087,6 +1123,7 @@ ephy_web_extension_manager_set_active (EphyWebExtensionManager *self,
} else {
g_hash_table_remove (self->browser_action_map, web_extension);
g_hash_table_remove (self->background_web_views, web_extension);
+ g_object_set_data (G_OBJECT (web_extension), "alarms", NULL); /* Set in alarms.c */
}
}
@@ -1104,3 +1141,33 @@ ephy_web_extension_manager_get_page_action (EphyWebExtensionManager *self,
return ret;
}
+
+void
+ephy_web_extension_manager_emit_in_extension_views (EphyWebExtensionManager *self,
+ EphyWebExtension *web_extension,
+ const char *name,
+ const char *json)
+{
+ WebKitWebView *background_view = ephy_web_extension_manager_get_background_web_view (self, web_extension);
+ GPtrArray *popup_views = g_hash_table_lookup (self->popup_web_views, web_extension);
+ g_autofree char *script = g_strdup_printf ("window.browser.%s._emit(%s);", name, json);
+
+ if (background_view) {
+ webkit_web_view_run_javascript (background_view,
+ script,
+ NULL,
+ NULL,
+ NULL);
+ }
+
+ if (popup_views) {
+ for (guint i = 0; i < popup_views->len; i++) {
+ WebKitWebView *popup_view = g_ptr_array_index (popup_views, i);
+ webkit_web_view_run_javascript (popup_view,
+ script,
+ NULL,
+ NULL,
+ NULL);
+ }
+ }
+}
diff --git a/src/webextension/meson.build b/src/webextension/meson.build
index 5656e0b6e..671ded29b 100644
--- a/src/webextension/meson.build
+++ b/src/webextension/meson.build
@@ -1,4 +1,5 @@
ephywebextension_src = [
+ 'webextension/api/alarms.c',
'webextension/api/notifications.c',
'webextension/api/pageaction.c',
'webextension/api/runtime.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]