1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3  *  Copyright © 2013 Red Hat, Inc.
4  *
5  *  This file is part of Epiphany.
6  *
7  *  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
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  Epiphany is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 #include "clear-data-view.h"
23 
24 #include <glib/gi18n.h>
25 #include <gtk/gtk.h>
26 #include <string.h>
27 #include <webkit2/webkit2.h>
28 
29 #include "ephy-history-service.h"
30 #include "ephy-embed-shell.h"
31 #include "ephy-settings.h"
32 
33 struct _ClearDataView {
34   EphyDataView parent_instance;
35 
36   GtkWidget *treeview;
37   GtkTreeModel *treestore;
38   GtkTreeModelFilter *treemodelfilter;
39 
40   GCancellable *cancellable;
41 };
42 
43 enum {
44   TYPE_COLUMN,
45   ACTIVE_COLUMN,
46   NAME_COLUMN,
47   DATA_COLUMN,
48   SENSITIVE_COLUMN
49 };
50 
51 G_DEFINE_TYPE (ClearDataView, clear_data_view, EPHY_TYPE_DATA_VIEW)
52 
53 #define PERSISTENT_DATA_TYPES WEBKIT_WEBSITE_DATA_COOKIES | \
54   WEBKIT_WEBSITE_DATA_DISK_CACHE | \
55   WEBKIT_WEBSITE_DATA_OFFLINE_APPLICATION_CACHE | \
56   WEBKIT_WEBSITE_DATA_LOCAL_STORAGE | \
57   WEBKIT_WEBSITE_DATA_WEBSQL_DATABASES | \
58   WEBKIT_WEBSITE_DATA_INDEXEDDB_DATABASES | \
59   WEBKIT_WEBSITE_DATA_PLUGIN_DATA | \
60   WEBKIT_WEBSITE_DATA_HSTS_CACHE | \
61   WEBKIT_WEBSITE_DATA_ITP
62 
63 typedef struct {
64   guint id;
65   WebKitWebsiteDataTypes type;
66   const char *name;
67 } DataEntry;
68 
69 static const DataEntry data_entries[] = {
70   { 0x001, WEBKIT_WEBSITE_DATA_COOKIES, N_("Cookies") },
71   { 0x002, WEBKIT_WEBSITE_DATA_DISK_CACHE, N_("HTTP disk cache") },
72   { 0x004, WEBKIT_WEBSITE_DATA_LOCAL_STORAGE, N_("Local storage data") },
73   { 0x008, WEBKIT_WEBSITE_DATA_OFFLINE_APPLICATION_CACHE, N_("Offline web application cache") },
74   { 0x010, WEBKIT_WEBSITE_DATA_INDEXEDDB_DATABASES, N_("IndexedDB databases") },
75   { 0x020, WEBKIT_WEBSITE_DATA_WEBSQL_DATABASES, N_("WebSQL databases") },
76   { 0x040, WEBKIT_WEBSITE_DATA_PLUGIN_DATA, N_("Plugins data") },
77   { 0x080, WEBKIT_WEBSITE_DATA_HSTS_CACHE, N_("HSTS policies cache") },
78   { 0x100, WEBKIT_WEBSITE_DATA_ITP, N_("Intelligent Tracking Prevention data") }
79 };
80 
81 static WebKitWebsiteDataManager *
get_website_data_manger(void)82 get_website_data_manger (void)
83 {
84   WebKitWebContext *web_context;
85 
86   web_context = ephy_embed_shell_get_web_context (ephy_embed_shell_get_default ());
87   return webkit_web_context_get_website_data_manager (web_context);
88 }
89 
90 static void
website_data_fetched_cb(WebKitWebsiteDataManager * manager,GAsyncResult * result,ClearDataView * clear_data_view)91 website_data_fetched_cb (WebKitWebsiteDataManager *manager,
92                          GAsyncResult             *result,
93                          ClearDataView            *clear_data_view)
94 {
95   GList *data_list;
96   GtkTreeStore *treestore;
97   GError *error = NULL;
98   int active_items;
99 
100   data_list = webkit_website_data_manager_fetch_finish (manager, result, &error);
101   if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
102     g_error_free (error);
103     return;
104   }
105 
106   ephy_data_view_set_is_loading (EPHY_DATA_VIEW (clear_data_view), FALSE);
107 
108   if (!data_list) {
109     ephy_data_view_set_has_data (EPHY_DATA_VIEW (clear_data_view), FALSE);
110 
111     if (error)
112       g_error_free (error);
113     return;
114   }
115 
116   ephy_data_view_set_has_data (EPHY_DATA_VIEW (clear_data_view), TRUE);
117   active_items = g_settings_get_int (EPHY_SETTINGS_MAIN, EPHY_PREFS_ACTIVE_CLEAR_DATA_ITEMS);
118 
119   treestore = GTK_TREE_STORE (clear_data_view->treestore);
120   for (guint i = 0; i < G_N_ELEMENTS (data_entries); i++) {
121     GtkTreeIter parent_iter;
122     gboolean empty = TRUE;
123 
124     gtk_tree_store_insert_with_values (treestore, &parent_iter, NULL, -1,
125                                        TYPE_COLUMN, data_entries[i].type,
126                                        ACTIVE_COLUMN, active_items & data_entries[i].id,
127                                        NAME_COLUMN, _(data_entries[i].name),
128                                        DATA_COLUMN, NULL,
129                                        SENSITIVE_COLUMN, TRUE,
130                                        -1);
131     for (GList *l = data_list; l && l->data; l = g_list_next (l)) {
132       WebKitWebsiteData *data = (WebKitWebsiteData *)l->data;
133 
134       if (!(webkit_website_data_get_types (data) & data_entries[i].type))
135         continue;
136 
137       gtk_tree_store_insert_with_values (treestore, NULL, &parent_iter, -1,
138                                          TYPE_COLUMN, data_entries[i].type,
139                                          ACTIVE_COLUMN, active_items & data_entries[i].id,
140                                          NAME_COLUMN, webkit_website_data_get_name (data),
141                                          DATA_COLUMN, webkit_website_data_ref (data),
142                                          SENSITIVE_COLUMN, TRUE,
143                                          -1);
144       empty = FALSE;
145     }
146 
147     if (empty)
148       gtk_tree_store_remove (treestore, &parent_iter);
149   }
150 
151   g_list_free_full (data_list, (GDestroyNotify)webkit_website_data_unref);
152 }
153 
154 static gboolean
all_children_visible(GtkTreeModel * model,GtkTreeIter * child_iter,GtkTreeModelFilter * filter)155 all_children_visible (GtkTreeModel       *model,
156                       GtkTreeIter        *child_iter,
157                       GtkTreeModelFilter *filter)
158 {
159   GtkTreeIter filter_iter;
160 
161   gtk_tree_model_filter_convert_child_iter_to_iter (filter, &filter_iter, child_iter);
162   return gtk_tree_model_iter_n_children (model, child_iter) == gtk_tree_model_iter_n_children (GTK_TREE_MODEL (filter), &filter_iter);
163 }
164 
165 static void
on_clear_button_clicked(ClearDataView * clear_data_view)166 on_clear_button_clicked (ClearDataView *clear_data_view)
167 {
168   GtkTreeIter top_iter;
169   WebKitWebsiteDataTypes types_to_clear = 0;
170   GList *data_to_remove = NULL;
171   WebKitWebsiteDataTypes types_to_remove = 0;
172 
173   if (!gtk_tree_model_get_iter_first (clear_data_view->treestore, &top_iter))
174     return;
175 
176   do {
177     guint type;
178     gboolean active;
179     GtkTreeIter child_iter;
180 
181     gtk_tree_model_get (clear_data_view->treestore, &top_iter,
182                         TYPE_COLUMN, &type,
183                         ACTIVE_COLUMN, &active,
184                         -1);
185     if (active && all_children_visible (clear_data_view->treestore, &top_iter, clear_data_view->treemodelfilter)) {
186       types_to_clear |= type;
187     } else if (gtk_tree_model_iter_children (clear_data_view->treestore, &child_iter, &top_iter)) {
188       gboolean empty = TRUE;
189 
190       do {
191         WebKitWebsiteData *data;
192         GtkTreeIter filter_iter;
193 
194         if (gtk_tree_model_filter_convert_child_iter_to_iter (clear_data_view->treemodelfilter, &filter_iter, &child_iter)) {
195           gtk_tree_model_get (clear_data_view->treestore, &child_iter,
196                               ACTIVE_COLUMN, &active,
197                               DATA_COLUMN, &data,
198                               -1);
199 
200           if (active) {
201             data_to_remove = g_list_prepend (data_to_remove, data);
202             empty = FALSE;
203           } else
204             webkit_website_data_unref (data);
205         }
206       } while (gtk_tree_model_iter_next (clear_data_view->treestore, &child_iter));
207 
208       if (!empty)
209         types_to_remove |= type;
210     }
211   } while (gtk_tree_model_iter_next (clear_data_view->treestore, &top_iter));
212 
213   if (types_to_clear) {
214     webkit_website_data_manager_clear (get_website_data_manger (),
215                                        types_to_clear, 0,
216                                        NULL, NULL, NULL);
217   }
218 
219   if (types_to_remove) {
220     webkit_website_data_manager_remove (get_website_data_manger (),
221                                         types_to_remove, data_to_remove,
222                                         NULL, NULL, NULL);
223   }
224 
225   g_list_free_full (data_to_remove, (GDestroyNotify)webkit_website_data_unref);
226 
227   /* Reload tree */
228   ephy_data_view_set_is_loading (EPHY_DATA_VIEW (clear_data_view), TRUE);
229   gtk_tree_store_clear (GTK_TREE_STORE (clear_data_view->treestore));
230   webkit_website_data_manager_fetch (get_website_data_manger (),
231                                      PERSISTENT_DATA_TYPES,
232                                      clear_data_view->cancellable,
233                                      (GAsyncReadyCallback)website_data_fetched_cb,
234                                      clear_data_view);
235 }
236 
237 static gboolean
any_item_checked(ClearDataView * self)238 any_item_checked (ClearDataView *self)
239 {
240   GtkTreeIter top_iter;
241 
242   if (!gtk_tree_model_get_iter_first (self->treestore, &top_iter))
243     return FALSE;
244 
245   do {
246     gboolean active;
247     GtkTreeIter child_iter;
248 
249     gtk_tree_model_get (self->treestore, &top_iter,
250                         ACTIVE_COLUMN, &active, -1);
251     if (active) {
252       return TRUE;
253     } else if (gtk_tree_model_iter_children (self->treestore, &child_iter, &top_iter)) {
254       do {
255         GtkTreeIter filter_iter;
256 
257         if (gtk_tree_model_filter_convert_child_iter_to_iter (self->treemodelfilter, &filter_iter, &child_iter)) {
258           gtk_tree_model_get (self->treestore, &child_iter,
259                               ACTIVE_COLUMN, &active, -1);
260           if (active)
261             return TRUE;
262         }
263       } while (gtk_tree_model_iter_next (self->treestore, &child_iter));
264     }
265   } while (gtk_tree_model_iter_next (self->treestore, &top_iter));
266 
267   return FALSE;
268 }
269 
270 static void
item_toggled_cb(GtkCellRendererToggle * renderer,const char * path_str,ClearDataView * clear_data_view)271 item_toggled_cb (GtkCellRendererToggle *renderer,
272                  const char            *path_str,
273                  ClearDataView         *clear_data_view)
274 {
275   GtkTreePath *path = gtk_tree_path_new_from_string (path_str);
276   GtkTreeIter filter_iter, iter;
277   gboolean active;
278 
279   gtk_tree_model_get_iter (GTK_TREE_MODEL (clear_data_view->treemodelfilter),
280                            &filter_iter, path);
281   gtk_tree_model_filter_convert_iter_to_child_iter (clear_data_view->treemodelfilter,
282                                                     &iter, &filter_iter);
283   gtk_tree_model_get (clear_data_view->treestore, &iter,
284                       ACTIVE_COLUMN, &active,
285                       -1);
286   gtk_tree_store_set (GTK_TREE_STORE (clear_data_view->treestore), &iter,
287                       ACTIVE_COLUMN, !active,
288                       -1);
289 
290   if (gtk_tree_model_iter_has_child (clear_data_view->treestore, &iter)) {
291     GtkTreeIter child_iter;
292     g_autofree char *name = NULL;
293     int active_items;
294 
295     active_items = g_settings_get_int (EPHY_SETTINGS_MAIN, EPHY_PREFS_ACTIVE_CLEAR_DATA_ITEMS);
296 
297     gtk_tree_model_get (clear_data_view->treestore, &iter,
298                         NAME_COLUMN, &name,
299                         -1);
300     for (guint i = 0; i < G_N_ELEMENTS (data_entries); i++) {
301       if (g_strcmp0 (gettext (data_entries[i].name), name) == 0) {
302         if (active)
303           active_items &= ~data_entries[i].id;
304         else
305           active_items |= data_entries[i].id;
306 
307         break;
308       }
309     }
310 
311     g_settings_set_int (EPHY_SETTINGS_MAIN, EPHY_PREFS_ACTIVE_CLEAR_DATA_ITEMS, active_items);
312 
313     gtk_tree_model_iter_children (clear_data_view->treestore, &child_iter, &iter);
314     do {
315       gtk_tree_store_set (GTK_TREE_STORE (clear_data_view->treestore), &child_iter,
316                           ACTIVE_COLUMN, !active,
317                           -1);
318     } while (gtk_tree_model_iter_next (clear_data_view->treestore, &child_iter));
319   } else {
320     GtkTreeIter parent_iter;
321 
322     /* Update the parent */
323     gtk_tree_model_iter_parent (clear_data_view->treestore, &parent_iter, &iter);
324     if (active) {
325       /* When unchecking a child we know the parent should be unchecked too */
326       gtk_tree_store_set (GTK_TREE_STORE (clear_data_view->treestore), &parent_iter,
327                           ACTIVE_COLUMN, FALSE,
328                           -1);
329     } else {
330       GtkTreeIter child_iter;
331       gboolean all_active = TRUE;
332 
333       /* When checking a child, parent should be checked if all its children are */
334       gtk_tree_model_iter_children (clear_data_view->treestore, &child_iter, &parent_iter);
335       do {
336         gtk_tree_model_get (clear_data_view->treestore, &child_iter,
337                             ACTIVE_COLUMN, &all_active,
338                             -1);
339       } while (all_active && gtk_tree_model_iter_next (clear_data_view->treestore, &child_iter));
340 
341       if (all_active) {
342         gtk_tree_store_set (GTK_TREE_STORE (clear_data_view->treestore), &parent_iter,
343                             ACTIVE_COLUMN, TRUE,
344                             -1);
345       }
346     }
347   }
348 
349   gtk_tree_path_free (path);
350 
351   ephy_data_view_set_can_clear (EPHY_DATA_VIEW (clear_data_view), any_item_checked (clear_data_view));
352 }
353 
354 static void
search_text_changed_cb(ClearDataView * clear_data_view)355 search_text_changed_cb (ClearDataView *clear_data_view)
356 {
357   gtk_tree_model_filter_refilter (clear_data_view->treemodelfilter);
358 }
359 
360 static gboolean
row_visible_func(GtkTreeModel * model,GtkTreeIter * iter,ClearDataView * clear_data_view)361 row_visible_func (GtkTreeModel  *model,
362                   GtkTreeIter   *iter,
363                   ClearDataView *clear_data_view)
364 {
365   const char *search_text;
366   char *name;
367   gboolean visible;
368 
369   if (gtk_tree_model_iter_has_child (model, iter))
370     return TRUE;
371 
372   search_text = ephy_data_view_get_search_text (EPHY_DATA_VIEW (clear_data_view));
373   if (!search_text || search_text[0] == '\0')
374     return TRUE;
375 
376   gtk_tree_model_get (model, iter,
377                       NAME_COLUMN, &name,
378                       -1);
379 
380   visible = name && strstr (name, search_text);
381   g_free (name);
382 
383   if (visible) {
384     GtkTreeIter parent_iter;
385     GtkTreePath *path;
386 
387     gtk_tree_model_iter_parent (model, &parent_iter, iter);
388     path = gtk_tree_model_get_path (model, &parent_iter);
389     gtk_tree_view_expand_row (GTK_TREE_VIEW (clear_data_view->treeview), path, FALSE);
390     gtk_tree_path_free (path);
391   }
392 
393   return visible;
394 }
395 
396 static void
clear_data_view_dispose(GObject * object)397 clear_data_view_dispose (GObject *object)
398 {
399   ClearDataView *clear_data_view = (ClearDataView *)object;
400 
401   if (clear_data_view->cancellable) {
402     g_cancellable_cancel (clear_data_view->cancellable);
403     g_clear_object (&clear_data_view->cancellable);
404   }
405 
406   G_OBJECT_CLASS (clear_data_view_parent_class)->dispose (object);
407 }
408 
409 static void
clear_data_view_class_init(ClearDataViewClass * klass)410 clear_data_view_class_init (ClearDataViewClass *klass)
411 {
412   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
413   GObjectClass *object_class = G_OBJECT_CLASS (klass);
414 
415   object_class->dispose = clear_data_view_dispose;
416 
417   g_type_ensure (WEBKIT_TYPE_WEBSITE_DATA);
418 
419   gtk_widget_class_set_template_from_resource (widget_class,
420                                                "/org/gnome/epiphany/gtk/clear-data-view.ui");
421 
422   gtk_widget_class_bind_template_child (widget_class, ClearDataView, treeview);
423   gtk_widget_class_bind_template_child (widget_class, ClearDataView, treestore);
424   gtk_widget_class_bind_template_child (widget_class, ClearDataView, treemodelfilter);
425   gtk_widget_class_bind_template_callback (widget_class, item_toggled_cb);
426   gtk_widget_class_bind_template_callback (widget_class, on_clear_button_clicked);
427   gtk_widget_class_bind_template_callback (widget_class, search_text_changed_cb);
428 }
429 
430 static void
clear_data_view_init(ClearDataView * clear_data_view)431 clear_data_view_init (ClearDataView *clear_data_view)
432 {
433   gtk_widget_init_template (GTK_WIDGET (clear_data_view));
434 
435   gtk_tree_model_filter_set_visible_func (clear_data_view->treemodelfilter,
436                                           (GtkTreeModelFilterVisibleFunc)row_visible_func,
437                                           clear_data_view,
438                                           NULL);
439 
440   ephy_data_view_set_is_loading (EPHY_DATA_VIEW (clear_data_view), TRUE);
441 
442   clear_data_view->cancellable = g_cancellable_new ();
443 
444   webkit_website_data_manager_fetch (get_website_data_manger (),
445                                      PERSISTENT_DATA_TYPES,
446                                      clear_data_view->cancellable,
447                                      (GAsyncReadyCallback)website_data_fetched_cb,
448                                      clear_data_view);
449 }
450