[GTK] Add basic tabs support to MiniBrowser
authorcarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 15 Jul 2016 09:25:15 +0000 (09:25 +0000)
committercarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 15 Jul 2016 09:25:15 +0000 (09:25 +0000)
https://bugs.webkit.org/show_bug.cgi?id=159803

Reviewed by Sergio Villar Senin.

It's quite common to have issues with web views loaded in secondary tabs, and we need to use an external browser
like epiphany to debug those issues. It would be a lot easier to work on those bugs if we could use the MiniBrowser.

* MiniBrowser/gtk/BrowserTab.c: Added.
(titleChanged):
(isLoadingChanged):
(decidePolicy):
(removeChildIfInfoBar):
(loadChanged):
(createInfoBarQuestionMessage):
(tlsErrorsDialogResponse):
(loadFailedWithTLSerrors):
(permissionRequestDialogResponse):
(decidePermissionRequest):
(colorChooserRGBAChanged):
(popoverColorClosed):
(colorChooserRequestFinished):
(runColorChooserCallback):
(inspectorOpenedInWindow):
(inspectorClosed):
(browserTabSetProperty):
(browserTabFinalize):
(browser_tab_init):
(browserTabConstructed):
(browser_tab_class_init):
(getInternalURI):
(browser_tab_new):
(browser_tab_get_web_view):
(browser_tab_load_uri):
(browser_tab_get_title_widget):
(browser_tab_set_status_text):
(browser_tab_toggle_inspector):
(browser_tab_start_search):
(browser_tab_stop_search):
(browser_tab_add_accelerators):
(fullScreenMessageTimeoutCallback):
(browser_tab_enter_fullscreen):
(browser_tab_leave_fullscreen):
* MiniBrowser/gtk/BrowserTab.h: Added.
* MiniBrowser/gtk/BrowserWindow.c:
(getExternalURI):
(browserWindowSetStatusText):
(reloadOrStopCallback):
(goBackCallback):
(goForwardCallback):
(settingsCallback):
(webViewURIChanged):
(browserWindowHistoryItemActivated):
(browserWindowUpdateNavigationActions):
(webViewCreate):
(webViewEnterFullScreen):
(webViewLeaveFullScreen):
(webViewDecidePolicy):
(browserWindowCanZoomIn):
(browserWindowCanZoomOut):
(browserWindowZoomIn):
(browserWindowZoomOut):
(scrollEventCallback):
(faviconChanged):
(webViewIsLoadingChanged):
(defaultZoomCallback):
(searchCallback):
(newTabCallback):
(toggleWebInspector):
(reloadPage):
(reloadPageIgnoringCache):
(stopPageLoad):
(loadHomePage):
(editingCommandCallback):
(insertImageCommandCallback):
(insertLinkCommandCallback):
(browserWindowSetupEditorToolbar):
(browserWindowSwitchTab):
(browserWindowTabAddedOrRemoved):
(browser_window_init):
(browserWindowConstructed):
(browserWindowSaveSession):
(browserWindowDeleteEvent):
(browser_window_new):
(browser_window_append_view):
(browser_window_load_uri):
(browser_window_load_session):
(browser_window_set_background_color):
(resetStatusText): Deleted.
(activateUriEntryCallback): Deleted.
(webViewTitleChanged): Deleted.
(resetEntryProgress): Deleted.
(browserWindowCreateBackForwardMenu): Deleted.
(webViewReadyToShow): Deleted.
(webViewLoadFailed): Deleted.
(webViewMouseTargetChanged): Deleted.
(browserWindowUpdateZoomActions): Deleted.
(webViewZoomLevelChanged): Deleted.
(updateUriEntryIcon): Deleted.
(zoomInCallback): Deleted.
(zoomOutCallback): Deleted.
(toggleFullScreen): Deleted.
(browserWindowEditingCommandToggleButtonSetActive): Deleted.
(browserWindowFinalize): Deleted.
(browser_window_class_init): Deleted.
* MiniBrowser/gtk/BrowserWindow.h:
* MiniBrowser/gtk/CMakeLists.txt:
* MiniBrowser/gtk/main.c:
(createBrowserTab):
(aboutURISchemeRequestCallback):
(main):
(parseBackgroundColor): Deleted.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@203271 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Tools/ChangeLog
Tools/MiniBrowser/gtk/BrowserTab.c [new file with mode: 0644]
Tools/MiniBrowser/gtk/BrowserTab.h [new file with mode: 0644]
Tools/MiniBrowser/gtk/BrowserWindow.c
Tools/MiniBrowser/gtk/BrowserWindow.h
Tools/MiniBrowser/gtk/CMakeLists.txt
Tools/MiniBrowser/gtk/main.c

index 5155916..4aff533 100644 (file)
@@ -1,3 +1,118 @@
+2016-07-15  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        [GTK] Add basic tabs support to MiniBrowser
+        https://bugs.webkit.org/show_bug.cgi?id=159803
+
+        Reviewed by Sergio Villar Senin.
+
+        It's quite common to have issues with web views loaded in secondary tabs, and we need to use an external browser
+        like epiphany to debug those issues. It would be a lot easier to work on those bugs if we could use the MiniBrowser.
+
+        * MiniBrowser/gtk/BrowserTab.c: Added.
+        (titleChanged):
+        (isLoadingChanged):
+        (decidePolicy):
+        (removeChildIfInfoBar):
+        (loadChanged):
+        (createInfoBarQuestionMessage):
+        (tlsErrorsDialogResponse):
+        (loadFailedWithTLSerrors):
+        (permissionRequestDialogResponse):
+        (decidePermissionRequest):
+        (colorChooserRGBAChanged):
+        (popoverColorClosed):
+        (colorChooserRequestFinished):
+        (runColorChooserCallback):
+        (inspectorOpenedInWindow):
+        (inspectorClosed):
+        (browserTabSetProperty):
+        (browserTabFinalize):
+        (browser_tab_init):
+        (browserTabConstructed):
+        (browser_tab_class_init):
+        (getInternalURI):
+        (browser_tab_new):
+        (browser_tab_get_web_view):
+        (browser_tab_load_uri):
+        (browser_tab_get_title_widget):
+        (browser_tab_set_status_text):
+        (browser_tab_toggle_inspector):
+        (browser_tab_start_search):
+        (browser_tab_stop_search):
+        (browser_tab_add_accelerators):
+        (fullScreenMessageTimeoutCallback):
+        (browser_tab_enter_fullscreen):
+        (browser_tab_leave_fullscreen):
+        * MiniBrowser/gtk/BrowserTab.h: Added.
+        * MiniBrowser/gtk/BrowserWindow.c:
+        (getExternalURI):
+        (browserWindowSetStatusText):
+        (reloadOrStopCallback):
+        (goBackCallback):
+        (goForwardCallback):
+        (settingsCallback):
+        (webViewURIChanged):
+        (browserWindowHistoryItemActivated):
+        (browserWindowUpdateNavigationActions):
+        (webViewCreate):
+        (webViewEnterFullScreen):
+        (webViewLeaveFullScreen):
+        (webViewDecidePolicy):
+        (browserWindowCanZoomIn):
+        (browserWindowCanZoomOut):
+        (browserWindowZoomIn):
+        (browserWindowZoomOut):
+        (scrollEventCallback):
+        (faviconChanged):
+        (webViewIsLoadingChanged):
+        (defaultZoomCallback):
+        (searchCallback):
+        (newTabCallback):
+        (toggleWebInspector):
+        (reloadPage):
+        (reloadPageIgnoringCache):
+        (stopPageLoad):
+        (loadHomePage):
+        (editingCommandCallback):
+        (insertImageCommandCallback):
+        (insertLinkCommandCallback):
+        (browserWindowSetupEditorToolbar):
+        (browserWindowSwitchTab):
+        (browserWindowTabAddedOrRemoved):
+        (browser_window_init):
+        (browserWindowConstructed):
+        (browserWindowSaveSession):
+        (browserWindowDeleteEvent):
+        (browser_window_new):
+        (browser_window_append_view):
+        (browser_window_load_uri):
+        (browser_window_load_session):
+        (browser_window_set_background_color):
+        (resetStatusText): Deleted.
+        (activateUriEntryCallback): Deleted.
+        (webViewTitleChanged): Deleted.
+        (resetEntryProgress): Deleted.
+        (browserWindowCreateBackForwardMenu): Deleted.
+        (webViewReadyToShow): Deleted.
+        (webViewLoadFailed): Deleted.
+        (webViewMouseTargetChanged): Deleted.
+        (browserWindowUpdateZoomActions): Deleted.
+        (webViewZoomLevelChanged): Deleted.
+        (updateUriEntryIcon): Deleted.
+        (zoomInCallback): Deleted.
+        (zoomOutCallback): Deleted.
+        (toggleFullScreen): Deleted.
+        (browserWindowEditingCommandToggleButtonSetActive): Deleted.
+        (browserWindowFinalize): Deleted.
+        (browser_window_class_init): Deleted.
+        * MiniBrowser/gtk/BrowserWindow.h:
+        * MiniBrowser/gtk/CMakeLists.txt:
+        * MiniBrowser/gtk/main.c:
+        (createBrowserTab):
+        (aboutURISchemeRequestCallback):
+        (main):
+        (parseBackgroundColor): Deleted.
+
 2016-07-14  Alex Christensen  <achristensen@webkit.org>
 
         Allow RefPtrs of const RefCounted types
diff --git a/Tools/MiniBrowser/gtk/BrowserTab.c b/Tools/MiniBrowser/gtk/BrowserTab.c
new file mode 100644 (file)
index 0000000..9fd5c8c
--- /dev/null
@@ -0,0 +1,550 @@
+/*
+ * 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);
+}
+
+#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 (file)
index 0000000..9d57275
--- /dev/null
@@ -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
index 1f73f6f..eac81ab 100644 (file)
 #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;
 
@@ -57,19 +52,15 @@ struct _BrowserWindow {
     GtkWidget *italicItem;
     GtkWidget *underlineItem;
     GtkWidget *strikethroughItem;
-    GtkWidget *statusLabel;
     GtkWidget *settingsDialog;
-    WebKitWebView *webView;
+    GtkWidget *notebook;
+    BrowserTab *activeTab;
     GtkWidget *downloadsBar;
-    BrowserSearchBar *searchBar;
     gboolean searchBarVisible;
-    gboolean inspectorWindowIsVisible;
     gboolean fullScreenIsEnabled;
     GdkPixbuf *favicon;
     GtkWidget *reloadOrStopButton;
-    GtkWidget *fullScreenMessageLabel;
     GtkWindow *parentWindow;
-    guint fullScreenMessageLabelId;
     guint resetEntryProgressTimeoutId;
     gchar *sessionFile;
 };
@@ -79,7 +70,6 @@ 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;
@@ -88,28 +78,18 @@ 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)
@@ -124,20 +104,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)
@@ -147,7 +130,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);
@@ -156,8 +140,11 @@ 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)
@@ -207,7 +194,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)
@@ -241,8 +229,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),
@@ -260,21 +249,6 @@ static void backForwadlistChanged(WebKitBackForwardList *backForwadlist, WebKitB
     browserWindowUpdateNavigationActions(window, backForwadlist);
 }
 
-static void permissionRequestDialogCallback(GtkDialog *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(GTK_WIDGET(dialog));
-    g_object_unref(request);
-}
-
 static void webViewClose(WebKitWebView *webView, BrowserWindow *window)
 {
     gtk_widget_destroy(GTK_WIDGET(window));
@@ -308,169 +282,59 @@ 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));
+    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, WebKitNavigationAction *navigation, 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.);
     return FALSE;
 }
 
-static gboolean webViewLoadFailedWithTLSerrors(WebKitWebView *webView, const char *failingURI, GTlsCertificate *certificate, GTlsCertificateFlags errors, BrowserWindow *window)
-{
-    GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
-        GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "%s", "Invalid TLS Certificate");
-    gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "Failed to load %s: Do you want to continue ignoring the TLS errors?", failingURI);
-    int response = gtk_dialog_run(GTK_DIALOG(dialog));
-    gtk_widget_destroy(dialog);
-
-    if (response == GTK_RESPONSE_YES) {
-        SoupURI *uri = soup_uri_new(failingURI);
-        webkit_web_context_allow_tls_certificate_for_host(webkit_web_view_get_context(webView), certificate, uri->host);
-        soup_uri_free(uri);
-        webkit_web_view_load_uri(webView, failingURI);
-    }
-
-    return TRUE;
-}
-
 static gboolean webViewDecidePolicy(WebKitWebView *webView, WebKitPolicyDecision *decision, WebKitPolicyDecisionType decisionType, BrowserWindow *window)
 {
-    switch (decisionType) {
-    case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION: {
-        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;
-
-        // 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_action_get_request(navigationAction));
-        gtk_widget_show(newWindow);
+    if (decisionType != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION)
+        return FALSE;
 
-        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:
+    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;
-    }
-}
 
-static gboolean webViewDecidePermissionRequest(WebKitWebView *webView, WebKitPermissionRequest *request, BrowserWindow *window)
-{
-    const gchar *dialog_title = NULL;
-    const gchar *dialog_message = NULL;
-    const gchar *dialog_message_format = NULL;
-
-    if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(request)) {
-        dialog_title = "Geolocation request";
-        dialog_message_format = "%s";
-        dialog_message = "Allow geolocation request?";
-    } else if (WEBKIT_IS_NOTIFICATION_PERMISSION_REQUEST(request)) {
-        dialog_title = "Notification request";
-        dialog_message_format = "%s";
-        dialog_message = "Allow notifications request?";
-    } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(request)) {
-        dialog_message_format = "Allow access to %s device?";
-        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));
-        dialog_title = "UserMedia request";
-        if (is_for_audio_device) {
-            if (is_for_video_device)
-                dialog_message = "audio/video";
-            else
-                dialog_message = "audio";
-        } else if (is_for_video_device)
-            dialog_message = "video";
-    } else if (WEBKIT_IS_INSTALL_MISSING_MEDIA_PLUGINS_PERMISSION_REQUEST(request)) {
-        dialog_title = "Media plugin missing request";
-        dialog_message_format = "The media backend was unable to find a plugin to play the requested media:\n%s.\nAllow to search and install the missing plugin?";
-        dialog_message = webkit_install_missing_media_plugins_permission_request_get_description(WEBKIT_INSTALL_MISSING_MEDIA_PLUGINS_PERMISSION_REQUEST(request));
-    } else
+    /* Multiple tabs are not allowed in editor mode. */
+    if (webkit_web_view_is_editable(webView))
         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,
-        "%s", dialog_title);
+    /* Opening a new tab if link clicked with the middle button. */
+    WebKitWebView *newWebView = WEBKIT_WEB_VIEW(webkit_web_view_new_with_context(webkit_web_view_get_context(webView)));
+    browser_window_append_view(window, newWebView);
+    webkit_web_view_load_request(newWebView, webkit_navigation_action_get_request(navigationAction));
 
-    gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), dialog_message_format, dialog_message);
-    g_signal_connect(dialog, "response", G_CALLBACK(permissionRequestDialogCallback), g_object_ref(request));
-    gtk_widget_show(dialog);
+    webkit_policy_decision_ignore(decision);
     return TRUE;
 }
 
@@ -485,21 +349,24 @@ 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)) {
-        gdouble zoomLevel = webkit_web_view_get_zoom_level(window->webView) * zoomStep;
-        webkit_web_view_set_zoom_level(window->webView, zoomLevel);
+        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;
@@ -508,8 +375,9 @@ static gboolean browserWindowZoomIn(BrowserWindow *window)
 static gboolean browserWindowZoomOut(BrowserWindow *window)
 {
     if (browserWindowCanZoomOut(window)) {
-        gdouble zoomLevel = webkit_web_view_get_zoom_level(window->webView) / zoomStep;
-        webkit_web_view_set_zoom_level(window->webView, zoomLevel);
+        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;
@@ -521,57 +389,12 @@ static gboolean scrollEventCallback(WebKitWebView *webView, const GdkEventScroll
 
     if ((event->state & mod) != GDK_CONTROL_MASK)
         return FALSE;
-    
+
     if (event->delta_y < 0)
         return browserWindowZoomIn(window);
-    
-    return browserWindowZoomOut(window);
-}
-
-#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, BrowserWindow *window)
-{
-    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;
+    return browserWindowZoomOut(window);
 }
-#endif /* GTK_CHECK_VERSION(3, 12, 0) */
 
 static void browserWindowUpdateZoomActions(BrowserWindow *window)
 {
@@ -593,10 +416,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);
@@ -611,24 +434,12 @@ 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 gboolean inspectorWasOpenedInAnotherWindow(WebKitWebInspector *inspectorWindow, BrowserWindow *window)
-{
-    window->inspectorWindowIsVisible = TRUE;
-    return FALSE;
-}
-
-static gboolean inspectorWasClosed(WebKitWebInspector *inspectorWindow, BrowserWindow *window)
-{
-    window->inspectorWindowIsVisible = FALSE;
-    return FALSE;
-}
-
 static void zoomInCallback(BrowserWindow *window)
 {
     browserWindowZoomIn(window);
@@ -641,49 +452,54 @@ static void zoomOutCallback(BrowserWindow *window)
 
 static void defaultZoomCallback(BrowserWindow *window)
 {
-    webkit_web_view_set_zoom_level(window->webView, defaultZoomLevel);
+    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 gboolean toggleWebInspector(BrowserWindow *window, gpointer user_data)
+static void newTabCallback(BrowserWindow *window)
 {
-    WebKitWebInspector *inspectorWindow;
-
-    inspectorWindow = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(window->webView));
-    if (!window->inspectorWindowIsVisible) {
-        webkit_web_inspector_show(inspectorWindow);
-        window->inspectorWindowIsVisible = TRUE;
-    } else
-        webkit_web_inspector_close(inspectorWindow);
+    WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
+    if (webkit_web_view_is_editable(webView))
+        return;
+    WebKitSettings *settings = webkit_web_view_get_settings(webView);
+    browser_window_append_view(window, WEBKIT_WEB_VIEW(webkit_web_view_new_with_settings(settings)));
+    gtk_notebook_set_current_page(GTK_NOTEBOOK(window->notebook), -1);
+}
 
-    return TRUE;
+static void toggleWebInspector(BrowserWindow *window)
+{
+    browser_tab_toggle_inspector(window->activeTab);
 }
 
-static void reloadPage(BrowserWindow *window, gpointer user_data)
+static void reloadPage(BrowserWindow *window)
 {
-    webkit_web_view_reload(window->webView);
+    WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
+    webkit_web_view_reload(webView);
 }
 
-static void reloadPageIgnoringCache(BrowserWindow *window, gpointer user_data)
+static void reloadPageIgnoringCache(BrowserWindow *window)
 {
-    webkit_web_view_reload_bypass_cache(window->webView);
+    WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
+    webkit_web_view_reload_bypass_cache(webView);
 }
 
-static void stopPageLoad(BrowserWindow *window, gpointer user_data)
+static void stopPageLoad(BrowserWindow *window)
 {
-    if (gtk_widget_get_visible(GTK_WIDGET(window->searchBar))) 
-        browser_search_bar_close(window->searchBar);
-    else if (webkit_web_view_is_loading(window->webView))
-        webkit_web_view_stop_loading(window->webView);
+    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, gpointer user_data)
+static void loadHomePage(BrowserWindow *window)
 {
-    webkit_web_view_load_uri(window->webView, BROWSER_DEFAULT_URL);
+    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)
@@ -702,7 +518,8 @@ static gboolean toggleFullScreen(BrowserWindow *window, gpointer user_data)
 
 static void editingCommandCallback(GtkWidget*widget, BrowserWindow *window)
 {
-    webkit_web_view_execute_editing_command(window->webView, gtk_widget_get_name(widget));
+    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)
@@ -718,7 +535,8 @@ static void insertImageCommandCallback(GtkWidget*widget, BrowserWindow *window)
     if (gtk_dialog_run(GTK_DIALOG(fileChooser)) == GTK_RESPONSE_ACCEPT) {
         char *uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(fileChooser));
         if (uri) {
-            webkit_web_view_execute_editing_command_with_argument(window->webView, WEBKIT_EDITING_COMMAND_INSERT_IMAGE, 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);
         }
     }
@@ -738,8 +556,10 @@ static void insertLinkCommandCallback(GtkWidget*widget, BrowserWindow *window)
 
     if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
         const char *url = gtk_entry_get_text(GTK_ENTRY(entry));
-        if (url && *url)
-            webkit_web_view_execute_editing_command_with_argument(window->webView, WEBKIT_EDITING_COMMAND_CREATE_LINK, url);
+        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);
@@ -774,9 +594,6 @@ static void browserWindowFinalize(GObject *gObject)
         window->accelGroup = NULL;
     }
 
-    if (window->fullScreenMessageLabelId)
-        g_source_remove(window->fullScreenMessageLabelId);
-
     if (window->resetEntryProgressTimeoutId)
         g_source_remove(window->resetEntryProgressTimeoutId);
 
@@ -788,32 +605,6 @@ static void browserWindowFinalize(GObject *gObject)
         gtk_main_quit();
 }
 
-static void browserWindowGetProperty(GObject *object, guint propId, GValue *value, GParamSpec *pspec)
-{
-    BrowserWindow *window = BROWSER_WINDOW(object);
-
-    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);
-    }
-}
-
-static void browserWindowSetProperty(GObject *object, guint propId, const GValue *value, GParamSpec *pspec)
-{
-    BrowserWindow* window = BROWSER_WINDOW(object);
-
-    switch (propId) {
-    case PROP_VIEW:
-        window->webView = g_value_get_object(value);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec);
-    }
-}
-
 static void browserWindowSetupEditorToolbar(BrowserWindow *window)
 {
     GtkWidget *toolbar = gtk_toolbar_new();
@@ -943,9 +734,66 @@ static void browserWindowSetupEditorToolbar(BrowserWindow *window)
     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 browserWindowSwitchTab(GtkNotebook *notebook, BrowserTab *tab, guint tabIndex, BrowserWindow *window)
+{
+    if (window->activeTab == tab)
+        return;
+
+    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);
+
+        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)
 {
     g_atomic_int_inc(&windowCount);
@@ -968,7 +816,7 @@ static void browser_window_init(BrowserWindow *window)
     gtk_accel_group_connect(window->accelGroup, GDK_KEY_F12, 0, GTK_ACCEL_VISIBLE,
         g_cclosure_new_swap(G_CALLBACK(toggleWebInspector), window, NULL));
 
-    /* Reload page */ 
+    /* 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,
@@ -980,13 +828,13 @@ static void browser_window_init(BrowserWindow *window)
     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 */ 
+    /* 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 */ 
+    /* 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));
 
@@ -1004,7 +852,7 @@ static void browser_window_init(BrowserWindow *window)
     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 */ 
+    /* Toggle fullscreen */
     gtk_accel_group_connect(window->accelGroup, GDK_KEY_F11, 0, GTK_ACCEL_VISIBLE,
         g_cclosure_new_swap(G_CALLBACK(toggleFullScreen), window, NULL));
 
@@ -1014,6 +862,8 @@ static void browser_window_init(BrowserWindow *window)
     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));
 
+    g_signal_connect(webkit_web_context_get_default(), "download-started", G_CALLBACK(downloadStarted), window);
+
     GtkWidget *toolbar = gtk_toolbar_new();
     window->toolbar = toolbar;
     gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_HORIZONTAL);
@@ -1062,6 +912,12 @@ static void browser_window_init(BrowserWindow *window)
     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);
@@ -1081,6 +937,15 @@ 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);
 }
@@ -1089,69 +954,7 @@ static void browserWindowConstructed(GObject *gObject)
 {
     BrowserWindow *window = BROWSER_WINDOW(gObject);
 
-    browserWindowUpdateZoomActions(window);
-    if (webkit_web_view_is_editable(window->webView)) {
-        browserWindowSetupEditorToolbar(window);
-        g_signal_connect(webkit_web_view_get_editor_state(window->webView), "notify::typing-attributes", G_CALLBACK(typingAttributesChanged), 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, "close", G_CALLBACK(webViewClose), window);
-    g_signal_connect(window->webView, "load-failed", G_CALLBACK(webViewLoadFailed), window);
-    g_signal_connect(window->webView, "load-failed-with-tls-errors", G_CALLBACK(webViewLoadFailedWithTLSerrors), 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(window->webView, "scroll-event", G_CALLBACK(scrollEventCallback), window);
-#if GTK_CHECK_VERSION(3, 12, 0)
-    g_signal_connect(window->webView, "run-color-chooser", G_CALLBACK(runColorChooserCallback), window);
-#endif
-
-    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);
-
-    WebKitWebInspector *inspectorWindow = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(window->webView));
-    g_signal_connect(inspectorWindow, "open-window", G_CALLBACK(inspectorWasOpenedInAnotherWindow), window);
-    g_signal_connect(inspectorWindow, "closed", G_CALLBACK(inspectorWasClosed), window);
-
-    GtkWidget *overlay = gtk_overlay_new();
-    gtk_box_pack_start(GTK_BOX(window->mainBox), overlay, TRUE, TRUE, 0);
-    gtk_widget_show(overlay);
-
-    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);
-
-    gtk_container_add(GTK_CONTAINER(overlay), GTK_WIDGET(window->webView));
-
-    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));
-
-    if (webkit_web_view_is_editable(window->webView))
-        webkit_web_view_load_html(window->webView, "<html></html>", "file:///");
+    G_OBJECT_CLASS(browser_window_parent_class)->constructed(gObject);
 }
 
 static void browserWindowSaveSession(BrowserWindow *window)
@@ -1159,7 +962,8 @@ static void browserWindowSaveSession(BrowserWindow *window)
     if (!window->sessionFile)
         return;
 
-    WebKitWebViewSessionState *state = webkit_web_view_get_session_state(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);
@@ -1170,7 +974,8 @@ static gboolean browserWindowDeleteEvent(GtkWidget *widget, GdkEventAny* event)
 {
     BrowserWindow *window = BROWSER_WINDOW(widget);
     browserWindowSaveSession(window);
-    webkit_web_view_try_close(window->webView);
+    WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
+    webkit_web_view_try_close(webView);
     return TRUE;
 }
 
@@ -1179,29 +984,17 @@ 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;
 
     GtkWidgetClass *widgetClass = GTK_WIDGET_CLASS(klass);
     widgetClass->delete_event = browserWindowDeleteEvent;
-
-    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));
 }
 
-// Public API.
-GtkWidget *browser_window_new(WebKitWebView *view, GtkWindow *parent)
+/* Public API. */
+GtkWidget *browser_window_new(GtkWindow *parent)
 {
-    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(view), 0);
-
     BrowserWindow *window = BROWSER_WINDOW(g_object_new(BROWSER_TYPE_WINDOW,
-        "type", GTK_WINDOW_TOPLEVEL, "view", view, NULL));
+        "type", GTK_WINDOW_TOPLEVEL, NULL));
 
     if (parent) {
         window->parentWindow = parent;
@@ -1211,11 +1004,21 @@ GtkWidget *browser_window_new(WebKitWebView *view, GtkWindow *parent)
     return GTK_WIDGET(window);
 }
 
-WebKitWebView *browser_window_get_view(BrowserWindow *window)
+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)
@@ -1223,14 +1026,7 @@ 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;
-    }
-
-    webkit_web_view_run_javascript(window->webView, strstr(uri, "javascript:"), NULL, NULL, NULL);
+    browser_tab_load_uri(window->activeTab, uri);
 }
 
 void browser_window_load_session(BrowserWindow *window, const char *sessionFile)
@@ -1238,6 +1034,7 @@ 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;
@@ -1247,17 +1044,17 @@ void browser_window_load_session(BrowserWindow *window, const char *sessionFile)
         g_bytes_unref(bytes);
 
         if (state) {
-            webkit_web_view_restore_session_state(window->webView, state);
+            webkit_web_view_restore_session_state(webView, state);
             webkit_web_view_session_state_unref(state);
         }
     }
 
-    WebKitBackForwardList *bfList = webkit_web_view_get_back_forward_list(window->webView);
+    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(window->webView, item);
+        webkit_web_view_go_to_back_forward_list_item(webView, item);
     else
-        webkit_web_view_load_uri(window->webView, BROWSER_DEFAULT_URL);
+        webkit_web_view_load_uri(webView, BROWSER_DEFAULT_URL);
 
 }
 
@@ -1266,8 +1063,9 @@ 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(window->webView, &viewRGBA);
+    webkit_web_view_get_background_color(webView, &viewRGBA);
     if (gdk_rgba_equal(rgba, &viewRGBA))
         return;
 
@@ -1280,5 +1078,5 @@ void browser_window_set_background_color(BrowserWindow *window, GdkRGBA *rgba)
         gtk_widget_set_app_paintable(GTK_WIDGET(window), TRUE);
     }
 
-    webkit_web_view_set_background_color(window->webView, rgba);
+    webkit_web_view_set_background_color(webView, rgba);
 }
index 97ae7b9..efe2327 100644 (file)
@@ -38,15 +38,16 @@ G_BEGIN_DECLS
 #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*);
+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*);
 
index 58038cb..26d2bf9 100644 (file)
@@ -13,6 +13,8 @@ set(MiniBrowser_SOURCES
     ${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
index 85e21a1..9af3286 100644 (file)
@@ -36,7 +36,6 @@
 #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;
@@ -60,32 +59,16 @@ static gchar *argumentToURL(const char *filename)
     return fileURL;
 }
 
-static void createBrowserWindow(const gchar *uri, WebKitSettings *webkitSettings, gboolean shouldLoadSession)
+static WebKitWebView *createBrowserTab(BrowserWindow *window, WebKitSettings *webkitSettings)
 {
-    GtkWidget *webView = webkit_web_view_new();
-    if (editorMode)
-        webkit_web_view_set_editable(WEBKIT_WEB_VIEW(webView), TRUE);
-    GtkWidget *mainWindow = browser_window_new(WEBKIT_WEB_VIEW(webView), NULL);
-    if (backgroundColor)
-        browser_window_set_background_color(BROWSER_WINDOW(mainWindow), backgroundColor);
-    if (geometry)
-        gtk_window_parse_geometry(GTK_WINDOW(mainWindow), geometry);
-
+    WebKitWebView *webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
     if (webkitSettings)
-        webkit_web_view_set_settings(WEBKIT_WEB_VIEW(webView), webkitSettings);
-
-    if (!editorMode) {
-        if (shouldLoadSession && sessionFile)
-            browser_window_load_session(BROWSER_WINDOW(mainWindow), sessionFile);
-        else {
-            gchar *url = argumentToURL(uri);
-            browser_window_load_uri(BROWSER_WINDOW(mainWindow), url);
-            g_free(url);
-        }
-    }
+        webkit_web_view_set_settings(webView, webkitSettings);
+    if (editorMode)
+        webkit_web_view_set_editable(webView, TRUE);
 
-    gtk_widget_grab_focus(webView);
-    gtk_widget_show(mainWindow);
+    browser_window_append_view(window, webView);
+    return webView;
 }
 
 static gboolean parseBackgroundColor(const char *optionName, const char *value, gpointer data, GError **error)
@@ -242,8 +225,7 @@ static gboolean addSettingsGroupToContext(GOptionContext *context, WebKitSetting
     return TRUE;
 }
 
-static void
-aboutURISchemeRequestCallback(WebKitURISchemeRequest *request, gpointer userData)
+static void aboutURISchemeRequestCallback(WebKitURISchemeRequest *request, gpointer userData)
 {
     GInputStream *stream;
     gsize streamLength;
@@ -303,15 +285,41 @@ int main(int argc, char *argv[])
     // 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_register_uri_scheme(webkit_web_context_get_default(), miniBrowserAboutScheme, aboutURISchemeRequestCallback, NULL, NULL);
+    webkit_web_context_register_uri_scheme(webkit_web_context_get_default(), BROWSER_ABOUT_SCHEME, aboutURISchemeRequestCallback, NULL, NULL);
 
+    BrowserWindow *mainWindow = BROWSER_WINDOW(browser_window_new(NULL));
+    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, FALSE);
-    } else
-        createBrowserWindow(BROWSER_DEFAULT_URL, webkitSettings, TRUE);
+        for (i = 0; uriArguments[i]; i++) {
+            WebKitWebView *webView = createBrowserTab(mainWindow, webkitSettings);
+            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);
+        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);