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 &lt;input&gt; or &lt;textarea&gt;
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