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