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 | static GHashTable *userMediaPermissionGrantedOrigins; |
60 | struct _BrowserTabClass { |
61 | GtkBoxClass parent; |
62 | }; |
63 | |
64 | G_DEFINE_TYPE(BrowserTab, browser_tab, GTK_TYPE_BOX) |
65 | |
66 | typedef struct { |
67 | WebKitPermissionRequest *request; |
68 | gchar *origin; |
69 | } PermissionRequestData; |
70 | |
71 | static 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 | |
81 | static 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 | |
88 | static 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 | |
97 | static 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 | |
104 | static 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 | |
115 | static 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 | |
134 | static void removeChildIfInfoBar(GtkWidget *child, GtkContainer *tab) |
135 | { |
136 | if (GTK_IS_INFO_BAR(child)) |
137 | gtk_container_remove(tab, child); |
138 | } |
139 | |
140 | static 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 | |
148 | static 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 | |
177 | static 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 | |
190 | static 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 | |
207 | static 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 | |
228 | static 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) |
283 | static 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 | |
290 | static void popoverColorClosed(GtkWidget *popover, WebKitColorChooserRequest *request) |
291 | { |
292 | webkit_color_chooser_request_finish(request); |
293 | } |
294 | |
295 | static void colorChooserRequestFinished(WebKitColorChooserRequest *request, GtkWidget *popover) |
296 | { |
297 | g_object_unref(request); |
298 | gtk_widget_destroy(popover); |
299 | } |
300 | |
301 | static 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 | |
327 | static gboolean inspectorOpenedInWindow(WebKitWebInspector *inspector, BrowserTab *tab) |
328 | { |
329 | tab->inspectorIsVisible = TRUE; |
330 | return FALSE; |
331 | } |
332 | |
333 | static gboolean inspectorClosed(WebKitWebInspector *inspector, BrowserTab *tab) |
334 | { |
335 | tab->inspectorIsVisible = FALSE; |
336 | return FALSE; |
337 | } |
338 | |
339 | static 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 | |
352 | static 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 | |
362 | static void browser_tab_init(BrowserTab *tab) |
363 | { |
364 | gtk_orientable_set_orientation(GTK_ORIENTABLE(tab), GTK_ORIENTATION_VERTICAL); |
365 | } |
366 | |
367 | static 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 | |
446 | static 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 | |
467 | static 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. */ |
477 | GtkWidget *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 | |
484 | WebKitWebView *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 | |
491 | void 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 | |
506 | GtkWidget *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 | |
513 | void 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 | |
521 | void 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 | |
533 | void 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 | |
541 | void 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 | |
549 | void 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 | |
557 | static gboolean fullScreenMessageTimeoutCallback(BrowserTab *tab) |
558 | { |
559 | gtk_widget_hide(tab->fullScreenMessageLabel); |
560 | tab->fullScreenMessageLabelId = 0; |
561 | return FALSE; |
562 | } |
563 | |
564 | void 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 | |
585 | void 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 | |
607 | void 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 | |