diff options
Diffstat (limited to 'Tools/MiniBrowser/gtk')
-rw-r--r-- | Tools/MiniBrowser/gtk/BrowserDownloadsBar.c | 1 | ||||
-rw-r--r-- | Tools/MiniBrowser/gtk/BrowserSearchBar.c | 12 | ||||
-rw-r--r-- | Tools/MiniBrowser/gtk/BrowserSettingsDialog.c | 55 | ||||
-rw-r--r-- | Tools/MiniBrowser/gtk/BrowserTab.c | 552 | ||||
-rw-r--r-- | Tools/MiniBrowser/gtk/BrowserTab.h | 60 | ||||
-rw-r--r-- | Tools/MiniBrowser/gtk/BrowserWindow.c | 898 | ||||
-rw-r--r-- | Tools/MiniBrowser/gtk/BrowserWindow.h | 11 | ||||
-rw-r--r-- | Tools/MiniBrowser/gtk/CMakeLists.txt | 64 | ||||
-rw-r--r-- | Tools/MiniBrowser/gtk/GNUmakefile.am | 75 | ||||
-rw-r--r-- | Tools/MiniBrowser/gtk/main.c | 307 |
10 files changed, 1679 insertions, 356 deletions
diff --git a/Tools/MiniBrowser/gtk/BrowserDownloadsBar.c b/Tools/MiniBrowser/gtk/BrowserDownloadsBar.c index 5f0e620e5..b0ec3ce14 100644 --- a/Tools/MiniBrowser/gtk/BrowserDownloadsBar.c +++ b/Tools/MiniBrowser/gtk/BrowserDownloadsBar.c @@ -23,6 +23,7 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ +#include "cmakeconfig.h" #include "BrowserDownloadsBar.h" #include <glib/gi18n.h> diff --git a/Tools/MiniBrowser/gtk/BrowserSearchBar.c b/Tools/MiniBrowser/gtk/BrowserSearchBar.c index 7585b564d..22930c4c5 100644 --- a/Tools/MiniBrowser/gtk/BrowserSearchBar.c +++ b/Tools/MiniBrowser/gtk/BrowserSearchBar.c @@ -23,9 +23,9 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ +#include "cmakeconfig.h" #include "BrowserSearchBar.h" - static const char *searchEntryFailedStyle = "GtkEntry#searchEntry {background-color: #ff6666;}"; struct _BrowserSearchBar { @@ -63,7 +63,7 @@ static void doSearch(BrowserSearchBar *searchBar) if (!gtk_entry_get_icon_stock(entry, GTK_ENTRY_ICON_SECONDARY)) gtk_entry_set_icon_from_stock(entry, GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_CLEAR); - WebKitFindOptions options = WEBKIT_FIND_OPTIONS_NONE; + WebKitFindOptions options = WEBKIT_FIND_OPTIONS_WRAP_AROUND; if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(searchBar->caseCheckButton))) options |= WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE; if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(searchBar->begginigWordCheckButton))) @@ -274,9 +274,13 @@ void browser_search_bar_open(BrowserSearchBar *searchBar) { g_return_if_fail(BROWSER_IS_SEARCH_BAR(searchBar)); + GtkEntry *entry = GTK_ENTRY(searchBar->entry); + gtk_widget_show(GTK_WIDGET(searchBar)); - gtk_widget_grab_focus(GTK_WIDGET(searchBar->entry)); - gtk_editable_select_region(GTK_EDITABLE(searchBar->entry), 0, -1); + gtk_widget_grab_focus(GTK_WIDGET(entry)); + gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1); + if (gtk_entry_get_text_length(entry)) + doSearch(searchBar); } void browser_search_bar_close(BrowserSearchBar *searchBar) diff --git a/Tools/MiniBrowser/gtk/BrowserSettingsDialog.c b/Tools/MiniBrowser/gtk/BrowserSettingsDialog.c index 1e4dcd804..511b3db04 100644 --- a/Tools/MiniBrowser/gtk/BrowserSettingsDialog.c +++ b/Tools/MiniBrowser/gtk/BrowserSettingsDialog.c @@ -55,6 +55,34 @@ struct _BrowserSettingsDialogClass { G_DEFINE_TYPE(BrowserSettingsDialog, browser_settings_dialog, GTK_TYPE_DIALOG) +static const char *hardwareAccelerationPolicyToString(WebKitHardwareAccelerationPolicy policy) +{ + switch (policy) { + case WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS: + return "always"; + case WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER: + return "never"; + case WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND: + return "ondemand"; + } + + g_assert_not_reached(); + return "ondemand"; +} + +static int stringToHardwareAccelerationPolicy(const char *policy) +{ + if (!g_ascii_strcasecmp(policy, "always")) + return WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS; + if (!g_ascii_strcasecmp(policy, "never")) + return WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER; + if (!g_ascii_strcasecmp(policy, "ondemand")) + return WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND; + + g_warning("Invalid value %s for hardware-acceleration-policy setting valid values are always, never and ondemand", policy); + return -1; +} + static void cellRendererChanged(GtkCellRenderer *renderer, const char *path, const GValue *value, BrowserSettingsDialog *dialog) { GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(dialog->settingsList)); @@ -62,12 +90,21 @@ static void cellRendererChanged(GtkCellRenderer *renderer, const char *path, con GtkTreeIter iter; gtk_tree_model_get_iter(model, &iter, treePath); + gboolean updateTreeStore = TRUE; char *name; gtk_tree_model_get(model, &iter, SETTINGS_LIST_COLUMN_NAME, &name, -1); - g_object_set_property(G_OBJECT(dialog->settings), name, value); + if (!g_strcmp0(name, "hardware-acceleration-policy")) { + int policy = stringToHardwareAccelerationPolicy(g_value_get_string(value)); + if (policy != -1) + webkit_settings_set_hardware_acceleration_policy(dialog->settings, policy); + else + updateTreeStore = FALSE; + } else + g_object_set_property(G_OBJECT(dialog->settings), name, value); g_free(name); - gtk_list_store_set(GTK_LIST_STORE(model), &iter, SETTINGS_LIST_COLUMN_VALUE, value, -1); + if (updateTreeStore) + gtk_list_store_set(GTK_LIST_STORE(model), &iter, SETTINGS_LIST_COLUMN_VALUE, value, -1); gtk_tree_path_free(treePath); } @@ -134,10 +171,19 @@ static void browserSettingsDialogConstructed(GObject *object) GParamSpec *property = properties[i]; const char *name = g_param_spec_get_name(property); const char *nick = g_param_spec_get_nick(property); + char *blurb = g_markup_escape_text(g_param_spec_get_blurb(property), -1); GValue value = { 0, { { 0 } } }; - g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(property)); - g_object_get_property(G_OBJECT(settings), name, &value); + if (!g_strcmp0(name, "hardware-acceleration-policy")) { + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, hardwareAccelerationPolicyToString(webkit_settings_get_hardware_acceleration_policy(settings))); + char *extendedBlutb = g_strdup_printf("%s (always, never or ondemand)", blurb); + g_free(blurb); + blurb = extendedBlutb; + } else { + g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(property)); + g_object_get_property(G_OBJECT(settings), name, &value); + } GtkAdjustment *adjustment = NULL; if (G_PARAM_SPEC_VALUE_TYPE(property) == G_TYPE_UINT) { @@ -146,7 +192,6 @@ static void browserSettingsDialogConstructed(GObject *object) uIntProperty->maximum, 1, 1, 1); } - char *blurb = g_markup_escape_text(g_param_spec_get_blurb(property), -1); GtkTreeIter iter; gtk_list_store_append(model, &iter); gtk_list_store_set(model, &iter, diff --git a/Tools/MiniBrowser/gtk/BrowserTab.c b/Tools/MiniBrowser/gtk/BrowserTab.c new file mode 100644 index 000000000..a6b2ea42e --- /dev/null +++ b/Tools/MiniBrowser/gtk/BrowserTab.c @@ -0,0 +1,552 @@ +/* + * Copyright (C) 2016 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. AND ITS CONTRIBUTORS ``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 ITS 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. + */ + +#if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H && defined(BUILDING_WITH_CMAKE) +#include "cmakeconfig.h" +#endif +#include "BrowserTab.h" + +#include "BrowserSearchBar.h" +#include "BrowserWindow.h" +#include <string.h> + +enum { + PROP_0, + + PROP_VIEW +}; + +struct _BrowserTab { + GtkBox parent; + + WebKitWebView *webView; + BrowserSearchBar *searchBar; + GtkWidget *statusLabel; + gboolean wasSearchingWhenEnteredFullscreen; + gboolean inspectorIsVisible; + GtkWidget *fullScreenMessageLabel; + guint fullScreenMessageLabelId; + + /* Tab Title */ + GtkWidget *titleBox; + GtkWidget *titleLabel; + GtkWidget *titleSpinner; + GtkWidget *titleCloseButton; +}; + +struct _BrowserTabClass { + GtkBoxClass parent; +}; + +G_DEFINE_TYPE(BrowserTab, browser_tab, GTK_TYPE_BOX) + +static void titleChanged(WebKitWebView *webView, GParamSpec *pspec, BrowserTab *tab) +{ + const char *title = webkit_web_view_get_title(webView); + if (title && *title) + gtk_label_set_text(GTK_LABEL(tab->titleLabel), title); +} + +static void isLoadingChanged(WebKitWebView *webView, GParamSpec *paramSpec, BrowserTab *tab) +{ + if (webkit_web_view_is_loading(webView)) { + gtk_spinner_start(GTK_SPINNER(tab->titleSpinner)); + gtk_widget_show(tab->titleSpinner); + } else { + gtk_spinner_stop(GTK_SPINNER(tab->titleSpinner)); + gtk_widget_hide(tab->titleSpinner); + } +} + +static gboolean decidePolicy(WebKitWebView *webView, WebKitPolicyDecision *decision, WebKitPolicyDecisionType decisionType, BrowserTab *tab) +{ + if (decisionType != WEBKIT_POLICY_DECISION_TYPE_RESPONSE) + return FALSE; + + WebKitResponsePolicyDecision *responseDecision = WEBKIT_RESPONSE_POLICY_DECISION(decision); + if (webkit_response_policy_decision_is_mime_type_supported(responseDecision)) + return FALSE; + + WebKitWebResource *mainResource = webkit_web_view_get_main_resource(webView); + WebKitURIRequest *request = webkit_response_policy_decision_get_request(responseDecision); + const char *requestURI = webkit_uri_request_get_uri(request); + if (g_strcmp0(webkit_web_resource_get_uri(mainResource), requestURI)) + return FALSE; + + webkit_policy_decision_download(decision); + return TRUE; +} + +static void removeChildIfInfoBar(GtkWidget *child, GtkContainer *tab) +{ + if (GTK_IS_INFO_BAR(child)) + gtk_container_remove(tab, child); +} + +static void loadChanged(WebKitWebView *webView, WebKitLoadEvent loadEvent, BrowserTab *tab) +{ + if (loadEvent != WEBKIT_LOAD_STARTED) + return; + + gtk_container_foreach(GTK_CONTAINER(tab), (GtkCallback)removeChildIfInfoBar, tab); +} + +static GtkWidget *createInfoBarQuestionMessage(const char *title, const char *text) +{ + GtkWidget *dialog = gtk_info_bar_new_with_buttons("No", GTK_RESPONSE_NO, "Yes", GTK_RESPONSE_YES, NULL); + gtk_info_bar_set_message_type(GTK_INFO_BAR(dialog), GTK_MESSAGE_QUESTION); + + GtkWidget *contentBox = gtk_info_bar_get_content_area(GTK_INFO_BAR(dialog)); + gtk_orientable_set_orientation(GTK_ORIENTABLE(contentBox), GTK_ORIENTATION_VERTICAL); + gtk_box_set_spacing(GTK_BOX(contentBox), 0); + + GtkWidget *label = gtk_label_new(NULL); + gchar *markup = g_strdup_printf("<span size='xx-large' weight='bold'>%s</span>", title); + gtk_label_set_markup(GTK_LABEL(label), markup); + g_free(markup); + gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); + gtk_label_set_selectable(GTK_LABEL(label), TRUE); + gtk_misc_set_alignment(GTK_MISC(label), 0., 0.5); + gtk_box_pack_start(GTK_BOX(contentBox), label, FALSE, FALSE, 2); + gtk_widget_show(label); + + label = gtk_label_new(text); + gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); + gtk_label_set_selectable(GTK_LABEL(label), TRUE); + gtk_misc_set_alignment(GTK_MISC(label), 0., 0.5); + gtk_box_pack_start(GTK_BOX(contentBox), label, FALSE, FALSE, 0); + gtk_widget_show(label); + + return dialog; +} + +static void tlsErrorsDialogResponse(GtkWidget *dialog, gint response, BrowserTab *tab) +{ + if (response == GTK_RESPONSE_YES) { + const char *failingURI = (const char *)g_object_get_data(G_OBJECT(dialog), "failingURI"); + GTlsCertificate *certificate = (GTlsCertificate *)g_object_get_data(G_OBJECT(dialog), "certificate"); + SoupURI *uri = soup_uri_new(failingURI); + webkit_web_context_allow_tls_certificate_for_host(webkit_web_view_get_context(tab->webView), certificate, uri->host); + soup_uri_free(uri); + webkit_web_view_load_uri(tab->webView, failingURI); + } + gtk_widget_destroy(dialog); +} + +static gboolean loadFailedWithTLSerrors(WebKitWebView *webView, const char *failingURI, GTlsCertificate *certificate, GTlsCertificateFlags errors, BrowserTab *tab) +{ + gchar *text = g_strdup_printf("Failed to load %s: Do you want to continue ignoring the TLS errors?", failingURI); + GtkWidget *dialog = createInfoBarQuestionMessage("Invalid TLS Certificate", text); + g_free(text); + g_object_set_data_full(G_OBJECT(dialog), "failingURI", g_strdup(failingURI), g_free); + g_object_set_data_full(G_OBJECT(dialog), "certificate", g_object_ref(certificate), g_object_unref); + + g_signal_connect(dialog, "response", G_CALLBACK(tlsErrorsDialogResponse), tab); + + gtk_box_pack_start(GTK_BOX(tab), dialog, FALSE, FALSE, 0); + gtk_box_reorder_child(GTK_BOX(tab), dialog, 0); + gtk_widget_show(dialog); + + return TRUE; +} + +static void permissionRequestDialogResponse(GtkWidget *dialog, gint response, WebKitPermissionRequest *request) +{ + switch (response) { + case GTK_RESPONSE_YES: + webkit_permission_request_allow(request); + break; + default: + webkit_permission_request_deny(request); + break; + } + + gtk_widget_destroy(dialog); + g_object_unref(request); +} + +static gboolean decidePermissionRequest(WebKitWebView *webView, WebKitPermissionRequest *request, BrowserTab *tab) +{ + const gchar *title = NULL; + gchar *text = NULL; + + if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(request)) { + title = "Geolocation request"; + text = g_strdup("Allow geolocation request?"); + } else if (WEBKIT_IS_NOTIFICATION_PERMISSION_REQUEST(request)) { + title = "Notification request"; + text = g_strdup("Allow notifications request?"); + } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(request)) { + title = "UserMedia request"; + gboolean is_for_audio_device = webkit_user_media_permission_is_for_audio_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request)); + gboolean is_for_video_device = webkit_user_media_permission_is_for_video_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request)); + const char *mediaType = NULL; + if (is_for_audio_device) { + if (is_for_video_device) + mediaType = "audio/video"; + else + mediaType = "audio"; + } else if (is_for_video_device) + mediaType = "video"; + text = g_strdup_printf("Allow access to %s device?", mediaType); + } else if (WEBKIT_IS_INSTALL_MISSING_MEDIA_PLUGINS_PERMISSION_REQUEST(request)) { + title = "Media plugin missing request"; + text = g_strdup_printf("The media backend was unable to find a plugin to play the requested media:\n%s.\nAllow to search and install the missing plugin?", + webkit_install_missing_media_plugins_permission_request_get_description(WEBKIT_INSTALL_MISSING_MEDIA_PLUGINS_PERMISSION_REQUEST(request))); + } else + return FALSE; + + GtkWidget *dialog = createInfoBarQuestionMessage(title, text); + g_free(text); + g_signal_connect(dialog, "response", G_CALLBACK(permissionRequestDialogResponse), g_object_ref(request)); + + gtk_box_pack_start(GTK_BOX(tab), dialog, FALSE, FALSE, 0); + gtk_box_reorder_child(GTK_BOX(tab), dialog, 0); + gtk_widget_show(dialog); + + return TRUE; +} + +#if GTK_CHECK_VERSION(3, 12, 0) +static void colorChooserRGBAChanged(GtkColorChooser *colorChooser, GParamSpec *paramSpec, WebKitColorChooserRequest *request) +{ + GdkRGBA rgba; + gtk_color_chooser_get_rgba(colorChooser, &rgba); + webkit_color_chooser_request_set_rgba(request, &rgba); +} + +static void popoverColorClosed(GtkWidget *popover, WebKitColorChooserRequest *request) +{ + webkit_color_chooser_request_finish(request); +} + +static void colorChooserRequestFinished(WebKitColorChooserRequest *request, GtkWidget *popover) +{ + g_object_unref(request); + gtk_widget_destroy(popover); +} + +static gboolean runColorChooserCallback(WebKitWebView *webView, WebKitColorChooserRequest *request, BrowserTab *tab) +{ + GtkWidget *popover = gtk_popover_new(GTK_WIDGET(webView)); + + GdkRectangle rectangle; + webkit_color_chooser_request_get_element_rectangle(request, &rectangle); + gtk_popover_set_pointing_to(GTK_POPOVER(popover), &rectangle); + + GtkWidget *colorChooser = gtk_color_chooser_widget_new(); + GdkRGBA rgba; + webkit_color_chooser_request_get_rgba(request, &rgba); + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(colorChooser), &rgba); + g_signal_connect(colorChooser, "notify::rgba", G_CALLBACK(colorChooserRGBAChanged), request); + gtk_container_add(GTK_CONTAINER(popover), colorChooser); + gtk_widget_show(colorChooser); + + g_object_ref(request); + g_signal_connect_object(popover, "hide", G_CALLBACK(popoverColorClosed), request, 0); + g_signal_connect_object(request, "finished", G_CALLBACK(colorChooserRequestFinished), popover, 0); + + gtk_widget_show(popover); + + return TRUE; +} +#endif /* GTK_CHECK_VERSION(3, 12, 0) */ + +static gboolean inspectorOpenedInWindow(WebKitWebInspector *inspector, BrowserTab *tab) +{ + tab->inspectorIsVisible = TRUE; + return FALSE; +} + +static gboolean inspectorClosed(WebKitWebInspector *inspector, BrowserTab *tab) +{ + tab->inspectorIsVisible = FALSE; + return FALSE; +} + +static void browserTabSetProperty(GObject *object, guint propId, const GValue *value, GParamSpec *pspec) +{ + BrowserTab *tab = BROWSER_TAB(object); + + switch (propId) { + case PROP_VIEW: + tab->webView = g_value_get_object(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); + } +} + +static void browserTabFinalize(GObject *gObject) +{ + BrowserTab *tab = BROWSER_TAB(gObject); + + if (tab->fullScreenMessageLabelId) + g_source_remove(tab->fullScreenMessageLabelId); + + G_OBJECT_CLASS(browser_tab_parent_class)->finalize(gObject); +} + +static void browser_tab_init(BrowserTab *tab) +{ + gtk_orientable_set_orientation(GTK_ORIENTABLE(tab), GTK_ORIENTATION_VERTICAL); +} + +static void browserTabConstructed(GObject *gObject) +{ + BrowserTab *tab = BROWSER_TAB(gObject); + + G_OBJECT_CLASS(browser_tab_parent_class)->constructed(gObject); + + tab->searchBar = BROWSER_SEARCH_BAR(browser_search_bar_new(tab->webView)); + gtk_box_pack_start(GTK_BOX(tab), GTK_WIDGET(tab->searchBar), FALSE, FALSE, 0); + + GtkWidget *overlay = gtk_overlay_new(); + gtk_box_pack_start(GTK_BOX(tab), overlay, TRUE, TRUE, 0); + gtk_widget_show(overlay); + + tab->statusLabel = gtk_label_new(NULL); + gtk_widget_set_halign(tab->statusLabel, GTK_ALIGN_START); + gtk_widget_set_valign(tab->statusLabel, GTK_ALIGN_END); + gtk_widget_set_margin_left(tab->statusLabel, 1); + gtk_widget_set_margin_right(tab->statusLabel, 1); + gtk_widget_set_margin_top(tab->statusLabel, 1); + gtk_widget_set_margin_bottom(tab->statusLabel, 1); + gtk_overlay_add_overlay(GTK_OVERLAY(overlay), tab->statusLabel); + + tab->fullScreenMessageLabel = gtk_label_new(NULL); + gtk_widget_set_halign(tab->fullScreenMessageLabel, GTK_ALIGN_CENTER); + gtk_widget_set_valign(tab->fullScreenMessageLabel, GTK_ALIGN_CENTER); + gtk_widget_set_no_show_all(tab->fullScreenMessageLabel, TRUE); + gtk_overlay_add_overlay(GTK_OVERLAY(overlay), tab->fullScreenMessageLabel); + + gtk_container_add(GTK_CONTAINER(overlay), GTK_WIDGET(tab->webView)); + gtk_widget_show(GTK_WIDGET(tab->webView)); + + tab->titleBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); + + GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + gtk_widget_set_halign(hbox, GTK_ALIGN_CENTER); + + tab->titleSpinner = gtk_spinner_new(); + gtk_box_pack_start(GTK_BOX(hbox), tab->titleSpinner, FALSE, FALSE, 0); + + tab->titleLabel = gtk_label_new(NULL); + gtk_label_set_ellipsize(GTK_LABEL(tab->titleLabel), PANGO_ELLIPSIZE_END); + gtk_label_set_single_line_mode(GTK_LABEL(tab->titleLabel), TRUE); + gtk_misc_set_padding(GTK_MISC(tab->titleLabel), 0, 0); + gtk_box_pack_start(GTK_BOX(hbox), tab->titleLabel, FALSE, FALSE, 0); + gtk_widget_show(tab->titleLabel); + + gtk_box_pack_start(GTK_BOX(tab->titleBox), hbox, TRUE, TRUE, 0); + gtk_widget_show(hbox); + + tab->titleCloseButton = gtk_button_new(); + g_signal_connect_swapped(tab->titleCloseButton, "clicked", G_CALLBACK(gtk_widget_destroy), tab); + gtk_button_set_relief(GTK_BUTTON(tab->titleCloseButton), GTK_RELIEF_NONE); + gtk_button_set_focus_on_click(GTK_BUTTON(tab->titleCloseButton), FALSE); + + GtkWidget *image = gtk_image_new_from_icon_name("window-close-symbolic", GTK_ICON_SIZE_MENU); + gtk_container_add(GTK_CONTAINER(tab->titleCloseButton), image); + gtk_widget_show(image); + + gtk_box_pack_start(GTK_BOX(tab->titleBox), tab->titleCloseButton, FALSE, FALSE, 0); + gtk_widget_show(tab->titleCloseButton); + + g_signal_connect(tab->webView, "notify::title", G_CALLBACK(titleChanged), tab); + g_signal_connect(tab->webView, "notify::is-loading", G_CALLBACK(isLoadingChanged), tab); + g_signal_connect(tab->webView, "decide-policy", G_CALLBACK(decidePolicy), tab); + g_signal_connect(tab->webView, "load-changed", G_CALLBACK(loadChanged), tab); + g_signal_connect(tab->webView, "load-failed-with-tls-errors", G_CALLBACK(loadFailedWithTLSerrors), tab); + g_signal_connect(tab->webView, "permission-request", G_CALLBACK(decidePermissionRequest), tab); +#if GTK_CHECK_VERSION(3, 12, 0) + g_signal_connect(tab->webView, "run-color-chooser", G_CALLBACK(runColorChooserCallback), tab); +#endif + + WebKitWebInspector *inspector = webkit_web_view_get_inspector(tab->webView); + g_signal_connect(inspector, "open-window", G_CALLBACK(inspectorOpenedInWindow), tab); + g_signal_connect(inspector, "closed", G_CALLBACK(inspectorClosed), tab); + + if (webkit_web_view_is_editable(tab->webView)) + webkit_web_view_load_html(tab->webView, "<html></html>", "file:///"); +} + +static void browser_tab_class_init(BrowserTabClass *klass) +{ + GObjectClass *gobjectClass = G_OBJECT_CLASS(klass); + gobjectClass->constructed = browserTabConstructed; + gobjectClass->set_property = browserTabSetProperty; + gobjectClass->finalize = browserTabFinalize; + + g_object_class_install_property( + gobjectClass, + PROP_VIEW, + g_param_spec_object( + "view", + "View", + "The web view of this tab", + WEBKIT_TYPE_WEB_VIEW, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); +} + +static char *getInternalURI(const char *uri) +{ + /* Internally we use minibrowser-about: as about: prefix is ignored by WebKit. */ + if (g_str_has_prefix(uri, "about:") && !g_str_equal(uri, "about:blank")) + return g_strconcat(BROWSER_ABOUT_SCHEME, uri + strlen ("about"), NULL); + + return g_strdup(uri); +} + +/* Public API. */ +GtkWidget *browser_tab_new(WebKitWebView *view) +{ + g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(view), NULL); + + return GTK_WIDGET(g_object_new(BROWSER_TYPE_TAB, "view", view, NULL)); +} + +WebKitWebView *browser_tab_get_web_view(BrowserTab *tab) +{ + g_return_val_if_fail(BROWSER_IS_TAB(tab), NULL); + + return tab->webView; +} + +void browser_tab_load_uri(BrowserTab *tab, const char *uri) +{ + g_return_if_fail(BROWSER_IS_TAB(tab)); + g_return_if_fail(uri); + + if (!g_str_has_prefix(uri, "javascript:")) { + char *internalURI = getInternalURI(uri); + webkit_web_view_load_uri(tab->webView, internalURI); + g_free(internalURI); + return; + } + + webkit_web_view_run_javascript(tab->webView, strstr(uri, "javascript:"), NULL, NULL, NULL); +} + +GtkWidget *browser_tab_get_title_widget(BrowserTab *tab) +{ + g_return_val_if_fail(BROWSER_IS_TAB(tab), NULL); + + return tab->titleBox; +} + +void browser_tab_set_status_text(BrowserTab *tab, const char *text) +{ + g_return_if_fail(BROWSER_IS_TAB(tab)); + + gtk_label_set_text(GTK_LABEL(tab->statusLabel), text); + gtk_widget_set_visible(tab->statusLabel, !!text); +} + +void browser_tab_toggle_inspector(BrowserTab *tab) +{ + g_return_if_fail(BROWSER_IS_TAB(tab)); + + WebKitWebInspector *inspector = webkit_web_view_get_inspector(tab->webView); + if (!tab->inspectorIsVisible) { + webkit_web_inspector_show(inspector); + tab->inspectorIsVisible = TRUE; + } else + webkit_web_inspector_close(inspector); +} + +void browser_tab_start_search(BrowserTab *tab) +{ + g_return_if_fail(BROWSER_IS_TAB(tab)); + + if (!gtk_widget_get_visible(GTK_WIDGET(tab->searchBar))) + browser_search_bar_open(tab->searchBar); +} + +void browser_tab_stop_search(BrowserTab *tab) +{ + g_return_if_fail(BROWSER_IS_TAB(tab)); + + if (gtk_widget_get_visible(GTK_WIDGET(tab->searchBar))) + browser_search_bar_close(tab->searchBar); +} + +void browser_tab_add_accelerators(BrowserTab *tab, GtkAccelGroup *accelGroup) +{ + g_return_if_fail(BROWSER_IS_TAB(tab)); + g_return_if_fail(GTK_IS_ACCEL_GROUP(accelGroup)); + + browser_search_bar_add_accelerators(tab->searchBar, accelGroup); +} + +static gboolean fullScreenMessageTimeoutCallback(BrowserTab *tab) +{ + gtk_widget_hide(tab->fullScreenMessageLabel); + tab->fullScreenMessageLabelId = 0; + return FALSE; +} + +void browser_tab_enter_fullscreen(BrowserTab *tab) +{ + g_return_if_fail(BROWSER_IS_TAB(tab)); + + const gchar *titleOrURI = webkit_web_view_get_title(tab->webView); + if (!titleOrURI || !titleOrURI[0]) + titleOrURI = webkit_web_view_get_uri(tab->webView); + + gchar *message = g_strdup_printf("%s is now full screen. Press ESC or f to exit.", titleOrURI); + gtk_label_set_text(GTK_LABEL(tab->fullScreenMessageLabel), message); + g_free(message); + + gtk_widget_show(tab->fullScreenMessageLabel); + + tab->fullScreenMessageLabelId = g_timeout_add_seconds(2, (GSourceFunc)fullScreenMessageTimeoutCallback, tab); + g_source_set_name_by_id(tab->fullScreenMessageLabelId, "[WebKit] fullScreenMessageTimeoutCallback"); + + tab->wasSearchingWhenEnteredFullscreen = gtk_widget_get_visible(GTK_WIDGET(tab->searchBar)); + browser_tab_stop_search(tab); +} + +void browser_tab_leave_fullscreen(BrowserTab *tab) +{ + g_return_if_fail(BROWSER_IS_TAB(tab)); + + if (tab->fullScreenMessageLabelId) { + g_source_remove(tab->fullScreenMessageLabelId); + tab->fullScreenMessageLabelId = 0; + } + + gtk_widget_hide(tab->fullScreenMessageLabel); + + if (tab->wasSearchingWhenEnteredFullscreen) { + /* Opening the search bar steals the focus. Usually, we want + * this but not when coming back from fullscreen. + */ + GtkWindow *window = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(tab))); + GtkWidget *focusWidget = gtk_window_get_focus(window); + browser_tab_start_search(tab); + gtk_window_set_focus(window, focusWidget); + } +} diff --git a/Tools/MiniBrowser/gtk/BrowserTab.h b/Tools/MiniBrowser/gtk/BrowserTab.h new file mode 100644 index 000000000..9d5727591 --- /dev/null +++ b/Tools/MiniBrowser/gtk/BrowserTab.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 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. AND ITS CONTRIBUTORS ``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 ITS 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. + */ + +#ifndef BrowserTab_h +#define BrowserTab_h + +#include <gtk/gtk.h> +#include <webkit2/webkit2.h> + +G_BEGIN_DECLS + +#define BROWSER_TYPE_TAB (browser_tab_get_type()) +#define BROWSER_TAB(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), BROWSER_TYPE_TAB, BrowserTab)) +#define BROWSER_TAB_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), BROWSER_TYPE_TAB, BrowserTabClass)) +#define BROWSER_IS_TAB(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), BROWSER_TYPE_TAB)) +#define BROWSER_IS_TAB_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), BROWSER_TYPE_TAB)) +#define BROWSER_TAB_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), BROWSER_TYPE_TAB, BrowserTabClass)) + +typedef struct _BrowserTab BrowserTab; +typedef struct _BrowserTabClass BrowserTabClass; + +GType browser_tab_get_type(void); + +GtkWidget* browser_tab_new(WebKitWebView*); +WebKitWebView* browser_tab_get_web_view(BrowserTab*); +void browser_tab_load_uri(BrowserTab*, const char* uri); +GtkWidget *browser_tab_get_title_widget(BrowserTab*); +void browser_tab_set_status_text(BrowserTab*, const char* text); +void browser_tab_toggle_inspector(BrowserTab*); +void browser_tab_start_search(BrowserTab*); +void browser_tab_stop_search(BrowserTab*); +void browser_tab_add_accelerators(BrowserTab*, GtkAccelGroup*); +void browser_tab_enter_fullscreen(BrowserTab*); +void browser_tab_leave_fullscreen(BrowserTab*); + +G_END_DECLS + +#endif diff --git a/Tools/MiniBrowser/gtk/BrowserWindow.c b/Tools/MiniBrowser/gtk/BrowserWindow.c index b8a0d056c..b80157fa2 100644 --- a/Tools/MiniBrowser/gtk/BrowserWindow.c +++ b/Tools/MiniBrowser/gtk/BrowserWindow.c @@ -25,23 +25,23 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ +#if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H && defined(BUILDING_WITH_CMAKE) +#include "cmakeconfig.h" +#endif #include "BrowserWindow.h" #include "BrowserDownloadsBar.h" #include "BrowserSearchBar.h" #include "BrowserSettingsDialog.h" +#include "BrowserTab.h" #include <gdk/gdkkeysyms.h> #include <string.h> -enum { - PROP_0, - - PROP_VIEW -}; - struct _BrowserWindow { GtkWindow parent; + WebKitWebContext *webContext; + GtkAccelGroup *accelGroup; GtkWidget *mainBox; GtkWidget *toolbar; @@ -50,18 +50,21 @@ struct _BrowserWindow { GtkWidget *forwardItem; GtkWidget *zoomInItem; GtkWidget *zoomOutItem; - GtkWidget *statusLabel; + GtkWidget *boldItem; + GtkWidget *italicItem; + GtkWidget *underlineItem; + GtkWidget *strikethroughItem; GtkWidget *settingsDialog; - WebKitWebView *webView; + GtkWidget *notebook; + BrowserTab *activeTab; GtkWidget *downloadsBar; - BrowserSearchBar *searchBar; gboolean searchBarVisible; + gboolean fullScreenIsEnabled; GdkPixbuf *favicon; GtkWidget *reloadOrStopButton; - GtkWidget *fullScreenMessageLabel; GtkWindow *parentWindow; - guint fullScreenMessageLabelId; guint resetEntryProgressTimeoutId; + gchar *sessionFile; }; struct _BrowserWindowClass { @@ -69,36 +72,26 @@ struct _BrowserWindowClass { }; static const char *defaultWindowTitle = "WebKitGTK+ MiniBrowser"; -static const char *miniBrowserAboutScheme = "minibrowser-about"; static const gdouble minimumZoomLevel = 0.5; static const gdouble maximumZoomLevel = 3; +static const gdouble defaultZoomLevel = 1; static const gdouble zoomStep = 1.2; static gint windowCount = 0; G_DEFINE_TYPE(BrowserWindow, browser_window, GTK_TYPE_WINDOW) -static char *getInternalURI(const char *uri) -{ - // Internally we use minibrowser-about: as about: prefix is ignored by WebKit. - if (g_str_has_prefix(uri, "about:") && !g_str_equal(uri, "about:blank")) - return g_strconcat(miniBrowserAboutScheme, uri + strlen ("about"), NULL); - - return g_strdup(uri); -} - static char *getExternalURI(const char *uri) { - // From the user point of view we support about: prefix. - if (g_str_has_prefix(uri, miniBrowserAboutScheme)) - return g_strconcat("about", uri + strlen(miniBrowserAboutScheme), NULL); + /* From the user point of view we support about: prefix. */ + if (uri && g_str_has_prefix(uri, BROWSER_ABOUT_SCHEME)) + return g_strconcat("about", uri + strlen(BROWSER_ABOUT_SCHEME), NULL); return g_strdup(uri); } static void browserWindowSetStatusText(BrowserWindow *window, const char *text) { - gtk_label_set_text(GTK_LABEL(window->statusLabel), text); - gtk_widget_set_visible(window->statusLabel, !!text); + browser_tab_set_status_text(window->activeTab, text); } static void resetStatusText(GtkWidget *widget, BrowserWindow *window) @@ -113,20 +106,23 @@ static void activateUriEntryCallback(BrowserWindow *window) static void reloadOrStopCallback(BrowserWindow *window) { - if (webkit_web_view_is_loading(window->webView)) - webkit_web_view_stop_loading(window->webView); + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + if (webkit_web_view_is_loading(webView)) + webkit_web_view_stop_loading(webView); else - webkit_web_view_reload(window->webView); + webkit_web_view_reload(webView); } static void goBackCallback(BrowserWindow *window) { - webkit_web_view_go_back(window->webView); + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + webkit_web_view_go_back(webView); } static void goForwardCallback(BrowserWindow *window) { - webkit_web_view_go_forward(window->webView); + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + webkit_web_view_go_forward(webView); } static void settingsCallback(BrowserWindow *window) @@ -136,7 +132,8 @@ static void settingsCallback(BrowserWindow *window) return; } - window->settingsDialog = browser_settings_dialog_new(webkit_web_view_get_settings(window->webView)); + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + window->settingsDialog = browser_settings_dialog_new(webkit_web_view_get_settings(webView)); gtk_window_set_transient_for(GTK_WINDOW(window->settingsDialog), GTK_WINDOW(window)); g_object_add_weak_pointer(G_OBJECT(window->settingsDialog), (gpointer *)&window->settingsDialog); gtk_widget_show(window->settingsDialog); @@ -145,14 +142,23 @@ static void settingsCallback(BrowserWindow *window) static void webViewURIChanged(WebKitWebView *webView, GParamSpec *pspec, BrowserWindow *window) { char *externalURI = getExternalURI(webkit_web_view_get_uri(webView)); - gtk_entry_set_text(GTK_ENTRY(window->uriEntry), externalURI); - g_free(externalURI); + if (externalURI) { + gtk_entry_set_text(GTK_ENTRY(window->uriEntry), externalURI); + g_free(externalURI); + } else + gtk_entry_set_text(GTK_ENTRY(window->uriEntry), ""); } static void webViewTitleChanged(WebKitWebView *webView, GParamSpec *pspec, BrowserWindow *window) { const char *title = webkit_web_view_get_title(webView); - gtk_window_set_title(GTK_WINDOW(window), title ? title : defaultWindowTitle); + if (!title) + title = defaultWindowTitle; + char *privateTitle = NULL; + if (webkit_web_view_is_ephemeral(webView)) + privateTitle = g_strdup_printf("[Private] %s", title); + gtk_window_set_title(GTK_WINDOW(window), privateTitle ? privateTitle : title); + g_free(privateTitle); } static gboolean resetEntryProgress(BrowserWindow *window) @@ -196,7 +202,8 @@ static void browserWindowHistoryItemActivated(BrowserWindow *window, GtkAction * if (!item) return; - webkit_web_view_go_to_back_forward_list_item(window->webView, item); + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + webkit_web_view_go_to_back_forward_list_item(webView, item); } static GtkWidget *browserWindowCreateBackForwardMenu(BrowserWindow *window, GList *list) @@ -230,8 +237,9 @@ static GtkWidget *browserWindowCreateBackForwardMenu(BrowserWindow *window, GLis static void browserWindowUpdateNavigationActions(BrowserWindow *window, WebKitBackForwardList *backForwadlist) { - gtk_widget_set_sensitive(window->backItem, webkit_web_view_can_go_back(window->webView)); - gtk_widget_set_sensitive(window->forwardItem, webkit_web_view_can_go_forward(window->webView)); + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + gtk_widget_set_sensitive(window->backItem, webkit_web_view_can_go_back(webView)); + gtk_widget_set_sensitive(window->forwardItem, webkit_web_view_can_go_forward(webView)); GList *list = g_list_reverse(webkit_back_forward_list_get_back_list_with_limit(backForwadlist, 10)); gtk_menu_tool_button_set_menu(GTK_MENU_TOOL_BUTTON(window->backItem), @@ -249,24 +257,22 @@ static void backForwadlistChanged(WebKitBackForwardList *backForwadlist, WebKitB browserWindowUpdateNavigationActions(window, backForwadlist); } -static void geolocationRequestDialogCallback(GtkDialog *dialog, gint response, WebKitPermissionRequest *request) +static void webViewClose(WebKitWebView *webView, BrowserWindow *window) { - switch (response) { - case GTK_RESPONSE_YES: - webkit_permission_request_allow(request); - break; - default: - webkit_permission_request_deny(request); - break; + int tabsCount = gtk_notebook_get_n_pages(GTK_NOTEBOOK(window->notebook)); + if (tabsCount == 1) { + gtk_widget_destroy(GTK_WIDGET(window)); + return; } - gtk_widget_destroy(GTK_WIDGET(dialog)); - g_object_unref(request); -} - -static void webViewClose(WebKitWebView *webView, BrowserWindow *window) -{ - gtk_widget_destroy(GTK_WIDGET(window)); + int i; + for (i = 0; i < tabsCount; ++i) { + BrowserTab *tab = (BrowserTab *)gtk_notebook_get_nth_page(GTK_NOTEBOOK(window->notebook), i); + if (browser_tab_get_web_view(tab) == webView) { + gtk_widget_destroy(GTK_WIDGET(tab)); + return; + } + } } static void webViewRunAsModal(WebKitWebView *webView, BrowserWindow *window) @@ -297,65 +303,33 @@ static void webViewReadyToShow(WebKitWebView *webView, BrowserWindow *window) gtk_widget_show(GTK_WIDGET(window)); } -static gboolean fullScreenMessageTimeoutCallback(BrowserWindow *window) +static GtkWidget *webViewCreate(WebKitWebView *webView, WebKitNavigationAction *navigation, BrowserWindow *window) { - gtk_widget_hide(window->fullScreenMessageLabel); - window->fullScreenMessageLabelId = 0; - return FALSE; + WebKitWebView *newWebView = WEBKIT_WEB_VIEW(webkit_web_view_new_with_related_view(webView)); + webkit_web_view_set_settings(newWebView, webkit_web_view_get_settings(webView)); + + GtkWidget *newWindow = browser_window_new(GTK_WINDOW(window), window->webContext); + browser_window_append_view(BROWSER_WINDOW(newWindow), newWebView); + g_signal_connect(newWebView, "ready-to-show", G_CALLBACK(webViewReadyToShow), newWindow); + g_signal_connect(newWebView, "run-as-modal", G_CALLBACK(webViewRunAsModal), newWindow); + g_signal_connect(newWebView, "close", G_CALLBACK(webViewClose), newWindow); + return GTK_WIDGET(newWebView); } static gboolean webViewEnterFullScreen(WebKitWebView *webView, BrowserWindow *window) { - gchar *titleOrURI = g_strdup(webkit_web_view_get_title(window->webView)); - if (!titleOrURI) - titleOrURI = getExternalURI(webkit_web_view_get_uri(window->webView)); - gchar *message = g_strdup_printf("%s is now full screen. Press ESC or f to exit.", titleOrURI); - gtk_label_set_text(GTK_LABEL(window->fullScreenMessageLabel), message); - g_free(titleOrURI); - g_free(message); - - gtk_widget_show(window->fullScreenMessageLabel); - - window->fullScreenMessageLabelId = g_timeout_add_seconds(2, (GSourceFunc)fullScreenMessageTimeoutCallback, window); - g_source_set_name_by_id(window->fullScreenMessageLabelId, "[WebKit] fullScreenMessageTimeoutCallback"); gtk_widget_hide(window->toolbar); - window->searchBarVisible = gtk_widget_get_visible(GTK_WIDGET(window->searchBar)); - browser_search_bar_close(window->searchBar); - + browser_tab_enter_fullscreen(window->activeTab); return FALSE; } static gboolean webViewLeaveFullScreen(WebKitWebView *webView, BrowserWindow *window) { - if (window->fullScreenMessageLabelId) { - g_source_remove(window->fullScreenMessageLabelId); - window->fullScreenMessageLabelId = 0; - } - gtk_widget_hide(window->fullScreenMessageLabel); + browser_tab_leave_fullscreen(window->activeTab); gtk_widget_show(window->toolbar); - if (window->searchBarVisible) { - // Opening the search bar steals the focus. Usually, we want - // this but not when coming back from fullscreen. - GtkWidget *focusWidget = gtk_window_get_focus(GTK_WINDOW(window)); - browser_search_bar_open(window->searchBar); - gtk_window_set_focus(GTK_WINDOW(window), focusWidget); - } - return FALSE; } -static GtkWidget *webViewCreate(WebKitWebView *webView, BrowserWindow *window) -{ - WebKitWebView *newWebView = WEBKIT_WEB_VIEW(webkit_web_view_new_with_related_view(webView)); - webkit_web_view_set_settings(newWebView, webkit_web_view_get_settings(webView)); - - GtkWidget *newWindow = browser_window_new(newWebView, GTK_WINDOW(window)); - g_signal_connect(newWebView, "ready-to-show", G_CALLBACK(webViewReadyToShow), newWindow); - g_signal_connect(newWebView, "run-as-modal", G_CALLBACK(webViewRunAsModal), newWindow); - g_signal_connect(newWebView, "close", G_CALLBACK(webViewClose), newWindow); - return GTK_WIDGET(newWebView); -} - static gboolean webViewLoadFailed(WebKitWebView *webView, WebKitLoadEvent loadEvent, const char *failingURI, GError *error, BrowserWindow *window) { gtk_entry_set_progress_fraction(GTK_ENTRY(window->uriEntry), 0.); @@ -364,57 +338,28 @@ static gboolean webViewLoadFailed(WebKitWebView *webView, WebKitLoadEvent loadEv static gboolean webViewDecidePolicy(WebKitWebView *webView, WebKitPolicyDecision *decision, WebKitPolicyDecisionType decisionType, BrowserWindow *window) { - switch (decisionType) { - case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION: { - WebKitNavigationPolicyDecision *navigationDecision = WEBKIT_NAVIGATION_POLICY_DECISION(decision); - if (webkit_navigation_policy_decision_get_navigation_type(navigationDecision) != WEBKIT_NAVIGATION_TYPE_LINK_CLICKED - || webkit_navigation_policy_decision_get_mouse_button(navigationDecision) != GDK_BUTTON_MIDDLE) - return FALSE; - - // Opening a new window if link clicked with the middle button. - WebKitWebView *newWebView = WEBKIT_WEB_VIEW(webkit_web_view_new_with_context(webkit_web_view_get_context(webView))); - GtkWidget *newWindow = browser_window_new(newWebView, GTK_WINDOW(window)); - webkit_web_view_load_request(newWebView, webkit_navigation_policy_decision_get_request(navigationDecision)); - gtk_widget_show(newWindow); - - webkit_policy_decision_ignore(decision); - return TRUE; - } - case WEBKIT_POLICY_DECISION_TYPE_RESPONSE: { - WebKitResponsePolicyDecision *responseDecision = WEBKIT_RESPONSE_POLICY_DECISION(decision); - if (webkit_response_policy_decision_is_mime_type_supported(responseDecision)) - return FALSE; - - WebKitWebResource *mainResource = webkit_web_view_get_main_resource(webView); - WebKitURIRequest *request = webkit_response_policy_decision_get_request(responseDecision); - const char *requestURI = webkit_uri_request_get_uri(request); - if (g_strcmp0(webkit_web_resource_get_uri(mainResource), requestURI)) - return FALSE; - - webkit_policy_decision_download(decision); - return TRUE; - } - case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION: - default: + if (decisionType != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) return FALSE; - } -} -static gboolean webViewDecidePermissionRequest(WebKitWebView *webView, WebKitPermissionRequest *request, BrowserWindow *window) -{ - if (!WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(request)) + WebKitNavigationAction *navigationAction = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision)); + if (webkit_navigation_action_get_navigation_type(navigationAction) != WEBKIT_NAVIGATION_TYPE_LINK_CLICKED + || webkit_navigation_action_get_mouse_button(navigationAction) != GDK_BUTTON_MIDDLE) return FALSE; - GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_QUESTION, - GTK_BUTTONS_YES_NO, - "Geolocation request"); + /* Multiple tabs are not allowed in editor mode. */ + if (webkit_web_view_is_editable(webView)) + return FALSE; - gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "Allow geolocation request?"); - g_signal_connect(dialog, "response", G_CALLBACK(geolocationRequestDialogCallback), g_object_ref(request)); - gtk_widget_show(dialog); + /* Opening a new tab if link clicked with the middle button. */ + WebKitWebView *newWebView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, + "web-context", webkit_web_view_get_context(webView), + "settings", webkit_web_view_get_settings(webView), + "user-content-manager", webkit_web_view_get_user_content_manager(webView), + NULL)); + browser_window_append_view(window, newWebView); + webkit_web_view_load_request(newWebView, webkit_navigation_action_get_request(navigationAction)); + webkit_policy_decision_ignore(decision); return TRUE; } @@ -429,16 +374,53 @@ static void webViewMouseTargetChanged(WebKitWebView *webView, WebKitHitTestResul static gboolean browserWindowCanZoomIn(BrowserWindow *window) { - gdouble zoomLevel = webkit_web_view_get_zoom_level(window->webView) * zoomStep; + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + gdouble zoomLevel = webkit_web_view_get_zoom_level(webView) * zoomStep; return zoomLevel < maximumZoomLevel; } static gboolean browserWindowCanZoomOut(BrowserWindow *window) { - gdouble zoomLevel = webkit_web_view_get_zoom_level(window->webView) / zoomStep; + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + gdouble zoomLevel = webkit_web_view_get_zoom_level(webView) / zoomStep; return zoomLevel > minimumZoomLevel; } +static gboolean browserWindowZoomIn(BrowserWindow *window) +{ + if (browserWindowCanZoomIn(window)) { + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + gdouble zoomLevel = webkit_web_view_get_zoom_level(webView) * zoomStep; + webkit_web_view_set_zoom_level(webView, zoomLevel); + return TRUE; + } + return FALSE; +} + +static gboolean browserWindowZoomOut(BrowserWindow *window) +{ + if (browserWindowCanZoomOut(window)) { + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + gdouble zoomLevel = webkit_web_view_get_zoom_level(webView) / zoomStep; + webkit_web_view_set_zoom_level(webView, zoomLevel); + return TRUE; + } + return FALSE; +} + +static gboolean scrollEventCallback(WebKitWebView *webView, const GdkEventScroll *event, BrowserWindow *window) +{ + GdkModifierType mod = gtk_accelerator_get_default_mod_mask(); + + if ((event->state & mod) != GDK_CONTROL_MASK) + return FALSE; + + if (event->delta_y < 0) + return browserWindowZoomIn(window); + + return browserWindowZoomOut(window); +} + static void browserWindowUpdateZoomActions(BrowserWindow *window) { gtk_widget_set_sensitive(window->zoomInItem, browserWindowCanZoomIn(window)); @@ -459,10 +441,10 @@ static void updateUriEntryIcon(BrowserWindow *window) gtk_entry_set_icon_from_stock(entry, GTK_ENTRY_ICON_PRIMARY, GTK_STOCK_NEW); } -static void faviconChanged(GObject *object, GParamSpec *paramSpec, BrowserWindow *window) +static void faviconChanged(WebKitWebView *webView, GParamSpec *paramSpec, BrowserWindow *window) { GdkPixbuf *favicon = NULL; - cairo_surface_t *surface = webkit_web_view_get_favicon(window->webView); + cairo_surface_t *surface = webkit_web_view_get_favicon(webView); if (surface) { int width = cairo_image_surface_get_width(surface); @@ -477,32 +459,194 @@ static void faviconChanged(GObject *object, GParamSpec *paramSpec, BrowserWindow updateUriEntryIcon(window); } -static void webViewIsLoadingChanged(GObject *object, GParamSpec *paramSpec, BrowserWindow *window) +static void webViewIsLoadingChanged(WebKitWebView *webView, GParamSpec *paramSpec, BrowserWindow *window) { - gboolean isLoading = webkit_web_view_is_loading(window->webView); + gboolean isLoading = webkit_web_view_is_loading(webView); gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(window->reloadOrStopButton), isLoading ? GTK_STOCK_STOP : GTK_STOCK_REFRESH); } static void zoomInCallback(BrowserWindow *window) { - gdouble zoomLevel = webkit_web_view_get_zoom_level(window->webView) * zoomStep; - webkit_web_view_set_zoom_level(window->webView, zoomLevel); + browserWindowZoomIn(window); } static void zoomOutCallback(BrowserWindow *window) { - gdouble zoomLevel = webkit_web_view_get_zoom_level(window->webView) / zoomStep; - webkit_web_view_set_zoom_level(window->webView, zoomLevel); + browserWindowZoomOut(window); +} + +static void defaultZoomCallback(BrowserWindow *window) +{ + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + webkit_web_view_set_zoom_level(webView, defaultZoomLevel); } static void searchCallback(BrowserWindow *window) { - browser_search_bar_open(window->searchBar); + browser_tab_start_search(window->activeTab); +} + +static void newTabCallback(BrowserWindow *window) +{ + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + if (webkit_web_view_is_editable(webView)) + return; + + browser_window_append_view(window, WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, + "web-context", webkit_web_view_get_context(webView), + "settings", webkit_web_view_get_settings(webView), + "user-content-manager", webkit_web_view_get_user_content_manager(webView), + NULL))); + gtk_notebook_set_current_page(GTK_NOTEBOOK(window->notebook), -1); +} + +static void toggleWebInspector(BrowserWindow *window) +{ + browser_tab_toggle_inspector(window->activeTab); +} + +static void openPrivateWindow(BrowserWindow *window) +{ + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + WebKitWebView *newWebView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, + "web-context", webkit_web_view_get_context(webView), + "settings", webkit_web_view_get_settings(webView), + "user-content-manager", webkit_web_view_get_user_content_manager(webView), + "is-ephemeral", TRUE, + NULL)); + GtkWidget *newWindow = browser_window_new(GTK_WINDOW(window), window->webContext); + browser_window_append_view(BROWSER_WINDOW(newWindow), newWebView); + gtk_widget_show(GTK_WIDGET(newWindow)); +} + +static void reloadPage(BrowserWindow *window) +{ + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + webkit_web_view_reload(webView); +} + +static void reloadPageIgnoringCache(BrowserWindow *window) +{ + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + webkit_web_view_reload_bypass_cache(webView); +} + +static void stopPageLoad(BrowserWindow *window) +{ + browser_tab_stop_search(window->activeTab); + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + if (webkit_web_view_is_loading(webView)) + webkit_web_view_stop_loading(webView); +} + +static void loadHomePage(BrowserWindow *window) +{ + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + webkit_web_view_load_uri(webView, BROWSER_DEFAULT_URL); +} + +static gboolean toggleFullScreen(BrowserWindow *window, gpointer user_data) +{ + if (!window->fullScreenIsEnabled) { + gtk_window_fullscreen(GTK_WINDOW(window)); + gtk_widget_hide(window->toolbar); + window->fullScreenIsEnabled = TRUE; + } else { + gtk_window_unfullscreen(GTK_WINDOW(window)); + gtk_widget_show(window->toolbar); + window->fullScreenIsEnabled = FALSE; + } + return TRUE; +} + +static void webKitPrintOperationFailedCallback(WebKitPrintOperation *printOperation, GError *error) +{ + g_warning("Print failed: '%s'", error->message); +} + +static gboolean printPage(BrowserWindow *window, gpointer user_data) +{ + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + WebKitPrintOperation *printOperation = webkit_print_operation_new(webView); + + g_signal_connect(printOperation, "failed", G_CALLBACK(webKitPrintOperationFailedCallback), NULL); + webkit_print_operation_run_dialog(printOperation, GTK_WINDOW(window)); + g_object_unref(printOperation); + + return TRUE; +} + +static void editingCommandCallback(GtkWidget *widget, BrowserWindow *window) +{ + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + webkit_web_view_execute_editing_command(webView, gtk_widget_get_name(widget)); +} + +static void insertImageCommandCallback(GtkWidget *widget, BrowserWindow *window) +{ + GtkWidget *fileChooser = gtk_file_chooser_dialog_new("Insert Image", GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_OPEN, + "Cancel", GTK_RESPONSE_CANCEL, "Open", GTK_RESPONSE_ACCEPT, NULL); + + GtkFileFilter *filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, "Images"); + gtk_file_filter_add_pixbuf_formats(filter); + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fileChooser), filter); + + if (gtk_dialog_run(GTK_DIALOG(fileChooser)) == GTK_RESPONSE_ACCEPT) { + char *uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(fileChooser)); + if (uri) { + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + webkit_web_view_execute_editing_command_with_argument(webView, WEBKIT_EDITING_COMMAND_INSERT_IMAGE, uri); + g_free(uri); + } + } + + gtk_widget_destroy(fileChooser); +} + +static void insertLinkCommandCallback(GtkWidget *widget, BrowserWindow *window) +{ + GtkWidget *dialog = gtk_dialog_new_with_buttons("Insert Link", GTK_WINDOW(window), GTK_DIALOG_MODAL, "Insert", GTK_RESPONSE_ACCEPT, NULL); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT); + GtkWidget *entry = gtk_entry_new(); + gtk_entry_set_placeholder_text(GTK_ENTRY(entry), "URL"); + gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE); + gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), entry); + gtk_widget_show(entry); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + const char *url = gtk_entry_get_text(GTK_ENTRY(entry)); + if (url && *url) { + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + webkit_web_view_execute_editing_command_with_argument(webView, WEBKIT_EDITING_COMMAND_CREATE_LINK, url); + } + } + + gtk_widget_destroy(dialog); +} + +static void browserWindowEditingCommandToggleButtonSetActive(BrowserWindow *window, GtkWidget *button, gboolean active) +{ + g_signal_handlers_block_by_func(button, G_CALLBACK(editingCommandCallback), window); + gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(button), active); + g_signal_handlers_unblock_by_func(button, G_CALLBACK(editingCommandCallback), window); +} + +static void typingAttributesChanged(WebKitEditorState *editorState, GParamSpec *spec, BrowserWindow *window) +{ + unsigned typingAttributes = webkit_editor_state_get_typing_attributes(editorState); + browserWindowEditingCommandToggleButtonSetActive(window, window->boldItem, typingAttributes & WEBKIT_EDITOR_TYPING_ATTRIBUTE_BOLD); + browserWindowEditingCommandToggleButtonSetActive(window, window->italicItem, typingAttributes & WEBKIT_EDITOR_TYPING_ATTRIBUTE_ITALIC); + browserWindowEditingCommandToggleButtonSetActive(window, window->underlineItem, typingAttributes & WEBKIT_EDITOR_TYPING_ATTRIBUTE_UNDERLINE); + browserWindowEditingCommandToggleButtonSetActive(window, window->strikethroughItem, typingAttributes & WEBKIT_EDITOR_TYPING_ATTRIBUTE_STRIKETHROUGH); } static void browserWindowFinalize(GObject *gObject) { BrowserWindow *window = BROWSER_WINDOW(gObject); + + g_signal_handlers_disconnect_matched(window->webContext, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, window); + if (window->favicon) { g_object_unref(window->favicon); window->favicon = NULL; @@ -513,42 +657,207 @@ static void browserWindowFinalize(GObject *gObject) window->accelGroup = NULL; } - if (window->fullScreenMessageLabelId) - g_source_remove(window->fullScreenMessageLabelId); - if (window->resetEntryProgressTimeoutId) g_source_remove(window->resetEntryProgressTimeoutId); + g_free(window->sessionFile); + G_OBJECT_CLASS(browser_window_parent_class)->finalize(gObject); if (g_atomic_int_dec_and_test(&windowCount)) gtk_main_quit(); } -static void browserWindowGetProperty(GObject *object, guint propId, GValue *value, GParamSpec *pspec) +static void browserWindowSetupEditorToolbar(BrowserWindow *window) { - BrowserWindow *window = BROWSER_WINDOW(object); + GtkWidget *toolbar = gtk_toolbar_new(); + gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_HORIZONTAL); + gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ); - switch (propId) { - case PROP_VIEW: - g_value_set_object(value, browser_window_get_view(window)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); - } + GtkToolItem *item = gtk_toggle_tool_button_new_from_stock(GTK_STOCK_BOLD); + window->boldItem = GTK_WIDGET(item); + gtk_widget_set_name(GTK_WIDGET(item), "Bold"); + g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_toggle_tool_button_new_from_stock(GTK_STOCK_ITALIC); + window->italicItem = GTK_WIDGET(item); + gtk_widget_set_name(GTK_WIDGET(item), "Italic"); + g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_toggle_tool_button_new_from_stock(GTK_STOCK_UNDERLINE); + window->underlineItem = GTK_WIDGET(item); + gtk_widget_set_name(GTK_WIDGET(item), "Underline"); + g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_toggle_tool_button_new_from_stock(GTK_STOCK_STRIKETHROUGH); + gtk_widget_set_name(GTK_WIDGET(item), "Strikethrough"); + window->strikethroughItem = GTK_WIDGET(item); + g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_separator_tool_item_new(); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_tool_button_new_from_stock(GTK_STOCK_CUT); + gtk_widget_set_name(GTK_WIDGET(item), WEBKIT_EDITING_COMMAND_CUT); + g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_tool_button_new_from_stock(GTK_STOCK_COPY); + gtk_widget_set_name(GTK_WIDGET(item), WEBKIT_EDITING_COMMAND_COPY); + g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_tool_button_new_from_stock(GTK_STOCK_PASTE); + gtk_widget_set_name(GTK_WIDGET(item), WEBKIT_EDITING_COMMAND_PASTE); + g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_separator_tool_item_new(); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_tool_button_new_from_stock(GTK_STOCK_UNDO); + gtk_widget_set_name(GTK_WIDGET(item), WEBKIT_EDITING_COMMAND_UNDO); + g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_tool_button_new_from_stock(GTK_STOCK_REDO); + gtk_widget_set_name(GTK_WIDGET(item), WEBKIT_EDITING_COMMAND_REDO); + g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_separator_tool_item_new(); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_radio_tool_button_new_from_stock(NULL, GTK_STOCK_JUSTIFY_LEFT); + GSList *justifyRadioGroup = gtk_radio_tool_button_get_group(GTK_RADIO_TOOL_BUTTON(item)); + gtk_widget_set_name(GTK_WIDGET(item), "JustifyLeft"); + g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_radio_tool_button_new_from_stock(justifyRadioGroup, GTK_STOCK_JUSTIFY_CENTER); + justifyRadioGroup = gtk_radio_tool_button_get_group(GTK_RADIO_TOOL_BUTTON(item)); + gtk_widget_set_name(GTK_WIDGET(item), "JustifyCenter"); + g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_radio_tool_button_new_from_stock(justifyRadioGroup, GTK_STOCK_JUSTIFY_RIGHT); + gtk_widget_set_name(GTK_WIDGET(item), "JustifyRight"); + g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_separator_tool_item_new(); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_tool_button_new_from_stock(GTK_STOCK_INDENT); + gtk_widget_set_name(GTK_WIDGET(item), "Indent"); + g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_tool_button_new_from_stock(GTK_STOCK_UNINDENT); + gtk_widget_set_name(GTK_WIDGET(item), "Outdent"); + g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_separator_tool_item_new(); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_tool_button_new(NULL, NULL); + gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), "insert-image"); + g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(insertImageCommandCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_tool_button_new(NULL, NULL); + gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), "insert-link"); + g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(insertLinkCommandCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_show(GTK_WIDGET(item)); + + gtk_box_pack_start(GTK_BOX(window->mainBox), toolbar, FALSE, FALSE, 0); + gtk_box_reorder_child(GTK_BOX(window->mainBox), toolbar, 1); + gtk_widget_show(toolbar); } -static void browserWindowSetProperty(GObject *object, guint propId, const GValue *value, GParamSpec *pspec) +static void browserWindowSwitchTab(GtkNotebook *notebook, BrowserTab *tab, guint tabIndex, BrowserWindow *window) { - BrowserWindow* window = BROWSER_WINDOW(object); + if (window->activeTab == tab) + return; - switch (propId) { - case PROP_VIEW: - window->webView = g_value_get_object(value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); + if (window->activeTab) { + browser_tab_set_status_text(window->activeTab, NULL); + g_clear_object(&window->favicon); + + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + g_signal_handlers_disconnect_by_data(webView, window); + + /* We always want close to be connected even for not active tabs */ + g_signal_connect(webView, "close", G_CALLBACK(webViewClose), window); + + WebKitBackForwardList *backForwadlist = webkit_web_view_get_back_forward_list(webView); + g_signal_handlers_disconnect_by_data(backForwadlist, window); + } + + window->activeTab = tab; + + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + if (webkit_web_view_is_editable(webView)) { + browserWindowSetupEditorToolbar(window); + g_signal_connect(webkit_web_view_get_editor_state(webView), "notify::typing-attributes", G_CALLBACK(typingAttributesChanged), window); } + webViewURIChanged(webView, NULL, window); + webViewTitleChanged(webView, NULL, window); + webViewIsLoadingChanged(webView, NULL, window); + faviconChanged(webView, NULL, window); + browserWindowUpdateZoomActions(window); + if (webkit_web_view_is_loading(webView)) + webViewLoadProgressChanged(webView, NULL, window); + + g_signal_connect(webView, "notify::uri", G_CALLBACK(webViewURIChanged), window); + g_signal_connect(webView, "notify::estimated-load-progress", G_CALLBACK(webViewLoadProgressChanged), window); + g_signal_connect(webView, "notify::title", G_CALLBACK(webViewTitleChanged), window); + g_signal_connect(webView, "notify::is-loading", G_CALLBACK(webViewIsLoadingChanged), window); + g_signal_connect(webView, "create", G_CALLBACK(webViewCreate), window); + g_signal_connect(webView, "close", G_CALLBACK(webViewClose), window); + g_signal_connect(webView, "load-failed", G_CALLBACK(webViewLoadFailed), window); + g_signal_connect(webView, "decide-policy", G_CALLBACK(webViewDecidePolicy), window); + g_signal_connect(webView, "mouse-target-changed", G_CALLBACK(webViewMouseTargetChanged), window); + g_signal_connect(webView, "notify::zoom-level", G_CALLBACK(webViewZoomLevelChanged), window); + g_signal_connect(webView, "notify::favicon", G_CALLBACK(faviconChanged), window); + g_signal_connect(webView, "enter-fullscreen", G_CALLBACK(webViewEnterFullScreen), window); + g_signal_connect(webView, "leave-fullscreen", G_CALLBACK(webViewLeaveFullScreen), window); + g_signal_connect(webView, "scroll-event", G_CALLBACK(scrollEventCallback), window); + + WebKitBackForwardList *backForwadlist = webkit_web_view_get_back_forward_list(webView); + browserWindowUpdateNavigationActions(window, backForwadlist); + g_signal_connect(backForwadlist, "changed", G_CALLBACK(backForwadlistChanged), window); +} + +static void browserWindowTabAddedOrRemoved(GtkNotebook *notebook, BrowserTab *tab, guint tabIndex, BrowserWindow *window) +{ + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(window->notebook), gtk_notebook_get_n_pages(notebook) > 1); } static void browser_window_init(BrowserWindow *window) @@ -567,6 +876,64 @@ static void browser_window_init(BrowserWindow *window) window->accelGroup = gtk_accel_group_new(); gtk_window_add_accel_group(GTK_WINDOW(window), window->accelGroup); + /* Global accelerators */ + gtk_accel_group_connect(window->accelGroup, GDK_KEY_I, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(toggleWebInspector), window, NULL)); + gtk_accel_group_connect(window->accelGroup, GDK_KEY_F12, 0, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(toggleWebInspector), window, NULL)); + gtk_accel_group_connect(window->accelGroup, GDK_KEY_P, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(openPrivateWindow), window, NULL)); + + /* Reload page */ + gtk_accel_group_connect(window->accelGroup, GDK_KEY_F5, 0, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(reloadPage), window, NULL)); + gtk_accel_group_connect(window->accelGroup, GDK_KEY_R, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(reloadPage), window, NULL)); + + /* Reload page ignoring cache */ + gtk_accel_group_connect(window->accelGroup, GDK_KEY_F5, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(reloadPageIgnoringCache), window, NULL)); + gtk_accel_group_connect(window->accelGroup, GDK_KEY_R, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(reloadPageIgnoringCache), window, NULL)); + + /* Stop page load */ + gtk_accel_group_connect(window->accelGroup, GDK_KEY_F6, 0, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(stopPageLoad), window, NULL)); + gtk_accel_group_connect(window->accelGroup, GDK_KEY_Escape, 0, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(stopPageLoad), window, NULL)); + + /* Load home page */ + gtk_accel_group_connect(window->accelGroup, GDK_KEY_Home, GDK_MOD1_MASK, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(loadHomePage), window, NULL)); + + /* Zoom in, zoom out and default zoom*/ + gtk_accel_group_connect(window->accelGroup, GDK_KEY_equal, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(zoomInCallback), window, NULL)); + gtk_accel_group_connect(window->accelGroup, GDK_KEY_KP_Add, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(zoomInCallback), window, NULL)); + gtk_accel_group_connect(window->accelGroup, GDK_KEY_minus, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(zoomOutCallback), window, NULL)); + gtk_accel_group_connect(window->accelGroup, GDK_KEY_KP_Subtract, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(zoomOutCallback), window, NULL)); + gtk_accel_group_connect(window->accelGroup, GDK_KEY_0, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(defaultZoomCallback), window, NULL)); + gtk_accel_group_connect(window->accelGroup, GDK_KEY_KP_0, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(defaultZoomCallback), window, NULL)); + + /* Toggle fullscreen */ + gtk_accel_group_connect(window->accelGroup, GDK_KEY_F11, 0, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(toggleFullScreen), window, NULL)); + + /* Quit */ + gtk_accel_group_connect(window->accelGroup, GDK_KEY_Q, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(gtk_widget_destroy), window, NULL)); + gtk_accel_group_connect(window->accelGroup, GDK_KEY_W, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(gtk_widget_destroy), window, NULL)); + + /* Print */ + gtk_accel_group_connect(window->accelGroup, GDK_KEY_P, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, + g_cclosure_new_swap(G_CALLBACK(printPage), window, NULL)); + GtkWidget *toolbar = gtk_toolbar_new(); window->toolbar = toolbar; gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_HORIZONTAL); @@ -609,6 +976,18 @@ static void browser_window_init(BrowserWindow *window) gtk_widget_add_accelerator(GTK_WIDGET(item), "clicked", window->accelGroup, GDK_KEY_F, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); gtk_widget_show(GTK_WIDGET(item)); + item = gtk_tool_button_new_from_stock(GTK_STOCK_HOME); + g_signal_connect_swapped(item, "clicked", G_CALLBACK(loadHomePage), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_add_accelerator(GTK_WIDGET(item), "clicked", window->accelGroup, GDK_KEY_Home, GDK_MOD1_MASK, GTK_ACCEL_VISIBLE); + gtk_widget_show(GTK_WIDGET(item)); + + item = gtk_tool_button_new(gtk_image_new_from_icon_name("tab-new", GTK_ICON_SIZE_SMALL_TOOLBAR), NULL); + g_signal_connect_swapped(item, "clicked", G_CALLBACK(newTabCallback), window); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1); + gtk_widget_add_accelerator(GTK_WIDGET(item), "clicked", window->accelGroup, GDK_KEY_T, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); + gtk_widget_show_all(GTK_WIDGET(item)); + item = gtk_tool_item_new(); gtk_tool_item_set_expand(item, TRUE); gtk_container_add(GTK_CONTAINER(item), window->uriEntry); @@ -628,60 +1007,44 @@ static void browser_window_init(BrowserWindow *window) gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0); gtk_widget_show(toolbar); + window->notebook = gtk_notebook_new(); + g_signal_connect(window->notebook, "switch-page", G_CALLBACK(browserWindowSwitchTab), window); + g_signal_connect(window->notebook, "page-added", G_CALLBACK(browserWindowTabAddedOrRemoved), window); + g_signal_connect(window->notebook, "page-removed", G_CALLBACK(browserWindowTabAddedOrRemoved), window); + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(window->notebook), FALSE); + gtk_notebook_set_show_border(GTK_NOTEBOOK(window->notebook), FALSE); + gtk_box_pack_start(GTK_BOX(window->mainBox), window->notebook, TRUE, TRUE, 0); + gtk_widget_show(window->notebook); + gtk_container_add(GTK_CONTAINER(window), vbox); gtk_widget_show(vbox); } static void browserWindowConstructed(GObject *gObject) { - BrowserWindow *window = BROWSER_WINDOW(gObject); - - browserWindowUpdateZoomActions(window); - - g_signal_connect(window->webView, "notify::uri", G_CALLBACK(webViewURIChanged), window); - g_signal_connect(window->webView, "notify::estimated-load-progress", G_CALLBACK(webViewLoadProgressChanged), window); - g_signal_connect(window->webView, "notify::title", G_CALLBACK(webViewTitleChanged), window); - g_signal_connect(window->webView, "create", G_CALLBACK(webViewCreate), window); - g_signal_connect(window->webView, "load-failed", G_CALLBACK(webViewLoadFailed), window); - g_signal_connect(window->webView, "decide-policy", G_CALLBACK(webViewDecidePolicy), window); - g_signal_connect(window->webView, "permission-request", G_CALLBACK(webViewDecidePermissionRequest), window); - g_signal_connect(window->webView, "mouse-target-changed", G_CALLBACK(webViewMouseTargetChanged), window); - g_signal_connect(window->webView, "notify::zoom-level", G_CALLBACK(webViewZoomLevelChanged), window); - g_signal_connect(window->webView, "notify::favicon", G_CALLBACK(faviconChanged), window); - g_signal_connect(window->webView, "enter-fullscreen", G_CALLBACK(webViewEnterFullScreen), window); - g_signal_connect(window->webView, "leave-fullscreen", G_CALLBACK(webViewLeaveFullScreen), window); - g_signal_connect(window->webView, "notify::is-loading", G_CALLBACK(webViewIsLoadingChanged), window); - - g_signal_connect(webkit_web_view_get_context(window->webView), "download-started", G_CALLBACK(downloadStarted), window); - - window->searchBar = BROWSER_SEARCH_BAR(browser_search_bar_new(window->webView)); - browser_search_bar_add_accelerators(window->searchBar, window->accelGroup); - gtk_box_pack_start(GTK_BOX(window->mainBox), GTK_WIDGET(window->searchBar), FALSE, FALSE, 0); - - WebKitBackForwardList *backForwadlist = webkit_web_view_get_back_forward_list(window->webView); - g_signal_connect(backForwadlist, "changed", G_CALLBACK(backForwadlistChanged), window); - - GtkWidget *overlay = gtk_overlay_new(); - gtk_box_pack_start(GTK_BOX(window->mainBox), overlay, TRUE, TRUE, 0); - gtk_widget_show(overlay); + G_OBJECT_CLASS(browser_window_parent_class)->constructed(gObject); +} - window->statusLabel = gtk_label_new(NULL); - gtk_widget_set_halign(window->statusLabel, GTK_ALIGN_START); - gtk_widget_set_valign(window->statusLabel, GTK_ALIGN_END); - gtk_widget_set_margin_left(window->statusLabel, 1); - gtk_widget_set_margin_right(window->statusLabel, 1); - gtk_widget_set_margin_top(window->statusLabel, 1); - gtk_widget_set_margin_bottom(window->statusLabel, 1); - gtk_overlay_add_overlay(GTK_OVERLAY(overlay), window->statusLabel); +static void browserWindowSaveSession(BrowserWindow *window) +{ + if (!window->sessionFile) + return; - gtk_container_add(GTK_CONTAINER(overlay), GTK_WIDGET(window->webView)); + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + WebKitWebViewSessionState *state = webkit_web_view_get_session_state(webView); + GBytes *bytes = webkit_web_view_session_state_serialize(state); + webkit_web_view_session_state_unref(state); + g_file_set_contents(window->sessionFile, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes), NULL); + g_bytes_unref(bytes); +} - window->fullScreenMessageLabel = gtk_label_new(NULL); - gtk_widget_set_halign(window->fullScreenMessageLabel, GTK_ALIGN_CENTER); - gtk_widget_set_valign(window->fullScreenMessageLabel, GTK_ALIGN_CENTER); - gtk_widget_set_no_show_all(window->fullScreenMessageLabel, TRUE); - gtk_overlay_add_overlay(GTK_OVERLAY(overlay), window->fullScreenMessageLabel); - gtk_widget_show(GTK_WIDGET(window->webView)); +static gboolean browserWindowDeleteEvent(GtkWidget *widget, GdkEventAny* event) +{ + BrowserWindow *window = BROWSER_WINDOW(widget); + browserWindowSaveSession(window); + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + webkit_web_view_try_close(webView); + return TRUE; } static void browser_window_class_init(BrowserWindowClass *klass) @@ -689,27 +1052,22 @@ static void browser_window_class_init(BrowserWindowClass *klass) GObjectClass *gobjectClass = G_OBJECT_CLASS(klass); gobjectClass->constructed = browserWindowConstructed; - gobjectClass->get_property = browserWindowGetProperty; - gobjectClass->set_property = browserWindowSetProperty; gobjectClass->finalize = browserWindowFinalize; - g_object_class_install_property(gobjectClass, - PROP_VIEW, - g_param_spec_object("view", - "View", - "The web view of this window", - WEBKIT_TYPE_WEB_VIEW, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + GtkWidgetClass *widgetClass = GTK_WIDGET_CLASS(klass); + widgetClass->delete_event = browserWindowDeleteEvent; } -// Public API. -GtkWidget *browser_window_new(WebKitWebView *view, GtkWindow *parent) +/* Public API. */ +GtkWidget *browser_window_new(GtkWindow *parent, WebKitWebContext *webContext) { - g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(view), 0); + g_return_val_if_fail(WEBKIT_IS_WEB_CONTEXT(webContext), NULL); BrowserWindow *window = BROWSER_WINDOW(g_object_new(BROWSER_TYPE_WINDOW, - "type", GTK_WINDOW_TOPLEVEL, "view", view, NULL)); + "type", GTK_WINDOW_TOPLEVEL, NULL)); + window->webContext = webContext; + g_signal_connect(window->webContext, "download-started", G_CALLBACK(downloadStarted), window); if (parent) { window->parentWindow = parent; g_object_add_weak_pointer(G_OBJECT(parent), (gpointer *)&window->parentWindow); @@ -718,11 +1076,28 @@ GtkWidget *browser_window_new(WebKitWebView *view, GtkWindow *parent) return GTK_WIDGET(window); } -WebKitWebView *browser_window_get_view(BrowserWindow *window) +WebKitWebContext *browser_window_get_web_context(BrowserWindow *window) +{ + g_return_val_if_fail(BROWSER_IS_WINDOW(window), NULL); + + return window->webContext; +} + +void browser_window_append_view(BrowserWindow *window, WebKitWebView *webView) { - g_return_val_if_fail(BROWSER_IS_WINDOW(window), 0); + g_return_if_fail(BROWSER_IS_WINDOW(window)); + g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView)); - return window->webView; + if (window->activeTab && webkit_web_view_is_editable(browser_tab_get_web_view(window->activeTab))) { + g_warning("Only one tab is allowed in editable mode"); + return; + } + + GtkWidget *tab = browser_tab_new(webView); + browser_tab_add_accelerators(BROWSER_TAB(tab), window->accelGroup); + gtk_notebook_append_page(GTK_NOTEBOOK(window->notebook), tab, browser_tab_get_title_widget(BROWSER_TAB(tab))); + gtk_container_child_set(GTK_CONTAINER(window->notebook), tab, "tab-expand", TRUE, NULL); + gtk_widget_show(tab); } void browser_window_load_uri(BrowserWindow *window, const char *uri) @@ -730,12 +1105,55 @@ void browser_window_load_uri(BrowserWindow *window, const char *uri) g_return_if_fail(BROWSER_IS_WINDOW(window)); g_return_if_fail(uri); - if (!g_str_has_prefix(uri, "javascript:")) { - char *internalURI = getInternalURI(uri); - webkit_web_view_load_uri(window->webView, internalURI); - g_free(internalURI); - return; + browser_tab_load_uri(window->activeTab, uri); +} + +void browser_window_load_session(BrowserWindow *window, const char *sessionFile) +{ + g_return_if_fail(BROWSER_IS_WINDOW(window)); + g_return_if_fail(sessionFile); + + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + window->sessionFile = g_strdup(sessionFile); + gchar *data = NULL; + gsize dataLength; + if (g_file_get_contents(sessionFile, &data, &dataLength, NULL)) { + GBytes *bytes = g_bytes_new_take(data, dataLength); + WebKitWebViewSessionState *state = webkit_web_view_session_state_new(bytes); + g_bytes_unref(bytes); + + if (state) { + webkit_web_view_restore_session_state(webView, state); + webkit_web_view_session_state_unref(state); + } } - webkit_web_view_run_javascript(window->webView, strstr(uri, "javascript:"), NULL, NULL, NULL); + WebKitBackForwardList *bfList = webkit_web_view_get_back_forward_list(webView); + WebKitBackForwardListItem *item = webkit_back_forward_list_get_current_item(bfList); + if (item) + webkit_web_view_go_to_back_forward_list_item(webView, item); + else + webkit_web_view_load_uri(webView, BROWSER_DEFAULT_URL); + +} + +void browser_window_set_background_color(BrowserWindow *window, GdkRGBA *rgba) +{ + g_return_if_fail(BROWSER_IS_WINDOW(window)); + g_return_if_fail(rgba); + + WebKitWebView *webView = browser_tab_get_web_view(window->activeTab); + GdkRGBA viewRGBA; + webkit_web_view_get_background_color(webView, &viewRGBA); + if (gdk_rgba_equal(rgba, &viewRGBA)) + return; + + GdkVisual *rgbaVisual = gdk_screen_get_rgba_visual(gtk_window_get_screen(GTK_WINDOW(window))); + if (!rgbaVisual) + return; + + gtk_widget_set_visual(GTK_WIDGET(window), rgbaVisual); + gtk_widget_set_app_paintable(GTK_WIDGET(window), TRUE); + + webkit_web_view_set_background_color(webView, rgba); } diff --git a/Tools/MiniBrowser/gtk/BrowserWindow.h b/Tools/MiniBrowser/gtk/BrowserWindow.h index 66675462f..15abd0612 100644 --- a/Tools/MiniBrowser/gtk/BrowserWindow.h +++ b/Tools/MiniBrowser/gtk/BrowserWindow.h @@ -37,15 +37,20 @@ G_BEGIN_DECLS #define BROWSER_IS_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), BROWSER_TYPE_WINDOW)) #define BROWSER_IS_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), BROWSER_TYPE_WINDOW)) #define BROWSER_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), BROWSER_TYPE_WINDOW, BrowserWindowClass)) +#define BROWSER_DEFAULT_URL "http://www.webkitgtk.org/" +#define BROWSER_ABOUT_SCHEME "minibrowser-about" typedef struct _BrowserWindow BrowserWindow; typedef struct _BrowserWindowClass BrowserWindowClass; GType browser_window_get_type(void); -GtkWidget* browser_window_new(WebKitWebView*, GtkWindow*); -WebKitWebView* browser_window_get_view(BrowserWindow*); -void browser_window_load_uri(BrowserWindow *, const char *uri); +GtkWidget* browser_window_new(GtkWindow*, WebKitWebContext*); +WebKitWebContext* browser_window_get_web_context(BrowserWindow*); +void browser_window_append_view(BrowserWindow*, WebKitWebView*); +void browser_window_load_uri(BrowserWindow*, const char *uri); +void browser_window_load_session(BrowserWindow *, const char *sessionFile); +void browser_window_set_background_color(BrowserWindow*, GdkRGBA*); G_END_DECLS diff --git a/Tools/MiniBrowser/gtk/CMakeLists.txt b/Tools/MiniBrowser/gtk/CMakeLists.txt new file mode 100644 index 000000000..e38bc6dc1 --- /dev/null +++ b/Tools/MiniBrowser/gtk/CMakeLists.txt @@ -0,0 +1,64 @@ +set(MINIBROWSER_DIR "${TOOLS_DIR}/MiniBrowser/gtk") +set(DERIVED_SOURCES_MINIBROWSER_DIR "${CMAKE_BINARY_DIR}/DerivedSources/MiniBrowser") + +file(MAKE_DIRECTORY ${DERIVED_SOURCES_MINIBROWSER_DIR}) + +set(MiniBrowser_SOURCES + ${DERIVED_SOURCES_MINIBROWSER_DIR}/BrowserMarshal.c + ${MINIBROWSER_DIR}/BrowserCellRendererVariant.c + ${MINIBROWSER_DIR}/BrowserCellRendererVariant.h + ${MINIBROWSER_DIR}/BrowserDownloadsBar.c + ${MINIBROWSER_DIR}/BrowserDownloadsBar.h + ${MINIBROWSER_DIR}/BrowserSearchBar.c + ${MINIBROWSER_DIR}/BrowserSearchBar.h + ${MINIBROWSER_DIR}/BrowserSettingsDialog.c + ${MINIBROWSER_DIR}/BrowserSettingsDialog.h + ${MINIBROWSER_DIR}/BrowserTab.c + ${MINIBROWSER_DIR}/BrowserTab.h + ${MINIBROWSER_DIR}/BrowserWindow.c + ${MINIBROWSER_DIR}/BrowserWindow.h + ${MINIBROWSER_DIR}/main.c +) + +set(MiniBrowser_INCLUDE_DIRECTORIES + ${DERIVED_SOURCES_MINIBROWSER_DIR} + ${DERIVED_SOURCES_WEBKIT2GTK_DIR} + ${FORWARDING_HEADERS_WEBKIT2GTK_DIR} + ${FORWARDING_HEADERS_DIR} + ${CMAKE_SOURCE_DIR}/Source +) + +set(MiniBrowser_SYSTEM_INCLUDE_DIRECTORIES + ${GTK3_INCLUDE_DIRS} + ${GLIB_INCLUDE_DIRS} + ${LIBSOUP_INCLUDE_DIRS} +) + +set(MiniBrowser_LIBRARIES + ${JavaScriptCore_LIBRARY_NAME} + WebKit2 + ${GTK3_LIBRARIES} + ${GLIB_LIBRARIES} + ${LIBSOUP_LIBRARIES} +) + +add_custom_command( + OUTPUT ${DERIVED_SOURCES_MINIBROWSER_DIR}/BrowserMarshal.c + ${DERIVED_SOURCES_MINIBROWSER_DIR}/BrowserMarshal.h + MAIN_DEPENDENCY ${MINIBROWSER_DIR}/browser-marshal.list + COMMAND glib-genmarshal --prefix=browser_marshal ${MINIBROWSER_DIR}/browser-marshal.list --body > ${DERIVED_SOURCES_MINIBROWSER_DIR}/BrowserMarshal.c + COMMAND glib-genmarshal --prefix=browser_marshal ${MINIBROWSER_DIR}/browser-marshal.list --header > ${DERIVED_SOURCES_MINIBROWSER_DIR}/BrowserMarshal.h + VERBATIM) + +if (DEVELOPER_MODE) + add_definitions(-DWEBKIT_INJECTED_BUNDLE_PATH="${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") +endif () + +add_definitions(-DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_6) + +include_directories(${MiniBrowser_INCLUDE_DIRECTORIES}) +include_directories(SYSTEM ${MiniBrowser_SYSTEM_INCLUDE_DIRECTORIES}) +add_executable(MiniBrowser ${MiniBrowser_SOURCES}) +target_link_libraries(MiniBrowser ${MiniBrowser_LIBRARIES}) + +install(TARGETS MiniBrowser DESTINATION "${LIBEXEC_INSTALL_DIR}") diff --git a/Tools/MiniBrowser/gtk/GNUmakefile.am b/Tools/MiniBrowser/gtk/GNUmakefile.am deleted file mode 100644 index 8bdbdd33e..000000000 --- a/Tools/MiniBrowser/gtk/GNUmakefile.am +++ /dev/null @@ -1,75 +0,0 @@ -if ENABLE_WEBKIT2 -noinst_PROGRAMS += \ - Programs/MiniBrowser -endif - -Programs_MiniBrowser_CPPFLAGS = \ - -I$(srcdir)/Source \ - -I$(top_builddir)/DerivedSources/WebKit2 \ - -I$(top_builddir)/DerivedSources/WebKit2/webkit2gtk \ - -I$(top_builddir)/DerivedSources/WebKit2/webkit2gtk/include \ - -DWEBKIT_EXEC_PATH=\"${shell pwd}/$(top_builddir)/Programs/\" \ - -DWEBKIT_INJECTED_BUNDLE_PATH=\"${shell pwd}/$(top_builddir)/.libs\" \ - -DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_6 \ - $(global_cppflags) \ - $(javascriptcore_cppflags) \ - $(FREETYPE_CFLAGS) \ - $(GLIB_CFLAGS) \ - $(GTK_CFLAGS) \ - $(LIBSOUP_CFLAGS) - -Programs_MiniBrowser_SOURCES = \ - Tools/MiniBrowser/gtk/BrowserCellRendererVariant.h \ - Tools/MiniBrowser/gtk/BrowserCellRendererVariant.c \ - Tools/MiniBrowser/gtk/BrowserDownloadsBar.h \ - Tools/MiniBrowser/gtk/BrowserDownloadsBar.c \ - Tools/MiniBrowser/gtk/BrowserSearchBar.h \ - Tools/MiniBrowser/gtk/BrowserSearchBar.c \ - Tools/MiniBrowser/gtk/BrowserSettingsDialog.h \ - Tools/MiniBrowser/gtk/BrowserSettingsDialog.c \ - Tools/MiniBrowser/gtk/BrowserWindow.h \ - Tools/MiniBrowser/gtk/BrowserWindow.c \ - Tools/MiniBrowser/gtk/main.c - -minibrowser_built_sources += \ - DerivedSources/WebKit2/BrowserMarshal.h \ - DerivedSources/WebKit2/BrowserMarshal.c -nodist_Programs_MiniBrowser_SOURCES = \ - $(minibrowser_built_sources) - -Programs_MiniBrowser_LDADD = \ - libwebkit2gtk-@WEBKITGTK_API_MAJOR_VERSION@.@WEBKITGTK_API_MINOR_VERSION@.la \ - $(FREETYPE_LIBS) \ - $(GLIB_LIBS) \ - $(GTK_LIBS) \ - $(LIBSOUP_LIBS) - -Programs_MiniBrowser_LDFLAGS = \ - -no-install - -CLEANFILES += \ - $(top_builddir)/Programs/MiniBrowser \ - $(minibrowser_built_sources) \ - $(top_builddir)/stamp-mini-browser-marshal.h \ - $(top_builddir)/stamp-mini-browser-marshal.c - -BUILT_SOURCES += $(minibrowser_built_sources) - -minibrowser_marshal_list = $(srcdir)/Tools/MiniBrowser/gtk/browser-marshal.list - -$(GENSOURCES_WEBKIT2)/BrowserMarshal.h: stamp-mini-browser-marshal.h - @true -$(GENSOURCES_WEBKIT2)/BrowserMarshal.c: stamp-mini-browser-marshal.c - @true - -stamp-mini-browser-marshal.c: $(minibrowser_marshal_list) - $(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=browser_marshal $(minibrowser_marshal_list) --body > $(GENSOURCES_WEBKIT2)/BrowserMarshal.c && \ - echo timestamp > $(@F) - -stamp-mini-browser-marshal.h: $(minibrowser_marshal_list) - $(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=browser_marshal $(minibrowser_marshal_list) --header > $(GENSOURCES_WEBKIT2)/BrowserMarshal.h && \ - echo timestamp > $(@F) - -EXTRA_DIST += \ - $(srcdir)/Tools/MiniBrowser/gtk/browser-marshal.list - diff --git a/Tools/MiniBrowser/gtk/main.c b/Tools/MiniBrowser/gtk/main.c index e383fda83..e5e5618cd 100644 --- a/Tools/MiniBrowser/gtk/main.c +++ b/Tools/MiniBrowser/gtk/main.c @@ -12,10 +12,10 @@ * 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 COMPUTER, INC. ``AS IS'' AND ANY + * 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 COMPUTER, INC. OR + * 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 @@ -25,7 +25,10 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "cmakeconfig.h" + #include "BrowserWindow.h" +#include <JavaScriptCore/JavaScript.h> #include <errno.h> #include <gtk/gtk.h> #include <string.h> @@ -34,7 +37,11 @@ #define MINI_BROWSER_ERROR (miniBrowserErrorQuark()) static const gchar **uriArguments = NULL; -static const char *miniBrowserAboutScheme = "minibrowser-about"; +static GdkRGBA *backgroundColor; +static gboolean editorMode; +static const char *sessionFile; +static char *geometry; +static gboolean privateMode; typedef enum { MINI_BROWSER_ERROR_INVALID_ABOUT_PATH @@ -54,24 +61,40 @@ static gchar *argumentToURL(const char *filename) return fileURL; } -static void createBrowserWindow(const gchar *uri, WebKitSettings *webkitSettings) +static WebKitWebView *createBrowserTab(BrowserWindow *window, WebKitSettings *webkitSettings, WebKitUserContentManager *userContentManager) { - GtkWidget *webView = webkit_web_view_new(); - GtkWidget *mainWindow = browser_window_new(WEBKIT_WEB_VIEW(webView), NULL); - gchar *url = argumentToURL(uri); + 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); - if (webkitSettings) - webkit_web_view_set_settings(WEBKIT_WEB_VIEW(webView), webkitSettings); + browser_window_append_view(window, webView); + return webView; +} - browser_window_load_uri(BROWSER_WINDOW(mainWindow), url); - g_free(url); +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; + } - gtk_widget_grab_focus(webView); - gtk_widget_show(mainWindow); + 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 } }; @@ -208,8 +231,196 @@ static gboolean addSettingsGroupToContext(GOptionContext *context, WebKitSetting return TRUE; } -static void -aboutURISchemeRequestCallback(WebKitURISchemeRequest *request, gpointer userData) +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, "<h1>%s (%s)</h1>\n<table>\n", title, totalDataSizeStr); + g_free(totalDataSizeStr); + } else + g_string_append_printf(result, "<h1>%s</h1>\n<table>\n", title); + if (dataPath) + g_string_append_printf(result, "<tr><td colspan=\"2\">Path: %s</td></tr>\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, "<tr><td>%s (%s)</td>", displayName, dataSizeStr); + g_free(dataSizeStr); + } else + g_string_append_printf(result, "<tr><td>%s</td>", displayName); + g_string_append_printf(result, "<td><input type=\"button\" value=\"Remove\" onclick=\"removeData('%"G_GUINT64_FORMAT":%u:%u');\"></td></tr>\n", + pageID, types, index); + } + g_string_append_printf(result, "<tr><td><input type=\"button\" value=\"Clear all\" onclick=\"clearData('%"G_GUINT64_FORMAT":%u');\"></td></tr></table>\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( + "<html><head>" + "<script>" + " function removeData(domain) {" + " window.webkit.messageHandlers.aboutData.postMessage(domain);" + " }" + " function clearData(dataType) {" + " window.webkit.messageHandlers.aboutData.postMessage(dataType);" + " }" + "</script></head><body>\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, "</body></html>"); + 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; @@ -228,7 +439,9 @@ aboutURISchemeRequestCallback(WebKitURISchemeRequest *request, gpointer userData webkit_uri_scheme_request_finish(request, stream, streamLength, "text/html"); g_object_unref(stream); - } else { + } 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); @@ -238,12 +451,9 @@ aboutURISchemeRequestCallback(WebKitURISchemeRequest *request, gpointer userData int main(int argc, char *argv[]) { gtk_init(&argc, &argv); - - const gchar *multiprocess = g_getenv("MINIBROWSER_MULTIPROCESS"); - if (multiprocess && *multiprocess) { - webkit_web_context_set_process_model(webkit_web_context_get_default(), - WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES); - } +#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); @@ -252,6 +462,7 @@ int main(int argc, char *argv[]) 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); @@ -265,24 +476,62 @@ int main(int argc, char *argv[]) } g_option_context_free (context); - g_setenv("WEBKIT_INJECTED_BUNDLE_PATH", WEBKIT_INJECTED_BUNDLE_PATH, FALSE); + 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(webkit_web_context_get_default(), NULL); + 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); - webkit_web_context_register_uri_scheme(webkit_web_context_get_default(), miniBrowserAboutScheme, aboutURISchemeRequestCallback, NULL, NULL); + 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++) - createBrowserWindow(uriArguments[i], webkitSettings); - } else - createBrowserWindow("http://www.webkitgtk.org/", webkitSettings); + 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; } |