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 | static gboolean darkMode; |
56 | |
57 | typedef enum { |
58 | MINI_BROWSER_ERROR_INVALID_ABOUT_PATH |
59 | } MiniBrowserError; |
60 | |
61 | static GQuark miniBrowserErrorQuark() |
62 | { |
63 | return g_quark_from_string("minibrowser-quark" ); |
64 | } |
65 | |
66 | static 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 | |
75 | static 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 | |
91 | static 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 | |
103 | static 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 | |
123 | static 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 | |
188 | static 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 | |
194 | static 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 | |
235 | static 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 | |
255 | typedef struct { |
256 | WebKitURISchemeRequest *request; |
257 | GList *dataList; |
258 | GHashTable *dataMap; |
259 | } AboutDataRequest; |
260 | |
261 | static GHashTable *aboutDataRequestMap; |
262 | |
263 | static 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 | |
278 | static 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 | |
290 | static AboutDataRequest *aboutDataRequestForView(guint64 pageID) |
291 | { |
292 | return aboutDataRequestMap ? g_hash_table_lookup(aboutDataRequestMap, GUINT_TO_POINTER(pageID)) : NULL; |
293 | } |
294 | |
295 | static 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 | |
301 | static 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 | |
307 | static 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 | |
342 | static void domainListFree(GList *domains) |
343 | { |
344 | g_list_free_full(domains, (GDestroyNotify)webkit_website_data_unref); |
345 | } |
346 | |
347 | static 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 | |
394 | static 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 | |
428 | static 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 | |
435 | static 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 | |
463 | static GtkWidget *createWebViewForAutomationCallback(WebKitAutomationSession* session) |
464 | { |
465 | return GTK_WIDGET(browser_window_get_or_create_web_view_for_automation()); |
466 | } |
467 | |
468 | static 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 | |
478 | typedef struct { |
479 | GMainLoop *mainLoop; |
480 | WebKitUserContentFilter *filter; |
481 | GError *error; |
482 | } FilterSaveData; |
483 | |
484 | static 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 | |
490 | int 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 | |