1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
2 /*
3  *  Copyright © 2007 Xan Lopez
4  *  Copyright © 2008 Jan Alonzo
5  *  Copyright © 2009 Gustavo Noronha Silva
6  *  Copyright © 2009 Igalia S.L.
7  *  Copyright © 2009 Collabora Ltd.
8  *
9  *  This file is part of Epiphany.
10  *
11  *  Epiphany is free software: you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License as published by
13  *  the Free Software Foundation, either version 3 of the License, or
14  *  (at your option) any later version.
15  *
16  *  Epiphany is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
23  */
24 
25 #include "config.h"
26 #include "ephy-embed.h"
27 
28 #include "ephy-debug.h"
29 #include "ephy-embed-prefs.h"
30 #include "ephy-embed-shell.h"
31 #include "ephy-embed-utils.h"
32 #include "ephy-find-toolbar.h"
33 #include "ephy-notification-container.h"
34 #include "ephy-prefs.h"
35 #include "ephy-settings.h"
36 #include "ephy-string.h"
37 #include "ephy-web-view.h"
38 #include "nautilus-floating-bar.h"
39 
40 #include <glib/gi18n.h>
41 #include <webkit2/webkit2.h>
42 
43 static void     ephy_embed_constructed (GObject *object);
44 static void     ephy_embed_restored_window_cb (EphyEmbedShell *shell,
45                                                EphyEmbed      *embed);
46 
47 #define EPHY_EMBED_STATUSBAR_TAB_MESSAGE_CONTEXT_DESCRIPTION "tab_message"
48 #define MAX_TITLE_LENGTH 512 /* characters */
49 
50 typedef struct {
51   gchar *text;
52   guint context_id;
53   guint message_id;
54 } EphyEmbedStatusbarMsg;
55 
56 struct _EphyEmbed {
57   GtkBox parent_instance;
58 
59   EphyFindToolbar *find_toolbar;
60   GtkBox *top_widgets_vbox;
61   WebKitWebView *web_view;
62   GSList *destroy_on_transition_list;
63   GtkWidget *overlay;
64   GtkWidget *floating_bar;
65   GtkWidget *progress;
66   GtkWidget *fullscreen_message_label;
67 
68   char *title;
69   WebKitURIRequest *delayed_request;
70   WebKitWebViewSessionState *delayed_state;
71   guint delayed_request_source_id;
72 
73   GSList *messages;
74   GSList *keys;
75 
76   guint seq_context_id;
77   guint seq_message_id;
78 
79   guint tab_message_id;
80   guint pop_statusbar_later_source_id;
81 
82   guint fullscreen_message_id;
83 
84   guint clear_progress_source_id;
85 
86   gulong status_handler_id;
87   gulong progress_update_handler_id;
88   gboolean inspector_loaded;
89   gboolean progress_bar_enabled;
90 };
91 
92 G_DEFINE_TYPE (EphyEmbed, ephy_embed, GTK_TYPE_BOX)
93 
94 enum {
95   PROP_0,
96   PROP_WEB_VIEW,
97   PROP_TITLE,
98   PROP_PROGRESS_BAR_ENABLED,
99   LAST_PROP
100 };
101 
102 static GParamSpec *obj_properties[LAST_PROP];
103 
104 /* Portions of the following code based on GTK+.
105  * License block as follows:
106  *
107  * GTK - The GIMP Toolkit
108  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
109  * GtkStatusbar Copyright (C) 1998 Shawn T. Amundson
110  *
111  * This library is free software; you can redistribute it and/or
112  * modify it under the terms of the GNU Lesser General Public
113  * License as published by the Free Software Foundation; either
114  * version 2 of the License, or (at your option) any later version.
115  *
116  * This library is distributed in the hope that it will be useful,
117  * but WITHOUT ANY WARRANTY; without even the implied warranty of
118  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
119  * Lesser General Public License for more details.
120  *
121  * You should have received a copy of the GNU Lesser General Public
122  * License along with this library; if not, write to the
123  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
124  * Boston, MA 02111-1307, USA.
125  *
126  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
127  * file for a list of people on the GTK+ Team.  See the ChangeLog
128  * files for a list of changes.  These files are distributed with
129  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
130  *
131  */
132 
133 static guint
ephy_embed_statusbar_get_context_id(EphyEmbed * embed,const char * context_description)134 ephy_embed_statusbar_get_context_id (EphyEmbed  *embed,
135                                      const char *context_description)
136 {
137   char *string;
138   guint id;
139 
140   g_assert (EPHY_IS_EMBED (embed));
141   g_assert (context_description != NULL);
142 
143   /* we need to preserve namespaces on object datas */
144   string = g_strconcat ("ephy-embed-status-bar-context:", context_description, NULL);
145 
146   id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (embed), string));
147   if (id == 0) {
148     id = embed->seq_context_id++;
149     g_object_set_data_full (G_OBJECT (embed), string, GUINT_TO_POINTER (id), NULL);
150     embed->keys = g_slist_prepend (embed->keys, string);
151   } else
152     g_free (string);
153 
154   return id;
155 }
156 
157 static void
ephy_embed_set_statusbar_label(EphyEmbed * embed,const char * label)158 ephy_embed_set_statusbar_label (EphyEmbed  *embed,
159                                 const char *label)
160 {
161   nautilus_floating_bar_set_primary_label (NAUTILUS_FLOATING_BAR (embed->floating_bar), label);
162 
163   if (label == NULL || label[0] == '\0') {
164     gtk_widget_hide (embed->floating_bar);
165     gtk_widget_set_halign (embed->floating_bar, GTK_ALIGN_START);
166   } else
167     gtk_widget_show (embed->floating_bar);
168 }
169 
170 static void
ephy_embed_statusbar_update(EphyEmbed * embed,const char * text)171 ephy_embed_statusbar_update (EphyEmbed  *embed,
172                              const char *text)
173 {
174   g_assert (EPHY_IS_EMBED (embed));
175 
176   ephy_embed_set_statusbar_label (embed, text);
177 }
178 
179 static guint
ephy_embed_statusbar_push(EphyEmbed * embed,guint context_id,const char * text)180 ephy_embed_statusbar_push (EphyEmbed  *embed,
181                            guint       context_id,
182                            const char *text)
183 {
184   EphyEmbedStatusbarMsg *msg;
185 
186   g_assert (EPHY_IS_EMBED (embed));
187   g_assert (context_id != 0);
188   g_assert (text != NULL);
189 
190   msg = g_new (EphyEmbedStatusbarMsg, 1);
191   msg->text = g_strdup (text);
192   msg->context_id = context_id;
193   msg->message_id = embed->seq_message_id++;
194 
195   embed->messages = g_slist_prepend (embed->messages, msg);
196 
197   ephy_embed_statusbar_update (embed, text);
198 
199   return msg->message_id;
200 }
201 
202 /* End of code based on GTK+ GtkStatusbar. */
203 
204 static void
ephy_embed_statusbar_pop(EphyEmbed * embed,guint context_id)205 ephy_embed_statusbar_pop (EphyEmbed *embed,
206                           guint      context_id)
207 {
208   EphyEmbedStatusbarMsg *msg;
209   GSList *list;
210 
211   g_assert (EPHY_IS_EMBED (embed));
212   g_assert (context_id != 0);
213 
214   for (list = embed->messages; list; list = list->next) {
215     msg = list->data;
216 
217     if (msg->context_id == context_id) {
218       embed->messages = g_slist_remove_link (embed->messages, list);
219       g_free (msg->text);
220       g_free (msg);
221       g_slist_free_1 (list);
222       break;
223     }
224   }
225 
226   msg = embed->messages ? embed->messages->data : NULL;
227   ephy_embed_statusbar_update (embed, msg ? msg->text : NULL);
228 }
229 
230 static void
remove_from_destroy_list_cb(GtkWidget * widget,EphyEmbed * embed)231 remove_from_destroy_list_cb (GtkWidget *widget,
232                              EphyEmbed *embed)
233 {
234   GSList *list;
235 
236   list = embed->destroy_on_transition_list;
237   list = g_slist_remove (list, widget);
238   embed->destroy_on_transition_list = list;
239 }
240 
241 static void
ephy_embed_destroy_top_widgets(EphyEmbed * embed)242 ephy_embed_destroy_top_widgets (EphyEmbed *embed)
243 {
244   GSList *iter;
245 
246   for (iter = embed->destroy_on_transition_list; iter; iter = iter->next) {
247     g_signal_handlers_disconnect_by_func (iter->data, remove_from_destroy_list_cb, embed);
248     gtk_widget_destroy (GTK_WIDGET (iter->data));
249   }
250 
251   embed->destroy_on_transition_list = NULL;
252 }
253 
254 static void
ephy_embed_set_title(EphyEmbed * embed,const char * title)255 ephy_embed_set_title (EphyEmbed  *embed,
256                       const char *title)
257 {
258   char *new_title;
259 
260   new_title = g_strdup (title);
261   if (new_title == NULL || g_strstrip (new_title)[0] == '\0') {
262     const char *address;
263 
264     g_free (new_title);
265     new_title = NULL;
266 
267     address = ephy_web_view_get_address (EPHY_WEB_VIEW (embed->web_view));
268     if (address && strcmp (address, "about:blank") != 0)
269       new_title = ephy_embed_utils_get_title_from_address (address);
270 
271     if (new_title == NULL || new_title[0] == '\0') {
272       g_free (new_title);
273       new_title = g_strdup (_(BLANK_PAGE_TITLE));
274     }
275   }
276 
277   g_free (embed->title);
278   embed->title = ephy_string_shorten (new_title, MAX_TITLE_LENGTH);
279 
280   g_object_notify_by_pspec (G_OBJECT (embed), obj_properties[PROP_TITLE]);
281 }
282 
283 static void
web_view_title_changed_cb(WebKitWebView * web_view,GParamSpec * spec,EphyEmbed * embed)284 web_view_title_changed_cb (WebKitWebView *web_view,
285                            GParamSpec    *spec,
286                            EphyEmbed     *embed)
287 {
288   ephy_embed_set_title (embed, webkit_web_view_get_title (web_view));
289 }
290 
291 static void
load_changed_cb(WebKitWebView * web_view,WebKitLoadEvent load_event,EphyEmbed * embed)292 load_changed_cb (WebKitWebView   *web_view,
293                  WebKitLoadEvent  load_event,
294                  EphyEmbed       *embed)
295 {
296   switch (load_event) {
297     case WEBKIT_LOAD_COMMITTED:
298       ephy_embed_destroy_top_widgets (embed);
299       break;
300     case WEBKIT_LOAD_FINISHED: {
301       const char *title = webkit_web_view_get_title (web_view);
302       if (ephy_web_view_get_is_blank (EPHY_WEB_VIEW (web_view)) || !title || !*title)
303         ephy_embed_set_title (embed, NULL);
304       break;
305     }
306     case WEBKIT_LOAD_STARTED:
307     case WEBKIT_LOAD_REDIRECTED:
308     default:
309       break;
310   }
311 }
312 
313 static void
ephy_embed_grab_focus(GtkWidget * widget)314 ephy_embed_grab_focus (GtkWidget *widget)
315 {
316   GtkWidget *child;
317 
318   child = GTK_WIDGET (ephy_embed_get_web_view (EPHY_EMBED (widget)));
319 
320   if (child)
321     gtk_widget_grab_focus (child);
322 }
323 
324 
325 static gboolean
fullscreen_message_label_hide(EphyEmbed * embed)326 fullscreen_message_label_hide (EphyEmbed *embed)
327 {
328   if (embed->fullscreen_message_id) {
329     gtk_widget_hide (embed->fullscreen_message_label);
330     g_source_remove (embed->fullscreen_message_id);
331     embed->fullscreen_message_id = 0;
332   }
333 
334   return FALSE;
335 }
336 
337 void
ephy_embed_entering_fullscreen(EphyEmbed * embed)338 ephy_embed_entering_fullscreen (EphyEmbed *embed)
339 {
340   if (!g_settings_get_boolean (EPHY_SETTINGS_LOCKDOWN, EPHY_PREFS_LOCKDOWN_FULLSCREEN)) {
341     gtk_widget_show (embed->fullscreen_message_label);
342 
343     g_clear_handle_id (&embed->fullscreen_message_id, g_source_remove);
344     embed->fullscreen_message_id = g_timeout_add_seconds (5,
345                                                           (GSourceFunc)fullscreen_message_label_hide,
346                                                           embed);
347     g_source_set_name_by_id (embed->fullscreen_message_id, "[epiphany] fullscreen_message_label_hide");
348   }
349 }
350 
351 void
ephy_embed_leaving_fullscreen(EphyEmbed * embed)352 ephy_embed_leaving_fullscreen (EphyEmbed *embed)
353 {
354   fullscreen_message_label_hide (embed);
355 }
356 
357 static void
ephy_embed_dispose(GObject * object)358 ephy_embed_dispose (GObject *object)
359 {
360   EphyEmbed *embed = EPHY_EMBED (object);
361 
362   g_clear_handle_id (&embed->pop_statusbar_later_source_id, g_source_remove);
363   g_clear_handle_id (&embed->clear_progress_source_id, g_source_remove);
364   g_clear_handle_id (&embed->delayed_request_source_id, g_source_remove);
365   g_clear_handle_id (&embed->fullscreen_message_id, g_source_remove);
366 
367   g_clear_signal_handler (&embed->status_handler_id, embed->web_view);
368   g_clear_signal_handler (&embed->progress_update_handler_id, embed->web_view);
369 
370   g_clear_object (&embed->delayed_request);
371   g_clear_pointer (&embed->delayed_state, webkit_web_view_session_state_unref);
372 
373   G_OBJECT_CLASS (ephy_embed_parent_class)->dispose (object);
374 }
375 
376 static void
ephy_embed_finalize(GObject * object)377 ephy_embed_finalize (GObject *object)
378 {
379   EphyEmbed *embed = EPHY_EMBED (object);
380   EphyEmbedShell *shell = ephy_embed_shell_get_default ();
381   GSList *list;
382 
383   g_signal_handlers_disconnect_by_func (shell, ephy_embed_restored_window_cb, embed);
384 
385   list = embed->destroy_on_transition_list;
386   for (; list; list = list->next) {
387     GtkWidget *widget = GTK_WIDGET (list->data);
388     g_signal_handlers_disconnect_by_func (widget, remove_from_destroy_list_cb, embed);
389   }
390   g_slist_free (embed->destroy_on_transition_list);
391 
392   for (list = embed->messages; list; list = list->next) {
393     EphyEmbedStatusbarMsg *msg;
394 
395     msg = list->data;
396     g_free (msg->text);
397     g_free (msg);
398   }
399 
400   g_slist_free (embed->messages);
401   embed->messages = NULL;
402 
403   for (list = embed->keys; list; list = list->next)
404     g_free (list->data);
405 
406   g_slist_free (embed->keys);
407   embed->keys = NULL;
408 
409   g_free (embed->title);
410 
411   G_OBJECT_CLASS (ephy_embed_parent_class)->finalize (object);
412 }
413 
414 static void
ephy_embed_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)415 ephy_embed_set_property (GObject      *object,
416                          guint         prop_id,
417                          const GValue *value,
418                          GParamSpec   *pspec)
419 {
420   EphyEmbed *embed = EPHY_EMBED (object);
421 
422   switch (prop_id) {
423     case PROP_WEB_VIEW:
424       embed->web_view = g_value_get_object (value);
425       break;
426     case PROP_TITLE:
427       ephy_embed_set_title (embed, g_value_get_string (value));
428       break;
429     case PROP_PROGRESS_BAR_ENABLED:
430       embed->progress_bar_enabled = g_value_get_boolean (value);
431       break;
432     default:
433       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
434       break;
435   }
436 }
437 
438 static void
ephy_embed_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)439 ephy_embed_get_property (GObject    *object,
440                          guint       prop_id,
441                          GValue     *value,
442                          GParamSpec *pspec)
443 {
444   EphyEmbed *embed = EPHY_EMBED (object);
445 
446   switch (prop_id) {
447     case PROP_WEB_VIEW:
448       g_value_set_object (value, ephy_embed_get_web_view (embed));
449       break;
450     case PROP_TITLE:
451       g_value_set_string (value, ephy_embed_get_title (embed));
452       break;
453     case PROP_PROGRESS_BAR_ENABLED:
454       g_value_set_boolean (value, embed->progress_bar_enabled);
455       break;
456     default:
457       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
458       break;
459   }
460 }
461 
462 static void
ephy_embed_find_toolbar_close_cb(EphyFindToolbar * toolbar,EphyEmbed * embed)463 ephy_embed_find_toolbar_close_cb (EphyFindToolbar *toolbar,
464                                   EphyEmbed       *embed)
465 {
466   ephy_find_toolbar_close (embed->find_toolbar);
467 
468   gtk_widget_grab_focus (GTK_WIDGET (embed));
469 }
470 
471 static void
ephy_embed_class_init(EphyEmbedClass * klass)472 ephy_embed_class_init (EphyEmbedClass *klass)
473 {
474   GObjectClass *object_class = (GObjectClass *)klass;
475   GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
476 
477   object_class->constructed = ephy_embed_constructed;
478   object_class->finalize = ephy_embed_finalize;
479   object_class->dispose = ephy_embed_dispose;
480   object_class->set_property = ephy_embed_set_property;
481   object_class->get_property = ephy_embed_get_property;
482   widget_class->grab_focus = ephy_embed_grab_focus;
483 
484   obj_properties[PROP_WEB_VIEW] =
485     g_param_spec_object ("web-view",
486                          "Web View",
487                          "The WebView contained in the embed",
488                          EPHY_TYPE_WEB_VIEW,
489                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
490 
491   obj_properties[PROP_TITLE] =
492     g_param_spec_string ("title",
493                          "Title",
494                          "The embed's title",
495                          NULL,
496                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
497 
498   obj_properties[PROP_PROGRESS_BAR_ENABLED] =
499     g_param_spec_boolean ("progress-bar-enabled",
500                           "Progress bar",
501                           "Whether to show progress bar within embed",
502                           TRUE,
503                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
504 
505   g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
506 }
507 
508 static gboolean
ephy_embed_attach_inspector_cb(WebKitWebInspector * inspector,EphyEmbed * embed)509 ephy_embed_attach_inspector_cb (WebKitWebInspector *inspector,
510                                 EphyEmbed          *embed)
511 {
512   embed->inspector_loaded = TRUE;
513 
514   return FALSE;
515 }
516 
517 static gboolean
ephy_embed_close_inspector_cb(WebKitWebInspector * inspector,EphyEmbed * embed)518 ephy_embed_close_inspector_cb (WebKitWebInspector *inspector,
519                                EphyEmbed          *embed)
520 {
521   embed->inspector_loaded = FALSE;
522 
523   return TRUE;
524 }
525 
526 static void
ephy_embed_set_fullscreen_message(EphyEmbed * embed,gboolean is_html5_fullscreen)527 ephy_embed_set_fullscreen_message (EphyEmbed *embed,
528                                    gboolean   is_html5_fullscreen)
529 {
530   char *message;
531 
532   /* Translators: 'ESC' and 'F11' are keyboard keys. */
533   message = g_strdup_printf (_("Press %s to exit fullscreen"), is_html5_fullscreen ? _("ESC") : _("F11"));
534   gtk_label_set_text (GTK_LABEL (embed->fullscreen_message_label),
535                       message);
536   g_free (message);
537 }
538 
539 static gboolean
entering_fullscreen_cb(WebKitWebView * web_view,EphyEmbed * embed)540 entering_fullscreen_cb (WebKitWebView *web_view,
541                         EphyEmbed     *embed)
542 {
543   ephy_embed_set_fullscreen_message (embed, TRUE);
544   return FALSE;
545 }
546 
547 static gboolean
leaving_fullscreen_cb(WebKitWebView * web_view,EphyEmbed * embed)548 leaving_fullscreen_cb (WebKitWebView *web_view,
549                        EphyEmbed     *embed)
550 {
551   ephy_embed_set_fullscreen_message (embed, FALSE);
552   return FALSE;
553 }
554 
555 static gboolean
pop_statusbar_later_cb(gpointer data)556 pop_statusbar_later_cb (gpointer data)
557 {
558   EphyEmbed *embed = EPHY_EMBED (data);
559 
560   ephy_embed_statusbar_pop (embed, embed->tab_message_id);
561   embed->pop_statusbar_later_source_id = 0;
562   return FALSE;
563 }
564 
565 static void
status_message_notify_cb(EphyWebView * view,GParamSpec * pspec,EphyEmbed * embed)566 status_message_notify_cb (EphyWebView *view,
567                           GParamSpec  *pspec,
568                           EphyEmbed   *embed)
569 {
570   const char *message;
571 
572   message = ephy_web_view_get_status_message (view);
573 
574   if (message) {
575     g_clear_handle_id (&embed->pop_statusbar_later_source_id, g_source_remove);
576     ephy_embed_statusbar_pop (embed, embed->tab_message_id);
577     ephy_embed_statusbar_push (embed, embed->tab_message_id, message);
578   } else {
579     /* A short timeout before hiding the statusbar ensures that while moving
580      *  over a series of links, the overlay widget doesn't flicker on and off. */
581     if (embed->pop_statusbar_later_source_id == 0) {
582       embed->pop_statusbar_later_source_id = g_timeout_add (250, pop_statusbar_later_cb, embed);
583       g_source_set_name_by_id (embed->pop_statusbar_later_source_id, "[epiphany] pop_statusbar_later_cb");
584     }
585   }
586 }
587 
588 static gboolean
clear_progress_cb(EphyEmbed * embed)589 clear_progress_cb (EphyEmbed *embed)
590 {
591   gtk_widget_hide (embed->progress);
592   embed->clear_progress_source_id = 0;
593 
594   return FALSE;
595 }
596 
597 static void
progress_update(EphyWebView * view,GParamSpec * pspec,EphyEmbed * embed)598 progress_update (EphyWebView *view,
599                  GParamSpec  *pspec,
600                  EphyEmbed   *embed)
601 {
602   gdouble progress;
603   gboolean loading;
604   const char *uri;
605 
606   g_clear_handle_id (&embed->clear_progress_source_id, g_source_remove);
607 
608   uri = webkit_web_view_get_uri (embed->web_view);
609   if (!uri || g_str_has_prefix (uri, "ephy-about:") ||
610       g_str_has_prefix (uri, "about:")) {
611     gtk_widget_hide (embed->progress);
612     return;
613   }
614 
615   progress = webkit_web_view_get_estimated_load_progress (embed->web_view);
616   loading = ephy_web_view_is_loading (EPHY_WEB_VIEW (embed->web_view));
617 
618   if (progress == 1.0 || !loading) {
619     embed->clear_progress_source_id = g_timeout_add (500,
620                                                      (GSourceFunc)clear_progress_cb,
621                                                      embed);
622     g_source_set_name_by_id (embed->clear_progress_source_id, "[epiphany] clear_progress_cb");
623   } else
624     gtk_widget_show (embed->progress);
625 
626   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (embed->progress),
627                                  (loading || progress == 1.0) ? progress : 0.0);
628 }
629 
630 static gboolean
load_delayed_request_if_mapped(gpointer user_data)631 load_delayed_request_if_mapped (gpointer user_data)
632 {
633   EphyEmbed *embed = EPHY_EMBED (user_data);
634   EphyWebView *web_view;
635   WebKitBackForwardListItem *item;
636 
637   embed->delayed_request_source_id = 0;
638 
639   if (!gtk_widget_get_mapped (GTK_WIDGET (embed)))
640     return G_SOURCE_REMOVE;
641 
642   web_view = ephy_embed_get_web_view (embed);
643   if (embed->delayed_state)
644     webkit_web_view_restore_session_state (WEBKIT_WEB_VIEW (web_view), embed->delayed_state);
645 
646   item = webkit_back_forward_list_get_current_item (webkit_web_view_get_back_forward_list (WEBKIT_WEB_VIEW (web_view)));
647   if (item)
648     webkit_web_view_go_to_back_forward_list_item (WEBKIT_WEB_VIEW (web_view), item);
649   else
650     ephy_web_view_load_request (web_view, embed->delayed_request);
651 
652   g_clear_object (&embed->delayed_request);
653   g_clear_pointer (&embed->delayed_state, webkit_web_view_session_state_unref);
654 
655   return G_SOURCE_REMOVE;
656 }
657 
658 static void
ephy_embed_maybe_load_delayed_request(EphyEmbed * embed)659 ephy_embed_maybe_load_delayed_request (EphyEmbed *embed)
660 {
661   if (!embed->delayed_request || embed->delayed_request_source_id != 0)
662     return;
663 
664   /* Add a very small delay before loading the request, so that if the user
665    * is scrolling rapidly through a bunch of delayed tabs, we don't start
666    * loading them all.
667    */
668   embed->delayed_request_source_id = g_timeout_add (300, load_delayed_request_if_mapped, embed);
669   g_source_set_name_by_id (embed->delayed_request_source_id, "[epiphany] load_delayed_request_if_mapped");
670 }
671 
672 static void
ephy_embed_restored_window_cb(EphyEmbedShell * shell,EphyEmbed * embed)673 ephy_embed_restored_window_cb (EphyEmbedShell *shell,
674                                EphyEmbed      *embed)
675 {
676   if (!gtk_widget_get_mapped (GTK_WIDGET (embed)))
677     return;
678 
679   ephy_embed_maybe_load_delayed_request (embed);
680 }
681 
682 static void
ephy_embed_mapped_cb(GtkWidget * widget,gpointer data)683 ephy_embed_mapped_cb (GtkWidget *widget,
684                       gpointer   data)
685 {
686   ephy_embed_maybe_load_delayed_request ((EphyEmbed *)widget);
687 }
688 
689 static gboolean
on_enter_notify_event(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)690 on_enter_notify_event (GtkWidget        *widget,
691                        GdkEventCrossing *event,
692                        gpointer          user_data)
693 {
694   EphyEmbed *embed = EPHY_EMBED (user_data);
695 
696   if (event->window != gtk_widget_get_window (embed->floating_bar))
697     return GDK_EVENT_PROPAGATE;
698 
699   if (gtk_widget_get_halign (embed->floating_bar) == GTK_ALIGN_START)
700     gtk_widget_set_halign (embed->floating_bar, GTK_ALIGN_END);
701   else
702     gtk_widget_set_halign (embed->floating_bar, GTK_ALIGN_START);
703 
704   gtk_widget_queue_allocate (embed->overlay);
705 
706   return GDK_EVENT_PROPAGATE;
707 }
708 
709 static void
ephy_embed_constructed(GObject * object)710 ephy_embed_constructed (GObject *object)
711 {
712   EphyEmbed *embed = (EphyEmbed *)object;
713   EphyEmbedShell *shell = ephy_embed_shell_get_default ();
714   WebKitWebInspector *inspector;
715 
716   g_signal_connect (shell, "window-restored",
717                     G_CALLBACK (ephy_embed_restored_window_cb), embed);
718 
719   g_signal_connect (embed, "map",
720                     G_CALLBACK (ephy_embed_mapped_cb), NULL);
721 
722   /* Skeleton */
723   embed->overlay = gtk_overlay_new ();
724 
725   gtk_widget_add_events (embed->overlay,
726                          GDK_ENTER_NOTIFY_MASK |
727                          GDK_LEAVE_NOTIFY_MASK);
728   gtk_container_add (GTK_CONTAINER (embed->overlay), GTK_WIDGET (embed->web_view));
729 
730   /* Floating message popup for fullscreen mode. */
731   embed->fullscreen_message_label = gtk_label_new (NULL);
732   gtk_widget_set_name (embed->fullscreen_message_label, "fullscreen-popup");
733   gtk_widget_set_halign (embed->fullscreen_message_label, GTK_ALIGN_CENTER);
734   gtk_widget_set_valign (embed->fullscreen_message_label, GTK_ALIGN_CENTER);
735   gtk_widget_set_no_show_all (embed->fullscreen_message_label, TRUE);
736   gtk_overlay_add_overlay (GTK_OVERLAY (embed->overlay), embed->fullscreen_message_label);
737   ephy_embed_set_fullscreen_message (embed, FALSE);
738 
739   /* statusbar is hidden by default */
740   embed->floating_bar = nautilus_floating_bar_new (NULL, NULL, FALSE);
741   gtk_widget_set_halign (embed->floating_bar, GTK_ALIGN_START);
742   gtk_widget_set_valign (embed->floating_bar, GTK_ALIGN_END);
743   gtk_widget_set_no_show_all (embed->floating_bar, TRUE);
744   g_signal_connect_object (embed->overlay, "enter-notify-event", G_CALLBACK (on_enter_notify_event), embed, 0);
745 
746   gtk_overlay_add_overlay (GTK_OVERLAY (embed->overlay), embed->floating_bar);
747 
748   if (embed->progress_bar_enabled) {
749     embed->progress = gtk_progress_bar_new ();
750     gtk_style_context_add_class (gtk_widget_get_style_context (embed->progress),
751                                  GTK_STYLE_CLASS_OSD);
752     gtk_widget_set_halign (embed->progress, GTK_ALIGN_FILL);
753     gtk_widget_set_valign (embed->progress, GTK_ALIGN_START);
754     gtk_overlay_add_overlay (GTK_OVERLAY (embed->overlay), embed->progress);
755   }
756 
757   embed->find_toolbar = ephy_find_toolbar_new (embed->web_view);
758   g_signal_connect (embed->find_toolbar, "close",
759                     G_CALLBACK (ephy_embed_find_toolbar_close_cb),
760                     embed);
761 
762   gtk_box_pack_start (GTK_BOX (embed),
763                       GTK_WIDGET (embed->find_toolbar),
764                       FALSE, FALSE, 0);
765 
766   if (embed->progress_bar_enabled)
767     embed->progress_update_handler_id = g_signal_connect (embed->web_view, "notify::estimated-load-progress",
768                                                           G_CALLBACK (progress_update), object);
769 
770   gtk_box_pack_start (GTK_BOX (embed),
771                       GTK_WIDGET (embed->top_widgets_vbox),
772                       FALSE, FALSE, 0);
773   gtk_box_pack_start (GTK_BOX (embed), embed->overlay, TRUE, TRUE, 0);
774 
775   gtk_widget_show (GTK_WIDGET (embed->top_widgets_vbox));
776   gtk_widget_show (GTK_WIDGET (embed->web_view));
777   gtk_widget_show_all (embed->overlay);
778 
779   g_object_connect (embed->web_view,
780                     "signal::notify::title", G_CALLBACK (web_view_title_changed_cb), embed,
781                     "signal::load-changed", G_CALLBACK (load_changed_cb), embed,
782                     "signal::enter-fullscreen", G_CALLBACK (entering_fullscreen_cb), embed,
783                     "signal::leave-fullscreen", G_CALLBACK (leaving_fullscreen_cb), embed,
784                     NULL);
785 
786   embed->status_handler_id = g_signal_connect (embed->web_view, "notify::status-message",
787                                                G_CALLBACK (status_message_notify_cb),
788                                                embed);
789 
790   /* The inspector */
791   inspector = webkit_web_view_get_inspector (embed->web_view);
792 
793   g_signal_connect (inspector, "attach",
794                     G_CALLBACK (ephy_embed_attach_inspector_cb),
795                     embed);
796   g_signal_connect (inspector, "closed",
797                     G_CALLBACK (ephy_embed_close_inspector_cb),
798                     embed);
799 
800   if (webkit_web_view_is_controlled_by_automation (embed->web_view)) {
801     GtkWidget *info_bar;
802     GtkWidget *label;
803 
804     info_bar = gtk_info_bar_new ();
805     gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_INFO);
806     /* Translators: this means WebDriver control. */
807     label = gtk_label_new (_("Web is being controlled by automation."));
808     gtk_box_pack_start (GTK_BOX (gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar))), label, FALSE, FALSE, 0);
809     gtk_widget_show (label);
810 
811     ephy_embed_add_top_widget (embed, info_bar, EPHY_EMBED_TOP_WIDGET_POLICY_RETAIN_ON_TRANSITION);
812     gtk_widget_show (info_bar);
813   }
814 }
815 
816 static void
ephy_embed_init(EphyEmbed * embed)817 ephy_embed_init (EphyEmbed *embed)
818 {
819   gtk_orientable_set_orientation (GTK_ORIENTABLE (embed),
820                                   GTK_ORIENTATION_VERTICAL);
821 
822   embed->top_widgets_vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
823   embed->seq_context_id = 1;
824   embed->seq_message_id = 1;
825   embed->tab_message_id = ephy_embed_statusbar_get_context_id (embed, EPHY_EMBED_STATUSBAR_TAB_MESSAGE_CONTEXT_DESCRIPTION);
826   embed->inspector_loaded = FALSE;
827 }
828 
829 /**
830  * ephy_embed_get_web_view:
831  * @embed: and #EphyEmbed
832  *
833  * Returns the #EphyWebView wrapped by @embed.
834  *
835  * Returns: (transfer none): an #EphyWebView
836  **/
837 EphyWebView *
ephy_embed_get_web_view(EphyEmbed * embed)838 ephy_embed_get_web_view (EphyEmbed *embed)
839 {
840   g_assert (EPHY_IS_EMBED (embed));
841 
842   return EPHY_WEB_VIEW (embed->web_view);
843 }
844 
845 /**
846  * ephy_embed_get_find_toolbar:
847  * @embed: and #EphyEmbed
848  *
849  * Returns the #EphyFindToolbar wrapped by @embed.
850  *
851  * Returns: (transfer none): an #EphyFindToolbar
852  **/
853 EphyFindToolbar *
ephy_embed_get_find_toolbar(EphyEmbed * embed)854 ephy_embed_get_find_toolbar (EphyEmbed *embed)
855 {
856   g_assert (EPHY_IS_EMBED (embed));
857 
858   return EPHY_FIND_TOOLBAR (embed->find_toolbar);
859 }
860 
861 
862 /**
863  * ephy_embed_add_top_widget:
864  * @embed: an #EphyEmbed
865  * @widget: a #GtkWidget
866  * @policy: whether the widget be automatically
867  * destroyed on page transitions
868  *
869  * Adds a #GtkWidget to the top of the embed.
870  */
871 void
ephy_embed_add_top_widget(EphyEmbed * embed,GtkWidget * widget,EphyEmbedTopWidgetPolicy policy)872 ephy_embed_add_top_widget (EphyEmbed                *embed,
873                            GtkWidget                *widget,
874                            EphyEmbedTopWidgetPolicy  policy)
875 {
876   GSList *list;
877 
878   if (policy == EPHY_EMBED_TOP_WIDGET_POLICY_DESTROY_ON_TRANSITION) {
879     list = embed->destroy_on_transition_list;
880     list = g_slist_prepend (list, widget);
881     embed->destroy_on_transition_list = list;
882 
883     g_signal_connect (widget, "destroy", G_CALLBACK (remove_from_destroy_list_cb), embed);
884   }
885 
886   gtk_box_pack_end (embed->top_widgets_vbox,
887                     GTK_WIDGET (widget), FALSE, FALSE, 0);
888 }
889 
890 /**
891  * ephy_embed_remove_top_widget:
892  * @embed: an #EphyEmbed
893  * @widget: a #GtkWidget
894  *
895  * Removes an #GtkWidget from the top of the embed. The #GtkWidget
896  * must have been added using ephy_embed_add_top_widget(), and not
897  * have been removed by other means. See gtk_container_remove() for
898  * details.
899  */
900 void
ephy_embed_remove_top_widget(EphyEmbed * embed,GtkWidget * widget)901 ephy_embed_remove_top_widget (EphyEmbed *embed,
902                               GtkWidget *widget)
903 {
904   if (g_slist_find (embed->destroy_on_transition_list, widget)) {
905     GSList *list;
906     g_signal_handlers_disconnect_by_func (widget, remove_from_destroy_list_cb, embed);
907 
908     list = embed->destroy_on_transition_list;
909     list = g_slist_remove (list, widget);
910     embed->destroy_on_transition_list = list;
911   }
912 
913   gtk_container_remove (GTK_CONTAINER (embed->top_widgets_vbox),
914                         GTK_WIDGET (widget));
915 }
916 
917 /**
918  * ephy_embed_set_delayed_load_request:
919  * @embed: a #EphyEmbed
920  * @request: a #WebKitNetworkRequest
921  * @state: (nullable): a #WebKitWebViewSessionState
922  *
923  * Sets the #WebKitNetworkRequest that should be loaded when the tab this embed
924  * is on is switched to.
925  */
926 void
ephy_embed_set_delayed_load_request(EphyEmbed * embed,WebKitURIRequest * request,WebKitWebViewSessionState * state)927 ephy_embed_set_delayed_load_request (EphyEmbed                 *embed,
928                                      WebKitURIRequest          *request,
929                                      WebKitWebViewSessionState *state)
930 {
931   g_assert (EPHY_IS_EMBED (embed));
932   g_assert (WEBKIT_IS_URI_REQUEST (request));
933 
934   g_clear_pointer (&embed->delayed_state, webkit_web_view_session_state_unref);
935   g_clear_object (&embed->delayed_request);
936 
937   embed->delayed_request = g_object_ref (request);
938   if (state)
939     embed->delayed_state = webkit_web_view_session_state_ref (state);
940 }
941 
942 /**
943  * ephy_embed_has_load_pending:
944  * @embed: a #EphyEmbed
945  *
946  * Checks whether a load has been delayed for this #EphyEmbed.
947  *
948  * Returns: %TRUE or %FALSE
949  */
950 gboolean
ephy_embed_has_load_pending(EphyEmbed * embed)951 ephy_embed_has_load_pending (EphyEmbed *embed)
952 {
953   g_assert (EPHY_IS_EMBED (embed));
954 
955   return !!embed->delayed_request;
956 }
957 
958 const char *
ephy_embed_get_title(EphyEmbed * embed)959 ephy_embed_get_title (EphyEmbed *embed)
960 {
961   g_assert (EPHY_IS_EMBED (embed));
962 
963   return embed->title;
964 }
965 
966 
967 /**
968  * ephy_embed_inspector_is_loaded:
969  * @embed: a #EphyEmbed
970  *
971  * Checks if the Web Inspector is loaded in this #EphyEmbed.
972  *
973  * Returns: %TRUE or %FALSE
974  */
975 gboolean
ephy_embed_inspector_is_loaded(EphyEmbed * embed)976 ephy_embed_inspector_is_loaded (EphyEmbed *embed)
977 {
978   g_assert (EPHY_IS_EMBED (embed));
979 
980   return embed->inspector_loaded;
981 }
982 
983 void
ephy_embed_attach_notification_container(EphyEmbed * embed)984 ephy_embed_attach_notification_container (EphyEmbed *embed)
985 {
986   EphyNotificationContainer *container;
987 
988   g_assert (EPHY_IS_EMBED (embed));
989 
990   container = ephy_notification_container_get_default ();
991   if (gtk_widget_get_parent (GTK_WIDGET (container)) == NULL)
992     gtk_overlay_add_overlay (GTK_OVERLAY (embed->overlay), GTK_WIDGET (container));
993 }
994 
995 void
ephy_embed_detach_notification_container(EphyEmbed * embed)996 ephy_embed_detach_notification_container (EphyEmbed *embed)
997 {
998   EphyNotificationContainer *container;
999 
1000   g_assert (EPHY_IS_EMBED (embed));
1001 
1002   container = ephy_notification_container_get_default ();
1003   if (gtk_widget_get_parent (GTK_WIDGET (container)) == embed->overlay) {
1004     /* Since the overlay container will own the one and only reference to the
1005      * notification widget, removing it from the container will destroy the
1006      * singleton. To prevent this, add a reference to it before removing it
1007      * from the container. */
1008     gtk_container_remove (GTK_CONTAINER (embed->overlay), g_object_ref (GTK_WIDGET (container)));
1009   }
1010 }
1011