[epiphany] Notify the web extension about history changes to update the overview



commit 59bc00280ce31e11b7056ea20dd339327892ff6e
Author: Carlos Garcia Campos <cgarcia igalia com>
Date:   Mon Feb 17 14:32:50 2014 +0100

    Notify the web extension about history changes to update the overview
    
    And remove the automatic refresh when an overview item is removed via
    javascript.

 embed/ephy-embed-shell.c                      |  129 ++++++
 embed/ephy-web-extension-proxy.c              |   96 +++++
 embed/ephy-web-extension-proxy.h              |   13 +
 embed/web-extension/Makefile.am               |    4 +
 embed/web-extension/ephy-web-extension.c      |  165 +++++----
 embed/web-extension/ephy-web-overview-model.c |  286 +++++++++++++
 embed/web-extension/ephy-web-overview-model.h |   84 ++++
 embed/web-extension/ephy-web-overview.c       |  558 +++++++++++++++++++++++++
 embed/web-extension/ephy-web-overview.h       |   62 +++
 src/resources/overview.html                   |    2 +-
 10 files changed, 1327 insertions(+), 72 deletions(-)
---
diff --git a/embed/ephy-embed-shell.c b/embed/ephy-embed-shell.c
index 0ebfab1..0efa3c7 100644
--- a/embed/ephy-embed-shell.c
+++ b/embed/ephy-embed-shell.c
@@ -212,6 +212,117 @@ ephy_embed_shell_unwatch_web_extension (EphyWebExtensionProxy *web_extension,
   g_object_weak_unref (G_OBJECT (web_extension), (GWeakNotify)web_extension_destroyed, shell);
 }
 
+static void
+history_service_query_urls_cb (EphyHistoryService *service,
+                               gboolean success,
+                               GList *urls,
+                               EphyEmbedShell *shell)
+{
+  GList *l;
+
+  if (!success)
+    return;
+
+  for (l = shell->priv->web_extensions; l; l = g_list_next (l)) {
+    EphyWebExtensionProxy *web_extension = (EphyWebExtensionProxy *)l->data;
+
+    ephy_web_extension_proxy_history_set_urls (web_extension, urls);
+  }
+}
+
+static void
+history_service_urls_visited_cb (EphyHistoryService *history,
+                                 EphyEmbedShell *shell)
+{
+  EphyHistoryQuery *query;
+
+  query = ephy_history_query_new ();
+  query->sort_type = EPHY_HISTORY_SORT_MOST_VISITED;
+  query->limit = ephy_frecent_store_get_history_length (ephy_embed_shell_get_frecent_store (shell));
+  query->ignore_hidden = TRUE;
+
+  ephy_history_service_query_urls (history, query, NULL,
+                                   (EphyHistoryJobCallback) history_service_query_urls_cb,
+                                   shell);
+  ephy_history_query_free (query);
+}
+
+static void
+history_service_url_title_changed_cb (EphyHistoryService *service,
+                                      const char *url,
+                                      const char *title,
+                                      EphyEmbedShell *shell)
+{
+  GList *l;
+
+  for (l = shell->priv->web_extensions; l; l = g_list_next (l)) {
+    EphyWebExtensionProxy *web_extension = (EphyWebExtensionProxy *)l->data;
+
+    ephy_web_extension_proxy_history_set_url_title (web_extension, url, title);
+  }
+}
+
+static void
+history_service_url_deleted_cb (EphyHistoryService *service,
+                                const char *url,
+                                EphyEmbedShell *shell)
+{
+  GList *l;
+
+  for (l = shell->priv->web_extensions; l; l = g_list_next (l)) {
+    EphyWebExtensionProxy *web_extension = (EphyWebExtensionProxy *)l->data;
+
+    ephy_web_extension_proxy_history_delete_url (web_extension, url);
+  }
+}
+
+static void
+history_service_host_deleted_cb (EphyHistoryService *service,
+                                 const char *deleted_url,
+                                 EphyEmbedShell *shell)
+{
+  GList *l;
+  SoupURI *deleted_uri;
+
+  deleted_uri = soup_uri_new (deleted_url);
+
+  for (l = shell->priv->web_extensions; l; l = g_list_next (l)) {
+    EphyWebExtensionProxy *web_extension = (EphyWebExtensionProxy *)l->data;
+
+    ephy_web_extension_proxy_history_delete_host (web_extension, soup_uri_get_host (deleted_uri));
+  }
+
+  soup_uri_free (deleted_uri);
+}
+
+static void
+history_service_cleared_cb (EphyHistoryService *service,
+                            EphyEmbedShell *shell)
+{
+  GList *l;
+
+  for (l = shell->priv->web_extensions; l; l = g_list_next (l)) {
+    EphyWebExtensionProxy *web_extension = (EphyWebExtensionProxy *)l->data;
+
+    ephy_web_extension_proxy_history_clear (web_extension);
+  }
+}
+
+static void
+frecent_store_snapshot_saved_cb (EphyOverviewStore *store,
+                                 const char *url,
+                                 const char *path,
+                                 EphyEmbedShell *shell)
+{
+  GList *l;
+
+  for (l = shell->priv->web_extensions; l; l = g_list_next (l)) {
+    EphyWebExtensionProxy *web_extension = (EphyWebExtensionProxy *)l->data;
+
+    ephy_web_extension_proxy_history_set_url_thumbnail (web_extension, url, path);
+  }
+}
+
 /**
  * ephy_embed_shell_get_global_history_service:
  * @shell: the #EphyEmbedShell
@@ -230,6 +341,24 @@ ephy_embed_shell_get_global_history_service (EphyEmbedShell *shell)
     shell->priv->global_history_service = ephy_history_service_new (filename, FALSE);
     g_free (filename);
     g_return_val_if_fail (shell->priv->global_history_service, NULL);
+    g_signal_connect (shell->priv->global_history_service, "urls-visited",
+                      G_CALLBACK (history_service_urls_visited_cb),
+                      shell);
+    g_signal_connect (shell->priv->global_history_service, "url-title-changed",
+                      G_CALLBACK (history_service_url_title_changed_cb),
+                      shell);
+    g_signal_connect (shell->priv->global_history_service, "url-deleted",
+                      G_CALLBACK (history_service_url_deleted_cb),
+                      shell);
+    g_signal_connect (shell->priv->global_history_service, "host-deleted",
+                      G_CALLBACK (history_service_host_deleted_cb),
+                      shell);
+    g_signal_connect (shell->priv->global_history_service, "cleared",
+                      G_CALLBACK (history_service_cleared_cb),
+                      shell);
+    g_signal_connect (ephy_embed_shell_get_frecent_store (shell), "snapshot-saved",
+                      G_CALLBACK (frecent_store_snapshot_saved_cb),
+                      shell);
   }
 
   return G_OBJECT (shell->priv->global_history_service);
diff --git a/embed/ephy-web-extension-proxy.c b/embed/ephy-web-extension-proxy.c
index dc67723..2056a74 100644
--- a/embed/ephy-web-extension-proxy.c
+++ b/embed/ephy-web-extension-proxy.c
@@ -22,6 +22,7 @@
 #include "ephy-web-extension-proxy.h"
 
 #include "ephy-web-extension.h"
+#include "ephy-history-service.h"
 
 struct _EphyWebExtensionProxyPrivate
 {
@@ -398,3 +399,98 @@ ephy_web_extension_proxy_get_web_app_title_finish (EphyWebExtensionProxy *web_ex
 
   return g_task_propagate_pointer (G_TASK (result), error);
 }
+
+void
+ephy_web_extension_proxy_history_set_urls (EphyWebExtensionProxy *web_extension,
+                                           GList *urls)
+{
+  GList *l;
+  GVariantBuilder builder;
+
+  if (!web_extension->priv->proxy)
+    return;
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ss)"));
+  for (l = urls; l; l = g_list_next (l)) {
+    EphyHistoryURL *url = (EphyHistoryURL *)l->data;
+
+    g_variant_builder_add (&builder, "(ss)", url->url, url->title);
+  }
+
+  g_dbus_proxy_call (web_extension->priv->proxy,
+                     "HistorySetURLs",
+                     g_variant_new ("(@a(ss))", g_variant_builder_end (&builder)),
+                     G_DBUS_CALL_FLAGS_NONE,
+                     -1, NULL, NULL, NULL);
+}
+
+void
+ephy_web_extension_proxy_history_set_url_thumbnail (EphyWebExtensionProxy *web_extension,
+                                                    const char *url,
+                                                    const char *path)
+{
+  if (!web_extension->priv->proxy)
+    return;
+
+  g_dbus_proxy_call (web_extension->priv->proxy,
+                     "HistorySetURLThumbnail",
+                     g_variant_new ("(ss)", url, path),
+                     G_DBUS_CALL_FLAGS_NONE,
+                     -1, NULL, NULL, NULL);
+}
+
+void
+ephy_web_extension_proxy_history_set_url_title (EphyWebExtensionProxy *web_extension,
+                                                const char *url,
+                                                const char *title)
+{
+  if (!web_extension->priv->proxy)
+    return;
+
+  g_dbus_proxy_call (web_extension->priv->proxy,
+                     "HistorySetURLTitle",
+                     g_variant_new ("(ss)", url, title),
+                     G_DBUS_CALL_FLAGS_NONE,
+                     -1, NULL, NULL, NULL);
+}
+
+void
+ephy_web_extension_proxy_history_delete_url (EphyWebExtensionProxy *web_extension,
+                                             const char *url)
+{
+  if (!web_extension->priv->proxy)
+    return;
+
+  g_dbus_proxy_call (web_extension->priv->proxy,
+                     "HistoryDeleteURL",
+                     g_variant_new ("(s)", url),
+                     G_DBUS_CALL_FLAGS_NONE,
+                     -1, NULL, NULL, NULL);
+}
+
+void
+ephy_web_extension_proxy_history_delete_host (EphyWebExtensionProxy *web_extension,
+                                              const char *host)
+{
+  if (!web_extension->priv->proxy)
+    return;
+
+  g_dbus_proxy_call (web_extension->priv->proxy,
+                     "HistoryDeleteHost",
+                     g_variant_new ("(s)", host),
+                     G_DBUS_CALL_FLAGS_NONE,
+                     -1, NULL, NULL, NULL);
+}
+
+void
+ephy_web_extension_proxy_history_clear (EphyWebExtensionProxy *web_extension)
+{
+  if (!web_extension->priv->proxy)
+    return;
+
+  g_dbus_proxy_call (web_extension->priv->proxy,
+                     "HistoryClear",
+                     NULL,
+                     G_DBUS_CALL_FLAGS_NONE,
+                     -1, NULL, NULL, NULL);
+}
diff --git a/embed/ephy-web-extension-proxy.h b/embed/ephy-web-extension-proxy.h
index fef44d2..094a544 100644
--- a/embed/ephy-web-extension-proxy.h
+++ b/embed/ephy-web-extension-proxy.h
@@ -94,6 +94,19 @@ void                   ephy_web_extension_proxy_get_web_app_title
 char                  *ephy_web_extension_proxy_get_web_app_title_finish                  
(EphyWebExtensionProxy *web_extension,
                                                                                            GAsyncResult      
    *result,
                                                                                            GError            
   **error);
+void                   ephy_web_extension_proxy_history_set_urls                          
(EphyWebExtensionProxy *web_extension,
+                                                                                           GList             
    *urls);
+void                   ephy_web_extension_proxy_history_set_url_thumbnail                 
(EphyWebExtensionProxy *web_extension,
+                                                                                           const char        
    *url,
+                                                                                           const char        
    *path);
+void                   ephy_web_extension_proxy_history_set_url_title                     
(EphyWebExtensionProxy *web_extension,
+                                                                                           const char        
    *url,
+                                                                                           const char        
    *title);
+void                   ephy_web_extension_proxy_history_delete_url                        
(EphyWebExtensionProxy *web_extension,
+                                                                                           const char        
    *url);
+void                   ephy_web_extension_proxy_history_delete_host                       
(EphyWebExtensionProxy *web_extension,
+                                                                                           const char        
    *host);
+void                   ephy_web_extension_proxy_history_clear                             
(EphyWebExtensionProxy *web_extension);
 
 G_END_DECLS
 
diff --git a/embed/web-extension/Makefile.am b/embed/web-extension/Makefile.am
index 5513d30..4063e15 100644
--- a/embed/web-extension/Makefile.am
+++ b/embed/web-extension/Makefile.am
@@ -8,6 +8,10 @@ libephywebextension_la_SOURCES = \
        ephy-embed-form-auth.h \
        ephy-web-extension.c \
        ephy-web-extension.h \
+       ephy-web-overview.h \
+       ephy-web-overview.c \
+       ephy-web-overview-model.h \
+       ephy-web-overview-model.c \
        $(top_srcdir)/embed/uri-tester.c \
        $(top_srcdir)/embed/uri-tester.h \
        $(top_srcdir)/lib/ephy-debug.c \
diff --git a/embed/web-extension/ephy-web-extension.c b/embed/web-extension/ephy-web-extension.c
index 9d3d7c7..d3ce630 100644
--- a/embed/web-extension/ephy-web-extension.c
+++ b/embed/web-extension/ephy-web-extension.c
@@ -30,6 +30,7 @@
 #include "ephy-web-dom-utils.h"
 #include "ephy-uri-helpers.h"
 #include "uri-tester.h"
+#include "ephy-web-overview.h"
 
 #include <gio/gio.h>
 #include <gtk/gtk.h>
@@ -43,6 +44,7 @@ static UriTester *uri_tester;
 static EphyFormAuthDataCache *form_auth_data_cache;
 static GDBusConnection *dbus_connection;
 static GArray *page_created_signals_pending;
+static EphyWebOverviewModel *overview_model;
 
 static const char introspection_xml[] =
   "<node>"
@@ -78,6 +80,24 @@ static const char introspection_xml[] =
   "   <arg type='u' name='request_id' direction='in'/>"
   "   <arg type='b' name='should_store' direction='in'/>"
   "  </method>"
+  "  <method name='HistorySetURLs'>"
+  "   <arg type='a(ss)' name='urls' direction='in'/>"
+  "  </method>"
+  "  <method name='HistorySetURLThumbnail'>"
+  "   <arg type='s' name='url' direction='in'/>"
+  "   <arg type='s' name='path' direction='in'/>"
+  "  </method>"
+  "  <method name='HistorySetURLTitle'>"
+  "   <arg type='s' name='url' direction='in'/>"
+  "   <arg type='s' name='title' direction='in'/>"
+  "  </method>"
+  "  <method name='HistoryDeleteURL'>"
+  "   <arg type='s' name='url' direction='in'/>"
+  "  </method>"
+  "  <method name='HistoryDeleteHost'>"
+  "   <arg type='s' name='host' direction='in'/>"
+  "  </method>"
+  "  <method name='HistoryClear'/>"
   " </interface>"
   "</node>";
 
@@ -255,7 +275,8 @@ request_decision_on_storing (EphyEmbedFormAuth *form_auth)
 }
 
 static void
-remove_from_overview_emit_signal (char *url)
+overview_item_removed (EphyWebOverview *overview,
+                       const char *url)
 {
   GError *error = NULL;
 
@@ -1041,12 +1062,12 @@ web_page_created_callback (WebKitWebExtension *extension,
   else
     queue_page_created_signal_emission (page_id);
 
-  g_signal_connect_object (web_page, "send-request",
-                           G_CALLBACK (web_page_send_request),
-                           NULL, 0);
-  g_signal_connect_object (web_page, "document-loaded",
-                           G_CALLBACK (web_page_document_loaded),
-                           NULL, 0);
+  g_signal_connect (web_page, "send-request",
+                    G_CALLBACK (web_page_send_request),
+                    NULL);
+  g_signal_connect (web_page, "document-loaded",
+                    G_CALLBACK (web_page_document_loaded),
+                    NULL);
 }
 
 static WebKitWebPage *
@@ -1147,8 +1168,63 @@ handle_method_call (GDBusConnection *connection,
     if (should_store)
       store_password (form_auth);
     g_hash_table_remove (requests, GINT_TO_POINTER (request_id));
-  }
+  } else if (g_strcmp0 (method_name, "HistorySetURLs") == 0) {
+    if (overview_model) {
+      GVariantIter iter;
+      GVariant *array;
+      const char *url;
+      const char *title;
+      GList *items = NULL;
+
+      g_variant_get (parameters, "(@a(ss))", &array);
+      g_variant_iter_init (&iter, array);
+
+      while (g_variant_iter_loop (&iter, "(&s&s)", &url, &title))
+        items = g_list_prepend (items, ephy_web_overview_model_item_new (url, title));
+      g_variant_unref (array);
+
+      ephy_web_overview_model_set_urls (overview_model, g_list_reverse (items));
+    }
+    g_dbus_method_invocation_return_value (invocation, NULL);
+  } else if (g_strcmp0 (method_name, "HistorySetURLThumbnail") == 0) {
+    if (overview_model) {
+      const char *url;
+      const char *path;
+
+      g_variant_get (parameters, "(&s&s)", &url, &path);
+      ephy_web_overview_model_set_url_thumbnail (overview_model, url, path);
+    }
+    g_dbus_method_invocation_return_value (invocation, NULL);
+  } else if (g_strcmp0 (method_name, "HistorySetURLTitle") == 0) {
+    if (overview_model) {
+      const char *url;
+      const char *title;
+
+      g_variant_get (parameters, "(&s&s)", &url, &title);
+      ephy_web_overview_model_set_url_title (overview_model, url, title);
+    }
+    g_dbus_method_invocation_return_value (invocation, NULL);
+  } else if (g_strcmp0 (method_name, "HistoryDeleteURL") == 0) {
+    if (overview_model) {
+      const char *url;
+
+      g_variant_get (parameters, "(&s)", &url);
+      ephy_web_overview_model_delete_url (overview_model, url);
+    }
+    g_dbus_method_invocation_return_value (invocation, NULL);
+  } else if (g_strcmp0 (method_name, "HistoryDeleteHost") == 0) {
+    if (overview_model) {
+      const char *host;
 
+      g_variant_get (parameters, "(&s)", &host);
+      ephy_web_overview_model_delete_host (overview_model, host);
+    }
+    g_dbus_method_invocation_return_value (invocation, NULL);
+  } else if (g_strcmp0 (method_name, "HistoryClear") == 0) {
+    if (overview_model)
+      ephy_web_overview_model_clear (overview_model);
+    g_dbus_method_invocation_return_value (invocation, NULL);
+  }
 }
 
 static const GDBusInterfaceVTable interface_vtable = {
@@ -1186,59 +1262,6 @@ bus_acquired_cb (GDBusConnection *connection,
   }
 }
 
-
-static JSValueRef
-remove_from_overview_cb (JSContextRef context,
-                         JSObjectRef function,
-                         JSObjectRef this_object,
-                         size_t argument_count,
-                         const JSValueRef arguments[],
-                         JSValueRef *exception)
-{
-  JSStringRef result_string_js;
-  size_t max_size;
-  char *result_string;
-
-  result_string_js = JSValueToStringCopy (context, arguments[0], NULL);
-  max_size = JSStringGetMaximumUTF8CStringSize (result_string_js);
-
-  result_string = g_malloc (max_size);
-  JSStringGetUTF8CString (result_string_js, result_string, max_size);
-  remove_from_overview_emit_signal (result_string);
-
-  JSStringRelease (result_string_js);
-  g_free (result_string);
-
-  return JSValueMakeUndefined (context);
-}
-
-static const JSStaticFunction overview_staticfuncs[] =
-{
-  { "removeItemFromOverview", remove_from_overview_cb, kJSPropertyAttributeReadOnly | 
kJSPropertyAttributeDontDelete },
-  { NULL, NULL, 0 }
-};
-
-static const JSClassDefinition overview_notification_def =
-{
-  0,                     /* version */
-  kJSClassAttributeNone, /* attributes */
-  "Overview",            /* className */
-  NULL,                  /* parentClass */
-  NULL,                  /* staticValues */
-  overview_staticfuncs,  /* staticFunctions */
-  NULL,                  /* initialize */
-  NULL,                  /* finalize */
-  NULL,                  /* hasProperty */
-  NULL,                  /* getProperty */
-  NULL,                  /* setProperty */
-  NULL,                  /* deleteProperty */
-  NULL,                  /* getPropertyNames */
-  NULL,                  /* callAsFunction */
-  NULL,                  /* callAsConstructor */
-  NULL,                  /* hasInstance */
-  NULL                   /* convertToType */
-};
-
 static void
 window_object_cleared_cb (WebKitScriptWorld *world,
                           WebKitWebPage     *web_page,
@@ -1246,18 +1269,17 @@ window_object_cleared_cb (WebKitScriptWorld *world,
                           gpointer           user_data)
 {
   JSGlobalContextRef context;
-  JSObjectRef global_object;
-  JSClassRef class_def;
-  JSObjectRef class_object;
-  JSStringRef str;
+  EphyWebOverview *overview;
 
-  context = webkit_frame_get_javascript_context_for_script_world (frame, world);
-  global_object = JSContextGetGlobalObject (context);
+  if (g_strcmp0 (webkit_web_page_get_uri (web_page), "ephy-about:overview") != 0)
+    return;
 
-  class_def = JSClassCreate (&overview_notification_def);
-  class_object = JSObjectMake (context, class_def, context);
-  str = JSStringCreateWithUTF8CString ("Overview");
-  JSObjectSetProperty (context, global_object, str, class_object, kJSPropertyAttributeNone, NULL);
+  overview = ephy_web_overview_new (web_page, overview_model);
+  g_signal_connect (overview, "item-removed",
+                    G_CALLBACK (overview_item_removed),
+                    NULL);
+  context = webkit_frame_get_javascript_context_for_script_world (frame, world);
+  ephy_web_overview_init_js (overview, context);
 }
 
 G_MODULE_EXPORT void
@@ -1279,6 +1301,7 @@ webkit_web_extension_initialize_with_user_data (WebKitWebExtension *extension,
 
   ephy_debug_init ();
   uri_tester = uri_tester_new (dot_dir);
+  overview_model = ephy_web_overview_model_new ();
   if (!private_profile)
     form_auth_data_cache = ephy_form_auth_data_cache_new ();
 
diff --git a/embed/web-extension/ephy-web-overview-model.c b/embed/web-extension/ephy-web-overview-model.c
new file mode 100644
index 0000000..633e8bf
--- /dev/null
+++ b/embed/web-extension/ephy-web-overview-model.c
@@ -0,0 +1,286 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2014 Igalia S.L.
+ *
+ *  This program 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 2, or (at your option)
+ *  any later version.
+ *
+ *  This program 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 this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+#include "ephy-web-overview-model.h"
+
+#include <libsoup/soup.h>
+
+struct _EphyWebOverviewModelPrivate
+{
+  GList *items;
+  GHashTable *thumbnails;
+};
+
+enum
+{
+  URLS_CHANGED,
+  THUMBNAIL_CHANGED,
+  TITLE_CHANGED,
+
+  LAST_SIGNAL
+};
+
+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)
+{
+  EphyWebOverviewModel *model = EPHY_WEB_OVERVIEW_MODEL (object);
+
+  if (model->priv->items) {
+    g_list_free_full (model->priv->items, (GDestroyNotify)ephy_web_overview_model_item_free);
+    model->priv->items = NULL;
+  }
+
+  if (model->priv->thumbnails) {
+    g_hash_table_destroy (model->priv->thumbnails);
+    model->priv->thumbnails = NULL;
+  }
+
+  G_OBJECT_CLASS (ephy_web_overview_model_parent_class)->dispose (object);
+}
+
+static void
+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,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+  signals[THUMBNAIL_CHANGED] =
+    g_signal_new ("thumbnail-changed",
+                  EPHY_TYPE_WEB_OVERVIEW_MODEL,
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  g_cclosure_marshal_generic,
+                  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,
+                  g_cclosure_marshal_generic,
+                  G_TYPE_NONE, 2,
+                  G_TYPE_STRING,
+                  G_TYPE_STRING);
+
+  g_type_class_add_private (object_class, sizeof(EphyWebOverviewModelPrivate));
+}
+
+static void
+ephy_web_overview_model_init (EphyWebOverviewModel *model)
+{
+  model->priv = G_TYPE_INSTANCE_GET_PRIVATE (model, EPHY_TYPE_WEB_OVERVIEW_MODEL, 
EphyWebOverviewModelPrivate);
+  model->priv->thumbnails = g_hash_table_new_full (g_str_hash,
+                                                   g_str_equal,
+                                                   (GDestroyNotify)g_free,
+                                                   (GDestroyNotify)g_free);
+}
+
+EphyWebOverviewModel *
+ephy_web_overview_model_new (void)
+{
+  return g_object_new (EPHY_TYPE_WEB_OVERVIEW_MODEL, NULL);
+}
+
+void
+ephy_web_overview_model_set_urls (EphyWebOverviewModel *model,
+                                  GList *urls)
+{
+  g_return_if_fail (EPHY_IS_WEB_OVERVIEW_MODEL (model));
+
+  g_list_free_full (model->priv->items, (GDestroyNotify)ephy_web_overview_model_item_free);
+  model->priv->items = urls;
+  g_signal_emit (model, signals[URLS_CHANGED], 0);
+}
+
+GList *
+ephy_web_overview_model_get_urls (EphyWebOverviewModel *model)
+{
+  g_return_val_if_fail (EPHY_IS_WEB_OVERVIEW_MODEL (model), NULL);
+
+  return model->priv->items;
+}
+
+void
+ephy_web_overview_model_set_url_thumbnail (EphyWebOverviewModel *model,
+                                           const char *url,
+                                           const char *path)
+{
+  const char *thumbnail_path;
+  GList *l;
+  gboolean changed = FALSE;
+
+  g_return_if_fail (EPHY_IS_WEB_OVERVIEW_MODEL (model));
+
+  thumbnail_path = ephy_web_overview_model_get_url_thumbnail (model, url);
+  if (g_strcmp0 (thumbnail_path, path) == 0)
+    return;
+
+  g_hash_table_insert (model->priv->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_return_val_if_fail (EPHY_IS_WEB_OVERVIEW_MODEL (model), NULL);
+
+  return g_hash_table_lookup (model->priv->thumbnails, url);
+}
+
+void
+ephy_web_overview_model_set_url_title (EphyWebOverviewModel *model,
+                                       const char *url,
+                                       const char *title)
+{
+  GList *l;
+  gboolean changed = FALSE;
+
+  g_return_if_fail (EPHY_IS_WEB_OVERVIEW_MODEL (model));
+
+  for (l = model->priv->items; l; l = g_list_next (l)) {
+    EphyWebOverviewModelItem *item = (EphyWebOverviewModelItem *)l->data;
+
+    if (g_strcmp0 (item->url, url) != 0)
+      continue;
+
+    if (g_strcmp0 (item->title, title) != 0) {
+      changed = TRUE;
+
+      g_free (item->title);
+      item->title = g_strdup (title);
+    }
+  }
+
+  if (changed)
+    g_signal_emit (model, signals[TITLE_CHANGED], 0, url, title);
+}
+
+void
+ephy_web_overview_model_delete_url (EphyWebOverviewModel *model,
+                                    const char *url)
+{
+  GList *l;
+  gboolean changed = FALSE;
+
+  g_return_if_fail (EPHY_IS_WEB_OVERVIEW_MODEL (model));
+
+  l = model->priv->items;
+  while (l) {
+    EphyWebOverviewModelItem *item = (EphyWebOverviewModelItem *)l->data;
+    GList *next = l->next;
+
+    if (g_strcmp0 (item->url, url) == 0) {
+      changed = TRUE;
+
+      ephy_web_overview_model_item_free (item);
+      model->priv->items = g_list_delete_link (model->priv->items, l);
+    }
+
+    l = next;
+  }
+
+  if (changed)
+    g_signal_emit (model, signals[URLS_CHANGED], 0);
+}
+
+void
+ephy_web_overview_model_delete_host (EphyWebOverviewModel *model,
+                                     const char *host)
+{
+  GList *l;
+  gboolean changed = FALSE;
+
+  g_return_if_fail (EPHY_IS_WEB_OVERVIEW_MODEL (model));
+
+  l = model->priv->items;
+  while (l) {
+    EphyWebOverviewModelItem *item = (EphyWebOverviewModelItem *)l->data;
+    SoupURI *uri = soup_uri_new (item->url);
+    GList *next = l->next;
+
+    if (g_strcmp0 (soup_uri_get_host (uri), host) == 0) {
+      changed = TRUE;
+
+      ephy_web_overview_model_item_free (item);
+      model->priv->items = g_list_delete_link (model->priv->items, l);
+    }
+
+    soup_uri_free (uri);
+    l = next;
+  }
+
+  if (changed)
+    g_signal_emit (model, signals[URLS_CHANGED], 0);
+}
+
+void
+ephy_web_overview_model_clear (EphyWebOverviewModel *model)
+{
+  g_return_if_fail (EPHY_IS_WEB_OVERVIEW_MODEL (model));
+
+  if (!model->priv->items)
+    return;
+
+  g_list_free_full (model->priv->items, (GDestroyNotify)ephy_web_overview_model_item_free);
+  model->priv->items = NULL;
+  g_signal_emit (model, signals[URLS_CHANGED], 0);
+}
+
+EphyWebOverviewModelItem *
+ephy_web_overview_model_item_new (const char *url,
+                                  const char *title)
+{
+  EphyWebOverviewModelItem *item;
+
+  item = g_slice_new0 (EphyWebOverviewModelItem);
+  item->url = g_strdup (url);
+  item->title = g_strdup (title);
+
+  return item;
+}
+
+void
+ephy_web_overview_model_item_free (EphyWebOverviewModelItem *item)
+{
+  if (G_UNLIKELY (!item))
+    return;
+
+  g_free (item->url);
+  g_free (item->title);
+
+  g_slice_free (EphyWebOverviewModelItem, item);
+}
diff --git a/embed/web-extension/ephy-web-overview-model.h b/embed/web-extension/ephy-web-overview-model.h
new file mode 100644
index 0000000..65838a0
--- /dev/null
+++ b/embed/web-extension/ephy-web-overview-model.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2014 Igalia S.L.
+ *
+ *  This program 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 2, or (at your option)
+ *  any later version.
+ *
+ *  This program 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 this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef _EPHY_WEB_OVERVIEW_MODEL_H
+#define _EPHY_WEB_OVERVIEW_MODEL_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_WEB_OVERVIEW_MODEL            (ephy_web_overview_model_get_type())
+#define EPHY_WEB_OVERVIEW_MODEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
EPHY_TYPE_WEB_OVERVIEW_MODEL, EphyWebOverviewModel))
+#define EPHY_IS_WEB_OVERVIEW_MODEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
EPHY_TYPE_WEB_OVERVIEW_MODEL))
+#define EPHY_WEB_OVERVIEW_MODEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), 
EPHY_TYPE_WEB_OVERVIEW_MODEL, EphyWebOverviewModelClass))
+#define EPHY_IS_WEB_OVERVIEW_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
EPHY_TYPE_WEB_OVERVIEW_MODEL))
+#define EPHY_WEB_OVERVIEW_MODEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), 
EPHY_TYPE_WEB_OVERVIEW_MODEL, EphyWebOverviewModelClass))
+
+typedef struct _EphyWebOverviewModel        EphyWebOverviewModel;
+typedef struct _EphyWebOverviewModelClass   EphyWebOverviewModelClass;
+typedef struct _EphyWebOverviewModelPrivate EphyWebOverviewModelPrivate;
+
+struct _EphyWebOverviewModel
+{
+  GObject parent;
+
+  EphyWebOverviewModelPrivate *priv;
+};
+
+struct _EphyWebOverviewModelClass
+{
+  GObjectClass parent_class;
+};
+
+GType                 ephy_web_overview_model_get_type          (void) G_GNUC_CONST;
+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);
+void                  ephy_web_overview_model_set_url_title     (EphyWebOverviewModel *model,
+                                                                 const char           *url,
+                                                                 const char           *title);
+void                  ephy_web_overview_model_delete_url        (EphyWebOverviewModel *model,
+                                                                 const char           *url);
+void                  ephy_web_overview_model_delete_host       (EphyWebOverviewModel *model,
+                                                                 const char           *host);
+void                  ephy_web_overview_model_clear             (EphyWebOverviewModel *model);
+
+
+typedef struct _EphyWebOverviewModelItem EphyWebOverviewModelItem;
+struct _EphyWebOverviewModelItem
+{
+  char *url;
+  char *title;
+};
+
+EphyWebOverviewModelItem *ephy_web_overview_model_item_new  (const char               *url,
+                                                             const char               *title);
+void                      ephy_web_overview_model_item_free (EphyWebOverviewModelItem *item);
+
+G_END_DECLS
+
+#endif /* _EPHY_WEB_OVERVIEW_MODEL_H */
diff --git a/embed/web-extension/ephy-web-overview.c b/embed/web-extension/ephy-web-overview.c
new file mode 100644
index 0000000..118dbb9
--- /dev/null
+++ b/embed/web-extension/ephy-web-overview.c
@@ -0,0 +1,558 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2014 Igalia S.L.
+ *
+ *  This program 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 2, or (at your option)
+ *  any later version.
+ *
+ *  This program 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 this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+#include "ephy-web-overview.h"
+
+#include <string.h>
+
+struct _EphyWebOverviewPrivate
+{
+  WebKitWebPage *web_page;
+  EphyWebOverviewModel *model;
+  GList *items;
+};
+
+enum
+{
+  PROP_0,
+  PROP_WEB_PAGE,
+  PROP_MODEL,
+};
+
+enum
+{
+  ITEM_REMOVED,
+
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (EphyWebOverview, ephy_web_overview, G_TYPE_OBJECT)
+
+typedef struct {
+  char *url;
+
+  WebKitDOMElement *anchor;
+  WebKitDOMElement *thumbnail;
+  WebKitDOMElement *title;
+} OverviewItem;
+
+static OverviewItem *
+overview_item_new (WebKitDOMElement *anchor)
+{
+  OverviewItem *item;
+  WebKitDOMNodeList *nodes;
+  int i, n_nodes;
+
+  item = g_slice_new0 (OverviewItem);
+  item->anchor = g_object_ref (anchor);
+  item->url = webkit_dom_element_get_attribute (anchor, "href");
+
+  nodes = webkit_dom_node_get_child_nodes (WEBKIT_DOM_NODE (anchor));
+  n_nodes = webkit_dom_node_list_get_length (nodes);
+  for (i = 0; i < n_nodes; i++) {
+    WebKitDOMNode* node = webkit_dom_node_list_item (nodes, i);
+    WebKitDOMElement *element;
+    char *tag;
+
+    if (!WEBKIT_DOM_IS_ELEMENT (node))
+      continue;
+
+    element = WEBKIT_DOM_ELEMENT (node);
+    tag = webkit_dom_element_get_tag_name (element);
+    if (g_strcmp0 (tag, "SPAN") == 0) {
+      char *class;
+
+      class = webkit_dom_element_get_class_name (element);
+      if (g_strcmp0 (class, "thumbnail") == 0)
+        item->thumbnail = g_object_ref (element);
+      else if (g_strcmp0 (class, "title") == 0)
+        item->title = g_object_ref (element);
+
+      g_free (class);
+    }
+
+    g_free (tag);
+  }
+  g_object_unref (nodes);
+
+  return item;
+}
+
+static void
+overview_item_free (OverviewItem *item)
+{
+  g_free (item->url);
+  g_clear_object (&item->anchor);
+  g_clear_object (&item->thumbnail);
+  g_clear_object (&item->title);
+
+  g_slice_free (OverviewItem, item);
+}
+
+static void
+ephy_web_overview_web_page_uri_changed (WebKitWebPage *web_page,
+                                        GParamSpec *spec,
+                                        EphyWebOverview *overview)
+{
+  if (g_strcmp0 (webkit_web_page_get_uri (web_page), "ephy-about:overview") != 0)
+    g_object_unref (overview);
+}
+
+static void
+ephy_web_overview_model_urls_changed (EphyWebOverviewModel *model,
+                                      EphyWebOverview *overview)
+{
+  GList *urls;
+  GList *l;
+  GList *items;
+  OverviewItem* item;
+
+  urls = ephy_web_overview_model_get_urls (model);
+
+  items = overview->priv->items;
+  for (l = urls; l; l = g_list_next (l)) {
+    EphyWebOverviewModelItem *url = (EphyWebOverviewModelItem *)l->data;
+    const char *thumbnail_path;
+
+    thumbnail_path = ephy_web_overview_model_get_url_thumbnail (model, url->url);
+
+    if (items) {
+      WebKitDOMDOMTokenList *class_list;
+
+      item = (OverviewItem *)items->data;
+
+      g_free (item->url);
+      item->url = g_strdup (url->url);
+
+      class_list = webkit_dom_element_get_class_list (webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE 
(item->anchor)));
+      if (class_list && webkit_dom_dom_token_list_contains (class_list, "removed", NULL))
+        webkit_dom_dom_token_list_remove (class_list, "removed", NULL);
+
+      webkit_dom_element_set_attribute (item->anchor, "href", url->url, NULL);
+      webkit_dom_element_set_attribute (item->anchor, "title", url->title, NULL);
+      webkit_dom_node_set_text_content (WEBKIT_DOM_NODE (item->title), url->title, NULL);
+
+      if (thumbnail_path) {
+        char *style;
+
+        style = g_strdup_printf ("background: url(ephy-about:/thumbnail-frame.png), url(file://%s) no-repeat 
10px 9px;", thumbnail_path);
+        webkit_dom_element_set_attribute (item->thumbnail, "style", style, NULL);
+        g_free (style);
+      } else {
+        webkit_dom_element_remove_attribute (item->thumbnail, "style");
+      }
+    } else {
+      WebKitDOMDocument *document;
+      WebKitDOMElement *item_list, *anchor;
+      WebKitDOMNode *new_node;
+
+      item = g_slice_new0 (OverviewItem);
+      item->url = g_strdup (url->url);
+
+      document = webkit_web_page_get_dom_document (overview->priv->web_page);
+      item_list = webkit_dom_document_get_element_by_id (document, "item-list");
+
+      new_node = WEBKIT_DOM_NODE (webkit_dom_document_create_element (document, "LI", NULL));
+      webkit_dom_node_append_child (WEBKIT_DOM_NODE (item_list), WEBKIT_DOM_NODE (new_node), NULL);
+
+      anchor = webkit_dom_document_create_element (document, "A", NULL);
+      item->anchor = g_object_ref (anchor);
+      webkit_dom_element_set_class_name (WEBKIT_DOM_ELEMENT (anchor), "overview-item");
+      webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (anchor), "href", url->url, NULL);
+      webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (anchor), "title", url->title, NULL);
+      webkit_dom_node_append_child (WEBKIT_DOM_NODE (new_node), WEBKIT_DOM_NODE (anchor), NULL);
+
+      new_node = WEBKIT_DOM_NODE (webkit_dom_document_create_element (document, "DIV", NULL));
+      webkit_dom_element_set_class_name (WEBKIT_DOM_ELEMENT (new_node), "close-button");
+      webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (new_node), "onclick", 
"removeFromOverview(this.parentNode,event)", NULL);
+      webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (new_node), "title", url->title, NULL);
+      webkit_dom_node_set_text_content (new_node, "✖", NULL);
+      webkit_dom_node_append_child (WEBKIT_DOM_NODE (anchor), new_node, NULL);
+
+      new_node = WEBKIT_DOM_NODE (webkit_dom_document_create_element (document, "SPAN", NULL));
+      item->thumbnail = g_object_ref (new_node);
+      webkit_dom_element_set_class_name (WEBKIT_DOM_ELEMENT (new_node), "thumbnail");
+      if (thumbnail_path) {
+        char *style;
+
+        style = g_strdup_printf ("background: url(ephy-about:/thumbnail-frame.png), url(file://%s) no-repeat 
10px 9px;", thumbnail_path);
+        webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (new_node), "style", style, NULL);
+        g_free (style);
+      }
+      webkit_dom_node_append_child (WEBKIT_DOM_NODE (anchor), new_node, NULL);
+
+      new_node = WEBKIT_DOM_NODE (webkit_dom_document_create_element (document, "SPAN", NULL));
+      item->title = g_object_ref (new_node);
+      webkit_dom_element_set_class_name (WEBKIT_DOM_ELEMENT (new_node), "title");
+      webkit_dom_node_set_text_content (new_node, url->title, NULL);
+      webkit_dom_node_append_child (WEBKIT_DOM_NODE (anchor), new_node, NULL);
+
+      overview->priv->items = g_list_append (overview->priv->items, item);
+    }
+
+    items = g_list_next (items);
+  }
+
+  while (items) {
+    WebKitDOMNode *anchor;
+    GList *next = items->next;
+
+    item = (OverviewItem *)items->data;
+
+    anchor = WEBKIT_DOM_NODE (item->anchor);
+    webkit_dom_node_remove_child (webkit_dom_node_get_parent_node (anchor), anchor, NULL);
+
+    overview_item_free (item);
+    overview->priv->items = g_list_delete_link (overview->priv->items, items);
+
+    items = next;
+  }
+}
+
+static void
+ephy_web_overview_model_thumbnail_changed (EphyWebOverviewModel *model,
+                                           const char *url,
+                                           const char *path,
+                                           EphyWebOverview *overview)
+{
+  GList *l;
+
+  for (l = overview->priv->items; l; l = g_list_next (l)) {
+    OverviewItem *item = (OverviewItem *)l->data;
+    char *style;
+
+    if (g_strcmp0 (item->url, url) != 0)
+      continue;
+
+    style = g_strdup_printf ("background: url(ephy-about:/thumbnail-frame.png), url(file://%s) no-repeat 
10px 9px;", path);
+    webkit_dom_element_set_attribute (item->thumbnail, "style", style, NULL);
+    g_free (style);
+  }
+}
+
+static void
+ephy_web_overview_model_title_changed (EphyWebOverviewModel *model,
+                                       const char *url,
+                                       const char *title,
+                                       EphyWebOverview *overview)
+{
+  GList *l;
+
+  for (l = overview->priv->items; l; l = g_list_next (l)) {
+    OverviewItem *item = (OverviewItem *)l->data;
+    char *style;
+
+    if (g_strcmp0 (item->url, url) != 0)
+      continue;
+
+    webkit_dom_element_set_attribute (item->anchor, "title", title, NULL);
+    webkit_dom_node_set_text_content (WEBKIT_DOM_NODE (item->title), title, NULL);
+  }
+}
+
+static void
+ephy_web_overview_update_thumbnail_in_model_from_element (EphyWebOverview *overview,
+                                                          const char *url,
+                                                          WebKitDOMElement *thumbnail)
+{
+  WebKitDOMCSSStyleDeclaration *style;
+  char *background;
+  char *thumbnail_path;
+
+  style = webkit_dom_element_get_style (thumbnail);
+  if (webkit_dom_css_style_declaration_is_property_implicit (style, "background"))
+    return;
+
+  background = webkit_dom_css_style_declaration_get_property_value (style, "background");
+  if (!background)
+    return;
+
+  thumbnail_path = g_strrstr (background, "file://");
+  if (thumbnail_path) {
+    char *p;
+
+    thumbnail_path += strlen ("file://");
+    p = g_strrstr (thumbnail_path, ")");
+    if (p) {
+      thumbnail_path = g_strndup (thumbnail_path, strlen (thumbnail_path) - strlen (p));
+      g_signal_handlers_block_by_func (overview->priv->model, G_CALLBACK 
(ephy_web_overview_model_thumbnail_changed), overview);
+      ephy_web_overview_model_set_url_thumbnail (overview->priv->model, url, thumbnail_path);
+      g_signal_handlers_unblock_by_func (overview->priv->model, G_CALLBACK 
(ephy_web_overview_model_thumbnail_changed), overview);
+      g_free (thumbnail_path);
+    }
+
+  }
+  g_free (background);
+}
+
+static void
+ephy_web_overview_document_loaded (WebKitWebPage *web_page,
+                                   EphyWebOverview *overview)
+{
+  WebKitDOMDocument *document;
+  WebKitDOMNodeList *nodes;
+  int i, n_nodes;
+
+  document = webkit_web_page_get_dom_document (web_page);
+  nodes = webkit_dom_document_get_elements_by_tag_name (document, "a");
+  n_nodes = webkit_dom_node_list_get_length (nodes);
+  for (i = 0; i < n_nodes; i++) {
+    WebKitDOMElement* element = WEBKIT_DOM_ELEMENT (webkit_dom_node_list_item (nodes, i));
+    char *class;
+
+    class = webkit_dom_element_get_class_name (element);
+    if (g_strcmp0 (class, "overview-item") == 0) {
+      OverviewItem *item = overview_item_new (element);
+
+      /* URLs and titles are always sent from the UI process, but thumbnails don't,
+       * so update the model with the thumbnail of there's one.
+       */
+      ephy_web_overview_update_thumbnail_in_model_from_element (overview, item->url, item->thumbnail);
+
+      overview->priv->items = g_list_prepend (overview->priv->items, item);
+    }
+    g_free (class);
+  }
+  g_object_unref (nodes);
+  overview->priv->items = g_list_reverse (overview->priv->items);
+}
+
+static void
+ephy_web_overview_set_property (GObject *object,
+                                guint prop_id,
+                                const GValue *value,
+                                GParamSpec *pspec)
+{
+  EphyWebOverview *overview = EPHY_WEB_OVERVIEW (object);
+
+  switch (prop_id)
+  {
+  case PROP_WEB_PAGE:
+    overview->priv->web_page = g_value_get_object (value);
+    break;
+  case PROP_MODEL:
+    overview->priv->model = g_value_get_object (value);
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    break;
+  }
+}
+
+static void
+ephy_web_overview_dispose (GObject *object)
+{
+  EphyWebOverview *overview = EPHY_WEB_OVERVIEW (object);
+
+  if (overview->priv->items) {
+    g_list_free_full (overview->priv->items, (GDestroyNotify)overview_item_free);
+    overview->priv->items = NULL;
+  }
+
+  G_OBJECT_CLASS (ephy_web_overview_parent_class)->dispose (object);
+}
+
+static void
+ephy_web_overview_web_view_destroyed (EphyWebOverview *overview,
+                                      GObject *destroyed_web_view)
+{
+  overview->priv->web_page = NULL;
+  g_object_unref (overview);
+}
+
+void
+ephy_web_overview_finalize (GObject *object)
+{
+  EphyWebOverview *overview = EPHY_WEB_OVERVIEW (object);
+
+  if (overview->priv->web_page) {
+    g_object_weak_unref (G_OBJECT (overview->priv->web_page),
+                         (GWeakNotify)ephy_web_overview_web_view_destroyed,
+                         overview);
+  }
+
+  G_OBJECT_CLASS (ephy_web_overview_parent_class)->finalize (object);
+}
+
+static void
+ephy_web_overview_constructed (GObject *object)
+{
+  EphyWebOverview *overview = EPHY_WEB_OVERVIEW (object);
+
+  G_OBJECT_CLASS (ephy_web_overview_parent_class)->constructed (object);
+
+  g_object_weak_ref (G_OBJECT (overview->priv->web_page),
+                     (GWeakNotify)ephy_web_overview_web_view_destroyed,
+                     overview);
+
+  g_signal_connect_object (overview->priv->web_page, "notify::uri",
+                           G_CALLBACK (ephy_web_overview_web_page_uri_changed),
+                           overview, 0);
+  g_signal_connect_object (overview->priv->web_page, "document-loaded",
+                           G_CALLBACK (ephy_web_overview_document_loaded),
+                           overview, 0);
+
+  g_signal_connect_object (overview->priv->model, "urls-changed",
+                           G_CALLBACK (ephy_web_overview_model_urls_changed),
+                           overview, 0);
+  g_signal_connect_object (overview->priv->model, "thumbnail-changed",
+                           G_CALLBACK (ephy_web_overview_model_thumbnail_changed),
+                           overview, 0);
+  g_signal_connect_object (overview->priv->model, "title-changed",
+                           G_CALLBACK (ephy_web_overview_model_title_changed),
+                           overview, 0);
+}
+
+static void
+ephy_web_overview_class_init (EphyWebOverviewClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = ephy_web_overview_dispose;
+  object_class->finalize = ephy_web_overview_finalize;
+  object_class->constructed = ephy_web_overview_constructed;
+  object_class->set_property = ephy_web_overview_set_property;
+
+  g_object_class_install_property (object_class,
+                                   PROP_WEB_PAGE,
+                                   g_param_spec_object ("web-page",
+                                                        "WebPage",
+                                                        "The overview WebPage",
+                                                        WEBKIT_TYPE_WEB_PAGE,
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | 
G_PARAM_STATIC_BLURB));
+  g_object_class_install_property (object_class,
+                                   PROP_MODEL,
+                                   g_param_spec_object ("model",
+                                                        "Model",
+                                                        "The overview model",
+                                                        EPHY_TYPE_WEB_OVERVIEW_MODEL,
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | 
G_PARAM_STATIC_BLURB));
+
+  signals[ITEM_REMOVED] =
+    g_signal_new ("item-removed",
+                  EPHY_TYPE_WEB_OVERVIEW,
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  g_cclosure_marshal_VOID__STRING,
+                  G_TYPE_NONE, 1,
+                  G_TYPE_STRING);
+
+  g_type_class_add_private (object_class, sizeof(EphyWebOverviewPrivate));
+}
+
+static void
+ephy_web_overview_init (EphyWebOverview *overview)
+{
+  overview->priv = G_TYPE_INSTANCE_GET_PRIVATE (overview, EPHY_TYPE_WEB_OVERVIEW, EphyWebOverviewPrivate);
+}
+
+EphyWebOverview *
+ephy_web_overview_new (WebKitWebPage *web_page,
+                       EphyWebOverviewModel *model)
+{
+  g_return_val_if_fail (WEBKIT_IS_WEB_PAGE (web_page), NULL);
+  g_return_val_if_fail (EPHY_IS_WEB_OVERVIEW_MODEL (model), NULL);
+
+  return g_object_new (EPHY_TYPE_WEB_OVERVIEW,
+                       "web-page", web_page,
+                       "model", model,
+                       NULL);
+}
+
+static JSValueRef
+remove_item_from_overview_cb (JSContextRef context,
+                              JSObjectRef function,
+                              JSObjectRef this_object,
+                              size_t argument_count,
+                              const JSValueRef arguments[],
+                              JSValueRef *exception)
+{
+  EphyWebOverview *overview;
+  JSStringRef result_string_js;
+  size_t max_size;
+  char *result_string;
+
+  overview = EPHY_WEB_OVERVIEW (JSObjectGetPrivate (this_object));
+
+  result_string_js = JSValueToStringCopy (context, arguments[0], NULL);
+  max_size = JSStringGetMaximumUTF8CStringSize (result_string_js);
+
+  result_string = g_malloc (max_size);
+  JSStringGetUTF8CString (result_string_js, result_string, max_size);
+  g_signal_emit (overview, signals[ITEM_REMOVED], 0, result_string);
+  JSStringRelease (result_string_js);
+  g_free (result_string);
+
+  return JSValueMakeUndefined (context);
+}
+
+static const JSStaticFunction overview_static_funcs[] =
+{
+  { "removeItemFromOverview", remove_item_from_overview_cb, kJSPropertyAttributeReadOnly | 
kJSPropertyAttributeDontDelete },
+  { NULL, NULL, 0 }
+};
+
+static const JSClassDefinition overview_class_def =
+{
+  0,                     /* version */
+  kJSClassAttributeNone, /* attributes */
+  "Overview",            /* className */
+  NULL,                  /* parentClass */
+  NULL,                  /* staticValues */
+  overview_static_funcs, /* staticFunctions */
+  NULL,                  /* initialize */
+  NULL,                  /* finalize */
+  NULL,                  /* hasProperty */
+  NULL,                  /* getProperty */
+  NULL,                  /* setProperty */
+  NULL,                  /* deleteProperty */
+  NULL,                  /* getPropertyNames */
+  NULL,                  /* callAsFunction */
+  NULL,                  /* callAsConstructor */
+  NULL,                  /* hasInstance */
+  NULL                   /* convertToType */
+};
+
+void
+ephy_web_overview_init_js (EphyWebOverview *overview,
+                           JSGlobalContextRef context)
+{
+  JSObjectRef global_object;
+  JSClassRef class_def;
+  JSObjectRef class_object;
+  JSStringRef str;
+
+  global_object = JSContextGetGlobalObject (context);
+
+  class_def = JSClassCreate (&overview_class_def);
+  class_object = JSObjectMake (context, class_def, overview);
+  str = JSStringCreateWithUTF8CString ("Overview");
+  JSObjectSetProperty (context, global_object, str, class_object, kJSPropertyAttributeNone, NULL);
+  JSStringRelease (str);
+
+  /* FIXME: check leaks here */
+}
diff --git a/embed/web-extension/ephy-web-overview.h b/embed/web-extension/ephy-web-overview.h
new file mode 100644
index 0000000..b52f7a5
--- /dev/null
+++ b/embed/web-extension/ephy-web-overview.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2014 Igalia S.L.
+ *
+ *  This program 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 2, or (at your option)
+ *  any later version.
+ *
+ *  This program 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 this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef _EPHY_WEB_OVERVIEW_H
+#define _EPHY_WEB_OVERVIEW_H
+
+#include "ephy-web-overview-model.h"
+#include <webkit2/webkit-web-extension.h>
+#include <JavaScriptCore/JavaScript.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_WEB_OVERVIEW            (ephy_web_overview_get_type())
+#define EPHY_WEB_OVERVIEW(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), EPHY_TYPE_WEB_OVERVIEW, 
EphyWebOverview))
+#define EPHY_IS_WEB_OVERVIEW(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EPHY_TYPE_WEB_OVERVIEW))
+#define EPHY_WEB_OVERVIEW_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EPHY_TYPE_WEB_OVERVIEW, 
EphyWebOverviewClass))
+#define EPHY_IS_WEB_OVERVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EPHY_TYPE_WEB_OVERVIEW))
+#define EPHY_WEB_OVERVIEW_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), EPHY_TYPE_WEB_OVERVIEW, 
EphyWebOverviewClass))
+
+typedef struct _EphyWebOverview        EphyWebOverview;
+typedef struct _EphyWebOverviewClass   EphyWebOverviewClass;
+typedef struct _EphyWebOverviewPrivate EphyWebOverviewPrivate;
+
+struct _EphyWebOverview
+{
+  GObject parent;
+
+  EphyWebOverviewPrivate *priv;
+};
+
+struct _EphyWebOverviewClass
+{
+  GObjectClass parent_class;
+};
+
+GType            ephy_web_overview_get_type (void) G_GNUC_CONST;
+
+EphyWebOverview *ephy_web_overview_new      (WebKitWebPage        *web_page,
+                                             EphyWebOverviewModel *model);
+void             ephy_web_overview_init_js  (EphyWebOverview      *overview,
+                                             JSGlobalContextRef    context);
+
+G_END_DECLS
+
+#endif /* _EPHY_WEB_OVERVIEW_H */
diff --git a/src/resources/overview.html b/src/resources/overview.html
index 56d2f45..2cfd6f4 100755
--- a/src/resources/overview.html
+++ b/src/resources/overview.html
@@ -141,7 +141,7 @@
 
   <div id="overview">
     <div id="grid">
-      <ul>
+      <ul id="item-list">
         %s
       </ul>
      </div>


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