| 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 | |