[epiphany] overview: Use the new JavaScriptCore GLib API instead of DOM API



commit 095d4b09a7fdd3b314f2f0b2ed58c4617c4a1a82
Author: Carlos Garcia Campos <cgarcia igalia com>
Date:   Tue Mar 27 16:59:41 2018 +0200

    overview: Use the new JavaScriptCore GLib API instead of DOM API
    
    https://bugzilla.gnome.org/show_bug.cgi?id=794395

 embed/ephy-about-handler.c                         |   21 +-
 embed/ephy-embed-shell.c                           |   38 --
 embed/web-extension/ephy-web-extension.c           |   60 ++-
 embed/web-extension/ephy-web-overview-model.c      |  311 +++++++++++---
 embed/web-extension/ephy-web-overview-model.h      |   10 +-
 embed/web-extension/ephy-web-overview.c            |  451 --------------------
 embed/web-extension/ephy-web-overview.h            |   38 --
 embed/web-extension/meson.build                    |    1 -
 .../resources/epiphany-web-extension.gresource.xml |    1 +
 embed/web-extension/resources/js/overview.js       |  225 ++++++++++
 10 files changed, 528 insertions(+), 628 deletions(-)
---
diff --git a/embed/ephy-about-handler.c b/embed/ephy-about-handler.c
index 8459c78..1331bcc 100644
--- a/embed/ephy-about-handler.c
+++ b/embed/ephy-about-handler.c
@@ -437,22 +437,7 @@ history_service_query_urls_cb (EphyHistoryService     *history,
                           "  <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n"
                           "  <meta name=\"viewport\" content=\"width=device-width\">"
                           "  <link href=\""EPHY_PAGE_TEMPLATE_ABOUT_CSS "\" rel=\"stylesheet\" 
type=\"text/css\">\n"
-                          "  <script>\n"
-                          "    document.onkeypress = function listenKeypress(event) {\n"
-                          "      // Remove from overview when Del is pressed\n"
-                          "      if (event.which == 127) {\n"
-                          "        var focused = document.activeElement;\n"
-                          "        if (focused.className == \"overview-item\") {\n"
-                          "          removeFromOverview(focused, event);\n"
-                          "        }\n"
-                          "      }\n"
-                          "    }\n"
-                          "    function removeFromOverview(elem, event) {\n"
-                          "      event.preventDefault();\n"
-                          "      elem.className +=\" overview-removed \";\n"
-                          "      window.webkit.messageHandlers.overview.postMessage(elem.href);\n"
-                          "    }\n"
-                          "  </script>\n"
+                          "  <script> </script>\n"
                           "</head>\n"
                           "<body>\n",
                           lang, lang,
@@ -468,7 +453,7 @@ history_service_query_urls_cb (EphyHistoryService     *history,
                                             128,
                                             0);
     g_string_append_printf (data_str,
-                            "  <div class=\"overview-empty\">\n"
+                            "  <div id=\"overview\" class=\"overview-empty\">\n"
                             "    <img src=\"file://%s\"/>\n"
                             "    <div><h1>%s</h1></div>\n"
                             "    <div><p>%s</p></div>\n"
@@ -498,7 +483,7 @@ history_service_query_urls_cb (EphyHistoryService     *history,
 
     g_string_append_printf (data_str,
                             "<a class=\"overview-item\" title=\"%s\" href=\"%s\">"
-                            "  <div class=\"overview-close-button\" 
onclick=\"removeFromOverview(this.parentNode, event)\" title=\"%s\">&#10006;</div>"
+                            "  <div class=\"overview-close-button\" title=\"%s\">&#10006;</div>"
                             "  <span class=\"overview-thumbnail\"%s></span>"
                             "  <span class=\"overview-title\">%s</span>"
                             "</a>",
diff --git a/embed/ephy-embed-shell.c b/embed/ephy-embed-shell.c
index fb3767f..e39655d 100644
--- a/embed/ephy-embed-shell.c
+++ b/embed/ephy-embed-shell.c
@@ -49,7 +49,6 @@
 
 #define PAGE_SETUP_FILENAME "page-setup-gtk.ini"
 #define PRINT_SETTINGS_FILENAME "print-settings.ini"
-#define OVERVIEW_RELOAD_DELAY 500
 
 typedef struct {
   WebKitWebContext *web_context;
@@ -63,8 +62,6 @@ typedef struct {
   EphyDownloadsManager *downloads_manager;
   EphyPermissionsManager *permissions_manager;
   EphyAboutHandler *about_handler;
-  guint update_overview_timeout_id;
-  guint hiding_overview_item;
   GDBusServer *dbus_server;
   GList *web_extensions;
   EphyFiltersManager *filters_manager;
@@ -159,11 +156,6 @@ ephy_embed_shell_dispose (GObject *object)
     g_clear_object (&priv->cancellable);
   }
 
-  if (priv->update_overview_timeout_id > 0) {
-    g_source_remove (priv->update_overview_timeout_id);
-    priv->update_overview_timeout_id = 0;
-  }
-
   g_clear_object (&priv->encodings);
   g_clear_object (&priv->page_setup);
   g_clear_object (&priv->print_settings);
@@ -275,37 +267,15 @@ history_service_urls_visited_cb (EphyHistoryService *history,
   ephy_embed_shell_update_overview_urls (shell);
 }
 
-static gboolean
-ephy_embed_shell_update_overview_timeout_cb (EphyEmbedShell *shell)
-{
-  EphyEmbedShellPrivate *priv = ephy_embed_shell_get_instance_private (shell);
-
-  priv->update_overview_timeout_id = 0;
-
-  if (priv->hiding_overview_item > 0)
-    return FALSE;
-
-  ephy_embed_shell_update_overview_urls (shell);
-
-  return FALSE;
-}
-
 static void
 history_set_url_hidden_cb (EphyHistoryService *service,
                            gboolean            success,
                            gpointer            result_data,
                            EphyEmbedShell     *shell)
 {
-  EphyEmbedShellPrivate *priv = ephy_embed_shell_get_instance_private (shell);
-
-  priv->hiding_overview_item--;
-
   if (!success)
     return;
 
-  if (priv->update_overview_timeout_id > 0)
-    return;
-
   ephy_embed_shell_update_overview_urls (shell);
 }
 
@@ -319,19 +289,11 @@ web_extension_overview_message_received_cb (WebKitUserContentManager *manager,
 
   url_to_remove = jsc_value_to_string (webkit_javascript_result_get_js_value (message));
 
-  priv->hiding_overview_item++;
   ephy_history_service_set_url_hidden (priv->global_history_service,
                                        url_to_remove, TRUE, NULL,
                                        (EphyHistoryJobCallback)history_set_url_hidden_cb,
                                        shell);
   g_free (url_to_remove);
-
-  if (priv->update_overview_timeout_id > 0)
-    g_source_remove (priv->update_overview_timeout_id);
-
-  /* Wait for the CSS animations to finish before refreshing */
-  priv->update_overview_timeout_id =
-    g_timeout_add (OVERVIEW_RELOAD_DELAY, (GSourceFunc)ephy_embed_shell_update_overview_timeout_cb, shell);
 }
 
 static void
diff --git a/embed/web-extension/ephy-web-extension.c b/embed/web-extension/ephy-web-extension.c
index 3987cd6..3e3db73 100644
--- a/embed/web-extension/ephy-web-extension.c
+++ b/embed/web-extension/ephy-web-extension.c
@@ -33,9 +33,10 @@
 #include "ephy-sync-utils.h"
 #include "ephy-uri-helpers.h"
 #include "ephy-uri-tester.h"
-#include "ephy-web-overview.h"
+#include "ephy-web-overview-model.h"
 
 #include <gio/gio.h>
+#include <glib/gi18n.h>
 #include <gtk/gtk.h>
 #include <jsc/jsc.h>
 #include <libsoup/soup.h>
@@ -393,19 +394,6 @@ web_page_form_controls_associated (WebKitWebPage    *web_page,
   g_object_unref (js_context);
 }
 
-static void
-web_page_uri_changed (WebKitWebPage    *web_page,
-                      GParamSpec       *param_spec,
-                      EphyWebExtension *extension)
-{
-  EphyWebOverview *overview = NULL;
-
-  if (g_strcmp0 (webkit_web_page_get_uri (web_page), "ephy-about:overview") == 0)
-    overview = ephy_web_overview_new (web_page, extension->overview_model);
-
-  g_object_set_data_full (G_OBJECT (web_page), "ephy-web-overview", overview, g_object_unref);
-}
-
 static gboolean
 web_page_context_menu (WebKitWebPage          *web_page,
                        WebKitContextMenu      *context_menu,
@@ -505,9 +493,6 @@ ephy_web_extension_page_created_cb (EphyWebExtension *extension,
   g_signal_connect (web_page, "send-request",
                     G_CALLBACK (web_page_send_request),
                     extension);
-  g_signal_connect (web_page, "notify::uri",
-                    G_CALLBACK (web_page_uri_changed),
-                    extension);
   g_signal_connect (web_page, "context-menu",
                     G_CALLBACK (web_page_context_menu),
                     extension);
@@ -583,7 +568,7 @@ handle_method_call (GDBusConnection       *connection,
       const char *path;
 
       g_variant_get (parameters, "(&s&s)", &url, &path);
-      ephy_web_overview_model_set_url_thumbnail (extension->overview_model, url, path);
+      ephy_web_overview_model_set_url_thumbnail (extension->overview_model, url, path, TRUE);
     }
     g_dbus_method_invocation_return_value (invocation, NULL);
   } else if (g_strcmp0 (method_name, "HistorySetURLTitle") == 0) {
@@ -808,6 +793,12 @@ js_log (const char *message)
   LOG ("%s", message);
 }
 
+static char *
+js_gettext (const char *message)
+{
+  return g_strdup (g_dgettext (GETTEXT_PACKAGE, message));
+}
+
 static void
 js_auto_fill (JSCValue   *js_element,
               const char *value)
@@ -883,6 +874,39 @@ window_object_cleared_cb (WebKitScriptWorld *world,
   jsc_value_object_set_property (js_ephy, "log", js_function);
   g_object_unref (js_function);
 
+  js_function = jsc_value_new_function (js_context,
+                                        "gettext",
+                                        G_CALLBACK (js_gettext), NULL, NULL,
+                                        G_TYPE_STRING, 1,
+                                        G_TYPE_STRING);
+  jsc_value_object_set_property (js_ephy, "_", js_function);
+  g_object_unref (js_function);
+
+  if (g_strcmp0 (webkit_web_page_get_uri (page), "ephy-about:overview") == 0) {
+    JSCValue *js_overview;
+    JSCValue *js_overview_ctor;
+    JSCValue *jsc_overview_model;
+
+    bytes = g_resources_lookup_data ("/org/gnome/epiphany-web-extension/js/overview.js", 
G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
+    data = g_bytes_get_data (bytes, &data_size);
+    result = jsc_context_evaluate_with_source_uri (js_context, data, data_size, 
"resource:///org/gnome/epiphany-web-extension/js/overview.js");
+    g_bytes_unref (bytes);
+    g_object_unref (result);
+
+    jsc_overview_model = ephy_web_overview_model_export_to_js_context (extension->overview_model,
+                                                                       js_context);
+
+    js_overview_ctor = jsc_value_object_get_property (js_ephy, "Overview");
+    js_overview = jsc_value_constructor_call (js_overview_ctor,
+                                              JSC_TYPE_VALUE, jsc_overview_model,
+                                              G_TYPE_NONE);
+    jsc_value_object_set_property (js_ephy, "overview", js_overview);
+
+    g_object_unref (js_overview);
+    g_object_unref (jsc_overview_model);
+    g_object_unref (js_overview_ctor);
+  }
+
   ephy_permissions_manager_export_to_js_context (extension->permissions_manager,
                                                  js_context,
                                                  js_ephy);
diff --git a/embed/web-extension/ephy-web-overview-model.c b/embed/web-extension/ephy-web-overview-model.c
index 606e213..3acd921 100644
--- a/embed/web-extension/ephy-web-overview-model.c
+++ b/embed/web-extension/ephy-web-overview-model.c
@@ -28,19 +28,13 @@ struct _EphyWebOverviewModel {
 
   GList *items;
   GHashTable *thumbnails;
-};
-
-G_DEFINE_TYPE (EphyWebOverviewModel, ephy_web_overview_model, G_TYPE_OBJECT)
 
-enum {
-  URLS_CHANGED,
-  THUMBNAIL_CHANGED,
-  TITLE_CHANGED,
-
-  LAST_SIGNAL
+  GHashTable *urls_listeners;
+  GHashTable *thumbnail_listeners;
+  GHashTable *title_listeners;
 };
 
-static guint signals[LAST_SIGNAL];
+G_DEFINE_TYPE (EphyWebOverviewModel, ephy_web_overview_model, G_TYPE_OBJECT)
 
 static void
 ephy_web_overview_model_dispose (GObject *object)
@@ -57,6 +51,10 @@ ephy_web_overview_model_dispose (GObject *object)
     model->thumbnails = NULL;
   }
 
+  g_clear_pointer (&model->urls_listeners, g_hash_table_destroy);
+  g_clear_pointer (&model->thumbnail_listeners, g_hash_table_destroy);
+  g_clear_pointer (&model->title_listeners, g_hash_table_destroy);
+
   G_OBJECT_CLASS (ephy_web_overview_model_parent_class)->dispose (object);
 }
 
@@ -66,31 +64,6 @@ ephy_web_overview_model_class_init (EphyWebOverviewModelClass *klass)
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
   object_class->dispose = ephy_web_overview_model_dispose;
-
-  signals[URLS_CHANGED] =
-    g_signal_new ("urls-changed",
-                  EPHY_TYPE_WEB_OVERVIEW_MODEL,
-                  G_SIGNAL_RUN_LAST,
-                  0, NULL, NULL, NULL,
-                  G_TYPE_NONE, 0);
-
-  signals[THUMBNAIL_CHANGED] =
-    g_signal_new ("thumbnail-changed",
-                  EPHY_TYPE_WEB_OVERVIEW_MODEL,
-                  G_SIGNAL_RUN_LAST,
-                  0, NULL, NULL, NULL,
-                  G_TYPE_NONE, 2,
-                  G_TYPE_STRING,
-                  G_TYPE_STRING);
-
-  signals[TITLE_CHANGED] =
-    g_signal_new ("title-changed",
-                  EPHY_TYPE_WEB_OVERVIEW_MODEL,
-                  G_SIGNAL_RUN_LAST,
-                  0, NULL, NULL, NULL,
-                  G_TYPE_NONE, 2,
-                  G_TYPE_STRING,
-                  G_TYPE_STRING);
 }
 
 static void
@@ -100,6 +73,112 @@ ephy_web_overview_model_init (EphyWebOverviewModel *model)
                                              g_str_equal,
                                              (GDestroyNotify)g_free,
                                              (GDestroyNotify)g_free);
+  model->urls_listeners = g_hash_table_new_full (g_direct_hash,
+                                                 g_direct_equal,
+                                                 g_object_unref,
+                                                 NULL);
+  model->thumbnail_listeners = g_hash_table_new_full (g_direct_hash,
+                                                      g_direct_equal,
+                                                      g_object_unref,
+                                                      NULL);
+  model->title_listeners = g_hash_table_new_full (g_direct_hash,
+                                                  g_direct_equal,
+                                                  g_object_unref,
+                                                  NULL);
+}
+
+static GPtrArray *
+ephy_web_overview_model_urls_to_js_value (EphyWebOverviewModel *model,
+                                          JSCContext           *js_context)
+{
+  GPtrArray *urls;
+  GList *l;
+
+  urls = g_ptr_array_new_with_free_func (g_object_unref);
+  for (l = model->items; l; l = g_list_next (l)) {
+    EphyWebOverviewModelItem *item = (EphyWebOverviewModelItem *)l->data;
+    JSCValue *js_item, *value;
+
+    js_item = jsc_value_new_object (js_context, NULL, NULL);
+    value = jsc_value_new_string (js_context, item->url);
+    jsc_value_object_set_property (js_item, "url", value);
+    g_object_unref (value);
+
+    value = jsc_value_new_string (js_context, item->title);
+    jsc_value_object_set_property (js_item, "title", value);
+    g_object_unref (value);
+
+    g_ptr_array_add (urls, js_item);
+  }
+
+  return urls;
+}
+
+static void
+ephy_web_overview_model_notify_urls_changed (EphyWebOverviewModel *model)
+{
+  GHashTableIter iter;
+  gpointer key;
+  GPtrArray *urls = NULL;
+
+  g_hash_table_iter_init (&iter, model->urls_listeners);
+  while (g_hash_table_iter_next (&iter, &key, NULL)) {
+    JSCValue *value;
+
+    value = jsc_weak_value_get_value (JSC_WEAK_VALUE (key));
+    if (value && jsc_value_is_function (value)) {
+      if (!urls)
+        urls = ephy_web_overview_model_urls_to_js_value (model, jsc_value_get_context (value));
+      jsc_value_function_call (value, G_TYPE_PTR_ARRAY, urls, G_TYPE_NONE);
+    }
+    if (value)
+      g_object_unref (value);
+  }
+
+  if (urls)
+    g_ptr_array_unref (urls);
+}
+
+static void
+ephy_web_overview_model_notify_thumbnail_changed (EphyWebOverviewModel *model,
+                                                  const char           *url,
+                                                  const char           *path)
+{
+  GHashTableIter iter;
+  gpointer key;
+
+  g_hash_table_iter_init (&iter, model->thumbnail_listeners);
+  while (g_hash_table_iter_next (&iter, &key, NULL)) {
+    JSCValue *value;
+
+    value = jsc_weak_value_get_value (JSC_WEAK_VALUE (key));
+    if (value) {
+      if (jsc_value_is_function (value))
+        jsc_value_function_call (value, G_TYPE_STRING, url, G_TYPE_STRING, path, G_TYPE_NONE);
+      g_object_unref (value);
+    }
+  }
+}
+
+static void
+ephy_web_overview_model_notify_title_changed (EphyWebOverviewModel *model,
+                                              const char           *url,
+                                              const char           *title)
+{
+  GHashTableIter iter;
+  gpointer key;
+
+  g_hash_table_iter_init (&iter, model->title_listeners);
+  while (g_hash_table_iter_next (&iter, &key, NULL)) {
+    JSCValue *value;
+
+    value = jsc_weak_value_get_value (JSC_WEAK_VALUE (key));
+    if (value) {
+      if (jsc_value_is_function (value))
+        jsc_value_function_call (value, G_TYPE_STRING, url, G_TYPE_STRING, title, G_TYPE_NONE);
+      g_object_unref (value);
+    }
+  }
 }
 
 EphyWebOverviewModel *
@@ -116,41 +195,26 @@ ephy_web_overview_model_set_urls (EphyWebOverviewModel *model,
 
   g_list_free_full (model->items, (GDestroyNotify)ephy_web_overview_model_item_free);
   model->items = urls;
-  g_signal_emit (model, signals[URLS_CHANGED], 0);
-}
-
-GList *
-ephy_web_overview_model_get_urls (EphyWebOverviewModel *model)
-{
-  g_assert (EPHY_IS_WEB_OVERVIEW_MODEL (model));
-
-  return model->items;
+  ephy_web_overview_model_notify_urls_changed (model);
 }
 
 void
 ephy_web_overview_model_set_url_thumbnail (EphyWebOverviewModel *model,
                                            const char           *url,
-                                           const char           *path)
+                                           const char           *path,
+                                           gboolean              notify)
 {
   const char *thumbnail_path;
 
   g_assert (EPHY_IS_WEB_OVERVIEW_MODEL (model));
 
-  thumbnail_path = ephy_web_overview_model_get_url_thumbnail (model, url);
+  thumbnail_path = g_hash_table_lookup (model->thumbnails, url);
   if (g_strcmp0 (thumbnail_path, path) == 0)
     return;
 
   g_hash_table_insert (model->thumbnails, g_strdup (url), g_strdup (path));
-  g_signal_emit (model, signals[THUMBNAIL_CHANGED], 0, url, path);
-}
-
-const char *
-ephy_web_overview_model_get_url_thumbnail (EphyWebOverviewModel *model,
-                                           const char           *url)
-{
-  g_assert (EPHY_IS_WEB_OVERVIEW_MODEL (model));
-
-  return g_hash_table_lookup (model->thumbnails, url);
+  if (notify)
+    ephy_web_overview_model_notify_thumbnail_changed (model, url, path);
 }
 
 void
@@ -178,7 +242,7 @@ ephy_web_overview_model_set_url_title (EphyWebOverviewModel *model,
   }
 
   if (changed)
-    g_signal_emit (model, signals[TITLE_CHANGED], 0, url, title);
+    ephy_web_overview_model_notify_title_changed (model, url, title);
 }
 
 void
@@ -206,7 +270,7 @@ ephy_web_overview_model_delete_url (EphyWebOverviewModel *model,
   }
 
   if (changed)
-    g_signal_emit (model, signals[URLS_CHANGED], 0);
+    ephy_web_overview_model_notify_urls_changed (model);
 }
 
 void
@@ -236,7 +300,7 @@ ephy_web_overview_model_delete_host (EphyWebOverviewModel *model,
   }
 
   if (changed)
-    g_signal_emit (model, signals[URLS_CHANGED], 0);
+    ephy_web_overview_model_notify_urls_changed (model);
 }
 
 void
@@ -249,7 +313,7 @@ ephy_web_overview_model_clear (EphyWebOverviewModel *model)
 
   g_list_free_full (model->items, (GDestroyNotify)ephy_web_overview_model_item_free);
   model->items = NULL;
-  g_signal_emit (model, signals[URLS_CHANGED], 0);
+  ephy_web_overview_model_notify_urls_changed (model);
 }
 
 EphyWebOverviewModelItem *
@@ -276,3 +340,130 @@ ephy_web_overview_model_item_free (EphyWebOverviewModelItem *item)
 
   g_slice_free (EphyWebOverviewModelItem, item);
 }
+
+static void
+js_web_overview_model_set_thumbnail (EphyWebOverviewModel *model,
+                                     const char           *url,
+                                     const char           *path)
+{
+  ephy_web_overview_model_set_url_thumbnail (model, url, path, FALSE);
+}
+
+static char *
+js_web_overview_model_get_thumbnail (EphyWebOverviewModel *model,
+                                     const char           *url)
+{
+  return g_strdup (g_hash_table_lookup (model->thumbnails, url));
+}
+
+static GPtrArray *
+js_web_overview_model_get_urls (EphyWebOverviewModel *model)
+{
+  return ephy_web_overview_model_urls_to_js_value (model, jsc_context_get_current ());
+}
+
+static void
+js_event_listener_destroyed (JSCWeakValue *weak_value,
+                             GHashTable   *listeners)
+{
+  g_hash_table_remove (listeners, weak_value);
+}
+
+static void
+js_web_overview_model_add_urls_changed_event_listener (EphyWebOverviewModel *model,
+                                                       JSCValue             *js_function)
+{
+  JSCWeakValue *weak_value;
+
+  if (!jsc_value_is_function (js_function)) {
+    jsc_context_throw (jsc_context_get_current (), "Invalid type passed to onurlschanged");
+    return;
+  }
+
+  weak_value = jsc_weak_value_new (js_function);
+  g_signal_connect (weak_value, "cleared",
+                    G_CALLBACK (js_event_listener_destroyed),
+                    model->urls_listeners);
+  g_hash_table_add (model->urls_listeners, weak_value);
+}
+
+static void
+js_web_overview_model_add_thumbnail_changed_event_listener (EphyWebOverviewModel *model,
+                                                            JSCValue             *js_function)
+{
+  JSCWeakValue *weak_value;
+
+  if (!jsc_value_is_function (js_function)) {
+    jsc_context_throw (jsc_context_get_current (), "Invalid type passed to onthumbnailchanged");
+    return;
+  }
+
+  weak_value = jsc_weak_value_new (js_function);
+  g_signal_connect (weak_value, "cleared",
+                    G_CALLBACK (js_event_listener_destroyed),
+                    model->thumbnail_listeners);
+  g_hash_table_add (model->thumbnail_listeners, weak_value);
+}
+
+static void
+js_web_overview_model_add_title_changed_event_listener (EphyWebOverviewModel *model,
+                                                        JSCValue             *js_function)
+{
+  JSCWeakValue *weak_value;
+
+  if (!jsc_value_is_function (js_function)) {
+    jsc_context_throw (jsc_context_get_current (), "Invalid type passed to ontitlechanged");
+    return;
+  }
+
+  weak_value = jsc_weak_value_new (js_function);
+  g_signal_connect (weak_value, "cleared",
+                    G_CALLBACK (js_event_listener_destroyed),
+                    model->title_listeners);
+  g_hash_table_add (model->title_listeners, weak_value);
+}
+
+JSCValue *
+ephy_web_overview_model_export_to_js_context (EphyWebOverviewModel *model,
+                                              JSCContext           *js_context)
+{
+  JSCClass *js_class;
+
+  js_class = jsc_context_register_class (js_context, "OverviewModel", NULL, NULL, NULL);
+  jsc_class_add_method (js_class,
+                        "setThumbnail",
+                        G_CALLBACK (js_web_overview_model_set_thumbnail), NULL, NULL,
+                        G_TYPE_NONE, 2,
+                        G_TYPE_STRING, G_TYPE_STRING);
+  jsc_class_add_method (js_class,
+                        "getThumbnail",
+                        G_CALLBACK (js_web_overview_model_get_thumbnail), NULL, NULL,
+                        G_TYPE_STRING, 1,
+                        G_TYPE_STRING);
+  jsc_class_add_property (js_class,
+                          "urls",
+                          G_TYPE_PTR_ARRAY,
+                          G_CALLBACK (js_web_overview_model_get_urls),
+                          NULL,
+                          NULL, NULL);
+  jsc_class_add_property (js_class,
+                          "onurlschanged",
+                          JSC_TYPE_VALUE,
+                          NULL,
+                          G_CALLBACK (js_web_overview_model_add_urls_changed_event_listener),
+                          NULL, NULL);
+  jsc_class_add_property (js_class,
+                          "onthumbnailchanged",
+                          JSC_TYPE_VALUE,
+                          NULL,
+                          G_CALLBACK (js_web_overview_model_add_thumbnail_changed_event_listener),
+                          NULL, NULL);
+  jsc_class_add_property (js_class,
+                          "ontitlechanged",
+                          JSC_TYPE_VALUE,
+                          NULL,
+                          G_CALLBACK (js_web_overview_model_add_title_changed_event_listener),
+                          NULL, NULL);
+
+  return jsc_value_new_object (js_context, model, js_class);
+}
diff --git a/embed/web-extension/ephy-web-overview-model.h b/embed/web-extension/ephy-web-overview-model.h
index 5fb6a6d..df8dcd8 100644
--- a/embed/web-extension/ephy-web-overview-model.h
+++ b/embed/web-extension/ephy-web-overview-model.h
@@ -21,6 +21,7 @@
 #pragma once
 
 #include <glib-object.h>
+#include <jsc/jsc.h>
 
 G_BEGIN_DECLS
 
@@ -31,12 +32,10 @@ G_DECLARE_FINAL_TYPE (EphyWebOverviewModel, ephy_web_overview_model, EPHY, WEB_O
 EphyWebOverviewModel *ephy_web_overview_model_new               (void);
 void                  ephy_web_overview_model_set_urls          (EphyWebOverviewModel *model,
                                                                  GList                *urls);
-GList                *ephy_web_overview_model_get_urls          (EphyWebOverviewModel *model);
 void                  ephy_web_overview_model_set_url_thumbnail (EphyWebOverviewModel *model,
                                                                  const char           *url,
-                                                                 const char           *path);
-const char           *ephy_web_overview_model_get_url_thumbnail (EphyWebOverviewModel *model,
-                                                                 const char           *url);
+                                                                 const char           *path,
+                                                                 gboolean              notify);
 void                  ephy_web_overview_model_set_url_title     (EphyWebOverviewModel *model,
                                                                  const char           *url,
                                                                  const char           *title);
@@ -58,4 +57,7 @@ EphyWebOverviewModelItem *ephy_web_overview_model_item_new  (const char
                                                              const char               *title);
 void                      ephy_web_overview_model_item_free (EphyWebOverviewModelItem *item);
 
+JSCValue *ephy_web_overview_model_export_to_js_context  (EphyWebOverviewModel *model,
+                                                         JSCContext           *js_context);
+
 G_END_DECLS
diff --git a/embed/web-extension/meson.build b/embed/web-extension/meson.build
index 06368d9..ad2b272 100644
--- a/embed/web-extension/meson.build
+++ b/embed/web-extension/meson.build
@@ -9,7 +9,6 @@ web_extension_sources = [
   'ephy-uri-tester.c',
   'ephy-web-extension.c',
   'ephy-web-extension-main.c',
-  'ephy-web-overview.c',
   'ephy-web-overview-model.c',
   resources
 ]
diff --git a/embed/web-extension/resources/epiphany-web-extension.gresource.xml 
b/embed/web-extension/resources/epiphany-web-extension.gresource.xml
index 657d576..97166cf 100644
--- a/embed/web-extension/resources/epiphany-web-extension.gresource.xml
+++ b/embed/web-extension/resources/epiphany-web-extension.gresource.xml
@@ -2,5 +2,6 @@
 <gresources>
   <gresource prefix="/org/gnome/epiphany-web-extension">
     <file compressed="true">js/ephy.js</file>
+    <file compressed="true">js/overview.js</file>
   </gresource>
 </gresources>
diff --git a/embed/web-extension/resources/js/overview.js b/embed/web-extension/resources/js/overview.js
new file mode 100644
index 0000000..ced2ee6
--- /dev/null
+++ b/embed/web-extension/resources/js/overview.js
@@ -0,0 +1,225 @@
+Ephy.Overview = class Overview
+{
+    constructor(model)
+    {
+        this._model = model;
+        this._items = [];
+
+        // Event handlers are weak references in EphyWebOverviewModel, we need to keep
+        // a strong reference to them while Ephy.Overview is alive.
+        this._onURLsChangedFunction = this._onURLsChanged.bind(this);
+        this._model.onurlschanged = this._onURLsChangedFunction;
+        this._onThumbnailChangedFunction = this._onThumbnailChanged.bind(this);
+        this._model.onthumbnailchanged = this._onThumbnailChangedFunction;
+        this._onTitleChangedFunction = this._onTitleChanged.bind(this);
+        this._model.ontitlechanged = this._onTitleChangedFunction;
+        document.addEventListener('DOMContentLoaded', this._initialize.bind(this), false);
+        document.addEventListener('keypress', this._onKeyPress.bind(this), false);
+    }
+
+    // Private
+
+    _initialize()
+    {
+        let anchors = document.getElementsByTagName('a');
+        for (let i = 0; i < anchors.length; i++) {
+            let anchor = anchors[i];
+            if (anchor.className != 'overview-item')
+                continue;
+
+            let item = new Ephy.Overview.Item(anchor);
+
+            let closeButton = anchor.getElementsByClassName('overview-close-button')[0];
+            closeButton.onclick = (event) => {
+                this._removeItem(anchor);
+                event.preventDefault();
+            };
+
+            // URLs and titles are always sent from the UI process, but thumbnails
+            // aren't, so update the model with the thumbnail if there's one.
+            let thumbnailPath = item.thumbnailPath();
+            if (thumbnailPath)
+                this._model.setThumbnail(item.url(), thumbnailPath);
+            else
+                item.setThumbnailPath(this._model.getThumbnail(item.url()));
+
+            this._items.push(item);
+        }
+        let items = this._model.urls;
+        if (items.length > this._items.length)
+            this._onURLsChanged(items);
+    }
+
+    _onKeyPress(event)
+    {
+        if (event.which != 127)
+            return;
+
+        let item = document.activeElement;
+        if (item.classList.contains('overview-item')) {
+            this._removeItem(item);
+            event.preventDefault();
+        }
+    }
+
+    _removeItem(item)
+    {
+        item.classList.add('overview-removed');
+        // Animation takes 0.75s, remove the item after 1s to ensure the animation finished.
+        setTimeout(() => {
+            item.parentNode.removeChild(item);
+            for (let i = 0; i < this._items.length; i++) {
+                if (this._items[i].url() == item.href) {
+                    this._items.splice(i, 1);
+                    break;
+                }
+            }
+            window.webkit.messageHandlers.overview.postMessage(item.href);
+        }, 1000);
+    }
+
+    _onURLsChanged(urls)
+    {
+        let overview = document.getElementById('overview');
+        if (overview.classList.contains('overview-empty')) {
+            while (overview.lastChild)
+                overview.removeChild(overview.lastChild);
+            overview.classList.remove('overview-empty');
+        }
+
+        for (let i = 0; i < urls.length; i++) {
+            let url = urls[i];
+
+            let item;
+            if (this._items[i]) {
+                item = this._items[i];
+            } else {
+                Ephy.log('create an item for the url ' + url.url);
+                let anchor = document.createElement('a');
+                anchor.classList.add('overview-item');
+                let closeButton = document.createElement('div');
+                closeButton.title = Ephy._("Remove from overview");
+                closeButton.onclick = (event) => {
+                    this._removeItem(anchor);
+                    event.preventDefault();
+                };
+                closeButton.innerHTML = '&#10006;';
+                closeButton.classList.add('overview-close-button');
+                anchor.appendChild(closeButton);
+                let thumbnailSpan = document.createElement('span');
+                thumbnailSpan.classList.add('overview-thumbnail');
+                anchor.appendChild(thumbnailSpan);
+                let titleSpan = document.createElement('span');
+                titleSpan.classList.add('overview-title');
+                anchor.appendChild(titleSpan);
+                document.getElementById('overview').appendChild(anchor);
+                item = new Ephy.Overview.Item(anchor);
+                this._items.push(item);
+            }
+
+            item.setURL(url.url);
+            item.setTitle(url.title);
+            item.setThumbnailPath(this._model.getThumbnail(url.url));
+        }
+
+        while (this._items.length > urls.length) {
+            let item = this._items.pop();
+            item.detachFromParent();
+        }
+    }
+
+    _onThumbnailChanged(url, path)
+    {
+        for (let i = 0; i < this._items.length; i++) {
+            let item = this._items[i];
+            if (item.url() == url) {
+                item.setThumbnailPath(path);
+                return;
+            }
+        }
+    }
+
+    _onTitleChanged(url, title)
+    {
+        for (let i = 0; i < this._items.length; i++) {
+            let item = this._items[i];
+            if (item.url() == url) {
+                item.setTitle(title);
+                return;
+            }
+        }
+    }
+};
+
+Ephy.Overview.Item = class OverviewItem
+{
+    constructor(item)
+    {
+        this._item = item;
+        this._title = null;
+        this._thumbnail = null;
+
+        for (let i = 0; i < this._item.childNodes.length; i++) {
+            let child = this._item.childNodes[i];
+            if (!(child instanceof Element))
+                continue;
+
+            if (child.classList.contains('overview-title'))
+                this._title = child;
+            else if (child.classList.contains('overview-thumbnail'))
+                this._thumbnail = child;
+        }
+    }
+
+    // Public
+
+    url()
+    {
+        return this._item.href;
+    }
+
+    setURL(url)
+    {
+        this._item.href = url;
+    }
+
+    title()
+    {
+        return this._item.title;
+    }
+
+    setTitle(title)
+    {
+        this._item.title = title;
+        this._title.textContent = title;
+    }
+
+    thumbnailPath()
+    {
+        let style = this._thumbnail.style;
+        if (style.isPropertyImplicit('background'))
+            return null;
+
+        let background = style.getPropertyValue('background');
+        if (!background)
+            return null;
+
+        if (background.startsWith('url(file://'))
+            return background.replace('url(file://', '').replace(') no-repeat', '');
+
+        return null;
+    }
+
+    setThumbnailPath(path)
+    {
+        if (path)
+            this._thumbnail.style.background = 'url(file://' + path + ') no-repeat';
+        else
+            this._thumbnail.style.background = null;
+    }
+
+    detachFromParent()
+    {
+        this._item.parentNode.removeChild(this._item);
+    }
+};


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