[GTK] MiniBrowser's browser_window_get_or_create_web_view_for_automation never create...
[WebKit-https.git] / Tools / MiniBrowser / gtk / BrowserTab.c
1 /*
2  * Copyright (C) 2016 Igalia S.L.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H && defined(BUILDING_WITH_CMAKE)
27 #include "cmakeconfig.h"
28 #endif
29 #include "BrowserTab.h"
30
31 #include "BrowserSearchBar.h"
32 #include "BrowserWindow.h"
33 #include <string.h>
34
35 enum {
36     PROP_0,
37
38     PROP_VIEW
39 };
40
41 struct _BrowserTab {
42     GtkBox parent;
43
44     WebKitWebView *webView;
45     BrowserSearchBar *searchBar;
46     GtkWidget *statusLabel;
47     gboolean wasSearchingWhenEnteredFullscreen;
48     gboolean inspectorIsVisible;
49     GtkWidget *fullScreenMessageLabel;
50     guint fullScreenMessageLabelId;
51
52     /* Tab Title */
53     GtkWidget *titleBox;
54     GtkWidget *titleLabel;
55     GtkWidget *titleSpinner;
56     GtkWidget *titleCloseButton;
57 };
58
59 struct _BrowserTabClass {
60     GtkBoxClass parent;
61 };
62
63 G_DEFINE_TYPE(BrowserTab, browser_tab, GTK_TYPE_BOX)
64
65 static void titleChanged(WebKitWebView *webView, GParamSpec *pspec, BrowserTab *tab)
66 {
67     const char *title = webkit_web_view_get_title(webView);
68     if (title && *title)
69         gtk_label_set_text(GTK_LABEL(tab->titleLabel), title);
70 }
71
72 static void isLoadingChanged(WebKitWebView *webView, GParamSpec *paramSpec, BrowserTab *tab)
73 {
74     if (webkit_web_view_is_loading(webView)) {
75         gtk_spinner_start(GTK_SPINNER(tab->titleSpinner));
76         gtk_widget_show(tab->titleSpinner);
77     } else {
78         gtk_spinner_stop(GTK_SPINNER(tab->titleSpinner));
79         gtk_widget_hide(tab->titleSpinner);
80     }
81 }
82
83 static gboolean decidePolicy(WebKitWebView *webView, WebKitPolicyDecision *decision, WebKitPolicyDecisionType decisionType, BrowserTab *tab)
84 {
85     if (decisionType != WEBKIT_POLICY_DECISION_TYPE_RESPONSE)
86         return FALSE;
87
88     WebKitResponsePolicyDecision *responseDecision = WEBKIT_RESPONSE_POLICY_DECISION(decision);
89     if (webkit_response_policy_decision_is_mime_type_supported(responseDecision))
90         return FALSE;
91
92     WebKitWebResource *mainResource = webkit_web_view_get_main_resource(webView);
93     WebKitURIRequest *request = webkit_response_policy_decision_get_request(responseDecision);
94     const char *requestURI = webkit_uri_request_get_uri(request);
95     if (g_strcmp0(webkit_web_resource_get_uri(mainResource), requestURI))
96         return FALSE;
97
98     webkit_policy_decision_download(decision);
99     return TRUE;
100 }
101
102 static void removeChildIfInfoBar(GtkWidget *child, GtkContainer *tab)
103 {
104     if (GTK_IS_INFO_BAR(child))
105         gtk_container_remove(tab, child);
106 }
107
108 static void loadChanged(WebKitWebView *webView, WebKitLoadEvent loadEvent, BrowserTab *tab)
109 {
110     if (loadEvent != WEBKIT_LOAD_STARTED)
111         return;
112
113     gtk_container_foreach(GTK_CONTAINER(tab), (GtkCallback)removeChildIfInfoBar, tab);
114 }
115
116 static GtkWidget *createInfoBarQuestionMessage(const char *title, const char *text)
117 {
118     GtkWidget *dialog = gtk_info_bar_new_with_buttons("No", GTK_RESPONSE_NO, "Yes", GTK_RESPONSE_YES, NULL);
119     gtk_info_bar_set_message_type(GTK_INFO_BAR(dialog), GTK_MESSAGE_QUESTION);
120
121     GtkWidget *contentBox = gtk_info_bar_get_content_area(GTK_INFO_BAR(dialog));
122     gtk_orientable_set_orientation(GTK_ORIENTABLE(contentBox), GTK_ORIENTATION_VERTICAL);
123     gtk_box_set_spacing(GTK_BOX(contentBox), 0);
124
125     GtkWidget *label = gtk_label_new(NULL);
126     gchar *markup = g_strdup_printf("<span size='xx-large' weight='bold'>%s</span>", title);
127     gtk_label_set_markup(GTK_LABEL(label), markup);
128     g_free(markup);
129     gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
130     gtk_label_set_selectable(GTK_LABEL(label), TRUE);
131     gtk_misc_set_alignment(GTK_MISC(label), 0., 0.5);
132     gtk_box_pack_start(GTK_BOX(contentBox), label, FALSE, FALSE, 2);
133     gtk_widget_show(label);
134
135     label = gtk_label_new(text);
136     gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
137     gtk_label_set_selectable(GTK_LABEL(label), TRUE);
138     gtk_misc_set_alignment(GTK_MISC(label), 0., 0.5);
139     gtk_box_pack_start(GTK_BOX(contentBox), label, FALSE, FALSE, 0);
140     gtk_widget_show(label);
141
142     return dialog;
143 }
144
145 static void tlsErrorsDialogResponse(GtkWidget *dialog, gint response, BrowserTab *tab)
146 {
147     if (response == GTK_RESPONSE_YES) {
148         const char *failingURI = (const char *)g_object_get_data(G_OBJECT(dialog), "failingURI");
149         GTlsCertificate *certificate = (GTlsCertificate *)g_object_get_data(G_OBJECT(dialog), "certificate");
150         SoupURI *uri = soup_uri_new(failingURI);
151         webkit_web_context_allow_tls_certificate_for_host(webkit_web_view_get_context(tab->webView), certificate, uri->host);
152         soup_uri_free(uri);
153         webkit_web_view_load_uri(tab->webView, failingURI);
154     }
155     gtk_widget_destroy(dialog);
156 }
157
158 static gboolean loadFailedWithTLSerrors(WebKitWebView *webView, const char *failingURI, GTlsCertificate *certificate, GTlsCertificateFlags errors, BrowserTab *tab)
159 {
160     gchar *text = g_strdup_printf("Failed to load %s: Do you want to continue ignoring the TLS errors?", failingURI);
161     GtkWidget *dialog = createInfoBarQuestionMessage("Invalid TLS Certificate", text);
162     g_free(text);
163     g_object_set_data_full(G_OBJECT(dialog), "failingURI", g_strdup(failingURI), g_free);
164     g_object_set_data_full(G_OBJECT(dialog), "certificate", g_object_ref(certificate), g_object_unref);
165
166     g_signal_connect(dialog, "response", G_CALLBACK(tlsErrorsDialogResponse), tab);
167
168     gtk_box_pack_start(GTK_BOX(tab), dialog, FALSE, FALSE, 0);
169     gtk_box_reorder_child(GTK_BOX(tab), dialog, 0);
170     gtk_widget_show(dialog);
171
172     return TRUE;
173 }
174
175 static void permissionRequestDialogResponse(GtkWidget *dialog, gint response, WebKitPermissionRequest *request)
176 {
177     switch (response) {
178     case GTK_RESPONSE_YES:
179         webkit_permission_request_allow(request);
180         break;
181     default:
182         webkit_permission_request_deny(request);
183         break;
184     }
185
186     gtk_widget_destroy(dialog);
187     g_object_unref(request);
188 }
189
190 static gboolean decidePermissionRequest(WebKitWebView *webView, WebKitPermissionRequest *request, BrowserTab *tab)
191 {
192     const gchar *title = NULL;
193     gchar *text = NULL;
194
195     if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(request)) {
196         title = "Geolocation request";
197         text = g_strdup("Allow geolocation request?");
198     } else if (WEBKIT_IS_NOTIFICATION_PERMISSION_REQUEST(request)) {
199         title = "Notification request";
200         text = g_strdup("Allow notifications request?");
201     } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(request)) {
202         title = "UserMedia request";
203         gboolean is_for_audio_device = webkit_user_media_permission_is_for_audio_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request));
204         gboolean is_for_video_device = webkit_user_media_permission_is_for_video_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request));
205         const char *mediaType = NULL;
206         if (is_for_audio_device) {
207             if (is_for_video_device)
208                 mediaType = "audio/video";
209             else
210                 mediaType = "audio";
211         } else if (is_for_video_device)
212             mediaType = "video";
213         text = g_strdup_printf("Allow access to %s device?", mediaType);
214     } else if (WEBKIT_IS_INSTALL_MISSING_MEDIA_PLUGINS_PERMISSION_REQUEST(request)) {
215         title = "Media plugin missing request";
216         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?",
217             webkit_install_missing_media_plugins_permission_request_get_description(WEBKIT_INSTALL_MISSING_MEDIA_PLUGINS_PERMISSION_REQUEST(request)));
218     } else
219         return FALSE;
220
221     GtkWidget *dialog = createInfoBarQuestionMessage(title, text);
222     g_free(text);
223     g_signal_connect(dialog, "response", G_CALLBACK(permissionRequestDialogResponse), g_object_ref(request));
224
225     gtk_box_pack_start(GTK_BOX(tab), dialog, FALSE, FALSE, 0);
226     gtk_box_reorder_child(GTK_BOX(tab), dialog, 0);
227     gtk_widget_show(dialog);
228
229     return TRUE;
230 }
231
232 #if GTK_CHECK_VERSION(3, 12, 0)
233 static void colorChooserRGBAChanged(GtkColorChooser *colorChooser, GParamSpec *paramSpec, WebKitColorChooserRequest *request)
234 {
235     GdkRGBA rgba;
236     gtk_color_chooser_get_rgba(colorChooser, &rgba);
237     webkit_color_chooser_request_set_rgba(request, &rgba);
238 }
239
240 static void popoverColorClosed(GtkWidget *popover, WebKitColorChooserRequest *request)
241 {
242     webkit_color_chooser_request_finish(request);
243 }
244
245 static void colorChooserRequestFinished(WebKitColorChooserRequest *request, GtkWidget *popover)
246 {
247     g_object_unref(request);
248     gtk_widget_destroy(popover);
249 }
250
251 static gboolean runColorChooserCallback(WebKitWebView *webView, WebKitColorChooserRequest *request, BrowserTab *tab)
252 {
253     GtkWidget *popover = gtk_popover_new(GTK_WIDGET(webView));
254
255     GdkRectangle rectangle;
256     webkit_color_chooser_request_get_element_rectangle(request, &rectangle);
257     gtk_popover_set_pointing_to(GTK_POPOVER(popover), &rectangle);
258
259     GtkWidget *colorChooser = gtk_color_chooser_widget_new();
260     GdkRGBA rgba;
261     webkit_color_chooser_request_get_rgba(request, &rgba);
262     gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(colorChooser), &rgba);
263     g_signal_connect(colorChooser, "notify::rgba", G_CALLBACK(colorChooserRGBAChanged), request);
264     gtk_container_add(GTK_CONTAINER(popover), colorChooser);
265     gtk_widget_show(colorChooser);
266
267     g_object_ref(request);
268     g_signal_connect_object(popover, "hide", G_CALLBACK(popoverColorClosed), request, 0);
269     g_signal_connect_object(request, "finished", G_CALLBACK(colorChooserRequestFinished), popover, 0);
270
271     gtk_widget_show(popover);
272
273     return TRUE;
274 }
275 #endif /* GTK_CHECK_VERSION(3, 12, 0) */
276
277 static gboolean inspectorOpenedInWindow(WebKitWebInspector *inspector, BrowserTab *tab)
278 {
279     tab->inspectorIsVisible = TRUE;
280     return FALSE;
281 }
282
283 static gboolean inspectorClosed(WebKitWebInspector *inspector, BrowserTab *tab)
284 {
285     tab->inspectorIsVisible = FALSE;
286     return FALSE;
287 }
288
289 static void browserTabSetProperty(GObject *object, guint propId, const GValue *value, GParamSpec *pspec)
290 {
291     BrowserTab *tab = BROWSER_TAB(object);
292
293     switch (propId) {
294     case PROP_VIEW:
295         tab->webView = g_value_get_object(value);
296         break;
297     default:
298         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec);
299     }
300 }
301
302 static void browserTabFinalize(GObject *gObject)
303 {
304     BrowserTab *tab = BROWSER_TAB(gObject);
305
306     if (tab->fullScreenMessageLabelId)
307         g_source_remove(tab->fullScreenMessageLabelId);
308
309     G_OBJECT_CLASS(browser_tab_parent_class)->finalize(gObject);
310 }
311
312 static void browser_tab_init(BrowserTab *tab)
313 {
314     gtk_orientable_set_orientation(GTK_ORIENTABLE(tab), GTK_ORIENTATION_VERTICAL);
315 }
316
317 static void browserTabConstructed(GObject *gObject)
318 {
319     BrowserTab *tab = BROWSER_TAB(gObject);
320
321     G_OBJECT_CLASS(browser_tab_parent_class)->constructed(gObject);
322
323     tab->searchBar = BROWSER_SEARCH_BAR(browser_search_bar_new(tab->webView));
324     gtk_box_pack_start(GTK_BOX(tab), GTK_WIDGET(tab->searchBar), FALSE, FALSE, 0);
325
326     GtkWidget *overlay = gtk_overlay_new();
327     gtk_box_pack_start(GTK_BOX(tab), overlay, TRUE, TRUE, 0);
328     gtk_widget_show(overlay);
329
330     tab->statusLabel = gtk_label_new(NULL);
331     gtk_widget_set_halign(tab->statusLabel, GTK_ALIGN_START);
332     gtk_widget_set_valign(tab->statusLabel, GTK_ALIGN_END);
333     gtk_widget_set_margin_left(tab->statusLabel, 1);
334     gtk_widget_set_margin_right(tab->statusLabel, 1);
335     gtk_widget_set_margin_top(tab->statusLabel, 1);
336     gtk_widget_set_margin_bottom(tab->statusLabel, 1);
337     gtk_overlay_add_overlay(GTK_OVERLAY(overlay), tab->statusLabel);
338
339     tab->fullScreenMessageLabel = gtk_label_new(NULL);
340     gtk_widget_set_halign(tab->fullScreenMessageLabel, GTK_ALIGN_CENTER);
341     gtk_widget_set_valign(tab->fullScreenMessageLabel, GTK_ALIGN_CENTER);
342     gtk_widget_set_no_show_all(tab->fullScreenMessageLabel, TRUE);
343     gtk_overlay_add_overlay(GTK_OVERLAY(overlay), tab->fullScreenMessageLabel);
344
345     gtk_container_add(GTK_CONTAINER(overlay), GTK_WIDGET(tab->webView));
346     gtk_widget_show(GTK_WIDGET(tab->webView));
347
348     tab->titleBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
349
350     GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
351     gtk_widget_set_halign(hbox, GTK_ALIGN_CENTER);
352
353     tab->titleSpinner = gtk_spinner_new();
354     gtk_box_pack_start(GTK_BOX(hbox), tab->titleSpinner, FALSE, FALSE, 0);
355
356     tab->titleLabel = gtk_label_new(NULL);
357     gtk_label_set_ellipsize(GTK_LABEL(tab->titleLabel), PANGO_ELLIPSIZE_END);
358     gtk_label_set_single_line_mode(GTK_LABEL(tab->titleLabel), TRUE);
359     gtk_misc_set_padding(GTK_MISC(tab->titleLabel), 0, 0);
360     gtk_box_pack_start(GTK_BOX(hbox), tab->titleLabel, FALSE, FALSE, 0);
361     gtk_widget_show(tab->titleLabel);
362
363     gtk_box_pack_start(GTK_BOX(tab->titleBox), hbox, TRUE, TRUE, 0);
364     gtk_widget_show(hbox);
365
366     tab->titleCloseButton = gtk_button_new();
367     g_signal_connect_swapped(tab->titleCloseButton, "clicked", G_CALLBACK(gtk_widget_destroy), tab);
368     gtk_button_set_relief(GTK_BUTTON(tab->titleCloseButton), GTK_RELIEF_NONE);
369     gtk_button_set_focus_on_click(GTK_BUTTON(tab->titleCloseButton), FALSE);
370
371     GtkWidget *image = gtk_image_new_from_icon_name("window-close-symbolic", GTK_ICON_SIZE_MENU);
372     gtk_container_add(GTK_CONTAINER(tab->titleCloseButton), image);
373     gtk_widget_show(image);
374
375     gtk_box_pack_start(GTK_BOX(tab->titleBox), tab->titleCloseButton, FALSE, FALSE, 0);
376     gtk_widget_show(tab->titleCloseButton);
377
378     g_signal_connect(tab->webView, "notify::title", G_CALLBACK(titleChanged), tab);
379     g_signal_connect(tab->webView, "notify::is-loading", G_CALLBACK(isLoadingChanged), tab);
380     g_signal_connect(tab->webView, "decide-policy", G_CALLBACK(decidePolicy), tab);
381     g_signal_connect(tab->webView, "load-changed", G_CALLBACK(loadChanged), tab);
382     g_signal_connect(tab->webView, "load-failed-with-tls-errors", G_CALLBACK(loadFailedWithTLSerrors), tab);
383     g_signal_connect(tab->webView, "permission-request", G_CALLBACK(decidePermissionRequest), tab);
384 #if GTK_CHECK_VERSION(3, 12, 0)
385     g_signal_connect(tab->webView, "run-color-chooser", G_CALLBACK(runColorChooserCallback), tab);
386 #endif
387
388     WebKitWebInspector *inspector = webkit_web_view_get_inspector(tab->webView);
389     g_signal_connect(inspector, "open-window", G_CALLBACK(inspectorOpenedInWindow), tab);
390     g_signal_connect(inspector, "closed", G_CALLBACK(inspectorClosed), tab);
391
392     if (webkit_web_view_is_editable(tab->webView))
393         webkit_web_view_load_html(tab->webView, "<html></html>", "file:///");
394 }
395
396 static void browser_tab_class_init(BrowserTabClass *klass)
397 {
398     GObjectClass *gobjectClass = G_OBJECT_CLASS(klass);
399     gobjectClass->constructed = browserTabConstructed;
400     gobjectClass->set_property = browserTabSetProperty;
401     gobjectClass->finalize = browserTabFinalize;
402
403     g_object_class_install_property(
404         gobjectClass,
405         PROP_VIEW,
406         g_param_spec_object(
407             "view",
408             "View",
409             "The web view of this tab",
410             WEBKIT_TYPE_WEB_VIEW,
411             G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
412 }
413
414 static char *getInternalURI(const char *uri)
415 {
416     /* Internally we use minibrowser-about: as about: prefix is ignored by WebKit. */
417     if (g_str_has_prefix(uri, "about:") && !g_str_equal(uri, "about:blank"))
418         return g_strconcat(BROWSER_ABOUT_SCHEME, uri + strlen ("about"), NULL);
419
420     return g_strdup(uri);
421 }
422
423 /* Public API. */
424 GtkWidget *browser_tab_new(WebKitWebView *view)
425 {
426     g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(view), NULL);
427
428     return GTK_WIDGET(g_object_new(BROWSER_TYPE_TAB, "view", view, NULL));
429 }
430
431 WebKitWebView *browser_tab_get_web_view(BrowserTab *tab)
432 {
433     g_return_val_if_fail(BROWSER_IS_TAB(tab), NULL);
434
435     return tab->webView;
436 }
437
438 void browser_tab_load_uri(BrowserTab *tab, const char *uri)
439 {
440     g_return_if_fail(BROWSER_IS_TAB(tab));
441     g_return_if_fail(uri);
442
443     if (!g_str_has_prefix(uri, "javascript:")) {
444         char *internalURI = getInternalURI(uri);
445         webkit_web_view_load_uri(tab->webView, internalURI);
446         g_free(internalURI);
447         return;
448     }
449
450     webkit_web_view_run_javascript(tab->webView, strstr(uri, "javascript:"), NULL, NULL, NULL);
451 }
452
453 GtkWidget *browser_tab_get_title_widget(BrowserTab *tab)
454 {
455     g_return_val_if_fail(BROWSER_IS_TAB(tab), NULL);
456
457     return tab->titleBox;
458 }
459
460 void browser_tab_set_status_text(BrowserTab *tab, const char *text)
461 {
462     g_return_if_fail(BROWSER_IS_TAB(tab));
463
464     gtk_label_set_text(GTK_LABEL(tab->statusLabel), text);
465     gtk_widget_set_visible(tab->statusLabel, !!text);
466 }
467
468 void browser_tab_toggle_inspector(BrowserTab *tab)
469 {
470     g_return_if_fail(BROWSER_IS_TAB(tab));
471
472     WebKitWebInspector *inspector = webkit_web_view_get_inspector(tab->webView);
473     if (!tab->inspectorIsVisible) {
474         webkit_web_inspector_show(inspector);
475         tab->inspectorIsVisible = TRUE;
476     } else
477         webkit_web_inspector_close(inspector);
478 }
479
480 void browser_tab_start_search(BrowserTab *tab)
481 {
482     g_return_if_fail(BROWSER_IS_TAB(tab));
483
484     if (!gtk_widget_get_visible(GTK_WIDGET(tab->searchBar)))
485         browser_search_bar_open(tab->searchBar);
486 }
487
488 void browser_tab_stop_search(BrowserTab *tab)
489 {
490     g_return_if_fail(BROWSER_IS_TAB(tab));
491
492     if (gtk_widget_get_visible(GTK_WIDGET(tab->searchBar)))
493         browser_search_bar_close(tab->searchBar);
494 }
495
496 void browser_tab_add_accelerators(BrowserTab *tab, GtkAccelGroup *accelGroup)
497 {
498     g_return_if_fail(BROWSER_IS_TAB(tab));
499     g_return_if_fail(GTK_IS_ACCEL_GROUP(accelGroup));
500
501     browser_search_bar_add_accelerators(tab->searchBar, accelGroup);
502 }
503
504 static gboolean fullScreenMessageTimeoutCallback(BrowserTab *tab)
505 {
506     gtk_widget_hide(tab->fullScreenMessageLabel);
507     tab->fullScreenMessageLabelId = 0;
508     return FALSE;
509 }
510
511 void browser_tab_enter_fullscreen(BrowserTab *tab)
512 {
513     g_return_if_fail(BROWSER_IS_TAB(tab));
514
515     const gchar *titleOrURI = webkit_web_view_get_title(tab->webView);
516     if (!titleOrURI || !titleOrURI[0])
517         titleOrURI = webkit_web_view_get_uri(tab->webView);
518
519     gchar *message = g_strdup_printf("%s is now full screen. Press ESC or f to exit.", titleOrURI);
520     gtk_label_set_text(GTK_LABEL(tab->fullScreenMessageLabel), message);
521     g_free(message);
522
523     gtk_widget_show(tab->fullScreenMessageLabel);
524
525     tab->fullScreenMessageLabelId = g_timeout_add_seconds(2, (GSourceFunc)fullScreenMessageTimeoutCallback, tab);
526     g_source_set_name_by_id(tab->fullScreenMessageLabelId, "[WebKit] fullScreenMessageTimeoutCallback");
527
528     tab->wasSearchingWhenEnteredFullscreen = gtk_widget_get_visible(GTK_WIDGET(tab->searchBar));
529     browser_tab_stop_search(tab);
530 }
531
532 void browser_tab_leave_fullscreen(BrowserTab *tab)
533 {
534     g_return_if_fail(BROWSER_IS_TAB(tab));
535
536     if (tab->fullScreenMessageLabelId) {
537         g_source_remove(tab->fullScreenMessageLabelId);
538         tab->fullScreenMessageLabelId = 0;
539     }
540
541     gtk_widget_hide(tab->fullScreenMessageLabel);
542
543     if (tab->wasSearchingWhenEnteredFullscreen) {
544         /* Opening the search bar steals the focus. Usually, we want
545          * this but not when coming back from fullscreen.
546          */
547         GtkWindow *window = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(tab)));
548         GtkWidget *focusWidget = gtk_window_get_focus(window);
549         browser_tab_start_search(tab);
550         gtk_window_set_focus(window, focusWidget);
551     }
552 }