/* * Copyright (C) 2006, 2007 Apple Inc. * Copyright (C) 2007 Alp Toker * Copyright (C) 2011 Igalia S.L. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "cmakeconfig.h" #include "BrowserWindow.h" #include #include #include #include #include #define MINI_BROWSER_ERROR (miniBrowserErrorQuark()) static const gchar **uriArguments = NULL; static GdkRGBA *backgroundColor; static gboolean editorMode; static const char *sessionFile; static char *geometry; static gboolean privateMode; typedef enum { MINI_BROWSER_ERROR_INVALID_ABOUT_PATH } MiniBrowserError; static GQuark miniBrowserErrorQuark() { return g_quark_from_string("minibrowser-quark"); } static gchar *argumentToURL(const char *filename) { GFile *gfile = g_file_new_for_commandline_arg(filename); gchar *fileURL = g_file_get_uri(gfile); g_object_unref(gfile); return fileURL; } static WebKitWebView *createBrowserTab(BrowserWindow *window, WebKitSettings *webkitSettings, WebKitUserContentManager *userContentManager) { WebKitWebView *webView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, "web-context", browser_window_get_web_context(window), "settings", webkitSettings, "user-content-manager", userContentManager, NULL)); if (editorMode) webkit_web_view_set_editable(webView, TRUE); browser_window_append_view(window, webView); return webView; } static gboolean parseBackgroundColor(const char *optionName, const char *value, gpointer data, GError **error) { GdkRGBA rgba; if (gdk_rgba_parse(&rgba, value)) { backgroundColor = gdk_rgba_copy(&rgba); return TRUE; } g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Failed to parse '%s' as RGBA color", value); return FALSE; } static const GOptionEntry commandLineOptions[] = { { "bg-color", 0, 0, G_OPTION_ARG_CALLBACK, parseBackgroundColor, "Background color", NULL }, { "editor-mode", 'e', 0, G_OPTION_ARG_NONE, &editorMode, "Run in editor mode", NULL }, { "session-file", 's', 0, G_OPTION_ARG_FILENAME, &sessionFile, "Session file", "FILE" }, { "geometry", 'g', 0, G_OPTION_ARG_STRING, &geometry, "Set the size and position of the window (WIDTHxHEIGHT+X+Y)", "GEOMETRY" }, { "private", 'p', 0, G_OPTION_ARG_NONE, &privateMode, "Run in private browsing mode", NULL }, { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &uriArguments, 0, "[URL…]" }, { 0, 0, 0, 0, 0, 0, 0 } }; static gboolean parseOptionEntryCallback(const gchar *optionNameFull, const gchar *value, WebKitSettings *webSettings, GError **error) { if (strlen(optionNameFull) <= 2) { g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Invalid option %s", optionNameFull); return FALSE; } /* We have two -- in option name so remove them. */ const gchar *optionName = optionNameFull + 2; GParamSpec *spec = g_object_class_find_property(G_OBJECT_GET_CLASS(webSettings), optionName); if (!spec) { g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Cannot find web settings for option %s", optionNameFull); return FALSE; } switch (G_PARAM_SPEC_VALUE_TYPE(spec)) { case G_TYPE_BOOLEAN: { gboolean propertyValue = !(value && g_ascii_strcasecmp(value, "true") && strcmp(value, "1")); g_object_set(G_OBJECT(webSettings), optionName, propertyValue, NULL); break; } case G_TYPE_STRING: g_object_set(G_OBJECT(webSettings), optionName, value, NULL); break; case G_TYPE_INT: { glong propertyValue; gchar *end; errno = 0; propertyValue = g_ascii_strtoll(value, &end, 0); if (errno == ERANGE || propertyValue > G_MAXINT || propertyValue < G_MININT) { g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Integer value '%s' for %s out of range", value, optionNameFull); return FALSE; } if (errno || value == end) { g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Cannot parse integer value '%s' for %s", value, optionNameFull); return FALSE; } g_object_set(G_OBJECT(webSettings), optionName, propertyValue, NULL); break; } case G_TYPE_FLOAT: { gdouble propertyValue; gchar *end; errno = 0; propertyValue = g_ascii_strtod(value, &end); if (errno == ERANGE || propertyValue > G_MAXFLOAT || propertyValue < G_MINFLOAT) { g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Float value '%s' for %s out of range", value, optionNameFull); return FALSE; } if (errno || value == end) { g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Cannot parse float value '%s' for %s", value, optionNameFull); return FALSE; } g_object_set(G_OBJECT(webSettings), optionName, propertyValue, NULL); break; } default: g_assert_not_reached(); } return TRUE; } static gboolean isValidParameterType(GType gParamType) { return (gParamType == G_TYPE_BOOLEAN || gParamType == G_TYPE_STRING || gParamType == G_TYPE_INT || gParamType == G_TYPE_FLOAT); } static GOptionEntry* getOptionEntriesFromWebKitSettings(WebKitSettings *webSettings) { GParamSpec **propertySpecs; GOptionEntry *optionEntries; guint numProperties, numEntries, i; propertySpecs = g_object_class_list_properties(G_OBJECT_GET_CLASS(webSettings), &numProperties); if (!propertySpecs) return NULL; optionEntries = g_new0(GOptionEntry, numProperties + 1); numEntries = 0; for (i = 0; i < numProperties; i++) { GParamSpec *param = propertySpecs[i]; /* Fill in structures only for writable and not construct-only properties. */ if (!param || !(param->flags & G_PARAM_WRITABLE) || (param->flags & G_PARAM_CONSTRUCT_ONLY)) continue; GType gParamType = G_PARAM_SPEC_VALUE_TYPE(param); if (!isValidParameterType(gParamType)) continue; GOptionEntry *optionEntry = &optionEntries[numEntries++]; optionEntry->long_name = g_param_spec_get_name(param); /* There is no easy way to figure our short name for generated option entries. optionEntry.short_name=*/ /* For bool arguments "enable" type make option argument not required. */ if (gParamType == G_TYPE_BOOLEAN && (strstr(optionEntry->long_name, "enable"))) optionEntry->flags = G_OPTION_FLAG_OPTIONAL_ARG; optionEntry->arg = G_OPTION_ARG_CALLBACK; optionEntry->arg_data = parseOptionEntryCallback; optionEntry->description = g_param_spec_get_blurb(param); optionEntry->arg_description = g_type_name(gParamType); } g_free(propertySpecs); return optionEntries; } static gboolean addSettingsGroupToContext(GOptionContext *context, WebKitSettings* webkitSettings) { GOptionEntry *optionEntries = getOptionEntriesFromWebKitSettings(webkitSettings); if (!optionEntries) return FALSE; GOptionGroup *webSettingsGroup = g_option_group_new("websettings", "WebKitSettings writable properties for default WebKitWebView", "WebKitSettings properties", webkitSettings, NULL); g_option_group_add_entries(webSettingsGroup, optionEntries); g_free(optionEntries); /* Option context takes ownership of the group. */ g_option_context_add_group(context, webSettingsGroup); return TRUE; } typedef struct { WebKitURISchemeRequest *request; GList *dataList; GHashTable *dataMap; } AboutDataRequest; static GHashTable *aboutDataRequestMap; static void aboutDataRequestFree(AboutDataRequest *request) { if (!request) return; g_list_free_full(request->dataList, g_object_unref); if (request->request) g_object_unref(request->request); if (request->dataMap) g_hash_table_destroy(request->dataMap); g_slice_free(AboutDataRequest, request); } static AboutDataRequest* aboutDataRequestNew(WebKitURISchemeRequest *uriRequest) { if (!aboutDataRequestMap) aboutDataRequestMap = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)aboutDataRequestFree); AboutDataRequest *request = g_slice_new0(AboutDataRequest); request->request = g_object_ref(uriRequest); g_hash_table_insert(aboutDataRequestMap, GUINT_TO_POINTER(webkit_web_view_get_page_id(webkit_uri_scheme_request_get_web_view(request->request))), request); return request; } static AboutDataRequest *aboutDataRequestForView(guint64 pageID) { return aboutDataRequestMap ? g_hash_table_lookup(aboutDataRequestMap, GUINT_TO_POINTER(pageID)) : NULL; } static void websiteDataRemovedCallback(WebKitWebsiteDataManager *manager, GAsyncResult *result, AboutDataRequest *dataRequest) { if (webkit_website_data_manager_remove_finish(manager, result, NULL)) webkit_web_view_reload(webkit_uri_scheme_request_get_web_view(dataRequest->request)); } static void websiteDataClearedCallback(WebKitWebsiteDataManager *manager, GAsyncResult *result, AboutDataRequest *dataRequest) { if (webkit_website_data_manager_clear_finish(manager, result, NULL)) webkit_web_view_reload(webkit_uri_scheme_request_get_web_view(dataRequest->request)); } static void aboutDataScriptMessageReceivedCallback(WebKitUserContentManager *userContentManager, WebKitJavascriptResult *message, WebKitWebContext *webContext) { JSValueRef jsValue = webkit_javascript_result_get_value(message); JSStringRef jsString = JSValueToStringCopy(webkit_javascript_result_get_global_context(message), jsValue, NULL); size_t maxSize = JSStringGetMaximumUTF8CStringSize(jsString); if (!maxSize) { JSStringRelease(jsString); return; } char *messageString = g_malloc(maxSize); JSStringGetUTF8CString(jsString, messageString, maxSize); JSStringRelease(jsString); char **tokens = g_strsplit(messageString, ":", 3); g_free(messageString); unsigned tokenCount = g_strv_length(tokens); if (!tokens || tokenCount < 2) { g_strfreev(tokens); return; } guint64 pageID = g_ascii_strtoull(tokens[0], NULL, 10); AboutDataRequest *dataRequest = aboutDataRequestForView(pageID); if (!dataRequest) { g_strfreev(tokens); return; } WebKitWebsiteDataManager *manager = webkit_web_context_get_website_data_manager(webContext); guint64 types = g_ascii_strtoull(tokens[1], NULL, 10); if (tokenCount == 2) webkit_website_data_manager_clear(manager, types, 0, NULL, (GAsyncReadyCallback)websiteDataClearedCallback, dataRequest); else { guint64 domainID = g_ascii_strtoull(tokens[2], NULL, 10); GList *dataList = g_hash_table_lookup(dataRequest->dataMap, GUINT_TO_POINTER(types)); WebKitWebsiteData *data = g_list_nth_data(dataList, domainID); if (data) { GList dataList = { data, NULL, NULL }; webkit_website_data_manager_remove(manager, types, &dataList, NULL, (GAsyncReadyCallback)websiteDataRemovedCallback, dataRequest); } } g_strfreev(tokens); } static void domainListFree(GList *domains) { g_list_free_full(domains, (GDestroyNotify)webkit_website_data_unref); } static void aboutDataFillTable(GString *result, AboutDataRequest *dataRequest, GList* dataList, const char *title, WebKitWebsiteDataTypes types, const char *dataPath, guint64 pageID) { guint64 totalDataSize = 0; GList *domains = NULL; GList *l; for (l = dataList; l; l = g_list_next(l)) { WebKitWebsiteData *data = (WebKitWebsiteData *)l->data; if (webkit_website_data_get_types(data) & types) { domains = g_list_prepend(domains, webkit_website_data_ref(data)); totalDataSize += webkit_website_data_get_size(data, types); } } if (!domains) return; if (!dataRequest->dataMap) dataRequest->dataMap = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)domainListFree); g_hash_table_insert(dataRequest->dataMap, GUINT_TO_POINTER(types), domains); if (totalDataSize) { char *totalDataSizeStr = g_format_size(totalDataSize); g_string_append_printf(result, "

%s (%s)

\n\n", title, totalDataSizeStr); g_free(totalDataSizeStr); } else g_string_append_printf(result, "

%s

\n
\n", title); if (dataPath) g_string_append_printf(result, "\n", dataPath); unsigned index; for (l = domains, index = 0; l; l = g_list_next(l), index++) { WebKitWebsiteData *data = (WebKitWebsiteData *)l->data; const char *displayName = webkit_website_data_get_name(data); guint64 dataSize = webkit_website_data_get_size(data, types); if (dataSize) { char *dataSizeStr = g_format_size(dataSize); g_string_append_printf(result, "", displayName, dataSizeStr); g_free(dataSizeStr); } else g_string_append_printf(result, "", displayName); g_string_append_printf(result, "\n", pageID, types, index); } g_string_append_printf(result, "
Path: %s
%s (%s)
%s
\n", pageID, types); } static void gotWebsiteDataCallback(WebKitWebsiteDataManager *manager, GAsyncResult *asyncResult, AboutDataRequest *dataRequest) { GList *dataList = webkit_website_data_manager_fetch_finish(manager, asyncResult, NULL); GString *result = g_string_new( "" "\n"); guint64 pageID = webkit_web_view_get_page_id(webkit_uri_scheme_request_get_web_view(dataRequest->request)); aboutDataFillTable(result, dataRequest, dataList, "Cookies", WEBKIT_WEBSITE_DATA_COOKIES, NULL, pageID); aboutDataFillTable(result, dataRequest, dataList, "Memory Cache", WEBKIT_WEBSITE_DATA_MEMORY_CACHE, NULL, pageID); aboutDataFillTable(result, dataRequest, dataList, "Disk Cache", WEBKIT_WEBSITE_DATA_DISK_CACHE, webkit_website_data_manager_get_disk_cache_directory(manager), pageID); aboutDataFillTable(result, dataRequest, dataList, "Session Storage", WEBKIT_WEBSITE_DATA_SESSION_STORAGE, NULL, pageID); aboutDataFillTable(result, dataRequest, dataList, "Local Storage", WEBKIT_WEBSITE_DATA_LOCAL_STORAGE, webkit_website_data_manager_get_local_storage_directory(manager), pageID); aboutDataFillTable(result, dataRequest, dataList, "WebSQL Databases", WEBKIT_WEBSITE_DATA_WEBSQL_DATABASES, webkit_website_data_manager_get_websql_directory(manager), pageID); aboutDataFillTable(result, dataRequest, dataList, "IndexedDB Databases", WEBKIT_WEBSITE_DATA_INDEXEDDB_DATABASES, webkit_website_data_manager_get_indexeddb_directory(manager), pageID); aboutDataFillTable(result, dataRequest, dataList, "Plugins Data", WEBKIT_WEBSITE_DATA_PLUGIN_DATA, NULL, pageID); 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); result = g_string_append(result, ""); gsize streamLength = result->len; GInputStream *stream = g_memory_input_stream_new_from_data(g_string_free(result, FALSE), streamLength, g_free); webkit_uri_scheme_request_finish(dataRequest->request, stream, streamLength, "text/html"); g_list_free_full(dataList, (GDestroyNotify)webkit_website_data_unref); } static void aboutDataHandleRequest(WebKitURISchemeRequest *request, WebKitWebContext *webContext) { AboutDataRequest *dataRequest = aboutDataRequestNew(request); WebKitWebsiteDataManager *manager = webkit_web_context_get_website_data_manager(webContext); webkit_website_data_manager_fetch(manager, WEBKIT_WEBSITE_DATA_ALL, NULL, (GAsyncReadyCallback)gotWebsiteDataCallback, dataRequest); } static void aboutURISchemeRequestCallback(WebKitURISchemeRequest *request, WebKitWebContext *webContext) { GInputStream *stream; gsize streamLength; const gchar *path; gchar *contents; GError *error; path = webkit_uri_scheme_request_get_path(request); if (!g_strcmp0(path, "minibrowser")) { contents = g_strdup_printf("

WebKitGTK+ MiniBrowser

The WebKit2 test browser of the GTK+ port.

WebKit version: %d.%d.%d

", webkit_get_major_version(), webkit_get_minor_version(), webkit_get_micro_version()); streamLength = strlen(contents); stream = g_memory_input_stream_new_from_data(contents, streamLength, g_free); webkit_uri_scheme_request_finish(request, stream, streamLength, "text/html"); g_object_unref(stream); } else if (!g_strcmp0(path, "data")) aboutDataHandleRequest(request, webContext); else { error = g_error_new(MINI_BROWSER_ERROR, MINI_BROWSER_ERROR_INVALID_ABOUT_PATH, "Invalid about:%s page.", path); webkit_uri_scheme_request_finish_error(request, error); g_error_free(error); } } int main(int argc, char *argv[]) { gtk_init(&argc, &argv); #if ENABLE_DEVELOPER_MODE g_setenv("WEBKIT_INJECTED_BUNDLE_PATH", WEBKIT_INJECTED_BUNDLE_PATH, FALSE); #endif GOptionContext *context = g_option_context_new(NULL); g_option_context_add_main_entries(context, commandLineOptions, 0); g_option_context_add_group(context, gtk_get_option_group(TRUE)); WebKitSettings *webkitSettings = webkit_settings_new(); webkit_settings_set_enable_developer_extras(webkitSettings, TRUE); webkit_settings_set_enable_webgl(webkitSettings, TRUE); webkit_settings_set_enable_media_stream(webkitSettings, TRUE); if (!addSettingsGroupToContext(context, webkitSettings)) g_clear_object(&webkitSettings); GError *error = 0; if (!g_option_context_parse(context, &argc, &argv, &error)) { g_printerr("Cannot parse arguments: %s\n", error->message); g_error_free(error); g_option_context_free(context); return 1; } g_option_context_free (context); WebKitWebContext *webContext = privateMode ? webkit_web_context_new_ephemeral() : webkit_web_context_get_default(); const gchar *singleprocess = g_getenv("MINIBROWSER_SINGLEPROCESS"); webkit_web_context_set_process_model(webContext, (singleprocess && *singleprocess) ? WEBKIT_PROCESS_MODEL_SHARED_SECONDARY_PROCESS : WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES); // Enable the favicon database, by specifying the default directory. webkit_web_context_set_favicon_database_directory(webContext, NULL); webkit_web_context_register_uri_scheme(webContext, BROWSER_ABOUT_SCHEME, (WebKitURISchemeRequestCallback)aboutURISchemeRequestCallback, webContext, NULL); WebKitUserContentManager *userContentManager = webkit_user_content_manager_new(); webkit_user_content_manager_register_script_message_handler(userContentManager, "aboutData"); g_signal_connect(userContentManager, "script-message-received::aboutData", G_CALLBACK(aboutDataScriptMessageReceivedCallback), webContext); BrowserWindow *mainWindow = BROWSER_WINDOW(browser_window_new(NULL, webContext)); if (geometry) gtk_window_parse_geometry(GTK_WINDOW(mainWindow), geometry); GtkWidget *firstTab = NULL; if (uriArguments) { int i; for (i = 0; uriArguments[i]; i++) { WebKitWebView *webView = createBrowserTab(mainWindow, webkitSettings, userContentManager); if (!i) firstTab = GTK_WIDGET(webView); gchar *url = argumentToURL(uriArguments[i]); webkit_web_view_load_uri(webView, url); g_free(url); } } else { WebKitWebView *webView = createBrowserTab(mainWindow, webkitSettings, userContentManager); firstTab = GTK_WIDGET(webView); if (backgroundColor) browser_window_set_background_color(mainWindow, backgroundColor); if (!editorMode) { if (sessionFile) browser_window_load_session(mainWindow, sessionFile); else webkit_web_view_load_uri(webView, BROWSER_DEFAULT_URL); } } gtk_widget_grab_focus(firstTab); gtk_widget_show(GTK_WIDGET(mainWindow)); g_clear_object(&webkitSettings); g_clear_object(&userContentManager); gtk_main(); if (privateMode) g_object_unref(webContext); return 0; }