cookies-dialog.c 13.9 KB
Newer Older
1
/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 3 4
/*
 *  Copyright © 2013 Red Hat, Inc.
 *
Michael Catanzaro's avatar
Michael Catanzaro committed
5 6 7
 *  This file is part of Epiphany.
 *
 *  Epiphany is free software: you can redistribute it and/or modify
8
 *  it under the terms of the GNU General Public License as published by
Michael Catanzaro's avatar
Michael Catanzaro committed
9 10
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
11
 *
Michael Catanzaro's avatar
Michael Catanzaro committed
12
 *  Epiphany is distributed in the hope that it will be useful,
13 14 15 16 17
 *  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
Michael Catanzaro's avatar
Michael Catanzaro committed
18
 *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
19 20 21 22 23 24 25 26 27 28 29 30
 */

#include "config.h"

#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <string.h>

#include <libsoup/soup.h>
#include <webkit2/webkit2.h>

#include "ephy-string.h"
31
#include "ephy-shell.h"
32 33 34

#include "cookies-dialog.h"

Michael Catanzaro's avatar
Michael Catanzaro committed
35 36 37 38
enum {
  COL_COOKIES_HOST,
  COL_COOKIES_HOST_KEY,
  COL_COOKIES_DATA,
39 40
};

Michael Catanzaro's avatar
Michael Catanzaro committed
41 42
struct _EphyCookiesDialog {
  GtkDialog parent_instance;
43

Michael Catanzaro's avatar
Michael Catanzaro committed
44 45 46 47 48
  GtkWidget *cookies_treeview;
  GtkTreeSelection *tree_selection;
  GtkWidget *liststore;
  GtkWidget *treemodelfilter;
  GtkWidget *treemodelsort;
Arnaud B.'s avatar
Arnaud B. committed
49

Michael Catanzaro's avatar
Michael Catanzaro committed
50
  GActionGroup *action_group;
51

52
  WebKitWebsiteDataManager *data_manager;
Michael Catanzaro's avatar
Michael Catanzaro committed
53
  gboolean filled;
54

Michael Catanzaro's avatar
Michael Catanzaro committed
55
  char *search_text;
56 57
};

58
G_DEFINE_TYPE (EphyCookiesDialog, ephy_cookies_dialog, GTK_TYPE_DIALOG)
59

Michael Catanzaro's avatar
Michael Catanzaro committed
60
static void populate_model (EphyCookiesDialog *dialog);
61
static void cookie_changed_cb (WebKitCookieManager *cookie_manager,
62
                               EphyCookiesDialog   *dialog);
63 64

static void
65
reload_model (EphyCookiesDialog *dialog)
66
{
67
  g_signal_handlers_disconnect_by_func (webkit_website_data_manager_get_cookie_manager (dialog->data_manager), cookie_changed_cb, dialog);
Michael Catanzaro's avatar
Michael Catanzaro committed
68 69 70
  gtk_list_store_clear (GTK_LIST_STORE (dialog->liststore));
  dialog->filled = FALSE;
  populate_model (dialog);
71 72 73 74
}

static void
cookie_changed_cb (WebKitCookieManager *cookie_manager,
75
                   EphyCookiesDialog   *dialog)
76
{
Michael Catanzaro's avatar
Michael Catanzaro committed
77
  reload_model (dialog);
78 79 80
}

static void
81
ephy_cookies_dialog_dispose (GObject *object)
82
{
83
  g_signal_handlers_disconnect_by_func (webkit_website_data_manager_get_cookie_manager (EPHY_COOKIES_DIALOG (object)->data_manager), cookie_changed_cb, object);
Michael Catanzaro's avatar
Michael Catanzaro committed
84
  G_OBJECT_CLASS (ephy_cookies_dialog_parent_class)->dispose (object);
85 86
}

87
static void
88
ephy_cookies_dialog_finalize (GObject *object)
89
{
Michael Catanzaro's avatar
Michael Catanzaro committed
90 91
  g_free (EPHY_COOKIES_DIALOG (object)->search_text);
  G_OBJECT_CLASS (ephy_cookies_dialog_parent_class)->finalize (object);
92 93
}

94
static void
Michael Catanzaro's avatar
Michael Catanzaro committed
95 96 97
forget (GSimpleAction *action,
        GVariant      *parameter,
        gpointer       user_data)
98
{
Michael Catanzaro's avatar
Michael Catanzaro committed
99 100
  EphyCookiesDialog *dialog = EPHY_COOKIES_DIALOG (user_data);
  GList *llist, *rlist = NULL, *l, *r;
101
  GList *data_to_remove = NULL;
Michael Catanzaro's avatar
Michael Catanzaro committed
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
  GtkTreeModel *model;
  GtkTreePath *path;
  GtkTreeIter iter, iter2;
  GtkTreeRowReference *row_ref = NULL;

  llist = gtk_tree_selection_get_selected_rows (dialog->tree_selection, &model);

  if (llist == NULL) {
    /* nothing to delete, return early */
    return;
  }

  for (l = llist; l != NULL; l = l->next) {
    rlist = g_list_prepend (rlist, gtk_tree_row_reference_new (model, (GtkTreePath *)l->data));
  }

  /* Intelligent selection logic, no actual selection yet */

  path = gtk_tree_row_reference_get_path ((GtkTreeRowReference *)g_list_first (rlist)->data);

  gtk_tree_model_get_iter (model, &iter, path);
  gtk_tree_path_free (path);
  iter2 = iter;

  if (gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter)) {
    path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
    row_ref = gtk_tree_row_reference_new (model, path);
  } else {
    path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter2);
    if (gtk_tree_path_prev (path)) {
      row_ref = gtk_tree_row_reference_new (model, path);
    }
  }
  gtk_tree_path_free (path);

  /* Removal */
  for (r = rlist; r != NULL; r = r->next) {
    GValue val = { 0, };

    GtkTreeIter filter_iter;
    GtkTreeIter child_iter;

    path = gtk_tree_row_reference_get_path ((GtkTreeRowReference *)r->data);
    gtk_tree_model_get_iter (model, &iter, path);
146 147
    gtk_tree_model_get_value (model, &iter, COL_COOKIES_DATA, &val);
    data_to_remove = g_list_prepend (data_to_remove, g_value_dup_boxed (&val));
Michael Catanzaro's avatar
Michael Catanzaro committed
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
    g_value_unset (&val);

    gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT (dialog->treemodelsort),
                                                    &filter_iter,
                                                    &iter);

    gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (dialog->treemodelfilter),
                                                      &child_iter,
                                                      &filter_iter);

    gtk_list_store_remove (GTK_LIST_STORE (dialog->liststore), &child_iter);

    gtk_tree_row_reference_free ((GtkTreeRowReference *)r->data);
    gtk_tree_path_free (path);
  }

  g_list_foreach (llist, (GFunc)gtk_tree_path_free, NULL);
  g_list_free (llist);
  g_list_free (rlist);

168 169 170 171 172
  if (data_to_remove) {
    webkit_website_data_manager_remove (dialog->data_manager, WEBKIT_WEBSITE_DATA_COOKIES, data_to_remove, NULL, NULL, NULL);
    g_list_free_full (data_to_remove, (GDestroyNotify)webkit_website_data_unref);
  }

Michael Catanzaro's avatar
Michael Catanzaro committed
173 174 175 176 177 178 179 180 181 182 183
  /* Selection */
  if (row_ref != NULL) {
    path = gtk_tree_row_reference_get_path (row_ref);

    if (path != NULL) {
      gtk_tree_view_set_cursor (GTK_TREE_VIEW (dialog->cookies_treeview), path, NULL, FALSE);
      gtk_tree_path_free (path);
    }

    gtk_tree_row_reference_free (row_ref);
  }
184 185
}

Arnaud B.'s avatar
Arnaud B. committed
186 187 188 189
static void
update_selection_actions (GActionMap *action_map,
                          gboolean    has_selection)
{
Michael Catanzaro's avatar
Michael Catanzaro committed
190
  GAction *forget_action;
191

Michael Catanzaro's avatar
Michael Catanzaro committed
192 193
  forget_action = g_action_map_lookup_action (action_map, "forget");
  g_simple_action_set_enabled (G_SIMPLE_ACTION (forget_action), has_selection);
194 195 196
}

static void
197 198
on_treeview_selection_changed (GtkTreeSelection  *selection,
                               EphyCookiesDialog *dialog)
199
{
Michael Catanzaro's avatar
Michael Catanzaro committed
200 201
  update_selection_actions (G_ACTION_MAP (dialog->action_group),
                            gtk_tree_selection_count_selected_rows (selection) > 0);
202 203
}

204
static void
205 206
on_search_entry_changed (GtkSearchEntry    *entry,
                         EphyCookiesDialog *dialog)
207
{
Michael Catanzaro's avatar
Michael Catanzaro committed
208
  const char *text;
209

Michael Catanzaro's avatar
Michael Catanzaro committed
210 211 212 213
  text = gtk_entry_get_text (GTK_ENTRY (entry));
  g_free (dialog->search_text);
  dialog->search_text = g_strdup (text);
  gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (dialog->treemodelfilter));
214 215
}

216
static void
Michael Catanzaro's avatar
Michael Catanzaro committed
217 218 219
forget_all (GSimpleAction *action,
            GVariant      *parameter,
            gpointer       user_data)
220
{
Michael Catanzaro's avatar
Michael Catanzaro committed
221
  EphyCookiesDialog *dialog = EPHY_COOKIES_DIALOG (user_data);
222

223
  webkit_website_data_manager_clear (dialog->data_manager, WEBKIT_WEBSITE_DATA_COOKIES, 0, NULL, NULL, NULL);
Michael Catanzaro's avatar
Michael Catanzaro committed
224
  reload_model (dialog);
225 226
}

227
static void
228
ephy_cookies_dialog_class_init (EphyCookiesDialogClass *klass)
229
{
Michael Catanzaro's avatar
Michael Catanzaro committed
230 231
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
232

Michael Catanzaro's avatar
Michael Catanzaro committed
233 234
  object_class->dispose = ephy_cookies_dialog_dispose;
  object_class->finalize = ephy_cookies_dialog_finalize;
235

236 237
  g_type_ensure (WEBKIT_TYPE_WEBSITE_DATA);

Michael Catanzaro's avatar
Michael Catanzaro committed
238
  gtk_widget_class_set_template_from_resource (widget_class,
239
                                               "/org/gnome/epiphany/gtk/cookies-dialog.ui");
240

Michael Catanzaro's avatar
Michael Catanzaro committed
241 242 243 244 245
  gtk_widget_class_bind_template_child (widget_class, EphyCookiesDialog, liststore);
  gtk_widget_class_bind_template_child (widget_class, EphyCookiesDialog, treemodelfilter);
  gtk_widget_class_bind_template_child (widget_class, EphyCookiesDialog, treemodelsort);
  gtk_widget_class_bind_template_child (widget_class, EphyCookiesDialog, cookies_treeview);
  gtk_widget_class_bind_template_child (widget_class, EphyCookiesDialog, tree_selection);
246

Michael Catanzaro's avatar
Michael Catanzaro committed
247 248
  gtk_widget_class_bind_template_callback (widget_class, on_treeview_selection_changed);
  gtk_widget_class_bind_template_callback (widget_class, on_search_entry_changed);
249 250 251 252
}

static gboolean
cookie_search_equal (GtkTreeModel *model,
Michael Catanzaro's avatar
Michael Catanzaro committed
253 254 255 256
                     int           column,
                     const gchar  *key,
                     GtkTreeIter  *iter,
                     gpointer      search_data)
257
{
Michael Catanzaro's avatar
Michael Catanzaro committed
258 259
  GValue value = { 0, };
  gboolean retval;
260

Michael Catanzaro's avatar
Michael Catanzaro committed
261
  /* Note that this is function has to return FALSE for a *match* ! */
262

Michael Catanzaro's avatar
Michael Catanzaro committed
263 264 265
  gtk_tree_model_get_value (model, iter, column, &value);
  retval = strstr (g_value_get_string (&value), key) == NULL;
  g_value_unset (&value);
266

Michael Catanzaro's avatar
Michael Catanzaro committed
267
  return retval;
268 269 270
}

static void
271
cookie_add (EphyCookiesDialog *dialog,
272
            WebKitWebsiteData *data)
273
{
274
  const char *domain;
Michael Catanzaro's avatar
Michael Catanzaro committed
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
  GtkListStore *store;
  GtkTreeIter iter;
  int column[3] = { COL_COOKIES_HOST, COL_COOKIES_HOST_KEY, COL_COOKIES_DATA };
  GValue value[3] = { { 0, }, { 0, }, { 0, } };

  store = GTK_LIST_STORE (dialog->liststore);

  /* NOTE: We use this strange method to insert the row, because
   * we want to use g_value_take_string but all the row data needs to
   * be inserted in one call as it's needed when the new row is sorted
   * into the model.
   */

  g_value_init (&value[0], G_TYPE_STRING);
  g_value_init (&value[1], G_TYPE_STRING);
290
  g_value_init (&value[2], WEBKIT_TYPE_WEBSITE_DATA);
Michael Catanzaro's avatar
Michael Catanzaro committed
291

292 293
  domain = webkit_website_data_get_name (data);
  g_value_set_string (&value[0], domain);
Michael Catanzaro's avatar
Michael Catanzaro committed
294
  g_value_take_string (&value[1], ephy_string_collate_key_for_domain (domain, -1));
295
  g_value_take_boxed (&value[2], data);
Michael Catanzaro's avatar
Michael Catanzaro committed
296 297 298 299 300 301 302 303

  gtk_list_store_insert_with_valuesv (store, &iter, -1,
                                      column, value,
                                      G_N_ELEMENTS (value));

  g_value_unset (&value[0]);
  g_value_unset (&value[1]);
  g_value_unset (&value[2]);
304 305 306 307
}

static int
compare_cookie_host_keys (GtkTreeModel *model,
Michael Catanzaro's avatar
Michael Catanzaro committed
308 309 310
                          GtkTreeIter  *a,
                          GtkTreeIter  *b,
                          gpointer      user_data)
311
{
Michael Catanzaro's avatar
Michael Catanzaro committed
312 313 314
  GValue a_value = { 0, };
  GValue b_value = { 0, };
  int retval;
315

Michael Catanzaro's avatar
Michael Catanzaro committed
316 317
  gtk_tree_model_get_value (model, a, COL_COOKIES_HOST_KEY, &a_value);
  gtk_tree_model_get_value (model, b, COL_COOKIES_HOST_KEY, &b_value);
318

Michael Catanzaro's avatar
Michael Catanzaro committed
319 320
  retval = strcmp (g_value_get_string (&a_value),
                   g_value_get_string (&b_value));
321

Michael Catanzaro's avatar
Michael Catanzaro committed
322 323
  g_value_unset (&a_value);
  g_value_unset (&b_value);
324

Michael Catanzaro's avatar
Michael Catanzaro committed
325
  return retval;
326 327 328
}

static void
329 330 331
get_domains_with_cookies_cb (WebKitWebsiteDataManager *data_manager,
                             GAsyncResult             *result,
                             EphyCookiesDialog        *dialog)
332
{
333
  GList *data_list;
Michael Catanzaro's avatar
Michael Catanzaro committed
334

335 336
  data_list = webkit_website_data_manager_fetch_finish (data_manager, result, NULL);
  if (!data_list)
Michael Catanzaro's avatar
Michael Catanzaro committed
337 338
    return;

339 340
  for (GList *l = data_list; l && l->data; l = g_list_next (l))
    cookie_add (dialog, (WebKitWebsiteData *)l->data);
Michael Catanzaro's avatar
Michael Catanzaro committed
341

342 343
  /* The list items have been consumed, so we need only to free the list. */
  g_list_free (data_list);
Michael Catanzaro's avatar
Michael Catanzaro committed
344 345 346 347 348 349 350 351 352 353

  /* Now turn on sorting */
  gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (dialog->liststore),
                                   COL_COOKIES_HOST_KEY,
                                   (GtkTreeIterCompareFunc)compare_cookie_host_keys,
                                   NULL, NULL);
  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (dialog->liststore),
                                        COL_COOKIES_HOST_KEY,
                                        GTK_SORT_ASCENDING);

354
  g_signal_connect (webkit_website_data_manager_get_cookie_manager (data_manager),
Michael Catanzaro's avatar
Michael Catanzaro committed
355 356 357 358 359
                    "changed",
                    G_CALLBACK (cookie_changed_cb),
                    dialog);

  dialog->filled = TRUE;
360 361
}

362
static gboolean
363 364 365
row_visible_func (GtkTreeModel      *model,
                  GtkTreeIter       *iter,
                  EphyCookiesDialog *dialog)
366
{
Michael Catanzaro's avatar
Michael Catanzaro committed
367 368
  gboolean visible = FALSE;
  gchar *host;
369

Michael Catanzaro's avatar
Michael Catanzaro committed
370 371
  if (dialog->search_text == NULL)
    return TRUE;
372

Michael Catanzaro's avatar
Michael Catanzaro committed
373 374 375
  gtk_tree_model_get (model, iter,
                      COL_COOKIES_HOST, &host,
                      -1);
376

Michael Catanzaro's avatar
Michael Catanzaro committed
377 378
  if (host != NULL && strstr (host, dialog->search_text) != NULL)
    visible = TRUE;
379

Michael Catanzaro's avatar
Michael Catanzaro committed
380
  g_free (host);
381

Michael Catanzaro's avatar
Michael Catanzaro committed
382
  return visible;
383 384
}

385
static void
386
populate_model (EphyCookiesDialog *dialog)
387
{
Michael Catanzaro's avatar
Michael Catanzaro committed
388
  g_assert (dialog->filled == FALSE);
389

390 391 392 393 394
  webkit_website_data_manager_fetch (dialog->data_manager,
                                     WEBKIT_WEBSITE_DATA_COOKIES,
                                     NULL,
                                     (GAsyncReadyCallback)get_domains_with_cookies_cb,
                                     dialog);
395 396 397
}

static void
398
setup_page (EphyCookiesDialog *dialog)
399
{
Michael Catanzaro's avatar
Michael Catanzaro committed
400 401 402 403
  gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (dialog->cookies_treeview),
                                       (GtkTreeViewSearchEqualFunc)cookie_search_equal,
                                       dialog, NULL);
  populate_model (dialog);
404 405
}

Arnaud B.'s avatar
Arnaud B. committed
406
static GActionGroup *
407
create_action_group (EphyCookiesDialog *dialog)
Arnaud B.'s avatar
Arnaud B. committed
408
{
Michael Catanzaro's avatar
Michael Catanzaro committed
409 410 411 412
  const GActionEntry entries[] = {
    { "forget", forget },
    { "forget-all", forget_all }
  };
Arnaud B.'s avatar
Arnaud B. committed
413

Michael Catanzaro's avatar
Michael Catanzaro committed
414
  GSimpleActionGroup *group;
Arnaud B.'s avatar
Arnaud B. committed
415

Michael Catanzaro's avatar
Michael Catanzaro committed
416 417
  group = g_simple_action_group_new ();
  g_action_map_add_action_entries (G_ACTION_MAP (group), entries, G_N_ELEMENTS (entries), dialog);
Arnaud B.'s avatar
Arnaud B. committed
418

Michael Catanzaro's avatar
Michael Catanzaro committed
419
  return G_ACTION_GROUP (group);
Arnaud B.'s avatar
Arnaud B. committed
420 421
}

422
static void
423
ephy_cookies_dialog_init (EphyCookiesDialog *dialog)
424
{
Michael Catanzaro's avatar
Michael Catanzaro committed
425 426
  WebKitWebContext *web_context;
  EphyEmbedShell *shell = ephy_embed_shell_get_default ();
427

Michael Catanzaro's avatar
Michael Catanzaro committed
428
  gtk_widget_init_template (GTK_WIDGET (dialog));
429

Michael Catanzaro's avatar
Michael Catanzaro committed
430 431 432 433
  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (dialog->treemodelfilter),
                                          (GtkTreeModelFilterVisibleFunc)row_visible_func,
                                          dialog,
                                          NULL);
434

Michael Catanzaro's avatar
Michael Catanzaro committed
435
  web_context = ephy_embed_shell_get_web_context (shell);
436
  dialog->data_manager = webkit_web_context_get_website_data_manager (web_context);
437

Michael Catanzaro's avatar
Michael Catanzaro committed
438
  setup_page (dialog);
Arnaud B.'s avatar
Arnaud B. committed
439

Michael Catanzaro's avatar
Michael Catanzaro committed
440 441
  dialog->action_group = create_action_group (dialog);
  gtk_widget_insert_action_group (GTK_WIDGET (dialog), "cookies", dialog->action_group);
Arnaud B.'s avatar
Arnaud B. committed
442

Michael Catanzaro's avatar
Michael Catanzaro committed
443
  update_selection_actions (G_ACTION_MAP (dialog->action_group), FALSE);
444 445 446 447 448
}

EphyCookiesDialog *
ephy_cookies_dialog_new (void)
{
Michael Catanzaro's avatar
Michael Catanzaro committed
449 450 451
  return g_object_new (EPHY_TYPE_COOKIES_DIALOG,
                       "use-header-bar", TRUE,
                       NULL);
452
}