Merge lp:~tuxator/midori/nojs into lp:midori

Proposed by Paweł Forysiuk
Status: Merged
Approved by: Cris Dywan
Approved revision: 6180
Merged at revision: 6291
Proposed branch: lp:~tuxator/midori/nojs
Merge into: lp:midori
Diff against target: 2975 lines (+2917/-0)
9 files modified
extensions/nojs/README.md (+1/-0)
extensions/nojs/main.c (+79/-0)
extensions/nojs/nojs-preferences.c (+746/-0)
extensions/nojs/nojs-preferences.h (+55/-0)
extensions/nojs/nojs-view.c (+804/-0)
extensions/nojs/nojs-view.h (+71/-0)
extensions/nojs/nojs.c (+1069/-0)
extensions/nojs/nojs.h (+89/-0)
po/POTFILES.in (+3/-0)
To merge this branch: bzr merge lp:~tuxator/midori/nojs
Reviewer Review Type Date Requested Status
Cris Dywan Approve
Review via email: mp+177460@code.launchpad.net

Commit message

Introduce extension for managing javascript execution policy per domain

To post a comment you must log in.
Revision history for this message
Cris Dywan (kalikiana) wrote :

Nice stuff. Let's get this in!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory 'data/nojs'
=== added file 'data/nojs/nojs-statusicon-allowed.png'
0Binary files data/nojs/nojs-statusicon-allowed.png 1970-01-01 00:00:00 +0000 and data/nojs/nojs-statusicon-allowed.png 2013-07-29 19:51:25 +0000 differ0Binary files data/nojs/nojs-statusicon-allowed.png 1970-01-01 00:00:00 +0000 and data/nojs/nojs-statusicon-allowed.png 2013-07-29 19:51:25 +0000 differ
=== added file 'data/nojs/nojs-statusicon-denied.png'
1Binary files data/nojs/nojs-statusicon-denied.png 1970-01-01 00:00:00 +0000 and data/nojs/nojs-statusicon-denied.png 2013-07-29 19:51:25 +0000 differ1Binary files data/nojs/nojs-statusicon-denied.png 1970-01-01 00:00:00 +0000 and data/nojs/nojs-statusicon-denied.png 2013-07-29 19:51:25 +0000 differ
=== added file 'data/nojs/nojs-statusicon-mixed.png'
2Binary files data/nojs/nojs-statusicon-mixed.png 1970-01-01 00:00:00 +0000 and data/nojs/nojs-statusicon-mixed.png 2013-07-29 19:51:25 +0000 differ2Binary files data/nojs/nojs-statusicon-mixed.png 1970-01-01 00:00:00 +0000 and data/nojs/nojs-statusicon-mixed.png 2013-07-29 19:51:25 +0000 differ
=== added directory 'extensions/nojs'
=== added file 'extensions/nojs/README.md'
--- extensions/nojs/README.md 1970-01-01 00:00:00 +0000
+++ extensions/nojs/README.md 2013-07-29 19:51:25 +0000
@@ -0,0 +1,1 @@
1This is an extension for Midori web browser. With this extension you can manage which sites are allowed to execute javascript.
02
=== added file 'extensions/nojs/main.c'
--- extensions/nojs/main.c 1970-01-01 00:00:00 +0000
+++ extensions/nojs/main.c 2013-07-29 19:51:25 +0000
@@ -0,0 +1,79 @@
1/*
2 Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 See the file COPYING for the full license text.
10*/
11
12#include "nojs.h"
13#include "nojs-preferences.h"
14
15/* Global instance */
16NoJS *noJS=NULL;
17
18/* This extension was activated */
19static void _nojs_on_activate(MidoriExtension *inExtension, MidoriApp *inApp, gpointer inUserData)
20{
21 g_return_if_fail(noJS==NULL);
22
23 noJS=nojs_new(inExtension, inApp);
24 nojs_set_policy_for_unknown_domain(noJS, midori_extension_get_integer(inExtension, "unknown-domain-policy"));
25 nojs_set_allow_all_sites(noJS, midori_extension_get_boolean(inExtension, "allow-all-sites"));
26 nojs_set_only_second_level_domain(noJS, midori_extension_get_boolean(inExtension, "only-second-level"));
27}
28
29/* This extension was deactivated */
30static void _nojs_on_deactivate(MidoriExtension *inExtension, gpointer inUserData)
31{
32 g_return_if_fail(noJS);
33
34 g_object_unref(noJS);
35 noJS=NULL;
36}
37
38/* Preferences of this extension should be opened */
39static void _nojs_on_preferences_response(GtkWidget* inDialog,
40 gint inResponse,
41 gpointer *inUserData)
42{
43 gtk_widget_destroy(inDialog);
44}
45
46static void _nojs_on_open_preferences(MidoriExtension *inExtension)
47{
48 g_return_if_fail(noJS);
49
50 /* Show preferences window */
51 GtkWidget* dialog;
52
53 dialog=nojs_preferences_new(noJS);
54 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
55 g_signal_connect(dialog, "response", G_CALLBACK (_nojs_on_preferences_response), NULL);
56 gtk_widget_show_all(dialog);
57}
58
59/* Main entry for extension */
60MidoriExtension *extension_init(void)
61{
62 /* Set up extension */
63 MidoriExtension *extension=g_object_new(MIDORI_TYPE_EXTENSION,
64 "name", _("NoJS"),
65 "description", _("Manage javascript permission per site"),
66 "version", "0.1" MIDORI_VERSION_SUFFIX,
67 "authors", "Stephan Haller <nomad@froevel.de>",
68 NULL);
69
70 midori_extension_install_integer(extension, "unknown-domain-policy", NOJS_POLICY_BLOCK);
71 midori_extension_install_boolean(extension, "allow-all-sites", FALSE);
72 midori_extension_install_boolean(extension, "only-second-level", TRUE);
73
74 g_signal_connect(extension, "activate", G_CALLBACK(_nojs_on_activate), NULL);
75 g_signal_connect(extension, "deactivate", G_CALLBACK(_nojs_on_deactivate), NULL);
76 g_signal_connect(extension, "open-preferences", G_CALLBACK(_nojs_on_open_preferences), NULL);
77
78 return(extension);
79}
080
=== added file 'extensions/nojs/nojs-preferences.c'
--- extensions/nojs/nojs-preferences.c 1970-01-01 00:00:00 +0000
+++ extensions/nojs/nojs-preferences.c 2013-07-29 19:51:25 +0000
@@ -0,0 +1,746 @@
1/*
2 Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 See the file COPYING for the full license text.
10*/
11
12#include "nojs-preferences.h"
13
14/* Define this class in GObject system */
15G_DEFINE_TYPE(NoJSPreferences,
16 nojs_preferences,
17 GTK_TYPE_DIALOG)
18
19/* Properties */
20enum
21{
22 PROP_0,
23
24 PROP_MANAGER,
25
26 PROP_LAST
27};
28
29static GParamSpec* NoJSPreferencesProperties[PROP_LAST]={ 0, };
30
31/* Private structure - access only by public API if needed */
32#define NOJS_PREFERENCES_GET_PRIVATE(obj) \
33 (G_TYPE_INSTANCE_GET_PRIVATE((obj), TYPE_NOJS_PREFERENCES, NoJSPreferencesPrivate))
34
35struct _NoJSPreferencesPrivate
36{
37 /* Extension related */
38 NoJS *manager;
39 sqlite3 *database;
40
41 /* Dialog related */
42 GtkWidget *contentArea;
43 GtkListStore *listStore;
44 GtkWidget *list;
45 GtkTreeSelection *listSelection;
46 GtkWidget *deleteButton;
47 GtkWidget *deleteAllButton;
48 GtkWidget *allowAllSitesCheckbox;
49 GtkWidget *blockUnknownDomainsCheckbox;
50 GtkWidget *checkSecondLevelOnlyCheckbox;
51
52 gint signalAllowAllSitesToggledID;
53 gint signalBlockUnknownDomainsToggledID;
54 gint signalCheckSecondLevelOnlyToggledID;
55
56 gint signalManagerChangedDatabaseID;
57 gint signalManagerChangedAllowAllSitesID;
58 gint signalManagerChangedUnknownDomainPolicyID;
59 gint signalManagerChangedCheckSecondLevelID;
60};
61
62enum
63{
64 DOMAIN_COLUMN,
65 POLICY_COLUMN,
66 N_COLUMN
67};
68
69
70/* IMPLEMENTATION: Private variables and methods */
71
72/* Fill domain list with stored policies */
73static void _nojs_preferences_fill(NoJSPreferences *self)
74{
75 NoJSPreferencesPrivate *priv=self->priv;
76 gint success;
77 sqlite3_stmt *statement=NULL;
78
79 /* Clear tree/list view */
80 gtk_list_store_clear(priv->listStore);
81
82 /* If no database is present return here */
83 if(!priv->database) return;
84
85 /* Fill list store with policies from database */
86 success=sqlite3_prepare_v2(priv->database,
87 "SELECT site, value FROM policies;",
88 -1,
89 &statement,
90 NULL);
91 if(statement && success==SQLITE_OK)
92 {
93 gchar *domain;
94 gint policy;
95 gchar *policyName;
96 GtkTreeIter iter;
97
98 while(sqlite3_step(statement)==SQLITE_ROW)
99 {
100 /* Get values */
101 domain=(gchar*)sqlite3_column_text(statement, 0);
102 policy=sqlite3_column_int(statement, 1);
103
104 switch(policy)
105 {
106 case NOJS_POLICY_ACCEPT:
107 policyName=_("Accept");
108 break;
109
110 case NOJS_POLICY_ACCEPT_TEMPORARILY:
111 policyName=_("Accept for session");
112 break;
113
114 case NOJS_POLICY_BLOCK:
115 policyName=_("Block");
116 break;
117
118 default:
119 policyName=NULL;
120 break;
121 }
122
123 if(policyName)
124 {
125 gtk_list_store_append(priv->listStore, &iter);
126 gtk_list_store_set(priv->listStore,
127 &iter,
128 DOMAIN_COLUMN, domain,
129 POLICY_COLUMN, policyName,
130 -1);
131 }
132 }
133 }
134 else g_warning(_("SQL fails: %s"), sqlite3_errmsg(priv->database));
135
136 sqlite3_finalize(statement);
137}
138
139/* Database instance in manager changed */
140static void _nojs_preferences_on_manager_database_changed(NoJSPreferences *self,
141 GParamSpec *inSpec,
142 gpointer inUserData)
143{
144 NoJSPreferencesPrivate *priv=self->priv;
145 NoJS *manager=NOJS(inUserData);
146 gchar *databaseFile;
147
148 /* Close connection to any open database */
149 if(priv->database) sqlite3_close(priv->database);
150 priv->database=NULL;
151
152 /* Get pointer to new database and open database */
153 g_object_get(manager, "database-filename", &databaseFile, NULL);
154 if(databaseFile)
155 {
156 gint success;
157
158 success=sqlite3_open(databaseFile, &priv->database);
159 if(success!=SQLITE_OK)
160 {
161 g_warning(_("Could not open database of extension: %s"), sqlite3_errmsg(priv->database));
162
163 if(priv->database) sqlite3_close(priv->database);
164 priv->database=NULL;
165 }
166
167 g_free(databaseFile);
168 }
169
170 /* Fill list with new database */
171 _nojs_preferences_fill(self);
172
173 /* Set up availability of management buttons */
174 gtk_widget_set_sensitive(priv->deleteAllButton, priv->database!=NULL);
175 gtk_widget_set_sensitive(priv->list, priv->database!=NULL);
176
177 return;
178}
179
180/* Allow-all-sites changed in check-box or manager */
181static void _nojs_preferences_on_allow_all_sites_changed(NoJSPreferences *self,
182 gpointer *inUserData)
183{
184 NoJSPreferencesPrivate *priv=self->priv;
185 gboolean state;
186
187 /* Get toggle state of widget (but block signal for manager) and set in manager */
188 g_signal_handler_block(priv->manager, priv->signalManagerChangedAllowAllSitesID);
189
190 state=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->allowAllSitesCheckbox));
191 nojs_set_allow_all_sites(priv->manager, state);
192
193 g_signal_handler_unblock(priv->manager, priv->signalManagerChangedAllowAllSitesID);
194}
195
196static void _nojs_preferences_on_manager_allow_all_sites_changed(NoJSPreferences *self,
197 GParamSpec *inSpec,
198 gpointer inUserData)
199{
200 NoJSPreferencesPrivate *priv=self->priv;
201 NoJS *manager=NOJS(inUserData);
202 gboolean state;
203
204 /* Get new value from manager */
205 state=nojs_get_allow_all_sites(manager);
206
207 /* Set toggle in widget (but block signal for toggle) */
208 g_signal_handler_block(priv->allowAllSitesCheckbox, priv->signalAllowAllSitesToggledID);
209
210 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->allowAllSitesCheckbox), state);
211
212 g_signal_handler_unblock(priv->allowAllSitesCheckbox, priv->signalAllowAllSitesToggledID);
213}
214
215/* Block-unknown-domains changed in check-box or manager */
216static void _nojs_preferences_on_block_unknown_domains_changed(NoJSPreferences *self,
217 gpointer *inUserData)
218{
219 NoJSPreferencesPrivate *priv=self->priv;
220 gboolean state;
221 NoJSPolicy policy;
222
223 /* Get toggle state of widget (but block signal for manager) and set in manager */
224 g_signal_handler_block(priv->manager, priv->signalManagerChangedUnknownDomainPolicyID);
225
226 state=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->blockUnknownDomainsCheckbox));
227 policy=(state ? NOJS_POLICY_BLOCK : NOJS_POLICY_ACCEPT);
228 nojs_set_policy_for_unknown_domain(priv->manager, policy);
229
230 g_signal_handler_unblock(priv->manager, priv->signalManagerChangedUnknownDomainPolicyID);
231}
232
233static void _nojs_preferences_on_manager_unknown_domain_policy_changed(NoJSPreferences *self,
234 GParamSpec *inSpec,
235 gpointer inUserData)
236{
237 NoJSPreferencesPrivate *priv=self->priv;
238 NoJS *manager=NOJS(inUserData);
239 NoJSPolicy policy;
240 gboolean state;
241
242 /* Get new value from manager */
243 policy=nojs_get_policy_for_unknown_domain(manager);
244
245 /* Set toggle in widget (but block signal for toggle) */
246 g_signal_handler_block(priv->blockUnknownDomainsCheckbox, priv->signalBlockUnknownDomainsToggledID);
247
248 state=(policy==NOJS_POLICY_BLOCK ? TRUE : FALSE);
249 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->blockUnknownDomainsCheckbox), state);
250
251 g_signal_handler_unblock(priv->blockUnknownDomainsCheckbox, priv->signalBlockUnknownDomainsToggledID);
252}
253
254/* Only-second-level changed in check-box or manager */
255static void _nojs_preferences_on_check_second_level_only_changed(NoJSPreferences *self,
256 gpointer *inUserData)
257{
258 NoJSPreferencesPrivate *priv=self->priv;
259 gboolean state;
260
261 /* Get toggle state of widget (but block signal for manager) and set in manager */
262 g_signal_handler_block(priv->manager, priv->signalManagerChangedCheckSecondLevelID);
263
264 state=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->checkSecondLevelOnlyCheckbox));
265 nojs_set_only_second_level_domain(priv->manager, state);
266
267 g_signal_handler_unblock(priv->manager, priv->signalManagerChangedCheckSecondLevelID);
268}
269
270static void _nojs_preferences_on_manager_only_second_level_changed(NoJSPreferences *self,
271 GParamSpec *inSpec,
272 gpointer inUserData)
273{
274 NoJSPreferencesPrivate *priv=self->priv;
275 NoJS *manager=NOJS(inUserData);
276 gboolean state;
277
278 /* Get new value from manager */
279 state=nojs_get_only_second_level_domain(manager);
280
281 /* Set toggle in widget (but block signal for toggle) */
282 g_signal_handler_block(priv->checkSecondLevelOnlyCheckbox, priv->signalCheckSecondLevelOnlyToggledID);
283
284 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->checkSecondLevelOnlyCheckbox), state);
285
286 g_signal_handler_unblock(priv->checkSecondLevelOnlyCheckbox, priv->signalCheckSecondLevelOnlyToggledID);
287}
288
289/* Selection in list changed */
290void _nojs_preferences_changed_selection(NoJSPreferences *self,
291 GtkTreeSelection *inSelection)
292{
293 gboolean selected=(gtk_tree_selection_count_selected_rows(inSelection)>0 ? TRUE: FALSE);
294
295 gtk_widget_set_sensitive(self->priv->deleteButton, selected);
296}
297
298/* Delete button was clicked on selection */
299void _nojs_preferences_on_delete_selection(NoJSPreferences *self,
300 GtkButton *inButton)
301{
302 NoJSPreferencesPrivate *priv=self->priv;
303 GList *rows, *row, *refs=NULL;
304 GtkTreeRowReference *ref;
305 GtkTreeModel *model=GTK_TREE_MODEL(priv->listStore);
306 GtkTreeIter iter;
307 GtkTreePath *path;
308 gchar *domain;
309 gchar *sql;
310 gint success;
311 gchar *error;
312
313 /* Get selected rows in list and create a row reference because
314 * we will modify the model while iterating through selected rows
315 */
316 rows=gtk_tree_selection_get_selected_rows(priv->listSelection, &model);
317 for(row=rows; row; row=row->next)
318 {
319 ref=gtk_tree_row_reference_new(model, (GtkTreePath*)row->data);
320 refs=g_list_prepend(refs, ref);
321 }
322 g_list_foreach(rows,(GFunc)gtk_tree_path_free, NULL);
323 g_list_free(rows);
324
325 /* Delete each selected row by its reference */
326 for(row=refs; row; row=row->next)
327 {
328 /* Get domain from selected row */
329 path=gtk_tree_row_reference_get_path((GtkTreeRowReference*)row->data);
330 gtk_tree_model_get_iter(model, &iter, path);
331 gtk_tree_model_get(model, &iter, DOMAIN_COLUMN, &domain, -1);
332
333 /* Delete domain from database */
334 sql=sqlite3_mprintf("DELETE FROM policies WHERE site='%q';", domain);
335 success=sqlite3_exec(priv->database,
336 sql,
337 NULL,
338 NULL,
339 &error);
340 if(success!=SQLITE_OK || error)
341 {
342 if(error)
343 {
344 g_critical(_("Failed to execute database statement: %s"), error);
345 sqlite3_free(error);
346 }
347 else g_critical(_("Failed to execute database statement: %s"), sqlite3_errmsg(priv->database));
348 }
349 sqlite3_free(sql);
350
351 /* Delete row from model */
352 gtk_list_store_remove(priv->listStore, &iter);
353 }
354 g_list_foreach(refs,(GFunc)gtk_tree_row_reference_free, NULL);
355 g_list_free(refs);
356}
357
358/* Delete all button was clicked */
359void _nojs_preferences_on_delete_all(NoJSPreferences *self,
360 GtkButton *inButton)
361{
362 NoJSPreferencesPrivate *priv=self->priv;
363 gint success;
364 gchar *error=NULL;
365 GtkWidget *dialog;
366 gint dialogResponse;
367
368 /* Ask user if he really wants to delete all permissions */
369 dialog=gtk_message_dialog_new(GTK_WINDOW(self),
370 GTK_DIALOG_MODAL,
371 GTK_MESSAGE_QUESTION,
372 GTK_BUTTONS_YES_NO,
373 _("Do you really want to delete all JavaScript permissions?"));
374
375 gtk_window_set_title(GTK_WINDOW(dialog), _("Delete all JavaScript permissions?"));
376 gtk_window_set_icon_name(GTK_WINDOW(dialog), GTK_STOCK_PROPERTIES);
377
378 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
379 _("This action will delete all JavaScript permissions. "
380 "You will be asked for permissions again for each web site visited."));
381
382 dialogResponse=gtk_dialog_run(GTK_DIALOG(dialog));
383 gtk_widget_destroy(dialog);
384
385 if(dialogResponse==GTK_RESPONSE_NO) return;
386
387 /* Delete all permission */
388 success=sqlite3_exec(priv->database,
389 "DELETE FROM policies;",
390 NULL,
391 NULL,
392 &error);
393
394 if(success!=SQLITE_OK || error)
395 {
396 if(error)
397 {
398 g_critical(_("Failed to execute database statement: %s"), error);
399 sqlite3_free(error);
400 }
401 }
402
403 /* Re-setup list */
404 _nojs_preferences_fill(self);
405}
406
407/* Sorting callbacks */
408static gint _nojs_preferences_sort_string_callback(GtkTreeModel *inModel,
409 GtkTreeIter *inLeft,
410 GtkTreeIter *inRight,
411 gpointer inUserData)
412{
413 gchar *left, *right;
414 gint column=GPOINTER_TO_INT(inUserData);
415 gint result;
416
417 gtk_tree_model_get(inModel, inLeft, column, &left, -1);
418 gtk_tree_model_get(inModel, inRight, column, &right, -1);
419
420 result=g_strcmp0(left, right);
421
422 g_free(left);
423 g_free(right);
424
425 return(result);
426}
427
428/* IMPLEMENTATION: GObject */
429
430/* Finalize this object */
431static void nojs_preferences_finalize(GObject *inObject)
432{
433 NoJSPreferencesPrivate *priv=NOJS_PREFERENCES(inObject)->priv;
434
435 /* Dispose allocated resources */
436 if(priv->database) sqlite3_close(priv->database);
437 priv->database=NULL;
438
439 if(priv->manager)
440 {
441 if(priv->signalManagerChangedDatabaseID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedDatabaseID);
442 priv->signalManagerChangedDatabaseID=0;
443
444 if(priv->signalManagerChangedAllowAllSitesID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedAllowAllSitesID);
445 priv->signalManagerChangedAllowAllSitesID=0;
446
447 if(priv->signalManagerChangedUnknownDomainPolicyID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedUnknownDomainPolicyID);
448 priv->signalManagerChangedUnknownDomainPolicyID=0;
449
450 if(priv->signalManagerChangedCheckSecondLevelID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedCheckSecondLevelID);
451 priv->signalManagerChangedCheckSecondLevelID=0;
452
453 g_object_unref(priv->manager);
454 priv->manager=NULL;
455 }
456
457 /* Call parent's class finalize method */
458 G_OBJECT_CLASS(nojs_preferences_parent_class)->finalize(inObject);
459}
460
461/* Set/get properties */
462static void nojs_preferences_set_property(GObject *inObject,
463 guint inPropID,
464 const GValue *inValue,
465 GParamSpec *inSpec)
466{
467 NoJSPreferences *self=NOJS_PREFERENCES(inObject);
468 NoJSPreferencesPrivate *priv=self->priv;
469 GObject *manager;
470
471 switch(inPropID)
472 {
473 /* Construct-only properties */
474 case PROP_MANAGER:
475 /* Release old manager if available and disconnect signals */
476 if(priv->manager)
477 {
478 if(priv->signalManagerChangedDatabaseID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedDatabaseID);
479 priv->signalManagerChangedDatabaseID=0;
480
481 if(priv->signalManagerChangedAllowAllSitesID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedAllowAllSitesID);
482 priv->signalManagerChangedAllowAllSitesID=0;
483
484 if(priv->signalManagerChangedUnknownDomainPolicyID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedUnknownDomainPolicyID);
485 priv->signalManagerChangedUnknownDomainPolicyID=0;
486
487 if(priv->signalManagerChangedCheckSecondLevelID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedCheckSecondLevelID);
488 priv->signalManagerChangedCheckSecondLevelID=0;
489
490 g_object_unref(priv->manager);
491 priv->manager=NULL;
492 }
493
494 /* Set new JavaScript permission manager and
495 * listen to changes in database property
496 */
497 manager=g_value_get_object(inValue);
498 if(manager)
499 {
500 priv->manager=g_object_ref(manager);
501
502 priv->signalManagerChangedDatabaseID=
503 g_signal_connect_swapped(priv->manager,
504 "notify::database-filename",
505 G_CALLBACK(_nojs_preferences_on_manager_database_changed),
506 self);
507 _nojs_preferences_on_manager_database_changed(self, NULL, priv->manager);
508
509 priv->signalManagerChangedAllowAllSitesID=
510 g_signal_connect_swapped(priv->manager,
511 "notify::allow-all-sites",
512 G_CALLBACK(_nojs_preferences_on_manager_allow_all_sites_changed),
513 self);
514 _nojs_preferences_on_manager_allow_all_sites_changed(self, NULL, priv->manager);
515
516 priv->signalManagerChangedUnknownDomainPolicyID=
517 g_signal_connect_swapped(priv->manager,
518 "notify::unknown-domain-policy",
519 G_CALLBACK(_nojs_preferences_on_manager_unknown_domain_policy_changed),
520 self);
521 _nojs_preferences_on_manager_unknown_domain_policy_changed(self, NULL, priv->manager);
522
523 priv->signalManagerChangedCheckSecondLevelID=
524 g_signal_connect_swapped(priv->manager,
525 "notify::only-second-level",
526 G_CALLBACK(_nojs_preferences_on_manager_only_second_level_changed),
527 self);
528 _nojs_preferences_on_manager_only_second_level_changed(self, NULL, priv->manager);
529 }
530 break;
531
532 default:
533 G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
534 break;
535 }
536}
537
538static void nojs_preferences_get_property(GObject *inObject,
539 guint inPropID,
540 GValue *outValue,
541 GParamSpec *inSpec)
542{
543 NoJSPreferences *self=NOJS_PREFERENCES(inObject);
544
545 switch(inPropID)
546 {
547 case PROP_MANAGER:
548 g_value_set_object(outValue, self->priv->manager);
549 break;
550
551 default:
552 G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
553 break;
554 }
555}
556
557/* Class initialization
558 * Override functions in parent classes and define properties and signals
559 */
560static void nojs_preferences_class_init(NoJSPreferencesClass *klass)
561{
562 GObjectClass *gobjectClass=G_OBJECT_CLASS(klass);
563
564 /* Override functions */
565 gobjectClass->finalize=nojs_preferences_finalize;
566 gobjectClass->set_property=nojs_preferences_set_property;
567 gobjectClass->get_property=nojs_preferences_get_property;
568
569 /* Set up private structure */
570 g_type_class_add_private(klass, sizeof(NoJSPreferencesPrivate));
571
572 /* Define properties */
573 NoJSPreferencesProperties[PROP_MANAGER]=
574 g_param_spec_object("manager",
575 _("Manager instance"),
576 _("Instance to global NoJS manager"),
577 TYPE_NOJS,
578 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
579
580 g_object_class_install_properties(gobjectClass, PROP_LAST, NoJSPreferencesProperties);
581}
582
583/* Object initialization
584 * Create private structure and set up default values
585 */
586static void nojs_preferences_init(NoJSPreferences *self)
587{
588 NoJSPreferencesPrivate *priv;
589 GtkTreeSortable *sortableList;
590 GtkCellRenderer *renderer;
591 GtkTreeViewColumn *column;
592 GtkWidget *widget;
593 gchar *dialogTitle;
594 GtkWidget *scrolled;
595 GtkWidget *vbox;
596 GtkWidget *hbox;
597 gint width, height;
598
599 priv=self->priv=NOJS_PREFERENCES_GET_PRIVATE(self);
600
601 /* Set up default values */
602 priv->manager=NULL;
603
604 /* Get content area to add gui controls to */
605 priv->contentArea=gtk_dialog_get_content_area(GTK_DIALOG(self));
606#if GTK_CHECK_VERSION (3, 0, 0)
607 vbox=gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
608 gtk_box_set_homogeneous(GTK_BOX(vbox), FALSE);
609#else
610 vbox=gtk_vbox_new(FALSE, 0);
611#endif
612
613 /* Set up dialog */
614 dialogTitle=_("Configure NoJS");
615
616 gtk_window_set_title(GTK_WINDOW(self), dialogTitle);
617 gtk_window_set_icon_name(GTK_WINDOW(self), GTK_STOCK_PROPERTIES);
618
619 sokoke_widget_get_text_size(GTK_WIDGET(self), "M", &width, &height);
620 gtk_window_set_default_size(GTK_WINDOW(self), width*52, -1);
621
622 widget=sokoke_xfce_header_new(gtk_window_get_icon_name(GTK_WINDOW(self)), dialogTitle);
623 if(widget) gtk_box_pack_start(GTK_BOX(priv->contentArea), widget, FALSE, FALSE, 0);
624
625 gtk_dialog_add_button(GTK_DIALOG(self), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
626
627 /* Set up description */
628 widget=gtk_label_new(NULL);
629 gtk_label_set_markup(GTK_LABEL(widget),
630 _("Below is a list of all web sites and the policy set for them. "
631 "You can delete policies by marking the entries and clicking on <i>Delete</i>."));
632 gtk_label_set_line_wrap(GTK_LABEL(widget), TRUE);
633 gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, FALSE, 4);
634
635 /* Set up domain list */
636 priv->listStore=gtk_list_store_new(N_COLUMN,
637 G_TYPE_STRING, /* DOMAIN_COLUMN */
638 G_TYPE_STRING /* POLICY_COLUMN */);
639
640 sortableList=GTK_TREE_SORTABLE(priv->listStore);
641 gtk_tree_sortable_set_sort_func(sortableList,
642 DOMAIN_COLUMN,
643 (GtkTreeIterCompareFunc)_nojs_preferences_sort_string_callback,
644 GINT_TO_POINTER(DOMAIN_COLUMN),
645 NULL);
646 gtk_tree_sortable_set_sort_func(sortableList,
647 POLICY_COLUMN,
648 (GtkTreeIterCompareFunc)_nojs_preferences_sort_string_callback,
649 GINT_TO_POINTER(POLICY_COLUMN),
650 NULL);
651 gtk_tree_sortable_set_sort_column_id(sortableList, DOMAIN_COLUMN, GTK_SORT_ASCENDING);
652
653 /* Set up domain list view */
654 priv->list=gtk_tree_view_new_with_model(GTK_TREE_MODEL(priv->listStore));
655
656#if !GTK_CHECK_VERSION (3, 0, 0)
657 gtk_widget_set_size_request(priv->list, -1, 300);
658#endif
659
660 priv->listSelection=gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->list));
661 gtk_tree_selection_set_mode(priv->listSelection, GTK_SELECTION_MULTIPLE);
662 g_signal_connect_swapped(priv->listSelection, "changed", G_CALLBACK(_nojs_preferences_changed_selection), self);
663
664 renderer=gtk_cell_renderer_text_new();
665 column=gtk_tree_view_column_new_with_attributes(_("Domain"),
666 renderer,
667 "text", DOMAIN_COLUMN,
668 NULL);
669 gtk_tree_view_column_set_sort_column_id(column, DOMAIN_COLUMN);
670 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->list), column);
671
672 renderer=gtk_cell_renderer_text_new();
673 column=gtk_tree_view_column_new_with_attributes(_("Policy"),
674 renderer,
675 "text", POLICY_COLUMN,
676 NULL);
677 gtk_tree_view_column_set_sort_column_id(column, POLICY_COLUMN);
678 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->list), column);
679
680 scrolled=gtk_scrolled_window_new(NULL, NULL);
681#if GTK_CHECK_VERSION (3, 0, 0)
682 gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(scrolled), height*10);
683#endif
684 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
685 gtk_container_add(GTK_CONTAINER(scrolled), priv->list);
686 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
687 gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 5);
688
689 /* Set up JavaScript domain list management buttons */
690#if GTK_CHECK_VERSION (3, 0, 0)
691 hbox=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
692 gtk_box_set_homogeneous(GTK_BOX(hbox), FALSE);
693#else
694 hbox=gtk_hbox_new(FALSE, 0);
695#endif
696
697 priv->deleteButton=gtk_button_new_from_stock(GTK_STOCK_DELETE);
698 gtk_widget_set_sensitive(priv->deleteButton, FALSE);
699 gtk_container_add(GTK_CONTAINER(hbox), priv->deleteButton);
700 g_signal_connect_swapped(priv->deleteButton, "clicked", G_CALLBACK(_nojs_preferences_on_delete_selection), self);
701
702 priv->deleteAllButton=gtk_button_new_with_mnemonic(_("Delete _all"));
703 gtk_button_set_image(GTK_BUTTON(priv->deleteAllButton), gtk_image_new_from_stock(GTK_STOCK_DELETE, GTK_ICON_SIZE_BUTTON));
704 gtk_widget_set_sensitive(priv->deleteAllButton, FALSE);
705 gtk_container_add(GTK_CONTAINER(hbox), priv->deleteAllButton);
706 g_signal_connect_swapped(priv->deleteAllButton, "clicked", G_CALLBACK(_nojs_preferences_on_delete_all), self);
707
708 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
709
710 /* Add "allow-all-sites" checkbox */
711 priv->allowAllSitesCheckbox=gtk_check_button_new_with_mnemonic(_("A_llow scripts at all sites"));
712 priv->signalAllowAllSitesToggledID=g_signal_connect_swapped(priv->allowAllSitesCheckbox,
713 "toggled",
714 G_CALLBACK(_nojs_preferences_on_allow_all_sites_changed),
715 self);
716 gtk_box_pack_start(GTK_BOX(vbox), priv->allowAllSitesCheckbox, TRUE, TRUE, 5);
717
718 /* Add "block-unknown-domains" checkbox */
719 priv->blockUnknownDomainsCheckbox=gtk_check_button_new_with_mnemonic(_("Bloc_k scripts at unknown domains by default"));
720 priv->signalBlockUnknownDomainsToggledID=g_signal_connect_swapped(priv->blockUnknownDomainsCheckbox,
721 "toggled",
722 G_CALLBACK(_nojs_preferences_on_block_unknown_domains_changed),
723 self);
724 gtk_box_pack_start(GTK_BOX(vbox), priv->blockUnknownDomainsCheckbox, TRUE, TRUE, 5);
725
726 /* Add "check-second-level-only" checkbox */
727 priv->checkSecondLevelOnlyCheckbox=gtk_check_button_new_with_mnemonic(_("S_et permissions on second-level domain"));
728 priv->signalCheckSecondLevelOnlyToggledID=g_signal_connect_swapped(priv->checkSecondLevelOnlyCheckbox,
729 "toggled",
730 G_CALLBACK(_nojs_preferences_on_check_second_level_only_changed),
731 self);
732 gtk_box_pack_start(GTK_BOX(vbox), priv->checkSecondLevelOnlyCheckbox, TRUE, TRUE, 5);
733
734 /* Finalize setup of content area */
735 gtk_container_add(GTK_CONTAINER(priv->contentArea), vbox);
736}
737
738/* Implementation: Public API */
739
740/* Create new object */
741GtkWidget* nojs_preferences_new(NoJS *inManager)
742{
743 return(g_object_new(TYPE_NOJS_PREFERENCES,
744 "manager", inManager,
745 NULL));
746}
0747
=== added file 'extensions/nojs/nojs-preferences.h'
--- extensions/nojs/nojs-preferences.h 1970-01-01 00:00:00 +0000
+++ extensions/nojs/nojs-preferences.h 2013-07-29 19:51:25 +0000
@@ -0,0 +1,55 @@
1/*
2 Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 See the file COPYING for the full license text.
10*/
11
12#ifndef __NOJS_PREFERENCES__
13#define __NOJS_PREFERENCES__
14
15#include "config.h"
16#include <midori/midori.h>
17
18#include "nojs.h"
19
20G_BEGIN_DECLS
21
22#define TYPE_NOJS_PREFERENCES (nojs_preferences_get_type())
23#define NOJS_PREFERENCES(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_NOJS_PREFERENCES, NoJSPreferences))
24#define IS_NOJS_PREFERENCES(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_NOJS_PREFERENCES))
25#define NOJS_PREFERENCES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_NOJS_PREFERENCES, NoJSPreferencesClass))
26#define IS_NOJS_PREFERENCES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_NOJS_PREFERENCES))
27#define NOJS_PREFERENCES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_NOJS_PREFERENCES, NoJSPreferencesClass))
28
29typedef struct _NoJSPreferences NoJSPreferences;
30typedef struct _NoJSPreferencesClass NoJSPreferencesClass;
31typedef struct _NoJSPreferencesPrivate NoJSPreferencesPrivate;
32
33struct _NoJSPreferences
34{
35 /* Parent instance */
36 GtkDialog parent_instance;
37
38 /* Private structure */
39 NoJSPreferencesPrivate *priv;
40};
41
42struct _NoJSPreferencesClass
43{
44 /* Parent class */
45 GtkDialogClass parent_class;
46};
47
48/* Public API */
49GType nojs_preferences_get_type(void);
50
51GtkWidget* nojs_preferences_new(NoJS *inManager);
52
53G_END_DECLS
54
55#endif /* __NOJS_PREFERENCES__ */
056
=== added file 'extensions/nojs/nojs-view.c'
--- extensions/nojs/nojs-view.c 1970-01-01 00:00:00 +0000
+++ extensions/nojs/nojs-view.c 2013-07-29 19:51:25 +0000
@@ -0,0 +1,804 @@
1/*
2 Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 See the file COPYING for the full license text.
10*/
11
12#include "nojs-view.h"
13#include "nojs-preferences.h"
14
15/* Define this class in GObject system */
16G_DEFINE_TYPE(NoJSView,
17 nojs_view,
18 G_TYPE_OBJECT)
19
20/* Properties */
21enum
22{
23 PROP_0,
24
25 PROP_MANAGER,
26 PROP_BROWSER,
27 PROP_VIEW,
28 PROP_MENU_ICON_STATE,
29
30 PROP_LAST
31};
32
33static GParamSpec* NoJSViewProperties[PROP_LAST]={ 0, };
34
35/* Private structure - access only by public API if needed */
36#define NOJS_VIEW_GET_PRIVATE(obj) \
37 (G_TYPE_INSTANCE_GET_PRIVATE((obj), TYPE_NOJS_VIEW, NoJSViewPrivate))
38
39struct _NoJSViewPrivate
40{
41 /* Extension related */
42 NoJS *manager;
43 MidoriBrowser *browser;
44 MidoriView *view;
45
46 GtkWidget *menu;
47 gboolean menuPolicyWasChanged;
48 NoJSMenuIconState menuIconState;
49
50 GSList *resourceURIs;
51};
52
53/* IMPLEMENTATION: Private variables and methods */
54
55/* Preferences of this extension should be opened */
56static void _nojs_view_on_preferences_response(GtkWidget* inDialog,
57 gint inResponse,
58 gpointer *inUserData)
59{
60 gtk_widget_destroy(inDialog);
61}
62
63static void _nojs_view_on_open_preferences(NoJSView *self, gpointer inUserData)
64{
65 g_return_if_fail(NOJS_IS_VIEW(self));
66
67 NoJSViewPrivate *priv=self->priv;
68
69 /* Show preferences window */
70 GtkWidget* dialog;
71
72 dialog=nojs_preferences_new(priv->manager);
73 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
74 g_signal_connect(dialog, "response", G_CALLBACK (_nojs_view_on_preferences_response), self);
75 gtk_widget_show_all(dialog);
76}
77
78/* Selection was done in menu */
79static void _nojs_view_on_menu_selection_done(NoJSView *self, gpointer inUserData)
80{
81 g_return_if_fail(NOJS_IS_VIEW(self));
82
83 NoJSViewPrivate *priv=self->priv;
84
85 /* Check if any policy was changed and reload page */
86 if(priv->menuPolicyWasChanged!=FALSE)
87 {
88 /* Reset flag that any policy was changed */
89 priv->menuPolicyWasChanged=FALSE;
90
91 /* Reload page */
92 midori_view_reload(priv->view, FALSE);
93g_message("%s: Reloading page %s as policy has changed", __func__, midori_view_get_display_uri(priv->view));
94 }
95}
96
97/* Destroy menu */
98static void _nojs_view_destroy_menu(NoJSView *self)
99{
100 g_return_if_fail(NOJS_IS_VIEW(self));
101 g_return_if_fail(self->priv->menu!=NULL);
102
103 NoJSViewPrivate *priv=self->priv;
104
105 /* Empty menu and list of domains added to menu */
106 gtk_widget_destroy(priv->menu);
107 priv->menu=NULL;
108
109 /* Reset menu icon to default state */
110 priv->menuIconState=NOJS_MENU_ICON_STATE_UNDETERMINED;
111 g_object_notify_by_pspec(G_OBJECT(self), NoJSViewProperties[PROP_MENU_ICON_STATE]);
112}
113
114/* Create empty menu */
115static void _nojs_view_create_empty_menu(NoJSView *self)
116{
117 g_return_if_fail(NOJS_IS_VIEW(self));
118 g_return_if_fail(self->priv->menu==NULL);
119
120 NoJSViewPrivate *priv=self->priv;
121 GtkWidget *item;
122
123 /* Create new menu and set up default items */
124 priv->menu=gtk_menu_new();
125
126 item=gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, NULL);
127 g_signal_connect_swapped(item, "activate", G_CALLBACK(_nojs_view_on_open_preferences), self);
128 gtk_menu_shell_prepend(GTK_MENU_SHELL(priv->menu), item);
129 gtk_widget_show_all(item);
130
131 /* Reset flag that any policy was changed */
132 priv->menuPolicyWasChanged=FALSE;
133
134 /* Reset menu icon to default state */
135 priv->menuIconState=NOJS_MENU_ICON_STATE_UNDETERMINED;
136 g_object_notify_by_pspec(G_OBJECT(self), NoJSViewProperties[PROP_MENU_ICON_STATE]);
137
138 /* Connect signal to menu */
139 g_signal_connect_swapped(priv->menu, "selection-done", G_CALLBACK(_nojs_view_on_menu_selection_done), self);
140}
141
142/* Change visibility state of menu item for a domain depending on policy */
143static gboolean _nojs_view_menu_item_change_policy(NoJSView *self, const gchar *inDomain, NoJSPolicy inPolicy)
144{
145 g_return_val_if_fail(NOJS_IS_VIEW(self), FALSE);
146 g_return_val_if_fail(inDomain, FALSE);
147
148 NoJSViewPrivate *priv=self->priv;
149 GList *items, *iter;
150 gboolean updated;
151
152 /* Handle accept-for-session like accept when showing or hiding menu items */
153 if(inPolicy==NOJS_POLICY_ACCEPT_TEMPORARILY) inPolicy=NOJS_POLICY_ACCEPT;
154
155 /* Update menu items */
156 updated=FALSE;
157 items=gtk_container_get_children(GTK_CONTAINER(priv->menu));
158 for(iter=items; iter; iter=iter->next)
159 {
160 /* Only check and update menu items (not separators and so on) */
161 if(GTK_IS_MENU_ITEM(iter->data))
162 {
163 GtkMenuItem *item=GTK_MENU_ITEM(iter->data);
164 const gchar *itemDomain;
165 NoJSPolicy itemPolicy;
166
167 itemDomain=(const gchar*)g_object_get_data(G_OBJECT(item), "domain");
168 itemPolicy=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "policy"));
169
170 /* Handle accept-for-session like accept when showing or hiding menu items */
171 if(itemPolicy==NOJS_POLICY_ACCEPT_TEMPORARILY) itemPolicy=NOJS_POLICY_ACCEPT;
172
173 /* If menu item has "domain"-data update its visibility state
174 * depending on matching policy
175 */
176 if(g_strcmp0(itemDomain, inDomain)==0)
177 {
178 if(itemPolicy==inPolicy) gtk_widget_hide(GTK_WIDGET(item));
179 else gtk_widget_show_all(GTK_WIDGET(item));
180
181 /* Set flag that at least one menu item was updated */
182 updated=TRUE;
183 }
184 }
185 }
186 g_list_free(items);
187
188 /* Return flag indicating if at least one menu item was updated */
189 return(updated);
190}
191
192/* A menu item was selected */
193static void _nojs_view_on_menu_item_activate(NoJSView *self, gpointer inUserData)
194{
195 g_return_if_fail(NOJS_IS_VIEW(self));
196 g_return_if_fail(GTK_IS_MENU_ITEM(inUserData));
197
198 NoJSViewPrivate *priv=self->priv;
199 GtkMenuItem *item=GTK_MENU_ITEM(inUserData);
200 const gchar *domain;
201 NoJSPolicy policy;
202
203 /* Get domain and policy to set */
204 domain=(const gchar*)g_object_get_data(G_OBJECT(item), "domain");
205 policy=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "policy"));
206 g_return_if_fail(domain);
207 g_return_if_fail(policy>=NOJS_POLICY_ACCEPT && policy<=NOJS_POLICY_BLOCK);
208
209 /* Set policy for domain and update menu items */
210 _nojs_view_menu_item_change_policy(self, domain, policy);
211 nojs_set_policy(priv->manager, domain, policy);
212
213 /* Set flag that a policy was changed */
214 priv->menuPolicyWasChanged=TRUE;
215}
216
217/* Add site to menu */
218static void _nojs_view_add_site_to_menu(NoJSView *self, const gchar *inDomain, NoJSPolicy inPolicy)
219{
220 g_return_if_fail(NOJS_IS_VIEW(self));
221 g_return_if_fail(inDomain);
222
223 NoJSViewPrivate *priv=self->priv;
224 GtkWidget *item;
225 gchar *itemLabel;
226 GtkWidget *itemImage;
227 static gint INSERT_POSITION=1;
228 NoJSMenuIconState newMenuIconState;
229
230 /* Create menu object if not available */
231 if(!priv->menu) _nojs_view_create_empty_menu(self);
232
233 /* Check if domain was already added to menu. If it exists just update it. */
234 if(_nojs_view_menu_item_change_policy(self, inDomain, inPolicy)==TRUE) return;
235
236 /* Add menu item(s) for domain */
237 itemLabel=g_strdup_printf(_("Deny %s"), inDomain);
238 item=gtk_image_menu_item_new_with_label(itemLabel);
239 itemImage=gtk_image_new_from_stock (GTK_STOCK_NO, GTK_ICON_SIZE_MENU);
240 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), itemImage);
241 gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(item), TRUE);
242 gtk_menu_shell_insert(GTK_MENU_SHELL(priv->menu), item, INSERT_POSITION);
243 if(inPolicy!=NOJS_POLICY_BLOCK) gtk_widget_show_all(item);
244 g_object_set_data_full(G_OBJECT(item), "domain", g_strdup(inDomain), (GDestroyNotify)g_free);
245 g_object_set_data(G_OBJECT(item), "policy", GINT_TO_POINTER(NOJS_POLICY_BLOCK));
246 g_signal_connect_swapped(item, "activate", G_CALLBACK(_nojs_view_on_menu_item_activate), self);
247 g_free(itemLabel);
248
249 itemLabel=g_strdup_printf(_("Allow %s"), inDomain);
250 item=gtk_image_menu_item_new_with_label(itemLabel);
251 itemImage=gtk_image_new_from_stock (GTK_STOCK_YES, GTK_ICON_SIZE_MENU);
252 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), itemImage);
253 gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(item), TRUE);
254 gtk_menu_shell_insert(GTK_MENU_SHELL(priv->menu), item, INSERT_POSITION);
255 if(inPolicy!=NOJS_POLICY_ACCEPT && inPolicy!=NOJS_POLICY_ACCEPT_TEMPORARILY) gtk_widget_show_all(item);
256 g_object_set_data_full(G_OBJECT(item), "domain", g_strdup(inDomain), (GDestroyNotify)g_free);
257 g_object_set_data(G_OBJECT(item), "policy", GINT_TO_POINTER(NOJS_POLICY_ACCEPT));
258 g_signal_connect_swapped(item, "activate", G_CALLBACK(_nojs_view_on_menu_item_activate), self);
259 g_free(itemLabel);
260
261 itemLabel=g_strdup_printf(_("Allow %s this session"), inDomain);
262 item=gtk_image_menu_item_new_with_label(itemLabel);
263 itemImage=gtk_image_new_from_stock (GTK_STOCK_OK, GTK_ICON_SIZE_MENU);
264 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), itemImage);
265 gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(item), TRUE);
266 gtk_menu_shell_insert(GTK_MENU_SHELL(priv->menu), item, INSERT_POSITION);
267 if(inPolicy!=NOJS_POLICY_ACCEPT && inPolicy!=NOJS_POLICY_ACCEPT_TEMPORARILY) gtk_widget_show_all(item);
268 g_object_set_data_full(G_OBJECT(item), "domain", g_strdup(inDomain), (GDestroyNotify)g_free);
269 g_object_set_data(G_OBJECT(item), "policy", GINT_TO_POINTER(NOJS_POLICY_ACCEPT_TEMPORARILY));
270 g_signal_connect_swapped(item, "activate", G_CALLBACK(_nojs_view_on_menu_item_activate), self);
271 g_free(itemLabel);
272
273 /* Add seperator to seperate actions for this domain from the other domains */
274 item=gtk_separator_menu_item_new();
275 gtk_menu_shell_insert(GTK_MENU_SHELL(priv->menu), item, INSERT_POSITION);
276 gtk_widget_show_all(item);
277
278 /* Determine state of status icon */
279 if(priv->menuIconState!=NOJS_MENU_ICON_STATE_MIXED)
280 {
281 switch(inPolicy)
282 {
283 case NOJS_POLICY_ACCEPT:
284 case NOJS_POLICY_ACCEPT_TEMPORARILY:
285 newMenuIconState=NOJS_MENU_ICON_STATE_ALLOWED;
286 break;
287
288 case NOJS_POLICY_BLOCK:
289 newMenuIconState=NOJS_MENU_ICON_STATE_DENIED;
290 break;
291
292 default:
293 newMenuIconState=NOJS_MENU_ICON_STATE_MIXED;
294 break;
295 }
296
297 if(priv->menuIconState==NOJS_MENU_ICON_STATE_UNDETERMINED ||
298 priv->menuIconState!=newMenuIconState)
299 {
300 priv->menuIconState=newMenuIconState;
301 g_object_notify_by_pspec(G_OBJECT(self), NoJSViewProperties[PROP_MENU_ICON_STATE]);
302 }
303 }
304}
305
306/* Status of loading a site has changed */
307static void _nojs_view_on_load_status_changed(NoJSView *self, GParamSpec *inSpec, gpointer inUserData)
308{
309 g_return_if_fail(NOJS_IS_VIEW(self));
310 g_return_if_fail(WEBKIT_IS_WEB_VIEW(inUserData));
311
312 NoJSViewPrivate *priv=self->priv;
313 WebKitWebView *webkitView=WEBKIT_WEB_VIEW(inUserData);
314 WebKitWebSettings *settings=webkit_web_view_get_settings(webkitView);
315 WebKitLoadStatus status;
316 SoupURI *uri;
317
318 /* Get URI of document loading/loaded */
319 uri=soup_uri_new(webkit_web_view_get_uri(webkitView));
320
321 /* Check load status */
322 status=webkit_web_view_get_load_status(webkitView);
323
324 /* Check if a view was emptied, e.g. for a new document going to be loaded soon */
325 if(status==WEBKIT_LOAD_PROVISIONAL)
326 {
327 /* Create a new empty menu */
328 _nojs_view_destroy_menu(self);
329 _nojs_view_create_empty_menu(self);
330
331 /* Free list of resource URIs, that's the list of URIs for all resources
332 * of a page
333 */
334 if(priv->resourceURIs)
335 {
336 g_slist_free_full(priv->resourceURIs, (GDestroyNotify)g_free);
337 priv->resourceURIs=NULL;
338 }
339 }
340
341 /* Check if document loading is going to start. Do not check special pages. */
342 if(status==WEBKIT_LOAD_COMMITTED &&
343 uri &&
344 uri->scheme &&
345 g_strcmp0(uri->scheme, "about")!=0)
346 {
347 /* Check if domain is black-listed or white-listed and enable or
348 * disable javascript accordingly. But if settings match already
349 * the state it should get do not set it again to avoid reloads of page.
350 */
351 gchar *domain;
352 NoJSPolicy policy;
353 gboolean currentScriptsEnabled;
354 gboolean newScriptsEnabled;
355
356 domain=nojs_get_domain(priv->manager, uri);
357 policy=nojs_get_policy(priv->manager, domain);
358 if(policy==NOJS_POLICY_UNDETERMINED)
359 {
360 policy=nojs_get_policy_for_unknown_domain(priv->manager);
361 // TODO: Show nick_name of policy (enum) to use in warning
362 g_warning("Got invalid policy. Using default policy for unknown domains.");
363 }
364
365 newScriptsEnabled=(policy==NOJS_POLICY_BLOCK ? FALSE : TRUE);
366 g_object_get(G_OBJECT(settings), "enable-scripts", &currentScriptsEnabled, NULL);
367
368 if(newScriptsEnabled!=currentScriptsEnabled)
369 {
370 g_object_set(G_OBJECT(settings), "enable-scripts", newScriptsEnabled, NULL);
371 // TODO: Set uri also to ensure this uri is going to be reloaded
372 }
373
374 _nojs_view_add_site_to_menu(self, domain, policy);
375 if(domain) g_free(domain);
376 }
377
378 /* Free allocated resources */
379 if(uri) soup_uri_free(uri);
380}
381
382/* A request is going to sent */
383static void _nojs_view_on_resource_request_starting(NoJSView *self,
384 WebKitWebFrame *inFrame,
385 WebKitWebResource *inResource,
386 WebKitNetworkRequest *inRequest,
387 WebKitNetworkResponse *inResponse,
388 gpointer inUserData)
389{
390 g_return_if_fail(NOJS_IS_VIEW(self));
391
392 NoJSViewPrivate *priv=self->priv;
393 SoupMessage *message;
394 SoupURI *uri;
395 gchar *uriText;
396
397 /* Remember resource URIs requesting */
398 message=(inRequest ? webkit_network_request_get_message(inRequest) : NULL);
399 if(message)
400 {
401 uri=soup_message_get_uri(message);
402 if(uri)
403 {
404 uriText=soup_uri_to_string(uri, FALSE);
405 priv->resourceURIs=g_slist_prepend(priv->resourceURIs, uriText);
406 }
407 }
408
409 message=(inResponse ? webkit_network_response_get_message(inResponse) : NULL);
410 if(message)
411 {
412 uri=soup_message_get_uri(message);
413 if(uri)
414 {
415 uriText=soup_uri_to_string(uri, FALSE);
416 priv->resourceURIs=g_slist_prepend(priv->resourceURIs, uriText);
417 }
418 }
419}
420
421/* A policy has changed */
422static void _nojs_view_on_policy_changed(NoJSView *self, gchar *inDomain, gpointer inUserData)
423{
424 g_return_if_fail(NOJS_IS_VIEW(self));
425 g_return_if_fail(inDomain);
426
427 NoJSViewPrivate *priv=self->priv;
428 GList *items, *iter;
429 gboolean reloaded;
430
431 /* Check if the policy of a domain has changed this view has referenced resources to */
432 reloaded=FALSE;
433 items=gtk_container_get_children(GTK_CONTAINER(priv->menu));
434 for(iter=items; reloaded==FALSE && iter; iter=iter->next)
435 {
436 if(GTK_IS_MENU_ITEM(iter->data))
437 {
438 const gchar *itemDomain;
439
440 /* Check if domain matches menu item */
441 itemDomain=(const gchar*)g_object_get_data(G_OBJECT(iter->data), "domain");
442 if(g_strcmp0(itemDomain, inDomain)==0)
443 {
444 /* Found domain in our menu so reload page */
445 midori_view_reload(priv->view, FALSE);
446 reloaded=TRUE;
447 }
448 }
449 }
450 g_list_free(items);
451}
452
453/* A javascript URI is going to loaded or blocked */
454static void _nojs_view_on_uri_load_policy_status(NoJSView *self, gchar *inURI, NoJSPolicy inPolicy, gpointer inUserData)
455{
456 g_return_if_fail(NOJS_IS_VIEW(self));
457
458 NoJSViewPrivate *priv=self->priv;
459 GSList *iter;
460 gchar *checkURI;
461
462 /* Check if uri (accepted or blocked) might be one of ours */
463 for(iter=priv->resourceURIs; iter; iter=iter->next)
464 {
465 checkURI=(gchar*)iter->data;
466 if(g_strcmp0(checkURI, inURI)==0)
467 {
468 SoupURI *uri;
469 gchar *domain;
470
471 uri=soup_uri_new(inURI);
472 domain=nojs_get_domain(priv->manager, uri);
473 if(domain)
474 {
475 _nojs_view_add_site_to_menu(self, domain, inPolicy);
476 g_free(domain);
477 }
478
479 soup_uri_free(uri);
480 break;
481 }
482 }
483}
484
485/* Property "view" has changed */
486static void _nojs_view_on_view_changed(NoJSView *self, MidoriView *inView)
487{
488 NoJSViewPrivate *priv=self->priv;
489 WebKitWebView *webkitView;
490
491 /* Disconnect signal on old view */
492 if(priv->view)
493 {
494 webkitView=WEBKIT_WEB_VIEW(midori_view_get_web_view(priv->view));
495 g_signal_handlers_disconnect_by_data(webkitView, self);
496 g_object_set_data(G_OBJECT(priv->view), "nojs-view-instance", NULL);
497 g_object_unref(priv->view);
498 priv->view=NULL;
499 }
500
501 /* Set new view if valid pointer */
502 if(!inView) return;
503
504 priv->view=g_object_ref(inView);
505 g_object_set_data(G_OBJECT(priv->view), "nojs-view-instance", self);
506
507 /* Listen to changes of load-status in view */
508 webkitView=WEBKIT_WEB_VIEW(midori_view_get_web_view(priv->view));
509 g_signal_connect_swapped(webkitView, "notify::load-status", G_CALLBACK(_nojs_view_on_load_status_changed), self);
510 g_signal_connect_swapped(webkitView, "resource-request-starting", G_CALLBACK(_nojs_view_on_resource_request_starting), self);
511
512 /* Create empty menu */
513 _nojs_view_destroy_menu(self);
514 _nojs_view_create_empty_menu(self);
515
516 /* Release list of resource URIs */
517 if(priv->resourceURIs)
518 {
519 g_slist_free_full(priv->resourceURIs, (GDestroyNotify)g_free);
520 priv->resourceURIs=NULL;
521 }
522}
523
524/* This extension is going to be deactivated */
525static void _nojs_view_on_extension_deactivated(NoJSView *self, MidoriExtension *inExtension)
526{
527 g_return_if_fail(NOJS_IS_VIEW(self));
528
529 /* Dispose allocated resources by unreferencing ourselve */
530 g_object_unref(self);
531}
532
533/* Property "manager" has changed */
534static void _nojs_view_on_manager_changed(NoJSView *self, NoJS *inNoJS)
535{
536 g_return_if_fail(NOJS_IS_VIEW(self));
537 g_return_if_fail(!inNoJS || IS_NOJS(inNoJS));
538
539 NoJSViewPrivate *priv=self->priv;
540 MidoriExtension *extension;
541
542 /* Release reference to old manager and clean up */
543 if(priv->manager)
544 {
545 g_object_get(priv->manager, "extension", &extension, NULL);
546 g_signal_handlers_disconnect_by_data(extension, self);
547 g_object_unref(extension);
548
549 g_signal_handlers_disconnect_by_data(priv->manager, self);
550 g_object_unref(priv->manager);
551 priv->manager=NULL;
552 }
553
554 /* Set new view if valid pointer */
555 if(!inNoJS) return;
556
557 priv->manager=g_object_ref(inNoJS);
558
559 /* Connect signals to manager */
560 g_signal_connect_swapped(priv->manager, "uri-load-policy-status", G_CALLBACK(_nojs_view_on_uri_load_policy_status), self);
561 g_signal_connect_swapped(priv->manager, "policy-changed", G_CALLBACK(_nojs_view_on_policy_changed), self);
562
563 /* Connect signal to get noticed when extension is going to be deactivated
564 * to release all references to GObjects
565 */
566 g_object_get(priv->manager, "extension", &extension, NULL);
567 g_signal_connect_swapped(extension, "deactivate", G_CALLBACK(_nojs_view_on_extension_deactivated), self);
568 g_object_unref(extension);
569}
570
571/* IMPLEMENTATION: GObject */
572
573/* Finalize this object */
574static void nojs_view_finalize(GObject *inObject)
575{
576 NoJSView *self=NOJS_VIEW(inObject);
577 NoJSViewPrivate *priv=self->priv;
578
579 /* Dispose allocated resources */
580 if(priv->manager)
581 {
582 MidoriExtension *extension;
583
584 g_object_get(priv->manager, "extension", &extension, NULL);
585 g_signal_handlers_disconnect_by_data(extension, self);
586 g_object_unref(extension);
587
588 g_signal_handlers_disconnect_by_data(priv->manager, self);
589 g_object_unref(priv->manager);
590 priv->manager=NULL;
591 }
592
593 if(priv->browser)
594 {
595 g_object_unref(priv->browser);
596 priv->browser=NULL;
597 }
598
599 if(priv->view)
600 {
601 _nojs_view_on_view_changed(self, NULL);
602 }
603
604 if(priv->menu)
605 {
606 gtk_widget_destroy(priv->menu);
607 priv->menu=NULL;
608 }
609
610 if(priv->resourceURIs)
611 {
612 g_slist_free_full(priv->resourceURIs, (GDestroyNotify)g_free);
613 priv->resourceURIs=NULL;
614 }
615
616 /* Call parent's class finalize method */
617 G_OBJECT_CLASS(nojs_view_parent_class)->finalize(inObject);
618}
619
620/* Set/get properties */
621static void nojs_view_set_property(GObject *inObject,
622 guint inPropID,
623 const GValue *inValue,
624 GParamSpec *inSpec)
625{
626 NoJSView *self=NOJS_VIEW(inObject);
627
628 switch(inPropID)
629 {
630 /* Construct-only properties */
631 case PROP_MANAGER:
632 _nojs_view_on_manager_changed(self, NOJS(g_value_get_object(inValue)));
633 break;
634
635 case PROP_BROWSER:
636 if(self->priv->browser) g_object_unref(self->priv->browser);
637 self->priv->browser=g_object_ref(g_value_get_object(inValue));
638 break;
639
640 case PROP_VIEW:
641 _nojs_view_on_view_changed(self, MIDORI_VIEW(g_value_get_object(inValue)));
642 break;
643
644 default:
645 G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
646 break;
647 }
648}
649
650static void nojs_view_get_property(GObject *inObject,
651 guint inPropID,
652 GValue *outValue,
653 GParamSpec *inSpec)
654{
655 NoJSView *self=NOJS_VIEW(inObject);
656
657 switch(inPropID)
658 {
659 case PROP_MANAGER:
660 g_value_set_object(outValue, self->priv->manager);
661 break;
662
663 case PROP_BROWSER:
664 g_value_set_object(outValue, self->priv->browser);
665 break;
666
667 case PROP_VIEW:
668 g_value_set_object(outValue, self->priv->view);
669 break;
670
671 case PROP_MENU_ICON_STATE:
672 g_value_set_enum(outValue, self->priv->menuIconState);
673 break;
674
675 default:
676 G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
677 break;
678 }
679}
680
681/* Class initialization
682 * Override functions in parent classes and define properties and signals
683 */
684static void nojs_view_class_init(NoJSViewClass *klass)
685{
686 GObjectClass *gobjectClass=G_OBJECT_CLASS(klass);
687
688 /* Override functions */
689 gobjectClass->finalize=nojs_view_finalize;
690 gobjectClass->set_property=nojs_view_set_property;
691 gobjectClass->get_property=nojs_view_get_property;
692
693 /* Set up private structure */
694 g_type_class_add_private(klass, sizeof(NoJSViewPrivate));
695
696 /* Define properties */
697 NoJSViewProperties[PROP_MANAGER]=
698 g_param_spec_object("manager",
699 _("Manager instance"),
700 _("Instance to global NoJS manager"),
701 TYPE_NOJS,
702 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
703
704 NoJSViewProperties[PROP_BROWSER]=
705 g_param_spec_object("browser",
706 _("Browser window"),
707 _("The Midori browser instance this view belongs to"),
708 MIDORI_TYPE_BROWSER,
709 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
710
711 NoJSViewProperties[PROP_VIEW]=
712 g_param_spec_object("view",
713 _("View"),
714 _("The Midori view instance this view belongs to"),
715 MIDORI_TYPE_VIEW,
716 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
717
718 NoJSViewProperties[PROP_MENU_ICON_STATE]=
719 g_param_spec_enum("menu-icon-state",
720 _("Menu icon state"),
721 _("State of menu icon to show in status bar"),
722 NOJS_TYPE_MENU_ICON_STATE,
723 NOJS_MENU_ICON_STATE_UNDETERMINED,
724 G_PARAM_READABLE);
725
726 g_object_class_install_properties(gobjectClass, PROP_LAST, NoJSViewProperties);
727}
728
729/* Object initialization
730 * Create private structure and set up default values
731 */
732static void nojs_view_init(NoJSView *self)
733{
734 NoJSViewPrivate *priv;
735
736 priv=self->priv=NOJS_VIEW_GET_PRIVATE(self);
737
738 /* Set up default values */
739 priv->manager=NULL;
740 priv->browser=NULL;
741 priv->view=NULL;
742
743 priv->menu=NULL;
744 priv->menuPolicyWasChanged=FALSE;
745 priv->menuIconState=NOJS_MENU_ICON_STATE_UNDETERMINED;
746
747 priv->resourceURIs=NULL;
748
749 /* Create empty menu */
750 _nojs_view_create_empty_menu(self);
751}
752
753/* Implementation: Public API */
754
755/* Create new object */
756NoJSView* nojs_view_new(NoJS *inNoJS, MidoriBrowser *inBrowser, MidoriView *inView)
757{
758 return(g_object_new(TYPE_NOJS_VIEW,
759 "manager", inNoJS,
760 "browser", inBrowser,
761 "view", inView,
762 NULL));
763}
764
765/* Get menu widget for this view */
766GtkMenu* nojs_view_get_menu(NoJSView *self)
767{
768 g_return_val_if_fail(NOJS_IS_VIEW(self), NULL);
769
770 return(GTK_MENU(self->priv->menu));
771}
772
773/* Get image used for menu icon in status bar */
774NoJSMenuIconState nojs_view_get_menu_icon_state(NoJSView *self)
775{
776 g_return_val_if_fail(NOJS_IS_VIEW(self), NOJS_MENU_ICON_STATE_UNDETERMINED);
777
778 return(self->priv->menuIconState);
779}
780
781/************************************************************************************/
782
783/* Implementation: Enumeration */
784GType nojs_menu_icon_state_get_type(void)
785{
786 static volatile gsize g_define_type_id__volatile=0;
787
788 if(g_once_init_enter(&g_define_type_id__volatile))
789 {
790 static const GEnumValue values[]=
791 {
792 { NOJS_MENU_ICON_STATE_UNDETERMINED, "NOJS_MENU_ICON_STATE_UNDETERMINED", N_("Undetermined") },
793 { NOJS_MENU_ICON_STATE_ALLOWED, "NOJS_MENU_ICON_STATE_ALLOWED", N_("Allowed") },
794 { NOJS_MENU_ICON_STATE_MIXED, "NOJS_MENU_ICON_STATE_MIXED", N_("Mixed") },
795 { NOJS_MENU_ICON_STATE_DENIED, "NOJS_MENU_ICON_STATE_DENIED", N_("Denied") },
796 { 0, NULL, NULL }
797 };
798
799 GType g_define_type_id=g_enum_register_static(g_intern_static_string("NoJSMenuIconState"), values);
800 g_once_init_leave(&g_define_type_id__volatile, g_define_type_id);
801 }
802
803 return(g_define_type_id__volatile);
804}
0805
=== added file 'extensions/nojs/nojs-view.h'
--- extensions/nojs/nojs-view.h 1970-01-01 00:00:00 +0000
+++ extensions/nojs/nojs-view.h 2013-07-29 19:51:25 +0000
@@ -0,0 +1,71 @@
1/*
2 Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 See the file COPYING for the full license text.
10*/
11
12#ifndef __NOJS_VIEW__
13#define __NOJS_VIEW__
14
15#include "config.h"
16#include "nojs.h"
17#include <midori/midori.h>
18
19G_BEGIN_DECLS
20
21/* NoJS view enums */
22typedef enum
23{
24 NOJS_MENU_ICON_STATE_UNDETERMINED,
25 NOJS_MENU_ICON_STATE_ALLOWED,
26 NOJS_MENU_ICON_STATE_MIXED,
27 NOJS_MENU_ICON_STATE_DENIED
28} NoJSMenuIconState;
29
30/* NoJS view object */
31#define TYPE_NOJS_VIEW (nojs_view_get_type())
32#define NOJS_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_NOJS_VIEW, NoJSView))
33#define NOJS_IS_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_NOJS_VIEW))
34#define NOJS_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_NOJS_VIEW, NoJSViewClass))
35#define NOJS_IS_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_NOJS_VIEW))
36#define NOJS_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_NOJS_VIEW, NoJSViewClass))
37
38typedef struct _NoJSView NoJSView;
39typedef struct _NoJSViewClass NoJSViewClass;
40typedef struct _NoJSViewPrivate NoJSViewPrivate;
41
42struct _NoJSView
43{
44 /* Parent instance */
45 GObject parent_instance;
46
47 /* Private structure */
48 NoJSViewPrivate *priv;
49};
50
51struct _NoJSViewClass
52{
53 /* Parent class */
54 GObjectClass parent_class;
55};
56
57/* Public API */
58GType nojs_view_get_type(void);
59
60NoJSView* nojs_view_new(NoJS *inNoJS, MidoriBrowser *inBrowser, MidoriView *inView);
61
62GtkMenu* nojs_view_get_menu(NoJSView *self);
63NoJSMenuIconState nojs_view_get_menu_icon_state(NoJSView *self);
64
65/* Enumeration */
66GType nojs_menu_icon_state_get_type(void) G_GNUC_CONST;
67#define NOJS_TYPE_MENU_ICON_STATE (nojs_menu_icon_state_get_type())
68
69G_END_DECLS
70
71#endif /* __NOJS_VIEW__ */
072
=== added file 'extensions/nojs/nojs.c'
--- extensions/nojs/nojs.c 1970-01-01 00:00:00 +0000
+++ extensions/nojs/nojs.c 2013-07-29 19:51:25 +0000
@@ -0,0 +1,1069 @@
1/*
2 Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 See the file COPYING for the full license text.
10*/
11
12#include "nojs.h"
13#include "nojs-view.h"
14
15#include <errno.h>
16
17/* Define this class in GObject system */
18G_DEFINE_TYPE(NoJS,
19 nojs,
20 G_TYPE_OBJECT)
21
22/* Properties */
23enum
24{
25 PROP_0,
26
27 PROP_EXTENSION,
28 PROP_APPLICATION,
29
30 PROP_DATABASE,
31 PROP_DATABASE_FILENAME,
32 PROP_ALLOW_ALL_SITES,
33 PROP_ONLY_SECOND_LEVEL,
34 PROP_UNKNOWN_DOMAIN_POLICY,
35
36 PROP_LAST
37};
38
39static GParamSpec* NoJSProperties[PROP_LAST]={ 0, };
40
41/* Signals */
42enum
43{
44 URI_LOAD_POLICY_STATUS,
45 POLICY_CHANGED,
46
47 SIGNAL_LAST
48};
49
50static guint NoJSSignals[SIGNAL_LAST]={ 0, };
51
52/* Private structure - access only by public API if needed */
53#define NOJS_GET_PRIVATE(obj) \
54 (G_TYPE_INSTANCE_GET_PRIVATE((obj), TYPE_NOJS, NoJSPrivate))
55
56struct _NoJSPrivate
57{
58 /* Extension related */
59 MidoriExtension *extension;
60 MidoriApp *application;
61 sqlite3 *database;
62 gchar *databaseFilename;
63 gboolean allowAllSites;
64 gboolean checkOnlySecondLevel;
65 NoJSPolicy unknownDomainPolicy;
66
67 guint requestStartedSignalID;
68};
69
70/* Taken from http://www.w3.org/html/wg/drafts/html/master/scripting-1.html#scriptingLanguages
71 * A list of javascript mime types
72 */
73static const gchar* javascriptTypes[]= {
74 "application/ecmascript",
75 "application/javascript",
76 "application/x-ecmascript",
77 "application/x-javascript",
78 "text/ecmascript",
79 "text/javascript",
80 "text/javascript1.0",
81 "text/javascript1.1",
82 "text/javascript1.2",
83 "text/javascript1.3",
84 "text/javascript1.4",
85 "text/javascript1.5",
86 "text/jscript",
87 "text/livescript",
88 "text/x-ecmascript",
89 "text/x-javascript",
90 NULL
91 };
92
93/* IMPLEMENTATION: Private variables and methods */
94
95/* Closure for: void (*closure)(NoJS *self, gchar *inURI, NoJSPolicy inPolicy) */
96static void _nojs_closure_VOID__STRING_ENUM(GClosure *inClosure,
97 GValue *ioReturnValue G_GNUC_UNUSED,
98 guint inNumberValues,
99 const GValue *inValues,
100 gpointer inInvocationHint G_GNUC_UNUSED,
101 gpointer inMarshalData)
102{
103 typedef void (*GMarshalFunc_VOID__STRING_ENUM)(gpointer inObject, gpointer inArg1, gint inArg2, gpointer inUserData);
104
105 register GMarshalFunc_VOID__STRING_ENUM callback;
106 register GCClosure *closure=(GCClosure*)inClosure;
107 register gpointer object, userData;
108
109 g_return_if_fail(inNumberValues==3);
110
111 if(G_CCLOSURE_SWAP_DATA(inClosure))
112 {
113 object=inClosure->data;
114 userData=g_value_peek_pointer(inValues+0);
115 }
116 else
117 {
118 object=g_value_peek_pointer(inValues+0);
119 userData=inClosure->data;
120 }
121
122 callback=(GMarshalFunc_VOID__STRING_ENUM)(inMarshalData ? inMarshalData : closure->callback);
123
124 callback(object,
125 (gchar*)g_value_get_string(inValues+1),
126 g_value_get_enum(inValues+2),
127 userData);
128}
129
130/* Show common error dialog */
131static void _nojs_error(NoJS *self, const gchar *inReason)
132{
133 g_return_if_fail(IS_NOJS(self));
134 g_return_if_fail(inReason);
135
136 GtkWidget *dialog;
137
138 /* Show confirmation dialog for undetermined cookies */
139 dialog=gtk_message_dialog_new(NULL,
140 GTK_DIALOG_MODAL,
141 GTK_MESSAGE_ERROR,
142 GTK_BUTTONS_OK,
143 _("A fatal error occurred which prevents "
144 "the NoJS extension to continue. "
145 "You should disable it."));
146
147 gtk_window_set_title(GTK_WINDOW(dialog), _("Error in NoJS extension"));
148 gtk_window_set_icon_name(GTK_WINDOW (dialog), "midori");
149
150 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
151 "%s:\n%s",
152 _("Reason"),
153 inReason);
154
155 gtk_dialog_run(GTK_DIALOG(dialog));
156
157 /* Free up allocated resources */
158 gtk_widget_destroy(dialog);
159}
160
161/* Open database containing policies for javascript sites.
162 * Create database and setup table structure if it does not exist yet.
163 */
164static void _nojs_open_database(NoJS *self)
165{
166 g_return_if_fail(IS_NOJS(self));
167
168 NoJSPrivate *priv=self->priv;
169 const gchar *configDir;
170 gchar *sql;
171 gchar *error=NULL;
172 gint success;
173
174 /* Close any open database */
175 if(priv->database)
176 {
177 priv->databaseFilename=NULL;
178
179 sqlite3_close(priv->database);
180 priv->database=NULL;
181
182 g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_DATABASE]);
183 g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_DATABASE_FILENAME]);
184 }
185
186 /* Build path to database file */
187 configDir=midori_extension_get_config_dir(priv->extension);
188 if(!configDir)
189 {
190 g_warning(_("Could not get path to configuration of extension: path is NULL"));
191
192 _nojs_error(self, _("Could not get path to configuration of extension."));
193 return;
194 }
195
196 if(katze_mkdir_with_parents(configDir, 0700))
197 {
198 g_warning(_("Could not create configuration folder for extension: %s"), g_strerror(errno));
199
200 _nojs_error(self, _("Could not create configuration folder for extension."));
201 return;
202 }
203
204 /* Open database */
205 priv->databaseFilename=g_build_filename(configDir, NOJS_DATABASE, NULL);
206 success=sqlite3_open(priv->databaseFilename, &priv->database);
207 if(success!=SQLITE_OK)
208 {
209 g_warning(_("Could not open database of extension: %s"), sqlite3_errmsg(priv->database));
210
211 g_free(priv->databaseFilename);
212 priv->databaseFilename=NULL;
213
214 if(priv->database) sqlite3_close(priv->database);
215 priv->database=NULL;
216
217 _nojs_error(self, _("Could not open database of extension."));
218 return;
219 }
220
221 /* Create table structure if it does not exist */
222 success=sqlite3_exec(priv->database,
223 "CREATE TABLE IF NOT EXISTS "
224 "policies(site text, value integer);",
225 NULL,
226 NULL,
227 &error);
228
229 if(success==SQLITE_OK)
230 {
231 success=sqlite3_exec(priv->database,
232 "CREATE UNIQUE INDEX IF NOT EXISTS "
233 "site ON policies (site);",
234 NULL,
235 NULL,
236 &error);
237 }
238
239 if(success==SQLITE_OK)
240 {
241 success=sqlite3_exec(priv->database,
242 "PRAGMA journal_mode=TRUNCATE;",
243 NULL,
244 NULL,
245 &error);
246 }
247
248 if(success!=SQLITE_OK || error)
249 {
250 _nojs_error(self, _("Could not set up database structure of extension."));
251
252 if(error)
253 {
254 g_critical(_("Failed to execute database statement: %s"), error);
255 sqlite3_free(error);
256 }
257
258 g_free(priv->databaseFilename);
259 priv->databaseFilename=NULL;
260
261 sqlite3_close(priv->database);
262 priv->database=NULL;
263 return;
264 }
265
266 /* Delete all temporarily allowed sites */
267 sql=sqlite3_mprintf("DELETE FROM policies WHERE value=%d;", NOJS_POLICY_ACCEPT_TEMPORARILY);
268 success=sqlite3_exec(priv->database, sql, NULL, NULL, &error);
269 if(success!=SQLITE_OK) g_warning(_("SQL fails: %s"), error);
270 if(error) sqlite3_free(error);
271 sqlite3_free(sql);
272
273 g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_DATABASE]);
274 g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_DATABASE_FILENAME]);
275}
276
277/* A request through libsoup is going to start and http headers must be
278 * checked for content type
279 */
280static void _nojs_on_got_headers(NoJS *self, gpointer inUserData)
281{
282 g_return_if_fail(IS_NOJS(self));
283 g_return_if_fail(SOUP_IS_MESSAGE(inUserData));
284
285 NoJSPrivate *priv=self->priv;
286 SoupMessage *message=SOUP_MESSAGE(inUserData);
287 SoupSession *session=webkit_get_default_session();
288 SoupMessageHeaders *headers;
289 SoupMessageBody *body;
290 const gchar *contentType;
291 SoupURI *uri;
292 gchar *uriText;
293 gchar *domain;
294 NoJSPolicy policy;
295 gboolean isJS;
296 const gchar **iter;
297
298 /* Get headers from message to retrieve content type */
299 g_object_get(message, "response-headers", &headers, NULL);
300 if(!headers)
301 {
302 g_warning("Could not get headers from message to check for javascript.");
303 return;
304 }
305
306 /* Get content type of uri and check if it is a javascript resource */
307 contentType=soup_message_headers_get_content_type(headers, NULL);
308
309 isJS=FALSE;
310 iter=javascriptTypes;
311 while(*iter && !isJS)
312 {
313 isJS=(g_strcmp0(contentType, *iter)==0);
314 iter++;
315 }
316
317 if(!isJS) return;
318
319 /* The document being loaded is javascript so get URI from message,
320 * get policy for domain of URI and emit signal
321 */
322 uri=soup_message_get_uri(message);
323
324 domain=nojs_get_domain(self, uri);
325 g_return_if_fail(domain);
326
327 policy=nojs_get_policy(self, domain);
328 if(policy==NOJS_POLICY_UNDETERMINED)
329 {
330 g_warning("Got invalid policy. Using default policy for unknown domains.");
331 policy=priv->unknownDomainPolicy;
332 }
333
334 uriText=soup_uri_to_string(uri, FALSE);
335
336 g_signal_emit(self, NoJSSignals[URI_LOAD_POLICY_STATUS], 0, uriText, policy==NOJS_POLICY_UNDETERMINED ? NOJS_POLICY_BLOCK : policy);
337
338 g_free(uriText);
339 g_free(domain);
340
341 /* Return here if policy is any type of accept */
342 if(policy!=NOJS_POLICY_UNDETERMINED && policy!=NOJS_POLICY_BLOCK) return;
343
344 /* Cancel this message */
345 soup_session_cancel_message(session, message, SOUP_STATUS_CANCELLED);
346
347 /* Discard any load data */
348 g_object_get(message, "response-body", &body, NULL);
349 if(body) soup_message_body_truncate(body);
350}
351
352static void _nojs_on_request_started(NoJS *self,
353 SoupMessage *inMessage,
354 SoupSocket *inSocket,
355 gpointer inUserData)
356{
357 g_return_if_fail(IS_NOJS(self));
358 g_return_if_fail(SOUP_IS_MESSAGE(inMessage));
359
360 /* Connect to "got-headers" to cancel loading javascript documents early */
361 g_signal_connect_swapped(inMessage, "got-headers", G_CALLBACK(_nojs_on_got_headers), self);
362}
363
364/* The icon in statusbar was clicked */
365static void _nojs_on_statusbar_icon_clicked(MidoriBrowser *inBrowser, gpointer inUserData)
366{
367 g_return_if_fail(MIDORI_IS_BROWSER(inBrowser));
368
369 MidoriView *activeView;
370 NoJSView *view;
371 GtkMenu *menu;
372
373 /* Get current active midori view */
374 activeView=MIDORI_VIEW(midori_browser_get_current_tab(inBrowser));
375 g_return_if_fail(MIDORI_IS_VIEW(activeView));
376
377 /* Get NoJS view of current active midori view */
378 view=NOJS_VIEW(g_object_get_data(G_OBJECT(activeView), "nojs-view-instance"));
379 g_return_if_fail(NOJS_IS_VIEW(view));
380
381 /* Get menu of current view */
382 menu=nojs_view_get_menu(view);
383 g_return_if_fail(menu);
384
385 /* Show menu */
386 gtk_menu_popup(menu, NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
387}
388
389gchar* nojs_get_icon_path (const gchar* icon)
390{
391 gchar* res_dir = midori_paths_get_res_filename ("");
392 return g_build_filename (res_dir, "nojs", icon, NULL);
393}
394
395/* Menu icon of a view has changed */
396static void _nojs_on_menu_icon_changed(MidoriBrowser *inBrowser, GParamSpec *inSpec, gpointer inUserData)
397{
398 g_return_if_fail(MIDORI_IS_BROWSER(inBrowser));
399 g_return_if_fail(NOJS_IS_VIEW(inUserData));
400
401 NoJSView *view=NOJS_VIEW(inUserData);
402 NoJSMenuIconState menuIconState;
403 GtkWidget *statusbarIcon;
404 GtkWidget *buttonImage;
405 gchar *imageFilename;
406
407 /* Get icon in status bar of this browser */
408 statusbarIcon=GTK_WIDGET(g_object_get_data(G_OBJECT(inBrowser), "nojs-statusicon"));
409 g_return_if_fail(GTK_IS_WIDGET(statusbarIcon));
410
411 /* Get menu icon state of view */
412 menuIconState=nojs_view_get_menu_icon_state(view);
413
414 /* Create image for statusbar button */
415 imageFilename=NULL;
416 switch(menuIconState)
417 {
418 case NOJS_MENU_ICON_STATE_ALLOWED:
419 imageFilename=nojs_get_icon_path("nojs-statusicon-allowed.png");
420 break;
421
422 case NOJS_MENU_ICON_STATE_MIXED:
423 imageFilename=nojs_get_icon_path("nojs-statusicon-mixed.png");
424 break;
425
426 case NOJS_MENU_ICON_STATE_DENIED:
427 case NOJS_MENU_ICON_STATE_UNDETERMINED:
428 imageFilename=nojs_get_icon_path("nojs-statusicon-denied.png");
429 break;
430 }
431
432 buttonImage=gtk_image_new_from_file(imageFilename);
433 g_free(imageFilename);
434
435 /* Set image at statusbar button */
436 gtk_button_set_image(GTK_BUTTON(statusbarIcon), buttonImage);
437}
438
439/* A tab in browser was activated */
440static void _nojs_on_switch_tab(NoJS *self, MidoriView *inOldView, MidoriView *inNewView, gpointer inUserData)
441{
442 g_return_if_fail(IS_NOJS(self));
443 g_return_if_fail(MIDORI_IS_BROWSER(inUserData));
444
445 MidoriBrowser *browser=MIDORI_BROWSER(inUserData);
446 NoJSView *view;
447
448 /* Disconnect signal handlers from old view */
449 if(inOldView)
450 {
451 /* Get NoJS view of old view */
452 view=(NoJSView*)g_object_get_data(G_OBJECT(inOldView), "nojs-view-instance");
453 g_return_if_fail(NOJS_IS_VIEW(view));
454
455 /* Disconnect signal handlers */
456 g_signal_handlers_disconnect_by_func(view, G_CALLBACK(_nojs_on_menu_icon_changed), browser);
457 }
458
459 /* Get NoJS view of new view */
460 view=(NoJSView*)g_object_get_data(G_OBJECT(inNewView), "nojs-view-instance");
461 g_return_if_fail(NOJS_IS_VIEW(view));
462
463 /* Connect signals */
464 g_signal_connect_swapped(view, "notify::menu-icon-state", G_CALLBACK(_nojs_on_menu_icon_changed), browser);
465
466 /* Update menu icon*/
467 _nojs_on_menu_icon_changed(browser, NULL, view);
468}
469
470/* A tab of a browser was removed */
471static void _nojs_on_remove_tab(NoJS *self, MidoriView *inView, gpointer inUserData)
472{
473 g_return_if_fail(IS_NOJS(self));
474
475 NoJSView *view;
476
477 /* Get NoJS view of current active midori view */
478 view=NOJS_VIEW(g_object_get_data(G_OBJECT(inView), "nojs-view-instance"));
479 g_return_if_fail(NOJS_IS_VIEW(view));
480
481 g_object_unref(view);
482}
483
484/* A tab of a browser was added */
485static void _nojs_on_add_tab(NoJS *self, MidoriView *inView, gpointer inUserData)
486{
487 g_return_if_fail(IS_NOJS(self));
488 g_return_if_fail(MIDORI_IS_BROWSER(inUserData));
489
490 /* Create nojs view and add to tab */
491 MidoriBrowser *browser=MIDORI_BROWSER(inUserData);
492
493 nojs_view_new(self, browser, inView);
494}
495
496/* A browser window was added */
497static void _nojs_on_add_browser(NoJS *self, MidoriBrowser *inBrowser, gpointer inUserData)
498{
499 g_return_if_fail(IS_NOJS(self));
500 g_return_if_fail(MIDORI_IS_BROWSER(inBrowser));
501
502 GList *tabs, *iter;
503 GtkWidget *statusbar;
504 GtkWidget *statusbarIcon;
505 MidoriView *view;
506 NoJSView *nojsView;
507
508 /* Set up all current available tabs in browser */
509 tabs=midori_browser_get_tabs(inBrowser);
510 for(iter=tabs; iter; iter=g_list_next(iter)) _nojs_on_add_tab(self, iter->data, inBrowser);
511 g_list_free(tabs);
512
513 /* Add status bar icon to browser */
514 g_object_get(inBrowser, "statusbar", &statusbar, NULL);
515 if(statusbar)
516 {
517 /* Create and set up status icon */
518 statusbarIcon=gtk_button_new();
519 gtk_button_set_relief(GTK_BUTTON(statusbarIcon), GTK_RELIEF_NONE);
520 gtk_widget_show_all(statusbarIcon);
521 gtk_box_pack_end(GTK_BOX(statusbar), statusbarIcon, FALSE, FALSE, 0);
522 g_object_set_data_full(G_OBJECT(inBrowser), "nojs-statusicon", g_object_ref(statusbarIcon), (GDestroyNotify)gtk_widget_destroy);
523
524 /* Connect signals */
525 g_signal_connect_swapped(statusbarIcon, "clicked", G_CALLBACK(_nojs_on_statusbar_icon_clicked), inBrowser);
526
527 /* Release our reference to statusbar and status icon */
528 g_object_unref(statusbarIcon);
529 g_object_unref(statusbar);
530
531 /* Update menu icon*/
532 view=MIDORI_VIEW(midori_browser_get_current_tab(inBrowser));
533 if(view)
534 {
535 nojsView=(NoJSView*)g_object_get_data(G_OBJECT(view), "nojs-view-instance");
536 if(nojsView) _nojs_on_menu_icon_changed(inBrowser, NULL, nojsView);
537 }
538 }
539
540 /* Listen to new tabs opened in browser */
541 g_signal_connect_swapped(inBrowser, "add-tab", G_CALLBACK(_nojs_on_add_tab), self);
542 g_signal_connect_swapped(inBrowser, "switch-tab", G_CALLBACK(_nojs_on_switch_tab), self);
543 g_signal_connect_swapped(inBrowser, "remove-tab", G_CALLBACK(_nojs_on_remove_tab), self);
544}
545
546/* Application property has changed */
547static void _nojs_on_application_changed(NoJS *self)
548{
549 g_return_if_fail(IS_NOJS(self));
550
551 NoJSPrivate *priv=NOJS(self)->priv;
552 GList *browsers, *iter;
553
554 /* Set up all current open browser windows */
555 browsers=midori_app_get_browsers(priv->application);
556 for(iter=browsers; iter; iter=g_list_next(iter)) _nojs_on_add_browser(self, MIDORI_BROWSER(iter->data), priv->application);
557 g_list_free(browsers);
558
559 /* Listen to new browser windows opened */
560 g_signal_connect_swapped(priv->application, "add-browser", G_CALLBACK(_nojs_on_add_browser), self);
561
562 /* Notify about property change */
563 g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_APPLICATION]);
564}
565
566/* IMPLEMENTATION: GObject */
567
568/* Finalize this object */
569static void nojs_finalize(GObject *inObject)
570{
571 NoJS *self=NOJS(inObject);
572 NoJSPrivate *priv=self->priv;
573 GList *browsers, *browser;
574 GList *tabs, *tab;
575 WebKitWebView *webkitView;
576 SoupSession *session;
577
578 /* Dispose allocated resources */
579 session=webkit_get_default_session();
580 g_signal_handlers_disconnect_by_data(session, self);
581
582 if(priv->databaseFilename)
583 {
584 g_free(priv->databaseFilename);
585 priv->databaseFilename=NULL;
586 }
587
588 if(priv->database)
589 {
590 sqlite3_close(priv->database);
591 priv->database=NULL;
592 }
593
594 if(priv->application)
595 {
596 g_signal_handlers_disconnect_by_data(priv->application, self);
597
598 browsers=midori_app_get_browsers(priv->application);
599 for(browser=browsers; browser; browser=g_list_next(browser))
600 {
601 g_signal_handlers_disconnect_by_data(browser->data, self);
602 g_object_set_data(G_OBJECT(browser->data), "nojs-statusicon", NULL);
603
604 tabs=midori_browser_get_tabs(MIDORI_BROWSER(browser->data));
605 for(tab=tabs; tab; tab=g_list_next(tab))
606 {
607 g_signal_handlers_disconnect_by_data(tab->data, self);
608
609 webkitView=WEBKIT_WEB_VIEW(midori_view_get_web_view(MIDORI_VIEW(tab->data)));
610 g_signal_handlers_disconnect_by_data(webkitView, self);
611 }
612 g_list_free(tabs);
613 }
614 g_list_free(browsers);
615
616 priv->application=NULL;
617 }
618
619 /* Call parent's class finalize method */
620 G_OBJECT_CLASS(nojs_parent_class)->finalize(inObject);
621}
622
623/* Set/get properties */
624static void nojs_set_property(GObject *inObject,
625 guint inPropID,
626 const GValue *inValue,
627 GParamSpec *inSpec)
628{
629 NoJS *self=NOJS(inObject);
630
631 switch(inPropID)
632 {
633 /* Construct-only properties */
634 case PROP_EXTENSION:
635 self->priv->extension=g_value_get_object(inValue);
636 _nojs_open_database(self);
637 break;
638
639 case PROP_APPLICATION:
640 self->priv->application=g_value_get_object(inValue);
641 _nojs_on_application_changed(self);
642 break;
643
644 case PROP_ALLOW_ALL_SITES:
645 self->priv->allowAllSites=g_value_get_boolean(inValue);
646 g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_ALLOW_ALL_SITES]);
647 break;
648
649 case PROP_ONLY_SECOND_LEVEL:
650 self->priv->checkOnlySecondLevel=g_value_get_boolean(inValue);
651 g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_ONLY_SECOND_LEVEL]);
652 break;
653
654 case PROP_UNKNOWN_DOMAIN_POLICY:
655 self->priv->unknownDomainPolicy=g_value_get_enum(inValue);
656 g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_UNKNOWN_DOMAIN_POLICY]);
657 break;
658
659 default:
660 G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
661 break;
662 }
663}
664
665static void nojs_get_property(GObject *inObject,
666 guint inPropID,
667 GValue *outValue,
668 GParamSpec *inSpec)
669{
670 NoJS *self=NOJS(inObject);
671
672 switch(inPropID)
673 {
674 case PROP_EXTENSION:
675 g_value_set_object(outValue, self->priv->extension);
676 break;
677
678 case PROP_APPLICATION:
679 g_value_set_object(outValue, self->priv->application);
680 break;
681
682 case PROP_DATABASE:
683 g_value_set_pointer(outValue, self->priv->database);
684 break;
685
686 case PROP_DATABASE_FILENAME:
687 g_value_set_string(outValue, self->priv->databaseFilename);
688 break;
689
690 case PROP_ALLOW_ALL_SITES:
691 g_value_set_boolean(outValue, self->priv->allowAllSites);
692 break;
693
694 case PROP_ONLY_SECOND_LEVEL:
695 g_value_set_boolean(outValue, self->priv->checkOnlySecondLevel);
696 break;
697
698 case PROP_UNKNOWN_DOMAIN_POLICY:
699 g_value_set_enum(outValue, self->priv->unknownDomainPolicy);
700 break;
701
702 default:
703 G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
704 break;
705 }
706}
707
708/* Class initialization
709 * Override functions in parent classes and define properties and signals
710 */
711static void nojs_class_init(NoJSClass *klass)
712{
713 GObjectClass *gobjectClass=G_OBJECT_CLASS(klass);
714
715 /* Override functions */
716 gobjectClass->finalize=nojs_finalize;
717 gobjectClass->set_property=nojs_set_property;
718 gobjectClass->get_property=nojs_get_property;
719
720 /* Set up private structure */
721 g_type_class_add_private(klass, sizeof(NoJSPrivate));
722
723 /* Define properties */
724 NoJSProperties[PROP_EXTENSION]=
725 g_param_spec_object("extension",
726 _("Extension instance"),
727 _("The Midori extension instance for this extension"),
728 MIDORI_TYPE_EXTENSION,
729 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
730
731 NoJSProperties[PROP_APPLICATION]=
732 g_param_spec_object("application",
733 _("Application instance"),
734 _("The Midori application instance this extension belongs to"),
735 MIDORI_TYPE_APP,
736 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
737
738 NoJSProperties[PROP_DATABASE]=
739 g_param_spec_pointer("database",
740 _("Database instance"),
741 _("Pointer to sqlite database instance used by this extension"),
742 G_PARAM_READABLE);
743
744 NoJSProperties[PROP_DATABASE_FILENAME]=
745 g_param_spec_string("database-filename",
746 _("Database path"),
747 _("Path to sqlite database instance used by this extension"),
748 NULL,
749 G_PARAM_READABLE);
750
751 NoJSProperties[PROP_ALLOW_ALL_SITES]=
752 g_param_spec_boolean("allow-all-sites",
753 _("Allow all sites"),
754 _("If true this extension will not check policy for each site but allow them."),
755 FALSE,
756 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
757
758 NoJSProperties[PROP_ONLY_SECOND_LEVEL]=
759 g_param_spec_boolean("only-second-level",
760 _("Only second level"),
761 _("If true this extension will reduce each domain to its second-level (www.example.org will reduced to example.org)"),
762 TRUE,
763 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
764
765 NoJSProperties[PROP_UNKNOWN_DOMAIN_POLICY]=
766 g_param_spec_enum("unknown-domain-policy",
767 _("Unknown domain policy"),
768 _("Policy to use for unknown domains."),
769 NOJS_TYPE_POLICY,
770 NOJS_POLICY_BLOCK,
771 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
772
773 g_object_class_install_properties(gobjectClass, PROP_LAST, NoJSProperties);
774
775 /* Define signals */
776
777 /* Why does this signal exist?
778 *
779 * The problem I faced when developing this extension was
780 * that I needed to cancel a SoupMessage as soon as possible
781 * (when http headers were received).
782 * I tried to connect to signal "resource-response-received"
783 * of WebKitWebView but the SoupMessage instance was not
784 * exactly the same which were sent or received by SoupSession.
785 * So I could not cancel the SoupMessage or better: I cancelled
786 * a SoupMessage which is not be handled so it had no effect.
787 * The body of SoupMessage was still being loaded and javascript
788 * was executed. I think the problem is that webkit-gtk creates
789 * a copy of the real SoupMessage which is going to be sent and
790 * received.
791 *
792 * So I decided to connect to signal "got-headers" of every
793 * SoupMessage sent by the default SoupSession which I notice
794 * by connecting to signal "request-started" of SoupSession. Each
795 * NoJSView connects to signal "resource-request-starting" of
796 * WebKitWebView to remember each URI going to be loaded. When
797 * a SoupMessage hits "got-headers" and is a javascript resource
798 * I can cancel the message immediately and clear the body which
799 * causes webkit-gtk to copy a empty body if it does at all as the
800 * SoupMessage was cancelled. Then I emit this signal
801 * "uri-load-policy-status" to notify each view but the cancellation.
802 * (It also notifies all views if it is going to load to keep the
803 * menu in right state.) Each view will check if it _could_ be a
804 * resource itself requested and will update its menu accordingly.
805 * It might happen that a request will match two views because only
806 * the URI will be checked by the view because I cannot determine
807 * to which view the SoupMessage belongs to. But it doesn't matter
808 * because if a javascript resource was denied or allowed in one view
809 * it is likely be denied or allowed in other views too ;)
810 */
811 NoJSSignals[URI_LOAD_POLICY_STATUS]=
812 g_signal_new("uri-load-policy-status",
813 G_TYPE_FROM_CLASS(klass),
814 G_SIGNAL_RUN_LAST,
815 G_STRUCT_OFFSET(NoJSClass, uri_load_policy_status),
816 NULL,
817 NULL,
818 _nojs_closure_VOID__STRING_ENUM,
819 G_TYPE_NONE,
820 2,
821 G_TYPE_STRING,
822 NOJS_TYPE_POLICY);
823
824 NoJSSignals[POLICY_CHANGED]=
825 g_signal_new("policy-changed",
826 G_TYPE_FROM_CLASS(klass),
827 G_SIGNAL_RUN_LAST,
828 G_STRUCT_OFFSET(NoJSClass, policy_changed),
829 NULL,
830 NULL,
831 g_cclosure_marshal_VOID__STRING,
832 G_TYPE_NONE,
833 1,
834 G_TYPE_STRING);
835}
836
837/* Object initialization
838 * Create private structure and set up default values
839 */
840
841static void nojs_init(NoJS *self)
842{
843 NoJSPrivate *priv;
844 SoupSession *session;
845
846 priv=self->priv=NOJS_GET_PRIVATE(self);
847
848 /* Set up default values */
849 priv->database=NULL;
850 priv->databaseFilename=NULL;
851 priv->allowAllSites=FALSE;
852 priv->checkOnlySecondLevel=TRUE;
853 priv->unknownDomainPolicy=NOJS_POLICY_BLOCK;
854
855 /* Connect to signals on session to be able to cancel messages
856 * loading javascript documents
857 */
858 session=webkit_get_default_session();
859 g_signal_connect_swapped(session, "request-started", G_CALLBACK(_nojs_on_request_started), self);
860}
861
862/* Implementation: Public API */
863
864/* Create new object */
865NoJS* nojs_new(MidoriExtension *inExtension, MidoriApp *inApp)
866{
867 return(g_object_new(TYPE_NOJS,
868 "extension", inExtension,
869 "application", inApp,
870 NULL));
871}
872
873/* Retrieves domain from uri depending on preferences (e.g. only second level domain) */
874gchar* nojs_get_domain(NoJS *self, SoupURI *inURI)
875{
876 g_return_val_if_fail(IS_NOJS(self), NULL);
877 g_return_val_if_fail(inURI, NULL);
878
879 NoJSPrivate *priv=self->priv;
880 const gchar *realDomain;
881 gchar *asciiDomain, *domain;
882 gchar *finalDomain;
883
884 /* Get domain of site to lookup */
885 realDomain=soup_uri_get_host(inURI);
886
887 domain=asciiDomain=g_hostname_to_ascii(realDomain);
888
889 if(priv->checkOnlySecondLevel)
890 {
891 /* Only get second level domain if host is not an IP address */
892 if(!g_hostname_is_ip_address(asciiDomain))
893 {
894 gint numberDots=0;
895
896 domain=asciiDomain+strlen(asciiDomain)-1;
897 while(domain>=asciiDomain && numberDots<2)
898 {
899 if(*domain=='.') numberDots++;
900 domain--;
901 }
902 domain++;
903 if(*domain=='.') domain++;
904 }
905 }
906
907 /* Create copy for return value */
908 if(strlen(domain)>0) finalDomain=g_strdup(domain);
909 else finalDomain=NULL;
910
911 /* Free allocated resources */
912 g_free(asciiDomain);
913
914 /* Return domain */
915 return(finalDomain);
916}
917
918/* Get/set policy for javascript from site */
919gint nojs_get_policy(NoJS *self, const gchar *inDomain)
920{
921 g_return_val_if_fail(IS_NOJS(self), NOJS_POLICY_UNDETERMINED);
922 g_return_val_if_fail(inDomain, NOJS_POLICY_UNDETERMINED);
923
924 NoJSPrivate *priv=self->priv;
925 sqlite3_stmt *statement=NULL;
926 gint error;
927 gint policy=NOJS_POLICY_UNDETERMINED;
928
929 /* Check to allow all sites */
930 if(priv->allowAllSites) return(NOJS_POLICY_ACCEPT);
931
932 /* Check for open database */
933 g_return_val_if_fail(priv->database, policy);
934
935 /* Lookup policy for site in database */
936 error=sqlite3_prepare_v2(priv->database,
937 "SELECT site, value FROM policies WHERE site LIKE ? LIMIT 1;",
938 -1,
939 &statement,
940 NULL);
941 if(statement && error==SQLITE_OK) error=sqlite3_bind_text(statement, 1, inDomain, -1, NULL);
942 if(statement && error==SQLITE_OK)
943 {
944 if(sqlite3_step(statement)==SQLITE_ROW) policy=sqlite3_column_int(statement, 1);
945 }
946 else g_warning(_("SQL fails: %s"), sqlite3_errmsg(priv->database));
947
948 sqlite3_finalize(statement);
949
950 /* If we have not found a policy for the domain then it is an unknown domain.
951 * Get default policy for unknown domains.
952 */
953 if(policy==NOJS_POLICY_UNDETERMINED) policy=priv->unknownDomainPolicy;
954
955 return(policy);
956}
957
958void nojs_set_policy(NoJS *self, const gchar *inDomain, NoJSPolicy inPolicy)
959{
960 g_return_if_fail(IS_NOJS(self));
961 g_return_if_fail(inDomain);
962 g_return_if_fail(inPolicy>=NOJS_POLICY_ACCEPT && inPolicy<=NOJS_POLICY_BLOCK);
963
964 NoJSPrivate *priv=self->priv;
965 gchar *sql;
966 gchar *error=NULL;
967 gint success;
968
969 /* Check for open database */
970 g_return_if_fail(priv->database);
971
972 /* Update policy in database */
973 sql=sqlite3_mprintf("INSERT OR REPLACE INTO policies (site, value) VALUES ('%q', %d);",
974 inDomain,
975 inPolicy);
976 success=sqlite3_exec(priv->database, sql, NULL, NULL, &error);
977 if(success!=SQLITE_OK) g_warning(_("SQL fails: %s"), error);
978 if(error) sqlite3_free(error);
979 sqlite3_free(sql);
980
981 /* Emit signal to notify about policy change */
982 if(success==SQLITE_OK) g_signal_emit(self, NoJSSignals[POLICY_CHANGED], 0, inDomain);
983}
984
985/* Get/set default policy for unknown domains */
986NoJSPolicy nojs_get_policy_for_unknown_domain(NoJS *self)
987{
988 g_return_val_if_fail(IS_NOJS(self), NOJS_POLICY_UNDETERMINED);
989
990 return(self->priv->unknownDomainPolicy);
991}
992
993void nojs_set_policy_for_unknown_domain(NoJS *self, NoJSPolicy inPolicy)
994{
995 g_return_if_fail(IS_NOJS(self));
996 g_return_if_fail(inPolicy>=NOJS_POLICY_ACCEPT && inPolicy<=NOJS_POLICY_BLOCK);
997
998 if(self->priv->unknownDomainPolicy!=inPolicy)
999 {
1000 self->priv->unknownDomainPolicy=inPolicy;
1001 midori_extension_set_integer(self->priv->extension, "unknown-domain-policy", inPolicy);
1002 g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_UNKNOWN_DOMAIN_POLICY]);
1003 }
1004}
1005
1006/* Get/set flag to allow javascript at all sites */
1007gboolean nojs_get_allow_all_sites(NoJS *self)
1008{
1009 g_return_val_if_fail(IS_NOJS(self), TRUE);
1010
1011 return(self->priv->allowAllSites);
1012}
1013
1014void nojs_set_allow_all_sites(NoJS *self, gboolean inAllow)
1015{
1016 g_return_if_fail(IS_NOJS(self));
1017
1018 if(self->priv->allowAllSites!=inAllow)
1019 {
1020 self->priv->allowAllSites=inAllow;
1021 midori_extension_set_boolean(self->priv->extension, "allow-all-sites", inAllow);
1022 g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_ALLOW_ALL_SITES]);
1023 }
1024}
1025
1026/* Get/set flag to check for second-level domains only */
1027gboolean nojs_get_only_second_level_domain(NoJS *self)
1028{
1029 g_return_val_if_fail(IS_NOJS(self), TRUE);
1030
1031 return(self->priv->checkOnlySecondLevel);
1032}
1033
1034void nojs_set_only_second_level_domain(NoJS *self, gboolean inOnlySecondLevel)
1035{
1036 g_return_if_fail(IS_NOJS(self));
1037
1038 if(self->priv->checkOnlySecondLevel!=inOnlySecondLevel)
1039 {
1040 self->priv->checkOnlySecondLevel=inOnlySecondLevel;
1041 midori_extension_set_boolean(self->priv->extension, "only-second-level", inOnlySecondLevel);
1042 g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_ONLY_SECOND_LEVEL]);
1043 }
1044}
1045
1046/************************************************************************************/
1047
1048/* Implementation: Enumeration */
1049GType nojs_policy_get_type(void)
1050{
1051 static volatile gsize g_define_type_id__volatile=0;
1052
1053 if(g_once_init_enter(&g_define_type_id__volatile))
1054 {
1055 static const GEnumValue values[]=
1056 {
1057 { NOJS_POLICY_UNDETERMINED, "NOJS_POLICY_UNDETERMINED", N_("Undetermined") },
1058 { NOJS_POLICY_ACCEPT, "NOJS_POLICY_ACCEPT", N_("Accept") },
1059 { NOJS_POLICY_ACCEPT_TEMPORARILY, "NOJS_POLICY_ACCEPT_TEMPORARILY", N_("Accept temporarily") },
1060 { NOJS_POLICY_BLOCK, "NOJS_POLICY_BLOCK", N_("Block") },
1061 { 0, NULL, NULL }
1062 };
1063
1064 GType g_define_type_id=g_enum_register_static(g_intern_static_string("NoJSPolicy"), values);
1065 g_once_init_leave(&g_define_type_id__volatile, g_define_type_id);
1066 }
1067
1068 return(g_define_type_id__volatile);
1069}
01070
=== added file 'extensions/nojs/nojs.h'
--- extensions/nojs/nojs.h 1970-01-01 00:00:00 +0000
+++ extensions/nojs/nojs.h 2013-07-29 19:51:25 +0000
@@ -0,0 +1,89 @@
1/*
2 Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 See the file COPYING for the full license text.
10*/
11
12#ifndef __NOJS__
13#define __NOJS__
14
15#include "config.h"
16#include <midori/midori.h>
17
18#define NOJS_DATABASE "nojs.db"
19
20G_BEGIN_DECLS
21
22/* NoJS manager enums */
23typedef enum
24{
25 NOJS_POLICY_UNDETERMINED,
26 NOJS_POLICY_ACCEPT,
27 NOJS_POLICY_ACCEPT_TEMPORARILY,
28 NOJS_POLICY_BLOCK
29} NoJSPolicy;
30
31/* NoJS manager object */
32#define TYPE_NOJS (nojs_get_type())
33#define NOJS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_NOJS, NoJS))
34#define IS_NOJS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_NOJS))
35#define NOJS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_NOJS, NoJSClass))
36#define IS_NOJS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_NOJS))
37#define NOJS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_NOJS, NoJSClass))
38
39typedef struct _NoJS NoJS;
40typedef struct _NoJSClass NoJSClass;
41typedef struct _NoJSPrivate NoJSPrivate;
42
43struct _NoJS
44{
45 /* Parent instance */
46 GObject parent_instance;
47
48 /* Private structure */
49 NoJSPrivate *priv;
50};
51
52struct _NoJSClass
53{
54 /* Parent class */
55 GObjectClass parent_class;
56
57 /* Virtual functions */
58 void (*uri_load_policy_status)(NoJS *self, gchar *inURI, NoJSPolicy inPolicy);
59 void (*policy_changed)(NoJS *self, gchar *inDomain);
60};
61
62/* Public API */
63GType nojs_get_type(void);
64
65NoJS* nojs_new(MidoriExtension *inExtension, MidoriApp *inApp);
66
67gchar* nojs_get_domain(NoJS *self, SoupURI *inURI);
68
69gint nojs_get_policy(NoJS *self, const gchar *inDomain);
70void nojs_set_policy(NoJS *self, const gchar *inDomain, NoJSPolicy inPolicy);
71
72NoJSPolicy nojs_get_policy_for_unknown_domain(NoJS *self);
73void nojs_set_policy_for_unknown_domain(NoJS *self, NoJSPolicy inPolicy);
74
75gboolean nojs_get_allow_all_sites(NoJS *self);
76void nojs_set_allow_all_sites(NoJS *self, gboolean inAllow);
77
78gboolean nojs_get_only_second_level_domain(NoJS *self);
79void nojs_set_only_second_level_domain(NoJS *self, gboolean inOnlySecondLevel);
80
81gchar* nojs_get_icon_path (const gchar* icon);
82
83/* Enumeration */
84GType nojs_policy_get_type(void) G_GNUC_CONST;
85#define NOJS_TYPE_POLICY (nojs_policy_get_type())
86
87G_END_DECLS
88
89#endif /* __NOJS__ */
090
=== modified file 'po/POTFILES.in'
--- po/POTFILES.in 2013-07-29 19:10:25 +0000
+++ po/POTFILES.in 2013-07-29 19:51:25 +0000
@@ -51,6 +51,9 @@
51extensions/formhistory/formhistory.c51extensions/formhistory/formhistory.c
52extensions/formhistory/formhistory-gdom-frontend.c52extensions/formhistory/formhistory-gdom-frontend.c
53extensions/history-list.vala53extensions/history-list.vala
54extensions/nojs/nojs-preferences.c
55extensions/nojs/nojs-view.c
56extensions/nojs/nojs.c
54extensions/mouse-gestures.c57extensions/mouse-gestures.c
55extensions/shortcuts.c58extensions/shortcuts.c
56extensions/status-clock.c59extensions/status-clock.c

Subscribers

People subscribed via source and target branches

to all changes: