summaryrefslogtreecommitdiff
path: root/Tools/MiniBrowser/gtk/BrowserTab.c
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/MiniBrowser/gtk/BrowserTab.c')
-rw-r--r--Tools/MiniBrowser/gtk/BrowserTab.c552
1 files changed, 552 insertions, 0 deletions
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);
+ }
+}