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

Proposed by Cris Dywan
Status: Merged
Merge reported by: Cris Dywan
Merged at revision: not available
Proposed branch: lp:~midori/midori/nojs
Merge into: lp:midori
Diff against target: 2994 lines (+2942/-0)
8 files modified
extensions/nojs/README.md (+1/-0)
extensions/nojs/main.c (+79/-0)
extensions/nojs/nojs-preferences.c (+748/-0)
extensions/nojs/nojs-preferences.h (+55/-0)
extensions/nojs/nojs-view.c (+823/-0)
extensions/nojs/nojs-view.h (+71/-0)
extensions/nojs/nojs.c (+1078/-0)
extensions/nojs/nojs.h (+87/-0)
To merge this branch: bzr merge lp:~midori/midori/nojs
Reviewer Review Type Date Requested Status
gue5t gue5t Needs Fixing
Stephan Haller Pending
Review via email: mp+165265@code.launchpad.net

Commit message

Introduce NoJS extension managing Javascript execution

Description of the change

Introduce NoJS extension managing Javascript execution

To post a comment you must log in.
Revision history for this message
gue5t gue5t (gue5t) wrote :
Download full text (3.9 KiB)

This is a really important addition to the browser and I'm super excited to have this added. That said, it's a lot of code and so there are a lot of tiny issues which could be addressed:

Icons - it would be nice to use stock items/named icons (providing a fallback image) so that themes and users can override and theme the icons. This could be done after merging the extension.

main.c:38: the "/* Preferences of this extension should be opened */" comment above _nojs_on_preferences_response seems to go with the next function.

nojs.c:96: it looks like _nojs_closure_VOID__STRING_ENUM is a generated function by glib-genmarshal. It would be nice to do this at build-time rather than embedding generated code directly, so it's easier to change later. This isn't a critical change for now.

nojs.c:177: it seems this would leak priv->databaseFilename.

nojs.c:209: typo, "extenstion"

I'm not really qualified to comment on the SQLite calls, but nothing sticks out as wrong. However, I'm curious about nojs.c:255 and nojs.c:270. Is line 268 guaranteed to set error to NULL if no error occurs? If not, line 270 could be a double-free. It's probably clearer to set error to NULL by hand anyway after freeing the error on line 255.

nojs.c:786: I've run into this webkit behavior as well, and it really sucks. Your workaround seems as good as any. :)

nojs.c:905: I don't think soup_uri_get_host will return "", but if it does, this will read from domain[-1]. In any event, the loop here would be more clear if you used strrchr instead. Regardless, a comment to say exactly what all the looping and char-comparisons are intended to do would be good.

nojs.c:1035: typo, "domanins"

nojs-preferences.c:95: this should probably be a const gchar*, since the function neither owns its memory nor modifies its contents; this better matches the return type of sqlite3_column_text.

nojs-preferences.c:161: "extenstion" again

nojs-preferences.c:177: no need for explicit return.

nojs-preferences.c:185: typo, "toogle". Same on 207 and 223, 245, 261, 281,

nojs-preferences.c:326: it seems like doing one-item delete queries in a for loop is probably not the most efficient way to interface with the db here, but for now this is fine. An XXX or TODO comment might be worth adding.

nojs-preferences.c:373: "cookie" should be "script" or "JS". This is duplicated in a variety of places in the file.

nojs-preferences.c:607: "#ifdef GTK__3_0_VERSION" should be "#if GTK_CHECK_VERSION(3, 0, 0)". Same for other references to "GTK__3_0_VERSION".

nojs-preferences.c:630: g_strdup_printf is called with a format-string argument derived from translations without any arguments to format. The strdup/free pair can be completely removed here.

nojs-view.c:93: it might be better to use G_STRFUNC here instead of __func__. I'm not sure of the exact reason, but I would expect the former is safer w/r/t portability. Maybe this print should be hidden behind a debugging environment variable as well. Not a blocking concern.

nojs-view.c:292: typos, "seperator" and "seperate"

nojs-view.c:548: typo, "ourselve"

Throughout the code: Generally we don't require extensions to conform to the Midori coding styl...

Read more...

review: Needs Fixing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'extensions/nojs'
2=== added file 'extensions/nojs/README.md'
3--- extensions/nojs/README.md 1970-01-01 00:00:00 +0000
4+++ extensions/nojs/README.md 2013-05-22 23:07:26 +0000
5@@ -0,0 +1,1 @@
6+This is an extension for Midori web browser. With this extension you can manage which sites are allowed to execute javascript.
7
8=== added file 'extensions/nojs/main.c'
9--- extensions/nojs/main.c 1970-01-01 00:00:00 +0000
10+++ extensions/nojs/main.c 2013-05-22 23:07:26 +0000
11@@ -0,0 +1,79 @@
12+/*
13+ Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
14+
15+ This library is free software; you can redistribute it and/or
16+ modify it under the terms of the GNU Lesser General Public
17+ License as published by the Free Software Foundation; either
18+ version 2.1 of the License, or (at your option) any later version.
19+
20+ See the file COPYING for the full license text.
21+*/
22+
23+#include "nojs.h"
24+#include "nojs-preferences.h"
25+
26+/* Global instance */
27+NoJS *noJS=NULL;
28+
29+/* This extension was activated */
30+static void _nojs_on_activate(MidoriExtension *inExtension, MidoriApp *inApp, gpointer inUserData)
31+{
32+ g_return_if_fail(noJS==NULL);
33+
34+ noJS=nojs_new(inExtension, inApp);
35+ nojs_set_policy_for_unknown_domain(noJS, midori_extension_get_integer(inExtension, "unknown-domain-policy"));
36+ nojs_set_allow_all_sites(noJS, midori_extension_get_boolean(inExtension, "allow-all-sites"));
37+ nojs_set_only_second_level_domain(noJS, midori_extension_get_boolean(inExtension, "only-second-level"));
38+}
39+
40+/* This extension was deactivated */
41+static void _nojs_on_deactivate(MidoriExtension *inExtension, gpointer inUserData)
42+{
43+ g_return_if_fail(noJS);
44+
45+ g_object_unref(noJS);
46+ noJS=NULL;
47+}
48+
49+/* Preferences of this extension should be opened */
50+static void _nojs_on_preferences_response(GtkWidget* inDialog,
51+ gint inResponse,
52+ gpointer *inUserData)
53+{
54+ gtk_widget_destroy(inDialog);
55+}
56+
57+static void _nojs_on_open_preferences(MidoriExtension *inExtension)
58+{
59+ g_return_if_fail(noJS);
60+
61+ /* Show preferences window */
62+ GtkWidget* dialog;
63+
64+ dialog=nojs_preferences_new(noJS);
65+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
66+ g_signal_connect(dialog, "response", G_CALLBACK (_nojs_on_preferences_response), NULL);
67+ gtk_widget_show_all(dialog);
68+}
69+
70+/* Main entry for extension */
71+MidoriExtension *extension_init(void)
72+{
73+ /* Set up extension */
74+ MidoriExtension *extension=g_object_new(MIDORI_TYPE_EXTENSION,
75+ "name", _("NoJS"),
76+ "description", _("Manage javascript permission per site"),
77+ "version", "0.1" MIDORI_VERSION_SUFFIX,
78+ "authors", "Stephan Haller <nomad@froevel.de>",
79+ NULL);
80+
81+ midori_extension_install_integer(extension, "unknown-domain-policy", NOJS_POLICY_BLOCK);
82+ midori_extension_install_boolean(extension, "allow-all-sites", FALSE);
83+ midori_extension_install_boolean(extension, "only-second-level", TRUE);
84+
85+ g_signal_connect(extension, "activate", G_CALLBACK(_nojs_on_activate), NULL);
86+ g_signal_connect(extension, "deactivate", G_CALLBACK(_nojs_on_deactivate), NULL);
87+ g_signal_connect(extension, "open-preferences", G_CALLBACK(_nojs_on_open_preferences), NULL);
88+
89+ return(extension);
90+}
91
92=== added file 'extensions/nojs/nojs-menu-accept.png'
93Binary files extensions/nojs/nojs-menu-accept.png 1970-01-01 00:00:00 +0000 and extensions/nojs/nojs-menu-accept.png 2013-05-22 23:07:26 +0000 differ
94=== added file 'extensions/nojs/nojs-menu-deny.png'
95Binary files extensions/nojs/nojs-menu-deny.png 1970-01-01 00:00:00 +0000 and extensions/nojs/nojs-menu-deny.png 2013-05-22 23:07:26 +0000 differ
96=== added file 'extensions/nojs/nojs-menu-temporary.png'
97Binary files extensions/nojs/nojs-menu-temporary.png 1970-01-01 00:00:00 +0000 and extensions/nojs/nojs-menu-temporary.png 2013-05-22 23:07:26 +0000 differ
98=== added file 'extensions/nojs/nojs-preferences.c'
99--- extensions/nojs/nojs-preferences.c 1970-01-01 00:00:00 +0000
100+++ extensions/nojs/nojs-preferences.c 2013-05-22 23:07:26 +0000
101@@ -0,0 +1,748 @@
102+/*
103+ Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
104+
105+ This library is free software; you can redistribute it and/or
106+ modify it under the terms of the GNU Lesser General Public
107+ License as published by the Free Software Foundation; either
108+ version 2.1 of the License, or (at your option) any later version.
109+
110+ See the file COPYING for the full license text.
111+*/
112+
113+#include "nojs-preferences.h"
114+
115+/* Define this class in GObject system */
116+G_DEFINE_TYPE(NoJSPreferences,
117+ nojs_preferences,
118+ GTK_TYPE_DIALOG)
119+
120+/* Properties */
121+enum
122+{
123+ PROP_0,
124+
125+ PROP_MANAGER,
126+
127+ PROP_LAST
128+};
129+
130+static GParamSpec* NoJSPreferencesProperties[PROP_LAST]={ 0, };
131+
132+/* Private structure - access only by public API if needed */
133+#define NOJS_PREFERENCES_GET_PRIVATE(obj) \
134+ (G_TYPE_INSTANCE_GET_PRIVATE((obj), TYPE_NOJS_PREFERENCES, NoJSPreferencesPrivate))
135+
136+struct _NoJSPreferencesPrivate
137+{
138+ /* Extension related */
139+ NoJS *manager;
140+ sqlite3 *database;
141+
142+ /* Dialog related */
143+ GtkWidget *contentArea;
144+ GtkListStore *listStore;
145+ GtkWidget *list;
146+ GtkTreeSelection *listSelection;
147+ GtkWidget *deleteButton;
148+ GtkWidget *deleteAllButton;
149+ GtkWidget *allowAllSitesCheckbox;
150+ GtkWidget *blockUnknownDomainsCheckbox;
151+ GtkWidget *checkSecondLevelOnlyCheckbox;
152+
153+ gint signalAllowAllSitesToggledID;
154+ gint signalBlockUnknownDomainsToggledID;
155+ gint signalCheckSecondLevelOnlyToggledID;
156+
157+ gint signalManagerChangedDatabaseID;
158+ gint signalManagerChangedAllowAllSitesID;
159+ gint signalManagerChangedUnknownDomainPolicyID;
160+ gint signalManagerChangedCheckSecondLevelID;
161+};
162+
163+enum
164+{
165+ DOMAIN_COLUMN,
166+ POLICY_COLUMN,
167+ N_COLUMN
168+};
169+
170+
171+/* IMPLEMENTATION: Private variables and methods */
172+
173+/* Fill domain list with stored policies */
174+static void _nojs_preferences_fill(NoJSPreferences *self)
175+{
176+ NoJSPreferencesPrivate *priv=self->priv;
177+ gint success;
178+ sqlite3_stmt *statement=NULL;
179+
180+ /* Clear tree/list view */
181+ gtk_list_store_clear(priv->listStore);
182+
183+ /* If no database is present return here */
184+ if(!priv->database) return;
185+
186+ /* Fill list store with policies from database */
187+ success=sqlite3_prepare_v2(priv->database,
188+ "SELECT site, value FROM policies;",
189+ -1,
190+ &statement,
191+ NULL);
192+ if(statement && success==SQLITE_OK)
193+ {
194+ gchar *domain;
195+ gint policy;
196+ gchar *policyName;
197+ GtkTreeIter iter;
198+
199+ while(sqlite3_step(statement)==SQLITE_ROW)
200+ {
201+ /* Get values */
202+ domain=(gchar*)sqlite3_column_text(statement, 0);
203+ policy=sqlite3_column_int(statement, 1);
204+
205+ switch(policy)
206+ {
207+ case NOJS_POLICY_ACCEPT:
208+ policyName=_("Accept");
209+ break;
210+
211+ case NOJS_POLICY_ACCEPT_TEMPORARILY:
212+ policyName=_("Accept for session");
213+ break;
214+
215+ case NOJS_POLICY_BLOCK:
216+ policyName=_("Block");
217+ break;
218+
219+ default:
220+ policyName=NULL;
221+ break;
222+ }
223+
224+ if(policyName)
225+ {
226+ gtk_list_store_append(priv->listStore, &iter);
227+ gtk_list_store_set(priv->listStore,
228+ &iter,
229+ DOMAIN_COLUMN, domain,
230+ POLICY_COLUMN, policyName,
231+ -1);
232+ }
233+ }
234+ }
235+ else g_warning(_("SQL fails: %s"), sqlite3_errmsg(priv->database));
236+
237+ sqlite3_finalize(statement);
238+}
239+
240+/* Database instance in manager changed */
241+static void _nojs_preferences_on_manager_database_changed(NoJSPreferences *self,
242+ GParamSpec *inSpec,
243+ gpointer inUserData)
244+{
245+ NoJSPreferencesPrivate *priv=self->priv;
246+ NoJS *manager=NOJS(inUserData);
247+ gchar *databaseFile;
248+
249+ /* Close connection to any open database */
250+ if(priv->database) sqlite3_close(priv->database);
251+ priv->database=NULL;
252+
253+ /* Get pointer to new database and open database */
254+ g_object_get(manager, "database-filename", &databaseFile, NULL);
255+ if(databaseFile)
256+ {
257+ gint success;
258+
259+ success=sqlite3_open(databaseFile, &priv->database);
260+ if(success!=SQLITE_OK)
261+ {
262+ g_warning(_("Could not open database of extenstion: %s"), sqlite3_errmsg(priv->database));
263+
264+ if(priv->database) sqlite3_close(priv->database);
265+ priv->database=NULL;
266+ }
267+
268+ g_free(databaseFile);
269+ }
270+
271+ /* Fill list with new database */
272+ _nojs_preferences_fill(self);
273+
274+ /* Set up availability of management buttons */
275+ gtk_widget_set_sensitive(priv->deleteAllButton, priv->database!=NULL);
276+ gtk_widget_set_sensitive(priv->list, priv->database!=NULL);
277+
278+ return;
279+}
280+
281+/* Allow-all-sites changed in check-box or manager */
282+static void _nojs_preferences_on_allow_all_sites_changed(NoJSPreferences *self,
283+ gpointer *inUserData)
284+{
285+ NoJSPreferencesPrivate *priv=self->priv;
286+ gboolean state;
287+
288+ /* Get toogle state of widget (but block signal for manager) and set in manager */
289+ g_signal_handler_block(priv->manager, priv->signalManagerChangedAllowAllSitesID);
290+
291+ state=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->allowAllSitesCheckbox));
292+ nojs_set_allow_all_sites(priv->manager, state);
293+
294+ g_signal_handler_unblock(priv->manager, priv->signalManagerChangedAllowAllSitesID);
295+}
296+
297+static void _nojs_preferences_on_manager_allow_all_sites_changed(NoJSPreferences *self,
298+ GParamSpec *inSpec,
299+ gpointer inUserData)
300+{
301+ NoJSPreferencesPrivate *priv=self->priv;
302+ NoJS *manager=NOJS(inUserData);
303+ gboolean state;
304+
305+ /* Get new value from manager */
306+ state=nojs_get_allow_all_sites(manager);
307+
308+ /* Set toogle in widget (but block signal for toggle) */
309+ g_signal_handler_block(priv->allowAllSitesCheckbox, priv->signalAllowAllSitesToggledID);
310+
311+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->allowAllSitesCheckbox), state);
312+
313+ g_signal_handler_unblock(priv->allowAllSitesCheckbox, priv->signalAllowAllSitesToggledID);
314+}
315+
316+/* Block-unknown-domains changed in check-box or manager */
317+static void _nojs_preferences_on_block_unknown_domains_changed(NoJSPreferences *self,
318+ gpointer *inUserData)
319+{
320+ NoJSPreferencesPrivate *priv=self->priv;
321+ gboolean state;
322+ NoJSPolicy policy;
323+
324+ /* Get toogle state of widget (but block signal for manager) and set in manager */
325+ g_signal_handler_block(priv->manager, priv->signalManagerChangedUnknownDomainPolicyID);
326+
327+ state=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->blockUnknownDomainsCheckbox));
328+ policy=(state ? NOJS_POLICY_BLOCK : NOJS_POLICY_ACCEPT);
329+ nojs_set_policy_for_unknown_domain(priv->manager, policy);
330+
331+ g_signal_handler_unblock(priv->manager, priv->signalManagerChangedUnknownDomainPolicyID);
332+}
333+
334+static void _nojs_preferences_on_manager_unknown_domain_policy_changed(NoJSPreferences *self,
335+ GParamSpec *inSpec,
336+ gpointer inUserData)
337+{
338+ NoJSPreferencesPrivate *priv=self->priv;
339+ NoJS *manager=NOJS(inUserData);
340+ NoJSPolicy policy;
341+ gboolean state;
342+
343+ /* Get new value from manager */
344+ policy=nojs_get_policy_for_unknown_domain(manager);
345+
346+ /* Set toogle in widget (but block signal for toggle) */
347+ g_signal_handler_block(priv->blockUnknownDomainsCheckbox, priv->signalBlockUnknownDomainsToggledID);
348+
349+ state=(policy==NOJS_POLICY_BLOCK ? TRUE : FALSE);
350+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->blockUnknownDomainsCheckbox), state);
351+
352+ g_signal_handler_unblock(priv->blockUnknownDomainsCheckbox, priv->signalBlockUnknownDomainsToggledID);
353+}
354+
355+/* Only-second-level changed in check-box or manager */
356+static void _nojs_preferences_on_check_second_level_only_changed(NoJSPreferences *self,
357+ gpointer *inUserData)
358+{
359+ NoJSPreferencesPrivate *priv=self->priv;
360+ gboolean state;
361+
362+ /* Get toogle state of widget (but block signal for manager) and set in manager */
363+ g_signal_handler_block(priv->manager, priv->signalManagerChangedCheckSecondLevelID);
364+
365+ state=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->checkSecondLevelOnlyCheckbox));
366+ nojs_set_only_second_level_domain(priv->manager, state);
367+
368+ g_signal_handler_unblock(priv->manager, priv->signalManagerChangedCheckSecondLevelID);
369+}
370+
371+static void _nojs_preferences_on_manager_only_second_level_changed(NoJSPreferences *self,
372+ GParamSpec *inSpec,
373+ gpointer inUserData)
374+{
375+ NoJSPreferencesPrivate *priv=self->priv;
376+ NoJS *manager=NOJS(inUserData);
377+ gboolean state;
378+
379+ /* Get new value from manager */
380+ state=nojs_get_only_second_level_domain(manager);
381+
382+ /* Set toogle in widget (but block signal for toggle) */
383+ g_signal_handler_block(priv->checkSecondLevelOnlyCheckbox, priv->signalCheckSecondLevelOnlyToggledID);
384+
385+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->checkSecondLevelOnlyCheckbox), state);
386+
387+ g_signal_handler_unblock(priv->checkSecondLevelOnlyCheckbox, priv->signalCheckSecondLevelOnlyToggledID);
388+}
389+
390+/* Selection in list changed */
391+void _nojs_preferences_changed_selection(NoJSPreferences *self,
392+ GtkTreeSelection *inSelection)
393+{
394+ gboolean selected=(gtk_tree_selection_count_selected_rows(inSelection)>0 ? TRUE: FALSE);
395+
396+ gtk_widget_set_sensitive(self->priv->deleteButton, selected);
397+}
398+
399+/* Delete button was clicked on selection */
400+void _nojs_preferences_on_delete_selection(NoJSPreferences *self,
401+ GtkButton *inButton)
402+{
403+ NoJSPreferencesPrivate *priv=self->priv;
404+ GList *rows, *row, *refs=NULL;
405+ GtkTreeRowReference *ref;
406+ GtkTreeModel *model=GTK_TREE_MODEL(priv->listStore);
407+ GtkTreeIter iter;
408+ GtkTreePath *path;
409+ gchar *domain;
410+ gchar *sql;
411+ gint success;
412+ gchar *error;
413+
414+ /* Get selected rows in list and create a row reference because
415+ * we will modify the model while iterating through selected rows
416+ */
417+ rows=gtk_tree_selection_get_selected_rows(priv->listSelection, &model);
418+ for(row=rows; row; row=row->next)
419+ {
420+ ref=gtk_tree_row_reference_new(model, (GtkTreePath*)row->data);
421+ refs=g_list_prepend(refs, ref);
422+ }
423+ g_list_foreach(rows,(GFunc)gtk_tree_path_free, NULL);
424+ g_list_free(rows);
425+
426+ /* Delete each selected row by its reference */
427+ for(row=refs; row; row=row->next)
428+ {
429+ /* Get domain from selected row */
430+ path=gtk_tree_row_reference_get_path((GtkTreeRowReference*)row->data);
431+ gtk_tree_model_get_iter(model, &iter, path);
432+ gtk_tree_model_get(model, &iter, DOMAIN_COLUMN, &domain, -1);
433+
434+ /* Delete domain from database */
435+ sql=sqlite3_mprintf("DELETE FROM policies WHERE site='%q';", domain);
436+ success=sqlite3_exec(priv->database,
437+ sql,
438+ NULL,
439+ NULL,
440+ &error);
441+ if(success!=SQLITE_OK || error)
442+ {
443+ if(error)
444+ {
445+ g_critical(_("Failed to execute database statement: %s"), error);
446+ sqlite3_free(error);
447+ }
448+ else g_critical(_("Failed to execute database statement: %s"), sqlite3_errmsg(priv->database));
449+ }
450+ sqlite3_free(sql);
451+
452+ /* Delete row from model */
453+ gtk_list_store_remove(priv->listStore, &iter);
454+ }
455+ g_list_foreach(refs,(GFunc)gtk_tree_row_reference_free, NULL);
456+ g_list_free(refs);
457+}
458+
459+/* Delete all button was clicked */
460+void _nojs_preferences_on_delete_all(NoJSPreferences *self,
461+ GtkButton *inButton)
462+{
463+ NoJSPreferencesPrivate *priv=self->priv;
464+ gint success;
465+ gchar *error=NULL;
466+ GtkWidget *dialog;
467+ gint dialogResponse;
468+
469+ /* Ask user if he really wants to delete all permissions */
470+ dialog=gtk_message_dialog_new(GTK_WINDOW(self),
471+ GTK_DIALOG_MODAL,
472+ GTK_MESSAGE_QUESTION,
473+ GTK_BUTTONS_YES_NO,
474+ _("Do you really want to delete all cookie permissions?"));
475+
476+ gtk_window_set_title(GTK_WINDOW(dialog), _("Delete all cookie permissions?"));
477+ gtk_window_set_icon_name(GTK_WINDOW(dialog), GTK_STOCK_PROPERTIES);
478+
479+ gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
480+ _("This action will delete all cookie permissions. "
481+ "You will be asked for permissions again for each web site visited."));
482+
483+ dialogResponse=gtk_dialog_run(GTK_DIALOG(dialog));
484+ gtk_widget_destroy(dialog);
485+
486+ if(dialogResponse==GTK_RESPONSE_NO) return;
487+
488+ /* Delete all permission */
489+ success=sqlite3_exec(priv->database,
490+ "DELETE FROM policies;",
491+ NULL,
492+ NULL,
493+ &error);
494+
495+ if(success!=SQLITE_OK || error)
496+ {
497+ if(error)
498+ {
499+ g_critical(_("Failed to execute database statement: %s"), error);
500+ sqlite3_free(error);
501+ }
502+ }
503+
504+ /* Re-setup list */
505+ _nojs_preferences_fill(self);
506+}
507+
508+/* Sorting callbacks */
509+static gint _nojs_preferences_sort_string_callback(GtkTreeModel *inModel,
510+ GtkTreeIter *inLeft,
511+ GtkTreeIter *inRight,
512+ gpointer inUserData)
513+{
514+ gchar *left, *right;
515+ gint column=GPOINTER_TO_INT(inUserData);
516+ gint result;
517+
518+ gtk_tree_model_get(inModel, inLeft, column, &left, -1);
519+ gtk_tree_model_get(inModel, inRight, column, &right, -1);
520+
521+ result=g_strcmp0(left, right);
522+
523+ g_free(left);
524+ g_free(right);
525+
526+ return(result);
527+}
528+
529+/* IMPLEMENTATION: GObject */
530+
531+/* Finalize this object */
532+static void nojs_preferences_finalize(GObject *inObject)
533+{
534+ NoJSPreferencesPrivate *priv=NOJS_PREFERENCES(inObject)->priv;
535+
536+ /* Dispose allocated resources */
537+ if(priv->database) sqlite3_close(priv->database);
538+ priv->database=NULL;
539+
540+ if(priv->manager)
541+ {
542+ if(priv->signalManagerChangedDatabaseID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedDatabaseID);
543+ priv->signalManagerChangedDatabaseID=0;
544+
545+ if(priv->signalManagerChangedAllowAllSitesID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedAllowAllSitesID);
546+ priv->signalManagerChangedAllowAllSitesID=0;
547+
548+ if(priv->signalManagerChangedUnknownDomainPolicyID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedUnknownDomainPolicyID);
549+ priv->signalManagerChangedUnknownDomainPolicyID=0;
550+
551+ if(priv->signalManagerChangedCheckSecondLevelID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedCheckSecondLevelID);
552+ priv->signalManagerChangedCheckSecondLevelID=0;
553+
554+ g_object_unref(priv->manager);
555+ priv->manager=NULL;
556+ }
557+
558+ /* Call parent's class finalize method */
559+ G_OBJECT_CLASS(nojs_preferences_parent_class)->finalize(inObject);
560+}
561+
562+/* Set/get properties */
563+static void nojs_preferences_set_property(GObject *inObject,
564+ guint inPropID,
565+ const GValue *inValue,
566+ GParamSpec *inSpec)
567+{
568+ NoJSPreferences *self=NOJS_PREFERENCES(inObject);
569+ NoJSPreferencesPrivate *priv=self->priv;
570+ GObject *manager;
571+
572+ switch(inPropID)
573+ {
574+ /* Construct-only properties */
575+ case PROP_MANAGER:
576+ /* Release old manager if available and disconnect signals */
577+ if(priv->manager)
578+ {
579+ if(priv->signalManagerChangedDatabaseID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedDatabaseID);
580+ priv->signalManagerChangedDatabaseID=0;
581+
582+ if(priv->signalManagerChangedAllowAllSitesID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedAllowAllSitesID);
583+ priv->signalManagerChangedAllowAllSitesID=0;
584+
585+ if(priv->signalManagerChangedUnknownDomainPolicyID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedUnknownDomainPolicyID);
586+ priv->signalManagerChangedUnknownDomainPolicyID=0;
587+
588+ if(priv->signalManagerChangedCheckSecondLevelID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedCheckSecondLevelID);
589+ priv->signalManagerChangedCheckSecondLevelID=0;
590+
591+ g_object_unref(priv->manager);
592+ priv->manager=NULL;
593+ }
594+
595+ /* Set new cookie permission manager and
596+ * listen to changes in database property
597+ */
598+ manager=g_value_get_object(inValue);
599+ if(manager)
600+ {
601+ priv->manager=g_object_ref(manager);
602+
603+ priv->signalManagerChangedDatabaseID=
604+ g_signal_connect_swapped(priv->manager,
605+ "notify::database-filename",
606+ G_CALLBACK(_nojs_preferences_on_manager_database_changed),
607+ self);
608+ _nojs_preferences_on_manager_database_changed(self, NULL, priv->manager);
609+
610+ priv->signalManagerChangedAllowAllSitesID=
611+ g_signal_connect_swapped(priv->manager,
612+ "notify::allow-all-sites",
613+ G_CALLBACK(_nojs_preferences_on_manager_allow_all_sites_changed),
614+ self);
615+ _nojs_preferences_on_manager_allow_all_sites_changed(self, NULL, priv->manager);
616+
617+ priv->signalManagerChangedUnknownDomainPolicyID=
618+ g_signal_connect_swapped(priv->manager,
619+ "notify::unknown-domain-policy",
620+ G_CALLBACK(_nojs_preferences_on_manager_unknown_domain_policy_changed),
621+ self);
622+ _nojs_preferences_on_manager_unknown_domain_policy_changed(self, NULL, priv->manager);
623+
624+ priv->signalManagerChangedCheckSecondLevelID=
625+ g_signal_connect_swapped(priv->manager,
626+ "notify::only-second-level",
627+ G_CALLBACK(_nojs_preferences_on_manager_only_second_level_changed),
628+ self);
629+ _nojs_preferences_on_manager_only_second_level_changed(self, NULL, priv->manager);
630+ }
631+ break;
632+
633+ default:
634+ G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
635+ break;
636+ }
637+}
638+
639+static void nojs_preferences_get_property(GObject *inObject,
640+ guint inPropID,
641+ GValue *outValue,
642+ GParamSpec *inSpec)
643+{
644+ NoJSPreferences *self=NOJS_PREFERENCES(inObject);
645+
646+ switch(inPropID)
647+ {
648+ case PROP_MANAGER:
649+ g_value_set_object(outValue, self->priv->manager);
650+ break;
651+
652+ default:
653+ G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
654+ break;
655+ }
656+}
657+
658+/* Class initialization
659+ * Override functions in parent classes and define properties and signals
660+ */
661+static void nojs_preferences_class_init(NoJSPreferencesClass *klass)
662+{
663+ GObjectClass *gobjectClass=G_OBJECT_CLASS(klass);
664+
665+ /* Override functions */
666+ gobjectClass->finalize=nojs_preferences_finalize;
667+ gobjectClass->set_property=nojs_preferences_set_property;
668+ gobjectClass->get_property=nojs_preferences_get_property;
669+
670+ /* Set up private structure */
671+ g_type_class_add_private(klass, sizeof(NoJSPreferencesPrivate));
672+
673+ /* Define properties */
674+ NoJSPreferencesProperties[PROP_MANAGER]=
675+ g_param_spec_object("manager",
676+ _("Manager instance"),
677+ _("Instance to global NoJS manager"),
678+ TYPE_NOJS,
679+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
680+
681+ g_object_class_install_properties(gobjectClass, PROP_LAST, NoJSPreferencesProperties);
682+}
683+
684+/* Object initialization
685+ * Create private structure and set up default values
686+ */
687+static void nojs_preferences_init(NoJSPreferences *self)
688+{
689+ NoJSPreferencesPrivate *priv;
690+ GtkTreeSortable *sortableList;
691+ GtkCellRenderer *renderer;
692+ GtkTreeViewColumn *column;
693+ GtkWidget *widget;
694+ gchar *text;
695+ gchar *dialogTitle;
696+ GtkWidget *scrolled;
697+ GtkWidget *vbox;
698+ GtkWidget *hbox;
699+ gint width, height;
700+
701+ priv=self->priv=NOJS_PREFERENCES_GET_PRIVATE(self);
702+
703+ /* Set up default values */
704+ priv->manager=NULL;
705+
706+ /* Get content area to add gui controls to */
707+ priv->contentArea=gtk_dialog_get_content_area(GTK_DIALOG(self));
708+#ifdef GTK__3_0_VERSION
709+ vbox=gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
710+ gtk_box_set_homogeneous(GTK_BOX(vbox), FALSE);
711+#else
712+ vbox=gtk_vbox_new(FALSE, 0);
713+#endif
714+
715+ /* Set up dialog */
716+ dialogTitle=_("Configure NoJS");
717+
718+ gtk_window_set_title(GTK_WINDOW(self), dialogTitle);
719+ gtk_window_set_icon_name(GTK_WINDOW(self), GTK_STOCK_PROPERTIES);
720+
721+ sokoke_widget_get_text_size(GTK_WIDGET(self), "M", &width, &height);
722+ gtk_window_set_default_size(GTK_WINDOW(self), width*52, -1);
723+
724+ widget=sokoke_xfce_header_new(gtk_window_get_icon_name(GTK_WINDOW(self)), dialogTitle);
725+ if(widget) gtk_box_pack_start(GTK_BOX(priv->contentArea), widget, FALSE, FALSE, 0);
726+
727+ gtk_dialog_add_button(GTK_DIALOG(self), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
728+
729+ /* Set up description */
730+ widget=gtk_label_new(NULL);
731+ text=g_strdup_printf(_("Below is a list of all web sites and the policy set for them. "
732+ "You can delete policies by marking the entries and clicking on <i>Delete</i>."));
733+ gtk_label_set_markup(GTK_LABEL(widget), text);
734+ g_free(text);
735+ gtk_label_set_line_wrap(GTK_LABEL(widget), TRUE);
736+ gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, FALSE, 4);
737+
738+ /* Set up domain list */
739+ priv->listStore=gtk_list_store_new(N_COLUMN,
740+ G_TYPE_STRING, /* DOMAIN_COLUMN */
741+ G_TYPE_STRING /* POLICY_COLUMN */);
742+
743+ sortableList=GTK_TREE_SORTABLE(priv->listStore);
744+ gtk_tree_sortable_set_sort_func(sortableList,
745+ DOMAIN_COLUMN,
746+ (GtkTreeIterCompareFunc)_nojs_preferences_sort_string_callback,
747+ GINT_TO_POINTER(DOMAIN_COLUMN),
748+ NULL);
749+ gtk_tree_sortable_set_sort_func(sortableList,
750+ POLICY_COLUMN,
751+ (GtkTreeIterCompareFunc)_nojs_preferences_sort_string_callback,
752+ GINT_TO_POINTER(POLICY_COLUMN),
753+ NULL);
754+ gtk_tree_sortable_set_sort_column_id(sortableList, DOMAIN_COLUMN, GTK_SORT_ASCENDING);
755+
756+ /* Set up domain list view */
757+ priv->list=gtk_tree_view_new_with_model(GTK_TREE_MODEL(priv->listStore));
758+
759+#ifndef GTK__3_0_VERSION
760+ gtk_widget_set_size_request(priv->list, -1, 300);
761+#endif
762+
763+ priv->listSelection=gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->list));
764+ gtk_tree_selection_set_mode(priv->listSelection, GTK_SELECTION_MULTIPLE);
765+ g_signal_connect_swapped(priv->listSelection, "changed", G_CALLBACK(_nojs_preferences_changed_selection), self);
766+
767+ renderer=gtk_cell_renderer_text_new();
768+ column=gtk_tree_view_column_new_with_attributes(_("Domain"),
769+ renderer,
770+ "text", DOMAIN_COLUMN,
771+ NULL);
772+ gtk_tree_view_column_set_sort_column_id(column, DOMAIN_COLUMN);
773+ gtk_tree_view_append_column(GTK_TREE_VIEW(priv->list), column);
774+
775+ renderer=gtk_cell_renderer_text_new();
776+ column=gtk_tree_view_column_new_with_attributes(_("Policy"),
777+ renderer,
778+ "text", POLICY_COLUMN,
779+ NULL);
780+ gtk_tree_view_column_set_sort_column_id(column, POLICY_COLUMN);
781+ gtk_tree_view_append_column(GTK_TREE_VIEW(priv->list), column);
782+
783+ scrolled=gtk_scrolled_window_new(NULL, NULL);
784+#ifdef GTK__3_0_VERSION
785+ gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(scrolled), height*10);
786+#endif
787+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
788+ gtk_container_add(GTK_CONTAINER(scrolled), priv->list);
789+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
790+ gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 5);
791+
792+ /* Set up cookie domain list management buttons */
793+#ifdef GTK__3_0_VERSION
794+ hbox=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
795+ gtk_box_set_homogeneous(GTK_BOX(hbox), FALSE);
796+#else
797+ hbox=gtk_hbox_new(FALSE, 0);
798+#endif
799+
800+ priv->deleteButton=gtk_button_new_from_stock(GTK_STOCK_DELETE);
801+ gtk_widget_set_sensitive(priv->deleteButton, FALSE);
802+ gtk_container_add(GTK_CONTAINER(hbox), priv->deleteButton);
803+ g_signal_connect_swapped(priv->deleteButton, "clicked", G_CALLBACK(_nojs_preferences_on_delete_selection), self);
804+
805+ priv->deleteAllButton=gtk_button_new_with_mnemonic(_("Delete _all"));
806+ gtk_button_set_image(GTK_BUTTON(priv->deleteAllButton), gtk_image_new_from_stock(GTK_STOCK_DELETE, GTK_ICON_SIZE_BUTTON));
807+ gtk_widget_set_sensitive(priv->deleteAllButton, FALSE);
808+ gtk_container_add(GTK_CONTAINER(hbox), priv->deleteAllButton);
809+ g_signal_connect_swapped(priv->deleteAllButton, "clicked", G_CALLBACK(_nojs_preferences_on_delete_all), self);
810+
811+ gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
812+
813+ /* Add "allow-all-sites" checkbox */
814+ priv->allowAllSitesCheckbox=gtk_check_button_new_with_mnemonic(_("A_llow scripts at all sites"));
815+ priv->signalAllowAllSitesToggledID=g_signal_connect_swapped(priv->allowAllSitesCheckbox,
816+ "toggled",
817+ G_CALLBACK(_nojs_preferences_on_allow_all_sites_changed),
818+ self);
819+ gtk_box_pack_start(GTK_BOX(vbox), priv->allowAllSitesCheckbox, TRUE, TRUE, 5);
820+
821+ /* Add "block-unknown-domains" checkbox */
822+ priv->blockUnknownDomainsCheckbox=gtk_check_button_new_with_mnemonic(_("Bloc_k scripts at unknown domains by default"));
823+ priv->signalBlockUnknownDomainsToggledID=g_signal_connect_swapped(priv->blockUnknownDomainsCheckbox,
824+ "toggled",
825+ G_CALLBACK(_nojs_preferences_on_block_unknown_domains_changed),
826+ self);
827+ gtk_box_pack_start(GTK_BOX(vbox), priv->blockUnknownDomainsCheckbox, TRUE, TRUE, 5);
828+
829+ /* Add "check-second-level-only" checkbox */
830+ priv->checkSecondLevelOnlyCheckbox=gtk_check_button_new_with_mnemonic(_("S_et permissions on second-level domain"));
831+ priv->signalCheckSecondLevelOnlyToggledID=g_signal_connect_swapped(priv->checkSecondLevelOnlyCheckbox,
832+ "toggled",
833+ G_CALLBACK(_nojs_preferences_on_check_second_level_only_changed),
834+ self);
835+ gtk_box_pack_start(GTK_BOX(vbox), priv->checkSecondLevelOnlyCheckbox, TRUE, TRUE, 5);
836+
837+ /* Finalize setup of content area */
838+ gtk_container_add(GTK_CONTAINER(priv->contentArea), vbox);
839+}
840+
841+/* Implementation: Public API */
842+
843+/* Create new object */
844+GtkWidget* nojs_preferences_new(NoJS *inManager)
845+{
846+ return(g_object_new(TYPE_NOJS_PREFERENCES,
847+ "manager", inManager,
848+ NULL));
849+}
850
851=== added file 'extensions/nojs/nojs-preferences.h'
852--- extensions/nojs/nojs-preferences.h 1970-01-01 00:00:00 +0000
853+++ extensions/nojs/nojs-preferences.h 2013-05-22 23:07:26 +0000
854@@ -0,0 +1,55 @@
855+/*
856+ Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
857+
858+ This library is free software; you can redistribute it and/or
859+ modify it under the terms of the GNU Lesser General Public
860+ License as published by the Free Software Foundation; either
861+ version 2.1 of the License, or (at your option) any later version.
862+
863+ See the file COPYING for the full license text.
864+*/
865+
866+#ifndef __NOJS_PREFERENCES__
867+#define __NOJS_PREFERENCES__
868+
869+#include "config.h"
870+#include <midori/midori.h>
871+
872+#include "nojs.h"
873+
874+G_BEGIN_DECLS
875+
876+#define TYPE_NOJS_PREFERENCES (nojs_preferences_get_type())
877+#define NOJS_PREFERENCES(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_NOJS_PREFERENCES, NoJSPreferences))
878+#define IS_NOJS_PREFERENCES(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_NOJS_PREFERENCES))
879+#define NOJS_PREFERENCES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_NOJS_PREFERENCES, NoJSPreferencesClass))
880+#define IS_NOJS_PREFERENCES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_NOJS_PREFERENCES))
881+#define NOJS_PREFERENCES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_NOJS_PREFERENCES, NoJSPreferencesClass))
882+
883+typedef struct _NoJSPreferences NoJSPreferences;
884+typedef struct _NoJSPreferencesClass NoJSPreferencesClass;
885+typedef struct _NoJSPreferencesPrivate NoJSPreferencesPrivate;
886+
887+struct _NoJSPreferences
888+{
889+ /* Parent instance */
890+ GtkDialog parent_instance;
891+
892+ /* Private structure */
893+ NoJSPreferencesPrivate *priv;
894+};
895+
896+struct _NoJSPreferencesClass
897+{
898+ /* Parent class */
899+ GtkDialogClass parent_class;
900+};
901+
902+/* Public API */
903+GType nojs_preferences_get_type(void);
904+
905+GtkWidget* nojs_preferences_new(NoJS *inManager);
906+
907+G_END_DECLS
908+
909+#endif /* __NOJS_PREFERENCES__ */
910
911=== added file 'extensions/nojs/nojs-statusicon-allowed.png'
912Binary files extensions/nojs/nojs-statusicon-allowed.png 1970-01-01 00:00:00 +0000 and extensions/nojs/nojs-statusicon-allowed.png 2013-05-22 23:07:26 +0000 differ
913=== added file 'extensions/nojs/nojs-statusicon-denied.png'
914Binary files extensions/nojs/nojs-statusicon-denied.png 1970-01-01 00:00:00 +0000 and extensions/nojs/nojs-statusicon-denied.png 2013-05-22 23:07:26 +0000 differ
915=== added file 'extensions/nojs/nojs-statusicon-mixed.png'
916Binary files extensions/nojs/nojs-statusicon-mixed.png 1970-01-01 00:00:00 +0000 and extensions/nojs/nojs-statusicon-mixed.png 2013-05-22 23:07:26 +0000 differ
917=== added file 'extensions/nojs/nojs-view.c'
918--- extensions/nojs/nojs-view.c 1970-01-01 00:00:00 +0000
919+++ extensions/nojs/nojs-view.c 2013-05-22 23:07:26 +0000
920@@ -0,0 +1,823 @@
921+/*
922+ Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
923+
924+ This library is free software; you can redistribute it and/or
925+ modify it under the terms of the GNU Lesser General Public
926+ License as published by the Free Software Foundation; either
927+ version 2.1 of the License, or (at your option) any later version.
928+
929+ See the file COPYING for the full license text.
930+*/
931+
932+#include "nojs-view.h"
933+#include "nojs-preferences.h"
934+
935+/* Define this class in GObject system */
936+G_DEFINE_TYPE(NoJSView,
937+ nojs_view,
938+ G_TYPE_OBJECT)
939+
940+/* Properties */
941+enum
942+{
943+ PROP_0,
944+
945+ PROP_MANAGER,
946+ PROP_BROWSER,
947+ PROP_VIEW,
948+ PROP_MENU_ICON_STATE,
949+
950+ PROP_LAST
951+};
952+
953+static GParamSpec* NoJSViewProperties[PROP_LAST]={ 0, };
954+
955+/* Private structure - access only by public API if needed */
956+#define NOJS_VIEW_GET_PRIVATE(obj) \
957+ (G_TYPE_INSTANCE_GET_PRIVATE((obj), TYPE_NOJS_VIEW, NoJSViewPrivate))
958+
959+struct _NoJSViewPrivate
960+{
961+ /* Extension related */
962+ NoJS *manager;
963+ MidoriBrowser *browser;
964+ MidoriView *view;
965+
966+ GtkWidget *menu;
967+ gboolean menuPolicyWasChanged;
968+ NoJSMenuIconState menuIconState;
969+
970+ GSList *resourceURIs;
971+};
972+
973+/* IMPLEMENTATION: Private variables and methods */
974+
975+/* Preferences of this extension should be opened */
976+static void _nojs_view_on_preferences_response(GtkWidget* inDialog,
977+ gint inResponse,
978+ gpointer *inUserData)
979+{
980+ gtk_widget_destroy(inDialog);
981+}
982+
983+static void _nojs_view_on_open_preferences(NoJSView *self, gpointer inUserData)
984+{
985+ g_return_if_fail(NOJS_IS_VIEW(self));
986+
987+ NoJSViewPrivate *priv=self->priv;
988+
989+ /* Show preferences window */
990+ GtkWidget* dialog;
991+
992+ dialog=nojs_preferences_new(priv->manager);
993+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
994+ g_signal_connect(dialog, "response", G_CALLBACK (_nojs_view_on_preferences_response), self);
995+ gtk_widget_show_all(dialog);
996+}
997+
998+/* Selection was done in menu */
999+static void _nojs_view_on_menu_selection_done(NoJSView *self, gpointer inUserData)
1000+{
1001+ g_return_if_fail(NOJS_IS_VIEW(self));
1002+
1003+ NoJSViewPrivate *priv=self->priv;
1004+
1005+ /* Check if any policy was changed and reload page */
1006+ if(priv->menuPolicyWasChanged!=FALSE)
1007+ {
1008+ /* Reset flag that any policy was changed */
1009+ priv->menuPolicyWasChanged=FALSE;
1010+
1011+ /* Reload page */
1012+ midori_view_reload(priv->view, FALSE);
1013+g_message("%s: Reloading page %s as policy has changed", __func__, midori_view_get_display_uri(priv->view));
1014+ }
1015+}
1016+
1017+/* Destroy menu */
1018+static void _nojs_view_destroy_menu(NoJSView *self)
1019+{
1020+ g_return_if_fail(NOJS_IS_VIEW(self));
1021+ g_return_if_fail(self->priv->menu!=NULL);
1022+
1023+ NoJSViewPrivate *priv=self->priv;
1024+
1025+ /* Empty menu and list of domains added to menu */
1026+ gtk_widget_destroy(priv->menu);
1027+ priv->menu=NULL;
1028+
1029+ /* Reset menu icon to default state */
1030+ priv->menuIconState=NOJS_MENU_ICON_STATE_UNDETERMINED;
1031+ g_object_notify_by_pspec(G_OBJECT(self), NoJSViewProperties[PROP_MENU_ICON_STATE]);
1032+}
1033+
1034+/* Create empty menu */
1035+static void _nojs_view_create_empty_menu(NoJSView *self)
1036+{
1037+ g_return_if_fail(NOJS_IS_VIEW(self));
1038+ g_return_if_fail(self->priv->menu==NULL);
1039+
1040+ NoJSViewPrivate *priv=self->priv;
1041+ GtkWidget *item;
1042+
1043+ /* Create new menu and set up default items */
1044+ priv->menu=gtk_menu_new();
1045+
1046+ item=gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, NULL);
1047+ g_signal_connect_swapped(item, "activate", G_CALLBACK(_nojs_view_on_open_preferences), self);
1048+ gtk_menu_shell_prepend(GTK_MENU_SHELL(priv->menu), item);
1049+ gtk_widget_show_all(item);
1050+
1051+ /* Reset flag that any policy was changed */
1052+ priv->menuPolicyWasChanged=FALSE;
1053+
1054+ /* Reset menu icon to default state */
1055+ priv->menuIconState=NOJS_MENU_ICON_STATE_UNDETERMINED;
1056+ g_object_notify_by_pspec(G_OBJECT(self), NoJSViewProperties[PROP_MENU_ICON_STATE]);
1057+
1058+ /* Connect signal to menu */
1059+ g_signal_connect_swapped(priv->menu, "selection-done", G_CALLBACK(_nojs_view_on_menu_selection_done), self);
1060+}
1061+
1062+/* Change visibility state of menu item for a domain depending on policy */
1063+static gboolean _nojs_view_menu_item_change_policy(NoJSView *self, const gchar *inDomain, NoJSPolicy inPolicy)
1064+{
1065+ g_return_val_if_fail(NOJS_IS_VIEW(self), FALSE);
1066+ g_return_val_if_fail(inDomain, FALSE);
1067+
1068+ NoJSViewPrivate *priv=self->priv;
1069+ GList *items, *iter;
1070+ gboolean updated;
1071+
1072+ /* Handle accept-for-session like accept when showing or hiding menu items */
1073+ if(inPolicy==NOJS_POLICY_ACCEPT_TEMPORARILY) inPolicy=NOJS_POLICY_ACCEPT;
1074+
1075+ /* Update menu items */
1076+ updated=FALSE;
1077+ items=gtk_container_get_children(GTK_CONTAINER(priv->menu));
1078+ for(iter=items; iter; iter=iter->next)
1079+ {
1080+ /* Only check and update menu items (not separators and so on) */
1081+ if(GTK_IS_MENU_ITEM(iter->data))
1082+ {
1083+ GtkMenuItem *item=GTK_MENU_ITEM(iter->data);
1084+ const gchar *itemDomain;
1085+ NoJSPolicy itemPolicy;
1086+
1087+ itemDomain=(const gchar*)g_object_get_data(G_OBJECT(item), "domain");
1088+ itemPolicy=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "policy"));
1089+
1090+ /* Handle accept-for-session like accept when showing or hiding menu items */
1091+ if(itemPolicy==NOJS_POLICY_ACCEPT_TEMPORARILY) itemPolicy=NOJS_POLICY_ACCEPT;
1092+
1093+ /* If menu item has "domain"-data update its visibility state
1094+ * depending on matching policy
1095+ */
1096+ if(g_strcmp0(itemDomain, inDomain)==0)
1097+ {
1098+ if(itemPolicy==inPolicy) gtk_widget_hide(GTK_WIDGET(item));
1099+ else gtk_widget_show_all(GTK_WIDGET(item));
1100+
1101+ /* Set flag that at least one menu item was updated */
1102+ updated=TRUE;
1103+ }
1104+ }
1105+ }
1106+ g_list_free(items);
1107+
1108+ /* Return flag indicating if at least one menu item was updated */
1109+ return(updated);
1110+}
1111+
1112+/* A menu item was selected */
1113+static void _nojs_view_on_menu_item_activate(NoJSView *self, gpointer inUserData)
1114+{
1115+ g_return_if_fail(NOJS_IS_VIEW(self));
1116+ g_return_if_fail(GTK_IS_MENU_ITEM(inUserData));
1117+
1118+ NoJSViewPrivate *priv=self->priv;
1119+ GtkMenuItem *item=GTK_MENU_ITEM(inUserData);
1120+ const gchar *domain;
1121+ NoJSPolicy policy;
1122+
1123+ /* Get domain and policy to set */
1124+ domain=(const gchar*)g_object_get_data(G_OBJECT(item), "domain");
1125+ policy=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "policy"));
1126+ g_return_if_fail(domain);
1127+ g_return_if_fail(policy>=NOJS_POLICY_ACCEPT && policy<=NOJS_POLICY_BLOCK);
1128+
1129+ /* Set policy for domain and update menu items */
1130+ _nojs_view_menu_item_change_policy(self, domain, policy);
1131+ nojs_set_policy(priv->manager, domain, policy);
1132+
1133+ /* Set flag that a policy was changed */
1134+ priv->menuPolicyWasChanged=TRUE;
1135+}
1136+
1137+/* Add site to menu */
1138+static void _nojs_view_add_site_to_menu(NoJSView *self, const gchar *inDomain, NoJSPolicy inPolicy)
1139+{
1140+ g_return_if_fail(NOJS_IS_VIEW(self));
1141+ g_return_if_fail(inDomain);
1142+
1143+ NoJSViewPrivate *priv=self->priv;
1144+ GtkWidget *item;
1145+ gchar *itemLabel;
1146+ GtkWidget *itemImage;
1147+ gchar *itemImageFile;
1148+ static gint INSERT_POSITION=1;
1149+ NoJSMenuIconState newMenuIconState;
1150+
1151+ /* Create menu object if not available */
1152+ if(!priv->menu) _nojs_view_create_empty_menu(self);
1153+
1154+ /* Check if domain was already added to menu. If it exists just update it. */
1155+ if(_nojs_view_menu_item_change_policy(self, inDomain, inPolicy)==TRUE) return;
1156+
1157+ /* Add menu item(s) for domain */
1158+ itemLabel=g_strdup_printf(_("Deny %s"), inDomain);
1159+ item=gtk_image_menu_item_new_with_label(itemLabel);
1160+#ifdef ALTERNATE_DATADIR
1161+ itemImageFile=g_build_filename(ALTERNATE_DATADIR, "nojs-menu-deny.png", NULL);
1162+#else
1163+ itemImageFile=g_build_filename(MDATADIR, PACKAGE_NAME, "nojs", "nojs-menu-deny.png", NULL);
1164+#endif
1165+ itemImage=gtk_image_new_from_file(itemImageFile);
1166+ gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), itemImage);
1167+ gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(item), TRUE);
1168+ gtk_menu_shell_insert(GTK_MENU_SHELL(priv->menu), item, INSERT_POSITION);
1169+ if(inPolicy!=NOJS_POLICY_BLOCK) gtk_widget_show_all(item);
1170+ g_object_set_data_full(G_OBJECT(item), "domain", g_strdup(inDomain), (GDestroyNotify)g_free);
1171+ g_object_set_data(G_OBJECT(item), "policy", GINT_TO_POINTER(NOJS_POLICY_BLOCK));
1172+ g_signal_connect_swapped(item, "activate", G_CALLBACK(_nojs_view_on_menu_item_activate), self);
1173+ g_free(itemLabel);
1174+ g_free(itemImageFile);
1175+
1176+ itemLabel=g_strdup_printf(_("Allow %s"), inDomain);
1177+ item=gtk_image_menu_item_new_with_label(itemLabel);
1178+#ifdef ALTERNATE_DATADIR
1179+ itemImageFile=g_build_filename(ALTERNATE_DATADIR, "nojs-menu-accept.png", NULL);
1180+#else
1181+ itemImageFile=g_build_filename(MDATADIR, PACKAGE_NAME, "nojs", "nojs-menu-accept.png", NULL);
1182+#endif
1183+ itemImage=gtk_image_new_from_file(itemImageFile);
1184+ gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), itemImage);
1185+ gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(item), TRUE);
1186+ gtk_menu_shell_insert(GTK_MENU_SHELL(priv->menu), item, INSERT_POSITION);
1187+ if(inPolicy!=NOJS_POLICY_ACCEPT && inPolicy!=NOJS_POLICY_ACCEPT_TEMPORARILY) gtk_widget_show_all(item);
1188+ g_object_set_data_full(G_OBJECT(item), "domain", g_strdup(inDomain), (GDestroyNotify)g_free);
1189+ g_object_set_data(G_OBJECT(item), "policy", GINT_TO_POINTER(NOJS_POLICY_ACCEPT));
1190+ g_signal_connect_swapped(item, "activate", G_CALLBACK(_nojs_view_on_menu_item_activate), self);
1191+ g_free(itemLabel);
1192+ g_free(itemImageFile);
1193+
1194+ itemLabel=g_strdup_printf(_("Allow %s this session"), inDomain);
1195+ item=gtk_image_menu_item_new_with_label(itemLabel);
1196+#ifdef ALTERNATE_DATADIR
1197+ itemImageFile=g_build_filename(ALTERNATE_DATADIR, "nojs-menu-temporary.png", NULL);
1198+#else
1199+ itemImageFile=g_build_filename(MDATADIR, PACKAGE_NAME, "nojs", "nojs-menu-temporary.png", NULL);
1200+#endif
1201+ itemImage=gtk_image_new_from_file(itemImageFile);
1202+ gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), itemImage);
1203+ gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(item), TRUE);
1204+ gtk_menu_shell_insert(GTK_MENU_SHELL(priv->menu), item, INSERT_POSITION);
1205+ if(inPolicy!=NOJS_POLICY_ACCEPT && inPolicy!=NOJS_POLICY_ACCEPT_TEMPORARILY) gtk_widget_show_all(item);
1206+ g_object_set_data_full(G_OBJECT(item), "domain", g_strdup(inDomain), (GDestroyNotify)g_free);
1207+ g_object_set_data(G_OBJECT(item), "policy", GINT_TO_POINTER(NOJS_POLICY_ACCEPT_TEMPORARILY));
1208+ g_signal_connect_swapped(item, "activate", G_CALLBACK(_nojs_view_on_menu_item_activate), self);
1209+ g_free(itemLabel);
1210+ g_free(itemImageFile);
1211+
1212+ /* Add seperator to seperate actions for this domain from the other domains */
1213+ item=gtk_separator_menu_item_new();
1214+ gtk_menu_shell_insert(GTK_MENU_SHELL(priv->menu), item, INSERT_POSITION);
1215+ gtk_widget_show_all(item);
1216+
1217+ /* Determine state of status icon */
1218+ if(priv->menuIconState!=NOJS_MENU_ICON_STATE_MIXED)
1219+ {
1220+ switch(inPolicy)
1221+ {
1222+ case NOJS_POLICY_ACCEPT:
1223+ case NOJS_POLICY_ACCEPT_TEMPORARILY:
1224+ newMenuIconState=NOJS_MENU_ICON_STATE_ALLOWED;
1225+ break;
1226+
1227+ case NOJS_POLICY_BLOCK:
1228+ newMenuIconState=NOJS_MENU_ICON_STATE_DENIED;
1229+ break;
1230+
1231+ default:
1232+ newMenuIconState=NOJS_MENU_ICON_STATE_MIXED;
1233+ break;
1234+ }
1235+
1236+ if(priv->menuIconState==NOJS_MENU_ICON_STATE_UNDETERMINED ||
1237+ priv->menuIconState!=newMenuIconState)
1238+ {
1239+ priv->menuIconState=newMenuIconState;
1240+ g_object_notify_by_pspec(G_OBJECT(self), NoJSViewProperties[PROP_MENU_ICON_STATE]);
1241+ }
1242+ }
1243+}
1244+
1245+/* Status of loading a site has changed */
1246+static void _nojs_view_on_load_status_changed(NoJSView *self, GParamSpec *inSpec, gpointer inUserData)
1247+{
1248+ g_return_if_fail(NOJS_IS_VIEW(self));
1249+ g_return_if_fail(WEBKIT_IS_WEB_VIEW(inUserData));
1250+
1251+ NoJSViewPrivate *priv=self->priv;
1252+ WebKitWebView *webkitView=WEBKIT_WEB_VIEW(inUserData);
1253+ WebKitWebSettings *settings=webkit_web_view_get_settings(webkitView);
1254+ WebKitLoadStatus status;
1255+ SoupURI *uri;
1256+
1257+ /* Get URI of document loading/loaded */
1258+ uri=soup_uri_new(webkit_web_view_get_uri(webkitView));
1259+
1260+ /* Check load status */
1261+ status=webkit_web_view_get_load_status(webkitView);
1262+
1263+ /* Check if a view was emptied, e.g. for a new document going to be loaded soon */
1264+ if(status==WEBKIT_LOAD_PROVISIONAL)
1265+ {
1266+ /* Create a new empty menu */
1267+ _nojs_view_destroy_menu(self);
1268+ _nojs_view_create_empty_menu(self);
1269+
1270+ /* Free list of resource URIs, that's the list of URIs for all resources
1271+ * of a page
1272+ */
1273+ if(priv->resourceURIs)
1274+ {
1275+ g_slist_free_full(priv->resourceURIs, (GDestroyNotify)g_free);
1276+ priv->resourceURIs=NULL;
1277+ }
1278+ }
1279+
1280+ /* Check if document loading is going to start. Do not check special pages. */
1281+ if(status==WEBKIT_LOAD_COMMITTED &&
1282+ uri &&
1283+ uri->scheme &&
1284+ g_strcmp0(uri->scheme, "about")!=0)
1285+ {
1286+ /* Check if domain is black-listed or white-listed and enable or
1287+ * disable javascript accordingly. But if settings match already
1288+ * the state it should get do not set it again to avoid reloads of page.
1289+ */
1290+ gchar *domain;
1291+ NoJSPolicy policy;
1292+ gboolean currentScriptsEnabled;
1293+ gboolean newScriptsEnabled;
1294+
1295+ domain=nojs_get_domain(priv->manager, uri);
1296+ policy=nojs_get_policy(priv->manager, domain);
1297+ if(policy==NOJS_POLICY_UNDETERMINED)
1298+ {
1299+ policy=nojs_get_policy_for_unknown_domain(priv->manager);
1300+ // TODO: Show nick_name of policy (enum) to use in warning
1301+ g_warning("Got invalid policy. Using default policy for unknown domains.");
1302+ }
1303+
1304+ newScriptsEnabled=(policy==NOJS_POLICY_BLOCK ? FALSE : TRUE);
1305+ g_object_get(G_OBJECT(settings), "enable-scripts", &currentScriptsEnabled, NULL);
1306+
1307+ if(newScriptsEnabled!=currentScriptsEnabled)
1308+ {
1309+ g_object_set(G_OBJECT(settings), "enable-scripts", newScriptsEnabled, NULL);
1310+ // TODO: Set uri also to ensure this uri is going to be reloaded
1311+ }
1312+
1313+ _nojs_view_add_site_to_menu(self, domain, policy);
1314+ if(domain) g_free(domain);
1315+ }
1316+
1317+ /* Free allocated resources */
1318+ if(uri) soup_uri_free(uri);
1319+}
1320+
1321+/* A request is going to sent */
1322+static void _nojs_view_on_resource_request_starting(NoJSView *self,
1323+ WebKitWebFrame *inFrame,
1324+ WebKitWebResource *inResource,
1325+ WebKitNetworkRequest *inRequest,
1326+ WebKitNetworkResponse *inResponse,
1327+ gpointer inUserData)
1328+{
1329+ g_return_if_fail(NOJS_IS_VIEW(self));
1330+
1331+ NoJSViewPrivate *priv=self->priv;
1332+ SoupMessage *message;
1333+ SoupURI *uri;
1334+ gchar *uriText;
1335+
1336+ /* Remember resource URIs requesting */
1337+ message=(inRequest ? webkit_network_request_get_message(inRequest) : NULL);
1338+ if(message)
1339+ {
1340+ uri=soup_message_get_uri(message);
1341+ if(uri)
1342+ {
1343+ uriText=soup_uri_to_string(uri, FALSE);
1344+ priv->resourceURIs=g_slist_prepend(priv->resourceURIs, uriText);
1345+ }
1346+ }
1347+
1348+ message=(inResponse ? webkit_network_response_get_message(inResponse) : NULL);
1349+ if(message)
1350+ {
1351+ uri=soup_message_get_uri(message);
1352+ if(uri)
1353+ {
1354+ uriText=soup_uri_to_string(uri, FALSE);
1355+ priv->resourceURIs=g_slist_prepend(priv->resourceURIs, uriText);
1356+ }
1357+ }
1358+}
1359+
1360+/* A policy has changed */
1361+static void _nojs_view_on_policy_changed(NoJSView *self, gchar *inDomain, gpointer inUserData)
1362+{
1363+ g_return_if_fail(NOJS_IS_VIEW(self));
1364+ g_return_if_fail(inDomain);
1365+
1366+ NoJSViewPrivate *priv=self->priv;
1367+ GList *items, *iter;
1368+ gboolean reloaded;
1369+
1370+ /* Check if the policy of a domain has changed this view has referenced resources to */
1371+ reloaded=FALSE;
1372+ items=gtk_container_get_children(GTK_CONTAINER(priv->menu));
1373+ for(iter=items; reloaded==FALSE && iter; iter=iter->next)
1374+ {
1375+ if(GTK_IS_MENU_ITEM(iter->data))
1376+ {
1377+ const gchar *itemDomain;
1378+
1379+ /* Check if domain matches menu item */
1380+ itemDomain=(const gchar*)g_object_get_data(G_OBJECT(iter->data), "domain");
1381+ if(g_strcmp0(itemDomain, inDomain)==0)
1382+ {
1383+ /* Found domain in our menu so reload page */
1384+ midori_view_reload(priv->view, FALSE);
1385+ reloaded=TRUE;
1386+ }
1387+ }
1388+ }
1389+ g_list_free(items);
1390+}
1391+
1392+/* A javascript URI is going to loaded or blocked */
1393+static void _nojs_view_on_uri_load_policy_status(NoJSView *self, gchar *inURI, NoJSPolicy inPolicy, gpointer inUserData)
1394+{
1395+ g_return_if_fail(NOJS_IS_VIEW(self));
1396+
1397+ NoJSViewPrivate *priv=self->priv;
1398+ GSList *iter;
1399+ gchar *checkURI;
1400+
1401+ /* Check if uri (accepted or blocked) might be one of ours */
1402+ for(iter=priv->resourceURIs; iter; iter=iter->next)
1403+ {
1404+ checkURI=(gchar*)iter->data;
1405+ if(g_strcmp0(checkURI, inURI)==0)
1406+ {
1407+ SoupURI *uri;
1408+ gchar *domain;
1409+
1410+ uri=soup_uri_new(inURI);
1411+ domain=nojs_get_domain(priv->manager, uri);
1412+ if(domain)
1413+ {
1414+ _nojs_view_add_site_to_menu(self, domain, inPolicy);
1415+ g_free(domain);
1416+ }
1417+
1418+ soup_uri_free(uri);
1419+ break;
1420+ }
1421+ }
1422+}
1423+
1424+/* Property "view" has changed */
1425+static void _nojs_view_on_view_changed(NoJSView *self, MidoriView *inView)
1426+{
1427+ NoJSViewPrivate *priv=self->priv;
1428+ WebKitWebView *webkitView;
1429+
1430+ /* Disconnect signal on old view */
1431+ if(priv->view)
1432+ {
1433+ webkitView=WEBKIT_WEB_VIEW(midori_view_get_web_view(priv->view));
1434+ g_signal_handlers_disconnect_by_data(webkitView, self);
1435+ g_object_set_data(G_OBJECT(priv->view), "nojs-view-instance", NULL);
1436+ g_object_unref(priv->view);
1437+ priv->view=NULL;
1438+ }
1439+
1440+ /* Set new view if valid pointer */
1441+ if(!inView) return;
1442+
1443+ priv->view=g_object_ref(inView);
1444+ g_object_set_data(G_OBJECT(priv->view), "nojs-view-instance", self);
1445+
1446+ /* Listen to changes of load-status in view */
1447+ webkitView=WEBKIT_WEB_VIEW(midori_view_get_web_view(priv->view));
1448+ g_signal_connect_swapped(webkitView, "notify::load-status", G_CALLBACK(_nojs_view_on_load_status_changed), self);
1449+ g_signal_connect_swapped(webkitView, "resource-request-starting", G_CALLBACK(_nojs_view_on_resource_request_starting), self);
1450+
1451+ /* Create empty menu */
1452+ _nojs_view_destroy_menu(self);
1453+ _nojs_view_create_empty_menu(self);
1454+
1455+ /* Release list of resource URIs */
1456+ if(priv->resourceURIs)
1457+ {
1458+ g_slist_free_full(priv->resourceURIs, (GDestroyNotify)g_free);
1459+ priv->resourceURIs=NULL;
1460+ }
1461+}
1462+
1463+/* This extension is going to be deactivated */
1464+static void _nojs_view_on_extension_deactivated(NoJSView *self, MidoriExtension *inExtension)
1465+{
1466+ g_return_if_fail(NOJS_IS_VIEW(self));
1467+
1468+ /* Dispose allocated resources by unreferencing ourselve */
1469+ g_object_unref(self);
1470+}
1471+
1472+/* Property "manager" has changed */
1473+static void _nojs_view_on_manager_changed(NoJSView *self, NoJS *inNoJS)
1474+{
1475+ g_return_if_fail(NOJS_IS_VIEW(self));
1476+ g_return_if_fail(!inNoJS || IS_NOJS(inNoJS));
1477+
1478+ NoJSViewPrivate *priv=self->priv;
1479+ MidoriExtension *extension;
1480+
1481+ /* Release reference to old manager and clean up */
1482+ if(priv->manager)
1483+ {
1484+ g_object_get(priv->manager, "extension", &extension, NULL);
1485+ g_signal_handlers_disconnect_by_data(extension, self);
1486+ g_object_unref(extension);
1487+
1488+ g_signal_handlers_disconnect_by_data(priv->manager, self);
1489+ g_object_unref(priv->manager);
1490+ priv->manager=NULL;
1491+ }
1492+
1493+ /* Set new view if valid pointer */
1494+ if(!inNoJS) return;
1495+
1496+ priv->manager=g_object_ref(inNoJS);
1497+
1498+ /* Connect signals to manager */
1499+ g_signal_connect_swapped(priv->manager, "uri-load-policy-status", G_CALLBACK(_nojs_view_on_uri_load_policy_status), self);
1500+ g_signal_connect_swapped(priv->manager, "policy-changed", G_CALLBACK(_nojs_view_on_policy_changed), self);
1501+
1502+ /* Connect signal to get noticed when extension is going to be deactivated
1503+ * to release all references to GObjects
1504+ */
1505+ g_object_get(priv->manager, "extension", &extension, NULL);
1506+ g_signal_connect_swapped(extension, "deactivate", G_CALLBACK(_nojs_view_on_extension_deactivated), self);
1507+ g_object_unref(extension);
1508+}
1509+
1510+/* IMPLEMENTATION: GObject */
1511+
1512+/* Finalize this object */
1513+static void nojs_view_finalize(GObject *inObject)
1514+{
1515+ NoJSView *self=NOJS_VIEW(inObject);
1516+ NoJSViewPrivate *priv=self->priv;
1517+
1518+ /* Dispose allocated resources */
1519+ if(priv->manager)
1520+ {
1521+ MidoriExtension *extension;
1522+
1523+ g_object_get(priv->manager, "extension", &extension, NULL);
1524+ g_signal_handlers_disconnect_by_data(extension, self);
1525+ g_object_unref(extension);
1526+
1527+ g_signal_handlers_disconnect_by_data(priv->manager, self);
1528+ g_object_unref(priv->manager);
1529+ priv->manager=NULL;
1530+ }
1531+
1532+ if(priv->browser)
1533+ {
1534+ g_object_unref(priv->browser);
1535+ priv->browser=NULL;
1536+ }
1537+
1538+ if(priv->view)
1539+ {
1540+ _nojs_view_on_view_changed(self, NULL);
1541+ }
1542+
1543+ if(priv->menu)
1544+ {
1545+ gtk_widget_destroy(priv->menu);
1546+ priv->menu=NULL;
1547+ }
1548+
1549+ if(priv->resourceURIs)
1550+ {
1551+ g_slist_free_full(priv->resourceURIs, (GDestroyNotify)g_free);
1552+ priv->resourceURIs=NULL;
1553+ }
1554+
1555+ /* Call parent's class finalize method */
1556+ G_OBJECT_CLASS(nojs_view_parent_class)->finalize(inObject);
1557+}
1558+
1559+/* Set/get properties */
1560+static void nojs_view_set_property(GObject *inObject,
1561+ guint inPropID,
1562+ const GValue *inValue,
1563+ GParamSpec *inSpec)
1564+{
1565+ NoJSView *self=NOJS_VIEW(inObject);
1566+
1567+ switch(inPropID)
1568+ {
1569+ /* Construct-only properties */
1570+ case PROP_MANAGER:
1571+ _nojs_view_on_manager_changed(self, NOJS(g_value_get_object(inValue)));
1572+ break;
1573+
1574+ case PROP_BROWSER:
1575+ if(self->priv->browser) g_object_unref(self->priv->browser);
1576+ self->priv->browser=g_object_ref(g_value_get_object(inValue));
1577+ break;
1578+
1579+ case PROP_VIEW:
1580+ _nojs_view_on_view_changed(self, MIDORI_VIEW(g_value_get_object(inValue)));
1581+ break;
1582+
1583+ default:
1584+ G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
1585+ break;
1586+ }
1587+}
1588+
1589+static void nojs_view_get_property(GObject *inObject,
1590+ guint inPropID,
1591+ GValue *outValue,
1592+ GParamSpec *inSpec)
1593+{
1594+ NoJSView *self=NOJS_VIEW(inObject);
1595+
1596+ switch(inPropID)
1597+ {
1598+ case PROP_MANAGER:
1599+ g_value_set_object(outValue, self->priv->manager);
1600+ break;
1601+
1602+ case PROP_BROWSER:
1603+ g_value_set_object(outValue, self->priv->browser);
1604+ break;
1605+
1606+ case PROP_VIEW:
1607+ g_value_set_object(outValue, self->priv->view);
1608+ break;
1609+
1610+ case PROP_MENU_ICON_STATE:
1611+ g_value_set_enum(outValue, self->priv->menuIconState);
1612+ break;
1613+
1614+ default:
1615+ G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
1616+ break;
1617+ }
1618+}
1619+
1620+/* Class initialization
1621+ * Override functions in parent classes and define properties and signals
1622+ */
1623+static void nojs_view_class_init(NoJSViewClass *klass)
1624+{
1625+ GObjectClass *gobjectClass=G_OBJECT_CLASS(klass);
1626+
1627+ /* Override functions */
1628+ gobjectClass->finalize=nojs_view_finalize;
1629+ gobjectClass->set_property=nojs_view_set_property;
1630+ gobjectClass->get_property=nojs_view_get_property;
1631+
1632+ /* Set up private structure */
1633+ g_type_class_add_private(klass, sizeof(NoJSViewPrivate));
1634+
1635+ /* Define properties */
1636+ NoJSViewProperties[PROP_MANAGER]=
1637+ g_param_spec_object("manager",
1638+ _("Manager instance"),
1639+ _("Instance to global NoJS manager"),
1640+ TYPE_NOJS,
1641+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
1642+
1643+ NoJSViewProperties[PROP_BROWSER]=
1644+ g_param_spec_object("browser",
1645+ _("Browser window"),
1646+ _("The Midori browser instance this view belongs to"),
1647+ MIDORI_TYPE_BROWSER,
1648+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
1649+
1650+ NoJSViewProperties[PROP_VIEW]=
1651+ g_param_spec_object("view",
1652+ _("View"),
1653+ _("The Midori view instance this view belongs to"),
1654+ MIDORI_TYPE_VIEW,
1655+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
1656+
1657+ NoJSViewProperties[PROP_MENU_ICON_STATE]=
1658+ g_param_spec_enum("menu-icon-state",
1659+ _("Menu icon state"),
1660+ _("State of menu icon to show in status bar"),
1661+ NOJS_TYPE_MENU_ICON_STATE,
1662+ NOJS_MENU_ICON_STATE_UNDETERMINED,
1663+ G_PARAM_READABLE);
1664+
1665+ g_object_class_install_properties(gobjectClass, PROP_LAST, NoJSViewProperties);
1666+}
1667+
1668+/* Object initialization
1669+ * Create private structure and set up default values
1670+ */
1671+static void nojs_view_init(NoJSView *self)
1672+{
1673+ NoJSViewPrivate *priv;
1674+
1675+ priv=self->priv=NOJS_VIEW_GET_PRIVATE(self);
1676+
1677+ /* Set up default values */
1678+ priv->manager=NULL;
1679+ priv->browser=NULL;
1680+ priv->view=NULL;
1681+
1682+ priv->menu=NULL;
1683+ priv->menuPolicyWasChanged=FALSE;
1684+ priv->menuIconState=NOJS_POLICY_UNDETERMINED;
1685+
1686+ priv->resourceURIs=NULL;
1687+
1688+ /* Create empty menu */
1689+ _nojs_view_create_empty_menu(self);
1690+}
1691+
1692+/* Implementation: Public API */
1693+
1694+/* Create new object */
1695+NoJSView* nojs_view_new(NoJS *inNoJS, MidoriBrowser *inBrowser, MidoriView *inView)
1696+{
1697+ return(g_object_new(TYPE_NOJS_VIEW,
1698+ "manager", inNoJS,
1699+ "browser", inBrowser,
1700+ "view", inView,
1701+ NULL));
1702+}
1703+
1704+/* Get menu widget for this view */
1705+GtkMenu* nojs_view_get_menu(NoJSView *self)
1706+{
1707+ g_return_val_if_fail(NOJS_IS_VIEW(self), NULL);
1708+
1709+ return(GTK_MENU(self->priv->menu));
1710+}
1711+
1712+/* Get image used for menu icon in status bar */
1713+NoJSMenuIconState nojs_view_get_menu_icon_state(NoJSView *self)
1714+{
1715+ g_return_val_if_fail(NOJS_IS_VIEW(self), NOJS_MENU_ICON_STATE_UNDETERMINED);
1716+
1717+ return(self->priv->menuIconState);
1718+}
1719+
1720+/************************************************************************************/
1721+
1722+/* Implementation: Enumeration */
1723+GType nojs_menu_icon_state_get_type(void)
1724+{
1725+ static volatile gsize g_define_type_id__volatile=0;
1726+
1727+ if(g_once_init_enter(&g_define_type_id__volatile))
1728+ {
1729+ static const GEnumValue values[]=
1730+ {
1731+ { NOJS_MENU_ICON_STATE_UNDETERMINED, "NOJS_MENU_ICON_STATE_UNDETERMINED", N_("Undetermined") },
1732+ { NOJS_MENU_ICON_STATE_ALLOWED, "NOJS_MENU_ICON_STATE_ALLOWED", N_("Allowed") },
1733+ { NOJS_MENU_ICON_STATE_MIXED, "NOJS_MENU_ICON_STATE_MIXED", N_("Mixed") },
1734+ { NOJS_MENU_ICON_STATE_DENIED, "NOJS_MENU_ICON_STATE_DENIED", N_("Denied") },
1735+ { 0, NULL, NULL }
1736+ };
1737+
1738+ GType g_define_type_id=g_enum_register_static(g_intern_static_string("NoJSMenuIconState"), values);
1739+ g_once_init_leave(&g_define_type_id__volatile, g_define_type_id);
1740+ }
1741+
1742+ return(g_define_type_id__volatile);
1743+}
1744
1745=== added file 'extensions/nojs/nojs-view.h'
1746--- extensions/nojs/nojs-view.h 1970-01-01 00:00:00 +0000
1747+++ extensions/nojs/nojs-view.h 2013-05-22 23:07:26 +0000
1748@@ -0,0 +1,71 @@
1749+/*
1750+ Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
1751+
1752+ This library is free software; you can redistribute it and/or
1753+ modify it under the terms of the GNU Lesser General Public
1754+ License as published by the Free Software Foundation; either
1755+ version 2.1 of the License, or (at your option) any later version.
1756+
1757+ See the file COPYING for the full license text.
1758+*/
1759+
1760+#ifndef __NOJS_VIEW__
1761+#define __NOJS_VIEW__
1762+
1763+#include "config.h"
1764+#include "nojs.h"
1765+#include <midori/midori.h>
1766+
1767+G_BEGIN_DECLS
1768+
1769+/* NoJS view enums */
1770+typedef enum
1771+{
1772+ NOJS_MENU_ICON_STATE_UNDETERMINED,
1773+ NOJS_MENU_ICON_STATE_ALLOWED,
1774+ NOJS_MENU_ICON_STATE_MIXED,
1775+ NOJS_MENU_ICON_STATE_DENIED
1776+} NoJSMenuIconState;
1777+
1778+/* NoJS view object */
1779+#define TYPE_NOJS_VIEW (nojs_view_get_type())
1780+#define NOJS_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_NOJS_VIEW, NoJSView))
1781+#define NOJS_IS_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_NOJS_VIEW))
1782+#define NOJS_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_NOJS_VIEW, NoJSViewClass))
1783+#define NOJS_IS_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_NOJS_VIEW))
1784+#define NOJS_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_NOJS_VIEW, NoJSViewClass))
1785+
1786+typedef struct _NoJSView NoJSView;
1787+typedef struct _NoJSViewClass NoJSViewClass;
1788+typedef struct _NoJSViewPrivate NoJSViewPrivate;
1789+
1790+struct _NoJSView
1791+{
1792+ /* Parent instance */
1793+ GObject parent_instance;
1794+
1795+ /* Private structure */
1796+ NoJSViewPrivate *priv;
1797+};
1798+
1799+struct _NoJSViewClass
1800+{
1801+ /* Parent class */
1802+ GObjectClass parent_class;
1803+};
1804+
1805+/* Public API */
1806+GType nojs_view_get_type(void);
1807+
1808+NoJSView* nojs_view_new(NoJS *inNoJS, MidoriBrowser *inBrowser, MidoriView *inView);
1809+
1810+GtkMenu* nojs_view_get_menu(NoJSView *self);
1811+NoJSMenuIconState nojs_view_get_menu_icon_state(NoJSView *self);
1812+
1813+/* Enumeration */
1814+GType nojs_menu_icon_state_get_type(void) G_GNUC_CONST;
1815+#define NOJS_TYPE_MENU_ICON_STATE (nojs_menu_icon_state_get_type())
1816+
1817+G_END_DECLS
1818+
1819+#endif /* __NOJS_VIEW__ */
1820
1821=== added file 'extensions/nojs/nojs.c'
1822--- extensions/nojs/nojs.c 1970-01-01 00:00:00 +0000
1823+++ extensions/nojs/nojs.c 2013-05-22 23:07:26 +0000
1824@@ -0,0 +1,1078 @@
1825+/*
1826+ Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
1827+
1828+ This library is free software; you can redistribute it and/or
1829+ modify it under the terms of the GNU Lesser General Public
1830+ License as published by the Free Software Foundation; either
1831+ version 2.1 of the License, or (at your option) any later version.
1832+
1833+ See the file COPYING for the full license text.
1834+*/
1835+
1836+#include "nojs.h"
1837+#include "nojs-view.h"
1838+
1839+#include <errno.h>
1840+
1841+/* Define this class in GObject system */
1842+G_DEFINE_TYPE(NoJS,
1843+ nojs,
1844+ G_TYPE_OBJECT)
1845+
1846+/* Properties */
1847+enum
1848+{
1849+ PROP_0,
1850+
1851+ PROP_EXTENSION,
1852+ PROP_APPLICATION,
1853+
1854+ PROP_DATABASE,
1855+ PROP_DATABASE_FILENAME,
1856+ PROP_ALLOW_ALL_SITES,
1857+ PROP_ONLY_SECOND_LEVEL,
1858+ PROP_UNKNOWN_DOMAIN_POLICY,
1859+
1860+ PROP_LAST
1861+};
1862+
1863+static GParamSpec* NoJSProperties[PROP_LAST]={ 0, };
1864+
1865+/* Signals */
1866+enum
1867+{
1868+ URI_LOAD_POLICY_STATUS,
1869+ POLICY_CHANGED,
1870+
1871+ SIGNAL_LAST
1872+};
1873+
1874+static guint NoJSSignals[SIGNAL_LAST]={ 0, };
1875+
1876+/* Private structure - access only by public API if needed */
1877+#define NOJS_GET_PRIVATE(obj) \
1878+ (G_TYPE_INSTANCE_GET_PRIVATE((obj), TYPE_NOJS, NoJSPrivate))
1879+
1880+struct _NoJSPrivate
1881+{
1882+ /* Extension related */
1883+ MidoriExtension *extension;
1884+ MidoriApp *application;
1885+ sqlite3 *database;
1886+ gchar *databaseFilename;
1887+ gboolean allowAllSites;
1888+ gboolean checkOnlySecondLevel;
1889+ NoJSPolicy unknownDomainPolicy;
1890+
1891+ guint requestStartedSignalID;
1892+};
1893+
1894+/* Taken from http://www.w3.org/html/wg/drafts/html/master/scripting-1.html#scriptingLanguages
1895+ * A list of javascript mime types
1896+ */
1897+static const gchar* javascriptTypes[]= {
1898+ "application/ecmascript",
1899+ "application/javascript",
1900+ "application/x-ecmascript",
1901+ "application/x-javascript",
1902+ "text/ecmascript",
1903+ "text/javascript",
1904+ "text/javascript1.0",
1905+ "text/javascript1.1",
1906+ "text/javascript1.2",
1907+ "text/javascript1.3",
1908+ "text/javascript1.4",
1909+ "text/javascript1.5",
1910+ "text/jscript",
1911+ "text/livescript",
1912+ "text/x-ecmascript",
1913+ "text/x-javascript",
1914+ NULL
1915+ };
1916+
1917+/* IMPLEMENTATION: Private variables and methods */
1918+
1919+/* Closure for: void (*closure)(NoJS *self, gchar *inURI, NoJSPolicy inPolicy) */
1920+static void _nojs_closure_VOID__STRING_ENUM(GClosure *inClosure,
1921+ GValue *ioReturnValue G_GNUC_UNUSED,
1922+ guint inNumberValues,
1923+ const GValue *inValues,
1924+ gpointer inInvocationHint G_GNUC_UNUSED,
1925+ gpointer inMarshalData)
1926+{
1927+ typedef void (*GMarshalFunc_VOID__STRING_ENUM)(gpointer inObject, gpointer inArg1, gint inArg2, gpointer inUserData);
1928+
1929+ register GMarshalFunc_VOID__STRING_ENUM callback;
1930+ register GCClosure *closure=(GCClosure*)inClosure;
1931+ register gpointer object, userData;
1932+
1933+ g_return_if_fail(inNumberValues==3);
1934+
1935+ if(G_CCLOSURE_SWAP_DATA(inClosure))
1936+ {
1937+ object=inClosure->data;
1938+ userData=g_value_peek_pointer(inValues+0);
1939+ }
1940+ else
1941+ {
1942+ object=g_value_peek_pointer(inValues+0);
1943+ userData=inClosure->data;
1944+ }
1945+
1946+ callback=(GMarshalFunc_VOID__STRING_ENUM)(inMarshalData ? inMarshalData : closure->callback);
1947+
1948+ callback(object,
1949+ (gchar*)g_value_get_string(inValues+1),
1950+ g_value_get_enum(inValues+2),
1951+ userData);
1952+}
1953+
1954+/* Show common error dialog */
1955+static void _nojs_error(NoJS *self, const gchar *inReason)
1956+{
1957+ g_return_if_fail(IS_NOJS(self));
1958+ g_return_if_fail(inReason);
1959+
1960+ GtkWidget *dialog;
1961+
1962+ /* Show confirmation dialog for undetermined cookies */
1963+ dialog=gtk_message_dialog_new(NULL,
1964+ GTK_DIALOG_MODAL,
1965+ GTK_MESSAGE_ERROR,
1966+ GTK_BUTTONS_OK,
1967+ _("A fatal error occurred which prevents "
1968+ "the NoJS extension to continue. "
1969+ "You should disable it."));
1970+
1971+ gtk_window_set_title(GTK_WINDOW(dialog), _("Error in NoJS extension"));
1972+ gtk_window_set_icon_name(GTK_WINDOW (dialog), "midori");
1973+
1974+ gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1975+ "%s:\n%s",
1976+ _("Reason"),
1977+ inReason);
1978+
1979+ gtk_dialog_run(GTK_DIALOG(dialog));
1980+
1981+ /* Free up allocated resources */
1982+ gtk_widget_destroy(dialog);
1983+}
1984+
1985+/* Open database containing policies for javascript sites.
1986+ * Create database and setup table structure if it does not exist yet.
1987+ */
1988+static void _nojs_open_database(NoJS *self)
1989+{
1990+ g_return_if_fail(IS_NOJS(self));
1991+
1992+ NoJSPrivate *priv=self->priv;
1993+ const gchar *configDir;
1994+ gchar *sql;
1995+ gchar *error=NULL;
1996+ gint success;
1997+
1998+ /* Close any open database */
1999+ if(priv->database)
2000+ {
2001+ priv->databaseFilename=NULL;
2002+
2003+ sqlite3_close(priv->database);
2004+ priv->database=NULL;
2005+
2006+ g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_DATABASE]);
2007+ g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_DATABASE_FILENAME]);
2008+ }
2009+
2010+ /* Build path to database file */
2011+ configDir=midori_extension_get_config_dir(priv->extension);
2012+ if(!configDir)
2013+ {
2014+ g_warning(_("Could not get path to configuration of extension: path is NULL"));
2015+
2016+ _nojs_error(self, _("Could not get path to configuration of extension."));
2017+ return;
2018+ }
2019+
2020+ if(katze_mkdir_with_parents(configDir, 0700))
2021+ {
2022+ g_warning(_("Could not create configuration folder for extension: %s"), g_strerror(errno));
2023+
2024+ _nojs_error(self, _("Could not create configuration folder for extension."));
2025+ return;
2026+ }
2027+
2028+ /* Open database */
2029+ priv->databaseFilename=g_build_filename(configDir, NOJS_DATABASE, NULL);
2030+ success=sqlite3_open(priv->databaseFilename, &priv->database);
2031+ if(success!=SQLITE_OK)
2032+ {
2033+ g_warning(_("Could not open database of extenstion: %s"), sqlite3_errmsg(priv->database));
2034+
2035+ g_free(priv->databaseFilename);
2036+ priv->databaseFilename=NULL;
2037+
2038+ if(priv->database) sqlite3_close(priv->database);
2039+ priv->database=NULL;
2040+
2041+ _nojs_error(self, _("Could not open database of extension."));
2042+ return;
2043+ }
2044+
2045+ /* Create table structure if it does not exist */
2046+ success=sqlite3_exec(priv->database,
2047+ "CREATE TABLE IF NOT EXISTS "
2048+ "policies(site text, value integer);",
2049+ NULL,
2050+ NULL,
2051+ &error);
2052+
2053+ if(success==SQLITE_OK)
2054+ {
2055+ success=sqlite3_exec(priv->database,
2056+ "CREATE UNIQUE INDEX IF NOT EXISTS "
2057+ "site ON policies (site);",
2058+ NULL,
2059+ NULL,
2060+ &error);
2061+ }
2062+
2063+ if(success==SQLITE_OK)
2064+ {
2065+ success=sqlite3_exec(priv->database,
2066+ "PRAGMA journal_mode=TRUNCATE;",
2067+ NULL,
2068+ NULL,
2069+ &error);
2070+ }
2071+
2072+ if(success!=SQLITE_OK || error)
2073+ {
2074+ _nojs_error(self, _("Could not set up database structure of extension."));
2075+
2076+ if(error)
2077+ {
2078+ g_critical(_("Failed to execute database statement: %s"), error);
2079+ sqlite3_free(error);
2080+ }
2081+
2082+ g_free(priv->databaseFilename);
2083+ priv->databaseFilename=NULL;
2084+
2085+ sqlite3_close(priv->database);
2086+ priv->database=NULL;
2087+ return;
2088+ }
2089+
2090+ /* Delete all temporarily allowed sites */
2091+ sql=sqlite3_mprintf("DELETE FROM policies WHERE value=%d;", NOJS_POLICY_ACCEPT_TEMPORARILY);
2092+ success=sqlite3_exec(priv->database, sql, NULL, NULL, &error);
2093+ if(success!=SQLITE_OK) g_warning(_("SQL fails: %s"), error);
2094+ if(error) sqlite3_free(error);
2095+ sqlite3_free(sql);
2096+
2097+ g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_DATABASE]);
2098+ g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_DATABASE_FILENAME]);
2099+}
2100+
2101+/* A request through libsoup is going to start and http headers must be
2102+ * checked for content type
2103+ */
2104+static void _nojs_on_got_headers(NoJS *self, gpointer inUserData)
2105+{
2106+ g_return_if_fail(IS_NOJS(self));
2107+ g_return_if_fail(SOUP_IS_MESSAGE(inUserData));
2108+
2109+ NoJSPrivate *priv=self->priv;
2110+ SoupMessage *message=SOUP_MESSAGE(inUserData);
2111+ SoupSession *session=webkit_get_default_session();
2112+ SoupMessageHeaders *headers;
2113+ SoupMessageBody *body;
2114+ const gchar *contentType;
2115+ SoupURI *uri;
2116+ gchar *uriText;
2117+ gchar *domain;
2118+ NoJSPolicy policy;
2119+ gboolean isJS;
2120+ const gchar **iter;
2121+
2122+ /* Get headers from message to retrieve content type */
2123+ g_object_get(message, "response-headers", &headers, NULL);
2124+ if(!headers)
2125+ {
2126+ g_warning("Could not get headers from message to check for javascript.");
2127+ return;
2128+ }
2129+
2130+ /* Get content type of uri and check if it is a javascript resource */
2131+ contentType=soup_message_headers_get_content_type(headers, NULL);
2132+
2133+ isJS=FALSE;
2134+ iter=javascriptTypes;
2135+ while(*iter && !isJS)
2136+ {
2137+ isJS=(g_strcmp0(contentType, *iter)==0);
2138+ iter++;
2139+ }
2140+
2141+ if(!isJS) return;
2142+
2143+ /* The document being loaded is javascript so get URI from message,
2144+ * get policy for domain of URI and emit signal
2145+ */
2146+ uri=soup_message_get_uri(message);
2147+
2148+ domain=nojs_get_domain(self, uri);
2149+ g_return_if_fail(domain);
2150+
2151+ policy=nojs_get_policy(self, domain);
2152+ if(policy==NOJS_POLICY_UNDETERMINED)
2153+ {
2154+ g_warning("Got invalid policy. Using default policy for unknown domains.");
2155+ policy=priv->unknownDomainPolicy;
2156+ }
2157+
2158+ uriText=soup_uri_to_string(uri, FALSE);
2159+
2160+ g_signal_emit(self, NoJSSignals[URI_LOAD_POLICY_STATUS], 0, uriText, policy==NOJS_POLICY_UNDETERMINED ? NOJS_POLICY_BLOCK : policy);
2161+
2162+ g_free(uriText);
2163+ g_free(domain);
2164+
2165+ /* Return here if policy is any type of accept */
2166+ if(policy!=NOJS_POLICY_UNDETERMINED && policy!=NOJS_POLICY_BLOCK) return;
2167+
2168+ /* Cancel this message */
2169+ soup_session_cancel_message(session, message, SOUP_STATUS_CANCELLED);
2170+
2171+ /* Discard any load data */
2172+ g_object_get(message, "response-body", &body, NULL);
2173+ if(body) soup_message_body_truncate(body);
2174+}
2175+
2176+static void _nojs_on_request_started(NoJS *self,
2177+ SoupMessage *inMessage,
2178+ SoupSocket *inSocket,
2179+ gpointer inUserData)
2180+{
2181+ g_return_if_fail(IS_NOJS(self));
2182+ g_return_if_fail(SOUP_IS_MESSAGE(inMessage));
2183+
2184+ /* Connect to "got-headers" to cancel loading javascript documents early */
2185+ g_signal_connect_swapped(inMessage, "got-headers", G_CALLBACK(_nojs_on_got_headers), self);
2186+}
2187+
2188+/* The icon in statusbar was clicked */
2189+static void _nojs_on_statusbar_icon_clicked(MidoriBrowser *inBrowser, gpointer inUserData)
2190+{
2191+ g_return_if_fail(MIDORI_IS_BROWSER(inBrowser));
2192+
2193+ MidoriView *activeView;
2194+ NoJSView *view;
2195+ GtkMenu *menu;
2196+
2197+ /* Get current active midori view */
2198+ activeView=MIDORI_VIEW(midori_browser_get_current_tab(inBrowser));
2199+ g_return_if_fail(MIDORI_IS_VIEW(activeView));
2200+
2201+ /* Get NoJS view of current active midori view */
2202+ view=NOJS_VIEW(g_object_get_data(G_OBJECT(activeView), "nojs-view-instance"));
2203+ g_return_if_fail(NOJS_IS_VIEW(view));
2204+
2205+ /* Get menu of current view */
2206+ menu=nojs_view_get_menu(view);
2207+ g_return_if_fail(menu);
2208+
2209+ /* Show menu */
2210+ gtk_menu_popup(menu, NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
2211+}
2212+
2213+/* Menu icon of a view has changed */
2214+static void _nojs_on_menu_icon_changed(MidoriBrowser *inBrowser, GParamSpec *inSpec, gpointer inUserData)
2215+{
2216+ g_return_if_fail(MIDORI_IS_BROWSER(inBrowser));
2217+ g_return_if_fail(NOJS_IS_VIEW(inUserData));
2218+
2219+ NoJSView *view=NOJS_VIEW(inUserData);
2220+ NoJSMenuIconState menuIconState;
2221+ GtkWidget *statusbarIcon;
2222+ GtkWidget *buttonImage;
2223+ gchar *imageFilename;
2224+
2225+ /* Get icon in status bar of this browser */
2226+ statusbarIcon=GTK_WIDGET(g_object_get_data(G_OBJECT(inBrowser), "nojs-statusicon"));
2227+ g_return_if_fail(GTK_IS_WIDGET(statusbarIcon));
2228+
2229+ /* Get menu icon state of view */
2230+ menuIconState=nojs_view_get_menu_icon_state(view);
2231+
2232+ /* Create image for statusbar button */
2233+ imageFilename=NULL;
2234+ switch(menuIconState)
2235+ {
2236+#ifdef ALTERNATE_DATADIR
2237+ case NOJS_MENU_ICON_STATE_ALLOWED:
2238+ imageFilename=g_build_filename(ALTERNATE_DATADIR, "nojs-statusicon-allowed.png", NULL);
2239+ break;
2240+
2241+ case NOJS_MENU_ICON_STATE_MIXED:
2242+ imageFilename=g_build_filename(ALTERNATE_DATADIR, "nojs-statusicon-mixed.png", NULL);
2243+ break;
2244+
2245+ case NOJS_MENU_ICON_STATE_DENIED:
2246+ case NOJS_MENU_ICON_STATE_UNDETERMINED:
2247+ imageFilename=g_build_filename(ALTERNATE_DATADIR, "nojs-statusicon-denied.png", NULL);
2248+ break;
2249+#else
2250+ case NOJS_MENU_ICON_STATE_ALLOWED:
2251+ imageFilename=g_build_filename(MDATADIR, PACKAGE_NAME, "nojs", "nojs-statusicon-allowed.png", NULL);
2252+ break;
2253+
2254+ case NOJS_MENU_ICON_STATE_MIXED:
2255+ imageFilename=g_build_filename(MDATADIR, PACKAGE_NAME, "nojs", "nojs-statusicon-mixed.png", NULL);
2256+ break;
2257+
2258+ case NOJS_MENU_ICON_STATE_DENIED:
2259+ case NOJS_MENU_ICON_STATE_UNDETERMINED:
2260+ imageFilename=g_build_filename(MDATADIR, PACKAGE_NAME, "nojs", "nojs-statusicon-denied.png", NULL);
2261+ break;
2262+#endif
2263+ }
2264+
2265+ buttonImage=gtk_image_new_from_file(imageFilename);
2266+ g_free(imageFilename);
2267+
2268+ /* Set image at statusbar button */
2269+ gtk_button_set_image(GTK_BUTTON(statusbarIcon), buttonImage);
2270+}
2271+
2272+/* A tab in browser was activated */
2273+static void _nojs_on_switch_tab(NoJS *self, MidoriView *inOldView, MidoriView *inNewView, gpointer inUserData)
2274+{
2275+ g_return_if_fail(IS_NOJS(self));
2276+ g_return_if_fail(MIDORI_IS_BROWSER(inUserData));
2277+
2278+ MidoriBrowser *browser=MIDORI_BROWSER(inUserData);
2279+ NoJSView *view;
2280+
2281+ /* Disconnect signal handlers from old view */
2282+ if(inOldView)
2283+ {
2284+ /* Get NoJS view of old view */
2285+ view=(NoJSView*)g_object_get_data(G_OBJECT(inOldView), "nojs-view-instance");
2286+ g_return_if_fail(NOJS_IS_VIEW(view));
2287+
2288+ /* Disconnect signal handlers */
2289+ g_signal_handlers_disconnect_by_func(view, G_CALLBACK(_nojs_on_menu_icon_changed), browser);
2290+ }
2291+
2292+ /* Get NoJS view of new view */
2293+ view=(NoJSView*)g_object_get_data(G_OBJECT(inNewView), "nojs-view-instance");
2294+ g_return_if_fail(NOJS_IS_VIEW(view));
2295+
2296+ /* Connect signals */
2297+ g_signal_connect_swapped(view, "notify::menu-icon-state", G_CALLBACK(_nojs_on_menu_icon_changed), browser);
2298+
2299+ /* Update menu icon*/
2300+ _nojs_on_menu_icon_changed(browser, NULL, view);
2301+}
2302+
2303+/* A tab of a browser was removed */
2304+static void _nojs_on_remove_tab(NoJS *self, MidoriView *inView, gpointer inUserData)
2305+{
2306+ g_return_if_fail(IS_NOJS(self));
2307+
2308+ NoJSView *view;
2309+
2310+ /* Get NoJS view of current active midori view */
2311+ view=NOJS_VIEW(g_object_get_data(G_OBJECT(inView), "nojs-view-instance"));
2312+ g_return_if_fail(NOJS_IS_VIEW(view));
2313+
2314+ g_object_unref(view);
2315+}
2316+
2317+/* A tab of a browser was added */
2318+static void _nojs_on_add_tab(NoJS *self, MidoriView *inView, gpointer inUserData)
2319+{
2320+ g_return_if_fail(IS_NOJS(self));
2321+ g_return_if_fail(MIDORI_IS_BROWSER(inUserData));
2322+
2323+ /* Create nojs view and add to tab */
2324+ MidoriBrowser *browser=MIDORI_BROWSER(inUserData);
2325+
2326+ nojs_view_new(self, browser, inView);
2327+}
2328+
2329+/* A browser window was added */
2330+static void _nojs_on_add_browser(NoJS *self, MidoriBrowser *inBrowser, gpointer inUserData)
2331+{
2332+ g_return_if_fail(IS_NOJS(self));
2333+ g_return_if_fail(MIDORI_IS_BROWSER(inBrowser));
2334+
2335+ GList *tabs, *iter;
2336+ GtkWidget *statusbar;
2337+ GtkWidget *statusbarIcon;
2338+ MidoriView *view;
2339+ NoJSView *nojsView;
2340+
2341+ /* Set up all current available tabs in browser */
2342+ tabs=midori_browser_get_tabs(inBrowser);
2343+ for(iter=tabs; iter; iter=g_list_next(iter)) _nojs_on_add_tab(self, iter->data, inBrowser);
2344+ g_list_free(tabs);
2345+
2346+ /* Add status bar icon to browser */
2347+ g_object_get(inBrowser, "statusbar", &statusbar, NULL);
2348+ if(statusbar)
2349+ {
2350+ /* Create and set up status icon */
2351+ statusbarIcon=gtk_button_new();
2352+ gtk_button_set_relief(GTK_BUTTON(statusbarIcon), GTK_RELIEF_NONE);
2353+ gtk_widget_show_all(statusbarIcon);
2354+ gtk_box_pack_end(GTK_BOX(statusbar), statusbarIcon, FALSE, FALSE, 0);
2355+ g_object_set_data_full(G_OBJECT(inBrowser), "nojs-statusicon", g_object_ref(statusbarIcon), (GDestroyNotify)gtk_widget_destroy);
2356+
2357+ /* Connect signals */
2358+ g_signal_connect_swapped(statusbarIcon, "clicked", G_CALLBACK(_nojs_on_statusbar_icon_clicked), inBrowser);
2359+
2360+ /* Release our reference to statusbar and status icon */
2361+ g_object_unref(statusbarIcon);
2362+ g_object_unref(statusbar);
2363+
2364+ /* Update menu icon*/
2365+ view=MIDORI_VIEW(midori_browser_get_current_tab(inBrowser));
2366+ if(view)
2367+ {
2368+ nojsView=(NoJSView*)g_object_get_data(G_OBJECT(view), "nojs-view-instance");
2369+ if(nojsView) _nojs_on_menu_icon_changed(inBrowser, NULL, nojsView);
2370+ }
2371+ }
2372+
2373+ /* Listen to new tabs opened in browser */
2374+ g_signal_connect_swapped(inBrowser, "add-tab", G_CALLBACK(_nojs_on_add_tab), self);
2375+ g_signal_connect_swapped(inBrowser, "switch-tab", G_CALLBACK(_nojs_on_switch_tab), self);
2376+ g_signal_connect_swapped(inBrowser, "remove-tab", G_CALLBACK(_nojs_on_remove_tab), self);
2377+}
2378+
2379+/* Application property has changed */
2380+static void _nojs_on_application_changed(NoJS *self)
2381+{
2382+ g_return_if_fail(IS_NOJS(self));
2383+
2384+ NoJSPrivate *priv=NOJS(self)->priv;
2385+ GList *browsers, *iter;
2386+
2387+ /* Set up all current open browser windows */
2388+ browsers=midori_app_get_browsers(priv->application);
2389+ for(iter=browsers; iter; iter=g_list_next(iter)) _nojs_on_add_browser(self, MIDORI_BROWSER(iter->data), priv->application);
2390+ g_list_free(browsers);
2391+
2392+ /* Listen to new browser windows opened */
2393+ g_signal_connect_swapped(priv->application, "add-browser", G_CALLBACK(_nojs_on_add_browser), self);
2394+
2395+ /* Notify about property change */
2396+ g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_APPLICATION]);
2397+}
2398+
2399+/* IMPLEMENTATION: GObject */
2400+
2401+/* Finalize this object */
2402+static void nojs_finalize(GObject *inObject)
2403+{
2404+ NoJS *self=NOJS(inObject);
2405+ NoJSPrivate *priv=self->priv;
2406+ GList *browsers, *browser;
2407+ GList *tabs, *tab;
2408+ WebKitWebView *webkitView;
2409+ SoupSession *session;
2410+
2411+ /* Dispose allocated resources */
2412+ session=webkit_get_default_session();
2413+ g_signal_handlers_disconnect_by_data(session, self);
2414+
2415+ if(priv->databaseFilename)
2416+ {
2417+ g_free(priv->databaseFilename);
2418+ priv->databaseFilename=NULL;
2419+ }
2420+
2421+ if(priv->database)
2422+ {
2423+ sqlite3_close(priv->database);
2424+ priv->database=NULL;
2425+ }
2426+
2427+ if(priv->application)
2428+ {
2429+ g_signal_handlers_disconnect_by_data(priv->application, self);
2430+
2431+ browsers=midori_app_get_browsers(priv->application);
2432+ for(browser=browsers; browser; browser=g_list_next(browser))
2433+ {
2434+ g_signal_handlers_disconnect_by_data(browser->data, self);
2435+ g_object_set_data(G_OBJECT(browser->data), "nojs-statusicon", NULL);
2436+
2437+ tabs=midori_browser_get_tabs(MIDORI_BROWSER(browser->data));
2438+ for(tab=tabs; tab; tab=g_list_next(tab))
2439+ {
2440+ g_signal_handlers_disconnect_by_data(tab->data, self);
2441+
2442+ webkitView=WEBKIT_WEB_VIEW(midori_view_get_web_view(MIDORI_VIEW(tab->data)));
2443+ g_signal_handlers_disconnect_by_data(webkitView, self);
2444+ }
2445+ g_list_free(tabs);
2446+ }
2447+ g_list_free(browsers);
2448+
2449+ priv->application=NULL;
2450+ }
2451+
2452+ /* Call parent's class finalize method */
2453+ G_OBJECT_CLASS(nojs_parent_class)->finalize(inObject);
2454+}
2455+
2456+/* Set/get properties */
2457+static void nojs_set_property(GObject *inObject,
2458+ guint inPropID,
2459+ const GValue *inValue,
2460+ GParamSpec *inSpec)
2461+{
2462+ NoJS *self=NOJS(inObject);
2463+
2464+ switch(inPropID)
2465+ {
2466+ /* Construct-only properties */
2467+ case PROP_EXTENSION:
2468+ self->priv->extension=g_value_get_object(inValue);
2469+ _nojs_open_database(self);
2470+ break;
2471+
2472+ case PROP_APPLICATION:
2473+ self->priv->application=g_value_get_object(inValue);
2474+ _nojs_on_application_changed(self);
2475+ break;
2476+
2477+ case PROP_ALLOW_ALL_SITES:
2478+ self->priv->allowAllSites=g_value_get_boolean(inValue);
2479+ g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_ALLOW_ALL_SITES]);
2480+ break;
2481+
2482+ case PROP_ONLY_SECOND_LEVEL:
2483+ self->priv->checkOnlySecondLevel=g_value_get_boolean(inValue);
2484+ g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_ONLY_SECOND_LEVEL]);
2485+ break;
2486+
2487+ case PROP_UNKNOWN_DOMAIN_POLICY:
2488+ self->priv->unknownDomainPolicy=g_value_get_enum(inValue);
2489+ g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_UNKNOWN_DOMAIN_POLICY]);
2490+ break;
2491+
2492+ default:
2493+ G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
2494+ break;
2495+ }
2496+}
2497+
2498+static void nojs_get_property(GObject *inObject,
2499+ guint inPropID,
2500+ GValue *outValue,
2501+ GParamSpec *inSpec)
2502+{
2503+ NoJS *self=NOJS(inObject);
2504+
2505+ switch(inPropID)
2506+ {
2507+ case PROP_EXTENSION:
2508+ g_value_set_object(outValue, self->priv->extension);
2509+ break;
2510+
2511+ case PROP_APPLICATION:
2512+ g_value_set_object(outValue, self->priv->application);
2513+ break;
2514+
2515+ case PROP_DATABASE:
2516+ g_value_set_pointer(outValue, self->priv->database);
2517+ break;
2518+
2519+ case PROP_DATABASE_FILENAME:
2520+ g_value_set_string(outValue, self->priv->databaseFilename);
2521+ break;
2522+
2523+ case PROP_ALLOW_ALL_SITES:
2524+ g_value_set_boolean(outValue, self->priv->allowAllSites);
2525+ break;
2526+
2527+ case PROP_ONLY_SECOND_LEVEL:
2528+ g_value_set_boolean(outValue, self->priv->checkOnlySecondLevel);
2529+ break;
2530+
2531+ case PROP_UNKNOWN_DOMAIN_POLICY:
2532+ g_value_set_enum(outValue, self->priv->unknownDomainPolicy);
2533+ break;
2534+
2535+ default:
2536+ G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
2537+ break;
2538+ }
2539+}
2540+
2541+/* Class initialization
2542+ * Override functions in parent classes and define properties and signals
2543+ */
2544+static void nojs_class_init(NoJSClass *klass)
2545+{
2546+ GObjectClass *gobjectClass=G_OBJECT_CLASS(klass);
2547+
2548+ /* Override functions */
2549+ gobjectClass->finalize=nojs_finalize;
2550+ gobjectClass->set_property=nojs_set_property;
2551+ gobjectClass->get_property=nojs_get_property;
2552+
2553+ /* Set up private structure */
2554+ g_type_class_add_private(klass, sizeof(NoJSPrivate));
2555+
2556+ /* Define properties */
2557+ NoJSProperties[PROP_EXTENSION]=
2558+ g_param_spec_object("extension",
2559+ _("Extension instance"),
2560+ _("The Midori extension instance for this extension"),
2561+ MIDORI_TYPE_EXTENSION,
2562+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
2563+
2564+ NoJSProperties[PROP_APPLICATION]=
2565+ g_param_spec_object("application",
2566+ _("Application instance"),
2567+ _("The Midori application instance this extension belongs to"),
2568+ MIDORI_TYPE_APP,
2569+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
2570+
2571+ NoJSProperties[PROP_DATABASE]=
2572+ g_param_spec_pointer("database",
2573+ _("Database instance"),
2574+ _("Pointer to sqlite database instance used by this extension"),
2575+ G_PARAM_READABLE);
2576+
2577+ NoJSProperties[PROP_DATABASE_FILENAME]=
2578+ g_param_spec_string("database-filename",
2579+ _("Database path"),
2580+ _("Path to sqlite database instance used by this extension"),
2581+ NULL,
2582+ G_PARAM_READABLE);
2583+
2584+ NoJSProperties[PROP_ALLOW_ALL_SITES]=
2585+ g_param_spec_boolean("allow-all-sites",
2586+ _("Allow all sites"),
2587+ _("If true this extension will not check policy for each site but allow them."),
2588+ FALSE,
2589+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
2590+
2591+ NoJSProperties[PROP_ONLY_SECOND_LEVEL]=
2592+ g_param_spec_boolean("only-second-level",
2593+ _("Only second level"),
2594+ _("If true this extension will reduce each domain to its second-level (www.example.org will reduced to example.org)"),
2595+ TRUE,
2596+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
2597+
2598+ NoJSProperties[PROP_UNKNOWN_DOMAIN_POLICY]=
2599+ g_param_spec_enum("unknown-domain-policy",
2600+ _("Unknown domain policy"),
2601+ _("Policy to use for unknown domains."),
2602+ NOJS_TYPE_POLICY,
2603+ NOJS_POLICY_BLOCK,
2604+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
2605+
2606+ g_object_class_install_properties(gobjectClass, PROP_LAST, NoJSProperties);
2607+
2608+ /* Define signals */
2609+
2610+ /* Why does this signal exist?
2611+ *
2612+ * The problem I faced when developing this extension was
2613+ * that I needed to cancel a SoupMessage as soon as possible
2614+ * (when http headers were received).
2615+ * I tried to connect to signal "resource-response-received"
2616+ * of WebKitWebView but the SoupMessage instance was not
2617+ * exactly the same which were sent or received by SoupSession.
2618+ * So I could not cancel the SoupMessage or better: I cancelled
2619+ * a SoupMessage which is not be handled so it had no effect.
2620+ * The body of SoupMessage was still being loaded and javascript
2621+ * was executed. I think the problem is that webkit-gtk creates
2622+ * a copy of the real SoupMessage which is going to be sent and
2623+ * received.
2624+ *
2625+ * So I decided to connect to signal "got-headers" of every
2626+ * SoupMessage sent by the default SoupSession which I notice
2627+ * by connecting to signal "request-started" of SoupSession. Each
2628+ * NoJSView connects to signal "resource-request-starting" of
2629+ * WebKitWebView to remember each URI going to be loaded. When
2630+ * a SoupMessage hits "got-headers" and is a javascript resource
2631+ * I can cancel the message immediately and clear the body which
2632+ * causes webkit-gtk to copy a empty body if it does at all as the
2633+ * SoupMessage was cancelled. Then I emit this signal
2634+ * "uri-load-policy-status" to notify each view but the cancellation.
2635+ * (It also notifies all views if it is going to load to keep the
2636+ * menu in right state.) Each view will check if it _could_ be a
2637+ * resource itself requested and will update its menu accordingly.
2638+ * It might happen that a request will match two views because only
2639+ * the URI will be checked by the view because I cannot determine
2640+ * to which view the SoupMessage belongs to. But it doesn't matter
2641+ * because if a javascript resource was denied or allowed in one view
2642+ * it is likely be denied or allowed in other views too ;)
2643+ */
2644+ NoJSSignals[URI_LOAD_POLICY_STATUS]=
2645+ g_signal_new("uri-load-policy-status",
2646+ G_TYPE_FROM_CLASS(klass),
2647+ G_SIGNAL_RUN_LAST,
2648+ G_STRUCT_OFFSET(NoJSClass, uri_load_policy_status),
2649+ NULL,
2650+ NULL,
2651+ _nojs_closure_VOID__STRING_ENUM,
2652+ G_TYPE_NONE,
2653+ 2,
2654+ G_TYPE_STRING,
2655+ NOJS_TYPE_POLICY);
2656+
2657+ NoJSSignals[POLICY_CHANGED]=
2658+ g_signal_new("policy-changed",
2659+ G_TYPE_FROM_CLASS(klass),
2660+ G_SIGNAL_RUN_LAST,
2661+ G_STRUCT_OFFSET(NoJSClass, policy_changed),
2662+ NULL,
2663+ NULL,
2664+ g_cclosure_marshal_VOID__STRING,
2665+ G_TYPE_NONE,
2666+ 1,
2667+ G_TYPE_STRING);
2668+}
2669+
2670+/* Object initialization
2671+ * Create private structure and set up default values
2672+ */
2673+
2674+static void nojs_init(NoJS *self)
2675+{
2676+ NoJSPrivate *priv;
2677+ SoupSession *session;
2678+
2679+ priv=self->priv=NOJS_GET_PRIVATE(self);
2680+
2681+ /* Set up default values */
2682+ priv->database=NULL;
2683+ priv->databaseFilename=NULL;
2684+ priv->allowAllSites=FALSE;
2685+ priv->checkOnlySecondLevel=TRUE;
2686+ priv->unknownDomainPolicy=NOJS_POLICY_BLOCK;
2687+
2688+ /* Connect to signals on session to be able to cancel messages
2689+ * loading javascript documents
2690+ */
2691+ session=webkit_get_default_session();
2692+ g_signal_connect_swapped(session, "request-started", G_CALLBACK(_nojs_on_request_started), self);
2693+}
2694+
2695+/* Implementation: Public API */
2696+
2697+/* Create new object */
2698+NoJS* nojs_new(MidoriExtension *inExtension, MidoriApp *inApp)
2699+{
2700+ return(g_object_new(TYPE_NOJS,
2701+ "extension", inExtension,
2702+ "application", inApp,
2703+ NULL));
2704+}
2705+
2706+/* Retrieves domain from uri depending on preferences (e.g. only second level domain) */
2707+gchar* nojs_get_domain(NoJS *self, SoupURI *inURI)
2708+{
2709+ g_return_val_if_fail(IS_NOJS(self), NULL);
2710+ g_return_val_if_fail(inURI, NULL);
2711+
2712+ NoJSPrivate *priv=self->priv;
2713+ const gchar *realDomain;
2714+ gchar *asciiDomain, *domain;
2715+ gchar *finalDomain;
2716+
2717+ /* Get domain of site to lookup */
2718+ realDomain=soup_uri_get_host(inURI);
2719+
2720+ domain=asciiDomain=g_hostname_to_ascii(realDomain);
2721+
2722+ if(priv->checkOnlySecondLevel)
2723+ {
2724+ /* Only get second level domain if host is not an IP address */
2725+ if(!g_hostname_is_ip_address(asciiDomain))
2726+ {
2727+ gint numberDots=0;
2728+
2729+ domain=asciiDomain+strlen(asciiDomain)-1;
2730+ while(domain>=asciiDomain && numberDots<2)
2731+ {
2732+ if(*domain=='.') numberDots++;
2733+ domain--;
2734+ }
2735+ domain++;
2736+ if(*domain=='.') domain++;
2737+ }
2738+ }
2739+
2740+ /* Create copy for return value */
2741+ if(strlen(domain)>0) finalDomain=g_strdup(domain);
2742+ else finalDomain=NULL;
2743+
2744+ /* Free allocated resources */
2745+ g_free(asciiDomain);
2746+
2747+ /* Return domain */
2748+ return(finalDomain);
2749+}
2750+
2751+/* Get/set policy for javascript from site */
2752+gint nojs_get_policy(NoJS *self, const gchar *inDomain)
2753+{
2754+ g_return_val_if_fail(IS_NOJS(self), NOJS_POLICY_UNDETERMINED);
2755+ g_return_val_if_fail(inDomain, NOJS_POLICY_UNDETERMINED);
2756+
2757+ NoJSPrivate *priv=self->priv;
2758+ sqlite3_stmt *statement=NULL;
2759+ gint error;
2760+ gint policy=NOJS_POLICY_UNDETERMINED;
2761+
2762+ /* Check to allow all sites */
2763+ if(priv->allowAllSites) return(NOJS_POLICY_ACCEPT);
2764+
2765+ /* Check for open database */
2766+ g_return_val_if_fail(priv->database, policy);
2767+
2768+ /* Lookup policy for site in database */
2769+ error=sqlite3_prepare_v2(priv->database,
2770+ "SELECT site, value FROM policies WHERE site LIKE ? LIMIT 1;",
2771+ -1,
2772+ &statement,
2773+ NULL);
2774+ if(statement && error==SQLITE_OK) error=sqlite3_bind_text(statement, 1, inDomain, -1, NULL);
2775+ if(statement && error==SQLITE_OK)
2776+ {
2777+ if(sqlite3_step(statement)==SQLITE_ROW) policy=sqlite3_column_int(statement, 1);
2778+ }
2779+ else g_warning(_("SQL fails: %s"), sqlite3_errmsg(priv->database));
2780+
2781+ sqlite3_finalize(statement);
2782+
2783+ /* If we have not found a policy for the domain then it is an unknown domain.
2784+ * Get default policy for unknown domains.
2785+ */
2786+ if(policy==NOJS_POLICY_UNDETERMINED) policy=priv->unknownDomainPolicy;
2787+
2788+ return(policy);
2789+}
2790+
2791+void nojs_set_policy(NoJS *self, const gchar *inDomain, NoJSPolicy inPolicy)
2792+{
2793+ g_return_if_fail(IS_NOJS(self));
2794+ g_return_if_fail(inDomain);
2795+ g_return_if_fail(inPolicy>=NOJS_POLICY_ACCEPT && inPolicy<=NOJS_POLICY_BLOCK);
2796+
2797+ NoJSPrivate *priv=self->priv;
2798+ gchar *sql;
2799+ gchar *error=NULL;
2800+ gint success;
2801+
2802+ /* Check for open database */
2803+ g_return_if_fail(priv->database);
2804+
2805+ /* Update policy in database */
2806+ sql=sqlite3_mprintf("INSERT OR REPLACE INTO policies (site, value) VALUES ('%q', %d);",
2807+ inDomain,
2808+ inPolicy);
2809+ success=sqlite3_exec(priv->database, sql, NULL, NULL, &error);
2810+ if(success!=SQLITE_OK) g_warning(_("SQL fails: %s"), error);
2811+ if(error) sqlite3_free(error);
2812+ sqlite3_free(sql);
2813+
2814+ /* Emit signal to notify about policy change */
2815+ if(success==SQLITE_OK) g_signal_emit(self, NoJSSignals[POLICY_CHANGED], 0, inDomain);
2816+}
2817+
2818+/* Get/set default policy for unknown domains */
2819+NoJSPolicy nojs_get_policy_for_unknown_domain(NoJS *self)
2820+{
2821+ g_return_val_if_fail(IS_NOJS(self), NOJS_POLICY_UNDETERMINED);
2822+
2823+ return(self->priv->unknownDomainPolicy);
2824+}
2825+
2826+void nojs_set_policy_for_unknown_domain(NoJS *self, NoJSPolicy inPolicy)
2827+{
2828+ g_return_if_fail(IS_NOJS(self));
2829+ g_return_if_fail(inPolicy>=NOJS_POLICY_ACCEPT && inPolicy<=NOJS_POLICY_BLOCK);
2830+
2831+ if(self->priv->unknownDomainPolicy!=inPolicy)
2832+ {
2833+ self->priv->unknownDomainPolicy=inPolicy;
2834+ midori_extension_set_integer(self->priv->extension, "unknown-domain-policy", inPolicy);
2835+ g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_UNKNOWN_DOMAIN_POLICY]);
2836+ }
2837+}
2838+
2839+/* Get/set flag to allow javascript at all sites */
2840+gboolean nojs_get_allow_all_sites(NoJS *self)
2841+{
2842+ g_return_val_if_fail(IS_NOJS(self), TRUE);
2843+
2844+ return(self->priv->allowAllSites);
2845+}
2846+
2847+void nojs_set_allow_all_sites(NoJS *self, gboolean inAllow)
2848+{
2849+ g_return_if_fail(IS_NOJS(self));
2850+
2851+ if(self->priv->allowAllSites!=inAllow)
2852+ {
2853+ self->priv->allowAllSites=inAllow;
2854+ midori_extension_set_boolean(self->priv->extension, "allow-all-sites", inAllow);
2855+ g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_ALLOW_ALL_SITES]);
2856+ }
2857+}
2858+
2859+/* Get/set flag to check for second-level domanins only */
2860+gboolean nojs_get_only_second_level_domain(NoJS *self)
2861+{
2862+ g_return_val_if_fail(IS_NOJS(self), TRUE);
2863+
2864+ return(self->priv->checkOnlySecondLevel);
2865+}
2866+
2867+void nojs_set_only_second_level_domain(NoJS *self, gboolean inOnlySecondLevel)
2868+{
2869+ g_return_if_fail(IS_NOJS(self));
2870+
2871+ if(self->priv->checkOnlySecondLevel!=inOnlySecondLevel)
2872+ {
2873+ self->priv->checkOnlySecondLevel=inOnlySecondLevel;
2874+ midori_extension_set_boolean(self->priv->extension, "only-second-level", inOnlySecondLevel);
2875+ g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_ONLY_SECOND_LEVEL]);
2876+ }
2877+}
2878+
2879+/************************************************************************************/
2880+
2881+/* Implementation: Enumeration */
2882+GType nojs_policy_get_type(void)
2883+{
2884+ static volatile gsize g_define_type_id__volatile=0;
2885+
2886+ if(g_once_init_enter(&g_define_type_id__volatile))
2887+ {
2888+ static const GEnumValue values[]=
2889+ {
2890+ { NOJS_POLICY_UNDETERMINED, "NOJS_POLICY_UNDETERMINED", N_("Undetermined") },
2891+ { NOJS_POLICY_ACCEPT, "NOJS_POLICY_ACCEPT", N_("Accept") },
2892+ { NOJS_POLICY_ACCEPT_TEMPORARILY, "NOJS_POLICY_ACCEPT_TEMPORARILY", N_("Accept temporarily") },
2893+ { NOJS_POLICY_BLOCK, "NOJS_POLICY_BLOCK", N_("Block") },
2894+ { 0, NULL, NULL }
2895+ };
2896+
2897+ GType g_define_type_id=g_enum_register_static(g_intern_static_string("NoJSPolicy"), values);
2898+ g_once_init_leave(&g_define_type_id__volatile, g_define_type_id);
2899+ }
2900+
2901+ return(g_define_type_id__volatile);
2902+}
2903
2904=== added file 'extensions/nojs/nojs.h'
2905--- extensions/nojs/nojs.h 1970-01-01 00:00:00 +0000
2906+++ extensions/nojs/nojs.h 2013-05-22 23:07:26 +0000
2907@@ -0,0 +1,87 @@
2908+/*
2909+ Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
2910+
2911+ This library is free software; you can redistribute it and/or
2912+ modify it under the terms of the GNU Lesser General Public
2913+ License as published by the Free Software Foundation; either
2914+ version 2.1 of the License, or (at your option) any later version.
2915+
2916+ See the file COPYING for the full license text.
2917+*/
2918+
2919+#ifndef __NOJS__
2920+#define __NOJS__
2921+
2922+#include "config.h"
2923+#include <midori/midori.h>
2924+
2925+#define NOJS_DATABASE "nojs.db"
2926+
2927+G_BEGIN_DECLS
2928+
2929+/* NoJS manager enums */
2930+typedef enum
2931+{
2932+ NOJS_POLICY_UNDETERMINED,
2933+ NOJS_POLICY_ACCEPT,
2934+ NOJS_POLICY_ACCEPT_TEMPORARILY,
2935+ NOJS_POLICY_BLOCK
2936+} NoJSPolicy;
2937+
2938+/* NoJS manager object */
2939+#define TYPE_NOJS (nojs_get_type())
2940+#define NOJS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_NOJS, NoJS))
2941+#define IS_NOJS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_NOJS))
2942+#define NOJS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_NOJS, NoJSClass))
2943+#define IS_NOJS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_NOJS))
2944+#define NOJS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_NOJS, NoJSClass))
2945+
2946+typedef struct _NoJS NoJS;
2947+typedef struct _NoJSClass NoJSClass;
2948+typedef struct _NoJSPrivate NoJSPrivate;
2949+
2950+struct _NoJS
2951+{
2952+ /* Parent instance */
2953+ GObject parent_instance;
2954+
2955+ /* Private structure */
2956+ NoJSPrivate *priv;
2957+};
2958+
2959+struct _NoJSClass
2960+{
2961+ /* Parent class */
2962+ GObjectClass parent_class;
2963+
2964+ /* Virtual functions */
2965+ void (*uri_load_policy_status)(NoJS *self, gchar *inURI, NoJSPolicy inPolicy);
2966+ void (*policy_changed)(NoJS *self, gchar *inDomain);
2967+};
2968+
2969+/* Public API */
2970+GType nojs_get_type(void);
2971+
2972+NoJS* nojs_new(MidoriExtension *inExtension, MidoriApp *inApp);
2973+
2974+gchar* nojs_get_domain(NoJS *self, SoupURI *inURI);
2975+
2976+gint nojs_get_policy(NoJS *self, const gchar *inDomain);
2977+void nojs_set_policy(NoJS *self, const gchar *inDomain, NoJSPolicy inPolicy);
2978+
2979+NoJSPolicy nojs_get_policy_for_unknown_domain(NoJS *self);
2980+void nojs_set_policy_for_unknown_domain(NoJS *self, NoJSPolicy inPolicy);
2981+
2982+gboolean nojs_get_allow_all_sites(NoJS *self);
2983+void nojs_set_allow_all_sites(NoJS *self, gboolean inAllow);
2984+
2985+gboolean nojs_get_only_second_level_domain(NoJS *self);
2986+void nojs_set_only_second_level_domain(NoJS *self, gboolean inOnlySecondLevel);
2987+
2988+/* Enumeration */
2989+GType nojs_policy_get_type(void) G_GNUC_CONST;
2990+#define NOJS_TYPE_POLICY (nojs_policy_get_type())
2991+
2992+G_END_DECLS
2993+
2994+#endif /* __NOJS__ */

Subscribers

People subscribed via source and target branches

to all changes: