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
35enum {
36 PROP_0,
37
38 PROP_VIEW
39};
40
41struct _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
59static GHashTable *userMediaPermissionGrantedOrigins;
60struct _BrowserTabClass {
61 GtkBoxClass parent;
62};
63
64G_DEFINE_TYPE(BrowserTab, browser_tab, GTK_TYPE_BOX)
65
66typedef struct {
67 WebKitPermissionRequest *request;
68 gchar *origin;
69} PermissionRequestData;
70
71static PermissionRequestData *permissionRequestDataNew(WebKitPermissionRequest *request, gchar *origin)
72{
73 PermissionRequestData *data = g_malloc0(sizeof(PermissionRequestData));
74
75 data->request = g_object_ref(request);
76 data->origin = origin;
77
78 return data;
79}
80
81static void permissionRequestDataFree(PermissionRequestData *data)
82{
83 g_clear_object(&data->request);
84 g_clear_pointer(&data->origin, g_free);
85 g_free(data);
86}
87
88static gchar *getWebViewOrigin(WebKitWebView *webView)
89{
90 WebKitSecurityOrigin *origin = webkit_security_origin_new_for_uri(webkit_web_view_get_uri(webView));
91 gchar *originStr = webkit_security_origin_to_string(origin);
92 webkit_security_origin_unref(origin);
93
94 return originStr;
95}
96
97static void titleChanged(WebKitWebView *webView, GParamSpec *pspec, BrowserTab *tab)
98{
99 const char *title = webkit_web_view_get_title(webView);
100 if (title && *title)
101 gtk_label_set_text(GTK_LABEL(tab->titleLabel), title);
102}
103
104static void isLoadingChanged(WebKitWebView *webView, GParamSpec *paramSpec, BrowserTab *tab)
105{
106 if (webkit_web_view_is_loading(webView)) {
107 gtk_spinner_start(GTK_SPINNER(tab->titleSpinner));
108 gtk_widget_show(tab->titleSpinner);
109 } else {
110 gtk_spinner_stop(GTK_SPINNER(tab->titleSpinner));
111 gtk_widget_hide(tab->titleSpinner);
112 }
113}
114
115static gboolean decidePolicy(WebKitWebView *webView, WebKitPolicyDecision *decision, WebKitPolicyDecisionType decisionType, BrowserTab *tab)
116{
117 if (decisionType != WEBKIT_POLICY_DECISION_TYPE_RESPONSE)
118 return FALSE;
119
120 WebKitResponsePolicyDecision *responseDecision = WEBKIT_RESPONSE_POLICY_DECISION(decision);
121 if (webkit_response_policy_decision_is_mime_type_supported(responseDecision))
122 return FALSE;
123
124 WebKitWebResource *mainResource = webkit_web_view_get_main_resource(webView);
125 WebKitURIRequest *request = webkit_response_policy_decision_get_request(responseDecision);
126 const char *requestURI = webkit_uri_request_get_uri(request);
127 if (g_strcmp0(webkit_web_resource_get_uri(mainResource), requestURI))
128 return FALSE;
129
130 webkit_policy_decision_download(decision);
131 return TRUE;
132}
133
134static void removeChildIfInfoBar(GtkWidget *child, GtkContainer *tab)
135{
136 if (GTK_IS_INFO_BAR(child))
137 gtk_container_remove(tab, child);
138}
139
140static void loadChanged(WebKitWebView *webView, WebKitLoadEvent loadEvent, BrowserTab *tab)
141{
142 if (loadEvent != WEBKIT_LOAD_STARTED)
143 return;
144
145 gtk_container_foreach(GTK_CONTAINER(tab), (GtkCallback)removeChildIfInfoBar, tab);
146}
147
148static GtkWidget *createInfoBarQuestionMessage(const char *title, const char *text)
149{
150 GtkWidget *dialog = gtk_info_bar_new_with_buttons("No", GTK_RESPONSE_NO, "Yes", GTK_RESPONSE_YES, NULL);
151 gtk_info_bar_set_message_type(GTK_INFO_BAR(dialog), GTK_MESSAGE_QUESTION);
152
153 GtkWidget *contentBox = gtk_info_bar_get_content_area(GTK_INFO_BAR(dialog));
154 gtk_orientable_set_orientation(GTK_ORIENTABLE(contentBox), GTK_ORIENTATION_VERTICAL);
155 gtk_box_set_spacing(GTK_BOX(contentBox), 0);
156
157 GtkWidget *label = gtk_label_new(NULL);
158 gchar *markup = g_strdup_printf("<span size='xx-large' weight='bold'>%s</span>", title);
159 gtk_label_set_markup(GTK_LABEL(label), markup);
160 g_free(markup);
161 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
162 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
163 gtk_misc_set_alignment(GTK_MISC(label), 0., 0.5);
164 gtk_box_pack_start(GTK_BOX(contentBox), label, FALSE, FALSE, 2);
165 gtk_widget_show(label);
166
167 label = gtk_label_new(text);
168 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
169 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
170 gtk_misc_set_alignment(GTK_MISC(label), 0., 0.5);
171 gtk_box_pack_start(GTK_BOX(contentBox), label, FALSE, FALSE, 0);
172 gtk_widget_show(label);
173
174 return dialog;
175}
176
177static void tlsErrorsDialogResponse(GtkWidget *dialog, gint response, BrowserTab *tab)
178{
179 if (response == GTK_RESPONSE_YES) {
180 const char *failingURI = (const char *)g_object_get_data(G_OBJECT(dialog), "failingURI");
181 GTlsCertificate *certificate = (GTlsCertificate *)g_object_get_data(G_OBJECT(dialog), "certificate");
182 SoupURI *uri = soup_uri_new(failingURI);
183 webkit_web_context_allow_tls_certificate_for_host(webkit_web_view_get_context(tab->webView), certificate, uri->host);
184 soup_uri_free(uri);
185 webkit_web_view_load_uri(tab->webView, failingURI);
186 }
187 gtk_widget_destroy(dialog);
188}
189
190static gboolean loadFailedWithTLSerrors(WebKitWebView *webView, const char *failingURI, GTlsCertificate *certificate, GTlsCertificateFlags errors, BrowserTab *tab)
191{
192 gchar *text = g_strdup_printf("Failed to load %s: Do you want to continue ignoring the TLS errors?", failingURI);
193 GtkWidget *dialog = createInfoBarQuestionMessage("Invalid TLS Certificate", text);
194 g_free(text);
195 g_object_set_data_full(G_OBJECT(dialog), "failingURI", g_strdup(failingURI), g_free);
196 g_object_set_data_full(G_OBJECT(dialog), "certificate", g_object_ref(certificate), g_object_unref);
197
198 g_signal_connect(dialog, "response", G_CALLBACK(tlsErrorsDialogResponse), tab);
199
200 gtk_box_pack_start(GTK_BOX(tab), dialog, FALSE, FALSE, 0);
201 gtk_box_reorder_child(GTK_BOX(tab), dialog, 0);
202 gtk_widget_show(dialog);
203
204 return TRUE;
205}
206
207static void permissionRequestDialogResponse(GtkWidget *dialog, gint response, PermissionRequestData *requestData)
208{
209 switch (response) {
210 case GTK_RESPONSE_YES:
211 if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(requestData->request))
212 g_hash_table_add(userMediaPermissionGrantedOrigins, g_strdup(requestData->origin));
213
214 webkit_permission_request_allow(requestData->request);
215 break;
216 default:
217 if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(requestData->request))
218 g_hash_table_remove(userMediaPermissionGrantedOrigins, requestData->origin);
219
220 webkit_permission_request_deny(requestData->request);
221 break;
222 }
223
224 gtk_widget_destroy(dialog);
225 g_clear_pointer(&requestData, permissionRequestDataFree);
226}
227
228static gboolean decidePermissionRequest(WebKitWebView *webView, WebKitPermissionRequest *request, BrowserTab *tab)
229{
230 const gchar *title = NULL;
231 gchar *text = NULL;
232
233 if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(request)) {
234 title = "Geolocation request";
235 text = g_strdup("Allow geolocation request?");
236 } else if (WEBKIT_IS_NOTIFICATION_PERMISSION_REQUEST(request)) {
237 title = "Notification request";
238 text = g_strdup("Allow notifications request?");
239 } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(request)) {
240 title = "UserMedia request";
241 gboolean is_for_audio_device = webkit_user_media_permission_is_for_audio_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request));
242 gboolean is_for_video_device = webkit_user_media_permission_is_for_video_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request));
243 const char *mediaType = NULL;
244 if (is_for_audio_device) {
245 if (is_for_video_device)
246 mediaType = "audio/video";
247 else
248 mediaType = "audio";
249 } else if (is_for_video_device)
250 mediaType = "video";
251 text = g_strdup_printf("Allow access to %s device?", mediaType);
252 } else if (WEBKIT_IS_INSTALL_MISSING_MEDIA_PLUGINS_PERMISSION_REQUEST(request)) {
253 title = "Media plugin missing request";
254 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?",
255 webkit_install_missing_media_plugins_permission_request_get_description(WEBKIT_INSTALL_MISSING_MEDIA_PLUGINS_PERMISSION_REQUEST(request)));
256 } else if (WEBKIT_IS_DEVICE_INFO_PERMISSION_REQUEST(request)) {
257 char* origin = getWebViewOrigin(webView);
258 if (g_hash_table_contains(userMediaPermissionGrantedOrigins, origin)) {
259 webkit_permission_request_allow(request);
260 g_free(origin);
261 return TRUE;
262 }
263 g_free(origin);
264 return FALSE;
265 } else {
266 g_print("%s request not handled\n", G_OBJECT_TYPE_NAME(request));
267 return FALSE;
268 }
269
270 GtkWidget *dialog = createInfoBarQuestionMessage(title, text);
271 g_free(text);
272 g_signal_connect(dialog, "response", G_CALLBACK(permissionRequestDialogResponse), permissionRequestDataNew(request,
273 getWebViewOrigin(webView)));
274
275 gtk_box_pack_start(GTK_BOX(tab), dialog, FALSE, FALSE, 0);
276 gtk_box_reorder_child(GTK_BOX(tab), dialog, 0);
277 gtk_widget_show(dialog);
278
279 return TRUE;
280}
281
282#if GTK_CHECK_VERSION(3, 12, 0)
283static void colorChooserRGBAChanged(GtkColorChooser *colorChooser, GParamSpec *paramSpec, WebKitColorChooserRequest *request)
284{
285 GdkRGBA rgba;
286 gtk_color_chooser_get_rgba(colorChooser, &rgba);
287 webkit_color_chooser_request_set_rgba(request, &rgba);
288}
289
290static void popoverColorClosed(GtkWidget *popover, WebKitColorChooserRequest *request)
291{
292 webkit_color_chooser_request_finish(request);
293}
294
295static void colorChooserRequestFinished(WebKitColorChooserRequest *request, GtkWidget *popover)
296{
297 g_object_unref(request);
298 gtk_widget_destroy(popover);
299}
300
301static gboolean runColorChooserCallback(WebKitWebView *webView, WebKitColorChooserRequest *request, BrowserTab *tab)
302{
303 GtkWidget *popover = gtk_popover_new(GTK_WIDGET(webView));
304
305 GdkRectangle rectangle;
306 webkit_color_chooser_request_get_element_rectangle(request, &rectangle);
307 gtk_popover_set_pointing_to(GTK_POPOVER(popover), &rectangle);
308
309 GtkWidget *colorChooser = gtk_color_chooser_widget_new();
310 GdkRGBA rgba;
311 webkit_color_chooser_request_get_rgba(request, &rgba);
312 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(colorChooser), &rgba);
313 g_signal_connect(colorChooser, "notify::rgba", G_CALLBACK(colorChooserRGBAChanged), request);
314 gtk_container_add(GTK_CONTAINER(popover), colorChooser);
315 gtk_widget_show(colorChooser);
316
317 g_object_ref(request);
318 g_signal_connect_object(popover, "hide", G_CALLBACK(popoverColorClosed), request, 0);
319 g_signal_connect_object(request, "finished", G_CALLBACK(colorChooserRequestFinished), popover, 0);
320
321 gtk_widget_show(popover);
322
323 return TRUE;
324}
325#endif /* GTK_CHECK_VERSION(3, 12, 0) */
326
327static gboolean inspectorOpenedInWindow(WebKitWebInspector *inspector, BrowserTab *tab)
328{
329 tab->inspectorIsVisible = TRUE;
330 return FALSE;
331}
332
333static gboolean inspectorClosed(WebKitWebInspector *inspector, BrowserTab *tab)
334{
335 tab->inspectorIsVisible = FALSE;
336 return FALSE;
337}
338
339static void browserTabSetProperty(GObject *object, guint propId, const GValue *value, GParamSpec *pspec)
340{
341 BrowserTab *tab = BROWSER_TAB(object);
342
343 switch (propId) {
344 case PROP_VIEW:
345 tab->webView = g_value_get_object(value);
346 break;
347 default:
348 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec);
349 }
350}
351
352static void browserTabFinalize(GObject *gObject)
353{
354 BrowserTab *tab = BROWSER_TAB(gObject);
355
356 if (tab->fullScreenMessageLabelId)
357 g_source_remove(tab->fullScreenMessageLabelId);
358
359 G_OBJECT_CLASS(browser_tab_parent_class)->finalize(gObject);
360}
361
362static void browser_tab_init(BrowserTab *tab)
363{
364 gtk_orientable_set_orientation(GTK_ORIENTABLE(tab), GTK_ORIENTATION_VERTICAL);
365}
366
367static void browserTabConstructed(GObject *gObject)
368{
369 BrowserTab *tab = BROWSER_TAB(gObject);
370
371 G_OBJECT_CLASS(browser_tab_parent_class)->constructed(gObject);
372
373 tab->searchBar = BROWSER_SEARCH_BAR(browser_search_bar_new(tab->webView));
374 gtk_box_pack_start(GTK_BOX(tab), GTK_WIDGET(tab->searchBar), FALSE, FALSE, 0);
375
376 GtkWidget *overlay = gtk_overlay_new();
377 gtk_box_pack_start(GTK_BOX(tab), overlay, TRUE, TRUE, 0);
378 gtk_widget_show(overlay);
379
380 tab->statusLabel = gtk_label_new(NULL);
381 gtk_widget_set_halign(tab->statusLabel, GTK_ALIGN_START);
382 gtk_widget_set_valign(tab->statusLabel, GTK_ALIGN_END);
383 gtk_widget_set_margin_left(tab->statusLabel, 1);
384 gtk_widget_set_margin_right(tab->statusLabel, 1);
385 gtk_widget_set_margin_top(tab->statusLabel, 1);
386 gtk_widget_set_margin_bottom(tab->statusLabel, 1);
387 gtk_overlay_add_overlay(GTK_OVERLAY(overlay), tab->statusLabel);
388
389 tab->fullScreenMessageLabel = gtk_label_new(NULL);
390 gtk_widget_set_halign(tab->fullScreenMessageLabel, GTK_ALIGN_CENTER);
391 gtk_widget_set_valign(tab->fullScreenMessageLabel, GTK_ALIGN_CENTER);
392 gtk_widget_set_no_show_all(tab->fullScreenMessageLabel, TRUE);
393 gtk_overlay_add_overlay(GTK_OVERLAY(overlay), tab->fullScreenMessageLabel);
394
395 gtk_container_add(GTK_CONTAINER(overlay), GTK_WIDGET(tab->webView));
396 gtk_widget_show(GTK_WIDGET(tab->webView));
397
398 tab->titleBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
399
400 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
401 gtk_widget_set_halign(hbox, GTK_ALIGN_CENTER);
402
403 tab->titleSpinner = gtk_spinner_new();
404 gtk_box_pack_start(GTK_BOX(hbox), tab->titleSpinner, FALSE, FALSE, 0);
405
406 tab->titleLabel = gtk_label_new(NULL);
407 gtk_label_set_ellipsize(GTK_LABEL(tab->titleLabel), PANGO_ELLIPSIZE_END);
408 gtk_label_set_single_line_mode(GTK_LABEL(tab->titleLabel), TRUE);
409 gtk_misc_set_padding(GTK_MISC(tab->titleLabel), 0, 0);
410 gtk_box_pack_start(GTK_BOX(hbox), tab->titleLabel, FALSE, FALSE, 0);
411 gtk_widget_show(tab->titleLabel);
412
413 gtk_box_pack_start(GTK_BOX(tab->titleBox), hbox, TRUE, TRUE, 0);
414 gtk_widget_show(hbox);
415
416 tab->titleCloseButton = gtk_button_new();
417 g_signal_connect_swapped(tab->titleCloseButton, "clicked", G_CALLBACK(gtk_widget_destroy), tab);
418 gtk_button_set_relief(GTK_BUTTON(tab->titleCloseButton), GTK_RELIEF_NONE);
419 gtk_button_set_focus_on_click(GTK_BUTTON(tab->titleCloseButton), FALSE);
420
421 GtkWidget *image = gtk_image_new_from_icon_name("window-close-symbolic", GTK_ICON_SIZE_MENU);
422 gtk_container_add(GTK_CONTAINER(tab->titleCloseButton), image);
423 gtk_widget_show(image);
424
425 gtk_box_pack_start(GTK_BOX(tab->titleBox), tab->titleCloseButton, FALSE, FALSE, 0);
426 gtk_widget_show(tab->titleCloseButton);
427
428 g_signal_connect(tab->webView, "notify::title", G_CALLBACK(titleChanged), tab);
429 g_signal_connect(tab->webView, "notify::is-loading", G_CALLBACK(isLoadingChanged), tab);
430 g_signal_connect(tab->webView, "decide-policy", G_CALLBACK(decidePolicy), tab);
431 g_signal_connect(tab->webView, "load-changed", G_CALLBACK(loadChanged), tab);
432 g_signal_connect(tab->webView, "load-failed-with-tls-errors", G_CALLBACK(loadFailedWithTLSerrors), tab);
433 g_signal_connect(tab->webView, "permission-request", G_CALLBACK(decidePermissionRequest), tab);
434#if GTK_CHECK_VERSION(3, 12, 0)
435 g_signal_connect(tab->webView, "run-color-chooser", G_CALLBACK(runColorChooserCallback), tab);
436#endif
437
438 WebKitWebInspector *inspector = webkit_web_view_get_inspector(tab->webView);
439 g_signal_connect(inspector, "open-window", G_CALLBACK(inspectorOpenedInWindow), tab);
440 g_signal_connect(inspector, "closed", G_CALLBACK(inspectorClosed), tab);
441
442 if (webkit_web_view_is_editable(tab->webView))
443 webkit_web_view_load_html(tab->webView, "<html></html>", "file:///");
444}
445
446static void browser_tab_class_init(BrowserTabClass *klass)
447{
448 GObjectClass *gobjectClass = G_OBJECT_CLASS(klass);
449 gobjectClass->constructed = browserTabConstructed;
450 gobjectClass->set_property = browserTabSetProperty;
451 gobjectClass->finalize = browserTabFinalize;
452
453 if (!userMediaPermissionGrantedOrigins)
454 userMediaPermissionGrantedOrigins = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
455
456 g_object_class_install_property(
457 gobjectClass,
458 PROP_VIEW,
459 g_param_spec_object(
460 "view",
461 "View",
462 "The web view of this tab",
463 WEBKIT_TYPE_WEB_VIEW,
464 G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
465}
466
467static char *getInternalURI(const char *uri)
468{
469 /* Internally we use minibrowser-about: as about: prefix is ignored by WebKit. */
470 if (g_str_has_prefix(uri, "about:") && !g_str_equal(uri, "about:blank"))
471 return g_strconcat(BROWSER_ABOUT_SCHEME, uri + strlen ("about"), NULL);
472
473 return g_strdup(uri);
474}
475
476/* Public API. */
477GtkWidget *browser_tab_new(WebKitWebView *view)
478{
479 g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(view), NULL);
480
481 return GTK_WIDGET(g_object_new(BROWSER_TYPE_TAB, "view", view, NULL));
482}
483
484WebKitWebView *browser_tab_get_web_view(BrowserTab *tab)
485{
486 g_return_val_if_fail(BROWSER_IS_TAB(tab), NULL);
487
488 return tab->webView;
489}
490
491void browser_tab_load_uri(BrowserTab *tab, const char *uri)
492{
493 g_return_if_fail(BROWSER_IS_TAB(tab));
494 g_return_if_fail(uri);
495
496 if (!g_str_has_prefix(uri, "javascript:")) {
497 char *internalURI = getInternalURI(uri);
498 webkit_web_view_load_uri(tab->webView, internalURI);
499 g_free(internalURI);
500 return;
501 }
502
503 webkit_web_view_run_javascript(tab->webView, strstr(uri, "javascript:"), NULL, NULL, NULL);
504}
505
506GtkWidget *browser_tab_get_title_widget(BrowserTab *tab)
507{
508 g_return_val_if_fail(BROWSER_IS_TAB(tab), NULL);
509
510 return tab->titleBox;
511}
512
513void browser_tab_set_status_text(BrowserTab *tab, const char *text)
514{
515 g_return_if_fail(BROWSER_IS_TAB(tab));
516
517 gtk_label_set_text(GTK_LABEL(tab->statusLabel), text);
518 gtk_widget_set_visible(tab->statusLabel, !!text);
519}
520
521void browser_tab_toggle_inspector(BrowserTab *tab)
522{
523 g_return_if_fail(BROWSER_IS_TAB(tab));
524
525 WebKitWebInspector *inspector = webkit_web_view_get_inspector(tab->webView);
526 if (!tab->inspectorIsVisible) {
527 webkit_web_inspector_show(inspector);
528 tab->inspectorIsVisible = TRUE;
529 } else
530 webkit_web_inspector_close(inspector);
531}
532
533void browser_tab_start_search(BrowserTab *tab)
534{
535 g_return_if_fail(BROWSER_IS_TAB(tab));
536
537 if (!gtk_widget_get_visible(GTK_WIDGET(tab->searchBar)))
538 browser_search_bar_open(tab->searchBar);
539}
540
541void browser_tab_stop_search(BrowserTab *tab)
542{
543 g_return_if_fail(BROWSER_IS_TAB(tab));
544
545 if (gtk_widget_get_visible(GTK_WIDGET(tab->searchBar)))
546 browser_search_bar_close(tab->searchBar);
547}
548
549void browser_tab_add_accelerators(BrowserTab *tab, GtkAccelGroup *accelGroup)
550{
551 g_return_if_fail(BROWSER_IS_TAB(tab));
552 g_return_if_fail(GTK_IS_ACCEL_GROUP(accelGroup));
553
554 browser_search_bar_add_accelerators(tab->searchBar, accelGroup);
555}
556
557static gboolean fullScreenMessageTimeoutCallback(BrowserTab *tab)
558{
559 gtk_widget_hide(tab->fullScreenMessageLabel);
560 tab->fullScreenMessageLabelId = 0;
561 return FALSE;
562}
563
564void browser_tab_enter_fullscreen(BrowserTab *tab)
565{
566 g_return_if_fail(BROWSER_IS_TAB(tab));
567
568 const gchar *titleOrURI = webkit_web_view_get_title(tab->webView);
569 if (!titleOrURI || !titleOrURI[0])
570 titleOrURI = webkit_web_view_get_uri(tab->webView);
571
572 gchar *message = g_strdup_printf("%s is now full screen. Press ESC or f to exit.", titleOrURI);
573 gtk_label_set_text(GTK_LABEL(tab->fullScreenMessageLabel), message);
574 g_free(message);
575
576 gtk_widget_show(tab->fullScreenMessageLabel);
577
578 tab->fullScreenMessageLabelId = g_timeout_add_seconds(2, (GSourceFunc)fullScreenMessageTimeoutCallback, tab);
579 g_source_set_name_by_id(tab->fullScreenMessageLabelId, "[WebKit] fullScreenMessageTimeoutCallback");
580
581 tab->wasSearchingWhenEnteredFullscreen = gtk_widget_get_visible(GTK_WIDGET(tab->searchBar));
582 browser_tab_stop_search(tab);
583}
584
585void browser_tab_leave_fullscreen(BrowserTab *tab)
586{
587 g_return_if_fail(BROWSER_IS_TAB(tab));
588
589 if (tab->fullScreenMessageLabelId) {
590 g_source_remove(tab->fullScreenMessageLabelId);
591 tab->fullScreenMessageLabelId = 0;
592 }
593
594 gtk_widget_hide(tab->fullScreenMessageLabel);
595
596 if (tab->wasSearchingWhenEnteredFullscreen) {
597 /* Opening the search bar steals the focus. Usually, we want
598 * this but not when coming back from fullscreen.
599 */
600 GtkWindow *window = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(tab)));
601 GtkWidget *focusWidget = gtk_window_get_focus(window);
602 browser_tab_start_search(tab);
603 gtk_window_set_focus(window, focusWidget);
604 }
605}
606
607void browser_tab_set_background_color(BrowserTab *tab, GdkRGBA *rgba)
608{
609 g_return_if_fail(BROWSER_IS_TAB(tab));
610 g_return_if_fail(rgba);
611
612 GdkRGBA viewRGBA;
613 webkit_web_view_get_background_color(tab->webView, &viewRGBA);
614 if (gdk_rgba_equal(rgba, &viewRGBA))
615 return;
616
617 webkit_web_view_set_background_color(tab->webView, rgba);
618}
619