[epiphany/wip/libdazzle: 1/2] Stop using DzlSuggestionEntry for now



commit df1db5026cef1343d33e11a1bd370b41ea1eeb14
Author: Michael Catanzaro <mcatanzaro igalia com>
Date:   Wed Nov 22 17:48:32 2017 -0600

    Stop using DzlSuggestionEntry for now
    
    It's very promising, but not ready for the master branch yet... poor
    Carlos Garcia has been suffering this whole time, because I was not
    using it regularly and did not realize how many problems it had.
    
    It's a great widget and a great library, and we should definitely aim to
    bring it back ASAP. But let's not break master in the meantime.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=790732

 embed/ephy-embed-shell.c                    |    3 +-
 embed/ephy-embed-shell.h                    |    5 +-
 embed/meson.build                           |    1 -
 lib/ephy-suggestion.c                       |  174 --------
 lib/ephy-suggestion.h                       |   37 --
 lib/meson.build                             |    2 -
 lib/widgets/contrib/gd-two-lines-renderer.c |  621 ++++++++++++++++++++++++++
 lib/widgets/contrib/gd-two-lines-renderer.h |   75 ++++
 lib/widgets/ephy-location-entry.c           |  378 +++++++++++++++--
 lib/widgets/ephy-location-entry.h           |   20 +-
 lib/widgets/meson.build                     |    2 +-
 meson.build                                 |    1 -
 org.gnome.Epiphany.json                     |   12 -
 src/ephy-completion-model.c                 |  625 +++++++++++++++++++++++++++
 src/ephy-completion-model.h                 |   53 +++
 src/ephy-location-controller.c              |  133 ++++++-
 src/ephy-suggestion-model.c                 |  476 --------------------
 src/ephy-suggestion-model.h                 |   46 --
 src/ephy-window.c                           |   22 +-
 src/ephy-window.h                           |    3 +-
 src/meson.build                             |    3 +-
 src/search-provider/ephy-search-provider.c  |  121 ++++--
 tests/ephy-completion-model-test.c          |  101 +++++
 tests/ephy-location-entry-test.c            |  160 +++++++
 tests/meson.build                           |   14 +-
 25 files changed, 2220 insertions(+), 868 deletions(-)
---
diff --git a/embed/ephy-embed-shell.c b/embed/ephy-embed-shell.c
index 0c1d38a..d0751ff 100644
--- a/embed/ephy-embed-shell.c
+++ b/embed/ephy-embed-shell.c
@@ -43,7 +43,6 @@
 #include "ephy-web-app-utils.h"
 #include "ephy-web-extension-proxy.h"
 
-#include <dazzle.h>
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
 #include <stdlib.h>
@@ -104,7 +103,7 @@ static EphyEmbedShell *embed_shell = NULL;
 
 static void ephy_embed_shell_tabs_catalog_iface_init (EphyTabsCatalogInterface *iface);
 
-G_DEFINE_TYPE_WITH_CODE (EphyEmbedShell, ephy_embed_shell, DZL_TYPE_APPLICATION,
+G_DEFINE_TYPE_WITH_CODE (EphyEmbedShell, ephy_embed_shell, GTK_TYPE_APPLICATION,
                          G_ADD_PRIVATE (EphyEmbedShell)
                          G_IMPLEMENT_INTERFACE (EPHY_TYPE_TABS_CATALOG,
                                                 ephy_embed_shell_tabs_catalog_iface_init))
diff --git a/embed/ephy-embed-shell.h b/embed/ephy-embed-shell.h
index c5b5dfd..c933d50 100644
--- a/embed/ephy-embed-shell.h
+++ b/embed/ephy-embed-shell.h
@@ -21,7 +21,6 @@
 
 #pragma once
 
-#include <dazzle.h>
 #include <webkit2/webkit2.h>
 
 #include "ephy-downloads-manager.h"
@@ -35,7 +34,7 @@ G_BEGIN_DECLS
 
 #define EPHY_TYPE_EMBED_SHELL (ephy_embed_shell_get_type ())
 
-G_DECLARE_DERIVABLE_TYPE (EphyEmbedShell, ephy_embed_shell, EPHY, EMBED_SHELL, DzlApplication)
+G_DECLARE_DERIVABLE_TYPE (EphyEmbedShell, ephy_embed_shell, EPHY, EMBED_SHELL, GtkApplication)
 
 typedef enum
 {
@@ -50,7 +49,7 @@ typedef enum
 
 struct _EphyEmbedShellClass
 {
-  DzlApplicationClass parent_class;
+  GtkApplicationClass parent_class;
 
   void    (* restored_window)  (EphyEmbedShell *shell);
 };
diff --git a/embed/meson.build b/embed/meson.build
index 99315d7..1f924f2 100644
--- a/embed/meson.build
+++ b/embed/meson.build
@@ -37,7 +37,6 @@ libephyembed_deps = [
   glib_dep,
   gtk_dep,
   icu_uc_dep,
-  libdazzle_dep,
   libsecret_dep,
   libsoup_dep,
   m_dep,
diff --git a/lib/meson.build b/lib/meson.build
index b9f5d8e..298dd7e 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -33,7 +33,6 @@ libephymisc_sources = [
   'ephy-sqlite-connection.c',
   'ephy-sqlite-statement.c',
   'ephy-string.c',
-  'ephy-suggestion.c',
   'ephy-sync-utils.c',
   'ephy-time-helpers.c',
   'ephy-uri-helpers.c',
@@ -62,7 +61,6 @@ libephymisc_deps = [
   gtk_dep,
   icu_uc_dep,
   json_glib_dep,
-  libdazzle_dep,
   libsecret_dep,
   libsoup_dep,
   libxml_dep,
diff --git a/lib/widgets/contrib/gd-two-lines-renderer.c b/lib/widgets/contrib/gd-two-lines-renderer.c
new file mode 100644
index 0000000..5a029f7
--- /dev/null
+++ b/lib/widgets/contrib/gd-two-lines-renderer.c
@@ -0,0 +1,621 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by 
+ * the Free Software Foundation; either version 2 of the License, 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 Lesser General Public 
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License 
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Author: Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+#include "gd-two-lines-renderer.h"
+#include <string.h>
+
+#define SUBTITLE_DIM_PERCENTAGE 0.55
+#define SUBTITLE_SIZE_PERCENTAGE 0.82
+
+G_DEFINE_TYPE (GdTwoLinesRenderer, gd_two_lines_renderer, GTK_TYPE_CELL_RENDERER_TEXT)
+
+struct _GdTwoLinesRendererPrivate {
+  gchar *line_two;
+  gint text_lines;
+};
+
+enum {
+  PROP_TEXT_LINES = 1,
+  PROP_LINE_TWO,
+  NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+static PangoLayout *
+create_layout_with_attrs (GtkWidget *widget,
+                          const GdkRectangle *cell_area,
+                          GdTwoLinesRenderer *self,
+                          PangoEllipsizeMode ellipsize)
+{
+  PangoLayout *layout;
+  gint wrap_width, xpad;
+  PangoWrapMode wrap_mode;
+  PangoAlignment alignment;
+
+  g_object_get (self,
+                "wrap-width", &wrap_width,
+                "wrap-mode", &wrap_mode,
+                "alignment", &alignment,
+                "xpad", &xpad,
+                NULL);
+
+  layout = pango_layout_new (gtk_widget_get_pango_context (widget));
+
+  pango_layout_set_ellipsize (layout, ellipsize);
+  pango_layout_set_alignment (layout, alignment);
+
+  if (wrap_width != -1)
+    {
+      pango_layout_set_width (layout, wrap_width * PANGO_SCALE);
+      pango_layout_set_wrap (layout, wrap_mode);
+    }
+  else
+    {
+      if (cell_area != NULL)
+        pango_layout_set_width (layout, (cell_area->width - 2 * xpad) * PANGO_SCALE);
+      else
+        pango_layout_set_width (layout, -1);
+
+      pango_layout_set_wrap (layout, PANGO_WRAP_CHAR);
+    }
+
+  return layout;
+}
+
+static void
+apply_subtitle_style_to_layout (GtkStyleContext *context,
+                                PangoLayout     *layout,
+                                GtkStateFlags    flags)
+{
+  PangoFontDescription *desc;
+  PangoAttrList *layout_attr;
+  PangoAttribute *attr_alpha;
+
+  gtk_style_context_save (context);
+  gtk_style_context_set_state (context, flags);
+  gtk_style_context_get (context, gtk_style_context_get_state (context),
+                         "font", &desc,
+                         NULL);
+  gtk_style_context_restore (context);
+
+  /* Set the font size */
+  pango_font_description_set_size (desc, pango_font_description_get_size (desc) * SUBTITLE_SIZE_PERCENTAGE);
+  pango_layout_set_font_description (layout, desc);
+  pango_font_description_free (desc);
+
+  /* Set the font alpha */
+  layout_attr = pango_attr_list_new ();
+  attr_alpha = pango_attr_foreground_alpha_new (SUBTITLE_DIM_PERCENTAGE * 65535);
+  pango_attr_list_insert (layout_attr, attr_alpha);
+
+  pango_layout_set_attributes (layout, layout_attr);
+  pango_attr_list_unref (layout_attr);
+}
+
+static void
+gd_two_lines_renderer_prepare_layouts (GdTwoLinesRenderer *self,
+                                       const GdkRectangle *cell_area,
+                                       GtkWidget *widget,
+                                       PangoLayout **layout_one,
+                                       PangoLayout **layout_two)
+{
+  PangoLayout *line_one;
+  PangoLayout *line_two = NULL;
+  gchar *text = NULL;
+
+  g_object_get (self,
+                "text", &text,
+                NULL);
+
+  line_one = create_layout_with_attrs (widget, cell_area,
+                                       self, PANGO_ELLIPSIZE_MIDDLE);
+
+  if (self->priv->line_two == NULL ||
+      g_strcmp0 (self->priv->line_two, "") == 0)
+    {
+      pango_layout_set_height (line_one, - (self->priv->text_lines));
+
+      if (text != NULL)
+        pango_layout_set_text (line_one, text, -1);
+    }
+  else
+    {
+      GtkStyleContext *context;
+
+      line_two = create_layout_with_attrs (widget, cell_area,
+                                           self, PANGO_ELLIPSIZE_END);
+
+      context = gtk_widget_get_style_context (widget);
+      gtk_style_context_save (context);
+      apply_subtitle_style_to_layout (context, line_two, GTK_STATE_FLAG_NORMAL);
+      gtk_style_context_restore (context);
+
+      pango_layout_set_height (line_one, - (self->priv->text_lines - 1));
+      pango_layout_set_height (line_two, -1);
+      pango_layout_set_text (line_two, self->priv->line_two, -1);
+
+      if (text != NULL)
+        pango_layout_set_text (line_one, text, -1);
+    }
+
+  if (layout_one)
+    *layout_one = line_one;
+  if (layout_two)
+    *layout_two = line_two;
+
+  g_free (text);
+}
+
+static void
+gd_two_lines_renderer_get_size (GtkCellRenderer *cell,
+                                GtkWidget *widget,
+                                PangoLayout *layout_1,
+                                PangoLayout *layout_2,
+                                gint *width,
+                                gint *height,
+                                const GdkRectangle *cell_area,
+                                gint *x_offset_1,
+                                gint *x_offset_2,
+                                gint *y_offset)
+{
+  GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (cell);
+  gint xpad, ypad;
+  PangoLayout *layout_one, *layout_two;
+  GdkRectangle layout_one_rect, layout_two_rect, layout_union;
+
+  if (layout_1 == NULL)
+    {
+      gd_two_lines_renderer_prepare_layouts (self, cell_area, widget, &layout_one, &layout_two);
+    }
+  else
+    {
+      layout_one = g_object_ref (layout_1);
+
+      if (layout_2 != NULL)
+        layout_two = g_object_ref (layout_2);
+      else
+        layout_two = NULL;
+    }
+
+  gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
+  pango_layout_get_pixel_extents (layout_one, NULL, (PangoRectangle *) &layout_one_rect);
+
+  if (layout_two != NULL)
+    {
+      pango_layout_get_pixel_extents (layout_two, NULL, (PangoRectangle *) &layout_two_rect);
+
+      layout_union.width = MAX (layout_one_rect.width, layout_two_rect.width);
+      layout_union.height = layout_one_rect.height + layout_two_rect.height;
+    }
+  else
+    {
+      layout_union = layout_one_rect;
+    }
+
+  if (cell_area)
+    {
+      gfloat xalign, yalign;
+
+      gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
+
+      layout_union.width  = MIN (layout_union.width, cell_area->width - 2 * xpad);
+      layout_union.height = MIN (layout_union.height, cell_area->height - 2 * ypad);
+
+      if (x_offset_1)
+       {
+         if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+           *x_offset_1 = (1.0 - xalign) * (cell_area->width - (layout_one_rect.width + (2 * xpad)));
+         else 
+           *x_offset_1 = xalign * (cell_area->width - (layout_one_rect.width + (2 * xpad)));
+
+          *x_offset_1 = MAX (*x_offset_1, 0);
+       }
+      if (x_offset_2)
+        {
+          if (layout_two != NULL)
+            {
+              if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+                *x_offset_2 = (1.0 - xalign) * (cell_area->width - (layout_two_rect.width + (2 * xpad)));
+              else 
+                *x_offset_2 = xalign * (cell_area->width - (layout_two_rect.width + (2 * xpad)));
+
+              *x_offset_2 = MAX (*x_offset_2, 0);
+            }
+          else
+            {
+              *x_offset_2 = 0;
+            }
+        }
+
+      if (y_offset)
+       {
+         *y_offset = yalign * (cell_area->height - (layout_union.height + (2 * ypad)));
+         *y_offset = MAX (*y_offset, 0);
+       }
+    }
+  else
+    {
+      if (x_offset_1) *x_offset_1 = 0;
+      if (x_offset_2) *x_offset_2 = 0;
+      if (y_offset) *y_offset = 0;
+    }
+
+  g_clear_object (&layout_one);
+  g_clear_object (&layout_two);
+
+  if (height)
+    *height = ypad * 2 + layout_union.height;
+
+  if (width)
+    *width = xpad * 2 + layout_union.width;
+}
+
+static void
+gd_two_lines_renderer_render (GtkCellRenderer      *cell,
+                              cairo_t              *cr,
+                              GtkWidget            *widget,
+                              const GdkRectangle   *background_area,
+                              const GdkRectangle   *cell_area,
+                              GtkCellRendererState  flags)
+{
+  GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (cell);
+  GtkStyleContext *context;
+  gint line_one_height;
+  GtkStateFlags state;
+  GdkRectangle area, render_area = *cell_area;
+  gint xpad, ypad, x_offset_1, x_offset_2, y_offset;
+  PangoLayout *layout_one, *layout_two;
+  PangoRectangle layout_rect;
+
+  /* fetch common information */
+  context = gtk_widget_get_style_context (widget);
+  gd_two_lines_renderer_prepare_layouts (self, cell_area, widget, &layout_one, &layout_two);
+  gd_two_lines_renderer_get_size (cell, widget,
+                                  layout_one, layout_two,
+                                  NULL, NULL,
+                                  cell_area,
+                                  &x_offset_1, &x_offset_2, &y_offset);
+  gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
+
+  area = *cell_area;
+  area.x += xpad;
+  area.y += ypad;
+
+  /* now render the first layout */
+  pango_layout_get_pixel_extents (layout_one, NULL, &layout_rect);
+
+  render_area = area;
+  render_area.x += x_offset_1 - layout_rect.x;
+
+  gtk_render_layout (context, cr,
+                     render_area.x,
+                     render_area.y,
+                     layout_one);
+
+  /* render the second layout */
+  if (layout_two != NULL)
+    {
+      pango_layout_get_pixel_size (layout_one,
+                                   NULL, &line_one_height);
+
+      gtk_style_context_save (context);
+
+      apply_subtitle_style_to_layout (context, layout_two, flags);
+
+      state = gtk_cell_renderer_get_state (cell, widget, flags);
+      gtk_style_context_set_state (context, state);
+
+      pango_layout_get_pixel_extents (layout_two, NULL, &layout_rect);
+
+      render_area = area;
+      render_area.x += x_offset_2 - layout_rect.x;
+      render_area.y += line_one_height;
+
+      gtk_render_layout (context, cr,
+                         render_area.x,
+                         render_area.y,
+                         layout_two);
+
+      gtk_style_context_restore (context);
+    }
+
+  g_clear_object (&layout_one);
+  g_clear_object (&layout_two);
+}
+
+static void
+gd_two_lines_renderer_get_preferred_width (GtkCellRenderer *cell,
+                                           GtkWidget       *widget,
+                                           gint            *minimum_size,
+                                           gint            *natural_size)
+{
+  PangoContext *context;
+  PangoFontMetrics *metrics;
+  PangoFontDescription *font_desc;
+  GtkStyleContext *style_context;
+  gint nat_width, min_width;
+  gint xpad, char_width, wrap_width, text_width;
+  gint width_chars, ellipsize_chars;
+
+  g_object_get (cell,
+                "xpad", &xpad,
+                "width-chars", &width_chars,
+                "wrap-width", &wrap_width,
+                NULL);
+  style_context = gtk_widget_get_style_context (widget);
+  gtk_cell_renderer_get_padding (cell, &xpad, NULL);
+
+  gd_two_lines_renderer_get_size (cell, widget,
+                                  NULL, NULL,
+                                  &text_width, NULL,
+                                  NULL, 
+                                  NULL, NULL, NULL);
+
+  /* Fetch the average size of a character */
+  context = gtk_widget_get_pango_context (widget);
+  gtk_style_context_save (style_context);
+  gtk_style_context_set_state (style_context, 0);
+  gtk_style_context_get (style_context, gtk_style_context_get_state (style_context),
+                         "font", &font_desc, NULL);
+  gtk_style_context_restore (style_context);
+  metrics = pango_context_get_metrics (context, font_desc,
+                                       pango_context_get_language (context));
+
+  char_width = pango_font_metrics_get_approximate_char_width (metrics);
+
+  pango_font_metrics_unref (metrics);
+  pango_font_description_free (font_desc);
+
+  /* enforce minimum width for ellipsized labels at ~3 chars */
+  ellipsize_chars = 3;
+
+  /* If no width-chars set, minimum for wrapping text will be the wrap-width */
+  if (wrap_width > -1)
+    min_width = xpad * 2 + MIN (text_width, wrap_width);
+  else
+    min_width = xpad * 2 +
+      MIN (text_width,
+           (PANGO_PIXELS (char_width) * MAX (width_chars, ellipsize_chars)));
+
+  if (width_chars > 0)
+    nat_width = xpad * 2 +
+      MAX ((PANGO_PIXELS (char_width) * width_chars), text_width);
+  else
+    nat_width = xpad * 2 + text_width;
+
+  nat_width = MAX (nat_width, min_width);
+
+  if (minimum_size)
+    *minimum_size = min_width;
+
+  if (natural_size)
+    *natural_size = nat_width;
+}
+
+static void
+gd_two_lines_renderer_get_preferred_height_for_width (GtkCellRenderer *cell,
+                                                      GtkWidget       *widget,
+                                                      gint             width,
+                                                      gint            *minimum_size,
+                                                      gint            *natural_size)
+{
+  GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (cell);
+  PangoLayout *layout_one, *layout_two;
+  gint text_height, wrap_width;
+  gint xpad, ypad;
+
+  gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
+  g_object_get (cell, "wrap-width", &wrap_width, NULL);
+  gd_two_lines_renderer_prepare_layouts (self, NULL, widget, &layout_one, &layout_two);
+
+  if (wrap_width != -1)
+    wrap_width = MIN (width - 2 * xpad, wrap_width);
+  else
+    wrap_width = width - 2 * xpad;
+
+  pango_layout_set_width (layout_one, wrap_width);
+  if (layout_two != NULL)
+    pango_layout_set_width (layout_two, wrap_width);
+
+  gd_two_lines_renderer_get_size (cell, widget,
+                                  layout_one, layout_two,
+                                  NULL, &text_height,
+                                  NULL, 
+                                  NULL, NULL, NULL);
+
+  text_height += 2 * ypad;
+
+  if (minimum_size != NULL)
+    *minimum_size = text_height;
+
+  if (natural_size != NULL)
+    *natural_size = text_height;
+
+  g_clear_object (&layout_one);
+  g_clear_object (&layout_two);
+}
+
+static void
+gd_two_lines_renderer_get_preferred_height (GtkCellRenderer *cell,
+                                            GtkWidget       *widget,
+                                            gint            *minimum_size,
+                                            gint            *natural_size)
+{
+  gint min_width;
+
+  gtk_cell_renderer_get_preferred_width (cell, widget, &min_width, NULL);
+  gd_two_lines_renderer_get_preferred_height_for_width (cell, widget, min_width,
+                                                        minimum_size, natural_size);
+}
+
+static void
+gd_two_lines_renderer_get_aligned_area (GtkCellRenderer      *cell,
+                                        GtkWidget            *widget,
+                                        GtkCellRendererState  flags,
+                                        const GdkRectangle   *cell_area,
+                                        GdkRectangle         *aligned_area)
+{
+  GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (cell);
+  gint x_offset, x_offset_1, x_offset_2, y_offset;
+  PangoLayout *layout_one, *layout_two;
+
+  /* fetch common information */
+  gd_two_lines_renderer_prepare_layouts (self, cell_area, widget, &layout_one, &layout_two);
+  gd_two_lines_renderer_get_size (cell, widget,
+                                  layout_one, layout_two,
+                                  &aligned_area->width, &aligned_area->height,
+                                  cell_area,
+                                  &x_offset_1, &x_offset_2, &y_offset);
+
+  x_offset = MIN (x_offset_1, x_offset_2);
+
+  aligned_area->x = cell_area->x + x_offset;
+  aligned_area->y = cell_area->y;
+
+  g_clear_object (&layout_one);
+  g_clear_object (&layout_two);
+}
+
+static void
+gd_two_lines_renderer_set_line_two (GdTwoLinesRenderer *self,
+                                    const gchar *line_two)
+{
+  if (g_strcmp0 (self->priv->line_two, line_two) == 0)
+    return;
+
+  g_free (self->priv->line_two);
+  self->priv->line_two = g_strdup (line_two);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LINE_TWO]);
+}
+
+static void
+gd_two_lines_renderer_set_text_lines (GdTwoLinesRenderer *self,
+                                      gint text_lines)
+{
+  if (self->priv->text_lines == text_lines)
+    return;
+
+  self->priv->text_lines = text_lines;
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TEXT_LINES]);
+}
+
+static void
+gd_two_lines_renderer_set_property (GObject    *object,
+                                    guint       property_id,
+                                    const GValue     *value,
+                                    GParamSpec *pspec)
+{
+  GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (object);
+
+  switch (property_id)
+    {
+    case PROP_TEXT_LINES:
+      gd_two_lines_renderer_set_text_lines (self, g_value_get_int (value));
+      break;
+    case PROP_LINE_TWO:
+      gd_two_lines_renderer_set_line_two (self, g_value_get_string (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gd_two_lines_renderer_get_property (GObject    *object,
+                                    guint       property_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (object);
+
+  switch (property_id)
+    {
+    case PROP_TEXT_LINES:
+      g_value_set_int (value, self->priv->text_lines);
+      break;
+    case PROP_LINE_TWO:
+      g_value_set_string (value, self->priv->line_two);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gd_two_lines_renderer_finalize (GObject *object)
+{
+  GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (object);
+
+  g_free (self->priv->line_two);
+
+  G_OBJECT_CLASS (gd_two_lines_renderer_parent_class)->finalize (object);
+}
+
+static void
+gd_two_lines_renderer_class_init (GdTwoLinesRendererClass *klass)
+{
+  GtkCellRendererClass *cclass = GTK_CELL_RENDERER_CLASS (klass);
+  GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+  cclass->render = gd_two_lines_renderer_render;
+  cclass->get_preferred_width = gd_two_lines_renderer_get_preferred_width;
+  cclass->get_preferred_height = gd_two_lines_renderer_get_preferred_height;
+  cclass->get_preferred_height_for_width = gd_two_lines_renderer_get_preferred_height_for_width;
+  cclass->get_aligned_area = gd_two_lines_renderer_get_aligned_area;
+
+  oclass->set_property = gd_two_lines_renderer_set_property;
+  oclass->get_property = gd_two_lines_renderer_get_property;
+  oclass->finalize = gd_two_lines_renderer_finalize;
+  
+  properties[PROP_TEXT_LINES] =
+    g_param_spec_int ("text-lines",
+                      "Lines of text",
+                      "The total number of lines to be displayed",
+                      2, G_MAXINT, 2,
+                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_LINE_TWO] =
+    g_param_spec_string ("line-two",
+                         "Second line",
+                         "Second line",
+                         NULL,
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  g_type_class_add_private (klass, sizeof (GdTwoLinesRendererPrivate));
+  g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+static void
+gd_two_lines_renderer_init (GdTwoLinesRenderer *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_TWO_LINES_RENDERER,
+                                            GdTwoLinesRendererPrivate);
+}
+
+GtkCellRenderer *
+gd_two_lines_renderer_new (void)
+{
+  return g_object_new (GD_TYPE_TWO_LINES_RENDERER, NULL);
+}
diff --git a/lib/widgets/contrib/gd-two-lines-renderer.h b/lib/widgets/contrib/gd-two-lines-renderer.h
new file mode 100644
index 0000000..23bf70f
--- /dev/null
+++ b/lib/widgets/contrib/gd-two-lines-renderer.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by 
+ * the Free Software Foundation; either version 2 of the License, 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 Lesser General Public 
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License 
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Author: Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+#ifndef _GD_TWO_LINES_RENDERER_H
+#define _GD_TWO_LINES_RENDERER_H
+
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_TWO_LINES_RENDERER gd_two_lines_renderer_get_type()
+
+#define GD_TWO_LINES_RENDERER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+   GD_TYPE_TWO_LINES_RENDERER, GdTwoLinesRenderer))
+
+#define GD_TWO_LINES_RENDERER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), \
+   GD_TYPE_TWO_LINES_RENDERER, GdTwoLinesRendererClass))
+
+#define GD_IS_TWO_LINES_RENDERER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+   GD_TYPE_TWO_LINES_RENDERER))
+
+#define GD_IS_TWO_LINES_RENDERER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+   GD_TYPE_TWO_LINES_RENDERER))
+
+#define GD_TWO_LINES_RENDERER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+   GD_TYPE_TWO_LINES_RENDERER, GdTwoLinesRendererClass))
+
+typedef struct _GdTwoLinesRenderer GdTwoLinesRenderer;
+typedef struct _GdTwoLinesRendererClass GdTwoLinesRendererClass;
+typedef struct _GdTwoLinesRendererPrivate GdTwoLinesRendererPrivate;
+
+struct _GdTwoLinesRenderer
+{
+  GtkCellRendererText parent;
+
+  GdTwoLinesRendererPrivate *priv;
+};
+
+struct _GdTwoLinesRendererClass
+{
+  GtkCellRendererTextClass parent_class;
+};
+
+GType gd_two_lines_renderer_get_type (void) G_GNUC_CONST;
+
+GtkCellRenderer *gd_two_lines_renderer_new (void);
+
+G_END_DECLS
+
+#endif /* _GD_TWO_LINES_RENDERER_H */
diff --git a/lib/widgets/ephy-location-entry.c b/lib/widgets/ephy-location-entry.c
index 472d0ab..74b590f 100644
--- a/lib/widgets/ephy-location-entry.c
+++ b/lib/widgets/ephy-location-entry.c
@@ -31,11 +31,10 @@
 #include "ephy-gui.h"
 #include "ephy-lib-type-builtins.h"
 #include "ephy-signal-accumulator.h"
-#include "ephy-suggestion.h"
 #include "ephy-title-widget.h"
 #include "ephy-uri-helpers.h"
+#include "gd-two-lines-renderer.h"
 
-#include <dazzle.h>
 #include <gdk/gdkkeysyms.h>
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
@@ -55,7 +54,7 @@
  */
 
 struct _EphyLocationEntry {
-  DzlSuggestionEntry parent_instance;
+  GtkEntry parent_instance;
 
   GtkTreeModel *model;
 
@@ -92,6 +91,12 @@ struct _EphyLocationEntry {
 
 static gboolean ephy_location_entry_reset_internal (EphyLocationEntry *, gboolean);
 
+static void extracell_data_func (GtkCellLayout   *cell_layout,
+                                 GtkCellRenderer *cell,
+                                 GtkTreeModel    *tree_model,
+                                 GtkTreeIter     *iter,
+                                 gpointer         data);
+
 enum {
   PROP_0,
   PROP_ADDRESS,
@@ -110,17 +115,11 @@ static gint signals[LAST_SIGNAL] = { 0 };
 
 static void ephy_location_entry_title_widget_interface_init (EphyTitleWidgetInterface *iface);
 
-G_DEFINE_TYPE_WITH_CODE (EphyLocationEntry, ephy_location_entry, DZL_TYPE_SUGGESTION_ENTRY,
+G_DEFINE_TYPE_WITH_CODE (EphyLocationEntry, ephy_location_entry, GTK_TYPE_ENTRY,
                          G_IMPLEMENT_INTERFACE (EPHY_TYPE_TITLE_WIDGET,
                                                 ephy_location_entry_title_widget_interface_init))
 
 static void
-ephy_location_entry_activate (EphyLocationEntry *entry)
-{
-  g_signal_emit_by_name (entry, "activate");
-}
-
-static void
 update_address_state (EphyLocationEntry *entry)
 {
   const char *text;
@@ -274,12 +273,8 @@ ephy_location_entry_get_property (GObject    *object,
 static void
 ephy_location_entry_constructed (GObject *object)
 {
-  EphyLocationEntry *entry = EPHY_LOCATION_ENTRY (object);
-
   G_OBJECT_CLASS (ephy_location_entry_parent_class)->constructed (object);
 
-  dzl_suggestion_entry_set_position_func (DZL_SUGGESTION_ENTRY (entry), 
dzl_suggestion_entry_window_position_func, NULL, NULL);
-
 #if GTK_CHECK_VERSION(3, 22, 20)
   gtk_entry_set_input_hints (GTK_ENTRY (object), GTK_INPUT_HINT_NO_EMOJI);
 #endif
@@ -366,16 +361,6 @@ ephy_location_entry_cut_clipboard (GtkEntry *entry)
 }
 
 static void
-ephy_location_entry_suggestion_activated (DzlSuggestionEntry *entry,
-                                          DzlSuggestion      *suggestion)
-{
-  gtk_entry_set_text (GTK_ENTRY (entry), ephy_suggestion_get_uri (EPHY_SUGGESTION (suggestion)));
-
-  /* Now trigger the load.... */
-  ephy_location_entry_activate (EPHY_LOCATION_ENTRY (entry));
-}
-
-static void
 ephy_location_entry_title_widget_interface_init (EphyTitleWidgetInterface *iface)
 {
   iface->get_address = ephy_location_entry_title_widget_get_address;
@@ -390,9 +375,7 @@ ephy_location_entry_class_init (EphyLocationEntryClass *klass)
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
   GtkEntryClass *entry_class = GTK_ENTRY_CLASS (klass);
-  DzlSuggestionEntryClass *dzl_entry_class = DZL_SUGGESTION_ENTRY_CLASS (klass);
 
-  object_class->constructed = ephy_location_entry_constructed;
   object_class->get_property = ephy_location_entry_get_property;
   object_class->set_property = ephy_location_entry_set_property;
   object_class->constructed = ephy_location_entry_constructed;
@@ -404,8 +387,6 @@ ephy_location_entry_class_init (EphyLocationEntryClass *klass)
   entry_class->copy_clipboard = ephy_location_entry_copy_clipboard;
   entry_class->cut_clipboard = ephy_location_entry_cut_clipboard;
 
-  dzl_entry_class->suggestion_activated = ephy_location_entry_suggestion_activated;
-
   g_object_class_override_property (object_class, PROP_ADDRESS, "address");
   g_object_class_override_property (object_class, PROP_SECURITY_LEVEL, "security-level");
 
@@ -507,18 +488,103 @@ entry_key_press_cb (GtkEntry          *entry,
     /* Make sure the location is activated on CTRL+l even when the
      * completion popup is shown and have an active keyboard grab.
      */
-    ephy_location_entry_focus (location_entry);
+    ephy_location_entry_activate (location_entry);
   }
 
-  if (event->keyval == GDK_KEY_Return ||
-      event->keyval == GDK_KEY_KP_Enter ||
-      event->keyval == GDK_KEY_ISO_Enter)
-    ephy_location_entry_activate (location_entry);
+  return FALSE;
+}
+
+static gboolean
+entry_key_press_after_cb (GtkEntry          *entry,
+                          GdkEventKey       *event,
+                          EphyLocationEntry *lentry)
+{
+  guint state = event->state & gtk_accelerator_get_default_mod_mask ();
+
+  if ((event->keyval == GDK_KEY_Return ||
+       event->keyval == GDK_KEY_KP_Enter ||
+       event->keyval == GDK_KEY_ISO_Enter) &&
+      (state == GDK_CONTROL_MASK ||
+       state == (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) {
+    /* gtk_im_context_reset (entry->im_context); */
+
+    lentry->needs_reset = TRUE;
+    g_signal_emit_by_name (entry, "activate");
+
+    return TRUE;
+  }
+
+  if ((event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down)
+      && state == 0) {
+    /* If we are focusing the entry, with the cursor at the end of it
+     * we emit the changed signal, so that the completion popup appears */
+    const char *string;
+
+    string = gtk_entry_get_text (entry);
+    if (gtk_editable_get_position (GTK_EDITABLE (entry)) == (int)strlen (string)) {
+      g_signal_emit_by_name (entry, "changed", 0);
+      return TRUE;
+    }
+  }
 
   return FALSE;
 }
 
 static void
+entry_activate_after_cb (GtkEntry          *entry,
+                         EphyLocationEntry *lentry)
+{
+  lentry->user_changed = FALSE;
+
+  if (lentry->needs_reset) {
+    ephy_location_entry_reset_internal (lentry, TRUE);
+    lentry->needs_reset = FALSE;
+  }
+}
+
+static gboolean
+match_selected_cb (GtkEntryCompletion *completion,
+                   GtkTreeModel       *model,
+                   GtkTreeIter        *iter,
+                   EphyLocationEntry  *entry)
+{
+  char *item = NULL;
+  guint state;
+
+  gtk_tree_model_get (model, iter,
+                      entry->action_col, &item, -1);
+  if (item == NULL) return FALSE;
+
+  ephy_gui_get_current_event (NULL, &state, NULL);
+
+  entry->needs_reset = (state == GDK_CONTROL_MASK ||
+                        state == (GDK_CONTROL_MASK | GDK_SHIFT_MASK));
+
+  ephy_title_widget_set_address (EPHY_TITLE_WIDGET (entry), item);
+  /* gtk_im_context_reset (GTK_ENTRY (entry)->im_context); */
+  g_signal_emit_by_name (entry, "activate");
+
+  g_free (item);
+
+  return TRUE;
+}
+
+static void
+action_activated_after_cb (GtkEntryCompletion *completion,
+                           gint                index,
+                           EphyLocationEntry  *lentry)
+{
+  guint state, button;
+
+  ephy_gui_get_current_event (NULL, &state, &button);
+  if ((state == GDK_CONTROL_MASK ||
+       state == (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) ||
+      button == 2) {
+    ephy_location_entry_reset_internal (lentry, TRUE);
+  }
+}
+
+static void
 entry_clear_activate_cb (GtkMenuItem       *item,
                          EphyLocationEntry *entry)
 {
@@ -535,7 +601,7 @@ paste_received (GtkClipboard      *clipboard,
 {
   if (text) {
     gtk_entry_set_text (GTK_ENTRY (entry), text);
-    ephy_location_entry_activate (entry);
+    g_signal_emit_by_name (entry, "activate");
   }
 }
 
@@ -692,6 +758,11 @@ ephy_location_entry_construct_contents (EphyLocationEntry *lentry)
                     "signal::key-press-event", G_CALLBACK (entry_key_press_cb), lentry,
                     "signal::changed", G_CALLBACK (editable_changed_cb), lentry,
                     NULL);
+
+  g_signal_connect_after (entry, "key-press-event",
+                          G_CALLBACK (entry_key_press_after_cb), lentry);
+  g_signal_connect_after (entry, "activate",
+                          G_CALLBACK (entry_activate_after_cb), lentry);
 }
 
 static void
@@ -768,6 +839,238 @@ schedule_dns_prefetch (EphyLocationEntry *entry, guint interval, const gchar *ur
 }
 #endif
 
+static gboolean
+cursor_on_match_cb (GtkEntryCompletion *completion,
+                    GtkTreeModel       *model,
+                    GtkTreeIter        *iter,
+                    EphyLocationEntry  *le)
+{
+  char *url = NULL;
+  GtkWidget *entry;
+
+  gtk_tree_model_get (model, iter,
+                      le->url_col,
+                      &url, -1);
+  entry = gtk_entry_completion_get_entry (completion);
+
+  /* Prevent the update so we keep the highlight from our input.
+   * See textcell_data_func().
+   */
+  le->block_update = TRUE;
+  gtk_entry_set_text (GTK_ENTRY (entry), url);
+  gtk_editable_set_position (GTK_EDITABLE (entry), -1);
+  le->block_update = FALSE;
+
+#if 0
+/* FIXME: Refactor the DNS prefetch, this is a layering violation */
+  schedule_dns_prefetch (le, 250, (const gchar *)url);
+#endif
+
+  g_free (url);
+
+  return TRUE;
+}
+
+static void
+extracell_data_func (GtkCellLayout   *cell_layout,
+                     GtkCellRenderer *cell,
+                     GtkTreeModel    *tree_model,
+                     GtkTreeIter     *iter,
+                     gpointer         data)
+{
+  EphyLocationEntry *entry = EPHY_LOCATION_ENTRY (data);
+  gboolean is_bookmark = FALSE;
+  GValue visible = { 0, };
+
+  gtk_tree_model_get (tree_model, iter,
+                      entry->extra_col, &is_bookmark,
+                      -1);
+
+  if (is_bookmark)
+    g_object_set (cell,
+                  "icon-name", "user-bookmarks-symbolic",
+                  NULL);
+
+  g_value_init (&visible, G_TYPE_BOOLEAN);
+  g_value_set_boolean (&visible, is_bookmark);
+  g_object_set_property (G_OBJECT (cell), "visible", &visible);
+  g_value_unset (&visible);
+}
+
+/**
+ * ephy_location_entry_set_match_func:
+ * @entry: an #EphyLocationEntry widget
+ * @match_func: a #GtkEntryCompletionMatchFunc
+ * @user_data: user_data to pass to the @match_func
+ * @notify: a #GDestroyNotify, like the one given to
+ * gtk_entry_completion_set_match_func
+ *
+ * Sets the match_func for the internal #GtkEntryCompletion to @match_func.
+ *
+ **/
+void
+ephy_location_entry_set_match_func (EphyLocationEntry          *entry,
+                                    GtkEntryCompletionMatchFunc match_func,
+                                    gpointer                    user_data,
+                                    GDestroyNotify              notify)
+{
+  GtkEntryCompletion *completion;
+
+  completion = gtk_entry_get_completion (GTK_ENTRY (entry));
+  gtk_entry_completion_set_match_func (completion, match_func, user_data, notify);
+}
+
+/**
+ * ephy_location_entry_set_completion:
+ * @entry: an #EphyLocationEntry widget
+ * @model: the #GtkModel for the completion
+ * @text_col: column id to access #GtkModel relevant data
+ * @action_col: column id to access #GtkModel relevant data
+ * @keywords_col: column id to access #GtkModel relevant data
+ * @relevance_col: column id to access #GtkModel relevant data
+ * @url_col: column id to access #GtkModel relevant data
+ * @extra_col: column id to access #GtkModel relevant data
+ * @favicon_col: column id to access #GtkModel relevant data
+ *
+ * Initializes @entry to have a #GtkEntryCompletion using @model as the
+ * internal #GtkModel. The *_col arguments are for internal data retrieval from
+ * @model, like when setting the text property of one of the #GtkCellRenderer
+ * of the completion.
+ *
+ **/
+void
+ephy_location_entry_set_completion (EphyLocationEntry *entry,
+                                    GtkTreeModel      *model,
+                                    guint              text_col,
+                                    guint              action_col,
+                                    guint              keywords_col,
+                                    guint              relevance_col,
+                                    guint              url_col,
+                                    guint              extra_col,
+                                    guint              favicon_col)
+{
+  GtkEntryCompletion *completion;
+  GtkCellRenderer *cell;
+
+  entry->text_col = text_col;
+  entry->action_col = action_col;
+  entry->keywords_col = keywords_col;
+  entry->relevance_col = relevance_col;
+  entry->url_col = url_col;
+  entry->extra_col = extra_col;
+  entry->favicon_col = favicon_col;
+
+  completion = gtk_entry_completion_new ();
+  gtk_entry_completion_set_model (completion, model);
+  g_signal_connect (completion, "match-selected",
+                    G_CALLBACK (match_selected_cb), entry);
+  g_signal_connect_after (completion, "action-activated",
+                          G_CALLBACK (action_activated_after_cb), entry);
+
+  cell = gtk_cell_renderer_pixbuf_new ();
+  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion),
+                              cell, FALSE);
+  gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion),
+                                 cell, "pixbuf", favicon_col);
+
+  /* Pixel-perfect aligment with the location entry favicon
+   * (16x16). Consider that this /might/ depend on the theme.
+   *
+   * The GtkEntryCompletion can not be themed so we work-around
+   * that with padding and fixed sizes.
+   * For the first cell, this is:
+   *
+   * ___+++++iiiiiiiiiiiiiiii++__ttt...bbb++++++__
+   *
+   * _ = widget spacing, can not be handled (3 px)
+   * + = padding (5 px) (ICON_PADDING_LEFT)
+   * i = the icon (16 px) (ICON_CONTENT_WIDTH)
+   * + = padding (2 px) (ICON_PADDING_RIGHT) (cut by the fixed_size)
+   * _ = spacing between cells, can not be handled (2 px)
+   * t = the text (expands)
+   * b = bookmark icon (16 px)
+   * + = padding (6 px) (BKMK_PADDING_RIGHT)
+   * _ = widget spacing, can not be handled (2 px)
+   *
+   * Each character is a pixel.
+   *
+   * The text cell and the bookmark icon cell are much more
+   * flexible in its aligment, because they do not have to align
+   * with anything in the entry.
+   */
+
+#define ROW_PADDING_VERT 4
+
+#define ICON_PADDING_LEFT 5
+#define ICON_CONTENT_WIDTH 16
+#define ICON_PADDING_RIGHT 9
+
+#define ICON_CONTENT_HEIGHT 16
+
+#define TEXT_PADDING_LEFT 0
+
+#define BKMK_PADDING_RIGHT 6
+
+  gtk_cell_renderer_set_padding
+    (cell, ICON_PADDING_LEFT, ROW_PADDING_VERT);
+  gtk_cell_renderer_set_fixed_size
+    (cell,
+    (ICON_PADDING_LEFT + ICON_CONTENT_WIDTH + ICON_PADDING_RIGHT),
+    ICON_CONTENT_HEIGHT);
+  gtk_cell_renderer_set_alignment (cell, 0.0, 0.5);
+
+  cell = gd_two_lines_renderer_new ();
+  g_object_set (cell,
+                "ellipsize", PANGO_ELLIPSIZE_END,
+                "text-lines", 2,
+                NULL);
+  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion),
+                              cell, TRUE);
+  gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion),
+                                 cell, "text", text_col);
+  gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion),
+                                 cell, "line-two", url_col);
+
+  /* Pixel-perfect aligment with the text in the location entry.
+   * See above.
+   */
+  gtk_cell_renderer_set_padding
+    (cell, TEXT_PADDING_LEFT, ROW_PADDING_VERT);
+  gtk_cell_renderer_set_alignment (cell, 0.0, 0.5);
+
+  /*
+   * As the width of the entry completion is known in advance
+   * (as big as the entry you are completing on), we can set
+   * any fixed width (the 1 is just this random number here)
+   * Since the height is known too, we avoid computing the actual
+   * sizes of the cells, which takes a lot of CPU time and does
+   * not get used anyway.
+   */
+  gtk_cell_renderer_set_fixed_size (cell, 1, -1);
+  gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (cell), 2);
+
+  cell = gtk_cell_renderer_pixbuf_new ();
+  g_object_set (cell, "follow-state", TRUE, NULL);
+  gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (completion),
+                            cell, FALSE);
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (completion),
+                                      cell, extracell_data_func,
+                                      entry,
+                                      NULL);
+
+  /* Pixel-perfect aligment. This just keeps the same margin from
+   * the border than the favicon on the other side. See above. */
+  gtk_cell_renderer_set_padding
+    (cell, BKMK_PADDING_RIGHT, ROW_PADDING_VERT);
+
+  g_object_set (completion, "inline-selection", TRUE, NULL);
+  g_signal_connect (completion, "cursor-on-match",
+                    G_CALLBACK (cursor_on_match_cb), entry);
+
+  gtk_entry_set_completion (GTK_ENTRY (entry), completion);
+  g_object_unref (completion);
+}
+
 /**
  * ephy_location_entry_get_can_undo:
  * @entry: an #EphyLocationEntry widget
@@ -822,8 +1125,9 @@ ephy_location_entry_reset_internal (EphyLocationEntry *entry,
   ephy_title_widget_set_address (EPHY_TITLE_WIDGET (entry), text);
   g_free (url);
 
-  if (notify)
+  if (notify) {
     g_signal_emit (entry, signals[USER_CHANGED], 0);
+  }
 
   entry->user_changed = FALSE;
 
@@ -863,7 +1167,7 @@ ephy_location_entry_reset (EphyLocationEntry *entry)
 }
 
 /**
- * ephy_location_entry_focus:
+ * ephy_location_entry_activate:
  * @entry: an #EphyLocationEntry widget
  *
  * Set focus on @entry and select the text whithin. This is called when the
@@ -871,7 +1175,7 @@ ephy_location_entry_reset (EphyLocationEntry *entry)
  *
  **/
 void
-ephy_location_entry_focus (EphyLocationEntry *entry)
+ephy_location_entry_activate (EphyLocationEntry *entry)
 {
   GtkWidget *toplevel, *widget = GTK_WIDGET (entry);
 
diff --git a/lib/widgets/ephy-location-entry.h b/lib/widgets/ephy-location-entry.h
index 950d061..490524a 100644
--- a/lib/widgets/ephy-location-entry.h
+++ b/lib/widgets/ephy-location-entry.h
@@ -23,7 +23,6 @@
 
 #pragma once
 
-#include <dazzle.h>
 #include <gtk/gtk.h>
 
 #include "ephy-security-levels.h"
@@ -32,7 +31,7 @@ G_BEGIN_DECLS
 
 #define EPHY_TYPE_LOCATION_ENTRY (ephy_location_entry_get_type())
 
-G_DECLARE_FINAL_TYPE (EphyLocationEntry, ephy_location_entry, EPHY, LOCATION_ENTRY, DzlSuggestionEntry)
+G_DECLARE_FINAL_TYPE (EphyLocationEntry, ephy_location_entry, EPHY, LOCATION_ENTRY, GtkEntry)
 
 typedef enum {
   EPHY_LOCATION_ENTRY_BOOKMARK_ICON_HIDDEN,
@@ -42,6 +41,21 @@ typedef enum {
 
 GtkWidget      *ephy_location_entry_new                        (void);
 
+void            ephy_location_entry_set_completion             (EphyLocationEntry *entry,
+                                                                GtkTreeModel      *model,
+                                                                guint              text_col,
+                                                                guint              action_col,
+                                                                guint              keywords_col,
+                                                                guint              relevance_col,
+                                                                guint              url_col,
+                                                                guint              extra_col,
+                                                                guint              favicon_col);
+
+void            ephy_location_entry_set_match_func             (EphyLocationEntry           *entry,
+                                                                GtkEntryCompletionMatchFunc  match_func,
+                                                                gpointer                     user_data,
+                                                                GDestroyNotify               notify);
+
 gboolean        ephy_location_entry_get_can_undo               (EphyLocationEntry *entry);
 
 gboolean        ephy_location_entry_get_can_redo               (EphyLocationEntry *entry);
@@ -52,7 +66,7 @@ gboolean        ephy_location_entry_reset                      (EphyLocationEntr
 
 void            ephy_location_entry_undo_reset                 (EphyLocationEntry *entry);
 
-void            ephy_location_entry_focus                      (EphyLocationEntry *entry);
+void            ephy_location_entry_activate                   (EphyLocationEntry *entry);
 
 void            ephy_location_entry_set_bookmark_icon_state    (EphyLocationEntry                  *entry,
                                                                 EphyLocationEntryBookmarkIconState  state);
diff --git a/lib/widgets/meson.build b/lib/widgets/meson.build
index c927648..1c5c849 100644
--- a/lib/widgets/meson.build
+++ b/lib/widgets/meson.build
@@ -7,6 +7,7 @@ enums = gnome.mkenums_simple('ephy-widgets-type-builtins',
 )
 
 libephywidgets_sources = [
+  'contrib/gd-two-lines-renderer.c',
   'contrib/nautilus-floating-bar.c',
   'ephy-certificate-dialog.c',
   'ephy-downloads-popover.c',
@@ -29,7 +30,6 @@ libephywidgets_deps = [
   gio_dep,
   glib_dep,
   gtk_dep,
-  libdazzle_dep,
   libsoup_dep,
   webkit2gtk_dep
 ]
diff --git a/meson.build b/meson.build
index 9fa9df1..535d499 100644
--- a/meson.build
+++ b/meson.build
@@ -63,7 +63,6 @@ hogweed_dep = dependency('hogweed', version: nettle_requirement)
 icu_uc_dep = dependency('icu-uc', version: '>= 4.6')
 iso_codes_dep = dependency('iso-codes', version: '>= 0.35')
 json_glib_dep = dependency('json-glib-1.0', version: '>= 1.2.4')
-libdazzle_dep = dependency('libdazzle-1.0', version: '>= 3.25.90')
 libnotify_dep = dependency('libnotify', version: '>= 0.5.1')
 libsecret_dep = dependency('libsecret-1', version: '>= 0.14')
 libsoup_dep = dependency('libsoup-2.4', version: '>= 2.48.0')
diff --git a/org.gnome.Epiphany.json b/org.gnome.Epiphany.json
index 426b7b4..0cec50c 100644
--- a/org.gnome.Epiphany.json
+++ b/org.gnome.Epiphany.json
@@ -51,18 +51,6 @@
             ]
         },
         {
-            "name": "libdazzle",
-            "config-opts": [ "--libdir=/app/lib" ],
-            "buildsystem": "meson",
-            "builddir": true,
-            "sources": [
-                {
-                    "type": "git",
-                    "url": "https://git.gnome.org/browse/libdazzle";
-                }
-            ]
-        },
-        {
             "name": "epiphany",
             "config-opts": ["-Dtech_preview=true"],
             "buildsystem": "meson",
diff --git a/src/ephy-completion-model.c b/src/ephy-completion-model.c
new file mode 100644
index 0000000..e1faae1
--- /dev/null
+++ b/src/ephy-completion-model.c
@@ -0,0 +1,625 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2012 Igalia S.L.
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-completion-model.h"
+
+#include "ephy-embed-prefs.h"
+#include "ephy-embed-shell.h"
+#include "ephy-favicon-helpers.h"
+#include "ephy-history-service.h"
+#include "ephy-shell.h"
+#include "ephy-uri-helpers.h"
+
+#include <string.h>
+
+enum {
+  PROP_0,
+  PROP_HISTORY_SERVICE,
+  PROP_BOOKMARKS_MANAGER,
+  LAST_PROP
+};
+
+struct _EphyCompletionModel {
+  GtkListStore parent_instance;
+
+  EphyHistoryService *history_service;
+  GCancellable *cancellable;
+
+  EphyBookmarksManager *bookmarks_manager;
+  GSList *search_terms;
+};
+
+static GParamSpec *obj_properties[LAST_PROP];
+
+G_DEFINE_TYPE (EphyCompletionModel, ephy_completion_model, GTK_TYPE_LIST_STORE)
+
+static void
+ephy_completion_model_constructed (GObject *object)
+{
+  GType types[N_COL] = { G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+                         G_TYPE_INT, G_TYPE_STRING, G_TYPE_BOOLEAN,
+                         GDK_TYPE_PIXBUF };
+
+  G_OBJECT_CLASS (ephy_completion_model_parent_class)->constructed (object);
+
+  gtk_list_store_set_column_types (GTK_LIST_STORE (object),
+                                   N_COL,
+                                   types);
+}
+
+static void
+free_search_terms (GSList *search_terms)
+{
+  GSList *iter;
+
+  for (iter = search_terms; iter != NULL; iter = iter->next)
+    g_regex_unref ((GRegex *)iter->data);
+
+  g_slist_free (search_terms);
+}
+
+static void
+ephy_completion_model_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec 
*pspec)
+{
+  EphyCompletionModel *self = EPHY_COMPLETION_MODEL (object);
+
+  switch (property_id) {
+    case PROP_HISTORY_SERVICE:
+      self->history_service = EPHY_HISTORY_SERVICE (g_value_get_pointer (value));
+      break;
+    case PROP_BOOKMARKS_MANAGER:
+      self->bookmarks_manager = EPHY_BOOKMARKS_MANAGER (g_value_get_object (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
+      break;
+  }
+}
+
+static void
+ephy_completion_model_finalize (GObject *object)
+{
+  EphyCompletionModel *model = EPHY_COMPLETION_MODEL (object);
+
+  if (model->search_terms) {
+    free_search_terms (model->search_terms);
+    model->search_terms = NULL;
+  }
+
+  if (model->cancellable) {
+    g_cancellable_cancel (model->cancellable);
+    g_clear_object (&model->cancellable);
+  }
+
+  G_OBJECT_CLASS (ephy_completion_model_parent_class)->finalize (object);
+}
+
+static void
+ephy_completion_model_class_init (EphyCompletionModelClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->set_property = ephy_completion_model_set_property;
+  object_class->constructed = ephy_completion_model_constructed;
+  object_class->finalize = ephy_completion_model_finalize;
+
+  obj_properties[PROP_HISTORY_SERVICE] =
+    g_param_spec_pointer ("history-service",
+                          "History Service",
+                          "The history service",
+                          G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+
+  obj_properties[PROP_BOOKMARKS_MANAGER] =
+    g_param_spec_object ("bookmarks-manager",
+                         "Bookmarks manager",
+                         "The bookmarks manager",
+                         EPHY_TYPE_BOOKMARKS_MANAGER,
+                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
+}
+
+static void
+ephy_completion_model_init (EphyCompletionModel *model)
+{
+}
+
+static gboolean
+is_base_address (const char *address)
+{
+  if (address == NULL)
+    return FALSE;
+
+  /* A base address is <scheme>://<host>/
+   * Neither scheme nor host contain a slash, so we can use slashes
+   * figure out if it's a base address.
+   *
+   * Note: previous code was using a GRegExp to do the same thing.
+   * While regexps are much nicer to read, they're also a lot
+   * slower.
+   */
+  address = strchr (address, '/');
+  if (address == NULL ||
+      address[1] != '/')
+    return FALSE;
+
+  address += 2;
+  address = strchr (address, '/');
+  if (address == NULL ||
+      address[1] != 0)
+    return FALSE;
+
+  return TRUE;
+}
+
+static int
+get_relevance (const char *location,
+               int         visit_count,
+               gboolean    is_bookmark)
+{
+  /* FIXME: use frecency. */
+  int relevance = 0;
+
+  /* We have three ordered groups: history's base addresses,
+     bookmarks, deep history addresses. */
+  if (is_bookmark)
+    relevance = 1 << 5;
+  else {
+    visit_count = MIN (visit_count, (1 << 5) - 1);
+
+    if (is_base_address (location))
+      relevance = visit_count << 10;
+    else
+      relevance = visit_count;
+  }
+
+  return relevance;
+}
+
+typedef struct {
+  char *title;
+  char *location;
+  char *keywords;
+  int relevance;
+  gboolean is_bookmark;
+} PotentialRow;
+
+typedef struct {
+  GtkListStore *model;
+  GtkTreeRowReference *row_reference;
+} IconLoadData;
+
+static void
+icon_loaded_cb (GObject *source, GAsyncResult *result, gpointer user_data)
+{
+  GtkTreeIter iter;
+  GtkTreePath *path;
+  IconLoadData *data = (IconLoadData *)user_data;
+  WebKitFaviconDatabase *database = WEBKIT_FAVICON_DATABASE (source);
+  GdkPixbuf *favicon = NULL;
+  cairo_surface_t *icon_surface = webkit_favicon_database_get_favicon_finish (database, result, NULL);
+
+  if (icon_surface) {
+    favicon = ephy_pixbuf_get_from_surface_scaled (icon_surface, FAVICON_SIZE, FAVICON_SIZE);
+    cairo_surface_destroy (icon_surface);
+  }
+
+  if (favicon) {
+    /* The completion model might have changed its contents */
+    if (gtk_tree_row_reference_valid (data->row_reference)) {
+      path = gtk_tree_row_reference_get_path (data->row_reference);
+      gtk_tree_model_get_iter (GTK_TREE_MODEL (data->model), &iter, path);
+      gtk_list_store_set (data->model, &iter, EPHY_COMPLETION_FAVICON_COL, favicon, -1);
+      g_object_unref (favicon);
+      gtk_tree_path_free (path);
+    }
+  }
+
+  g_object_unref (data->model);
+  gtk_tree_row_reference_free (data->row_reference);
+  g_slice_free (IconLoadData, data);
+}
+
+static void
+set_row_in_model (EphyCompletionModel *model, int position, PotentialRow *row)
+{
+  GtkTreeIter iter;
+  GtkTreePath *path;
+  IconLoadData *data;
+  WebKitFaviconDatabase *database;
+  EphyEmbedShell *shell = ephy_embed_shell_get_default ();
+
+  database = webkit_web_context_get_favicon_database (ephy_embed_shell_get_web_context (shell));
+
+  gtk_list_store_insert_with_values (GTK_LIST_STORE (model), &iter, position,
+                                     EPHY_COMPLETION_TEXT_COL, row->title ? row->title : "",
+                                     EPHY_COMPLETION_URL_COL, row->location,
+                                     EPHY_COMPLETION_ACTION_COL, row->location,
+                                     EPHY_COMPLETION_KEYWORDS_COL, row->keywords ? row->keywords : "",
+                                     EPHY_COMPLETION_EXTRA_COL, row->is_bookmark,
+                                     EPHY_COMPLETION_RELEVANCE_COL, row->relevance,
+                                     -1);
+
+  data = g_slice_new (IconLoadData);
+  data->model = GTK_LIST_STORE (g_object_ref (model));
+  path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+  data->row_reference = gtk_tree_row_reference_new (GTK_TREE_MODEL (model), path);
+  gtk_tree_path_free (path);
+
+  webkit_favicon_database_get_favicon (database, row->location,
+                                       NULL, icon_loaded_cb, data);
+}
+
+static void
+replace_rows_in_model (EphyCompletionModel *model, GSList *new_rows)
+{
+  /* This is by far the simplest way of doing, and yet it gives
+   * basically the same result than the other methods... */
+  int i;
+
+  gtk_list_store_clear (GTK_LIST_STORE (model));
+
+  if (!new_rows)
+    return;
+
+  for (i = 0; new_rows != NULL; i++) {
+    PotentialRow *row = (PotentialRow *)new_rows->data;
+
+    set_row_in_model (model, i, row);
+    new_rows = new_rows->next;
+  }
+}
+
+static gboolean
+should_add_bookmark_to_model (EphyCompletionModel *model,
+                              const char          *search_string,
+                              EphyBookmark        *bookmark)
+{
+  gboolean ret = TRUE;
+  GSequence *tags;
+  GSequenceIter *tag_iter;
+  const char *url;
+  const char *title;
+  char *tag_string = NULL;
+  char **tag_array;
+  int i;
+
+  title = ephy_bookmark_get_title (bookmark);
+  url = ephy_bookmark_get_url (bookmark);
+  tags = ephy_bookmark_get_tags (bookmark);
+
+  tag_array = g_malloc0 ((g_sequence_get_length (tags) + 1) * sizeof (char *));
+
+  for (i = 0, tag_iter = g_sequence_get_begin_iter (tags);
+       !g_sequence_iter_is_end (tag_iter);
+       i++, tag_iter = g_sequence_iter_next (tag_iter)) {
+    tag_array[i] = g_sequence_get (tag_iter);
+  }
+
+  tag_string = g_strjoinv (" ", tag_array);
+
+  if (model->search_terms) {
+    GSList *iter;
+    GRegex *current = NULL;
+
+    for (iter = model->search_terms; iter != NULL; iter = iter->next) {
+      current = (GRegex *)iter->data;
+
+      if ((!g_regex_match (current, title ? title : "", G_REGEX_MATCH_NOTEMPTY, NULL)) &&
+          (!g_regex_match (current, url ? url : "", G_REGEX_MATCH_NOTEMPTY, NULL)) &&
+          (!g_regex_match (current, tag_string ? tag_string : "", G_REGEX_MATCH_NOTEMPTY, NULL))) {
+        ret = FALSE;
+        break;
+      }
+    }
+  }
+
+  g_free (tag_array);
+  g_free (tag_string);
+
+  return ret;
+}
+
+typedef struct {
+  EphyCompletionModel *model;
+  char *search_string;
+  EphyHistoryJobCallback callback;
+  gpointer user_data;
+} FindURLsData;
+
+static int
+find_url (gconstpointer a,
+          gconstpointer b)
+{
+  return g_strcmp0 (((PotentialRow *)a)->location,
+                    ((char *)b));
+}
+
+static PotentialRow *
+potential_row_new (const char *title, const char *location,
+                   const char *keywords, int visit_count,
+                   gboolean is_bookmark)
+{
+  PotentialRow *row = g_slice_new0 (PotentialRow);
+
+  row->title = g_strdup (title);
+  row->location = g_strdup (location);
+  row->keywords = g_strdup (keywords);
+  row->relevance = get_relevance (location, visit_count, is_bookmark);
+  row->is_bookmark = is_bookmark;
+
+  return row;
+}
+
+static void
+free_potential_row (PotentialRow *row)
+{
+  g_free (row->title);
+  g_free (row->location);
+  g_free (row->keywords);
+
+  g_slice_free (PotentialRow, row);
+}
+
+static GSList *
+add_to_potential_rows (GSList     *rows,
+                       const char *title,
+                       const char *location,
+                       const char *keywords,
+                       int         visit_count,
+                       gboolean    is_bookmark,
+                       gboolean    search_for_duplicates)
+{
+  gboolean found = FALSE;
+  PotentialRow *row = potential_row_new (title, location, keywords, visit_count, is_bookmark);
+
+  if (search_for_duplicates) {
+    GSList *p;
+
+    p = g_slist_find_custom (rows, location, find_url);
+    if (p) {
+      PotentialRow *match = (PotentialRow *)p->data;
+      if (row->relevance > match->relevance)
+        match->relevance = row->relevance;
+
+      found = TRUE;
+      free_potential_row (row);
+    }
+  }
+
+  if (!found)
+    rows = g_slist_prepend (rows, row);
+
+  return rows;
+}
+
+static int
+sort_by_relevance (gconstpointer a, gconstpointer b)
+{
+  PotentialRow *r1 = (PotentialRow *)a;
+  PotentialRow *r2 = (PotentialRow *)b;
+
+  if (r1->relevance < r2->relevance)
+    return 1;
+  else if (r1->relevance > r2->relevance)
+    return -1;
+  else
+    return 0;
+}
+
+static void
+query_completed_cb (EphyHistoryService *service,
+                    gboolean            success,
+                    gpointer            result_data,
+                    FindURLsData       *user_data)
+{
+  EphyCompletionModel *model = user_data->model;
+  GList *p, *urls;
+  GSequence *bookmarks;
+  GSequenceIter *iter;
+  GSList *list = NULL;
+
+  /* Bookmarks */
+  bookmarks = ephy_bookmarks_manager_get_bookmarks (model->bookmarks_manager);
+
+  /* FIXME: perhaps this could be done in a service thread? There
+   * should never be a ton of bookmarks, but seems a bit cleaner and
+   * consistent with what we do for the history. */
+  for (iter = g_sequence_get_begin_iter (bookmarks);
+       !g_sequence_iter_is_end (iter);
+       iter = g_sequence_iter_next (iter)) {
+    EphyBookmark *bookmark;
+    const char *url, *title;
+
+    bookmark = g_sequence_get (iter);
+
+    if (should_add_bookmark_to_model (model, user_data->search_string, bookmark)) {
+      url = ephy_bookmark_get_url (bookmark);
+      title = ephy_bookmark_get_title (bookmark);
+      list = add_to_potential_rows (list, title, url, NULL, 0, TRUE, FALSE);
+    }
+  }
+
+  /* History */
+  urls = (GList *)result_data;
+
+  for (p = urls; p != NULL; p = p->next) {
+    EphyHistoryURL *url = (EphyHistoryURL *)p->data;
+
+    list = add_to_potential_rows (list, url->title, url->url, NULL, url->visit_count, FALSE, TRUE);
+  }
+
+  /* Sort the rows by relevance. */
+  list = g_slist_sort (list, sort_by_relevance);
+
+  /* Now that we have all the rows we want to insert, replace the rows
+   * in the current model one by one, sorted by relevance. */
+  replace_rows_in_model (model, list);
+
+  /* Notify */
+  if (user_data->callback)
+    user_data->callback (service, success, result_data, user_data->user_data);
+
+  g_free (user_data->search_string);
+  g_slice_free (FindURLsData, user_data);
+  g_list_free_full (urls, (GDestroyNotify)ephy_history_url_free);
+  g_slist_free_full (list, (GDestroyNotify)free_potential_row);
+  g_clear_object (&model->cancellable);
+}
+
+static void
+update_search_terms (EphyCompletionModel *model,
+                     const char          *text)
+{
+  const char *current;
+  const char *ptr;
+  char *tmp;
+  char *term;
+  GRegex *term_regex;
+  GRegex *quote_regex;
+  gint count;
+  gboolean inside_quotes = FALSE;
+
+  if (model->search_terms) {
+    free_search_terms (model->search_terms);
+    model->search_terms = NULL;
+  }
+
+  quote_regex = g_regex_new ("\"", G_REGEX_OPTIMIZE,
+                             G_REGEX_MATCH_NOTEMPTY, NULL);
+
+  /*
+   * This code loops through the string using pointer arythmetics.
+   * Although the string we are handling may contain UTF-8 chars
+   * this works because only ASCII chars affect what is actually
+   * copied from the string as a search term.
+   */
+  for (count = 0, current = ptr = text; ptr[0] != '\0'; ptr++, count++) {
+    /*
+     * If we found a double quote character; we will
+     * consume bytes up until the next quote, or
+     * end of line;
+     */
+    if (ptr[0] == '"')
+      inside_quotes = !inside_quotes;
+
+    /*
+     * If we found a space, and we are not looking for a
+     * closing double quote, or if the next char is the
+     * end of the string, append what we have already as
+     * a search term.
+     */
+    if (((ptr[0] == ' ') && (!inside_quotes)) || ptr[1] == '\0') {
+      /*
+       * We special-case the end of the line because
+       * we would otherwise not copy the last character
+       * of the search string, since the for loop will
+       * stop before that.
+       */
+      if (ptr[1] == '\0')
+        count++;
+
+      /*
+       * remove quotes, and quote any regex-sensitive
+       * characters
+       */
+      tmp = g_regex_escape_string (current, count);
+      term = g_regex_replace (quote_regex, tmp, -1, 0,
+                              "", G_REGEX_MATCH_NOTEMPTY, NULL);
+      g_strstrip (term);
+      g_free (tmp);
+
+      /* we don't want empty search terms */
+      if (term[0] != '\0') {
+        term_regex = g_regex_new (term,
+                                  G_REGEX_CASELESS | G_REGEX_OPTIMIZE,
+                                  G_REGEX_MATCH_NOTEMPTY, NULL);
+        model->search_terms = g_slist_append (model->search_terms, term_regex);
+      }
+      g_free (term);
+
+      /* count will be incremented by the for loop */
+      count = -1;
+      current = ptr + 1;
+    }
+  }
+
+  g_regex_unref (quote_regex);
+}
+
+#define MAX_COMPLETION_HISTORY_URLS 8
+
+void
+ephy_completion_model_update_for_string (EphyCompletionModel   *model,
+                                         const char            *search_string,
+                                         EphyHistoryJobCallback callback,
+                                         gpointer               data)
+{
+  char **strings;
+  int i;
+  GList *query = NULL;
+  FindURLsData *user_data;
+
+  g_return_if_fail (EPHY_IS_COMPLETION_MODEL (model));
+  g_return_if_fail (search_string != NULL);
+
+  /* Split the search string. */
+  strings = g_strsplit (search_string, " ", -1);
+  for (i = 0; strings[i]; i++)
+    query = g_list_append (query, g_strdup (strings[i]));
+  g_strfreev (strings);
+
+  update_search_terms (model, search_string);
+
+  user_data = g_slice_new (FindURLsData);
+  user_data->model = model;
+  user_data->search_string = g_strdup (search_string);
+  user_data->callback = callback;
+  user_data->user_data = data;
+
+  if (model->cancellable) {
+    g_cancellable_cancel (model->cancellable);
+    g_object_unref (model->cancellable);
+  }
+  model->cancellable = g_cancellable_new ();
+
+  ephy_history_service_find_urls (model->history_service,
+                                  0, 0,
+                                  MAX_COMPLETION_HISTORY_URLS, 0,
+                                  query,
+                                  EPHY_HISTORY_SORT_MOST_VISITED,
+                                  model->cancellable,
+                                  (EphyHistoryJobCallback)query_completed_cb,
+                                  user_data);
+}
+
+EphyCompletionModel *
+ephy_completion_model_new (EphyHistoryService   *history_service,
+                           EphyBookmarksManager *bookmarks_manager)
+{
+  g_return_val_if_fail (EPHY_IS_HISTORY_SERVICE (history_service), NULL);
+  g_return_val_if_fail (EPHY_IS_BOOKMARKS_MANAGER (bookmarks_manager), NULL);
+
+  return g_object_new (EPHY_TYPE_COMPLETION_MODEL,
+                       "history-service", history_service,
+                       "bookmarks-manager", bookmarks_manager,
+                       NULL);
+}
diff --git a/src/ephy-completion-model.h b/src/ephy-completion-model.h
new file mode 100644
index 0000000..912c366
--- /dev/null
+++ b/src/ephy-completion-model.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2012 Igalia S.L.
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ephy-bookmarks-manager.h"
+#include "ephy-history-service.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_COMPLETION_MODEL (ephy_completion_model_get_type ())
+
+G_DECLARE_FINAL_TYPE (EphyCompletionModel, ephy_completion_model, EPHY, COMPLETION_MODEL, GtkListStore)
+
+typedef enum
+{
+  EPHY_COMPLETION_TEXT_COL,
+  EPHY_COMPLETION_ACTION_COL,
+  EPHY_COMPLETION_KEYWORDS_COL,
+  EPHY_COMPLETION_RELEVANCE_COL,
+  EPHY_COMPLETION_URL_COL,
+  EPHY_COMPLETION_EXTRA_COL,
+  EPHY_COMPLETION_FAVICON_COL,
+  N_COL
+} EphyCompletionColumn;
+
+EphyCompletionModel *ephy_completion_model_new               (EphyHistoryService   *history_service,
+                                                              EphyBookmarksManager *bookmarks_manager);
+
+void                 ephy_completion_model_update_for_string (EphyCompletionModel *model,
+                                                              const char *string,
+                                                              EphyHistoryJobCallback callback,
+                                                              gpointer data);
+G_END_DECLS
diff --git a/src/ephy-location-controller.c b/src/ephy-location-controller.c
index f46e232..c42e31f 100644
--- a/src/ephy-location-controller.c
+++ b/src/ephy-location-controller.c
@@ -22,6 +22,8 @@
 #include "config.h"
 #include "ephy-location-controller.h"
 
+
+#include "ephy-completion-model.h"
 #include "ephy-debug.h"
 #include "ephy-dnd.h"
 #include "ephy-embed-container.h"
@@ -29,12 +31,10 @@
 #include "ephy-link.h"
 #include "ephy-location-entry.h"
 #include "ephy-shell.h"
-#include "ephy-suggestion-model.h"
 #include "ephy-title-widget.h"
 #include "ephy-uri-helpers.h"
 #include "ephy-widgets-type-builtins.h"
 
-#include <dazzle.h>
 #include <gdk/gdkkeysyms.h>
 #include <gtk/gtk.h>
 #include <string.h>
@@ -79,6 +79,16 @@ G_DEFINE_TYPE_WITH_CODE (EphyLocationController, ephy_location_controller, G_TYP
                          G_IMPLEMENT_INTERFACE (EPHY_TYPE_LINK,
                                                 NULL))
 
+static gboolean
+match_func (GtkEntryCompletion *completion,
+            const char         *key,
+            GtkTreeIter        *iter,
+            gpointer            data)
+{
+  /* We want every row in the model to show up. */
+  return TRUE;
+}
+
 static void
 entry_drag_data_received_cb (GtkWidget *widget,
                              GdkDragContext *context,
@@ -172,18 +182,33 @@ entry_activate_cb (GtkEntry               *entry,
 }
 
 static void
+update_done_cb (EphyHistoryService *service,
+                gboolean            success,
+                gpointer            result_data,
+                gpointer            user_data)
+{
+  /* FIXME: this hack is needed for the completion entry popup
+   * to resize smoothly. See:
+   * https://bugzilla.gnome.org/show_bug.cgi?id=671074 */
+  gtk_entry_completion_complete (GTK_ENTRY_COMPLETION (user_data));
+}
+
+static void
 user_changed_cb (GtkWidget *widget, EphyLocationController *controller)
 {
   const char *address;
-  GListModel *model;
+  GtkTreeModel *model;
+  GtkEntryCompletion *completion;
 
   address = ephy_title_widget_get_address (EPHY_TITLE_WIDGET (widget));
 
   LOG ("user_changed_cb, address %s", address);
 
-  model = dzl_suggestion_entry_get_model (DZL_SUGGESTION_ENTRY (widget));
+  completion = gtk_entry_get_completion (GTK_ENTRY (widget));
+  model = gtk_entry_completion_get_model (completion);
 
-  ephy_suggestion_model_query_async (EPHY_SUGGESTION_MODEL (model), address, NULL, NULL, NULL);
+  ephy_completion_model_update_for_string (EPHY_COMPLETION_MODEL (model), address,
+                                           update_done_cb, completion);
 }
 
 static void
@@ -267,12 +292,84 @@ switch_page_cb (GtkNotebook            *notebook,
 }
 
 static void
+action_activated_cb (GtkEntryCompletion     *completion,
+                     int                     index,
+                     EphyLocationController *controller)
+{
+  GtkWidget *entry;
+  char *content;
+  char *url;
+  char **engine_names;
+
+  entry = gtk_entry_completion_get_entry (completion);
+  content = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1);
+  if (content == NULL)
+    return;
+
+  engine_names = ephy_search_engine_manager_get_names (controller->search_engine_manager);
+  url = ephy_search_engine_manager_build_search_address (controller->search_engine_manager,
+                                                         engine_names[index],
+                                                         content);
+  g_strfreev (engine_names);
+
+  ephy_link_open (EPHY_LINK (controller), url, NULL,
+                  ephy_link_flags_from_current_event ());
+  g_free (content);
+  g_free (url);
+}
+
+static void
+fill_entry_completion_with_actions (GtkEntryCompletion     *completion,
+                                    EphyLocationController *controller)
+{
+  char **engine_names;
+
+  engine_names = ephy_search_engine_manager_get_names (controller->search_engine_manager);
+
+  controller->num_search_engines_actions = 0;
+
+  for (guint i = 0; engine_names[i] != NULL; i++) {
+    gtk_entry_completion_insert_action_text (completion, i, engine_names[i]);
+    controller->num_search_engines_actions++;
+  }
+
+  g_strfreev (engine_names);
+}
+
+static void
+add_completion_actions (EphyLocationController *controller,
+                        EphyLocationEntry      *lentry)
+{
+  GtkEntryCompletion *completion = gtk_entry_get_completion (GTK_ENTRY (lentry));
+
+  fill_entry_completion_with_actions (completion, controller);
+  g_signal_connect (completion, "action_activated",
+                    G_CALLBACK (action_activated_cb), controller);
+}
+
+static void
+search_engines_changed_cb (EphySearchEngineManager *manager,
+                           gpointer  data)
+{
+  EphyLocationController *controller;
+  GtkEntryCompletion *completion;
+
+  controller = EPHY_LOCATION_CONTROLLER (data);
+  completion = gtk_entry_get_completion (GTK_ENTRY (controller->title_widget));
+
+  for (guint i = 0; i < controller->num_search_engines_actions; i++)
+    gtk_entry_completion_delete_action (completion, 0);
+
+  fill_entry_completion_with_actions (completion, controller);
+}
+
+static void
 ephy_location_controller_constructed (GObject *object)
 {
   EphyLocationController *controller = EPHY_LOCATION_CONTROLLER (object);
   EphyHistoryService *history_service;
   EphyBookmarksManager *bookmarks_manager;
-  EphySuggestionModel *model;
+  EphyCompletionModel *model;
   GtkWidget *notebook, *widget;
 
   G_OBJECT_CLASS (ephy_location_controller_parent_class)->constructed (object);
@@ -292,13 +389,27 @@ ephy_location_controller_constructed (GObject *object)
 
   history_service = ephy_embed_shell_get_global_history_service (ephy_embed_shell_get_default ());
   bookmarks_manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
-  model = ephy_suggestion_model_new (history_service, bookmarks_manager);
-  dzl_suggestion_entry_set_model (DZL_SUGGESTION_ENTRY (controller->title_widget), G_LIST_MODEL (model));
+  model = ephy_completion_model_new (history_service, bookmarks_manager);
+  ephy_location_entry_set_completion (EPHY_LOCATION_ENTRY (controller->title_widget),
+                                      GTK_TREE_MODEL (model),
+                                      EPHY_COMPLETION_TEXT_COL,
+                                      EPHY_COMPLETION_ACTION_COL,
+                                      EPHY_COMPLETION_KEYWORDS_COL,
+                                      EPHY_COMPLETION_RELEVANCE_COL,
+                                      EPHY_COMPLETION_URL_COL,
+                                      EPHY_COMPLETION_EXTRA_COL,
+                                      EPHY_COMPLETION_FAVICON_COL);
   g_object_unref (model);
 
-/* FIXME: What to do with search engines? Add them to the model? Probably not?
- * Make the bangs mandatory?
- */
+  ephy_location_entry_set_match_func (EPHY_LOCATION_ENTRY (controller->title_widget),
+                                      match_func,
+                                      controller->title_widget,
+                                      NULL);
+
+  add_completion_actions (controller, EPHY_LOCATION_ENTRY (controller->title_widget));
+
+  g_signal_connect_object (controller->search_engine_manager, "changed",
+                           G_CALLBACK (search_engines_changed_cb), controller, 0);
 
   g_object_bind_property (controller, "editable",
                           controller->title_widget, "editable",
diff --git a/src/ephy-window.c b/src/ephy-window.c
index 2883c0d..96af170 100644
--- a/src/ephy-window.c
+++ b/src/ephy-window.c
@@ -53,7 +53,6 @@
 #include "popup-commands.h"
 #include "window-commands.h"
 
-#include <dazzle.h>
 #include <gdk/gdkkeysyms.h>
 #include <gdk/gdkx.h>
 #include <gio/gio.h>
@@ -75,10 +74,6 @@ static void ephy_window_change_allow_popup_windows_state (GSimpleAction *action,
                                                           GVariant      *state,
                                                           gpointer       user_data);
 
-static void ephy_window_embed_container_iface_init (EphyEmbedContainerInterface *iface);
-
-static void ephy_window_link_iface_init (EphyLinkInterface *iface);
-
 const struct {
   const char *action_and_target;
   const char *accelerators[9];
@@ -143,7 +138,7 @@ const struct {
 #define SETTINGS_CONNECTION_DATA_KEY    "EphyWindowSettings"
 
 struct _EphyWindow {
-  DzlApplicationWindow parent_instance;
+  GtkApplicationWindow parent_instance;
 
   GtkWidget *header_bar;
   EphyBookmarksManager *bookmarks_manager;
@@ -190,13 +185,6 @@ enum {
   SENS_FLAG_IS_BLANK      = 1 << 5
 };
 
-G_DEFINE_TYPE_WITH_CODE (EphyWindow, ephy_window, DZL_TYPE_APPLICATION_WINDOW,
-                         G_IMPLEMENT_INTERFACE (EPHY_TYPE_LINK,
-                                                ephy_window_link_iface_init)
-                         G_IMPLEMENT_INTERFACE (EPHY_TYPE_EMBED_CONTAINER,
-                                                ephy_window_embed_container_iface_init))
-
-
 static gint
 impl_add_child (EphyEmbedContainer *container,
                 EphyEmbed          *child,
@@ -405,6 +393,12 @@ ephy_window_link_iface_init (EphyLinkInterface *iface)
   iface->open_link = ephy_window_open_link;
 }
 
+G_DEFINE_TYPE_WITH_CODE (EphyWindow, ephy_window, GTK_TYPE_APPLICATION_WINDOW,
+                         G_IMPLEMENT_INTERFACE (EPHY_TYPE_LINK,
+                                                ephy_window_link_iface_init)
+                         G_IMPLEMENT_INTERFACE (EPHY_TYPE_EMBED_CONTAINER,
+                                                ephy_window_embed_container_iface_init))
+
 static void
 sync_chromes_visibility (EphyWindow *window)
 {
@@ -3322,7 +3316,7 @@ ephy_window_activate_location (EphyWindow *window)
   title_widget = ephy_header_bar_get_title_widget (EPHY_HEADER_BAR (window->header_bar));
 
   if (EPHY_IS_LOCATION_ENTRY (title_widget))
-    ephy_location_entry_focus (EPHY_LOCATION_ENTRY (title_widget));
+    ephy_location_entry_activate (EPHY_LOCATION_ENTRY (title_widget));
 }
 
 /**
diff --git a/src/ephy-window.h b/src/ephy-window.h
index edec1a9..ff01c3f 100644
--- a/src/ephy-window.h
+++ b/src/ephy-window.h
@@ -25,14 +25,13 @@
 #include "ephy-location-controller.h"
 #include "ephy-web-view.h"
 
-#include <dazzle.h>
 #include <gtk/gtk.h>
 
 G_BEGIN_DECLS
 
 #define EPHY_TYPE_WINDOW (ephy_window_get_type ())
 
-G_DECLARE_FINAL_TYPE (EphyWindow, ephy_window, EPHY, WINDOW, DzlApplicationWindow)
+G_DECLARE_FINAL_TYPE (EphyWindow, ephy_window, EPHY, WINDOW, GtkApplicationWindow)
 
 typedef enum
 {
diff --git a/src/meson.build b/src/meson.build
index 1373b58..2d810a6 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -22,6 +22,7 @@ libephymain_sources = [
   'clear-data-dialog.c',
   'cookies-dialog.c',
   'ephy-action-helper.c',
+  'ephy-completion-model.c',
   'ephy-encoding-dialog.c',
   'ephy-encoding-row.c',
   'ephy-header-bar.c',
@@ -33,7 +34,6 @@ libephymain_sources = [
   'ephy-search-engine-dialog.c',
   'ephy-session.c',
   'ephy-shell.c',
-  'ephy-suggestion-model.c',
   'ephy-window.c',
   'passwords-dialog.c',
   'popup-commands.c',
@@ -102,6 +102,7 @@ executable('epiphany-search-provider',
   install_rpath: pkglibdir
 )
 
+
 resource_files = files('resources/epiphany.gresource.xml')
 resources = gnome.compile_resources('epiphany-resources',
     resource_files,
diff --git a/src/search-provider/ephy-search-provider.c b/src/search-provider/ephy-search-provider.c
index 076c86b..daa04f3 100644
--- a/src/search-provider/ephy-search-provider.c
+++ b/src/search-provider/ephy-search-provider.c
@@ -23,12 +23,11 @@
 #include "ephy-search-provider.h"
 
 #include "ephy-bookmarks-manager.h"
+#include "ephy-completion-model.h"
 #include "ephy-file-helpers.h"
 #include "ephy-prefs.h"
 #include "ephy-profile-utils.h"
 #include "ephy-shell.h"
-#include "ephy-suggestion-model.h"
-#include "ephy-uri-helpers.h"
 
 #include <gio/gio.h>
 #include <gio/gdesktopappinfo.h>
@@ -44,7 +43,7 @@ struct _EphySearchProvider {
 
   GSettings                *settings;
   EphyBookmarksManager     *bookmarks_manager;
-  EphySuggestionModel      *model;
+  EphyCompletionModel      *model;
 };
 
 struct _EphySearchProviderClass {
@@ -56,29 +55,29 @@ G_DEFINE_TYPE (EphySearchProvider, ephy_search_provider, G_TYPE_APPLICATION)
 #define INACTIVITY_TIMEOUT 60 * 1000 /* One minute, in milliseconds */
 
 static void
-on_model_updated (GObject      *source_object,
-                  GAsyncResult *result,
-                  GTask        *task)
+on_model_updated (EphyHistoryService *service,
+                  gboolean            success,
+                  gpointer            result_data,
+                  gpointer            user_data)
 {
+  GTask *task = user_data;
   EphySearchProvider *self = g_task_get_source_object (task);
-  EphySuggestion *suggestion;
+  GtkTreeModel *model = GTK_TREE_MODEL (self->model);
+  GtkTreeIter iter;
   GPtrArray *results;
   const char *search_string;
-  guint n_items;
-  GError *error = NULL;
+  gboolean ok;
+
   results = g_ptr_array_new ();
 
-  if (ephy_suggestion_model_query_finish (self->model,
-                                          result,
-                                          &error)) {
-    n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
-    for (guint i = 0; i < n_items; i++) {
-      suggestion = g_list_model_get_item (G_LIST_MODEL (self->model), i);
-      g_ptr_array_add (results, g_strdup (ephy_suggestion_get_uri (suggestion)));
-    }
-  } else {
-    g_warning ("Failed to query suggestion model: %s", error->message);
-    g_error_free (error);
+  ok = gtk_tree_model_get_iter_first (model, &iter);
+  while (ok) {
+    char *result;
+
+    result = gtk_tree_model_get_string_from_iter (model, &iter);
+    g_ptr_array_add (results, result);
+
+    ok = gtk_tree_model_iter_next (model, &iter);
   }
 
   search_string = g_task_get_task_data (task);
@@ -107,16 +106,16 @@ gather_results_async (EphySearchProvider *self,
 {
   GTask *task;
   char *search_string;
+
   task = g_task_new (self, cancellable, callback, user_data);
 
   search_string = g_strjoinv (" ", terms);
   g_task_set_task_data (task, search_string, g_free);
 
-  ephy_suggestion_model_query_async (self->model,
-                                     search_string,
-                                     cancellable,
-                                     (GAsyncReadyCallback)on_model_updated,
-                                     task);
+  ephy_completion_model_update_for_string (self->model,
+                                           search_string,
+                                           on_model_updated,
+                                           task);
 }
 
 static void
@@ -127,6 +126,7 @@ complete_request (GObject      *object,
   EphySearchProvider *self = EPHY_SEARCH_PROVIDER (object);
   char **results;
   GError *error;
+
   error = NULL;
   results = gather_results_finish (self, result, &error);
 
@@ -177,8 +177,14 @@ handle_get_result_metas (EphyShellSearchProvider2 *skeleton,
                          char                    **results,
                          EphySearchProvider       *self)
 {
+  GtkTreeModel *model = GTK_TREE_MODEL (self->model);
+  GtkTreeIter iter;
   int i;
   GVariantBuilder builder;
+  GIcon *favicon;
+  char *name, *url;
+  gboolean is_bookmark;
+
   g_application_hold (G_APPLICATION (self));
   g_cancellable_cancel (self->cancellable);
 
@@ -195,28 +201,54 @@ handle_get_result_metas (EphyShellSearchProvider2 *skeleton,
       g_variant_builder_add (&builder, "{sv}",
                              "gicon", g_variant_new_string ("org.gnome.Epiphany"));
       g_variant_builder_close (&builder);
-    } else {
-      EphySuggestion *suggestion;
-      const char *title;
-      const char *uri;
-      char *decoded_uri;
+      continue;
+    }
 
-      suggestion = ephy_suggestion_model_get_suggestion_with_uri (self->model, results[i]);
-      title = ephy_suggestion_get_unescaped_title (suggestion);
-      uri = ephy_suggestion_get_uri (suggestion);
-      decoded_uri = ephy_uri_decode (uri);
+    if (!gtk_tree_model_get_iter_from_string (model, &iter, results[i]))
+      continue;
 
-      g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
-      g_variant_builder_add (&builder, "{sv}",
-                             "id", g_variant_new_string (decoded_uri));
-      g_variant_builder_add (&builder, "{sv}",
-                             "name", g_variant_new_string (title));
-      g_variant_builder_add (&builder, "{sv}",
-                             "gicon", g_variant_new_string ("text-html"));
-      g_variant_builder_close (&builder);
+    gtk_tree_model_get (model, &iter,
+                        EPHY_COMPLETION_TEXT_COL, &name,
+                        EPHY_COMPLETION_URL_COL, &url,
+                        EPHY_COMPLETION_FAVICON_COL, &favicon,
+                        EPHY_COMPLETION_EXTRA_COL, &is_bookmark,
+                        -1);
+
+    g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
+    g_variant_builder_add (&builder, "{sv}",
+                           "id", g_variant_new_string (url));
+    g_variant_builder_add (&builder, "{sv}",
+                           "name", g_variant_new_string (name));
+
+    if (favicon == NULL) {
+      char *type;
+
+      type = g_content_type_from_mime_type ("text/html");
+      favicon = g_content_type_get_icon (type);
 
-      g_free (decoded_uri);
+      if (is_bookmark) {
+        GEmblem *emblem;
+        GIcon *emblem_icon, *emblemed;
+
+        emblem_icon = g_themed_icon_new ("emblem-favorite");
+        emblem = g_emblem_new (emblem_icon);
+
+        emblemed = g_emblemed_icon_new (favicon, emblem);
+
+        g_object_unref (emblem);
+        g_object_unref (emblem_icon);
+        g_object_unref (favicon);
+        favicon = emblemed;
+      }
     }
+
+    g_variant_builder_add (&builder, "{sv}",
+                           "icon", g_icon_serialize (favicon));
+    g_variant_builder_close (&builder);
+
+    g_object_unref (favicon);
+    g_free (name);
+    g_free (url);
   }
 
   ephy_shell_search_provider2_complete_get_result_metas (skeleton,
@@ -233,6 +265,7 @@ launch_uri (const char *uri,
             guint       timestamp)
 {
   char *str;
+
   /* TODO: Handle the timestamp */
   str = g_strdup_printf ("epiphany %s", uri);
   g_spawn_command_line_async (str, NULL);
@@ -337,7 +370,7 @@ ephy_search_provider_init (EphySearchProvider *self)
 
   filename = g_build_filename (ephy_dot_dir (), EPHY_HISTORY_FILE, NULL);
   self->bookmarks_manager = ephy_bookmarks_manager_new ();
-  self->model = ephy_suggestion_model_new (ephy_embed_shell_get_global_history_service (shell),
+  self->model = ephy_completion_model_new (ephy_embed_shell_get_global_history_service (shell),
                                            self->bookmarks_manager);
   g_free (filename);
 
diff --git a/tests/ephy-completion-model-test.c b/tests/ephy-completion-model-test.c
new file mode 100644
index 0000000..869d16b
--- /dev/null
+++ b/tests/ephy-completion-model-test.c
@@ -0,0 +1,101 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2012 Igalia S.L.
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-completion-model.h"
+
+#include "ephy-debug.h"
+#include "ephy-embed-prefs.h"
+#include "ephy-file-helpers.h"
+#include "ephy-shell.h"
+
+static void
+test_ephy_completion_model_create (void)
+{
+  EphyCompletionModel *model;
+  model = ephy_completion_model_new (ephy_embed_shell_get_global_history_service 
(ephy_embed_shell_get_default ()),
+                                     ephy_shell_get_bookmarks_manager (ephy_shell_get_default ()));
+  g_assert (model);
+  g_object_unref (model);
+}
+
+static void
+update_empty_cb (EphyHistoryService *service,
+                 gboolean            success,
+                 gpointer            result_data,
+                 GMainLoop          *loop)
+{
+  GList *results = (GList *)result_data;
+
+  g_assert (success);
+  g_assert (results == NULL);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+test_ephy_completion_model_update_empty (void)
+{
+  EphyCompletionModel *model;
+  GMainLoop *loop = NULL;
+
+  model = ephy_completion_model_new (ephy_embed_shell_get_global_history_service 
(ephy_embed_shell_get_default ()),
+                                     ephy_shell_get_bookmarks_manager (ephy_shell_get_default ()));
+  g_assert (model);
+
+  loop = g_main_loop_new (NULL, FALSE);
+
+  ephy_completion_model_update_for_string (model, "hello",
+                                           (EphyHistoryJobCallback)update_empty_cb,
+                                           loop);
+
+  g_main_loop_run (loop);
+
+  g_object_unref (model);
+  g_main_loop_unref (loop);
+}
+
+int
+main (int argc, char *argv[])
+{
+  gboolean ret;
+
+  gtk_test_init (&argc, &argv);
+  ephy_debug_init ();
+
+  if (!ephy_file_helpers_init (NULL,
+                               EPHY_FILE_HELPERS_PRIVATE_PROFILE | EPHY_FILE_HELPERS_ENSURE_EXISTS,
+                               NULL)) {
+    g_debug ("Something wrong happened with ephy_file_helpers_init()");
+    return -1;
+  }
+
+  _ephy_shell_create_instance (EPHY_EMBED_SHELL_MODE_TEST);
+
+  g_test_add_func ("/src/ephy-completion-model/create",
+                   test_ephy_completion_model_create);
+
+  g_test_add_func ("/src/ephy-completion-model/update_empty",
+                   test_ephy_completion_model_update_empty);
+
+  ret = g_test_run ();
+
+  return ret;
+}
diff --git a/tests/ephy-location-entry-test.c b/tests/ephy-location-entry-test.c
new file mode 100644
index 0000000..bcd6beb
--- /dev/null
+++ b/tests/ephy-location-entry-test.c
@@ -0,0 +1,160 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2008 Diego Escalante Urrelo
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-debug.h"
+#include "ephy-location-entry.h"
+#include "ephy-title-widget.h"
+#include <glib.h>
+#include <gtk/gtk.h>
+
+static void
+test_entry_new (void)
+{
+  GtkWidget *entry;
+  entry = ephy_location_entry_new ();
+
+  g_assert (GTK_IS_WIDGET (entry));
+  g_assert (EPHY_IS_LOCATION_ENTRY (entry));
+}
+
+static void
+test_entry_get_entry (void)
+{
+  EphyLocationEntry *entry;
+  entry = EPHY_LOCATION_ENTRY (ephy_location_entry_new ());
+
+  g_assert (GTK_IS_ENTRY (entry));
+}
+
+static void
+test_entry_set_location (void)
+{
+  const char *set = "test";
+  const char *get;
+
+  EphyTitleWidget *widget;
+  widget = EPHY_TITLE_WIDGET (ephy_location_entry_new ());
+
+  ephy_title_widget_set_address (widget, set);
+  get = ephy_title_widget_get_address (widget);
+  g_assert_cmpstr (set, ==, get);
+}
+
+static void
+test_entry_set_location_null (void)
+{
+  const char *set = "test";
+  const char *get;
+
+  EphyTitleWidget *widget;
+  widget = EPHY_TITLE_WIDGET (ephy_location_entry_new ());
+
+  ephy_title_widget_set_address (widget, NULL);
+  get = ephy_title_widget_get_address (widget);
+  g_assert_cmpstr (set, !=, get);
+}
+
+static void
+test_entry_get_location (void)
+{
+  const char *set = "test";
+  const char *get;
+
+  EphyTitleWidget *widget;
+  widget = EPHY_TITLE_WIDGET (ephy_location_entry_new ());
+
+  ephy_title_widget_set_address (widget, set);
+  get = ephy_title_widget_get_address (widget);
+  g_assert_cmpstr (set, ==, get);
+}
+
+static void
+test_entry_get_location_empty (void)
+{
+  const char *get;
+
+  EphyTitleWidget *widget;
+  widget = EPHY_TITLE_WIDGET (ephy_location_entry_new ());
+
+  get = ephy_title_widget_get_address (widget);
+  g_assert_cmpstr ("", ==, get);
+}
+
+static void
+test_entry_can_undo (void)
+{
+  const char *test = "test";
+
+  EphyLocationEntry *entry;
+  entry = EPHY_LOCATION_ENTRY (ephy_location_entry_new ());
+
+  g_assert_cmpint (ephy_location_entry_get_can_undo (entry), ==, FALSE);
+
+  /* Use gtk_* function or otherwise user_changed won't be correctly handled
+   * internally by the location entry (see editable_changed_cb and
+   * block_update) */
+  gtk_entry_set_text (GTK_ENTRY (entry), test);
+  g_assert_cmpint (ephy_location_entry_get_can_undo (entry), ==, TRUE);
+}
+
+static void
+test_entry_can_redo (void)
+{
+  const char *test = "test";
+
+  EphyLocationEntry *entry;
+  entry = EPHY_LOCATION_ENTRY (ephy_location_entry_new ());
+  g_assert_cmpint (ephy_location_entry_get_can_redo (entry), ==, FALSE);
+
+  /* Can't redo, in this point we can undo */
+  ephy_title_widget_set_address (EPHY_TITLE_WIDGET (entry), test);
+  g_assert_cmpint (ephy_location_entry_get_can_redo (entry), ==, FALSE);
+
+  /* Reset should set redo to TRUE */
+  ephy_location_entry_reset (entry);
+  g_assert_cmpint (ephy_location_entry_get_can_redo (entry), ==, TRUE);
+}
+
+int
+main (int argc, char *argv[])
+{
+  gtk_test_init (&argc, &argv);
+  ephy_debug_init ();
+
+  g_test_add_func ("/lib/widgets/ephy-location-entry/new",
+                   test_entry_new);
+  g_test_add_func ("/lib/widgets/ephy-location-entry/get_entry",
+                   test_entry_get_entry);
+  g_test_add_func ("/lib/widgets/ephy-location-entry/set_location",
+                   test_entry_set_location);
+  g_test_add_func ("/lib/widgets/ephy-location-entry/get_location",
+                   test_entry_get_location);
+  g_test_add_func ("/lib/widgets/ephy-location-entry/set_location_null",
+                   test_entry_set_location_null);
+  g_test_add_func ("/lib/widgets/ephy-location-entry/get_location_empty",
+                   test_entry_get_location_empty);
+  g_test_add_func ("/lib/widgets/ephy-location-entry/can_undo",
+                   test_entry_can_undo);
+  g_test_add_func ("/lib/widgets/ephy-location-entry/can_redo",
+                   test_entry_can_redo);
+
+  return g_test_run ();
+}
diff --git a/tests/meson.build b/tests/meson.build
index b86fc41..8ced8eb 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -9,7 +9,13 @@ if get_option('unit_tests')
   # ephytestutils_dep = declare_dependency(
   #   link_with: libephytestutils
   # )
-  #
+
+  completion_model_test = executable('test-ephy-completion-model',
+    'ephy-completion-model-test.c',
+    dependencies: ephymain_dep
+  )
+  test('Completion model test', completion_model_test)
+
   # FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=778153
   # download_test = executable('test-ephy-download',
   #   'ephy-download-test.c',
@@ -55,6 +61,12 @@ if get_option('unit_tests')
   )
   test('History test', history_test)
 
+  location_entry_test = executable('test-location-entry',
+    'ephy-location-entry-test.c',
+    dependencies: ephymain_dep
+  )
+  test('Location entry test', location_entry_test)
+
   migration_test = executable('test-ephy-migration',
     'ephy-migration-test.c',
     dependencies: ephymain_dep


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