a9a3c1efdf3a9d8425fcd68b3bab2556b42e1d2e
[WebKit-https.git] / Tools / MiniBrowser / gtk / main.c
1 /*
2  * Copyright (C) 2006, 2007 Apple Inc.
3  * Copyright (C) 2007 Alp Toker <alp@atoker.com>
4  * Copyright (C) 2011 Igalia S.L.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "cmakeconfig.h"
29
30 #include "BrowserWindow.h"
31 #include <errno.h>
32 #if ENABLE_WEB_AUDIO || ENABLE_VIDEO
33 #include <gst/gst.h>
34 #endif
35 #include <gtk/gtk.h>
36 #include <string.h>
37 #include <webkit2/webkit2.h>
38
39 #define MINI_BROWSER_ERROR (miniBrowserErrorQuark())
40
41 static const gchar **uriArguments = NULL;
42 static const gchar **ignoreHosts = NULL;
43 static GdkRGBA *backgroundColor;
44 static gboolean editorMode;
45 static const char *sessionFile;
46 static char *geometry;
47 static gboolean privateMode;
48 static gboolean automationMode;
49 static gboolean fullScreen;
50 static gboolean ignoreTLSErrors;
51 static const char *contentFilter;
52 static const char *cookiesFile;
53 static const char *cookiesPolicy;
54 static const char *proxy;
55
56 typedef enum {
57     MINI_BROWSER_ERROR_INVALID_ABOUT_PATH
58 } MiniBrowserError;
59
60 static GQuark miniBrowserErrorQuark()
61 {
62     return g_quark_from_string("minibrowser-quark");
63 }
64
65 static gchar *argumentToURL(const char *filename)
66 {
67     GFile *gfile = g_file_new_for_commandline_arg(filename);
68     gchar *fileURL = g_file_get_uri(gfile);
69     g_object_unref(gfile);
70
71     return fileURL;
72 }
73
74 static WebKitWebView *createBrowserTab(BrowserWindow *window, WebKitSettings *webkitSettings, WebKitUserContentManager *userContentManager)
75 {
76     WebKitWebView *webView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
77         "web-context", browser_window_get_web_context(window),
78         "settings", webkitSettings,
79         "user-content-manager", userContentManager,
80         "is-controlled-by-automation", automationMode,
81         NULL));
82
83     if (editorMode)
84         webkit_web_view_set_editable(webView, TRUE);
85
86     browser_window_append_view(window, webView);
87     return webView;
88 }
89
90 static gboolean parseBackgroundColor(const char *optionName, const char *value, gpointer data, GError **error)
91 {
92     GdkRGBA rgba;
93     if (gdk_rgba_parse(&rgba, value)) {
94         backgroundColor = gdk_rgba_copy(&rgba);
95         return TRUE;
96     }
97
98     g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Failed to parse '%s' as RGBA color", value);
99     return FALSE;
100 }
101
102 static const GOptionEntry commandLineOptions[] =
103 {
104     { "bg-color", 0, 0, G_OPTION_ARG_CALLBACK, parseBackgroundColor, "Background color", NULL },
105     { "editor-mode", 'e', 0, G_OPTION_ARG_NONE, &editorMode, "Run in editor mode", NULL },
106     { "session-file", 's', 0, G_OPTION_ARG_FILENAME, &sessionFile, "Session file", "FILE" },
107     { "geometry", 'g', 0, G_OPTION_ARG_STRING, &geometry, "Set the size and position of the window (WIDTHxHEIGHT+X+Y)", "GEOMETRY" },
108     { "full-screen", 'f', 0, G_OPTION_ARG_NONE, &fullScreen, "Set the window to full-screen mode", NULL },
109     { "private", 'p', 0, G_OPTION_ARG_NONE, &privateMode, "Run in private browsing mode", NULL },
110     { "automation", 0, 0, G_OPTION_ARG_NONE, &automationMode, "Run in automation mode", NULL },
111     { "cookies-file", 'c', 0, G_OPTION_ARG_FILENAME, &cookiesFile, "Persistent cookie storage database file", "FILE" },
112     { "cookies-policy", 0, 0, G_OPTION_ARG_STRING, &cookiesPolicy, "Cookies accept policy (always, never, no-third-party). Default: no-third-party", "POLICY" },
113     { "proxy", 0, 0, G_OPTION_ARG_STRING, &proxy, "Set proxy", "PROXY" },
114     { "ignore-host", 0, 0, G_OPTION_ARG_STRING_ARRAY, &ignoreHosts, "Set proxy ignore hosts", "HOSTS" },
115     { "ignore-tls-errors", 0, 0, G_OPTION_ARG_NONE, &ignoreTLSErrors, "Ignore TLS errors", NULL },
116     { "content-filter", 0, 0, G_OPTION_ARG_FILENAME, &contentFilter, "JSON with content filtering rules", "FILE" },
117     { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &uriArguments, 0, "[URL…]" },
118     { 0, 0, 0, 0, 0, 0, 0 }
119 };
120
121 static gboolean parseOptionEntryCallback(const gchar *optionNameFull, const gchar *value, WebKitSettings *webSettings, GError **error)
122 {
123     if (strlen(optionNameFull) <= 2) {
124         g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Invalid option %s", optionNameFull);
125         return FALSE;
126     }
127
128     /* We have two -- in option name so remove them. */
129     const gchar *optionName = optionNameFull + 2;
130     GParamSpec *spec = g_object_class_find_property(G_OBJECT_GET_CLASS(webSettings), optionName);
131     if (!spec) {
132         g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Cannot find web settings for option %s", optionNameFull);
133         return FALSE;
134     }
135
136     switch (G_PARAM_SPEC_VALUE_TYPE(spec)) {
137     case G_TYPE_BOOLEAN: {
138         gboolean propertyValue = !(value && g_ascii_strcasecmp(value, "true") && strcmp(value, "1"));
139         g_object_set(G_OBJECT(webSettings), optionName, propertyValue, NULL);
140         break;
141     }
142     case G_TYPE_STRING:
143         g_object_set(G_OBJECT(webSettings), optionName, value, NULL);
144         break;
145     case G_TYPE_INT: {
146         glong propertyValue;
147         gchar *end;
148
149         errno = 0;
150         propertyValue = g_ascii_strtoll(value, &end, 0);
151         if (errno == ERANGE || propertyValue > G_MAXINT || propertyValue < G_MININT) {
152             g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Integer value '%s' for %s out of range", value, optionNameFull);
153             return FALSE;
154         }
155         if (errno || value == end) {
156             g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Cannot parse integer value '%s' for %s", value, optionNameFull);
157             return FALSE;
158         }
159         g_object_set(G_OBJECT(webSettings), optionName, propertyValue, NULL);
160         break;
161     }
162     case G_TYPE_FLOAT: {
163         gdouble propertyValue;
164         gchar *end;
165
166         errno = 0;
167         propertyValue = g_ascii_strtod(value, &end);
168         if (errno == ERANGE || propertyValue > G_MAXFLOAT || propertyValue < G_MINFLOAT) {
169             g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Float value '%s' for %s out of range", value, optionNameFull);
170             return FALSE;
171         }
172         if (errno || value == end) {
173             g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Cannot parse float value '%s' for %s", value, optionNameFull);
174             return FALSE;
175         }
176         g_object_set(G_OBJECT(webSettings), optionName, propertyValue, NULL);
177         break;
178     }
179     default:
180         g_assert_not_reached();
181     }
182
183     return TRUE;
184 }
185
186 static gboolean isValidParameterType(GType gParamType)
187 {
188     return (gParamType == G_TYPE_BOOLEAN || gParamType == G_TYPE_STRING || gParamType == G_TYPE_INT
189             || gParamType == G_TYPE_FLOAT);
190 }
191
192 static GOptionEntry* getOptionEntriesFromWebKitSettings(WebKitSettings *webSettings)
193 {
194     GParamSpec **propertySpecs;
195     GOptionEntry *optionEntries;
196     guint numProperties, numEntries, i;
197
198     propertySpecs = g_object_class_list_properties(G_OBJECT_GET_CLASS(webSettings), &numProperties);
199     if (!propertySpecs)
200         return NULL;
201
202     optionEntries = g_new0(GOptionEntry, numProperties + 1);
203     numEntries = 0;
204     for (i = 0; i < numProperties; i++) {
205         GParamSpec *param = propertySpecs[i];
206
207         /* Fill in structures only for writable and not construct-only properties. */
208         if (!param || !(param->flags & G_PARAM_WRITABLE) || (param->flags & G_PARAM_CONSTRUCT_ONLY))
209             continue;
210
211         GType gParamType = G_PARAM_SPEC_VALUE_TYPE(param);
212         if (!isValidParameterType(gParamType))
213             continue;
214
215         GOptionEntry *optionEntry = &optionEntries[numEntries++];
216         optionEntry->long_name = g_param_spec_get_name(param);
217
218         /* There is no easy way to figure our short name for generated option entries.
219            optionEntry.short_name=*/
220         /* For bool arguments "enable" type make option argument not required. */
221         if (gParamType == G_TYPE_BOOLEAN && (strstr(optionEntry->long_name, "enable")))
222             optionEntry->flags = G_OPTION_FLAG_OPTIONAL_ARG;
223         optionEntry->arg = G_OPTION_ARG_CALLBACK;
224         optionEntry->arg_data = parseOptionEntryCallback;
225         optionEntry->description = g_param_spec_get_blurb(param);
226         optionEntry->arg_description = g_type_name(gParamType);
227     }
228     g_free(propertySpecs);
229
230     return optionEntries;
231 }
232
233 static gboolean addSettingsGroupToContext(GOptionContext *context, WebKitSettings* webkitSettings)
234 {
235     GOptionEntry *optionEntries = getOptionEntriesFromWebKitSettings(webkitSettings);
236     if (!optionEntries)
237         return FALSE;
238
239     GOptionGroup *webSettingsGroup = g_option_group_new("websettings",
240                                                         "WebKitSettings writable properties for default WebKitWebView",
241                                                         "WebKitSettings properties",
242                                                         webkitSettings,
243                                                         NULL);
244     g_option_group_add_entries(webSettingsGroup, optionEntries);
245     g_free(optionEntries);
246
247     /* Option context takes ownership of the group. */
248     g_option_context_add_group(context, webSettingsGroup);
249
250     return TRUE;
251 }
252
253 typedef struct {
254     WebKitURISchemeRequest *request;
255     GList *dataList;
256     GHashTable *dataMap;
257 } AboutDataRequest;
258
259 static GHashTable *aboutDataRequestMap;
260
261 static void aboutDataRequestFree(AboutDataRequest *request)
262 {
263     if (!request)
264         return;
265
266     g_list_free_full(request->dataList, g_object_unref);
267
268     if (request->request)
269         g_object_unref(request->request);
270     if (request->dataMap)
271         g_hash_table_destroy(request->dataMap);
272
273     g_free(request);
274 }
275
276 static AboutDataRequest* aboutDataRequestNew(WebKitURISchemeRequest *uriRequest)
277 {
278     if (!aboutDataRequestMap)
279         aboutDataRequestMap = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)aboutDataRequestFree);
280
281     AboutDataRequest *request = g_new0(AboutDataRequest, 1);
282     request->request = g_object_ref(uriRequest);
283     g_hash_table_insert(aboutDataRequestMap, GUINT_TO_POINTER(webkit_web_view_get_page_id(webkit_uri_scheme_request_get_web_view(request->request))), request);
284
285     return request;
286 }
287
288 static AboutDataRequest *aboutDataRequestForView(guint64 pageID)
289 {
290     return aboutDataRequestMap ? g_hash_table_lookup(aboutDataRequestMap, GUINT_TO_POINTER(pageID)) : NULL;
291 }
292
293 static void websiteDataRemovedCallback(WebKitWebsiteDataManager *manager, GAsyncResult *result, AboutDataRequest *dataRequest)
294 {
295     if (webkit_website_data_manager_remove_finish(manager, result, NULL))
296         webkit_web_view_reload(webkit_uri_scheme_request_get_web_view(dataRequest->request));
297 }
298
299 static void websiteDataClearedCallback(WebKitWebsiteDataManager *manager, GAsyncResult *result, AboutDataRequest *dataRequest)
300 {
301     if (webkit_website_data_manager_clear_finish(manager, result, NULL))
302         webkit_web_view_reload(webkit_uri_scheme_request_get_web_view(dataRequest->request));
303 }
304
305 static void aboutDataScriptMessageReceivedCallback(WebKitUserContentManager *userContentManager, WebKitJavascriptResult *message, WebKitWebContext *webContext)
306 {
307     char *messageString = jsc_value_to_string(webkit_javascript_result_get_js_value(message));
308     char **tokens = g_strsplit(messageString, ":", 3);
309     g_free(messageString);
310
311     unsigned tokenCount = g_strv_length(tokens);
312     if (!tokens || tokenCount < 2) {
313         g_strfreev(tokens);
314         return;
315     }
316
317     guint64 pageID = g_ascii_strtoull(tokens[0], NULL, 10);
318     AboutDataRequest *dataRequest = aboutDataRequestForView(pageID);
319     if (!dataRequest) {
320         g_strfreev(tokens);
321         return;
322     }
323
324     WebKitWebsiteDataManager *manager = webkit_web_context_get_website_data_manager(webContext);
325     guint64 types = g_ascii_strtoull(tokens[1], NULL, 10);
326     if (tokenCount == 2)
327         webkit_website_data_manager_clear(manager, types, 0, NULL, (GAsyncReadyCallback)websiteDataClearedCallback, dataRequest);
328     else {
329         guint64 domainID = g_ascii_strtoull(tokens[2], NULL, 10);
330         GList *dataList = g_hash_table_lookup(dataRequest->dataMap, GUINT_TO_POINTER(types));
331         WebKitWebsiteData *data = g_list_nth_data(dataList, domainID);
332         if (data) {
333             GList dataList = { data, NULL, NULL };
334             webkit_website_data_manager_remove(manager, types, &dataList, NULL, (GAsyncReadyCallback)websiteDataRemovedCallback, dataRequest);
335         }
336     }
337     g_strfreev(tokens);
338 }
339
340 static void domainListFree(GList *domains)
341 {
342     g_list_free_full(domains, (GDestroyNotify)webkit_website_data_unref);
343 }
344
345 static void aboutDataFillTable(GString *result, AboutDataRequest *dataRequest, GList* dataList, const char *title, WebKitWebsiteDataTypes types, const char *dataPath, guint64 pageID)
346 {
347     guint64 totalDataSize = 0;
348     GList *domains = NULL;
349     GList *l;
350     for (l = dataList; l; l = g_list_next(l)) {
351         WebKitWebsiteData *data = (WebKitWebsiteData *)l->data;
352
353         if (webkit_website_data_get_types(data) & types) {
354             domains = g_list_prepend(domains, webkit_website_data_ref(data));
355             totalDataSize += webkit_website_data_get_size(data, types);
356         }
357     }
358     if (!domains)
359         return;
360
361     if (!dataRequest->dataMap)
362         dataRequest->dataMap = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)domainListFree);
363     g_hash_table_insert(dataRequest->dataMap, GUINT_TO_POINTER(types), domains);
364
365     if (totalDataSize) {
366         char *totalDataSizeStr = g_format_size(totalDataSize);
367         g_string_append_printf(result, "<h1>%s (%s)</h1>\n<table>\n", title, totalDataSizeStr);
368         g_free(totalDataSizeStr);
369     } else
370         g_string_append_printf(result, "<h1>%s</h1>\n<table>\n", title);
371     if (dataPath)
372         g_string_append_printf(result, "<tr><td colspan=\"2\">Path: %s</td></tr>\n", dataPath);
373
374     unsigned index;
375     for (l = domains, index = 0; l; l = g_list_next(l), index++) {
376         WebKitWebsiteData *data = (WebKitWebsiteData *)l->data;
377         const char *displayName = webkit_website_data_get_name(data);
378         guint64 dataSize = webkit_website_data_get_size(data, types);
379         if (dataSize) {
380             char *dataSizeStr = g_format_size(dataSize);
381             g_string_append_printf(result, "<tr><td>%s (%s)</td>", displayName, dataSizeStr);
382             g_free(dataSizeStr);
383         } else
384             g_string_append_printf(result, "<tr><td>%s</td>", displayName);
385         g_string_append_printf(result, "<td><input type=\"button\" value=\"Remove\" onclick=\"removeData('%"G_GUINT64_FORMAT":%u:%u');\"></td></tr>\n",
386             pageID, types, index);
387     }
388     g_string_append_printf(result, "<tr><td><input type=\"button\" value=\"Clear all\" onclick=\"clearData('%"G_GUINT64_FORMAT":%u');\"></td></tr></table>\n",
389         pageID, types);
390 }
391
392 static void gotWebsiteDataCallback(WebKitWebsiteDataManager *manager, GAsyncResult *asyncResult, AboutDataRequest *dataRequest)
393 {
394     GList *dataList = webkit_website_data_manager_fetch_finish(manager, asyncResult, NULL);
395
396     GString *result = g_string_new(
397         "<html><head>"
398         "<script>"
399         "  function removeData(domain) {"
400         "    window.webkit.messageHandlers.aboutData.postMessage(domain);"
401         "  }"
402         "  function clearData(dataType) {"
403         "    window.webkit.messageHandlers.aboutData.postMessage(dataType);"
404         "  }"
405         "</script></head><body>\n");
406
407     guint64 pageID = webkit_web_view_get_page_id(webkit_uri_scheme_request_get_web_view(dataRequest->request));
408     aboutDataFillTable(result, dataRequest, dataList, "Cookies", WEBKIT_WEBSITE_DATA_COOKIES, NULL, pageID);
409     aboutDataFillTable(result, dataRequest, dataList, "Device Id Hash Salt", WEBKIT_WEBSITE_DATA_DEVICE_ID_HASH_SALT, NULL, pageID);
410     aboutDataFillTable(result, dataRequest, dataList, "Memory Cache", WEBKIT_WEBSITE_DATA_MEMORY_CACHE, NULL, pageID);
411     aboutDataFillTable(result, dataRequest, dataList, "Disk Cache", WEBKIT_WEBSITE_DATA_DISK_CACHE, webkit_website_data_manager_get_disk_cache_directory(manager), pageID);
412     aboutDataFillTable(result, dataRequest, dataList, "Session Storage", WEBKIT_WEBSITE_DATA_SESSION_STORAGE, NULL, pageID);
413     aboutDataFillTable(result, dataRequest, dataList, "Local Storage", WEBKIT_WEBSITE_DATA_LOCAL_STORAGE, webkit_website_data_manager_get_local_storage_directory(manager), pageID);
414     aboutDataFillTable(result, dataRequest, dataList, "WebSQL Databases", WEBKIT_WEBSITE_DATA_WEBSQL_DATABASES, webkit_website_data_manager_get_websql_directory(manager), pageID);
415     aboutDataFillTable(result, dataRequest, dataList, "IndexedDB Databases", WEBKIT_WEBSITE_DATA_INDEXEDDB_DATABASES, webkit_website_data_manager_get_indexeddb_directory(manager), pageID);
416     aboutDataFillTable(result, dataRequest, dataList, "Plugins Data", WEBKIT_WEBSITE_DATA_PLUGIN_DATA, NULL, pageID);
417     aboutDataFillTable(result, dataRequest, dataList, "Offline Web Applications Cache", WEBKIT_WEBSITE_DATA_OFFLINE_APPLICATION_CACHE, webkit_website_data_manager_get_offline_application_cache_directory(manager), pageID);
418
419     result = g_string_append(result, "</body></html>");
420     gsize streamLength = result->len;
421     GInputStream *stream = g_memory_input_stream_new_from_data(g_string_free(result, FALSE), streamLength, g_free);
422     webkit_uri_scheme_request_finish(dataRequest->request, stream, streamLength, "text/html");
423     g_list_free_full(dataList, (GDestroyNotify)webkit_website_data_unref);
424 }
425
426 static void aboutDataHandleRequest(WebKitURISchemeRequest *request, WebKitWebContext *webContext)
427 {
428     AboutDataRequest *dataRequest = aboutDataRequestNew(request);
429     WebKitWebsiteDataManager *manager = webkit_web_context_get_website_data_manager(webContext);
430     webkit_website_data_manager_fetch(manager, WEBKIT_WEBSITE_DATA_ALL, NULL, (GAsyncReadyCallback)gotWebsiteDataCallback, dataRequest);
431 }
432
433 static void aboutURISchemeRequestCallback(WebKitURISchemeRequest *request, WebKitWebContext *webContext)
434 {
435     GInputStream *stream;
436     gsize streamLength;
437     const gchar *path;
438     gchar *contents;
439     GError *error;
440
441     path = webkit_uri_scheme_request_get_path(request);
442     if (!g_strcmp0(path, "minibrowser")) {
443         contents = g_strdup_printf("<html><body><h1>WebKitGTK+ MiniBrowser</h1><p>The WebKit2 test browser of the GTK+ port.</p><p>WebKit version: %d.%d.%d</p></body></html>",
444             webkit_get_major_version(),
445             webkit_get_minor_version(),
446             webkit_get_micro_version());
447         streamLength = strlen(contents);
448         stream = g_memory_input_stream_new_from_data(contents, streamLength, g_free);
449
450         webkit_uri_scheme_request_finish(request, stream, streamLength, "text/html");
451         g_object_unref(stream);
452     } else if (!g_strcmp0(path, "data"))
453         aboutDataHandleRequest(request, webContext);
454     else {
455         error = g_error_new(MINI_BROWSER_ERROR, MINI_BROWSER_ERROR_INVALID_ABOUT_PATH, "Invalid about:%s page.", path);
456         webkit_uri_scheme_request_finish_error(request, error);
457         g_error_free(error);
458     }
459 }
460
461 static GtkWidget *createWebViewForAutomationCallback(WebKitAutomationSession* session)
462 {
463     return GTK_WIDGET(browser_window_get_or_create_web_view_for_automation());
464 }
465
466 static void automationStartedCallback(WebKitWebContext *webContext, WebKitAutomationSession *session)
467 {
468     WebKitApplicationInfo *info = webkit_application_info_new();
469     webkit_application_info_set_version(info, WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION);
470     webkit_automation_session_set_application_info(session, info);
471     webkit_application_info_unref(info);
472
473     g_signal_connect(session, "create-web-view", G_CALLBACK(createWebViewForAutomationCallback), NULL);
474 }
475
476 typedef struct {
477     GMainLoop *mainLoop;
478     WebKitUserContentFilter *filter;
479     GError *error;
480 } FilterSaveData;
481
482 static void filterSavedCallback(WebKitUserContentFilterStore *store, GAsyncResult *result, FilterSaveData *data)
483 {
484     data->filter = webkit_user_content_filter_store_save_finish(store, result, &data->error);
485     g_main_loop_quit(data->mainLoop);
486 }
487
488 int main(int argc, char *argv[])
489 {
490 #if ENABLE_DEVELOPER_MODE
491     g_setenv("WEBKIT_INJECTED_BUNDLE_PATH", WEBKIT_INJECTED_BUNDLE_PATH, FALSE);
492 #endif
493
494     gtk_init(&argc, &argv);
495
496     GOptionContext *context = g_option_context_new(NULL);
497     g_option_context_add_main_entries(context, commandLineOptions, 0);
498     g_option_context_add_group(context, gtk_get_option_group(TRUE));
499 #if ENABLE_WEB_AUDIO || ENABLE_VIDEO
500     g_option_context_add_group(context, gst_init_get_option_group());
501 #endif
502
503     WebKitSettings *webkitSettings = webkit_settings_new();
504     webkit_settings_set_enable_developer_extras(webkitSettings, TRUE);
505     webkit_settings_set_enable_webgl(webkitSettings, TRUE);
506     webkit_settings_set_enable_media_stream(webkitSettings, TRUE);
507     if (!addSettingsGroupToContext(context, webkitSettings))
508         g_clear_object(&webkitSettings);
509
510     GError *error = 0;
511     if (!g_option_context_parse(context, &argc, &argv, &error)) {
512         g_printerr("Cannot parse arguments: %s\n", error->message);
513         g_error_free(error);
514         g_option_context_free(context);
515
516         return 1;
517     }
518     g_option_context_free (context);
519
520     WebKitWebContext *webContext = (privateMode || automationMode) ? webkit_web_context_new_ephemeral() : webkit_web_context_get_default();
521
522     if (cookiesPolicy) {
523         WebKitCookieManager *cookieManager = webkit_web_context_get_cookie_manager(webContext);
524         GEnumClass *enumClass = g_type_class_ref(WEBKIT_TYPE_COOKIE_ACCEPT_POLICY);
525         GEnumValue *enumValue = g_enum_get_value_by_nick(enumClass, cookiesPolicy);
526         if (enumValue)
527             webkit_cookie_manager_set_accept_policy(cookieManager, enumValue->value);
528         g_type_class_unref(enumClass);
529     }
530
531     if (cookiesFile && !webkit_web_context_is_ephemeral(webContext)) {
532         WebKitCookieManager *cookieManager = webkit_web_context_get_cookie_manager(webContext);
533         WebKitCookiePersistentStorage storageType = g_str_has_suffix(cookiesFile, ".txt") ? WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT : WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE;
534         webkit_cookie_manager_set_persistent_storage(cookieManager, cookiesFile, storageType);
535     }
536
537     if (proxy) {
538         WebKitNetworkProxySettings *webkitProxySettings = webkit_network_proxy_settings_new(proxy, ignoreHosts);
539         webkit_web_context_set_network_proxy_settings(webContext, WEBKIT_NETWORK_PROXY_MODE_CUSTOM, webkitProxySettings);
540         webkit_network_proxy_settings_free(webkitProxySettings);
541     }
542
543     const gchar *singleprocess = g_getenv("MINIBROWSER_SINGLEPROCESS");
544     webkit_web_context_set_process_model(webContext, (singleprocess && *singleprocess) ?
545         WEBKIT_PROCESS_MODEL_SHARED_SECONDARY_PROCESS : WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
546
547     // Enable the favicon database, by specifying the default directory.
548     webkit_web_context_set_favicon_database_directory(webContext, NULL);
549
550     webkit_web_context_register_uri_scheme(webContext, BROWSER_ABOUT_SCHEME, (WebKitURISchemeRequestCallback)aboutURISchemeRequestCallback, webContext, NULL);
551
552     WebKitUserContentManager *userContentManager = webkit_user_content_manager_new();
553     webkit_user_content_manager_register_script_message_handler(userContentManager, "aboutData");
554     g_signal_connect(userContentManager, "script-message-received::aboutData", G_CALLBACK(aboutDataScriptMessageReceivedCallback), webContext);
555
556     if (contentFilter) {
557         GFile *contentFilterFile = g_file_new_for_commandline_arg(contentFilter);
558
559         FilterSaveData saveData = { NULL, NULL, NULL };
560         gchar *filtersPath = g_build_filename(g_get_user_cache_dir(), g_get_prgname(), "filters", NULL);
561         WebKitUserContentFilterStore *store = webkit_user_content_filter_store_new(filtersPath);
562         g_free(filtersPath);
563
564         webkit_user_content_filter_store_save_from_file(store, "GTKMiniBrowserFilter", contentFilterFile, NULL, (GAsyncReadyCallback)filterSavedCallback, &saveData);
565         saveData.mainLoop = g_main_loop_new(NULL, FALSE);
566         g_main_loop_run(saveData.mainLoop);
567         g_object_unref(store);
568
569         if (saveData.filter)
570             webkit_user_content_manager_add_filter(userContentManager, saveData.filter);
571         else
572             g_printerr("Cannot save filter '%s': %s\n", contentFilter, saveData.error->message);
573
574         g_clear_pointer(&saveData.error, g_error_free);
575         g_clear_pointer(&saveData.filter, webkit_user_content_filter_unref);
576         g_main_loop_unref(saveData.mainLoop);
577         g_object_unref(contentFilterFile);
578     }
579
580     webkit_web_context_set_automation_allowed(webContext, automationMode);
581     g_signal_connect(webContext, "automation-started", G_CALLBACK(automationStartedCallback), NULL);
582
583     if (ignoreTLSErrors)
584         webkit_web_context_set_tls_errors_policy(webContext, WEBKIT_TLS_ERRORS_POLICY_IGNORE);
585
586     BrowserWindow *mainWindow = BROWSER_WINDOW(browser_window_new(NULL, webContext));
587     if (fullScreen)
588         gtk_window_fullscreen(GTK_WINDOW(mainWindow));
589     else if (geometry)
590         gtk_window_parse_geometry(GTK_WINDOW(mainWindow), geometry);
591
592     if (backgroundColor)
593         browser_window_set_background_color(mainWindow, backgroundColor);
594
595     GtkWidget *firstTab = NULL;
596     if (uriArguments) {
597         int i;
598
599         for (i = 0; uriArguments[i]; i++) {
600             WebKitWebView *webView = createBrowserTab(mainWindow, webkitSettings, userContentManager);
601             if (!i)
602                 firstTab = GTK_WIDGET(webView);
603             gchar *url = argumentToURL(uriArguments[i]);
604             webkit_web_view_load_uri(webView, url);
605             g_free(url);
606         }
607     } else {
608         WebKitWebView *webView = createBrowserTab(mainWindow, webkitSettings, userContentManager);
609         firstTab = GTK_WIDGET(webView);
610
611         if (!editorMode) {
612             if (sessionFile)
613                 browser_window_load_session(mainWindow, sessionFile);
614             else if (!automationMode)
615                 webkit_web_view_load_uri(webView, BROWSER_DEFAULT_URL);
616         }
617     }
618
619     gtk_widget_grab_focus(firstTab);
620     gtk_widget_show(GTK_WIDGET(mainWindow));
621
622     g_clear_object(&webkitSettings);
623     g_clear_object(&userContentManager);
624
625     gtk_main();
626
627     if (privateMode)
628         g_object_unref(webContext);
629
630     return 0;
631 }