1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3 * Copyright © 2008, 2009 Gustavo Noronha Silva
4 * Copyright © 2009, 2010, 2014 Igalia S.L.
5 *
6 * This file is part of Epiphany.
7 *
8 * Epiphany is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * Epiphany is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "config.h"
23 #include "ephy-web-view.h"
24
25 #include "ephy-about-handler.h"
26 #include "ephy-debug.h"
27 #include "ephy-embed-container.h"
28 #include "ephy-embed-prefs.h"
29 #include "ephy-embed-shell.h"
30 #include "ephy-embed-type-builtins.h"
31 #include "ephy-embed-utils.h"
32 #include "ephy-embed.h"
33 #include "ephy-favicon-helpers.h"
34 #include "ephy-file-chooser.h"
35 #include "ephy-file-helpers.h"
36 #include "ephy-file-monitor.h"
37 #include "ephy-filters-manager.h"
38 #include "ephy-gsb-utils.h"
39 #include "ephy-history-service.h"
40 #include "ephy-lib-type-builtins.h"
41 #include "ephy-permissions-manager.h"
42 #include "ephy-prefs.h"
43 #include "ephy-reader-handler.h"
44 #include "ephy-settings.h"
45 #include "ephy-snapshot-service.h"
46 #include "ephy-string.h"
47 #include "ephy-uri-helpers.h"
48 #include "ephy-pdf-handler.h"
49 #include "ephy-view-source-handler.h"
50 #include "ephy-web-app-utils.h"
51 #include "ephy-zoom.h"
52
53 #include <gio/gio.h>
54 #include <glib/gi18n.h>
55 #include <glib/gstdio.h>
56 #include <gtk/gtk.h>
57
58 /**
59 * SECTION:ephy-web-view
60 * @short_description: Epiphany custom #WebkitWebView
61 *
62 * #EphyWebView wraps #WebkitWebView implementing custom functionality on top of
63 * it.
64 */
65
66 #define EPHY_PAGE_TEMPLATE_ERROR "/org/gnome/epiphany/page-templates/error.html"
67 #define EPHY_PAGE_TEMPLATE_ERROR_CSS "/org/gnome/epiphany/page-templates/error.css"
68
69 static guint64 web_view_uid = 1;
70
71 struct _EphyWebView {
72 WebKitWebView parent_instance;
73
74 EphySecurityLevel security_level;
75 EphyWebViewDocumentType document_type;
76 EphyWebViewNavigationFlags nav_flags;
77
78 /* Flags */
79 guint is_blank : 1;
80 guint is_setting_zoom : 1;
81 guint load_failed : 1;
82 guint history_frozen : 1;
83 guint ever_committed : 1;
84 guint in_auth_dialog : 1;
85
86 char *address;
87 char *display_address;
88 char *typed_address;
89 char *last_committed_address;
90 char *loading_message;
91 char *link_message;
92 GdkPixbuf *icon;
93
94 /* Reader mode */
95 gboolean entering_reader_mode;
96 gboolean reader_mode_available;
97 guint reader_js_timeout;
98
99 /* Local file watch. */
100 EphyFileMonitor *file_monitor;
101
102 GtkWidget *geolocation_info_bar;
103 GtkWidget *notification_info_bar;
104 GtkWidget *microphone_info_bar;
105 GtkWidget *webcam_info_bar;
106 GtkWidget *webcam_mic_info_bar;
107 GtkWidget *password_info_bar;
108 GtkWidget *password_form_info_bar;
109 GtkWidget *itp_info_bar;
110
111 EphyHistoryService *history_service;
112
113 GCancellable *cancellable;
114
115 guint snapshot_timeout_id;
116 char *pending_snapshot_uri;
117
118 EphyHistoryPageVisitType visit_type;
119
120 /* TLS information. */
121 GTlsCertificate *certificate;
122 GTlsCertificateFlags tls_errors;
123
124 gboolean bypass_safe_browsing;
125 gboolean loading_error_page;
126 char *tls_error_failing_uri;
127
128 EphyWebViewErrorPage error_page;
129
130 guint unresponsive_process_timeout_id;
131 GtkWidget *unresponsive_process_dialog;
132
133 guint64 uid;
134 };
135
136 enum {
137 PROP_0,
138 PROP_ADDRESS,
139 PROP_DOCUMENT_TYPE,
140 PROP_ICON,
141 PROP_LINK_MESSAGE,
142 PROP_NAVIGATION,
143 PROP_SECURITY,
144 PROP_STATUS_MESSAGE,
145 PROP_TYPED_ADDRESS,
146 PROP_IS_BLANK,
147 PROP_READER_MODE,
148 PROP_DISPLAY_ADDRESS,
149 PROP_ENTERING_READER_MODE,
150 LAST_PROP
151 };
152
153 static GParamSpec *obj_properties[LAST_PROP];
154
G_DEFINE_TYPE(EphyWebView,ephy_web_view,WEBKIT_TYPE_WEB_VIEW)155 G_DEFINE_TYPE (EphyWebView, ephy_web_view, WEBKIT_TYPE_WEB_VIEW)
156
157 static void
158 open_response_cb (GtkFileChooser *dialog,
159 gint response,
160 WebKitFileChooserRequest *request)
161 {
162 if (response == GTK_RESPONSE_ACCEPT) {
163 GSList *file_list = gtk_file_chooser_get_filenames (dialog);
164 GPtrArray *file_array = g_ptr_array_new ();
165
166 for (GSList *file = file_list; file; file = g_slist_next (file))
167 g_ptr_array_add (file_array, file->data);
168
169 g_ptr_array_add (file_array, NULL);
170 webkit_file_chooser_request_select_files (request, (const char * const *)file_array->pdata);
171 g_slist_free_full (file_list, g_free);
172 g_ptr_array_free (file_array, FALSE);
173 g_settings_set_string (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_LAST_UPLOAD_DIRECTORY, gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dialog)));
174 } else {
175 webkit_file_chooser_request_cancel (request);
176 }
177
178 g_object_unref (request);
179 g_object_unref (dialog);
180 }
181
182 static gboolean
ephy_web_view_run_file_chooser(WebKitWebView * web_view,WebKitFileChooserRequest * request)183 ephy_web_view_run_file_chooser (WebKitWebView *web_view,
184 WebKitFileChooserRequest *request)
185 {
186 GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
187 GtkFileChooser *dialog;
188 gboolean allows_multiple_selection = webkit_file_chooser_request_get_select_multiple (request);
189 GtkFileFilter *filter = webkit_file_chooser_request_get_mime_types_filter (request);
190
191 dialog = ephy_create_file_chooser (_("Open"),
192 GTK_WIDGET (toplevel),
193 GTK_FILE_CHOOSER_ACTION_OPEN,
194 EPHY_FILE_FILTER_ALL);
195
196 if (filter) {
197 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
198 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);
199 }
200
201 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), g_settings_get_string (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_LAST_UPLOAD_DIRECTORY));
202 gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), allows_multiple_selection);
203
204 g_signal_connect (dialog, "response",
205 G_CALLBACK (open_response_cb),
206 g_object_ref (request));
207
208 gtk_native_dialog_show (GTK_NATIVE_DIALOG (dialog));
209
210 return TRUE;
211 }
212
213 static void
ephy_web_view_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)214 ephy_web_view_get_property (GObject *object,
215 guint prop_id,
216 GValue *value,
217 GParamSpec *pspec)
218 {
219 EphyWebView *view = EPHY_WEB_VIEW (object);
220
221 switch (prop_id) {
222 case PROP_ADDRESS:
223 g_value_set_string (value, view->address);
224 break;
225 case PROP_TYPED_ADDRESS:
226 g_value_set_string (value, view->typed_address);
227 break;
228 case PROP_DOCUMENT_TYPE:
229 g_value_set_enum (value, view->document_type);
230 break;
231 case PROP_ICON:
232 g_value_set_object (value, view->icon);
233 break;
234 case PROP_LINK_MESSAGE:
235 g_value_set_string (value, view->link_message);
236 break;
237 case PROP_NAVIGATION:
238 g_value_set_flags (value, view->nav_flags);
239 break;
240 case PROP_SECURITY:
241 g_value_set_enum (value, view->security_level);
242 break;
243 case PROP_STATUS_MESSAGE:
244 g_value_set_string (value, ephy_web_view_get_status_message (EPHY_WEB_VIEW (object)));
245 break;
246 case PROP_IS_BLANK:
247 g_value_set_boolean (value, view->is_blank);
248 break;
249 case PROP_READER_MODE:
250 g_value_set_boolean (value, view->reader_mode_available);
251 break;
252 case PROP_DISPLAY_ADDRESS:
253 g_value_set_string (value, view->display_address);
254 break;
255 case PROP_ENTERING_READER_MODE:
256 g_value_set_boolean (value, view->entering_reader_mode);
257 break;
258 default:
259 break;
260 }
261 }
262
263 static void
ephy_web_view_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)264 ephy_web_view_set_property (GObject *object,
265 guint prop_id,
266 const GValue *value,
267 GParamSpec *pspec)
268 {
269 switch (prop_id) {
270 case PROP_TYPED_ADDRESS:
271 ephy_web_view_set_typed_address (EPHY_WEB_VIEW (object), g_value_get_string (value));
272 break;
273 case PROP_ADDRESS:
274 case PROP_DOCUMENT_TYPE:
275 case PROP_ICON:
276 case PROP_LINK_MESSAGE:
277 case PROP_NAVIGATION:
278 case PROP_SECURITY:
279 case PROP_STATUS_MESSAGE:
280 case PROP_IS_BLANK:
281 case PROP_READER_MODE:
282 case PROP_DISPLAY_ADDRESS:
283 case PROP_ENTERING_READER_MODE:
284 /* read only */
285 break;
286 default:
287 break;
288 }
289 }
290
291 static gboolean
ephy_web_view_key_press_event(GtkWidget * widget,GdkEventKey * event)292 ephy_web_view_key_press_event (GtkWidget *widget,
293 GdkEventKey *event)
294 {
295 EphyWebView *web_view = EPHY_WEB_VIEW (widget);
296 gboolean key_handled = FALSE;
297
298 key_handled = GTK_WIDGET_CLASS (ephy_web_view_parent_class)->key_press_event (widget, event);
299
300 if (key_handled)
301 return TRUE;
302
303 g_signal_emit_by_name (web_view, "search-key-press", event, &key_handled);
304
305 return key_handled;
306 }
307
308 static gboolean
ephy_web_view_button_press_event(GtkWidget * widget,GdkEventButton * event)309 ephy_web_view_button_press_event (GtkWidget *widget,
310 GdkEventButton *event)
311 {
312 /* These are the special cases WebkitWebView doesn't handle but we have an
313 * interest in handling. */
314
315 /* Handle typical back/forward mouse buttons. */
316 if (event->button == 8) {
317 webkit_web_view_go_back (WEBKIT_WEB_VIEW (widget));
318 return TRUE;
319 }
320
321 if (event->button == 9) {
322 webkit_web_view_go_forward (WEBKIT_WEB_VIEW (widget));
323 return TRUE;
324 }
325
326 /* Let WebKitWebView handle this. */
327 return GTK_WIDGET_CLASS (ephy_web_view_parent_class)->button_press_event (widget, event);
328 }
329
330 static void
untrack_info_bar(GtkWidget ** tracked_info_bar)331 untrack_info_bar (GtkWidget **tracked_info_bar)
332 {
333 g_assert (tracked_info_bar);
334 g_assert (!*tracked_info_bar || GTK_IS_INFO_BAR (*tracked_info_bar));
335
336 if (*tracked_info_bar) {
337 g_object_remove_weak_pointer (G_OBJECT (*tracked_info_bar), (gpointer *)tracked_info_bar);
338 gtk_widget_destroy (*tracked_info_bar);
339 *tracked_info_bar = NULL;
340 }
341 }
342
343 static void
track_info_bar(GtkWidget * new_info_bar,GtkWidget ** tracked_info_bar)344 track_info_bar (GtkWidget *new_info_bar,
345 GtkWidget **tracked_info_bar)
346 {
347 g_assert (GTK_IS_INFO_BAR (new_info_bar));
348 g_assert (tracked_info_bar);
349 g_assert (!*tracked_info_bar || GTK_IS_INFO_BAR (*tracked_info_bar));
350
351 untrack_info_bar (tracked_info_bar);
352
353 *tracked_info_bar = new_info_bar;
354 g_object_add_weak_pointer (G_OBJECT (new_info_bar),
355 (gpointer *)tracked_info_bar);
356 }
357
358 static GtkWidget *
ephy_web_view_create_form_auth_save_confirmation_info_bar(EphyWebView * web_view,const char * origin,const char * username)359 ephy_web_view_create_form_auth_save_confirmation_info_bar (EphyWebView *web_view,
360 const char *origin,
361 const char *username)
362 {
363 GtkWidget *info_bar;
364 GtkWidget *content_area;
365 GtkWidget *label;
366 char *message;
367
368 LOG ("Going to show infobar about %s", webkit_web_view_get_uri (WEBKIT_WEB_VIEW (web_view)));
369
370 info_bar = gtk_info_bar_new_with_buttons (_("Not No_w"), GTK_RESPONSE_CLOSE,
371 _("_Never Save"), GTK_RESPONSE_REJECT,
372 _("_Save"), GTK_RESPONSE_YES,
373 NULL);
374
375 label = gtk_label_new (NULL);
376 /* Translators: The %s the hostname where this is happening.
377 * Example: mail.google.com.
378 */
379 message = g_markup_printf_escaped (_("Do you want to save your password for “%s”?"), origin);
380 gtk_label_set_markup (GTK_LABEL (label), message);
381 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
382 gtk_label_set_xalign (GTK_LABEL (label), 0);
383 g_free (message);
384
385 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
386 gtk_container_add (GTK_CONTAINER (content_area), label);
387 gtk_widget_show (label);
388
389 track_info_bar (info_bar, &web_view->password_info_bar);
390
391 ephy_embed_add_top_widget (EPHY_GET_EMBED_FROM_EPHY_WEB_VIEW (web_view),
392 info_bar,
393 EPHY_EMBED_TOP_WIDGET_POLICY_RETAIN_ON_TRANSITION);
394
395 return info_bar;
396 }
397
398 typedef struct {
399 EphyPasswordSaveRequestCallback callback;
400 gpointer callback_data;
401 GDestroyNotify callback_destroy;
402 } SaveRequestData;
403
404 static void
save_auth_request_destroy(SaveRequestData * data,GClosure * ignored)405 save_auth_request_destroy (SaveRequestData *data,
406 GClosure *ignored)
407 {
408 if (data->callback_destroy)
409 data->callback_destroy (data->callback_data);
410
411 g_free (data);
412 }
413
414 static void
info_bar_save_request_response_cb(GtkInfoBar * info_bar,gint response_id,SaveRequestData * data)415 info_bar_save_request_response_cb (GtkInfoBar *info_bar,
416 gint response_id,
417 SaveRequestData *data)
418 {
419 g_assert (data->callback);
420 data->callback (response_id, data->callback_data);
421 gtk_widget_destroy (GTK_WIDGET (info_bar));
422 }
423
424 void
ephy_web_view_show_auth_form_save_request(EphyWebView * web_view,const char * origin,const char * username,EphyPasswordSaveRequestCallback response_callback,gpointer response_data,GDestroyNotify response_destroy)425 ephy_web_view_show_auth_form_save_request (EphyWebView *web_view,
426 const char *origin,
427 const char *username,
428 EphyPasswordSaveRequestCallback response_callback,
429 gpointer response_data,
430 GDestroyNotify response_destroy)
431 {
432 GtkWidget *info_bar;
433 SaveRequestData *data;
434
435 info_bar = ephy_web_view_create_form_auth_save_confirmation_info_bar (web_view, origin, username);
436
437 data = g_new (SaveRequestData, 1);
438 data->callback = response_callback;
439 data->callback_data = response_data;
440 data->callback_destroy = response_destroy;
441
442 g_signal_connect_data (info_bar, "response",
443 G_CALLBACK (info_bar_save_request_response_cb),
444 data, (GClosureNotify)save_auth_request_destroy, 0);
445
446 gtk_widget_show (info_bar);
447 }
448
449 static void
update_navigation_flags(WebKitWebView * view)450 update_navigation_flags (WebKitWebView *view)
451 {
452 guint flags = 0;
453
454 if (webkit_web_view_can_go_back (view))
455 flags |= EPHY_WEB_VIEW_NAV_BACK;
456
457 if (webkit_web_view_can_go_forward (view))
458 flags |= EPHY_WEB_VIEW_NAV_FORWARD;
459
460 if (EPHY_WEB_VIEW (view)->nav_flags != (EphyWebViewNavigationFlags)flags) {
461 EPHY_WEB_VIEW (view)->nav_flags = (EphyWebViewNavigationFlags)flags;
462
463 g_object_notify_by_pspec (G_OBJECT (view), obj_properties[PROP_NAVIGATION]);
464 }
465 }
466
467 static void
ephy_web_view_freeze_history(EphyWebView * view)468 ephy_web_view_freeze_history (EphyWebView *view)
469 {
470 view->history_frozen = TRUE;
471 }
472
473 static void
ephy_web_view_thaw_history(EphyWebView * view)474 ephy_web_view_thaw_history (EphyWebView *view)
475 {
476 view->history_frozen = FALSE;
477 }
478
479 static gboolean
ephy_web_view_is_history_frozen(EphyWebView * view)480 ephy_web_view_is_history_frozen (EphyWebView *view)
481 {
482 return view->history_frozen;
483 }
484
485 static void
got_snapshot_path_cb(EphySnapshotService * service,GAsyncResult * result,char * url)486 got_snapshot_path_cb (EphySnapshotService *service,
487 GAsyncResult *result,
488 char *url)
489 {
490 char *snapshot;
491 GError *error = NULL;
492
493 snapshot = ephy_snapshot_service_get_snapshot_path_finish (service, result, &error);
494 if (snapshot) {
495 ephy_embed_shell_set_thumbnail_path (ephy_embed_shell_get_default (), url, snapshot);
496 g_free (snapshot);
497 } else {
498 /* Bad luck, not something to warn about. */
499 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
500 g_info ("Failed to get snapshot for URL %s: %s", url, error->message);
501 g_error_free (error);
502 }
503 g_free (url);
504 }
505
506 static void
take_snapshot(EphyWebView * view)507 take_snapshot (EphyWebView *view)
508 {
509 EphySnapshotService *service = ephy_snapshot_service_get_default ();
510
511 ephy_snapshot_service_get_snapshot_path_async (service, WEBKIT_WEB_VIEW (view),
512 view->cancellable,
513 (GAsyncReadyCallback)got_snapshot_path_cb,
514 g_strdup (view->pending_snapshot_uri));
515 }
516
517 static void
history_service_query_urls_cb(EphyHistoryService * service,gboolean success,GList * urls,EphyWebView * view)518 history_service_query_urls_cb (EphyHistoryService *service,
519 gboolean success,
520 GList *urls,
521 EphyWebView *view)
522 {
523 const char *url = webkit_web_view_get_uri (WEBKIT_WEB_VIEW (view));
524
525 if (!success)
526 goto out;
527
528 /* Have we already started a new load? */
529 if (g_strcmp0 (url, view->pending_snapshot_uri) != 0)
530 goto out;
531
532 for (GList *l = urls; l; l = g_list_next (l)) {
533 EphyHistoryURL *history_url = l->data;
534
535 /* Take snapshot if this URL is one of the top history results. */
536 if (strcmp (history_url->url, view->pending_snapshot_uri) == 0) {
537 take_snapshot (view);
538 break;
539 }
540 }
541
542 out:
543 g_clear_pointer (&view->pending_snapshot_uri, g_free);
544 g_object_unref (view);
545 }
546
547 static gboolean
maybe_take_snapshot(EphyWebView * view)548 maybe_take_snapshot (EphyWebView *view)
549 {
550 EphyEmbedShell *shell;
551 EphyHistoryService *service;
552 EphyHistoryQuery *query;
553
554 view->snapshot_timeout_id = 0;
555
556 if (view->error_page != EPHY_WEB_VIEW_ERROR_PAGE_NONE)
557 return FALSE;
558
559 shell = ephy_embed_shell_get_default ();
560 service = ephy_embed_shell_get_global_history_service (shell);
561
562 /* We want to save snapshots for just a couple more pages than are present
563 * in the overview, so new snapshots are immediately available when the user
564 * deletes a couple pages from the overview. Let's say five more.
565 */
566 query = ephy_history_query_new_for_overview ();
567 query->limit += 5;
568 ephy_history_service_query_urls (service, query, NULL,
569 (EphyHistoryJobCallback)history_service_query_urls_cb,
570 g_object_ref (view));
571 ephy_history_query_free (query);
572
573 return FALSE;
574 }
575
576 static void
_ephy_web_view_update_icon(EphyWebView * view)577 _ephy_web_view_update_icon (EphyWebView *view)
578 {
579 g_clear_object (&view->icon);
580
581 if (view->address) {
582 cairo_surface_t *icon_surface = webkit_web_view_get_favicon (WEBKIT_WEB_VIEW (view));
583 if (icon_surface) {
584 gint scale = gtk_widget_get_scale_factor (GTK_WIDGET (view));
585 view->icon = ephy_pixbuf_get_from_surface_scaled (icon_surface, scale * FAVICON_SIZE, scale * FAVICON_SIZE);
586 }
587 }
588
589 g_object_notify_by_pspec (G_OBJECT (view), obj_properties[PROP_ICON]);
590 }
591
592 static void
icon_changed_cb(EphyWebView * view,GParamSpec * pspec,gpointer user_data)593 icon_changed_cb (EphyWebView *view,
594 GParamSpec *pspec,
595 gpointer user_data)
596 {
597 _ephy_web_view_update_icon (view);
598 }
599
600 static void
password_form_focused_cb(EphyEmbedShell * shell,guint64 page_id,gboolean insecure_form_action,EphyWebView * web_view)601 password_form_focused_cb (EphyEmbedShell *shell,
602 guint64 page_id,
603 gboolean insecure_form_action,
604 EphyWebView *web_view)
605 {
606 GtkWidget *info_bar;
607 GtkWidget *label;
608 GtkWidget *content_area;
609
610 if (web_view->password_form_info_bar)
611 return;
612 if (webkit_web_view_get_page_id (WEBKIT_WEB_VIEW (web_view)) != page_id)
613 return;
614 if (!insecure_form_action && ephy_security_level_is_secure (web_view->security_level))
615 return;
616
617 /* Translators: Message appears when insecure password form is focused. */
618 label = gtk_label_new (_("Heads-up: this form is not secure. If you type your password, it will not be kept private."));
619 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
620 gtk_label_set_xalign (GTK_LABEL (label), 0);
621 gtk_widget_show (label);
622
623 info_bar = gtk_info_bar_new ();
624 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
625 gtk_info_bar_set_show_close_button (GTK_INFO_BAR (info_bar), TRUE);
626 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
627 gtk_container_add (GTK_CONTAINER (content_area), label);
628
629 g_signal_connect (info_bar, "response", G_CALLBACK (gtk_widget_hide), NULL);
630
631 track_info_bar (info_bar, &web_view->password_form_info_bar);
632
633 ephy_embed_add_top_widget (EPHY_GET_EMBED_FROM_EPHY_WEB_VIEW (web_view),
634 info_bar,
635 EPHY_EMBED_TOP_WIDGET_POLICY_DESTROY_ON_TRANSITION);
636 gtk_widget_show (info_bar);
637 }
638
639 static void
allow_tls_certificate_cb(EphyEmbedShell * shell,guint64 page_id,EphyWebView * view)640 allow_tls_certificate_cb (EphyEmbedShell *shell,
641 guint64 page_id,
642 EphyWebView *view)
643 {
644 g_autoptr (GUri) uri = NULL;
645
646 if (webkit_web_view_get_page_id (WEBKIT_WEB_VIEW (view)) != page_id)
647 return;
648
649 g_assert (G_IS_TLS_CERTIFICATE (view->certificate));
650 g_assert (view->tls_error_failing_uri != NULL);
651
652 uri = g_uri_parse (view->tls_error_failing_uri, G_URI_FLAGS_NONE, NULL);
653 webkit_web_context_allow_tls_certificate_for_host (ephy_embed_shell_get_web_context (shell),
654 view->certificate,
655 g_uri_get_host (uri));
656 ephy_web_view_load_url (view, ephy_web_view_get_address (view));
657 }
658
659 static void
allow_unsafe_browsing_cb(EphyEmbedShell * shell,guint64 page_id,EphyWebView * view)660 allow_unsafe_browsing_cb (EphyEmbedShell *shell,
661 guint64 page_id,
662 EphyWebView *view)
663 {
664 if (webkit_web_view_get_page_id (WEBKIT_WEB_VIEW (view)) != page_id)
665 return;
666
667 ephy_web_view_set_should_bypass_safe_browsing (view, TRUE);
668 ephy_web_view_load_url (view, ephy_web_view_get_address (view));
669 }
670
671 static void
_ephy_web_view_set_is_blank(EphyWebView * view,gboolean is_blank)672 _ephy_web_view_set_is_blank (EphyWebView *view,
673 gboolean is_blank)
674 {
675 if (view->is_blank != is_blank) {
676 view->is_blank = is_blank;
677 g_object_notify_by_pspec (G_OBJECT (view), obj_properties[PROP_IS_BLANK]);
678 }
679 }
680
681 static void
readability_js_finish_cb(GObject * object,GAsyncResult * result,gpointer user_data)682 readability_js_finish_cb (GObject *object,
683 GAsyncResult *result,
684 gpointer user_data)
685 {
686 EphyWebView *view = EPHY_WEB_VIEW (user_data);
687 g_autoptr (WebKitJavascriptResult) js_result = NULL;
688 g_autoptr (GError) error = NULL;
689 JSCValue *jsc_value;
690
691 js_result = webkit_web_view_run_javascript_finish (WEBKIT_WEB_VIEW (object), result, &error);
692 if (!js_result) {
693 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
694 g_warning ("Error running javascript: %s", error->message);
695 return;
696 }
697
698 jsc_value = webkit_javascript_result_get_js_value (js_result);
699 if (!jsc_value_is_boolean (jsc_value)) {
700 return;
701 }
702
703 view->reader_mode_available = jsc_value_to_boolean (jsc_value);
704
705 g_object_notify_by_pspec (G_OBJECT (view), obj_properties[PROP_READER_MODE]);
706 }
707
708 static gboolean
run_readability_js_if_needed(gpointer data)709 run_readability_js_if_needed (gpointer data)
710 {
711 EphyWebView *web_view = data;
712
713 /* Internal pages should never receive reader mode. */
714 if (!ephy_embed_utils_is_no_show_address (web_view->address)) {
715 webkit_web_view_run_javascript_from_gresource (WEBKIT_WEB_VIEW (web_view),
716 "/org/gnome/epiphany/readability/Readability-readerable.js",
717 web_view->cancellable,
718 readability_js_finish_cb,
719 web_view);
720 }
721
722 web_view->reader_js_timeout = 0;
723 return G_SOURCE_REMOVE;
724 }
725
726 static void
title_changed_cb(WebKitWebView * web_view,GParamSpec * spec,gpointer data)727 title_changed_cb (WebKitWebView *web_view,
728 GParamSpec *spec,
729 gpointer data)
730 {
731 const char *uri;
732 const char *title;
733 char *title_from_address = NULL;
734 EphyWebView *webview = EPHY_WEB_VIEW (web_view);
735 EphyHistoryService *history = webview->history_service;
736
737 uri = webkit_web_view_get_uri (web_view);
738 title = webkit_web_view_get_title (web_view);
739
740 if (!title && uri)
741 title = title_from_address = ephy_embed_utils_get_title_from_address (uri);
742
743 /* FIXME: we don't follow the same rules for transforming uri here that we do
744 * when adding it to the history db in load_committed. Should probably try to
745 * use EphyWebView:address instead?
746 */
747 if (uri && title && *title && !ephy_web_view_is_history_frozen (webview))
748 ephy_history_service_set_url_title (history, uri, title, NULL, NULL, NULL);
749
750 g_free (title_from_address);
751 }
752
753 static void
ephy_web_view_set_display_address(EphyWebView * view)754 ephy_web_view_set_display_address (EphyWebView *view)
755 {
756 g_clear_pointer (&view->display_address, g_free);
757
758 if (view->address) {
759 if (g_str_has_prefix (view->address, EPHY_PDF_SCHEME))
760 view->display_address = ephy_uri_decode (view->address + strlen (EPHY_PDF_SCHEME) + 1);
761 else
762 view->display_address = ephy_uri_decode (view->address);
763 }
764 }
765
766 /*
767 * Sets the view location to be address. Note that this function might
768 * also set the typed-address property to NULL.
769 */
770 static void
ephy_web_view_set_address(EphyWebView * view,const char * address)771 ephy_web_view_set_address (EphyWebView *view,
772 const char *address)
773 {
774 GObject *object = G_OBJECT (view);
775 gboolean was_empty;
776
777 if (g_strcmp0 (view->address, address) == 0)
778 return;
779
780 was_empty = view->address == NULL;
781 g_free (view->address);
782 view->address = g_strdup (address);
783
784 ephy_web_view_set_display_address (view);
785
786 _ephy_web_view_set_is_blank (view, ephy_embed_utils_url_is_empty (address));
787
788 /* If the view was empty there is no need to clean the typed address. */
789 if (!was_empty && ephy_web_view_is_loading (view) && view->typed_address != NULL)
790 ephy_web_view_set_typed_address (view, NULL);
791
792 g_object_notify_by_pspec (object, obj_properties[PROP_ADDRESS]);
793 g_object_notify_by_pspec (object, obj_properties[PROP_DISPLAY_ADDRESS]);
794 }
795
796 static void
uri_changed_cb(WebKitWebView * web_view,GParamSpec * spec,gpointer data)797 uri_changed_cb (WebKitWebView *web_view,
798 GParamSpec *spec,
799 gpointer data)
800 {
801 EphyWebView *view = EPHY_WEB_VIEW (web_view);
802
803 if (view->document_type != EPHY_WEB_VIEW_DOCUMENT_PDF)
804 ephy_web_view_set_address (view,
805 webkit_web_view_get_uri (web_view));
806 }
807
808 static void
mouse_target_changed_cb(EphyWebView * web_view,WebKitHitTestResult * hit_test_result,guint modifiers,gpointer data)809 mouse_target_changed_cb (EphyWebView *web_view,
810 WebKitHitTestResult *hit_test_result,
811 guint modifiers,
812 gpointer data)
813 {
814 const char *message = NULL;
815
816 if (webkit_hit_test_result_context_is_link (hit_test_result))
817 message = webkit_hit_test_result_get_link_uri (hit_test_result);
818
819 ephy_web_view_set_link_message (web_view, message);
820 }
821
822 static void
process_terminated_cb(EphyWebView * web_view,WebKitWebProcessTerminationReason reason,gpointer user_data)823 process_terminated_cb (EphyWebView *web_view,
824 WebKitWebProcessTerminationReason reason,
825 gpointer user_data)
826 {
827 EphyWebViewErrorPage error_page = EPHY_WEB_VIEW_ERROR_PROCESS_CRASH;
828
829 switch (reason) {
830 case WEBKIT_WEB_PROCESS_CRASHED:
831 g_warning (_("Web process crashed"));
832 break;
833 case WEBKIT_WEB_PROCESS_EXCEEDED_MEMORY_LIMIT:
834 g_warning (_("Web process terminated due to exceeding memory limit"));
835 break;
836 case WEBKIT_WEB_PROCESS_TERMINATED_BY_API:
837 g_warning (_("Web process terminated by API request"));
838 error_page = EPHY_WEB_VIEW_ERROR_UNRESPONSIVE_PROCESS;
839 break;
840 }
841
842 if (!ephy_embed_has_load_pending (EPHY_GET_EMBED_FROM_EPHY_WEB_VIEW (web_view))) {
843 ephy_web_view_load_error_page (web_view, ephy_web_view_get_address (web_view),
844 error_page, NULL, NULL);
845 }
846 }
847
848 static gboolean
849 unresponsive_process_timeout_cb (gpointer user_data);
850
851 static void
on_unresponsive_dialog_response(GtkDialog * dialog,int response_id,gpointer user_data)852 on_unresponsive_dialog_response (GtkDialog *dialog,
853 int response_id,
854 gpointer user_data)
855 {
856 EphyWebView *web_view = EPHY_WEB_VIEW (user_data);
857
858 if (response_id == GTK_RESPONSE_YES)
859 webkit_web_view_terminate_web_process (WEBKIT_WEB_VIEW (web_view));
860 else
861 web_view->unresponsive_process_timeout_id = g_timeout_add_seconds_full (G_PRIORITY_HIGH,
862 5,
863 (GSourceFunc)unresponsive_process_timeout_cb,
864 web_view,
865 NULL);
866 g_clear_pointer (&web_view->unresponsive_process_dialog, gtk_widget_destroy);
867 }
868
869 static gboolean
unresponsive_process_timeout_cb(gpointer user_data)870 unresponsive_process_timeout_cb (gpointer user_data)
871 {
872 EphyWebView *web_view = EPHY_WEB_VIEW (user_data);
873
874 if (!gtk_widget_get_mapped (GTK_WIDGET (web_view)))
875 return G_SOURCE_CONTINUE;
876
877 web_view->unresponsive_process_dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (web_view))),
878 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_USE_HEADER_BAR,
879 GTK_MESSAGE_QUESTION,
880 GTK_BUTTONS_NONE,
881 _("The current page '%s' is unresponsive"),
882 ephy_web_view_get_address (web_view));
883
884 gtk_dialog_add_button (GTK_DIALOG (web_view->unresponsive_process_dialog), _("_Wait"), GTK_RESPONSE_NO);
885 gtk_dialog_add_button (GTK_DIALOG (web_view->unresponsive_process_dialog), _("_Kill"), GTK_RESPONSE_YES);
886
887 g_signal_connect (web_view->unresponsive_process_dialog, "response", G_CALLBACK (on_unresponsive_dialog_response), web_view);
888 gtk_widget_show_all (web_view->unresponsive_process_dialog);
889
890 web_view->unresponsive_process_timeout_id = 0;
891
892 return G_SOURCE_REMOVE;
893 }
894
895 static void
is_web_process_responsive_changed_cb(EphyWebView * web_view,GParamSpec * pspec,gpointer user_data)896 is_web_process_responsive_changed_cb (EphyWebView *web_view,
897 GParamSpec *pspec,
898 gpointer user_data)
899 {
900 gboolean responsive = webkit_web_view_get_is_web_process_responsive (WEBKIT_WEB_VIEW (web_view));
901
902 g_clear_handle_id (&web_view->unresponsive_process_timeout_id, g_source_remove);
903
904 if (web_view->unresponsive_process_dialog && responsive) {
905 g_signal_handlers_disconnect_by_func (web_view->unresponsive_process_dialog, on_unresponsive_dialog_response, web_view);
906 g_clear_pointer (&web_view->unresponsive_process_dialog, gtk_widget_destroy);
907 }
908
909 if (!responsive) {
910 web_view->unresponsive_process_timeout_id = g_timeout_add_seconds_full (G_PRIORITY_HIGH,
911 10,
912 (GSourceFunc)unresponsive_process_timeout_cb,
913 web_view,
914 NULL);
915 }
916 }
917
918 static gboolean
decide_policy_cb(WebKitWebView * web_view,WebKitPolicyDecision * decision,WebKitPolicyDecisionType decision_type,gpointer user_data)919 decide_policy_cb (WebKitWebView *web_view,
920 WebKitPolicyDecision *decision,
921 WebKitPolicyDecisionType decision_type,
922 gpointer user_data)
923 {
924 WebKitResponsePolicyDecision *response_decision;
925 WebKitURIResponse *response;
926 WebKitURIRequest *request;
927 WebKitWebResource *main_resource;
928 EphyWebViewDocumentType type;
929 const char *mime_type;
930 const char *request_uri;
931 gboolean is_main_resource;
932
933 if (decision_type != WEBKIT_POLICY_DECISION_TYPE_RESPONSE)
934 return FALSE;
935
936 /* If WebKit can handle the MIME type, let it.
937 * Otherwise, we'll start a download.
938 */
939 response_decision = WEBKIT_RESPONSE_POLICY_DECISION (decision);
940 if (webkit_response_policy_decision_is_mime_type_supported (response_decision))
941 return FALSE;
942
943 response = webkit_response_policy_decision_get_response (response_decision);
944 mime_type = webkit_uri_response_get_mime_type (response);
945 request = webkit_response_policy_decision_get_request (response_decision);
946 request_uri = webkit_uri_request_get_uri (request);
947
948 main_resource = webkit_web_view_get_main_resource (web_view);
949 is_main_resource = g_strcmp0 (webkit_web_resource_get_uri (main_resource), request_uri) == 0;
950
951 /* If it's not the main resource, we don't need to set the document type. */
952 if (is_main_resource) {
953 const char *method = webkit_uri_request_get_http_method (request);
954
955 type = EPHY_WEB_VIEW_DOCUMENT_OTHER;
956 if (strcmp (mime_type, "text/html") == 0 || strcmp (mime_type, "text/plain") == 0) {
957 type = EPHY_WEB_VIEW_DOCUMENT_HTML;
958 } else if (strcmp (mime_type, "application/xhtml+xml") == 0) {
959 type = EPHY_WEB_VIEW_DOCUMENT_XML;
960 } else if (strncmp (mime_type, "image/", 6) == 0) {
961 type = EPHY_WEB_VIEW_DOCUMENT_IMAGE;
962 } else if (strcmp (mime_type, "application/pdf") == 0 && strcmp (method, "GET") == 0) {
963 g_autofree char *pdf_uri = NULL;
964
965 /* FIXME: figure out how to make PDFs work in iframes. */
966 type = EPHY_WEB_VIEW_DOCUMENT_PDF;
967 EPHY_WEB_VIEW (web_view)->document_type = type;
968
969 pdf_uri = g_strconcat (EPHY_PDF_SCHEME, ":", request_uri, NULL);
970
971 webkit_web_view_load_uri (web_view, pdf_uri);
972
973 return FALSE;
974 }
975
976 /* FIXME: maybe it makes more sense to have an API to query the mime
977 * type when the load of a page starts than doing this here.
978 */
979 if (EPHY_WEB_VIEW (web_view)->document_type != type) {
980 EPHY_WEB_VIEW (web_view)->document_type = type;
981
982 g_object_notify_by_pspec (G_OBJECT (web_view), obj_properties[PROP_DOCUMENT_TYPE]);
983 }
984 }
985
986 webkit_policy_decision_download (decision);
987 return TRUE;
988 }
989
990 typedef struct {
991 EphyWebView *web_view;
992 WebKitPermissionRequest *request;
993 char *origin;
994 } PermissionRequestData;
995
996 static PermissionRequestData *
permission_request_data_new(EphyWebView * web_view,WebKitPermissionRequest * request,const char * origin)997 permission_request_data_new (EphyWebView *web_view,
998 WebKitPermissionRequest *request,
999 const char *origin)
1000 {
1001 PermissionRequestData *data;
1002 data = g_new (PermissionRequestData, 1);
1003 data->web_view = web_view;
1004 /* Ref the decision to keep it alive while we decide */
1005 data->request = g_object_ref (request);
1006 data->origin = g_strdup (origin);
1007 return data;
1008 }
1009
1010 static void
permission_request_data_free(PermissionRequestData * data)1011 permission_request_data_free (PermissionRequestData *data)
1012 {
1013 g_object_unref (data->request);
1014 g_free (data->origin);
1015 g_free (data);
1016 }
1017
1018 static void
permission_request_info_bar_destroyed_cb(PermissionRequestData * data,GObject * where_the_info_bar_was)1019 permission_request_info_bar_destroyed_cb (PermissionRequestData *data,
1020 GObject *where_the_info_bar_was)
1021 {
1022 webkit_permission_request_deny (data->request);
1023 permission_request_data_free (data);
1024 }
1025
1026 static void
decide_on_permission_request(GtkWidget * info_bar,int response,PermissionRequestData * data)1027 decide_on_permission_request (GtkWidget *info_bar,
1028 int response,
1029 PermissionRequestData *data)
1030 {
1031 const char *address;
1032 EphyPermissionType permission_type = EPHY_PERMISSION_TYPE_SHOW_NOTIFICATIONS;
1033
1034 switch (response) {
1035 case GTK_RESPONSE_YES:
1036 webkit_permission_request_allow (data->request);
1037 break;
1038 default:
1039 webkit_permission_request_deny (data->request);
1040 break;
1041 }
1042
1043 if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST (data->request)) {
1044 permission_type = EPHY_PERMISSION_TYPE_ACCESS_LOCATION;
1045 } else if (WEBKIT_IS_NOTIFICATION_PERMISSION_REQUEST (data->request)) {
1046 permission_type = EPHY_PERMISSION_TYPE_SHOW_NOTIFICATIONS;
1047 } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST (data->request)) {
1048 gboolean is_for_audio_device = webkit_user_media_permission_is_for_audio_device (WEBKIT_USER_MEDIA_PERMISSION_REQUEST (data->request));
1049 gboolean is_for_video_device = webkit_user_media_permission_is_for_video_device (WEBKIT_USER_MEDIA_PERMISSION_REQUEST (data->request));
1050
1051 if (is_for_audio_device) {
1052 if (is_for_video_device)
1053 permission_type = EPHY_PERMISSION_TYPE_ACCESS_WEBCAM_AND_MICROPHONE;
1054 else
1055 permission_type = EPHY_PERMISSION_TYPE_ACCESS_MICROPHONE;
1056 } else if (is_for_video_device) {
1057 permission_type = EPHY_PERMISSION_TYPE_ACCESS_WEBCAM;
1058 }
1059 } else {
1060 g_assert_not_reached ();
1061 }
1062
1063 address = ephy_web_view_get_address (data->web_view);
1064
1065 if (response != GTK_RESPONSE_NONE && ephy_embed_utils_address_has_web_scheme (address)) {
1066 EphyEmbedShell *shell;
1067 EphyPermissionsManager *permissions_manager;
1068
1069 shell = ephy_embed_shell_get_default ();
1070 permissions_manager = ephy_embed_shell_get_permissions_manager (shell);
1071
1072 if (permission_type != EPHY_PERMISSION_TYPE_ACCESS_WEBCAM_AND_MICROPHONE) {
1073 ephy_permissions_manager_set_permission (permissions_manager,
1074 permission_type,
1075 data->origin,
1076 response == GTK_RESPONSE_YES ? EPHY_PERMISSION_PERMIT
1077 : EPHY_PERMISSION_DENY);
1078 } else {
1079 ephy_permissions_manager_set_permission (permissions_manager,
1080 EPHY_PERMISSION_TYPE_ACCESS_WEBCAM,
1081 data->origin,
1082 response == GTK_RESPONSE_YES ? EPHY_PERMISSION_PERMIT
1083 : EPHY_PERMISSION_DENY);
1084 ephy_permissions_manager_set_permission (permissions_manager,
1085 EPHY_PERMISSION_TYPE_ACCESS_MICROPHONE,
1086 data->origin,
1087 response == GTK_RESPONSE_YES ? EPHY_PERMISSION_PERMIT
1088 : EPHY_PERMISSION_DENY);
1089 }
1090 }
1091
1092 g_object_weak_unref (G_OBJECT (info_bar), (GWeakNotify)permission_request_info_bar_destroyed_cb, data);
1093 gtk_widget_destroy (info_bar);
1094 permission_request_data_free (data);
1095 }
1096
1097 static void
show_permission_request_info_bar(WebKitWebView * web_view,WebKitPermissionRequest * decision,EphyPermissionType permission_type)1098 show_permission_request_info_bar (WebKitWebView *web_view,
1099 WebKitPermissionRequest *decision,
1100 EphyPermissionType permission_type)
1101 {
1102 PermissionRequestData *data;
1103 GtkWidget *info_bar;
1104 GtkWidget *content_area;
1105 GtkWidget *label;
1106 char *message;
1107 char *origin;
1108 char *bold_origin;
1109
1110 info_bar = gtk_info_bar_new_with_buttons (_("Deny"), GTK_RESPONSE_NO,
1111 _("Allow"), GTK_RESPONSE_YES,
1112 NULL);
1113
1114 /* Label */
1115 origin = ephy_uri_to_security_origin (webkit_web_view_get_uri (web_view));
1116 if (origin == NULL)
1117 return;
1118
1119 bold_origin = g_markup_printf_escaped ("<b>%s</b>", origin);
1120
1121 switch (permission_type) {
1122 case EPHY_PERMISSION_TYPE_SHOW_NOTIFICATIONS:
1123 /* Translators: Notification policy for a specific site. */
1124 message = g_strdup_printf (_("The page at %s wants to show desktop notifications."),
1125 bold_origin);
1126 break;
1127 case EPHY_PERMISSION_TYPE_ACCESS_LOCATION:
1128 /* Translators: Geolocation policy for a specific site. */
1129 message = g_strdup_printf (_("The page at %s wants to know your location."),
1130 bold_origin);
1131 break;
1132 case EPHY_PERMISSION_TYPE_ACCESS_MICROPHONE:
1133 /* Translators: Microphone policy for a specific site. */
1134 message = g_strdup_printf (_("The page at %s wants to use your microphone."),
1135 bold_origin);
1136 break;
1137 case EPHY_PERMISSION_TYPE_ACCESS_WEBCAM:
1138 /* Translators: Webcam policy for a specific site. */
1139 message = g_strdup_printf (_("The page at %s wants to use your webcam."),
1140 bold_origin);
1141 break;
1142 case EPHY_PERMISSION_TYPE_ACCESS_WEBCAM_AND_MICROPHONE:
1143 /* Translators: Webcam and microphone policy for a specific site. */
1144 message = g_strdup_printf (_("The page at %s wants to use your webcam and microphone."),
1145 bold_origin);
1146 break;
1147 case EPHY_PERMISSION_TYPE_SAVE_PASSWORD:
1148 default:
1149 g_assert_not_reached ();
1150 }
1151
1152 label = gtk_label_new (NULL);
1153 gtk_label_set_markup (GTK_LABEL (label), message);
1154 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1155 gtk_label_set_xalign (GTK_LABEL (label), 0);
1156
1157 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1158 gtk_container_add (GTK_CONTAINER (content_area), label);
1159
1160 gtk_widget_show_all (info_bar);
1161
1162 data = permission_request_data_new (EPHY_WEB_VIEW (web_view), decision, origin);
1163
1164 g_signal_connect (info_bar, "response",
1165 G_CALLBACK (decide_on_permission_request),
1166 data);
1167 g_object_weak_ref (G_OBJECT (info_bar), (GWeakNotify)permission_request_info_bar_destroyed_cb, data);
1168
1169 switch (permission_type) {
1170 case EPHY_PERMISSION_TYPE_SHOW_NOTIFICATIONS:
1171 track_info_bar (info_bar, &EPHY_WEB_VIEW (web_view)->notification_info_bar);
1172 break;
1173 case EPHY_PERMISSION_TYPE_ACCESS_LOCATION:
1174 track_info_bar (info_bar, &EPHY_WEB_VIEW (web_view)->geolocation_info_bar);
1175 break;
1176 case EPHY_PERMISSION_TYPE_ACCESS_MICROPHONE:
1177 track_info_bar (info_bar, &EPHY_WEB_VIEW (web_view)->microphone_info_bar);
1178 break;
1179 case EPHY_PERMISSION_TYPE_ACCESS_WEBCAM:
1180 track_info_bar (info_bar, &EPHY_WEB_VIEW (web_view)->webcam_info_bar);
1181 break;
1182 case EPHY_PERMISSION_TYPE_ACCESS_WEBCAM_AND_MICROPHONE:
1183 track_info_bar (info_bar, &EPHY_WEB_VIEW (web_view)->webcam_mic_info_bar);
1184 break;
1185 case EPHY_PERMISSION_TYPE_SAVE_PASSWORD:
1186 default:
1187 g_assert_not_reached ();
1188 }
1189
1190 ephy_embed_add_top_widget (EPHY_GET_EMBED_FROM_EPHY_WEB_VIEW (web_view),
1191 info_bar,
1192 EPHY_EMBED_TOP_WIDGET_POLICY_DESTROY_ON_TRANSITION);
1193
1194 g_free (message);
1195 g_free (origin);
1196 g_free (bold_origin);
1197 }
1198
1199 static void
decide_on_itp_permission_request(GtkWidget * info_bar,int response,WebKitPermissionRequest * request)1200 decide_on_itp_permission_request (GtkWidget *info_bar,
1201 int response,
1202 WebKitPermissionRequest *request)
1203 {
1204 switch (response) {
1205 case GTK_RESPONSE_YES:
1206 webkit_permission_request_allow (request);
1207 break;
1208 default:
1209 webkit_permission_request_deny (request);
1210 break;
1211 }
1212
1213 g_object_set_data (G_OBJECT (info_bar), "ephy-itp-decision", NULL);
1214 gtk_widget_destroy (info_bar);
1215 }
1216
1217 static void
ephy_web_view_show_itp_permission_info_bar(EphyWebView * web_view,WebKitWebsiteDataAccessPermissionRequest * decision)1218 ephy_web_view_show_itp_permission_info_bar (EphyWebView *web_view,
1219 WebKitWebsiteDataAccessPermissionRequest *decision)
1220 {
1221 GtkWidget *info_bar;
1222 GtkWidget *content_area;
1223 GtkWidget *box;
1224 GtkWidget *label;
1225 g_autofree char *message = NULL;
1226 g_autofree char *secondary_message = NULL;
1227 g_autofree char *markup = NULL;
1228 const char *requesting_domain;
1229 const char *current_domain;
1230
1231 info_bar = gtk_info_bar_new_with_buttons (_("Deny"), GTK_RESPONSE_NO,
1232 _("Allow"), GTK_RESPONSE_YES,
1233 NULL);
1234
1235 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1236
1237 requesting_domain = webkit_website_data_access_permission_request_get_requesting_domain (decision);
1238 current_domain = webkit_website_data_access_permission_request_get_current_domain (decision);
1239 message = g_strdup_printf (_("Do you want to allow “%s” to use cookies while browsing “%s”?"), requesting_domain, current_domain);
1240 markup = g_strdup_printf ("<span weight='bold'>%s</span>", message);
1241 label = gtk_label_new (NULL);
1242 gtk_label_set_markup (GTK_LABEL (label), markup);
1243 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1244 gtk_label_set_xalign (GTK_LABEL (label), 0);
1245 gtk_container_add (GTK_CONTAINER (box), label);
1246 gtk_widget_show (label);
1247
1248 secondary_message = g_strdup_printf (_("This will allow “%s” to track your activity."), requesting_domain);
1249 label = gtk_label_new (secondary_message);
1250 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1251 gtk_label_set_xalign (GTK_LABEL (label), 0);
1252 gtk_container_add (GTK_CONTAINER (box), label);
1253 gtk_widget_show (label);
1254
1255 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1256 gtk_container_add (GTK_CONTAINER (content_area), box);
1257 gtk_widget_show (box);
1258
1259 track_info_bar (info_bar, &web_view->itp_info_bar);
1260
1261 g_signal_connect (info_bar, "response",
1262 G_CALLBACK (decide_on_itp_permission_request),
1263 decision);
1264 g_object_set_data_full (G_OBJECT (info_bar), "ephy-itp-decision", g_object_ref (decision), g_object_unref);
1265
1266 ephy_embed_add_top_widget (EPHY_GET_EMBED_FROM_EPHY_WEB_VIEW (web_view),
1267 info_bar,
1268 EPHY_EMBED_TOP_WIDGET_POLICY_DESTROY_ON_TRANSITION);
1269 gtk_widget_show (info_bar);
1270 }
1271
1272 static gboolean
permission_request_cb(WebKitWebView * web_view,WebKitPermissionRequest * decision)1273 permission_request_cb (WebKitWebView *web_view,
1274 WebKitPermissionRequest *decision)
1275 {
1276 const char *address;
1277 char *origin;
1278 EphyEmbedShell *shell;
1279 EphyPermissionsManager *permissions_manager;
1280 EphyPermission permission;
1281 EphyPermissionType permission_type = EPHY_PERMISSION_TYPE_SHOW_NOTIFICATIONS;
1282
1283 shell = ephy_embed_shell_get_default ();
1284
1285 if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST (decision)) {
1286 permission_type = EPHY_PERMISSION_TYPE_ACCESS_LOCATION;
1287 } else if (WEBKIT_IS_NOTIFICATION_PERMISSION_REQUEST (decision)) {
1288 permission_type = EPHY_PERMISSION_TYPE_SHOW_NOTIFICATIONS;
1289 } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST (decision)) {
1290 gboolean is_for_audio_device = webkit_user_media_permission_is_for_audio_device (WEBKIT_USER_MEDIA_PERMISSION_REQUEST (decision));
1291 gboolean is_for_video_device = webkit_user_media_permission_is_for_video_device (WEBKIT_USER_MEDIA_PERMISSION_REQUEST (decision));
1292
1293 if (is_for_audio_device) {
1294 if (is_for_video_device)
1295 permission_type = EPHY_PERMISSION_TYPE_ACCESS_WEBCAM_AND_MICROPHONE;
1296 else
1297 permission_type = EPHY_PERMISSION_TYPE_ACCESS_MICROPHONE;
1298 } else if (is_for_video_device) {
1299 permission_type = EPHY_PERMISSION_TYPE_ACCESS_WEBCAM;
1300 } else {
1301 return FALSE;
1302 }
1303 } else if (WEBKIT_IS_WEBSITE_DATA_ACCESS_PERMISSION_REQUEST (decision)) {
1304 ephy_web_view_show_itp_permission_info_bar (EPHY_WEB_VIEW (web_view),
1305 WEBKIT_WEBSITE_DATA_ACCESS_PERMISSION_REQUEST (decision));
1306 return TRUE;
1307 } else {
1308 return FALSE;
1309 }
1310
1311 address = ephy_web_view_get_address (EPHY_WEB_VIEW (web_view));
1312 origin = ephy_uri_to_security_origin (address);
1313 if (origin == NULL)
1314 return FALSE;
1315
1316 permissions_manager = ephy_embed_shell_get_permissions_manager (ephy_embed_shell_get_default ());
1317
1318 if (permission_type != EPHY_PERMISSION_TYPE_ACCESS_WEBCAM_AND_MICROPHONE) {
1319 permission = ephy_permissions_manager_get_permission (permissions_manager,
1320 permission_type,
1321 origin);
1322 } else {
1323 EphyPermission video_permission;
1324 EphyPermission mic_permission;
1325
1326 video_permission = ephy_permissions_manager_get_permission (permissions_manager,
1327 EPHY_PERMISSION_TYPE_ACCESS_WEBCAM,
1328 origin);
1329 mic_permission = ephy_permissions_manager_get_permission (permissions_manager,
1330 EPHY_PERMISSION_TYPE_ACCESS_MICROPHONE,
1331 origin);
1332
1333 if (video_permission == mic_permission)
1334 permission = video_permission;
1335 else
1336 permission = EPHY_PERMISSION_UNDECIDED;
1337 }
1338
1339 switch (permission) {
1340 case EPHY_PERMISSION_PERMIT:
1341 webkit_permission_request_allow (decision);
1342 goto out;
1343 case EPHY_PERMISSION_DENY:
1344 webkit_permission_request_deny (decision);
1345 goto out;
1346 case EPHY_PERMISSION_UNDECIDED:
1347 /* Application mode implies being OK with notifications. */
1348 if (permission_type == EPHY_PERMISSION_TYPE_SHOW_NOTIFICATIONS &&
1349 ephy_embed_shell_get_mode (shell) == EPHY_EMBED_SHELL_MODE_APPLICATION) {
1350 ephy_permissions_manager_set_permission (permissions_manager,
1351 permission_type,
1352 origin,
1353 EPHY_PERMISSION_PERMIT);
1354 webkit_permission_request_allow (decision);
1355 } else {
1356 show_permission_request_info_bar (web_view, decision, permission_type);
1357 }
1358 }
1359
1360 out:
1361 g_free (origin);
1362
1363 return TRUE;
1364 }
1365
1366 static void
get_host_for_url_cb(gpointer service,gboolean success,gpointer result_data,gpointer user_data)1367 get_host_for_url_cb (gpointer service,
1368 gboolean success,
1369 gpointer result_data,
1370 gpointer user_data)
1371 {
1372 EphyHistoryHost *host;
1373 EphyWebView *view;
1374 double current_zoom;
1375 double set_zoom;
1376
1377 if (success == FALSE)
1378 return;
1379
1380 view = EPHY_WEB_VIEW (user_data);
1381 host = (EphyHistoryHost *)result_data;
1382
1383 current_zoom = webkit_web_view_get_zoom_level (WEBKIT_WEB_VIEW (view));
1384
1385 /* Use default zoom level in case web page is
1386 * - not visited before
1387 * - uses default zoom level (0)
1388 */
1389 if (host->visit_count == 0 || host->zoom_level == 0.0) {
1390 set_zoom = g_settings_get_double (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_DEFAULT_ZOOM_LEVEL);
1391 } else {
1392 set_zoom = host->zoom_level;
1393 }
1394
1395 if (set_zoom != current_zoom) {
1396 view->is_setting_zoom = TRUE;
1397 webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (view), set_zoom);
1398 view->is_setting_zoom = FALSE;
1399 }
1400 }
1401
1402 static void
restore_zoom_level(EphyWebView * view,const char * address)1403 restore_zoom_level (EphyWebView *view,
1404 const char *address)
1405 {
1406 if (ephy_embed_utils_address_has_web_scheme (address))
1407 ephy_history_service_get_host_for_url (view->history_service,
1408 address, view->cancellable,
1409 (EphyHistoryJobCallback)get_host_for_url_cb, view);
1410 }
1411
1412 static void
ephy_web_view_set_loading_message(EphyWebView * view,const char * address)1413 ephy_web_view_set_loading_message (EphyWebView *view,
1414 const char *address)
1415 {
1416 g_clear_pointer (&view->loading_message, g_free);
1417 if (address) {
1418 char *decoded_address;
1419 char *title;
1420
1421 decoded_address = ephy_uri_decode (address);
1422 title = ephy_embed_utils_get_title_from_address (decoded_address);
1423
1424 if (title != NULL && title[0] != '\0') {
1425 /* translators: %s here is the address of the web page */
1426 view->loading_message = g_strdup_printf (_("Loading “%s”…"), title);
1427 } else {
1428 view->loading_message = g_strdup (_("Loading…"));
1429 }
1430
1431 g_free (decoded_address);
1432 g_free (title);
1433 } else {
1434 view->loading_message = g_strdup (_("Loading…"));
1435 }
1436
1437 g_object_notify_by_pspec (G_OBJECT (view), obj_properties[PROP_STATUS_MESSAGE]);
1438 }
1439
1440 static void
ephy_web_view_unset_loading_message(EphyWebView * view)1441 ephy_web_view_unset_loading_message (EphyWebView *view)
1442 {
1443 g_clear_pointer (&view->loading_message, g_free);
1444 g_object_notify_by_pspec (G_OBJECT (view), obj_properties[PROP_STATUS_MESSAGE]);
1445 }
1446
1447 static void
ephy_web_view_set_committed_location(EphyWebView * view,const char * location)1448 ephy_web_view_set_committed_location (EphyWebView *view,
1449 const char *location)
1450 {
1451 GObject *object = G_OBJECT (view);
1452
1453 g_object_freeze_notify (object);
1454
1455 /* Do this up here so we still have the old address around. */
1456 ephy_file_monitor_update_location (view->file_monitor, location);
1457
1458 if (location == NULL || location[0] == '\0') {
1459 ephy_web_view_set_address (view, NULL);
1460 } else if (g_str_has_prefix (location, EPHY_ABOUT_SCHEME ":applications")) {
1461 g_autoptr (GUri) uri = NULL;
1462 g_autofree char *new_address = NULL;
1463
1464 /* Strip the query from the URL for about:applications. */
1465 uri = g_uri_parse (location, G_URI_FLAGS_NONE, NULL);
1466 new_address = g_uri_to_string_partial (uri, G_URI_HIDE_QUERY);
1467 ephy_web_view_set_address (view, new_address);
1468 } else {
1469 /* We do this to get rid of an eventual password in the URL. */
1470 ephy_web_view_set_address (view, location);
1471 ephy_web_view_set_loading_message (view, location);
1472 }
1473
1474 g_clear_pointer (&view->last_committed_address, g_free);
1475 view->last_committed_address = g_strdup (view->address);
1476
1477 ephy_web_view_set_link_message (view, NULL);
1478
1479 _ephy_web_view_update_icon (view);
1480
1481 g_object_thaw_notify (object);
1482 }
1483
1484 static char *
hostname_to_tld(const char * hostname)1485 hostname_to_tld (const char *hostname)
1486 {
1487 g_auto (GStrv) parts = NULL;
1488 guint length;
1489
1490 parts = g_strsplit (hostname, ".", 0);
1491 length = g_strv_length (parts);
1492
1493 if (length >= 1)
1494 return g_strdup (parts[length - 1]);
1495
1496 return g_strdup ("");
1497 }
1498
1499 static void
update_security_status_for_committed_load(EphyWebView * view,const char * uri)1500 update_security_status_for_committed_load (EphyWebView *view,
1501 const char *uri)
1502 {
1503 EphySecurityLevel security_level = EPHY_SECURITY_LEVEL_NO_SECURITY;
1504 EphyEmbed *embed = NULL;
1505 GtkWidget *toplevel;
1506 WebKitWebContext *web_context;
1507 WebKitSecurityManager *security_manager;
1508 g_autoptr (GUri) guri = NULL;
1509 g_autofree char *tld = NULL;
1510
1511 if (view->loading_error_page)
1512 return;
1513
1514 if (g_str_has_prefix (uri, "ephy-webextension://")) {
1515 /* Hidden WebExtension webview, ignoring */
1516 return;
1517 }
1518
1519 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
1520 if (EPHY_IS_EMBED_CONTAINER (toplevel))
1521 embed = EPHY_GET_EMBED_FROM_EPHY_WEB_VIEW (view);
1522 web_context = webkit_web_view_get_context (WEBKIT_WEB_VIEW (view));
1523 security_manager = webkit_web_context_get_security_manager (web_context);
1524 guri = g_uri_parse (uri, G_URI_FLAGS_NONE, NULL);
1525
1526 g_clear_object (&view->certificate);
1527 g_clear_pointer (&view->tls_error_failing_uri, g_free);
1528
1529 if (guri && g_uri_get_host (guri))
1530 tld = hostname_to_tld (g_uri_get_host (guri));
1531
1532 if (!guri ||
1533 strcmp (g_uri_get_scheme (guri), EPHY_VIEW_SOURCE_SCHEME) == 0 ||
1534 strcmp (g_uri_get_scheme (guri), EPHY_READER_SCHEME) == 0 ||
1535 strcmp (g_uri_get_scheme (guri), EPHY_PDF_SCHEME) == 0 ||
1536 g_strcmp0 (tld, "127.0.0.1") == 0 ||
1537 g_strcmp0 (tld, "::1") == 0 ||
1538 g_strcmp0 (tld, "localhost") == 0 || /* We trust localhost to be local since glib!616. */
1539 webkit_security_manager_uri_scheme_is_local (security_manager, g_uri_get_scheme (guri)) ||
1540 webkit_security_manager_uri_scheme_is_empty_document (security_manager, g_uri_get_scheme (guri))) {
1541 security_level = EPHY_SECURITY_LEVEL_LOCAL_PAGE;
1542 } else if (webkit_web_view_get_tls_info (WEBKIT_WEB_VIEW (view), &view->certificate, &view->tls_errors)) {
1543 g_object_ref (view->certificate);
1544 security_level = view->tls_errors == 0 ?
1545 EPHY_SECURITY_LEVEL_STRONG_SECURITY : EPHY_SECURITY_LEVEL_UNACCEPTABLE_CERTIFICATE;
1546 } else if (!embed || ephy_embed_has_load_pending (embed)) {
1547 security_level = EPHY_SECURITY_LEVEL_TO_BE_DETERMINED;
1548 }
1549
1550 ephy_web_view_set_security_level (view, security_level);
1551 }
1552
1553 static void
update_ucm_ads_state(WebKitWebView * web_view,const char * uri)1554 update_ucm_ads_state (WebKitWebView *web_view,
1555 const char *uri)
1556 {
1557 WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager (web_view);
1558 EphyPermission permission = EPHY_PERMISSION_UNDECIDED;
1559 gboolean enable = FALSE;
1560 g_autofree gchar *origin = NULL;
1561 EphyEmbedShell *shell = ephy_embed_shell_get_default ();
1562
1563 origin = ephy_uri_to_security_origin (uri);
1564
1565 /* Check page setting first in case it overwrites global setting */
1566 if (origin)
1567 permission = ephy_permissions_manager_get_permission (ephy_embed_shell_get_permissions_manager (shell),
1568 EPHY_PERMISSION_TYPE_SHOW_ADS,
1569 origin);
1570 enable = permission == EPHY_PERMISSION_DENY;
1571 if (permission == EPHY_PERMISSION_UNDECIDED && g_settings_get_boolean (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_ENABLE_ADBLOCK))
1572 enable = TRUE;
1573
1574 ephy_filters_manager_set_ucm_forbids_ads (ephy_embed_shell_get_filters_manager (shell), ucm, enable);
1575 }
1576
1577 static void
reset_background_color(WebKitWebView * web_view)1578 reset_background_color (WebKitWebView *web_view)
1579 {
1580 GdkRGBA white = { 1.0, 1.0, 1.0, 1.0 };
1581
1582 /* https://bugs.webkit.org/show_bug.cgi?id=206953#c2 */
1583 webkit_web_view_set_background_color (web_view, &white);
1584 }
1585
1586 static void
load_changed_cb(WebKitWebView * web_view,WebKitLoadEvent load_event,gpointer user_data)1587 load_changed_cb (WebKitWebView *web_view,
1588 WebKitLoadEvent load_event,
1589 gpointer user_data)
1590 {
1591 EphyWebView *view = EPHY_WEB_VIEW (web_view);
1592 GObject *object = G_OBJECT (web_view);
1593
1594 g_object_freeze_notify (object);
1595
1596 view->in_auth_dialog = 0;
1597
1598 /* Warning: the URI property may remain set to the URI of the
1599 * previously-loaded page until WEBKIT_LOAD_COMMITTED! During
1600 * WEBKIT_LOAD_STARTED, it may or may not match the URI being loaded.
1601 */
1602 switch (load_event) {
1603 case WEBKIT_LOAD_STARTED: {
1604 view->load_failed = FALSE;
1605 view->document_type = EPHY_WEB_VIEW_DOCUMENT_HTML;
1606
1607 g_clear_handle_id (&view->snapshot_timeout_id, g_source_remove);
1608
1609 if (view->address == NULL || view->address[0] == '\0') {
1610 /* We've probably never loaded any page before. */
1611 ephy_web_view_set_address (view, webkit_web_view_get_uri (web_view));
1612 }
1613
1614 ephy_web_view_set_loading_message (view, NULL);
1615
1616 break;
1617 }
1618 case WEBKIT_LOAD_REDIRECTED:
1619 break;
1620 case WEBKIT_LOAD_COMMITTED: {
1621 const char *uri;
1622 view->ever_committed = TRUE;
1623
1624 /* Title and location. */
1625 uri = webkit_web_view_get_uri (web_view);
1626 ephy_web_view_set_committed_location (view, uri);
1627 update_security_status_for_committed_load (view, uri);
1628 update_ucm_ads_state (web_view, uri);
1629
1630 /* History. */
1631 if (ephy_embed_utils_is_no_show_address (uri))
1632 ephy_web_view_freeze_history (view);
1633
1634 if (!ephy_web_view_is_history_frozen (view)) {
1635 char *history_uri = NULL;
1636
1637 /* TODO: move the normalization down to the history service? */
1638 if (g_str_has_prefix (uri, EPHY_ABOUT_SCHEME))
1639 history_uri = g_strdup_printf ("about:%s", uri + EPHY_ABOUT_SCHEME_LEN + 1);
1640 else if (g_str_has_prefix (uri, EPHY_PDF_SCHEME))
1641 history_uri = g_strdup (uri + strlen (EPHY_PDF_SCHEME) + 1);
1642 else
1643 history_uri = g_strdup (uri);
1644
1645 ephy_history_service_visit_url (view->history_service,
1646 history_uri,
1647 NULL,
1648 g_get_real_time (),
1649 view->visit_type,
1650 TRUE);
1651
1652 g_free (history_uri);
1653 }
1654
1655 if (view->loading_error_page)
1656 view->loading_error_page = FALSE;
1657 else
1658 view->error_page = EPHY_WEB_VIEW_ERROR_PAGE_NONE;
1659
1660 /* Zoom level. */
1661 restore_zoom_level (view, uri);
1662
1663 /* We have to reset the background color here because we set a
1664 * nonwhite background in constructed.
1665 */
1666 if (!g_str_has_prefix (uri, EPHY_ABOUT_SCHEME))
1667 reset_background_color (web_view);
1668
1669 break;
1670 }
1671 case WEBKIT_LOAD_FINISHED:
1672 ephy_web_view_unset_loading_message (view);
1673
1674 /* Ensure we load the icon for this web view, if available. */
1675 _ephy_web_view_update_icon (view);
1676
1677 if (g_str_has_prefix (webkit_web_view_get_uri (web_view), "ephy-pdf"))
1678 ephy_embed_shell_pdf_handler_stop (ephy_embed_shell_get_default (), web_view);
1679
1680 /* Reset visit type. */
1681 view->visit_type = EPHY_PAGE_VISIT_NONE;
1682
1683 if (view->entering_reader_mode) {
1684 view->entering_reader_mode = FALSE;
1685 g_object_notify_by_pspec (G_OBJECT (web_view), obj_properties[PROP_ENTERING_READER_MODE]);
1686 }
1687
1688 if (!ephy_web_view_is_history_frozen (view) &&
1689 ephy_embed_shell_get_mode (ephy_embed_shell_get_default ()) != EPHY_EMBED_SHELL_MODE_INCOGNITO) {
1690 /* FIXME: The 1s delay is a workaround to allow time to render the page and get a favicon.
1691 * https://bugzilla.gnome.org/show_bug.cgi?id=761065
1692 * https://bugs.webkit.org/show_bug.cgi?id=164180
1693 */
1694 if (view->snapshot_timeout_id == 0) {
1695 view->snapshot_timeout_id = g_timeout_add_seconds_full (G_PRIORITY_LOW, 1,
1696 (GSourceFunc)maybe_take_snapshot,
1697 web_view, NULL);
1698 g_free (view->pending_snapshot_uri);
1699 view->pending_snapshot_uri = g_strdup (webkit_web_view_get_uri (WEBKIT_WEB_VIEW (view)));
1700 }
1701 }
1702
1703 ephy_web_view_thaw_history (view);
1704
1705 g_clear_handle_id (&view->reader_js_timeout, g_source_remove);
1706
1707 if (!ephy_embed_utils_is_no_show_address (view->address))
1708 view->reader_js_timeout = g_idle_add (run_readability_js_if_needed, web_view);
1709
1710 break;
1711
1712 default:
1713 break;
1714 }
1715
1716 g_object_thaw_notify (object);
1717 }
1718
1719 /**
1720 * ephy_web_view_set_placeholder:
1721 * @view: an #EphyWebView
1722 * @uri: uri that will eventually be loaded
1723 * @title: last-known title of the page that will eventually be loaded
1724 *
1725 * Makes the #EphyWebView pretend a page that will eventually be loaded is
1726 * already there.
1727 *
1728 **/
1729 void
ephy_web_view_set_placeholder(EphyWebView * view,const char * uri,const char * title)1730 ephy_web_view_set_placeholder (EphyWebView *view,
1731 const char *uri,
1732 const char *title)
1733 {
1734 char *html;
1735
1736 g_assert (EPHY_IS_WEB_VIEW (view));
1737
1738 /* We want only the actual load to be the one recorded in history, but
1739 * doing a load here is the simplest way to replace the loading
1740 * spinner with the favicon. */
1741 ephy_web_view_freeze_history (view);
1742
1743 html = g_markup_printf_escaped ("<head><title>%s</title></head>", title);
1744
1745 webkit_web_view_load_alternate_html (WEBKIT_WEB_VIEW (view), html, uri, NULL);
1746
1747 g_free (html);
1748
1749 ephy_web_view_set_address (view, uri);
1750 }
1751
1752 static char *
get_style_sheet(void)1753 get_style_sheet (void)
1754 {
1755 GBytes *bytes;
1756 char *sheet;
1757
1758 bytes = g_resources_lookup_data (EPHY_PAGE_TEMPLATE_ERROR_CSS, 0, NULL);
1759 sheet = g_strdup (g_bytes_get_data (bytes, NULL));
1760 g_bytes_unref (bytes);
1761
1762 return sheet;
1763 }
1764
1765 static char *
detailed_message_from_tls_errors(GTlsCertificateFlags tls_errors)1766 detailed_message_from_tls_errors (GTlsCertificateFlags tls_errors)
1767 {
1768 GPtrArray *errors = g_ptr_array_new ();
1769 char *retval = NULL;
1770
1771 if (tls_errors & G_TLS_CERTIFICATE_BAD_IDENTITY) {
1772 /* Possible error message when a site presents a bad certificate. */
1773 g_ptr_array_add (errors, _("This website presented identification that belongs to a different website."));
1774 }
1775
1776 if (tls_errors & G_TLS_CERTIFICATE_EXPIRED) {
1777 /* Possible error message when a site presents a bad certificate. */
1778 g_ptr_array_add (errors, _("This website’s identification is too old to trust. Check the date on your computer’s calendar."));
1779 }
1780
1781 if (tls_errors & G_TLS_CERTIFICATE_UNKNOWN_CA) {
1782 /* Possible error message when a site presents a bad certificate. */
1783 g_ptr_array_add (errors, _("This website’s identification was not issued by a trusted organization."));
1784 }
1785
1786 if (tls_errors & G_TLS_CERTIFICATE_GENERIC_ERROR) {
1787 /* Possible error message when a site presents a bad certificate. */
1788 g_ptr_array_add (errors, _("This website’s identification could not be processed. It may be corrupted."));
1789 }
1790
1791 if (tls_errors & G_TLS_CERTIFICATE_REVOKED) {
1792 /* Possible error message when a site presents a bad certificate. */
1793 g_ptr_array_add (errors, _("This website’s identification has been revoked by the trusted organization that issued it."));
1794 }
1795
1796 if (tls_errors & G_TLS_CERTIFICATE_INSECURE) {
1797 /* Possible error message when a site presents a bad certificate. */
1798 g_ptr_array_add (errors, _("This website’s identification cannot be trusted because it uses very weak encryption."));
1799 }
1800
1801 if (tls_errors & G_TLS_CERTIFICATE_NOT_ACTIVATED) {
1802 /* Possible error message when a site presents a bad certificate. */
1803 g_ptr_array_add (errors, _("This website’s identification is only valid for future dates. Check the date on your computer’s calendar."));
1804 }
1805
1806 if (errors->len == 1) {
1807 retval = g_strdup (g_ptr_array_index (errors, 0));
1808 } else if (errors->len > 1) {
1809 GString *message = g_string_new ("<ul>");
1810 guint i;
1811
1812 for (i = 0; i < errors->len; i++) {
1813 g_string_append_printf (message, "<li>%s</li>", (char *)g_ptr_array_index (errors, i));
1814 }
1815
1816 g_string_append (message, "</ul>");
1817 retval = g_string_free (message, FALSE);
1818 } else {
1819 g_assert_not_reached ();
1820 }
1821
1822 g_ptr_array_free (errors, TRUE);
1823
1824 return retval;
1825 }
1826
1827 /**
1828 * ephy_web_view_get_error_page:
1829 * @view: an #EphyWebView
1830 *
1831 * Returns the error page currently displayed, or
1832 * %EPHY_WEB_VIEW_ERROR_PAGE_NONE.
1833 *
1834 **/
1835 EphyWebViewErrorPage
ephy_web_view_get_error_page(EphyWebView * view)1836 ephy_web_view_get_error_page (EphyWebView *view)
1837 {
1838 g_assert (EPHY_IS_WEB_VIEW (view));
1839
1840 return view->error_page;
1841 }
1842
1843 /* Note we go to some effort to avoid error-prone markup in translatable
1844 * strings. Everywhere, but also here on the error pages in particular. */
1845
1846 static void
format_network_error_page(const char * uri,const char * origin,const char * reason,char ** page_title,char ** message_title,char ** message_body,char ** message_details,char ** button_label,char ** button_action,const char ** button_accesskey,const char ** icon_name,const char ** style)1847 format_network_error_page (const char *uri,
1848 const char *origin,
1849 const char *reason,
1850 char **page_title,
1851 char **message_title,
1852 char **message_body,
1853 char **message_details,
1854 char **button_label,
1855 char **button_action,
1856 const char **button_accesskey,
1857 const char **icon_name,
1858 const char **style)
1859 {
1860 char *formatted_origin;
1861 char *formatted_reason;
1862 char *first_paragraph;
1863 const char *second_paragraph;
1864
1865 /* Page title when a site cannot be loaded due to a network error. */
1866 *page_title = g_strdup_printf (_("Problem Loading Page"));
1867
1868 /* Message title when a site cannot be loaded due to a network error. */
1869 *message_title = g_strdup (_("Unable to display this website"));
1870
1871 formatted_origin = g_strdup_printf ("<strong>%s</strong>", origin);
1872 /* Error details when a site cannot be loaded due to a network error. */
1873 first_paragraph = g_strdup_printf (_("The site at %s seems to be "
1874 "unavailable."),
1875 formatted_origin);
1876 /* Further error details when a site cannot be loaded due to a network error. */
1877 second_paragraph = _("It may be temporarily inaccessible or moved to a new "
1878 "address. You may wish to verify that your internet "
1879 "connection is working correctly.");
1880 *message_body = g_strdup_printf ("<p>%s</p><p>%s</p>",
1881 first_paragraph,
1882 second_paragraph);
1883
1884 formatted_reason = g_strdup_printf ("<i>%s</i>", reason);
1885 g_free (first_paragraph);
1886 /* Technical details when a site cannot be loaded due to a network error. */
1887 first_paragraph = g_strdup_printf (_("The precise error was: %s"),
1888 formatted_reason);
1889 *message_details = g_strdup_printf ("<p>%s</p>", first_paragraph);
1890
1891 /* The button on the network error page. DO NOT ADD MNEMONICS HERE. */
1892 *button_label = g_strdup (_("Reload"));
1893 *button_action = g_strdup_printf ("window.location = '%s';", uri);
1894 /* Mnemonic for the Reload button on browser error pages. */
1895 *button_accesskey = C_("reload-access-key", "R");
1896
1897 *icon_name = "network-error-symbolic.svg";
1898 *style = "default";
1899
1900 g_free (formatted_origin);
1901 g_free (formatted_reason);
1902 g_free (first_paragraph);
1903 }
1904
1905 static void
format_crash_error_page(const char * uri,char ** page_title,char ** message_title,char ** message_body,char ** button_label,char ** button_action,const char ** button_accesskey,const char ** icon_name,const char ** style)1906 format_crash_error_page (const char *uri,
1907 char **page_title,
1908 char **message_title,
1909 char **message_body,
1910 char **button_label,
1911 char **button_action,
1912 const char **button_accesskey,
1913 const char **icon_name,
1914 const char **style)
1915 {
1916 char *formatted_uri;
1917 char *formatted_distributor;
1918 char *first_paragraph;
1919 char *second_paragraph;
1920
1921 /* Page title when a site cannot be loaded due to a page crash error. */
1922 *page_title = g_strdup_printf (_("Problem Loading Page"));
1923
1924 /* Message title when a site cannot be loaded due to a page crash error. */
1925 *message_title = g_strdup (_("Oops! There may be a problem"));
1926
1927 formatted_uri = g_strdup_printf ("<strong>%s</strong>", uri);
1928 /* Error details when a site cannot be loaded due to a page crash error. */
1929 first_paragraph = g_strdup_printf (_("The page %s may have caused Web to "
1930 "close unexpectedly."),
1931 formatted_uri);
1932
1933 formatted_distributor = g_strdup_printf ("<strong>%s</strong>",
1934 DISTRIBUTOR_NAME);
1935 /* Further error details when a site cannot be loaded due to a page crash error. */
1936 second_paragraph = g_strdup_printf (_("If this happens again, please report "
1937 "the problem to the %s developers."),
1938 formatted_distributor);
1939
1940 *message_body = g_strdup_printf ("<p>%s</p><p>%s</p>",
1941 first_paragraph,
1942 second_paragraph);
1943
1944 /* The button on the page crash error page. DO NOT ADD MNEMONICS HERE. */
1945 *button_label = g_strdup (_("Reload"));
1946 *button_action = g_strdup_printf ("window.location = '%s';", uri);
1947 /* Mnemonic for the Reload button on browser error pages. */
1948 *button_accesskey = C_("reload-access-key", "R");
1949
1950 *icon_name = "computer-fail-symbolic.svg";
1951 *style = "default";
1952
1953 g_free (formatted_uri);
1954 g_free (formatted_distributor);
1955 g_free (first_paragraph);
1956 g_free (second_paragraph);
1957 }
1958
1959 static void
format_process_crash_error_page(const char * uri,char ** page_title,char ** message_title,char ** message_body,char ** button_label,char ** button_action,const char ** button_accesskey,const char ** icon_name,const char ** style)1960 format_process_crash_error_page (const char *uri,
1961 char **page_title,
1962 char **message_title,
1963 char **message_body,
1964 char **button_label,
1965 char **button_action,
1966 const char **button_accesskey,
1967 const char **icon_name,
1968 const char **style)
1969 {
1970 const char *first_paragraph;
1971
1972 /* Page title when a site cannot be loaded due to a process crash error. */
1973 *page_title = g_strdup_printf (_("Problem Displaying Page"));
1974
1975 /* Message title when a site cannot be loaded due to a process crash error. */
1976 *message_title = g_strdup (_("Oops!"));
1977
1978 /* Error details when a site cannot be loaded due to a process crash error. */
1979 first_paragraph = _("Something went wrong while displaying this page. Please reload or visit a different page to continue.");
1980 *message_body = g_strdup_printf ("<p>%s</p>",
1981 first_paragraph);
1982
1983 /* The button on the process crash error page. DO NOT ADD MNEMONICS HERE. */
1984 *button_label = g_strdup (_("Reload"));
1985 *button_action = g_strdup_printf ("window.location = '%s';", uri);
1986 /* Mnemonic for the Reload button on browser error pages. */
1987 *button_accesskey = C_("reload-access-key", "R");
1988
1989 *icon_name = "computer-fail-symbolic.svg";
1990 *style = "default";
1991 }
1992
1993 static void
format_unresponsive_process_error_page(const char * uri,char ** page_title,char ** message_title,char ** message_body,char ** button_label,char ** button_action,const char ** button_accesskey,const char ** icon_name,const char ** style)1994 format_unresponsive_process_error_page (const char *uri,
1995 char **page_title,
1996 char **message_title,
1997 char **message_body,
1998 char **button_label,
1999 char **button_action,
2000 const char **button_accesskey,
2001 const char **icon_name,
2002 const char **style)
2003 {
2004 const char *first_paragraph;
2005
2006 /* Page title when web content has become unresponsive. */
2007 *page_title = g_strdup_printf (_("Unresponsive Page"));
2008
2009 /* Message title when web content has become unresponsive. */
2010 *message_title = g_strdup (_("Uh-oh!"));
2011
2012 /* Error details when web content has become unresponsive. */
2013 first_paragraph = _("This page has been unresponsive for too long. Please reload or visit a different page to continue.");
2014 *message_body = g_strdup_printf ("<p>%s</p>",
2015 first_paragraph);
2016
2017 /* The button on the unresponsive process error page. DO NOT ADD MNEMONICS HERE. */
2018 *button_label = g_strdup (_("Reload"));
2019 *button_action = g_strdup_printf ("window.location = '%s';", uri);
2020 /* Mnemonic for the Reload button on browser error pages. */
2021 *button_accesskey = C_("reload-access-key", "R");
2022
2023 *icon_name = "computer-fail-symbolic.svg";
2024 *style = "default";
2025 }
2026
2027 static void
format_tls_error_page(EphyWebView * view,const char * origin,char ** page_title,char ** message_title,char ** message_body,char ** message_details,char ** button_label,char ** button_action,const char ** button_accesskey,char ** hidden_button_label,char ** hidden_button_action,const char ** hidden_button_accesskey,const char ** icon_name,const char ** style)2028 format_tls_error_page (EphyWebView *view,
2029 const char *origin,
2030 char **page_title,
2031 char **message_title,
2032 char **message_body,
2033 char **message_details,
2034 char **button_label,
2035 char **button_action,
2036 const char **button_accesskey,
2037 char **hidden_button_label,
2038 char **hidden_button_action,
2039 const char **hidden_button_accesskey,
2040 const char **icon_name,
2041 const char **style)
2042 {
2043 char *formatted_origin;
2044 char *first_paragraph;
2045
2046 /* Page title when a site is not loaded due to an invalid TLS certificate. */
2047 *page_title = g_strdup_printf (_("Security Violation"));
2048
2049 /* Message title when a site is not loaded due to an invalid TLS certificate. */
2050 *message_title = g_strdup (_("This Connection is Not Secure"));
2051
2052 formatted_origin = g_strdup_printf ("<strong>%s</strong>", origin);
2053 /* Error details when a site is not loaded due to an invalid TLS certificate. */
2054 first_paragraph = g_strdup_printf (_("This does not look like the real %s. "
2055 "Attackers might be trying to steal or "
2056 "alter information going to or from "
2057 "this site."),
2058 formatted_origin);
2059
2060 *message_body = g_strdup_printf ("<p>%s</p>", first_paragraph);
2061 *message_details = detailed_message_from_tls_errors (view->tls_errors);
2062
2063 /* The button on the invalid TLS certificate error page. DO NOT ADD MNEMONICS HERE. */
2064 *button_label = g_strdup (_("Go Back"));
2065 *button_action = g_strdup ("window.history.back();");
2066 /* Mnemonic for the Go Back button on the invalid TLS certificate error page. */
2067 *button_accesskey = C_("back-access-key", "B");
2068
2069 /* The hidden button on the invalid TLS certificate error page. Do not add mnemonics here. */
2070 *hidden_button_label = g_strdup (_("Accept Risk and Proceed"));
2071 *hidden_button_action = g_strdup_printf ("window.webkit.messageHandlers.tlsErrorPage.postMessage(%"G_GUINT64_FORMAT ");",
2072 webkit_web_view_get_page_id (WEBKIT_WEB_VIEW (view)));
2073 /* Mnemonic for the Accept Risk and Proceed button on the invalid TLS certificate error page. */
2074 *hidden_button_accesskey = C_("proceed-anyway-access-key", "P");
2075
2076 *icon_name = "channel-insecure-symbolic.svg";
2077 *style = "danger";
2078
2079 g_free (formatted_origin);
2080 g_free (first_paragraph);
2081 }
2082
2083 static void
format_unsafe_browsing_error_page(EphyWebView * view,const char * origin,const char * threat_type,char ** page_title,char ** message_title,char ** message_body,char ** message_details,char ** button_label,char ** button_action,const char ** button_accesskey,char ** hidden_button_label,char ** hidden_button_action,const char ** hidden_button_accesskey,const char ** icon_name,const char ** style)2084 format_unsafe_browsing_error_page (EphyWebView *view,
2085 const char *origin,
2086 const char *threat_type,
2087 char **page_title,
2088 char **message_title,
2089 char **message_body,
2090 char **message_details,
2091 char **button_label,
2092 char **button_action,
2093 const char **button_accesskey,
2094 char **hidden_button_label,
2095 char **hidden_button_action,
2096 const char **hidden_button_accesskey,
2097 const char **icon_name,
2098 const char **style)
2099 {
2100 char *formatted_origin;
2101 char *first_paragraph;
2102
2103 /* Page title when a site is flagged by Google Safe Browsing verification. */
2104 *page_title = g_strdup_printf (_("Security Warning"));
2105
2106 /* Message title on the unsafe browsing error page. */
2107 *message_title = g_strdup (_("Unsafe website detected!"));
2108
2109 formatted_origin = g_strdup_printf ("<strong>%s</strong>", origin);
2110 /* Error details on the unsafe browsing error page.
2111 * https://developers.google.com/safe-browsing/v4/usage-limits#UserWarnings
2112 */
2113 if (!g_strcmp0 (threat_type, GSB_THREAT_TYPE_MALWARE)) {
2114 first_paragraph = g_strdup_printf (_("Visiting %s may harm your computer. This "
2115 "page appears to contain malicious code that could "
2116 "be downloaded to your computer without your consent."),
2117 formatted_origin);
2118 *message_details = g_strdup_printf (_("You can learn more about harmful web content "
2119 "including viruses and other malicious code "
2120 "and how to protect your computer at %s."),
2121 "<a href=\"https://www.stopbadware.org/\">"
2122 "www.stopbadware.org"
2123 "</a>");
2124 } else if (!g_strcmp0 (threat_type, GSB_THREAT_TYPE_SOCIAL_ENGINEERING)) {
2125 first_paragraph = g_strdup_printf (_("Attackers on %s may trick you into doing "
2126 "something dangerous like installing software or "
2127 "revealing your personal information (for example, "
2128 "passwords, phone numbers, or credit cards)."),
2129 formatted_origin);
2130 *message_details = g_strdup_printf (_("You can find out more about social engineering "
2131 "(phishing) at %s or from %s."),
2132 "<a href=\"https://support.google.com/webmasters/answer/6350487\">"
2133 "Social Engineering (Phishing and Deceptive Sites)"
2134 "</a>",
2135 "<a href=\"https://www.antiphishing.org/\">"
2136 "www.antiphishing.org"
2137 "</a>");
2138 } else {
2139 first_paragraph = g_strdup_printf (_("%s may contain harmful programs. Attackers might "
2140 "attempt to trick you into installing programs that "
2141 "harm your browsing experience (for example, by changing "
2142 "your homepage or showing extra ads on sites you visit)."),
2143 formatted_origin);
2144 *message_details = g_strdup_printf (_("You can learn more about unwanted software at %s."),
2145 "<a href=\"https://www.google.com/about/unwanted-software-policy.html\">"
2146 "Unwanted Software Policy"
2147 "</a>");
2148 }
2149
2150 *message_body = g_strdup_printf ("<p>%s</p>", first_paragraph);
2151
2152 /* The button on unsafe browsing error page. DO NOT ADD MNEMONICS HERE. */
2153 *button_label = g_strdup (_("Go Back"));
2154 *button_action = g_strdup ("window.history.back();");
2155 /* Mnemonic for the Go Back button on the unsafe browsing error page. */
2156 *button_accesskey = C_("back-access-key", "B");
2157
2158 /* The hidden button on the unsafe browsing error page. Do not add mnemonics here. */
2159 *hidden_button_label = g_strdup (_("Accept Risk and Proceed"));
2160 *hidden_button_action = g_strdup_printf ("window.webkit.messageHandlers.unsafeBrowsingErrorPage.postMessage(%"G_GUINT64_FORMAT ");",
2161 webkit_web_view_get_page_id (WEBKIT_WEB_VIEW (view)));
2162 /* Mnemonic for the Accept Risk and Proceed button on the unsafe browsing error page. */
2163 *hidden_button_accesskey = C_("proceed-anyway-access-key", "P");
2164
2165 *icon_name = "security-high-symbolic.svg";
2166 *style = "danger";
2167
2168 g_free (formatted_origin);
2169 g_free (first_paragraph);
2170 }
2171
2172 static void
format_no_such_file_error_page(EphyWebView * view,char ** page_title,char ** message_title,char ** message_body,char ** button_label,char ** button_action,const char ** button_accesskey,const char ** icon_name,const char ** style)2173 format_no_such_file_error_page (EphyWebView *view,
2174 char **page_title,
2175 char **message_title,
2176 char **message_body,
2177 char **button_label,
2178 char **button_action,
2179 const char **button_accesskey,
2180 const char **icon_name,
2181 const char **style)
2182 {
2183 g_autofree gchar *formatted_origin = NULL;
2184 g_autofree gchar *first_paragraph = NULL;
2185 g_autofree gchar *second_paragraph = NULL;
2186
2187 /* Page title on no such file error page */
2188 *page_title = g_strdup_printf (_("File not found"));
2189
2190 /* Message title on the no such file error page. */
2191 *message_title = g_strdup (_("File not found"));
2192
2193 formatted_origin = g_strdup_printf ("<strong>%s</strong>", view->address);
2194
2195 first_paragraph = g_strdup_printf (_("%s could not be found."),
2196 formatted_origin);
2197 second_paragraph = g_strdup_printf (_("Please check the file name for "
2198 "capitalization or other typing errors. Also check if "
2199 "it has been moved, renamed, or deleted."));
2200
2201 *message_body = g_strdup_printf ("<p>%s</p><p>%s</p>", first_paragraph, second_paragraph);
2202
2203 /* The button on no such file error page. DO NOT ADD MNEMONICS HERE. */
2204 *button_label = g_strdup (_("Go Back"));
2205 *button_action = g_strdup ("window.history.back();");
2206 /* Mnemonic for the Go Back button on the no such file error page. */
2207 *button_accesskey = C_("back-access-key", "B");
2208
2209 *icon_name = "computer-fail-symbolic.svg";
2210 *style = "default";
2211 }
2212
2213 /**
2214 * ephy_web_view_load_error_page:
2215 * @view: an #EphyWebView
2216 * @uri: uri that caused the failure
2217 * @page: one of #EphyWebViewErrorPage
2218 * @error: a GError to inspect, or %NULL
2219 * @user_data: a pointer to additional data
2220 *
2221 * Loads an error page appropiate for @page in @view.
2222 *
2223 **/
2224 void
ephy_web_view_load_error_page(EphyWebView * view,const char * uri,EphyWebViewErrorPage page,GError * error,gpointer user_data)2225 ephy_web_view_load_error_page (EphyWebView *view,
2226 const char *uri,
2227 EphyWebViewErrorPage page,
2228 GError *error,
2229 gpointer user_data)
2230 {
2231 GBytes *html_file;
2232 GString *html = g_string_new ("");
2233 char *origin = NULL;
2234 char *lang = NULL;
2235 char *page_title = NULL;
2236 char *msg_title = NULL;
2237 char *msg_body = NULL;
2238 char *msg_details = NULL;
2239 char *button_label = NULL;
2240 char *hidden_button_label = NULL;
2241 char *button_action = NULL;
2242 char *hidden_button_action = NULL;
2243 char *style_sheet = NULL;
2244 const char *button_accesskey = NULL;
2245 const char *hidden_button_accesskey = NULL;
2246 const char *icon_name = NULL;
2247 const char *style = NULL;
2248 const char *reason = NULL;
2249
2250 g_assert (page != EPHY_WEB_VIEW_ERROR_PAGE_NONE);
2251
2252 view->loading_error_page = TRUE;
2253 view->error_page = page;
2254
2255 if (page == EPHY_WEB_VIEW_ERROR_INVALID_TLS_CERTIFICATE)
2256 ephy_web_view_set_security_level (view, EPHY_SECURITY_LEVEL_UNACCEPTABLE_CERTIFICATE);
2257 else
2258 ephy_web_view_set_security_level (view, EPHY_SECURITY_LEVEL_LOCAL_PAGE);
2259
2260 reason = error ? error->message : _("None specified");
2261
2262 origin = ephy_uri_to_security_origin (uri);
2263 if (origin == NULL)
2264 origin = g_strdup (uri);
2265
2266 lang = g_strdup (pango_language_to_string (gtk_get_default_language ()));
2267 g_strdelimit (lang, "_-@", '\0');
2268
2269 html_file = g_resources_lookup_data (EPHY_PAGE_TEMPLATE_ERROR, 0, NULL);
2270
2271 switch (page) {
2272 case EPHY_WEB_VIEW_ERROR_PAGE_NETWORK_ERROR:
2273 format_network_error_page (uri,
2274 origin,
2275 reason,
2276 &page_title,
2277 &msg_title,
2278 &msg_body,
2279 &msg_details,
2280 &button_label,
2281 &button_action,
2282 &button_accesskey,
2283 &icon_name,
2284 &style);
2285 break;
2286 case EPHY_WEB_VIEW_ERROR_PAGE_CRASH:
2287 format_crash_error_page (uri,
2288 &page_title,
2289 &msg_title,
2290 &msg_body,
2291 &button_label,
2292 &button_action,
2293 &button_accesskey,
2294 &icon_name,
2295 &style);
2296 break;
2297 case EPHY_WEB_VIEW_ERROR_PROCESS_CRASH:
2298 format_process_crash_error_page (uri,
2299 &page_title,
2300 &msg_title,
2301 &msg_body,
2302 &button_label,
2303 &button_action,
2304 &button_accesskey,
2305 &icon_name,
2306 &style);
2307 break;
2308 case EPHY_WEB_VIEW_ERROR_UNRESPONSIVE_PROCESS:
2309 format_unresponsive_process_error_page (uri,
2310 &page_title,
2311 &msg_title,
2312 &msg_body,
2313 &button_label,
2314 &button_action,
2315 &button_accesskey,
2316 &icon_name,
2317 &style);
2318 break;
2319 case EPHY_WEB_VIEW_ERROR_INVALID_TLS_CERTIFICATE:
2320 format_tls_error_page (view,
2321 origin,
2322 &page_title,
2323 &msg_title,
2324 &msg_body,
2325 &msg_details,
2326 &button_label,
2327 &button_action,
2328 &button_accesskey,
2329 &hidden_button_label,
2330 &hidden_button_action,
2331 &hidden_button_accesskey,
2332 &icon_name,
2333 &style);
2334 break;
2335 case EPHY_WEB_VIEW_ERROR_UNSAFE_BROWSING:
2336 format_unsafe_browsing_error_page (view,
2337 origin,
2338 user_data,
2339 &page_title,
2340 &msg_title,
2341 &msg_body,
2342 &msg_details,
2343 &button_label,
2344 &button_action,
2345 &button_accesskey,
2346 &hidden_button_label,
2347 &hidden_button_action,
2348 &hidden_button_accesskey,
2349 &icon_name,
2350 &style);
2351 break;
2352 case EPHY_WEB_VIEW_ERROR_NO_SUCH_FILE:
2353 format_no_such_file_error_page (view,
2354 &page_title,
2355 &msg_title,
2356 &msg_body,
2357 &button_label,
2358 &button_action,
2359 &button_accesskey,
2360 &icon_name,
2361 &style);
2362 break;
2363
2364 case EPHY_WEB_VIEW_ERROR_PAGE_NONE:
2365 default:
2366 g_assert_not_reached ();
2367 }
2368
2369 _ephy_web_view_update_icon (view);
2370
2371 style_sheet = get_style_sheet ();
2372
2373 #pragma GCC diagnostic push
2374 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
2375 /* The HTML file is trusted input. */
2376 g_string_printf (html,
2377 g_bytes_get_data (html_file, NULL),
2378 lang, lang,
2379 ((gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL) ? "rtl" : "ltr"),
2380 page_title,
2381 style_sheet,
2382 button_action, hidden_button_action,
2383 icon_name,
2384 style,
2385 msg_title, msg_body,
2386 msg_details ? "visible" : "hidden",
2387 _("Technical information"),
2388 msg_details,
2389 hidden_button_label ? "visible" : "hidden",
2390 hidden_button_accesskey, hidden_button_label,
2391 button_accesskey, button_label);
2392 #pragma GCC diagnostic pop
2393
2394 g_bytes_unref (html_file);
2395 g_free (origin);
2396 g_free (lang);
2397 g_free (page_title);
2398 g_free (msg_title);
2399 g_free (msg_body);
2400 g_free (msg_details);
2401 g_free (button_label);
2402 g_free (button_action);
2403 g_free (hidden_button_label);
2404 g_free (hidden_button_action);
2405 g_free (style_sheet);
2406
2407 /* Make our history backend ignore the next page load, since it will be an error page. */
2408 ephy_web_view_freeze_history (view);
2409 webkit_web_view_load_alternate_html (WEBKIT_WEB_VIEW (view), html->str, uri, 0);
2410 g_string_free (html, TRUE);
2411 }
2412
2413 static gboolean
load_failed_cb(WebKitWebView * web_view,WebKitLoadEvent load_event,const char * uri,GError * error,gpointer user_data)2414 load_failed_cb (WebKitWebView *web_view,
2415 WebKitLoadEvent load_event,
2416 const char *uri,
2417 GError *error,
2418 gpointer user_data)
2419 {
2420 EphyWebView *view = EPHY_WEB_VIEW (web_view);
2421
2422 view->load_failed = TRUE;
2423 ephy_web_view_set_link_message (view, NULL);
2424
2425 if (error->domain != WEBKIT_NETWORK_ERROR &&
2426 error->domain != WEBKIT_POLICY_ERROR &&
2427 error->domain != WEBKIT_PLUGIN_ERROR) {
2428 if (view->address && g_str_has_prefix (view->address, "file:"))
2429 ephy_web_view_load_error_page (view, uri, EPHY_WEB_VIEW_ERROR_NO_SUCH_FILE, error, NULL);
2430 else
2431 ephy_web_view_load_error_page (view, uri, EPHY_WEB_VIEW_ERROR_PAGE_NETWORK_ERROR, error, NULL);
2432 return TRUE;
2433 }
2434
2435 switch (error->code) {
2436 case WEBKIT_NETWORK_ERROR_FAILED:
2437 case WEBKIT_NETWORK_ERROR_TRANSPORT:
2438 case WEBKIT_NETWORK_ERROR_UNKNOWN_PROTOCOL:
2439 case WEBKIT_NETWORK_ERROR_FILE_DOES_NOT_EXIST:
2440 case WEBKIT_POLICY_ERROR_FAILED:
2441 case WEBKIT_POLICY_ERROR_CANNOT_SHOW_MIME_TYPE:
2442 case WEBKIT_POLICY_ERROR_CANNOT_SHOW_URI:
2443 case WEBKIT_POLICY_ERROR_CANNOT_USE_RESTRICTED_PORT:
2444 case WEBKIT_PLUGIN_ERROR_FAILED:
2445 case WEBKIT_PLUGIN_ERROR_CANNOT_FIND_PLUGIN:
2446 case WEBKIT_PLUGIN_ERROR_CANNOT_LOAD_PLUGIN:
2447 case WEBKIT_PLUGIN_ERROR_JAVA_UNAVAILABLE:
2448 case WEBKIT_PLUGIN_ERROR_CONNECTION_CANCELLED:
2449 ephy_web_view_load_error_page (view, uri, EPHY_WEB_VIEW_ERROR_PAGE_NETWORK_ERROR, error, NULL);
2450 return TRUE;
2451 case WEBKIT_NETWORK_ERROR_CANCELLED: {
2452 if (!view->typed_address) {
2453 const char *prev_uri;
2454
2455 prev_uri = webkit_web_view_get_uri (web_view);
2456 ephy_web_view_set_address (view, prev_uri);
2457 }
2458 }
2459 break;
2460 case WEBKIT_POLICY_ERROR_FRAME_LOAD_INTERRUPTED_BY_POLICY_CHANGE:
2461 /* If we are going to download something, and this is the first
2462 * page to load in this tab, we may want to close it down. */
2463 if (!view->ever_committed)
2464 g_signal_emit_by_name (view, "download-only-load", NULL);
2465 break;
2466 /* In case the resource is going to be showed with a plugin just let
2467 * WebKit do it */
2468 case WEBKIT_PLUGIN_ERROR_WILL_HANDLE_LOAD:
2469 default:
2470 break;
2471 }
2472
2473 return FALSE;
2474 }
2475
2476 static gboolean
load_failed_with_tls_error_cb(WebKitWebView * web_view,const char * uri,GTlsCertificate * certificate,GTlsCertificateFlags errors,gpointer user_data)2477 load_failed_with_tls_error_cb (WebKitWebView *web_view,
2478 const char *uri,
2479 GTlsCertificate *certificate,
2480 GTlsCertificateFlags errors,
2481 gpointer user_data)
2482 {
2483 EphyWebView *view = EPHY_WEB_VIEW (web_view);
2484
2485 g_clear_object (&view->certificate);
2486 g_clear_pointer (&view->tls_error_failing_uri, g_free);
2487
2488 view->certificate = g_object_ref (certificate);
2489 view->tls_errors = errors;
2490 view->tls_error_failing_uri = g_strdup (uri);
2491 ephy_web_view_load_error_page (EPHY_WEB_VIEW (web_view), uri,
2492 EPHY_WEB_VIEW_ERROR_INVALID_TLS_CERTIFICATE, NULL, NULL);
2493
2494 return TRUE;
2495 }
2496
2497 static void
mixed_content_detected_cb(WebKitWebView * web_view,WebKitInsecureContentEvent event,gpointer user_data)2498 mixed_content_detected_cb (WebKitWebView *web_view,
2499 WebKitInsecureContentEvent event,
2500 gpointer user_data)
2501 {
2502 EphyWebView *view = EPHY_WEB_VIEW (web_view);
2503
2504 if (view->security_level != EPHY_SECURITY_LEVEL_UNACCEPTABLE_CERTIFICATE)
2505 ephy_web_view_set_security_level (view, EPHY_SECURITY_LEVEL_MIXED_CONTENT);
2506 }
2507
2508 static void
close_web_view_cb(WebKitWebView * web_view,gpointer user_data)2509 close_web_view_cb (WebKitWebView *web_view,
2510 gpointer user_data)
2511
2512 {
2513 GtkWidget *widget = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
2514
2515 LOG ("close web view");
2516
2517 if (EPHY_IS_EMBED_CONTAINER (widget))
2518 ephy_embed_container_remove_child (EPHY_EMBED_CONTAINER (widget),
2519 EPHY_GET_EMBED_FROM_EPHY_WEB_VIEW (web_view));
2520 else
2521 gtk_widget_destroy (widget);
2522 }
2523
2524
2525 static void
zoom_changed_cb(WebKitWebView * web_view,GParamSpec * pspec,gpointer user_data)2526 zoom_changed_cb (WebKitWebView *web_view,
2527 GParamSpec *pspec,
2528 gpointer user_data)
2529 {
2530 const char *address;
2531 double zoom;
2532
2533 zoom = webkit_web_view_get_zoom_level (web_view);
2534
2535 if (EPHY_WEB_VIEW (web_view)->is_setting_zoom)
2536 return;
2537
2538 address = ephy_web_view_get_address (EPHY_WEB_VIEW (web_view));
2539 if (ephy_embed_utils_address_has_web_scheme (address)) {
2540 ephy_history_service_set_url_zoom_level (EPHY_WEB_VIEW (web_view)->history_service,
2541 address, zoom,
2542 NULL, NULL, NULL);
2543 }
2544 }
2545
2546 static gboolean
script_dialog_cb(WebKitWebView * web_view,WebKitScriptDialog * dialog)2547 script_dialog_cb (WebKitWebView *web_view,
2548 WebKitScriptDialog *dialog)
2549 {
2550 if (webkit_script_dialog_get_dialog_type (dialog) != WEBKIT_SCRIPT_DIALOG_BEFORE_UNLOAD_CONFIRM)
2551 return FALSE;
2552
2553 /* Ignore beforeunload events for now until we properly support webkit_web_view_try_close()
2554 * See https://bugzilla.gnome.org/show_bug.cgi?id=722032.
2555 */
2556 webkit_script_dialog_confirm_set_confirmed (dialog, TRUE);
2557
2558 return TRUE;
2559 }
2560
2561 static const char *
enum_nick(GType enum_type,int value)2562 enum_nick (GType enum_type,
2563 int value)
2564 {
2565 GEnumClass *enum_class;
2566 const GEnumValue *enum_value;
2567 const char *nick = NULL;
2568
2569 enum_class = g_type_class_ref (enum_type);
2570 enum_value = g_enum_get_value (enum_class, value);
2571 if (enum_value)
2572 nick = enum_value->value_nick;
2573
2574 g_type_class_unref (enum_class);
2575 return nick;
2576 }
2577
2578 static void
reader_setting_changed_cb(GSettings * settings,gchar * key,EphyWebView * web_view)2579 reader_setting_changed_cb (GSettings *settings,
2580 gchar *key,
2581 EphyWebView *web_view)
2582 {
2583 const gchar *font_style;
2584 const gchar *color_scheme;
2585 gchar *js_snippet;
2586
2587 if (!g_str_has_prefix (web_view->address, EPHY_READER_SCHEME))
2588 return;
2589
2590 font_style = enum_nick (EPHY_TYPE_PREFS_READER_FONT_STYLE,
2591 g_settings_get_enum (settings,
2592 EPHY_PREFS_READER_FONT_STYLE));
2593 color_scheme = enum_nick (EPHY_TYPE_PREFS_READER_COLOR_SCHEME,
2594 g_settings_get_enum (settings,
2595 EPHY_PREFS_READER_COLOR_SCHEME));
2596
2597 js_snippet = g_strdup_printf ("document.body.className = '%s %s'",
2598 font_style,
2599 color_scheme);
2600 webkit_web_view_run_javascript_in_world (WEBKIT_WEB_VIEW (web_view),
2601 js_snippet,
2602 ephy_embed_shell_get_guid (ephy_embed_shell_get_default ()),
2603 NULL,
2604 NULL,
2605 NULL);
2606 g_free (js_snippet);
2607 }
2608
2609 typedef struct {
2610 EphyWebView *web_view;
2611 WebKitAuthenticationRequest *request;
2612 } AuthenticationData;
2613
2614 static AuthenticationData *
authentication_data_new(EphyWebView * web_view,WebKitAuthenticationRequest * request)2615 authentication_data_new (EphyWebView *web_view,
2616 WebKitAuthenticationRequest *request)
2617 {
2618 AuthenticationData *data;
2619
2620 data = g_new (AuthenticationData, 1);
2621 data->web_view = g_object_ref (web_view);
2622 data->request = g_object_ref (request);
2623
2624 return data;
2625 }
2626
2627 static void
authentication_data_free(AuthenticationData * data)2628 authentication_data_free (AuthenticationData *data)
2629 {
2630 g_object_unref (data->web_view);
2631 g_object_unref (data->request);
2632 g_free (data);
2633 }
2634
2635 static void
auth_password_query_finished_cb(GList * records,AuthenticationData * data)2636 auth_password_query_finished_cb (GList *records,
2637 AuthenticationData *data)
2638 {
2639 EphyPasswordRecord *record;
2640 g_autoptr (WebKitCredential) credential = NULL;
2641
2642 record = records && records->data ? EPHY_PASSWORD_RECORD (records->data) : NULL;
2643 if (record) {
2644 credential = webkit_credential_new (ephy_password_record_get_username (record),
2645 ephy_password_record_get_password (record),
2646 WEBKIT_CREDENTIAL_PERSISTENCE_NONE);
2647 } else {
2648 /* Provide a non-empty wrong credential to force a retry */
2649 credential = webkit_credential_new (" ", "", WEBKIT_CREDENTIAL_PERSISTENCE_NONE);
2650 }
2651
2652 webkit_authentication_request_authenticate (data->request, credential);
2653 authentication_data_free (data);
2654 }
2655
2656 static void
authenticate_succeeded_cb(WebKitAuthenticationRequest * request,WebKitCredential * credential)2657 authenticate_succeeded_cb (WebKitAuthenticationRequest *request,
2658 WebKitCredential *credential)
2659 {
2660 EphyPasswordManager *password_manager;
2661 g_autoptr (WebKitSecurityOrigin) security_origin = NULL;
2662 g_autofree char *origin = NULL;
2663
2664 if (webkit_credential_get_persistence (credential) != WEBKIT_CREDENTIAL_PERSISTENCE_PERMANENT)
2665 return;
2666
2667 security_origin = webkit_authentication_request_get_security_origin (request);
2668 origin = webkit_security_origin_to_string (security_origin);
2669 password_manager = ephy_embed_shell_get_password_manager (ephy_embed_shell_get_default ());
2670 ephy_password_manager_save (password_manager,
2671 origin,
2672 origin,
2673 webkit_credential_get_username (credential),
2674 webkit_credential_get_password (credential),
2675 "org.gnome.Epiphany.HTTPAuthCredentials.Username",
2676 "org.gnome.Epiphany.HTTPAuthCredentials.Password",
2677 TRUE);
2678 }
2679
2680 static gboolean
authenticate_cb(WebKitWebView * web_view,WebKitAuthenticationRequest * request,gpointer user_data)2681 authenticate_cb (WebKitWebView *web_view,
2682 WebKitAuthenticationRequest *request,
2683 gpointer user_data)
2684 {
2685 EphyWebView *ephy_web_view = EPHY_WEB_VIEW (web_view);
2686 EphyPasswordManager *password_manager;
2687 AuthenticationData *data;
2688 g_autoptr (WebKitSecurityOrigin) security_origin = NULL;
2689 g_autofree char *origin = NULL;
2690
2691 if (webkit_authentication_request_is_retry (request)) {
2692 webkit_authentication_request_set_can_save_credentials (request, TRUE);
2693 g_signal_connect_object (request, "authenticated",
2694 G_CALLBACK (authenticate_succeeded_cb),
2695 ephy_web_view, 0);
2696 ephy_web_view->in_auth_dialog = 1;
2697 return FALSE;
2698 }
2699
2700 data = authentication_data_new (ephy_web_view, request);
2701 security_origin = webkit_authentication_request_get_security_origin (request);
2702 origin = webkit_security_origin_to_string (security_origin);
2703 password_manager = ephy_embed_shell_get_password_manager (ephy_embed_shell_get_default ());
2704 ephy_password_manager_query (password_manager,
2705 NULL,
2706 origin,
2707 origin,
2708 NULL,
2709 "org.gnome.Epiphany.HTTPAuthCredentials.Username",
2710 "org.gnome.Epiphany.HTTPAuthCredentials.Password",
2711 (EphyPasswordManagerQueryCallback)auth_password_query_finished_cb,
2712 data);
2713 return TRUE;
2714 }
2715
2716 typedef struct {
2717 WebKitWebView *web_view;
2718 char *origin;
2719 WebKitUserMessage *message;
2720 } PasswordManagerData;
2721
2722 static void
password_manager_data_free(PasswordManagerData * data)2723 password_manager_data_free (PasswordManagerData *data)
2724 {
2725 g_object_unref (data->web_view);
2726 g_object_unref (data->message);
2727 g_free (data);
2728 }
2729
2730 static void
password_manager_query_finished_cb(GList * records,PasswordManagerData * data)2731 password_manager_query_finished_cb (GList *records,
2732 PasswordManagerData *data)
2733 {
2734 EphyPasswordRecord *record;
2735 const char *origin;
2736 const char *username = NULL;
2737 const char *password = NULL;
2738 g_autofree char *real_origin = NULL;
2739
2740 record = records && records->data ? EPHY_PASSWORD_RECORD (records->data) : NULL;
2741 if (record) {
2742 username = ephy_password_record_get_username (record);
2743 password = ephy_password_record_get_password (record);
2744 }
2745
2746 g_variant_get (webkit_user_message_get_parameters (data->message), "(&s@sm@sm@s@s)", &origin, NULL, NULL, NULL, NULL);
2747 real_origin = ephy_uri_to_security_origin (webkit_web_view_get_uri (data->web_view));
2748 if (g_strcmp0 (real_origin, origin) != 0) {
2749 g_debug ("Extension's origin '%s' doesn't match real origin '%s'", origin, real_origin);
2750 password_manager_data_free (data);
2751 return;
2752 }
2753
2754 webkit_user_message_send_reply (data->message,
2755 webkit_user_message_new ("PasswordManager.QueryPasswordResponse",
2756 g_variant_new ("(msms)", username, password)));
2757 password_manager_data_free (data);
2758 }
2759
2760 static gboolean
password_manager_handle_query_usernames_message(WebKitWebView * web_view,WebKitUserMessage * message)2761 password_manager_handle_query_usernames_message (WebKitWebView *web_view,
2762 WebKitUserMessage *message)
2763 {
2764 GVariant *parameters;
2765 const char *origin;
2766 EphyPasswordManager *password_manager;
2767 GList *usernames, *l;
2768 GVariantBuilder builder;
2769 g_autofree char *real_origin = NULL;
2770
2771 parameters = webkit_user_message_get_parameters (message);
2772 if (!parameters)
2773 return FALSE;
2774
2775 g_variant_get (parameters, "&s", &origin);
2776 real_origin = ephy_uri_to_security_origin (webkit_web_view_get_uri (web_view));
2777 if (g_strcmp0 (real_origin, origin) != 0) {
2778 g_debug ("Extension's origin '%s' doesn't match real origin '%s'", origin, real_origin);
2779 return FALSE;
2780 }
2781
2782 password_manager = ephy_embed_shell_get_password_manager (ephy_embed_shell_get_default ());
2783 usernames = ephy_password_manager_get_usernames_for_origin (password_manager, origin);
2784
2785 g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY);
2786 for (l = usernames; l != NULL; l = g_list_next (l))
2787 g_variant_builder_add (&builder, "s", l->data);
2788
2789 webkit_user_message_send_reply (message, webkit_user_message_new ("PasswordManager.QueryUsernamesResponse",
2790 g_variant_builder_end (&builder)));
2791 return TRUE;
2792 }
2793
2794 static gboolean
password_manager_handle_query_password_message(WebKitWebView * web_view,WebKitUserMessage * message)2795 password_manager_handle_query_password_message (WebKitWebView *web_view,
2796 WebKitUserMessage *message)
2797 {
2798 GVariant *parameters;
2799 const char *origin;
2800 const char *target_origin;
2801 const char *username;
2802 const char *username_field;
2803 const char *password_field;
2804 EphyPasswordManager *password_manager;
2805 PasswordManagerData *data;
2806
2807 parameters = webkit_user_message_get_parameters (message);
2808 if (!parameters)
2809 return FALSE;
2810
2811 g_variant_get (parameters, "(&s&sm&sm&s&s)", &origin, &target_origin, &username, &username_field, &password_field);
2812
2813 /* Don't include username_field in queries unless we actually have a username
2814 * to go along with it, or the query will fail because we don't save
2815 * username_field without a corresponding username.
2816 */
2817 if (!username && username_field)
2818 username_field = NULL;
2819
2820 data = g_new (PasswordManagerData, 1);
2821 data->web_view = g_object_ref (web_view);
2822 data->message = g_object_ref (message);
2823
2824 password_manager = ephy_embed_shell_get_password_manager (ephy_embed_shell_get_default ());
2825 ephy_password_manager_query (password_manager,
2826 NULL,
2827 origin,
2828 target_origin,
2829 username,
2830 username_field,
2831 password_field,
2832 (EphyPasswordManagerQueryCallback)password_manager_query_finished_cb,
2833 data);
2834 return TRUE;
2835 }
2836
2837 static gboolean
user_message_received_cb(WebKitWebView * web_view,WebKitUserMessage * message)2838 user_message_received_cb (WebKitWebView *web_view,
2839 WebKitUserMessage *message)
2840 {
2841 const char *name;
2842
2843 name = webkit_user_message_get_name (message);
2844 if (g_strcmp0 (name, "PasswordManager.QueryUsernames") == 0)
2845 return password_manager_handle_query_usernames_message (web_view, message);
2846
2847 if (g_strcmp0 (name, "PasswordManager.QueryPassword") == 0)
2848 return password_manager_handle_query_password_message (web_view, message);
2849
2850 return FALSE;
2851 }
2852
2853 static void
scale_factor_changed_cb(EphyWebView * web_view,GParamSpec * pspec,gpointer user_data)2854 scale_factor_changed_cb (EphyWebView *web_view,
2855 GParamSpec *pspec,
2856 gpointer user_data)
2857 {
2858 _ephy_web_view_update_icon (web_view);
2859 }
2860
2861 GtkWidget *
ephy_web_view_new_with_user_content_manager(WebKitUserContentManager * ucm)2862 ephy_web_view_new_with_user_content_manager (WebKitUserContentManager *ucm)
2863 {
2864 EphyEmbedShell *shell = ephy_embed_shell_get_default ();
2865
2866 return g_object_new (EPHY_TYPE_WEB_VIEW,
2867 "web-context", ephy_embed_shell_get_web_context (shell),
2868 "user-content-manager", ucm,
2869 "settings", ephy_embed_prefs_get_settings (),
2870 "is-controlled-by-automation", ephy_embed_shell_get_mode (shell) == EPHY_EMBED_SHELL_MODE_AUTOMATION,
2871 NULL);
2872 }
2873
2874 /**
2875 * ephy_web_view_load_request:
2876 * @view: the #EphyWebView in which to load the request
2877 * @request: the #WebKitNetworkRequest to be loaded
2878 *
2879 * Loads the given #WebKitNetworkRequest in the given #EphyWebView.
2880 **/
2881 void
ephy_web_view_load_request(EphyWebView * view,WebKitURIRequest * request)2882 ephy_web_view_load_request (EphyWebView *view,
2883 WebKitURIRequest *request)
2884 {
2885 const char *url;
2886 char *effective_url;
2887
2888 g_assert (EPHY_IS_WEB_VIEW (view));
2889 g_assert (WEBKIT_IS_URI_REQUEST (request));
2890
2891 url = webkit_uri_request_get_uri (request);
2892 effective_url = ephy_embed_utils_normalize_address (url);
2893
2894 webkit_uri_request_set_uri (request, effective_url);
2895 g_free (effective_url);
2896
2897 webkit_web_view_load_request (WEBKIT_WEB_VIEW (view), request);
2898 }
2899
2900 /**
2901 * ephy_web_view_load_url:
2902 * @view: an #EphyWebView
2903 * @url: a URL
2904 *
2905 * Loads @url in @view.
2906 **/
2907 void
ephy_web_view_load_url(EphyWebView * view,const char * url)2908 ephy_web_view_load_url (EphyWebView *view,
2909 const char *url)
2910 {
2911 char *effective_url;
2912
2913 g_assert (EPHY_IS_WEB_VIEW (view));
2914 g_assert (url);
2915
2916 effective_url = ephy_embed_utils_normalize_address (url);
2917 if (g_str_has_prefix (effective_url, "javascript:")) {
2918 g_autoptr (GUri) uri = NULL;
2919 g_autofree char *decoded_url = NULL;
2920
2921 uri = g_uri_parse (effective_url, G_URI_FLAGS_NONE, NULL);
2922 decoded_url = g_uri_to_string (uri);
2923 webkit_web_view_run_javascript (WEBKIT_WEB_VIEW (view), decoded_url, NULL, NULL, NULL);
2924 } else
2925 webkit_web_view_load_uri (WEBKIT_WEB_VIEW (view), effective_url);
2926
2927 g_free (effective_url);
2928 }
2929
2930 /**
2931 * ephy_web_view_get_is_blank:
2932 * @view: an #EphyWebView
2933 *
2934 * Returns whether the @view's address is "blank".
2935 *
2936 * Return value: %TRUE if the @view's address is "blank"
2937 **/
2938 gboolean
ephy_web_view_get_is_blank(EphyWebView * view)2939 ephy_web_view_get_is_blank (EphyWebView *view)
2940 {
2941 return view->is_blank;
2942 }
2943
2944 gboolean
ephy_web_view_is_overview(EphyWebView * view)2945 ephy_web_view_is_overview (EphyWebView *view)
2946 {
2947 if (!view->address)
2948 return FALSE;
2949
2950 return (!strcmp (view->address, EPHY_ABOUT_SCHEME ":overview") ||
2951 !strcmp (view->address, "about:overview"));
2952 }
2953
2954 /**
2955 * ephy_web_view_get_address:
2956 * @view: an #EphyWebView
2957 *
2958 * Returns the address of the currently-loaded page, percent-encoded.
2959 * This URI should not be displayed to the user; to do that, use
2960 * ephy_web_view_get_display_address().
2961 *
2962 * Return value: @view's address. Will never be %NULL.
2963 **/
2964 const char *
ephy_web_view_get_address(EphyWebView * view)2965 ephy_web_view_get_address (EphyWebView *view)
2966 {
2967 if (view->address) {
2968 if (g_str_has_prefix (view->address, EPHY_READER_SCHEME))
2969 return view->address + strlen (EPHY_READER_SCHEME) + 1;
2970
2971 return view->address;
2972 }
2973
2974 return "about:blank";
2975 }
2976
2977 /**
2978 * ephy_web_view_get_display_address:
2979 * @view: an #EphyWebView
2980 *
2981 * Returns the display address of the currently-loaded page. This is a
2982 * decoded URI suitable for display to the user. To get a URI suitable
2983 * for sending to a server, e.g. for storage in the bookmarks or history
2984 * database, use ephy_web_view_get_address().
2985 *
2986 * Return value: @view's address. Will never be %NULL.
2987 */
2988 const char *
ephy_web_view_get_display_address(EphyWebView * view)2989 ephy_web_view_get_display_address (EphyWebView *view)
2990 {
2991 return view->display_address ? view->display_address : "about:blank";
2992 }
2993
2994 /**
2995 * ephy_web_view_is_loading:
2996 * @view: an #EphyWebView
2997 *
2998 * Returns whether the web page in @view has finished loading. A web
2999 * page is only finished loading after all images, styles, and other
3000 * dependencies have been downloaded and rendered, or when the load
3001 * has failed for some reason.
3002 *
3003 * Return value: %TRUE if the page is still loading, %FALSE if complete
3004 **/
3005 gboolean
ephy_web_view_is_loading(EphyWebView * view)3006 ephy_web_view_is_loading (EphyWebView *view)
3007 {
3008 return webkit_web_view_is_loading (WEBKIT_WEB_VIEW (view));
3009 }
3010
3011 /**
3012 * ephy_web_view_load_failed:
3013 * @view: an #EphyWebView
3014 *
3015 * Returns whether the web page in @view has failed to load.
3016 *
3017 * Return value: %TRUE if the page failed to load, %FALSE if it's loading
3018 * or load finished successfully
3019 **/
3020 gboolean
ephy_web_view_load_failed(EphyWebView * view)3021 ephy_web_view_load_failed (EphyWebView *view)
3022 {
3023 return view->load_failed;
3024 }
3025
3026 /**
3027 * ephy_web_view_get_icon:
3028 * @view: an #EphyWebView
3029 *
3030 * Returns the view's site icon as a #GdkPixbuf,
3031 * or %NULL if it is not available.
3032 *
3033 * Return value: (transfer none): a the view's site icon
3034 **/
3035 GdkPixbuf *
ephy_web_view_get_icon(EphyWebView * view)3036 ephy_web_view_get_icon (EphyWebView *view)
3037 {
3038 return view->icon;
3039 }
3040
3041 /**
3042 * ephy_web_view_get_document_type:
3043 * @view: an #EphyWebView
3044 *
3045 * Returns the type of document loaded in the @view
3046 *
3047 * Return value: the #EphyWebViewDocumentType
3048 **/
3049 EphyWebViewDocumentType
ephy_web_view_get_document_type(EphyWebView * view)3050 ephy_web_view_get_document_type (EphyWebView *view)
3051 {
3052 return view->document_type;
3053 }
3054
3055 /**
3056 * ephy_web_view_get_navigation_flags:
3057 * @view: an #EphyWebView
3058 *
3059 * Returns @view's navigation flags.
3060 *
3061 * Return value: @view's navigation flags
3062 **/
3063 EphyWebViewNavigationFlags
ephy_web_view_get_navigation_flags(EphyWebView * view)3064 ephy_web_view_get_navigation_flags (EphyWebView *view)
3065 {
3066 return view->nav_flags;
3067 }
3068
3069 /**
3070 * ephy_web_view_get_status_message:
3071 * @view: an #EphyWebView
3072 *
3073 * Returns the message displayed in @view's #EphyWindow's
3074 * #EphyStatusbar. If the user is hovering the mouse over a hyperlink,
3075 * this function will return the same value as
3076 * ephy_web_view_get_link_message(). Otherwise, it will return a network
3077 * status message, or NULL.
3078 *
3079 * The message returned has a limited lifetime, and so should be copied with
3080 * g_strdup() if it must be stored.
3081 *
3082 * Return value: The current statusbar message
3083 **/
3084 const char *
ephy_web_view_get_status_message(EphyWebView * view)3085 ephy_web_view_get_status_message (EphyWebView *view)
3086 {
3087 g_assert (EPHY_IS_WEB_VIEW (view));
3088
3089 if (view->link_message && view->link_message[0] != '\0')
3090 return view->link_message;
3091
3092 if (view->loading_message)
3093 return view->loading_message;
3094
3095 return NULL;
3096 }
3097
3098 /**
3099 * ephy_web_view_get_link_message:
3100 * @view: an #EphyWebView
3101 *
3102 * When the user is hovering the mouse over a hyperlink, returns the URL of the
3103 * hyperlink.
3104 *
3105 * Return value: the URL of the link over which the mouse is hovering
3106 **/
3107 const char *
ephy_web_view_get_link_message(EphyWebView * view)3108 ephy_web_view_get_link_message (EphyWebView *view)
3109 {
3110 g_assert (EPHY_IS_WEB_VIEW (view));
3111
3112 return view->link_message;
3113 }
3114
3115 /**
3116 * ephy_web_view_set_link_message:
3117 * @view: an #EphyWebView
3118 * @address: new value for link-message in @view
3119 *
3120 * Sets the value of link-message property which tells the URL of the hovered
3121 * link.
3122 **/
3123 void
ephy_web_view_set_link_message(EphyWebView * view,const char * address)3124 ephy_web_view_set_link_message (EphyWebView *view,
3125 const char *address)
3126 {
3127 char *decoded_address;
3128
3129 g_assert (EPHY_IS_WEB_VIEW (view));
3130
3131 g_free (view->link_message);
3132
3133 if (address) {
3134 decoded_address = ephy_uri_decode (address);
3135 view->link_message = ephy_embed_utils_link_message_parse (decoded_address);
3136 g_free (decoded_address);
3137 } else {
3138 view->link_message = NULL;
3139 }
3140
3141 g_object_notify_by_pspec (G_OBJECT (view), obj_properties[PROP_STATUS_MESSAGE]);
3142 g_object_notify_by_pspec (G_OBJECT (view), obj_properties[PROP_LINK_MESSAGE]);
3143 }
3144
3145 /**
3146 * ephy_web_view_set_security_level:
3147 * @view: an #EphyWebView
3148 * @level: the new #EphySecurityLevel for @view
3149 *
3150 * Sets @view's security-level property to @level.
3151 **/
3152 void
ephy_web_view_set_security_level(EphyWebView * view,EphySecurityLevel level)3153 ephy_web_view_set_security_level (EphyWebView *view,
3154 EphySecurityLevel level)
3155 {
3156 g_assert (EPHY_IS_WEB_VIEW (view));
3157
3158 if (view->security_level != level) {
3159 view->security_level = level;
3160
3161 g_object_notify_by_pspec (G_OBJECT (view), obj_properties[PROP_SECURITY]);
3162 }
3163 }
3164
3165 /**
3166 * ephy_web_view_get_typed_address:
3167 * @view: an #EphyWebView
3168 *
3169 * Returns the text that the user introduced in the @view's
3170 * #EphyWindow location entry, if any.
3171 *
3172 * This is not guaranteed to be the same as @view's location,
3173 * available through ephy_web_view_get_address(). As the user types a
3174 * new address into the location entry,
3175 * ephy_web_view_get_typed_address()'s returned string will
3176 * change. When the load starts, ephy_web_view_get_typed_address()
3177 * will return %NULL, and ephy_web_view_get_address() will return the
3178 * new page being loaded. Note that the typed_address can be changed
3179 * again while a load is in progress (in case the user starts to type
3180 * again in the location entry); in that case
3181 * ephy_web_view_get_typed_address() will be again non-%NULL, and the
3182 * contents of the entry will not be overwritten.
3183 *
3184 * Return value: @view's #EphyWindow's location entry text when @view
3185 * is selected.
3186 **/
3187 const char *
ephy_web_view_get_typed_address(EphyWebView * view)3188 ephy_web_view_get_typed_address (EphyWebView *view)
3189 {
3190 g_assert (EPHY_IS_WEB_VIEW (view));
3191
3192 return view->typed_address;
3193 }
3194
3195 /**
3196 * ephy_web_view_set_typed_address:
3197 * @view: an #EphyWebView
3198 * @address: the new typed address, or %NULL to clear it
3199 *
3200 * Sets the text that @view's #EphyWindow will display in its location toolbar
3201 * entry when @view is selected.
3202 **/
3203 void
ephy_web_view_set_typed_address(EphyWebView * view,const char * address)3204 ephy_web_view_set_typed_address (EphyWebView *view,
3205 const char *address)
3206 {
3207 g_assert (EPHY_IS_WEB_VIEW (view));
3208
3209 g_free (view->typed_address);
3210 view->typed_address = g_strdup (address);
3211
3212 g_object_notify_by_pspec (G_OBJECT (view), obj_properties[PROP_TYPED_ADDRESS]);
3213 }
3214
3215 gboolean
ephy_web_view_get_should_bypass_safe_browsing(EphyWebView * view)3216 ephy_web_view_get_should_bypass_safe_browsing (EphyWebView *view)
3217 {
3218 g_assert (EPHY_IS_WEB_VIEW (view));
3219
3220 return view->bypass_safe_browsing;
3221 }
3222
3223 void
ephy_web_view_set_should_bypass_safe_browsing(EphyWebView * view,gboolean bypass_safe_browsing)3224 ephy_web_view_set_should_bypass_safe_browsing (EphyWebView *view,
3225 gboolean bypass_safe_browsing)
3226 {
3227 g_assert (EPHY_IS_WEB_VIEW (view));
3228
3229 view->bypass_safe_browsing = bypass_safe_browsing;
3230 }
3231
3232 static gboolean
has_modified_forms_timeout_cb(gpointer user_data)3233 has_modified_forms_timeout_cb (gpointer user_data)
3234 {
3235 GTask *task = user_data;
3236
3237 g_assert (!g_task_get_completed (task));
3238 g_task_set_task_data (task, GINT_TO_POINTER (0), NULL);
3239 g_task_return_boolean (task, FALSE);
3240
3241 return G_SOURCE_REMOVE;
3242 }
3243
3244 static void
has_modified_forms_cb(WebKitWebView * view,GAsyncResult * result,GTask * task)3245 has_modified_forms_cb (WebKitWebView *view,
3246 GAsyncResult *result,
3247 GTask *task)
3248 {
3249 WebKitJavascriptResult *js_result;
3250 gboolean retval = FALSE;
3251 GError *error = NULL;
3252 gulong id;
3253
3254 js_result = webkit_web_view_run_javascript_in_world_finish (view, result, &error);
3255
3256 id = GPOINTER_TO_INT (g_task_get_task_data (task));
3257 if (id == 0) {
3258 /* We hit the timeout. Our task has already returned. */
3259 goto out;
3260 }
3261 g_source_remove (id);
3262
3263 if (!js_result) {
3264 g_task_return_error (task, error);
3265 } else {
3266 retval = jsc_value_to_boolean (webkit_javascript_result_get_js_value (js_result));
3267 g_task_return_boolean (task, retval);
3268 }
3269
3270 out:
3271 if (js_result)
3272 webkit_javascript_result_unref (js_result);
3273 g_object_unref (task);
3274 }
3275
3276 /**
3277 * ephy_web_view_has_modified_forms:
3278 * @view: an #EphyWebView
3279 *
3280 * A small heuristic is used here. If there's only one input element modified
3281 * and it does not have a lot of text the user is likely not very interested in
3282 * saving this work, so it returns %FALSE in this case (eg, google search
3283 * input).
3284 *
3285 * Returns %TRUE if the user has modified <input> or <textarea>
3286 * values in @view's loaded document.
3287 *
3288 * Return value: %TRUE if @view has user-modified forms
3289 **/
3290 void
ephy_web_view_has_modified_forms(EphyWebView * view,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)3291 ephy_web_view_has_modified_forms (EphyWebView *view,
3292 GCancellable *cancellable,
3293 GAsyncReadyCallback callback,
3294 gpointer user_data)
3295 {
3296 GTask *task;
3297 gulong id;
3298
3299 g_assert (EPHY_IS_WEB_VIEW (view));
3300
3301 task = g_task_new (view, cancellable, callback, user_data);
3302
3303 /* Set timeout to guard against web process hangs. Otherwise, a single
3304 * unresponsive web process would prevent the window from closing. Note that
3305 * although webkit_web_view_run_javascript_in_world() takes a cancellable,
3306 * it's not *really* cancellable and attempting to cancel it just causes it to
3307 * return G_IO_ERROR_CANCELLED after however long it takes to finish, which
3308 * will be never if the web process is unresponsive, so we always fake
3309 * completion after a two second delay.
3310 */
3311 id = g_timeout_add_seconds (2, has_modified_forms_timeout_cb, task);
3312 g_task_set_task_data (task, GINT_TO_POINTER (id), NULL);
3313
3314 webkit_web_view_run_javascript_in_world (WEBKIT_WEB_VIEW (view),
3315 "Ephy.hasModifiedForms();",
3316 ephy_embed_shell_get_guid (ephy_embed_shell_get_default ()),
3317 cancellable,
3318 (GAsyncReadyCallback)has_modified_forms_cb,
3319 task);
3320 }
3321
3322 gboolean
ephy_web_view_has_modified_forms_finish(EphyWebView * view,GAsyncResult * result,GError ** error)3323 ephy_web_view_has_modified_forms_finish (EphyWebView *view,
3324 GAsyncResult *result,
3325 GError **error)
3326 {
3327 g_assert (g_task_is_valid (result, view));
3328
3329 return g_task_propagate_boolean (G_TASK (result), error);
3330 }
3331
3332 typedef struct {
3333 char *icon_uri;
3334 char *icon_color;
3335 } GetBestWebAppIconAsyncData;
3336
3337 static void
get_best_web_app_icon_async_data_free(GetBestWebAppIconAsyncData * data)3338 get_best_web_app_icon_async_data_free (GetBestWebAppIconAsyncData *data)
3339 {
3340 g_free (data->icon_uri);
3341 g_free (data->icon_color);
3342
3343 g_free (data);
3344 }
3345
3346 static void
get_best_web_app_icon_cb(WebKitWebView * view,GAsyncResult * result,GTask * task)3347 get_best_web_app_icon_cb (WebKitWebView *view,
3348 GAsyncResult *result,
3349 GTask *task)
3350 {
3351 WebKitJavascriptResult *js_result;
3352 GError *error = NULL;
3353
3354 js_result = webkit_web_view_run_javascript_in_world_finish (view, result, &error);
3355 if (js_result) {
3356 JSCValue *js_value, *js_uri, *js_color;
3357 GetBestWebAppIconAsyncData *data;
3358
3359 data = g_new0 (GetBestWebAppIconAsyncData, 1);
3360 js_value = webkit_javascript_result_get_js_value (js_result);
3361 g_assert (jsc_value_is_object (js_value));
3362
3363 js_uri = jsc_value_object_get_property (js_value, "url");
3364 data->icon_uri = jsc_value_to_string (js_uri);
3365 g_object_unref (js_uri);
3366
3367 js_color = jsc_value_object_get_property (js_value, "icon");
3368 data->icon_color = jsc_value_is_null (js_color) || jsc_value_is_undefined (js_color) ? NULL : jsc_value_to_string (js_color);
3369 g_object_unref (js_color);
3370
3371 g_task_return_pointer (task, data, (GDestroyNotify)get_best_web_app_icon_async_data_free);
3372 webkit_javascript_result_unref (js_result);
3373 } else
3374 g_task_return_error (task, error);
3375
3376 g_object_unref (task);
3377 }
3378
3379 void
ephy_web_view_get_best_web_app_icon(EphyWebView * view,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)3380 ephy_web_view_get_best_web_app_icon (EphyWebView *view,
3381 GCancellable *cancellable,
3382 GAsyncReadyCallback callback,
3383 gpointer user_data)
3384 {
3385 WebKitWebView *wk_view;
3386 GTask *task;
3387 char *script;
3388
3389 g_assert (EPHY_IS_WEB_VIEW (view));
3390 wk_view = WEBKIT_WEB_VIEW (view);
3391
3392 task = g_task_new (view, cancellable, callback, user_data);
3393 script = g_strdup_printf ("Ephy.getWebAppIcon(\"%s\");", webkit_web_view_get_uri (wk_view));
3394 webkit_web_view_run_javascript_in_world (wk_view,
3395 script,
3396 ephy_embed_shell_get_guid (ephy_embed_shell_get_default ()),
3397 cancellable,
3398 (GAsyncReadyCallback)get_best_web_app_icon_cb,
3399 task);
3400 g_free (script);
3401 }
3402
3403 gboolean
ephy_web_view_get_best_web_app_icon_finish(EphyWebView * view,GAsyncResult * result,char ** icon_uri,GdkRGBA * icon_color,GError ** error)3404 ephy_web_view_get_best_web_app_icon_finish (EphyWebView *view,
3405 GAsyncResult *result,
3406 char **icon_uri,
3407 GdkRGBA *icon_color,
3408 GError **error)
3409 {
3410 GetBestWebAppIconAsyncData *data;
3411 GTask *task = G_TASK (result);
3412
3413 g_assert (g_task_is_valid (result, view));
3414
3415 data = g_task_propagate_pointer (task, error);
3416 if (!data)
3417 return FALSE;
3418
3419 if (data->icon_uri != NULL && data->icon_uri[0] != '\0') {
3420 *icon_uri = data->icon_uri;
3421 data->icon_uri = NULL;
3422 }
3423
3424 if (data->icon_color != NULL && data->icon_color[0] != '\0')
3425 gdk_rgba_parse (icon_color, data->icon_color);
3426
3427 get_best_web_app_icon_async_data_free (data);
3428
3429 return TRUE;
3430 }
3431
3432 static void
get_web_app_title_cb(WebKitWebView * view,GAsyncResult * result,GTask * task)3433 get_web_app_title_cb (WebKitWebView *view,
3434 GAsyncResult *result,
3435 GTask *task)
3436 {
3437 WebKitJavascriptResult *js_result;
3438 GError *error = NULL;
3439
3440 js_result = webkit_web_view_run_javascript_in_world_finish (view, result, &error);
3441 if (js_result) {
3442 JSCValue *js_value;
3443 char *retval = NULL;
3444
3445 js_value = webkit_javascript_result_get_js_value (js_result);
3446 if (!jsc_value_is_null (js_value) && !jsc_value_is_undefined (js_value))
3447 retval = jsc_value_to_string (js_value);
3448 g_task_return_pointer (task, retval, (GDestroyNotify)g_free);
3449 webkit_javascript_result_unref (js_result);
3450 } else
3451 g_task_return_error (task, error);
3452
3453 g_object_unref (task);
3454 }
3455
3456 void
ephy_web_view_get_web_app_title(EphyWebView * view,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)3457 ephy_web_view_get_web_app_title (EphyWebView *view,
3458 GCancellable *cancellable,
3459 GAsyncReadyCallback callback,
3460 gpointer user_data)
3461 {
3462 GTask *task;
3463
3464 g_assert (EPHY_IS_WEB_VIEW (view));
3465
3466 task = g_task_new (view, cancellable, callback, user_data);
3467 webkit_web_view_run_javascript_in_world (WEBKIT_WEB_VIEW (view),
3468 "Ephy.getWebAppTitle();",
3469 ephy_embed_shell_get_guid (ephy_embed_shell_get_default ()),
3470 cancellable,
3471 (GAsyncReadyCallback)get_web_app_title_cb,
3472 task);
3473 }
3474
3475 char *
ephy_web_view_get_web_app_title_finish(EphyWebView * view,GAsyncResult * result,GError ** error)3476 ephy_web_view_get_web_app_title_finish (EphyWebView *view,
3477 GAsyncResult *result,
3478 GError **error)
3479 {
3480 g_assert (g_task_is_valid (result, view));
3481
3482 return g_task_propagate_pointer (G_TASK (result), error);
3483 }
3484
3485 static void
get_web_app_mobile_capable_cb(WebKitWebView * view,GAsyncResult * result,GTask * task)3486 get_web_app_mobile_capable_cb (WebKitWebView *view,
3487 GAsyncResult *result,
3488 GTask *task)
3489 {
3490 WebKitJavascriptResult *js_result;
3491 GError *error = NULL;
3492
3493 js_result = webkit_web_view_run_javascript_in_world_finish (view, result, &error);
3494 if (js_result) {
3495 JSCValue *js_value;
3496 gboolean retval = FALSE;
3497
3498 js_value = webkit_javascript_result_get_js_value (js_result);
3499 retval = jsc_value_to_boolean (js_value);
3500
3501 g_task_return_boolean (task, retval);
3502 webkit_javascript_result_unref (js_result);
3503 } else
3504 g_task_return_error (task, error);
3505
3506 g_object_unref (task);
3507 }
3508
3509 void
ephy_web_view_get_web_app_mobile_capable(EphyWebView * view,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)3510 ephy_web_view_get_web_app_mobile_capable (EphyWebView *view,
3511 GCancellable *cancellable,
3512 GAsyncReadyCallback callback,
3513 gpointer user_data)
3514 {
3515 GTask *task;
3516
3517 g_assert (EPHY_IS_WEB_VIEW (view));
3518
3519 task = g_task_new (view, cancellable, callback, user_data);
3520 webkit_web_view_run_javascript_in_world (WEBKIT_WEB_VIEW (view),
3521 "Ephy.getAppleMobileWebAppCapable();",
3522 ephy_embed_shell_get_guid (ephy_embed_shell_get_default ()),
3523 cancellable,
3524 (GAsyncReadyCallback)get_web_app_mobile_capable_cb,
3525 task);
3526 }
3527
3528 gboolean
ephy_web_view_get_web_app_mobile_capable_finish(EphyWebView * view,GAsyncResult * result,GError ** error)3529 ephy_web_view_get_web_app_mobile_capable_finish (EphyWebView *view,
3530 GAsyncResult *result,
3531 GError **error)
3532 {
3533 g_assert (g_task_is_valid (result, view));
3534
3535 return g_task_propagate_boolean (G_TASK (result), error);
3536 }
3537
3538 /**
3539 * ephy_web_view_get_security_level:
3540 * @view: an #EphyWebView
3541 * @level: (out): return value of security level
3542 * @address: (out) (transfer none): the URI to which the security level corresponds
3543 * @certificate: (out) (transfer none): return value of TLS certificate
3544 * @errors: (out): return value of TLS errors
3545 *
3546 * Fetches the #EphySecurityLevel and a #GTlsCertificate associated
3547 * with @view and a #GTlsCertificateFlags showing what problems, if any,
3548 * have been found with that certificate.
3549 **/
3550 void
ephy_web_view_get_security_level(EphyWebView * view,EphySecurityLevel * level,const char ** address,GTlsCertificate ** certificate,GTlsCertificateFlags * errors)3551 ephy_web_view_get_security_level (EphyWebView *view,
3552 EphySecurityLevel *level,
3553 const char **address,
3554 GTlsCertificate **certificate,
3555 GTlsCertificateFlags *errors)
3556 {
3557 g_assert (EPHY_IS_WEB_VIEW (view));
3558
3559 if (level)
3560 *level = view->security_level;
3561
3562 if (address)
3563 *address = view->last_committed_address;
3564
3565 if (certificate)
3566 *certificate = view->certificate;
3567
3568 if (errors)
3569 *errors = view->tls_errors;
3570 }
3571
3572 static void
ephy_web_view_print_failed(EphyWebView * view,GError * error)3573 ephy_web_view_print_failed (EphyWebView *view,
3574 GError *error)
3575 {
3576 GtkWidget *info_bar;
3577 GtkWidget *label;
3578 GtkContainer *content_area;
3579 EphyEmbed *embed = EPHY_GET_EMBED_FROM_EPHY_WEB_VIEW (view);
3580
3581 info_bar = gtk_info_bar_new_with_buttons (_("_OK"), GTK_RESPONSE_OK, NULL);
3582 label = gtk_label_new (error->message);
3583 content_area = GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar)));
3584
3585 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_ERROR);
3586 gtk_container_add (content_area, label);
3587 g_signal_connect (info_bar, "response",
3588 G_CALLBACK (gtk_widget_destroy), NULL);
3589
3590 ephy_embed_add_top_widget (embed, info_bar, EPHY_EMBED_TOP_WIDGET_POLICY_RETAIN_ON_TRANSITION);
3591 gtk_widget_show_all (info_bar);
3592 }
3593
3594 static void
print_operation_finished_cb(WebKitPrintOperation * operation,EphyWebView * view)3595 print_operation_finished_cb (WebKitPrintOperation *operation,
3596 EphyWebView *view)
3597 {
3598 ephy_embed_shell_set_page_setup (ephy_embed_shell_get_default (),
3599 webkit_print_operation_get_page_setup (operation));
3600 }
3601
3602 static void
print_operation_failed_cb(WebKitPrintOperation * operation,GError * error,EphyWebView * view)3603 print_operation_failed_cb (WebKitPrintOperation *operation,
3604 GError *error,
3605 EphyWebView *view)
3606 {
3607 g_signal_handlers_disconnect_by_func (operation, print_operation_finished_cb, view);
3608 ephy_web_view_print_failed (view, error);
3609 }
3610
3611 /**
3612 * ephy_web_view_print:
3613 * @view: an #EphyWebView
3614 *
3615 * Opens a dialog to print the specified view.
3616 *
3617 * Since: 2.30
3618 **/
3619 void
ephy_web_view_print(EphyWebView * view)3620 ephy_web_view_print (EphyWebView *view)
3621 {
3622 WebKitPrintOperation *operation;
3623 EphyEmbedShell *shell;
3624 GtkPrintSettings *settings;
3625
3626 g_assert (EPHY_IS_WEB_VIEW (view));
3627
3628 shell = ephy_embed_shell_get_default ();
3629
3630 operation = webkit_print_operation_new (WEBKIT_WEB_VIEW (view));
3631 g_signal_connect (operation, "finished",
3632 G_CALLBACK (print_operation_finished_cb),
3633 view);
3634 g_signal_connect (operation, "failed",
3635 G_CALLBACK (print_operation_failed_cb),
3636 view);
3637 webkit_print_operation_set_page_setup (operation, ephy_embed_shell_get_page_setup (shell));
3638 settings = ephy_embed_shell_get_print_settings (shell);
3639 gtk_print_settings_set (settings,
3640 GTK_PRINT_SETTINGS_OUTPUT_BASENAME,
3641 webkit_web_view_get_title (WEBKIT_WEB_VIEW (view)));
3642 webkit_print_operation_set_print_settings (operation, settings);
3643 if (webkit_print_operation_run_dialog (operation, NULL) == WEBKIT_PRINT_OPERATION_RESPONSE_PRINT)
3644 ephy_embed_shell_set_print_settings (shell, webkit_print_operation_get_print_settings (operation));
3645
3646 g_object_unref (operation);
3647 }
3648
3649 static void
web_resource_get_data_cb(WebKitWebResource * resource,GAsyncResult * result,GOutputStream * output_stream)3650 web_resource_get_data_cb (WebKitWebResource *resource,
3651 GAsyncResult *result,
3652 GOutputStream *output_stream)
3653 {
3654 guchar *data;
3655 gsize data_length;
3656 GInputStream *input_stream;
3657 GError *error = NULL;
3658
3659 data = webkit_web_resource_get_data_finish (resource, result, &data_length, &error);
3660 if (!data) {
3661 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
3662 g_warning ("Failed to save page: %s", error->message);
3663 g_error_free (error);
3664 g_object_unref (output_stream);
3665 return;
3666 }
3667
3668 input_stream = g_memory_input_stream_new_from_data (data, data_length, g_free);
3669 g_output_stream_splice_async (output_stream, input_stream,
3670 G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
3671 G_PRIORITY_DEFAULT,
3672 NULL, NULL, NULL);
3673 g_object_unref (input_stream);
3674 g_object_unref (output_stream);
3675 }
3676
3677 static void
ephy_web_view_save_main_resource_cb(GFile * file,GAsyncResult * result,WebKitWebView * view)3678 ephy_web_view_save_main_resource_cb (GFile *file,
3679 GAsyncResult *result,
3680 WebKitWebView *view)
3681 {
3682 GFileOutputStream *output_stream;
3683 WebKitWebResource *resource;
3684 GError *error = NULL;
3685
3686 output_stream = g_file_replace_finish (file, result, &error);
3687 if (!output_stream) {
3688 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
3689 g_warning ("Failed to save page: %s", error->message);
3690 g_error_free (error);
3691 return;
3692 }
3693
3694 resource = webkit_web_view_get_main_resource (view);
3695 webkit_web_resource_get_data (resource,
3696 EPHY_WEB_VIEW (view)->cancellable,
3697 (GAsyncReadyCallback)web_resource_get_data_cb,
3698 output_stream);
3699 }
3700 /**
3701 * ephy_web_view_save:
3702 * @view: an #EphyWebView
3703 * @uri: location to store the saved page
3704 *
3705 * Saves the currently loaded page of @view to @uri.
3706 **/
3707 void
ephy_web_view_save(EphyWebView * view,const char * uri)3708 ephy_web_view_save (EphyWebView *view,
3709 const char *uri)
3710 {
3711 GFile *file;
3712
3713 g_assert (EPHY_IS_WEB_VIEW (view));
3714 g_assert (uri);
3715
3716 file = g_file_new_for_uri (uri);
3717
3718 if (g_str_has_suffix (uri, ".mhtml"))
3719 webkit_web_view_save_to_file (WEBKIT_WEB_VIEW (view), file, WEBKIT_SAVE_MODE_MHTML,
3720 NULL, NULL, NULL);
3721 else
3722 g_file_replace_async (file, NULL, FALSE,
3723 G_FILE_CREATE_REPLACE_DESTINATION | G_FILE_CREATE_PRIVATE,
3724 G_PRIORITY_DEFAULT,
3725 view->cancellable,
3726 (GAsyncReadyCallback)ephy_web_view_save_main_resource_cb,
3727 view);
3728 g_object_unref (file);
3729 }
3730
3731 /**
3732 * ephy_web_view_load_homepage:
3733 * @view: an #EphyWebView
3734 *
3735 * Loads the homepage set by the user in @view.
3736 **/
3737 void
ephy_web_view_load_homepage(EphyWebView * view)3738 ephy_web_view_load_homepage (EphyWebView *view)
3739 {
3740 EphyEmbedShell *shell;
3741 EphyEmbedShellMode mode;
3742 char *home;
3743
3744 g_assert (EPHY_IS_WEB_VIEW (view));
3745
3746 shell = ephy_embed_shell_get_default ();
3747 mode = ephy_embed_shell_get_mode (shell);
3748
3749 if (mode == EPHY_EMBED_SHELL_MODE_INCOGNITO ||
3750 mode == EPHY_EMBED_SHELL_MODE_AUTOMATION) {
3751 ephy_web_view_load_new_tab_page (view);
3752 return;
3753 }
3754
3755 home = g_settings_get_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_HOMEPAGE_URL);
3756 if (home == NULL || home[0] == '\0') {
3757 ephy_web_view_load_new_tab_page (view);
3758 } else {
3759 ephy_web_view_freeze_history (view);
3760 ephy_web_view_set_visit_type (view, EPHY_PAGE_VISIT_HOMEPAGE);
3761 ephy_web_view_load_url (view, home);
3762 }
3763 g_free (home);
3764 }
3765
3766 void
ephy_web_view_load_new_tab_page(EphyWebView * view)3767 ephy_web_view_load_new_tab_page (EphyWebView *view)
3768 {
3769 EphyEmbedShell *shell;
3770 EphyEmbedShellMode mode;
3771
3772 g_assert (EPHY_IS_WEB_VIEW (view));
3773
3774 shell = ephy_embed_shell_get_default ();
3775 mode = ephy_embed_shell_get_mode (shell);
3776
3777 ephy_web_view_freeze_history (view);
3778 ephy_web_view_set_visit_type (view, EPHY_PAGE_VISIT_HOMEPAGE);
3779 if (mode == EPHY_EMBED_SHELL_MODE_INCOGNITO)
3780 ephy_web_view_load_url (view, "about:incognito");
3781 else if (mode == EPHY_EMBED_SHELL_MODE_AUTOMATION)
3782 ephy_web_view_load_url (view, "about:blank");
3783 else
3784 ephy_web_view_load_url (view, "about:overview");
3785 }
3786
3787 /**
3788 * ephy_web_view_get_visit_type:
3789 * @view: an #EphyWebView
3790 *
3791 * Returns: the @view #EphyWebViewVisitType
3792 **/
3793 EphyHistoryPageVisitType
ephy_web_view_get_visit_type(EphyWebView * view)3794 ephy_web_view_get_visit_type (EphyWebView *view)
3795 {
3796 g_assert (EPHY_IS_WEB_VIEW (view));
3797
3798 return view->visit_type;
3799 }
3800
3801 /**
3802 * ephy_web_view_set_visit_type:
3803 * @view: an #EphyWebView
3804 * @visit_type: an #EphyHistoryPageVisitType
3805 *
3806 * Sets the @visit_type for @view, so that the URI can be
3807 * properly weighted in the history backend.
3808 **/
3809 void
ephy_web_view_set_visit_type(EphyWebView * view,EphyHistoryPageVisitType visit_type)3810 ephy_web_view_set_visit_type (EphyWebView *view,
3811 EphyHistoryPageVisitType visit_type)
3812 {
3813 g_assert (EPHY_IS_WEB_VIEW (view));
3814
3815 view->visit_type = visit_type;
3816 }
3817
3818
3819 /**
3820 * ephy_web_view_toggle_reader_mode:
3821 * @view: an #EphyWebView
3822 * @active: active flag
3823 *
3824 * Sets reader mode state to @active if necessary.
3825 **/
3826 void
ephy_web_view_toggle_reader_mode(EphyWebView * view,gboolean active)3827 ephy_web_view_toggle_reader_mode (EphyWebView *view,
3828 gboolean active)
3829 {
3830 WebKitWebView *web_view = WEBKIT_WEB_VIEW (view);
3831 char *reader_uri = NULL;
3832 const gchar *address;
3833 gboolean view_active = g_str_has_prefix (view->address, EPHY_READER_SCHEME);
3834
3835 if (view_active == active)
3836 return;
3837
3838 address = ephy_web_view_get_address (view);
3839
3840 if (view_active) {
3841 ephy_web_view_freeze_history (view);
3842 webkit_web_view_load_uri (web_view, address);
3843 return;
3844 }
3845
3846 if (!ephy_web_view_is_reader_mode_available (view))
3847 return;
3848
3849 reader_uri = g_strconcat (EPHY_READER_SCHEME, ":", address, NULL);
3850
3851 view->entering_reader_mode = TRUE;
3852 g_object_notify_by_pspec (G_OBJECT (web_view), obj_properties[PROP_ENTERING_READER_MODE]);
3853
3854 webkit_web_view_load_uri (web_view, reader_uri);
3855 }
3856
3857 gboolean
ephy_web_view_is_reader_mode_available(EphyWebView * view)3858 ephy_web_view_is_reader_mode_available (EphyWebView *view)
3859 {
3860 return view->reader_mode_available;
3861 }
3862
3863 gboolean
ephy_web_view_get_reader_mode_state(EphyWebView * view)3864 ephy_web_view_get_reader_mode_state (EphyWebView *view)
3865 {
3866 if (!view->address)
3867 return FALSE;
3868 return g_str_has_prefix (view->address, EPHY_READER_SCHEME);
3869 }
3870
3871 gboolean
ephy_web_view_is_in_auth_dialog(EphyWebView * view)3872 ephy_web_view_is_in_auth_dialog (EphyWebView *view)
3873 {
3874 return view->in_auth_dialog;
3875 }
3876
3877 static void
ephy_web_view_dispose(GObject * object)3878 ephy_web_view_dispose (GObject *object)
3879 {
3880 EphyWebView *view = EPHY_WEB_VIEW (object);
3881 WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager (WEBKIT_WEB_VIEW (view));
3882
3883 ephy_embed_prefs_unregister_ucm (ucm);
3884 ephy_embed_shell_unregister_ucm_handler (ephy_embed_shell_get_default (), ucm);
3885
3886 untrack_info_bar (&view->geolocation_info_bar);
3887 untrack_info_bar (&view->notification_info_bar);
3888 untrack_info_bar (&view->microphone_info_bar);
3889 untrack_info_bar (&view->webcam_info_bar);
3890 untrack_info_bar (&view->webcam_mic_info_bar);
3891 untrack_info_bar (&view->password_info_bar);
3892 untrack_info_bar (&view->password_form_info_bar);
3893 untrack_info_bar (&view->itp_info_bar);
3894
3895 g_clear_object (&view->certificate);
3896 g_clear_object (&view->file_monitor);
3897 g_clear_object (&view->icon);
3898 g_clear_pointer (&view->unresponsive_process_dialog, gtk_widget_destroy);
3899
3900 if (view->cancellable) {
3901 g_cancellable_cancel (view->cancellable);
3902 g_clear_object (&view->cancellable);
3903 }
3904
3905 g_clear_handle_id (&view->snapshot_timeout_id, g_source_remove);
3906 g_clear_handle_id (&view->reader_js_timeout, g_source_remove);
3907 g_clear_handle_id (&view->unresponsive_process_timeout_id, g_source_remove);
3908
3909 G_OBJECT_CLASS (ephy_web_view_parent_class)->dispose (object);
3910 }
3911
3912 static void
ephy_web_view_finalize(GObject * object)3913 ephy_web_view_finalize (GObject *object)
3914 {
3915 EphyWebView *view = EPHY_WEB_VIEW (object);
3916
3917 g_free (view->address);
3918 g_free (view->display_address);
3919 g_free (view->typed_address);
3920 g_free (view->last_committed_address);
3921 g_free (view->link_message);
3922 g_free (view->loading_message);
3923 g_free (view->tls_error_failing_uri);
3924 g_free (view->pending_snapshot_uri);
3925
3926 G_OBJECT_CLASS (ephy_web_view_parent_class)->finalize (object);
3927 }
3928
3929 static void
ephy_web_view_constructed(GObject * object)3930 ephy_web_view_constructed (GObject *object)
3931 {
3932 EphyWebView *web_view = EPHY_WEB_VIEW (object);
3933 g_auto (GStrv) cors_allowlist = NULL;
3934 GtkStyleContext *context;
3935 GdkRGBA color;
3936
3937 G_OBJECT_CLASS (ephy_web_view_parent_class)->constructed (object);
3938
3939 g_signal_emit_by_name (ephy_embed_shell_get_default (), "web-view-created", web_view);
3940
3941 g_signal_connect (web_view, "web-process-terminated",
3942 G_CALLBACK (process_terminated_cb), NULL);
3943 g_signal_connect_swapped (webkit_web_view_get_back_forward_list (WEBKIT_WEB_VIEW (web_view)),
3944 "changed", G_CALLBACK (update_navigation_flags), web_view);
3945
3946 /* Avoid flashing a white background when loading the overview in
3947 * dark mode. Note that we have to later reset this to white before
3948 * loading any non-Epiphany page.
3949 */
3950 context = gtk_widget_get_style_context (GTK_WIDGET (web_view));
3951 if (gtk_style_context_lookup_color (context, "theme_base_color", &color))
3952 webkit_web_view_set_background_color (WEBKIT_WEB_VIEW (web_view), &color);
3953
3954 cors_allowlist = g_new (char *, 2);
3955 cors_allowlist[0] = g_strdup ("ephy-resource://*/*");
3956 cors_allowlist[1] = NULL;
3957 webkit_web_view_set_cors_allowlist (WEBKIT_WEB_VIEW (web_view), (const char * const *)cors_allowlist);
3958 }
3959
3960 static void
ephy_web_view_init(EphyWebView * web_view)3961 ephy_web_view_init (EphyWebView *web_view)
3962 {
3963 EphyEmbedShell *shell;
3964
3965 shell = ephy_embed_shell_get_default ();
3966
3967 web_view->uid = web_view_uid++;
3968
3969 web_view->is_blank = TRUE;
3970 web_view->ever_committed = FALSE;
3971 web_view->document_type = EPHY_WEB_VIEW_DOCUMENT_HTML;
3972 web_view->security_level = EPHY_SECURITY_LEVEL_TO_BE_DETERMINED;
3973
3974 web_view->file_monitor = ephy_file_monitor_new (web_view);
3975
3976 web_view->history_service = ephy_embed_shell_get_global_history_service (shell);
3977
3978 web_view->cancellable = g_cancellable_new ();
3979
3980 g_signal_connect_object (EPHY_SETTINGS_READER, "changed::" EPHY_PREFS_READER_FONT_STYLE,
3981 G_CALLBACK (reader_setting_changed_cb),
3982 web_view, 0);
3983
3984 g_signal_connect_object (EPHY_SETTINGS_READER, "changed::" EPHY_PREFS_READER_COLOR_SCHEME,
3985 G_CALLBACK (reader_setting_changed_cb),
3986 web_view, 0);
3987
3988 g_signal_connect (web_view, "decide-policy",
3989 G_CALLBACK (decide_policy_cb),
3990 NULL);
3991
3992 g_signal_connect (web_view, "permission-request",
3993 G_CALLBACK (permission_request_cb),
3994 NULL);
3995
3996 g_signal_connect (web_view, "load-changed",
3997 G_CALLBACK (load_changed_cb),
3998 NULL);
3999
4000 g_signal_connect (web_view, "close",
4001 G_CALLBACK (close_web_view_cb),
4002 NULL);
4003 g_signal_connect (web_view, "load-failed",
4004 G_CALLBACK (load_failed_cb),
4005 NULL);
4006
4007 g_signal_connect (web_view, "load-failed-with-tls-errors",
4008 G_CALLBACK (load_failed_with_tls_error_cb),
4009 NULL);
4010
4011 g_signal_connect (web_view, "insecure-content-detected",
4012 G_CALLBACK (mixed_content_detected_cb),
4013 NULL);
4014
4015 g_signal_connect (web_view, "notify::zoom-level",
4016 G_CALLBACK (zoom_changed_cb),
4017 NULL);
4018
4019 g_signal_connect (web_view, "notify::title",
4020 G_CALLBACK (title_changed_cb),
4021 NULL);
4022
4023 g_signal_connect (web_view, "notify::uri",
4024 G_CALLBACK (uri_changed_cb),
4025 NULL);
4026
4027 g_signal_connect (web_view, "notify::is-web-process-responsive",
4028 G_CALLBACK (is_web_process_responsive_changed_cb),
4029 NULL);
4030
4031 g_signal_connect (web_view, "mouse-target-changed",
4032 G_CALLBACK (mouse_target_changed_cb),
4033 NULL);
4034
4035 g_signal_connect (web_view, "notify::favicon",
4036 G_CALLBACK (icon_changed_cb),
4037 NULL);
4038
4039 g_signal_connect (web_view, "script-dialog",
4040 G_CALLBACK (script_dialog_cb),
4041 NULL);
4042
4043 g_signal_connect (web_view, "authenticate",
4044 G_CALLBACK (authenticate_cb),
4045 NULL);
4046
4047 g_signal_connect (web_view, "user-message-received",
4048 G_CALLBACK (user_message_received_cb),
4049 NULL);
4050
4051 g_signal_connect (web_view, "notify::scale-factor",
4052 G_CALLBACK (scale_factor_changed_cb),
4053 NULL);
4054
4055 g_signal_connect_object (shell, "password-form-focused",
4056 G_CALLBACK (password_form_focused_cb),
4057 web_view, 0);
4058
4059 g_signal_connect_object (shell, "allow-tls-certificate",
4060 G_CALLBACK (allow_tls_certificate_cb),
4061 web_view, 0);
4062
4063 g_signal_connect_object (shell, "allow-unsafe-browsing",
4064 G_CALLBACK (allow_unsafe_browsing_cb),
4065 web_view, 0);
4066 }
4067
4068 static void
ephy_web_view_class_init(EphyWebViewClass * klass)4069 ephy_web_view_class_init (EphyWebViewClass *klass)
4070 {
4071 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
4072 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
4073 WebKitWebViewClass *webkit_webview_class = WEBKIT_WEB_VIEW_CLASS (klass);
4074
4075 gobject_class->dispose = ephy_web_view_dispose;
4076 gobject_class->finalize = ephy_web_view_finalize;
4077 gobject_class->get_property = ephy_web_view_get_property;
4078 gobject_class->set_property = ephy_web_view_set_property;
4079 gobject_class->constructed = ephy_web_view_constructed;
4080
4081 widget_class->button_press_event = ephy_web_view_button_press_event;
4082 widget_class->key_press_event = ephy_web_view_key_press_event;
4083
4084 webkit_webview_class->run_file_chooser = ephy_web_view_run_file_chooser;
4085
4086 /**
4087 * EphyWebView:address:
4088 *
4089 * View's current address. This is a percent-encoded URI.
4090 **/
4091 obj_properties[PROP_ADDRESS] =
4092 g_param_spec_string ("address",
4093 "Address",
4094 "The view's address",
4095 "",
4096 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
4097
4098 /**
4099 * EphyWebView:typed-address:
4100 *
4101 * User typed address for the current view.
4102 **/
4103 obj_properties[PROP_TYPED_ADDRESS] =
4104 g_param_spec_string ("typed-address",
4105 "Typed Address",
4106 "The typed address",
4107 "",
4108 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
4109
4110 /**
4111 * EphyWebView:security-level:
4112 *
4113 * One of #EphySecurityLevel, determining view's current security level.
4114 **/
4115 obj_properties[PROP_SECURITY] =
4116 g_param_spec_enum ("security-level",
4117 "Security Level",
4118 "The view's security level",
4119 EPHY_TYPE_SECURITY_LEVEL,
4120 EPHY_SECURITY_LEVEL_TO_BE_DETERMINED,
4121 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
4122
4123 /**
4124 * EphyWebView:document-type:
4125 *
4126 * Document type determined for the view.
4127 **/
4128 obj_properties[PROP_DOCUMENT_TYPE] =
4129 g_param_spec_enum ("document-type",
4130 "Document Type",
4131 "The view's document type",
4132 EPHY_TYPE_WEB_VIEW_DOCUMENT_TYPE,
4133 EPHY_WEB_VIEW_DOCUMENT_HTML,
4134 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
4135
4136 /**
4137 * EphyWebView:navigation:
4138 *
4139 * View's navigation flags as #EphyWebViewNavigationFlags.
4140 **/
4141 obj_properties[PROP_NAVIGATION] =
4142 g_param_spec_flags ("navigation",
4143 "Navigation flags",
4144 "The view's navigation flags",
4145 EPHY_TYPE_WEB_VIEW_NAVIGATION_FLAGS,
4146 0,
4147 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
4148
4149 /**
4150 * EphyWebView:status-message:
4151 *
4152 * Statusbar message corresponding to this view.
4153 **/
4154 obj_properties[PROP_STATUS_MESSAGE] =
4155 g_param_spec_string ("status-message",
4156 "Status Message",
4157 "The view's statusbar message",
4158 NULL,
4159 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
4160
4161 /**
4162 * EphyWebView:link-message:
4163 *
4164 * ???
4165 **/
4166 obj_properties[PROP_LINK_MESSAGE] =
4167 g_param_spec_string ("link-message",
4168 "Link Message",
4169 "The view's link message",
4170 NULL,
4171 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
4172
4173 /**
4174 * EphyWebView:icon:
4175 *
4176 * View's favicon set by the loaded site.
4177 **/
4178 obj_properties[PROP_ICON] =
4179 g_param_spec_object ("icon",
4180 "Icon",
4181 "The view icon's",
4182 GDK_TYPE_PIXBUF,
4183 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
4184
4185 /**
4186 * EphyWebView:is-blank:
4187 *
4188 * Whether the view is showing the blank address.
4189 **/
4190 obj_properties[PROP_IS_BLANK] =
4191 g_param_spec_boolean ("is-blank",
4192 "Is blank",
4193 "If the EphyWebView is blank",
4194 FALSE,
4195 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
4196
4197 /**
4198 * EphyWebView:reader-mode:
4199 *
4200 * Whether the view is in reader mode.
4201 **/
4202 obj_properties[PROP_READER_MODE] =
4203 g_param_spec_boolean ("reader-mode",
4204 "Reader mode",
4205 "If the EphyWebView is in reader mode",
4206 FALSE,
4207 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
4208
4209 /**
4210 * EphyWebView:display-address:
4211 *
4212 * View's current display address.
4213 **/
4214 obj_properties[PROP_DISPLAY_ADDRESS] =
4215 g_param_spec_string ("display-address",
4216 "Display address",
4217 "The view's display address",
4218 "",
4219 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
4220
4221 /**
4222 * EphyWebView:entering-reader-mode:
4223 *
4224 * Whether the view is entering reader mode.
4225 **/
4226 obj_properties[PROP_ENTERING_READER_MODE] =
4227 g_param_spec_boolean ("entering-reader-mode",
4228 "Entering reader mode",
4229 "If the EphyWebView is entering reader mode",
4230 FALSE,
4231 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
4232
4233 g_object_class_install_properties (gobject_class, LAST_PROP, obj_properties);
4234
4235 /**
4236 * EphyWebView::new-window:
4237 * @view: the #EphyWebView that received the signal
4238 * @new_view: the newly opened #EphyWebView
4239 *
4240 * The ::new-window signal is emitted after a new window has been opened by
4241 * the view. For example, when a JavaScript popup window is opened.
4242 **/
4243 g_signal_new ("new-window",
4244 EPHY_TYPE_WEB_VIEW,
4245 G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST,
4246 0, NULL, NULL, NULL,
4247 G_TYPE_NONE,
4248 1,
4249 GTK_TYPE_WIDGET);
4250
4251 /**
4252 * EphyWebView::search-key-press:
4253 * @view: the #EphyWebView that received the signal
4254 * @event: the #GdkEventKey which triggered this signal
4255 *
4256 * The ::search-key-press signal is emitted for keypresses which
4257 * should be used for find implementations.
4258 **/
4259 g_signal_new ("search-key-press",
4260 EPHY_TYPE_WEB_VIEW,
4261 G_SIGNAL_RUN_LAST,
4262 0, g_signal_accumulator_true_handled, NULL, NULL,
4263 G_TYPE_BOOLEAN,
4264 1,
4265 GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
4266
4267 /**
4268 * EphyWebView::download-only-load:
4269 * @view: the #EphyWebView that received the signal
4270 *
4271 * The ::download-only-load signal is emitted when the @view has its main load
4272 * replaced by a download, and that is the only reason why the @view has been created.
4273 **/
4274 g_signal_new ("download-only-load",
4275 EPHY_TYPE_WEB_VIEW,
4276 G_SIGNAL_RUN_FIRST,
4277 0, NULL, NULL, NULL,
4278 G_TYPE_NONE,
4279 0);
4280 }
4281
4282 static void
ephy_web_view_ucm_add_custom_scripts(WebKitUserContentManager * ucm)4283 ephy_web_view_ucm_add_custom_scripts (WebKitUserContentManager *ucm)
4284 {
4285 WebKitUserScript *script;
4286 g_autoptr (GBytes) youtube_js = NULL;
4287 g_auto (GStrv) allow_list = NULL;
4288 g_autoptr (GError) error = NULL;
4289
4290 youtube_js = g_resources_lookup_data ("/org/gnome/epiphany/adguard/youtube.js", 0, &error);
4291 if (!youtube_js) {
4292 g_warning ("Failed to load youtube.js from AdGuard: %s", error->message);
4293 return;
4294 }
4295
4296 allow_list = g_new (char *, 2);
4297 allow_list[0] = g_strdup ("https://*.youtube.com/*");
4298 allow_list[1] = NULL;
4299
4300 script = webkit_user_script_new (g_bytes_get_data (youtube_js, NULL),
4301 WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
4302 WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END,
4303 (const char * const *)allow_list,
4304 NULL);
4305 webkit_user_content_manager_add_script (ucm, script);
4306 }
4307
4308 /**
4309 * ephy_web_view_new:
4310 *
4311 * Equivalent to g_object_new() but returns an #GtkWidget so you don't have
4312 * to cast it when dealing with most code.
4313 *
4314 * Return value: the newly created #EphyWebView widget
4315 **/
4316 GtkWidget *
ephy_web_view_new(void)4317 ephy_web_view_new (void)
4318 {
4319 EphyEmbedShell *shell = ephy_embed_shell_get_default ();
4320 WebKitUserContentManager *ucm = webkit_user_content_manager_new ();
4321
4322 ephy_embed_shell_register_ucm_handler (shell, ucm);
4323 ephy_embed_prefs_register_ucm (ucm);
4324
4325 ephy_web_view_ucm_add_custom_scripts (ucm);
4326
4327 return g_object_new (EPHY_TYPE_WEB_VIEW,
4328 "web-context", ephy_embed_shell_get_web_context (shell),
4329 "user-content-manager", ucm,
4330 "settings", ephy_embed_prefs_get_settings (),
4331 "is-controlled-by-automation", ephy_embed_shell_get_mode (shell) == EPHY_EMBED_SHELL_MODE_AUTOMATION,
4332 NULL);
4333 }
4334
4335 GtkWidget *
ephy_web_view_new_with_related_view(WebKitWebView * related_view)4336 ephy_web_view_new_with_related_view (WebKitWebView *related_view)
4337 {
4338 EphyEmbedShell *shell = ephy_embed_shell_get_default ();
4339 WebKitUserContentManager *ucm = webkit_user_content_manager_new ();
4340
4341 ephy_web_view_ucm_add_custom_scripts (ucm);
4342
4343 ephy_embed_shell_register_ucm_handler (shell, ucm);
4344 ephy_embed_prefs_register_ucm (ucm);
4345
4346 return g_object_new (EPHY_TYPE_WEB_VIEW,
4347 "related-view", related_view,
4348 "user-content-manager", ucm,
4349 "settings", ephy_embed_prefs_get_settings (),
4350 NULL);
4351 }
4352
4353 guint64
ephy_web_view_get_uid(EphyWebView * web_view)4354 ephy_web_view_get_uid (EphyWebView *web_view)
4355 {
4356 return web_view->uid;
4357 }
4358