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