1 /*
2  * e-web-view.c
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, see <http://www.gnu.org/licenses/>.
15  *
16  */
17 
18 #include "evolution-config.h"
19 
20 #include <glib/gi18n-lib.h>
21 
22 #include <math.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include <pango/pango.h>
27 
28 #include <camel/camel.h>
29 #include <libebackend/libebackend.h>
30 
31 #include <libsoup/soup.h>
32 
33 #include "e-alert-dialog.h"
34 #include "e-alert-sink.h"
35 #include "e-file-request.h"
36 #include "e-misc-utils.h"
37 #include "e-plugin-ui.h"
38 #include "e-popup-action.h"
39 #include "e-selectable.h"
40 #include "e-stock-request.h"
41 #include "e-web-view-jsc-utils.h"
42 
43 #include "e-web-view.h"
44 
45 #define E_WEB_VIEW_GET_PRIVATE(obj) \
46 	(G_TYPE_INSTANCE_GET_PRIVATE \
47 	((obj), E_TYPE_WEB_VIEW, EWebViewPrivate))
48 
49 typedef struct _AsyncContext AsyncContext;
50 
51 typedef struct _ElementClickedData {
52 	EWebViewElementClickedFunc callback;
53 	gpointer user_data;
54 } ElementClickedData;
55 
56 struct _EWebViewPrivate {
57 	GtkUIManager *ui_manager;
58 	gchar *selected_uri;
59 	gchar *cursor_image_src;
60 
61 	GQueue highlights;
62 	gboolean highlights_enabled;
63 
64 	GtkAction *open_proxy;
65 	GtkAction *print_proxy;
66 	GtkAction *save_as_proxy;
67 
68 	/* Lockdown Options */
69 	gboolean disable_printing;
70 	gboolean disable_save_to_disk;
71 
72 	gboolean caret_mode;
73 
74 	GSettings *font_settings;
75 	gulong font_name_changed_handler_id;
76 	gulong monospace_font_name_changed_handler_id;
77 
78 	GHashTable *scheme_handlers; /* gchar *scheme ~> EContentRequest */
79 	GHashTable *old_settings;
80 
81 	WebKitFindController *find_controller;
82 	gulong found_text_handler_id;
83 	gulong failed_to_find_text_handler_id;
84 
85 	gboolean has_hover_link;
86 
87 	GHashTable *element_clicked_cbs; /* gchar *element_class ~> GPtrArray {ElementClickedData} */
88 
89 	gboolean has_selection;
90 	gboolean need_input;
91 
92 	GCancellable *cancellable;
93 
94 	gchar *last_popup_iframe_src;
95 	gchar *last_popup_iframe_id;
96 	gchar *last_popup_element_id;
97 	gchar *last_popup_link_uri;
98 
99 	gint minimum_font_size;
100 };
101 
102 struct _AsyncContext {
103 	EActivity *activity;
104 	GFile *destination;
105 	GInputStream *input_stream;
106 	EContentRequest *content_request;
107 	gchar *uri;
108 };
109 
110 enum {
111 	PROP_0,
112 	PROP_CARET_MODE,
113 	PROP_COPY_TARGET_LIST,
114 	PROP_CURSOR_IMAGE_SRC,
115 	PROP_DISABLE_PRINTING,
116 	PROP_DISABLE_SAVE_TO_DISK,
117 	PROP_HAS_SELECTION,
118 	PROP_NEED_INPUT,
119 	PROP_MINIMUM_FONT_SIZE,
120 	PROP_OPEN_PROXY,
121 	PROP_PASTE_TARGET_LIST,
122 	PROP_PRINT_PROXY,
123 	PROP_SAVE_AS_PROXY,
124 	PROP_SELECTED_URI
125 };
126 
127 enum {
128 	NEW_ACTIVITY,
129 	POPUP_EVENT,
130 	STATUS_MESSAGE,
131 	STOP_LOADING,
132 	UPDATE_ACTIONS,
133 	PROCESS_MAILTO,
134 	URI_REQUESTED,
135 	CONTENT_LOADED,
136 	BEFORE_POPUP_EVENT,
137 	LAST_SIGNAL
138 };
139 
140 static guint signals[LAST_SIGNAL];
141 
142 static const gchar *ui =
143 "<ui>"
144 "  <popup name='context'>"
145 "    <menuitem action='copy-clipboard'/>"
146 "    <menuitem action='search-web'/>"
147 "    <separator/>"
148 "    <placeholder name='custom-actions-1'>"
149 "      <menuitem action='open'/>"
150 "      <menuitem action='save-as'/>"
151 "      <menuitem action='http-open'/>"
152 "      <menuitem action='send-message'/>"
153 "      <menuitem action='print'/>"
154 "    </placeholder>"
155 "    <placeholder name='custom-actions-2'>"
156 "      <menuitem action='uri-copy'/>"
157 "      <menuitem action='mailto-copy'/>"
158 "      <menuitem action='mailto-copy-raw'/>"
159 "      <menuitem action='image-copy'/>"
160 "      <menuitem action='image-save'/>"
161 "    </placeholder>"
162 "    <placeholder name='custom-actions-3'/>"
163 "    <separator/>"
164 "    <menuitem action='select-all'/>"
165 "    <placeholder name='inspect-menu' />"
166 "  </popup>"
167 "</ui>";
168 
169 /* Forward Declarations */
170 static void e_web_view_alert_sink_init (EAlertSinkInterface *iface);
171 static void e_web_view_selectable_init (ESelectableInterface *iface);
172 
G_DEFINE_TYPE_WITH_CODE(EWebView,e_web_view,WEBKIT_TYPE_WEB_VIEW,G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE,NULL)G_IMPLEMENT_INTERFACE (E_TYPE_ALERT_SINK,e_web_view_alert_sink_init)G_IMPLEMENT_INTERFACE (E_TYPE_SELECTABLE,e_web_view_selectable_init))173 G_DEFINE_TYPE_WITH_CODE (
174 	EWebView,
175 	e_web_view,
176 	WEBKIT_TYPE_WEB_VIEW,
177 	G_IMPLEMENT_INTERFACE (
178 		E_TYPE_EXTENSIBLE, NULL)
179 	G_IMPLEMENT_INTERFACE (
180 		E_TYPE_ALERT_SINK,
181 		e_web_view_alert_sink_init)
182 	G_IMPLEMENT_INTERFACE (
183 		E_TYPE_SELECTABLE,
184 		e_web_view_selectable_init))
185 
186 static void
187 async_context_free (gpointer ptr)
188 {
189 	AsyncContext *async_context = ptr;
190 
191 	if (!async_context)
192 		return;
193 
194 	g_clear_object (&async_context->activity);
195 	g_clear_object (&async_context->destination);
196 	g_clear_object (&async_context->input_stream);
197 	g_clear_object (&async_context->content_request);
198 	g_free (async_context->uri);
199 
200 	g_slice_free (AsyncContext, async_context);
201 }
202 
203 static void
action_copy_clipboard_cb(GtkAction * action,EWebView * web_view)204 action_copy_clipboard_cb (GtkAction *action,
205                           EWebView *web_view)
206 {
207 	e_web_view_copy_clipboard (web_view);
208 }
209 
210 static void
e_web_view_search_web_get_selection_cb(GObject * source,GAsyncResult * result,gpointer user_data)211 e_web_view_search_web_get_selection_cb (GObject *source,
212 					GAsyncResult *result,
213 					gpointer user_data)
214 {
215 	GSList *texts;
216 	GError *local_error = NULL;
217 
218 	g_return_if_fail (E_IS_WEB_VIEW (source));
219 
220 	e_web_view_jsc_get_selection_finish (WEBKIT_WEB_VIEW (source), result, &texts, &local_error);
221 
222 	if (local_error &&
223 	    !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
224 		e_alert_submit (E_ALERT_SINK (source), "widgets:get-selected-text-failed", local_error->message, NULL);
225 	} else if (texts) {
226 		GSettings *settings;
227 		gchar *text = texts->data;
228 		gchar *uri_prefix;
229 		gchar *escaped;
230 		gchar *uri;
231 
232 		g_strstrip (text);
233 
234 		settings = e_util_ref_settings ("org.gnome.evolution.shell");
235 		uri_prefix = g_settings_get_string (settings, "search-web-uri-prefix");
236 		g_object_unref (settings);
237 
238 		escaped = camel_url_encode (text, "& ?#:;,/\\");
239 
240 		uri = g_strconcat (uri_prefix, escaped, NULL);
241 		if (uri && g_ascii_strncasecmp (uri, "https://", 8) == 0) {
242 			GtkWidget *toplevel;
243 
244 			toplevel = gtk_widget_get_toplevel (GTK_WIDGET (source));
245 
246 			e_show_uri (GTK_IS_WINDOW (toplevel) ? GTK_WINDOW (toplevel) : NULL, uri);
247 		} else {
248 			g_printerr ("Incorrect URI provided, expects https:// prefix, but has got: '%s'\n", uri ? uri : "null");
249 		}
250 
251 		g_free (uri_prefix);
252 		g_free (escaped);
253 		g_free (uri);
254 	}
255 
256 	g_clear_error (&local_error);
257 	g_slist_free_full (texts, g_free);
258 }
259 
260 static void
action_search_web_cb(GtkAction * action,EWebView * web_view)261 action_search_web_cb (GtkAction *action,
262 		      EWebView *web_view)
263 {
264 	e_web_view_jsc_get_selection (WEBKIT_WEB_VIEW (web_view), E_TEXT_FORMAT_PLAIN, web_view->priv->cancellable,
265 		e_web_view_search_web_get_selection_cb, NULL);
266 }
267 
268 static void
action_http_open_cb(GtkAction * action,EWebView * web_view)269 action_http_open_cb (GtkAction *action,
270                      EWebView *web_view)
271 {
272 	const gchar *uri;
273 	gpointer parent;
274 
275 	parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
276 	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
277 
278 	uri = e_web_view_get_selected_uri (web_view);
279 	g_return_if_fail (uri != NULL);
280 
281 	e_show_uri (parent, uri);
282 }
283 
284 static void
webview_mailto_copy(EWebView * web_view,gboolean only_email_address)285 webview_mailto_copy (EWebView *web_view,
286 		     gboolean only_email_address)
287 {
288 	CamelURL *curl;
289 	CamelInternetAddress *inet_addr;
290 	GtkClipboard *clipboard;
291 	const gchar *uri, *name = NULL, *email = NULL;
292 	gchar *text;
293 
294 	uri = e_web_view_get_selected_uri (web_view);
295 	g_return_if_fail (uri != NULL);
296 
297 	/* This should work because we checked it in update_actions(). */
298 	curl = camel_url_new (uri, NULL);
299 	g_return_if_fail (curl != NULL);
300 
301 	inet_addr = camel_internet_address_new ();
302 	camel_address_decode (CAMEL_ADDRESS (inet_addr), curl->path);
303 	if (only_email_address &&
304 	    camel_internet_address_get (inet_addr, 0, &name, &email) &&
305 	    email && *email) {
306 		text = g_strdup (email);
307 	} else {
308 		text = camel_address_format (CAMEL_ADDRESS (inet_addr));
309 		if (text == NULL || *text == '\0')
310 			text = g_strdup (uri + strlen ("mailto:"));
311 	}
312 
313 	g_object_unref (inet_addr);
314 	camel_url_free (curl);
315 
316 	clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
317 	gtk_clipboard_set_text (clipboard, text, -1);
318 	gtk_clipboard_store (clipboard);
319 
320 	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
321 	gtk_clipboard_set_text (clipboard, text, -1);
322 	gtk_clipboard_store (clipboard);
323 
324 	g_free (text);
325 }
326 
327 static void
action_mailto_copy_cb(GtkAction * action,EWebView * web_view)328 action_mailto_copy_cb (GtkAction *action,
329                        EWebView *web_view)
330 {
331 	webview_mailto_copy (web_view, FALSE);
332 }
333 
334 static void
action_mailto_copy_raw_cb(GtkAction * action,EWebView * web_view)335 action_mailto_copy_raw_cb (GtkAction *action,
336 			   EWebView *web_view)
337 {
338 	webview_mailto_copy (web_view, TRUE);
339 }
340 
341 static void
action_select_all_cb(GtkAction * action,EWebView * web_view)342 action_select_all_cb (GtkAction *action,
343                       EWebView *web_view)
344 {
345 	e_web_view_select_all (web_view);
346 }
347 
348 static void
action_send_message_cb(GtkAction * action,EWebView * web_view)349 action_send_message_cb (GtkAction *action,
350                         EWebView *web_view)
351 {
352 	const gchar *uri;
353 	gpointer parent;
354 	gboolean handled;
355 
356 	parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
357 	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
358 
359 	uri = e_web_view_get_selected_uri (web_view);
360 	g_return_if_fail (uri != NULL);
361 
362 	handled = FALSE;
363 	g_signal_emit (web_view, signals[PROCESS_MAILTO], 0, uri, &handled);
364 
365 	if (!handled)
366 		e_show_uri (parent, uri);
367 }
368 
369 static void
action_uri_copy_cb(GtkAction * action,EWebView * web_view)370 action_uri_copy_cb (GtkAction *action,
371                     EWebView *web_view)
372 {
373 	GtkClipboard *clipboard;
374 	const gchar *uri;
375 
376 	uri = e_web_view_get_selected_uri (web_view);
377 	g_return_if_fail (uri != NULL);
378 
379 	clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
380 	gtk_clipboard_set_text (clipboard, uri, -1);
381 	gtk_clipboard_store (clipboard);
382 
383 	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
384 	gtk_clipboard_set_text (clipboard, uri, -1);
385 	gtk_clipboard_store (clipboard);
386 }
387 
388 static void
action_image_copy_cb(GtkAction * action,EWebView * web_view)389 action_image_copy_cb (GtkAction *action,
390                       EWebView *web_view)
391 {
392 	e_web_view_cursor_image_copy (web_view);
393 }
394 
395 static void
action_image_save_cb(GtkAction * action,EWebView * web_view)396 action_image_save_cb (GtkAction *action,
397                       EWebView *web_view)
398 {
399 	e_web_view_cursor_image_save (web_view);
400 }
401 
402 static GtkActionEntry uri_entries[] = {
403 
404 	{ "uri-copy",
405 	  "edit-copy",
406 	  N_("_Copy Link Location"),
407 	  "<Control>c",
408 	  N_("Copy the link to the clipboard"),
409 	  G_CALLBACK (action_uri_copy_cb) }
410 };
411 
412 static GtkActionEntry http_entries[] = {
413 
414 	{ "http-open",
415 	  "emblem-web",
416 	  N_("_Open Link in Browser"),
417 	  NULL,
418 	  N_("Open the link in a web browser"),
419 	  G_CALLBACK (action_http_open_cb) }
420 };
421 
422 static GtkActionEntry mailto_entries[] = {
423 
424 	{ "mailto-copy",
425 	  "edit-copy",
426 	  N_("_Copy Email Address"),
427 	  "<Control>c",
428 	  N_("Copy the email address to the clipboard"),
429 	  G_CALLBACK (action_mailto_copy_cb) },
430 
431 	{ "mailto-copy-raw",
432 	  "edit-copy",
433 	  N_("Copy _Raw Email Address"),
434 	  NULL,
435 	  N_("Copy the raw email address to the clipboard"),
436 	  G_CALLBACK (action_mailto_copy_raw_cb) },
437 
438 	{ "send-message",
439 	  "mail-message-new",
440 	  N_("_Send New Message To…"),
441 	  NULL,
442 	  N_("Send a mail message to this address"),
443 	  G_CALLBACK (action_send_message_cb) }
444 };
445 
446 static GtkActionEntry image_entries[] = {
447 
448 	{ "image-copy",
449 	  "edit-copy",
450 	  N_("_Copy Image"),
451 	  "<Control>c",
452 	  N_("Copy the image to the clipboard"),
453 	  G_CALLBACK (action_image_copy_cb) },
454 
455 	{ "image-save",
456 	  "document-save",
457 	  N_("Save _Image…"),
458 	  "<Control>s",
459 	  N_("Save the image to a file"),
460 	  G_CALLBACK (action_image_save_cb) }
461 };
462 
463 static GtkActionEntry selection_entries[] = {
464 
465 	{ "copy-clipboard",
466 	  "edit-copy",
467 	  N_("_Copy"),
468 	  "<Control>c",
469 	  N_("Copy the selection"),
470 	  G_CALLBACK (action_copy_clipboard_cb) },
471 
472 	{ "search-web",
473 	  NULL,
474 	  N_("Search _Web…"),
475 	  NULL,
476 	  N_("Search the Web with the selected text"),
477 	  G_CALLBACK (action_search_web_cb) }
478 };
479 
480 static GtkActionEntry standard_entries[] = {
481 
482 	{ "select-all",
483 	  "edit-select-all",
484 	  N_("Select _All"),
485 	  NULL,
486 	  N_("Select all text and images"),
487 	  G_CALLBACK (action_select_all_cb) }
488 };
489 
490 static void
web_view_menu_item_select_cb(EWebView * web_view,GtkWidget * widget)491 web_view_menu_item_select_cb (EWebView *web_view,
492                               GtkWidget *widget)
493 {
494 	GtkAction *action;
495 	GtkActivatable *activatable;
496 	const gchar *tooltip;
497 
498 	activatable = GTK_ACTIVATABLE (widget);
499 	action = gtk_activatable_get_related_action (activatable);
500 	tooltip = gtk_action_get_tooltip (action);
501 
502 	if (tooltip == NULL)
503 		return;
504 
505 	e_web_view_status_message (web_view, tooltip);
506 }
507 
508 static void
webkit_find_controller_found_text_cb(WebKitFindController * find_controller,guint match_count,EWebView * web_view)509 webkit_find_controller_found_text_cb (WebKitFindController *find_controller,
510                                       guint match_count,
511                                       EWebView *web_view)
512 {
513 	if (web_view->priv->highlights_enabled && !g_queue_is_empty (&web_view->priv->highlights))
514 		e_web_view_unselect_all (web_view);
515 }
516 
517 static void
webkit_find_controller_failed_to_found_text_cb(WebKitFindController * find_controller,EWebView * web_view)518 webkit_find_controller_failed_to_found_text_cb (WebKitFindController *find_controller,
519                                                 EWebView *web_view)
520 {
521 }
522 
523 static void
web_view_set_find_controller(EWebView * web_view)524 web_view_set_find_controller (EWebView *web_view)
525 {
526 	WebKitFindController *find_controller;
527 
528 	find_controller =
529 		webkit_web_view_get_find_controller (WEBKIT_WEB_VIEW (web_view));
530 
531 	web_view->priv->found_text_handler_id = g_signal_connect (
532 		find_controller, "found-text",
533 		G_CALLBACK (webkit_find_controller_found_text_cb), web_view);
534 
535 	web_view->priv->failed_to_find_text_handler_id = g_signal_connect (
536 		find_controller, "failed-to-find-text",
537 		G_CALLBACK (webkit_find_controller_failed_to_found_text_cb), web_view);
538 
539 	web_view->priv->find_controller = find_controller;
540 }
541 
542 static void
web_view_update_document_highlights(EWebView * web_view)543 web_view_update_document_highlights (EWebView *web_view)
544 {
545 	GList *head, *link;
546 
547 	head = g_queue_peek_head_link (&web_view->priv->highlights);
548 
549 	for (link = head; link != NULL; link = g_list_next (link)) {
550 		webkit_find_controller_search (
551 			web_view->priv->find_controller,
552 			link->data,
553 			WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE,
554 			G_MAXUINT);
555 	}
556 }
557 
558 static void
web_view_menu_item_deselect_cb(EWebView * web_view)559 web_view_menu_item_deselect_cb (EWebView *web_view)
560 {
561 	e_web_view_status_message (web_view, NULL);
562 }
563 
564 static void
web_view_connect_proxy_cb(EWebView * web_view,GtkAction * action,GtkWidget * proxy)565 web_view_connect_proxy_cb (EWebView *web_view,
566                            GtkAction *action,
567                            GtkWidget *proxy)
568 {
569 	if (!GTK_IS_MENU_ITEM (proxy))
570 		return;
571 
572 	g_signal_connect_swapped (
573 		proxy, "select",
574 		G_CALLBACK (web_view_menu_item_select_cb), web_view);
575 
576 	g_signal_connect_swapped (
577 		proxy, "deselect",
578 		G_CALLBACK (web_view_menu_item_deselect_cb), web_view);
579 }
580 
581 static void
web_view_got_elem_from_point_for_popup_event_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)582 web_view_got_elem_from_point_for_popup_event_cb (GObject *source_object,
583 						 GAsyncResult *result,
584 						 gpointer user_data)
585 {
586 	EWebView *web_view;
587 	GdkEvent *event = user_data;
588 	GError *error = NULL;
589 
590 	g_return_if_fail (E_IS_WEB_VIEW (source_object));
591 
592 	web_view = E_WEB_VIEW (source_object);
593 
594 	g_clear_pointer (&web_view->priv->last_popup_iframe_src, g_free);
595 	g_clear_pointer (&web_view->priv->last_popup_iframe_id, g_free);
596 	g_clear_pointer (&web_view->priv->last_popup_element_id, g_free);
597 
598 	if (!e_web_view_jsc_get_element_from_point_finish (WEBKIT_WEB_VIEW (web_view), result,
599 		&web_view->priv->last_popup_iframe_src,
600 		&web_view->priv->last_popup_iframe_id,
601 		&web_view->priv->last_popup_element_id,
602 		&error)) {
603 		g_warning ("%s: Failed to get element from point: %s", G_STRFUNC, error ? error->message : "Unknown error");
604 	}
605 
606 	if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
607 		gboolean handled = FALSE;
608 
609 		g_signal_emit (web_view, signals[BEFORE_POPUP_EVENT], 0,
610 			web_view->priv->last_popup_link_uri, NULL);
611 
612 		g_signal_emit (web_view, signals[POPUP_EVENT], 0,
613 			web_view->priv->last_popup_link_uri, event, &handled);
614 	}
615 
616 	if (event)
617 		gdk_event_free (event);
618 	g_clear_error (&error);
619 }
620 
621 static gboolean
web_view_context_menu_cb(WebKitWebView * webkit_web_view,WebKitContextMenu * context_menu,GdkEvent * event,WebKitHitTestResult * hit_test_result,gpointer user_data)622 web_view_context_menu_cb (WebKitWebView *webkit_web_view,
623                           WebKitContextMenu *context_menu,
624                           GdkEvent *event,
625                           WebKitHitTestResult *hit_test_result,
626                           gpointer user_data)
627 {
628 	WebKitHitTestResultContext context;
629 	EWebView *web_view;
630 	gchar *link_uri = NULL;
631 	gdouble xx, yy;
632 
633 	web_view = E_WEB_VIEW (webkit_web_view);
634 
635 	g_clear_pointer (&web_view->priv->cursor_image_src, g_free);
636 	g_clear_pointer (&web_view->priv->last_popup_iframe_src, g_free);
637 	g_clear_pointer (&web_view->priv->last_popup_iframe_id, g_free);
638 	g_clear_pointer (&web_view->priv->last_popup_element_id, g_free);
639 	g_clear_pointer (&web_view->priv->last_popup_link_uri, g_free);
640 
641 	if (!hit_test_result)
642 		return FALSE;
643 
644 	context = webkit_hit_test_result_get_context (hit_test_result);
645 
646 	if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) {
647 		gchar *image_uri = NULL;
648 
649 		g_object_get (hit_test_result, "image-uri", &image_uri, NULL);
650 
651 		web_view->priv->cursor_image_src = image_uri;
652 	}
653 
654 	if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK)
655 		g_object_get (hit_test_result, "link-uri", &link_uri, NULL);
656 
657 	web_view->priv->last_popup_link_uri = link_uri;
658 
659 	if (!gdk_event_get_coords (event, &xx, &yy)) {
660 		xx = 1;
661 		yy = 1;
662 	}
663 
664 	e_web_view_jsc_get_element_from_point (WEBKIT_WEB_VIEW (web_view), xx, yy, web_view->priv->cancellable,
665 		web_view_got_elem_from_point_for_popup_event_cb, event ? gdk_event_copy (event) : NULL);
666 
667 	return TRUE;
668 }
669 
670 static void
web_view_mouse_target_changed_cb(EWebView * web_view,WebKitHitTestResult * hit_test_result,guint modifiers,gpointer user_data)671 web_view_mouse_target_changed_cb (EWebView *web_view,
672                                   WebKitHitTestResult *hit_test_result,
673                                   guint modifiers,
674                                   gpointer user_data)
675 {
676 	EWebViewClass *class;
677 	const gchar *title, *uri;
678 
679 	title = webkit_hit_test_result_get_link_title (hit_test_result);
680 	uri = webkit_hit_test_result_get_link_uri (hit_test_result);
681 
682 	web_view->priv->has_hover_link = uri && *uri;
683 
684 	/* XXX WebKitWebView does not provide a class method for
685 	 *     this signal, so we do so we can override the default
686 	 *     behavior from subclasses for special URI types. */
687 
688 	class = E_WEB_VIEW_GET_CLASS (web_view);
689 	g_return_if_fail (class != NULL);
690 	g_return_if_fail (class->hovering_over_link != NULL);
691 
692 	class->hovering_over_link (web_view, title, uri);
693 }
694 
695 static gboolean
web_view_decide_policy_cb(EWebView * web_view,WebKitPolicyDecision * decision,WebKitPolicyDecisionType type)696 web_view_decide_policy_cb (EWebView *web_view,
697                            WebKitPolicyDecision *decision,
698                            WebKitPolicyDecisionType type)
699 {
700 	EWebViewClass *class;
701 	WebKitNavigationPolicyDecision *navigation_decision;
702 	WebKitNavigationAction *navigation_action;
703 	WebKitNavigationType navigation_type;
704 	WebKitURIRequest *request;
705 	const gchar *uri, *view_uri;
706 
707 	if (type != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION &&
708 	    type != WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION)
709 		return FALSE;
710 
711 	navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
712 	navigation_action = webkit_navigation_policy_decision_get_navigation_action (navigation_decision);
713 	navigation_type = webkit_navigation_action_get_navigation_type (navigation_action);
714 
715 	if (navigation_type != WEBKIT_NAVIGATION_TYPE_LINK_CLICKED)
716 		return FALSE;
717 
718 	request = webkit_navigation_action_get_request (navigation_action);
719 	uri = webkit_uri_request_get_uri (request);
720 	view_uri = webkit_web_view_get_uri (WEBKIT_WEB_VIEW (web_view));
721 
722 	/* Allow navigation through fragments in page */
723 	if (uri && *uri && view_uri && *view_uri) {
724 		SoupURI *uri_link, *uri_view;
725 
726 		uri_link = soup_uri_new (uri);
727 		uri_view = soup_uri_new (view_uri);
728 		if (uri_link && uri_view) {
729 			const gchar *tmp1, *tmp2;
730 
731 			tmp1 = soup_uri_get_scheme (uri_link);
732 			tmp2 = soup_uri_get_scheme (uri_view);
733 
734 			/* The scheme on both URIs should be the same */
735 			if (tmp1 && tmp2 && g_ascii_strcasecmp (tmp1, tmp2) != 0)
736 				goto free_uris;
737 
738 			tmp1 = soup_uri_get_host (uri_link);
739 			tmp2 = soup_uri_get_host (uri_view);
740 
741 			/* The host on both URIs should be the same */
742 			if (tmp1 && tmp2 && g_ascii_strcasecmp (tmp1, tmp2) != 0)
743 				goto free_uris;
744 
745 			/* URI from link should have fragment set - could be empty */
746 			if (soup_uri_get_fragment (uri_link)) {
747 				soup_uri_free (uri_link);
748 				soup_uri_free (uri_view);
749 				webkit_policy_decision_use (decision);
750 				return TRUE;
751 			}
752 		}
753 
754  free_uris:
755 		if (uri_link)
756 			soup_uri_free (uri_link);
757 		if (uri_view)
758 			soup_uri_free (uri_view);
759 	}
760 
761 	/* XXX WebKitWebView does not provide a class method for
762 	 *     this signal, so we do so we can override the default
763 	 *     behavior from subclasses for special URI types. */
764 
765 	class = E_WEB_VIEW_GET_CLASS (web_view);
766 	g_return_val_if_fail (class != NULL, FALSE);
767 	g_return_val_if_fail (class->link_clicked != NULL, FALSE);
768 
769 	webkit_policy_decision_ignore (decision);
770 
771 	class->link_clicked (web_view, uri);
772 
773 	return TRUE;
774 }
775 
776 static void
e_web_view_update_styles(EWebView * web_view,const gchar * iframe_id)777 e_web_view_update_styles (EWebView *web_view,
778 			  const gchar *iframe_id)
779 {
780 	GdkRGBA color;
781 	gchar *color_value;
782 	gchar *style;
783 	GtkStyleContext *style_context;
784 	WebKitWebView *webkit_web_view;
785 
786 	style_context = gtk_widget_get_style_context (GTK_WIDGET (web_view));
787 
788 	if (gtk_style_context_lookup_color (style_context, "theme_base_color", &color))
789 		color_value = g_strdup_printf ("#%06x", e_rgba_to_value (&color));
790 	else {
791 		color_value = g_strdup (E_UTILS_DEFAULT_THEME_BASE_COLOR);
792 		if (!gdk_rgba_parse (&color, color_value)) {
793 			color.red = 1.0;
794 			color.green = 1.0;
795 			color.blue = 1.0;
796 			color.alpha = 1.0;
797 		}
798 	}
799 
800 	style = g_strconcat ("background-color: ", color_value, ";", NULL);
801 
802 	webkit_web_view = WEBKIT_WEB_VIEW (web_view);
803 
804 	webkit_web_view_set_background_color (webkit_web_view, &color);
805 
806 	e_web_view_jsc_add_rule_into_style_sheet (webkit_web_view,
807 		iframe_id,
808 		"-e-web-view-style-sheet",
809 		".-e-web-view-background-color",
810 		style,
811 		web_view->priv->cancellable);
812 
813 	g_free (color_value);
814 	g_free (style);
815 
816 	if (gtk_style_context_lookup_color (style_context, "theme_fg_color", &color))
817 		color_value = g_strdup_printf ("#%06x", e_rgba_to_value (&color));
818 	else
819 		color_value = g_strdup (E_UTILS_DEFAULT_THEME_FG_COLOR);
820 
821 	style = g_strconcat ("color: ", color_value, ";", NULL);
822 
823 	e_web_view_jsc_add_rule_into_style_sheet (webkit_web_view,
824 		iframe_id,
825 		"-e-web-view-style-sheet",
826 		".-e-web-view-text-color",
827 		style,
828 		web_view->priv->cancellable);
829 
830 	g_free (color_value);
831 	g_free (style);
832 
833 	e_web_view_jsc_add_rule_into_style_sheet (webkit_web_view,
834 		iframe_id,
835 		"-e-web-view-style-sheet",
836 		"body, div, p, td",
837 		"unicode-bidi: plaintext;",
838 		web_view->priv->cancellable);
839 }
840 
841 static void
style_updated_cb(EWebView * web_view)842 style_updated_cb (EWebView *web_view)
843 {
844 	e_web_view_update_styles (web_view, "*");
845 }
846 
847 static void
web_view_load_changed_cb(WebKitWebView * webkit_web_view,WebKitLoadEvent load_event,gpointer user_data)848 web_view_load_changed_cb (WebKitWebView *webkit_web_view,
849                           WebKitLoadEvent load_event,
850                           gpointer user_data)
851 {
852 	EWebView *web_view;
853 
854 	web_view = E_WEB_VIEW (webkit_web_view);
855 
856 	if (load_event == WEBKIT_LOAD_STARTED)
857 		g_hash_table_remove_all (web_view->priv->element_clicked_cbs);
858 
859 	if (load_event != WEBKIT_LOAD_FINISHED)
860 		return;
861 
862 	/* Make sure the initialize function is called for the top document when it is loaded. */
863 	e_web_view_jsc_run_script (webkit_web_view, web_view->priv->cancellable,
864 		"Evo.EnsureMainDocumentInitialized();");
865 
866 	e_web_view_update_styles (web_view, "");
867 
868 	web_view_update_document_highlights (web_view);
869 }
870 
871 static GObjectConstructParam*
find_property(guint n_properties,GObjectConstructParam * properties,GParamSpec * param_spec)872 find_property (guint n_properties,
873                GObjectConstructParam* properties,
874                GParamSpec* param_spec)
875 {
876 	while (n_properties--) {
877 		if (properties->pspec == param_spec)
878 			return properties;
879 		properties++;
880 	}
881 
882 	return NULL;
883 }
884 
885 static void
web_view_uri_request_done_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)886 web_view_uri_request_done_cb (GObject *source_object,
887 			      GAsyncResult *result,
888 			      gpointer user_data)
889 {
890 	WebKitURISchemeRequest *request = user_data;
891 	GInputStream *stream = NULL;
892 	gint64 stream_length = -1;
893 	gchar *mime_type = NULL;
894 	GError *error = NULL;
895 
896 	g_return_if_fail (E_IS_CONTENT_REQUEST (source_object));
897 	g_return_if_fail (WEBKIT_IS_URI_SCHEME_REQUEST (request));
898 
899 	if (!e_content_request_process_finish (E_CONTENT_REQUEST (source_object),
900 		result, &stream, &stream_length, &mime_type, &error)) {
901 		webkit_uri_scheme_request_finish_error (request, error);
902 		g_clear_error (&error);
903 	} else {
904 		webkit_uri_scheme_request_finish (request, stream, stream_length, mime_type);
905 
906 		g_clear_object (&stream);
907 		g_free (mime_type);
908 	}
909 
910 	g_object_unref (request);
911 }
912 
913 static void
e_web_view_process_uri_request(EWebView * web_view,WebKitURISchemeRequest * request)914 e_web_view_process_uri_request (EWebView *web_view,
915 				WebKitURISchemeRequest *request)
916 {
917 	EContentRequest *content_request;
918 	const gchar *scheme;
919 	const gchar *uri;
920 	gchar *redirect_to_uri = NULL;
921 
922 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
923 	g_return_if_fail (WEBKIT_IS_URI_SCHEME_REQUEST (request));
924 
925 	scheme = webkit_uri_scheme_request_get_scheme (request);
926 	g_return_if_fail (scheme != NULL);
927 
928 	content_request = g_hash_table_lookup (web_view->priv->scheme_handlers, scheme);
929 
930 	if (!content_request) {
931 		g_warning ("%s: Cannot find handler for scheme '%s'", G_STRFUNC, scheme);
932 		return;
933 	}
934 
935 	uri = webkit_uri_scheme_request_get_uri (request);
936 
937 	g_return_if_fail (e_content_request_can_process_uri (content_request, uri));
938 
939 	/* Expects an empty string to abandon the request,
940 	   or NULL to keep the passed-in uri,
941 	   or a new uri to load instead. */
942 	g_signal_emit (web_view, signals[URI_REQUESTED], 0, uri, &redirect_to_uri);
943 
944 	if (redirect_to_uri && *redirect_to_uri) {
945 		uri = redirect_to_uri;
946 	} else if (redirect_to_uri) {
947 		GError *error;
948 
949 		g_free (redirect_to_uri);
950 
951 		error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled");
952 
953 		webkit_uri_scheme_request_finish_error (request, error);
954 		g_clear_error (&error);
955 
956 		return;
957 	}
958 
959 	e_content_request_process (content_request, uri, G_OBJECT (web_view), web_view->priv->cancellable,
960 		web_view_uri_request_done_cb, g_object_ref (request));
961 
962 	g_free (redirect_to_uri);
963 }
964 
965 static void
web_view_process_uri_request_cb(WebKitURISchemeRequest * request,gpointer user_data)966 web_view_process_uri_request_cb (WebKitURISchemeRequest *request,
967 				 gpointer user_data)
968 {
969 	WebKitWebView *web_view;
970 
971 	web_view = webkit_uri_scheme_request_get_web_view (request);
972 
973 	if (E_IS_WEB_VIEW (web_view)) {
974 		e_web_view_process_uri_request (E_WEB_VIEW (web_view), request);
975 	} else {
976 		GError *error;
977 
978 		error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, "Unexpected WebView type");
979 		webkit_uri_scheme_request_finish_error (request, error);
980 		g_clear_error (&error);
981 
982 		g_warning ("%s: Unexpected WebView type '%s' received", G_STRFUNC, web_view ? G_OBJECT_TYPE_NAME (web_view) : "null");
983 
984 		return;
985 	}
986 }
987 
988 static GSList *known_schemes = NULL;
989 
990 static void
web_view_web_context_gone(gpointer user_data,GObject * obj)991 web_view_web_context_gone (gpointer user_data,
992 			   GObject *obj)
993 {
994 	gpointer *pweb_context = user_data;
995 
996 	g_return_if_fail (pweb_context != NULL);
997 
998 	*pweb_context = NULL;
999 
1000 	g_slist_free_full (known_schemes, g_free);
1001 	known_schemes = NULL;
1002 }
1003 
1004 static void
web_view_ensure_scheme_known(WebKitWebContext * web_context,const gchar * scheme)1005 web_view_ensure_scheme_known (WebKitWebContext *web_context,
1006 			      const gchar *scheme)
1007 {
1008 	GSList *link;
1009 
1010 	g_return_if_fail (WEBKIT_IS_WEB_CONTEXT (web_context));
1011 	g_return_if_fail (scheme != NULL);
1012 
1013 	for (link = known_schemes; link; link = g_slist_next (link)) {
1014 		if (g_strcmp0 (scheme, link->data) == 0)
1015 			break;
1016 	}
1017 
1018 	if (!link) {
1019 		known_schemes = g_slist_prepend (known_schemes, g_strdup (scheme));
1020 
1021 		webkit_web_context_register_uri_scheme (web_context, scheme, web_view_process_uri_request_cb, NULL, NULL);
1022 	}
1023 }
1024 
1025 static GObject*
web_view_constructor(GType type,guint n_construct_properties,GObjectConstructParam * construct_properties)1026 web_view_constructor (GType type,
1027                       guint n_construct_properties,
1028                       GObjectConstructParam *construct_properties)
1029 {
1030 	GObjectClass* object_class;
1031 	GParamSpec* param_spec;
1032 	GObjectConstructParam *param = NULL;
1033 
1034 	object_class = G_OBJECT_CLASS (g_type_class_ref(type));
1035 	g_return_val_if_fail (object_class != NULL, NULL);
1036 
1037 	if (construct_properties && n_construct_properties != 0) {
1038 		param_spec = g_object_class_find_property (object_class, "settings");
1039 		if ((param = find_property (n_construct_properties, construct_properties, param_spec)))
1040 			g_value_take_object (param->value, e_web_view_get_default_webkit_settings ());
1041 		param_spec = g_object_class_find_property(object_class, "user-content-manager");
1042 		if ((param = find_property (n_construct_properties, construct_properties, param_spec)))
1043 			g_value_take_object (param->value, webkit_user_content_manager_new ());
1044 		param_spec = g_object_class_find_property (object_class, "web-context");
1045 		if ((param = find_property (n_construct_properties, construct_properties, param_spec))) {
1046 			/* Share one web_context between all previews. */
1047 			static gpointer web_context = NULL;
1048 
1049 			if (!web_context) {
1050 				GSList *link;
1051 				#ifdef ENABLE_MAINTAINER_MODE
1052 				const gchar *source_webkitdatadir;
1053 				#endif
1054 
1055 				web_context = webkit_web_context_new ();
1056 
1057 				webkit_web_context_set_cache_model (web_context, WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);
1058 				webkit_web_context_set_web_extensions_directory (web_context, EVOLUTION_WEB_EXTENSIONS_DIR);
1059 				webkit_web_context_set_sandbox_enabled (web_context, TRUE);
1060 				webkit_web_context_add_path_to_sandbox (web_context, EVOLUTION_WEBKITDATADIR, TRUE);
1061 
1062 				#ifdef ENABLE_MAINTAINER_MODE
1063 				source_webkitdatadir = g_getenv ("EVOLUTION_SOURCE_WEBKITDATADIR");
1064 				if (source_webkitdatadir && *source_webkitdatadir)
1065 					webkit_web_context_add_path_to_sandbox (web_context, source_webkitdatadir, TRUE);
1066 				#endif
1067 
1068 				g_object_weak_ref (G_OBJECT (web_context), web_view_web_context_gone, &web_context);
1069 
1070 				for (link = known_schemes; link; link = g_slist_next (link)) {
1071 					const gchar *scheme = link->data;
1072 
1073 					webkit_web_context_register_uri_scheme (web_context, scheme, web_view_process_uri_request_cb, NULL, NULL);
1074 				}
1075 			} else {
1076 				g_object_ref (web_context);
1077 			}
1078 
1079 			g_value_take_object (param->value, web_context);
1080 		}
1081 	}
1082 
1083 	g_type_class_unref (object_class);
1084 
1085 	return G_OBJECT_CLASS (e_web_view_parent_class)->constructor (type, n_construct_properties, construct_properties);
1086 }
1087 
1088 static void
web_view_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)1089 web_view_set_property (GObject *object,
1090                        guint property_id,
1091                        const GValue *value,
1092                        GParamSpec *pspec)
1093 {
1094 	switch (property_id) {
1095 		case PROP_CARET_MODE:
1096 			e_web_view_set_caret_mode (
1097 				E_WEB_VIEW (object),
1098 				g_value_get_boolean (value));
1099 			return;
1100 
1101 		case PROP_COPY_TARGET_LIST:
1102 			/* This is a fake property. */
1103 			g_warning ("%s: EWebView::copy-target-list not used", G_STRFUNC);
1104 			return;
1105 
1106 		case PROP_CURSOR_IMAGE_SRC:
1107 			e_web_view_set_cursor_image_src (
1108 				E_WEB_VIEW (object),
1109 				g_value_get_string (value));
1110 			return;
1111 
1112 		case PROP_DISABLE_PRINTING:
1113 			e_web_view_set_disable_printing (
1114 				E_WEB_VIEW (object),
1115 				g_value_get_boolean (value));
1116 			return;
1117 
1118 		case PROP_DISABLE_SAVE_TO_DISK:
1119 			e_web_view_set_disable_save_to_disk (
1120 				E_WEB_VIEW (object),
1121 				g_value_get_boolean (value));
1122 			return;
1123 
1124 		case PROP_MINIMUM_FONT_SIZE:
1125 			e_web_view_set_minimum_font_size (
1126 				E_WEB_VIEW (object),
1127 				g_value_get_int (value));
1128 			return;
1129 
1130 		case PROP_OPEN_PROXY:
1131 			e_web_view_set_open_proxy (
1132 				E_WEB_VIEW (object),
1133 				g_value_get_object (value));
1134 			return;
1135 
1136 		case PROP_PASTE_TARGET_LIST:
1137 			/* This is a fake property. */
1138 			g_warning ("%s: EWebView::paste-target-list not used", G_STRFUNC);
1139 			return;
1140 
1141 		case PROP_PRINT_PROXY:
1142 			e_web_view_set_print_proxy (
1143 				E_WEB_VIEW (object),
1144 				g_value_get_object (value));
1145 			return;
1146 
1147 		case PROP_SAVE_AS_PROXY:
1148 			e_web_view_set_save_as_proxy (
1149 				E_WEB_VIEW (object),
1150 				g_value_get_object (value));
1151 			return;
1152 
1153 		case PROP_SELECTED_URI:
1154 			e_web_view_set_selected_uri (
1155 				E_WEB_VIEW (object),
1156 				g_value_get_string (value));
1157 			return;
1158 	}
1159 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1160 }
1161 
1162 static void
web_view_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)1163 web_view_get_property (GObject *object,
1164                        guint property_id,
1165                        GValue *value,
1166                        GParamSpec *pspec)
1167 {
1168 	switch (property_id) {
1169 		case PROP_CARET_MODE:
1170 			g_value_set_boolean (
1171 				value, e_web_view_get_caret_mode (
1172 				E_WEB_VIEW (object)));
1173 			return;
1174 
1175 		case PROP_COPY_TARGET_LIST:
1176 			/* This is a fake property. */
1177 			g_value_set_boxed (value, NULL);
1178 			return;
1179 
1180 		case PROP_CURSOR_IMAGE_SRC:
1181 			g_value_set_string (
1182 				value, e_web_view_get_cursor_image_src (
1183 				E_WEB_VIEW (object)));
1184 			return;
1185 
1186 		case PROP_DISABLE_PRINTING:
1187 			g_value_set_boolean (
1188 				value, e_web_view_get_disable_printing (
1189 				E_WEB_VIEW (object)));
1190 			return;
1191 
1192 		case PROP_DISABLE_SAVE_TO_DISK:
1193 			g_value_set_boolean (
1194 				value, e_web_view_get_disable_save_to_disk (
1195 				E_WEB_VIEW (object)));
1196 			return;
1197 
1198 		case PROP_HAS_SELECTION:
1199 			g_value_set_boolean (value, e_web_view_has_selection (E_WEB_VIEW (object)));
1200 			return;
1201 
1202 		case PROP_MINIMUM_FONT_SIZE:
1203 			g_value_set_int (
1204 				value, e_web_view_get_minimum_font_size (
1205 				E_WEB_VIEW (object)));
1206 			return;
1207 
1208 		case PROP_NEED_INPUT:
1209 			g_value_set_boolean (
1210 				value, e_web_view_get_need_input (
1211 				E_WEB_VIEW (object)));
1212 			return;
1213 
1214 		case PROP_OPEN_PROXY:
1215 			g_value_set_object (
1216 				value, e_web_view_get_open_proxy (
1217 				E_WEB_VIEW (object)));
1218 			return;
1219 
1220 		case PROP_PASTE_TARGET_LIST:
1221 			/* This is a fake property. */
1222 			g_value_set_boxed (value, NULL);
1223 			return;
1224 
1225 		case PROP_PRINT_PROXY:
1226 			g_value_set_object (
1227 				value, e_web_view_get_print_proxy (
1228 				E_WEB_VIEW (object)));
1229 			return;
1230 
1231 		case PROP_SAVE_AS_PROXY:
1232 			g_value_set_object (
1233 				value, e_web_view_get_save_as_proxy (
1234 				E_WEB_VIEW (object)));
1235 			return;
1236 
1237 		case PROP_SELECTED_URI:
1238 			g_value_set_string (
1239 				value, e_web_view_get_selected_uri (
1240 				E_WEB_VIEW (object)));
1241 			return;
1242 	}
1243 
1244 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1245 }
1246 
1247 static void
web_view_dispose(GObject * object)1248 web_view_dispose (GObject *object)
1249 {
1250 	EWebViewPrivate *priv;
1251 
1252 	/* This can be called during dispose, thus disconnect early */
1253 	g_signal_handlers_disconnect_by_func (object, G_CALLBACK (style_updated_cb), NULL);
1254 
1255 	priv = E_WEB_VIEW_GET_PRIVATE (object);
1256 
1257 	if (priv->cancellable) {
1258 		g_cancellable_cancel (priv->cancellable);
1259 		g_clear_object (&priv->cancellable);
1260 	}
1261 
1262 	if (priv->font_name_changed_handler_id > 0) {
1263 		g_signal_handler_disconnect (
1264 			priv->font_settings,
1265 			priv->font_name_changed_handler_id);
1266 		priv->font_name_changed_handler_id = 0;
1267 	}
1268 
1269 	if (priv->monospace_font_name_changed_handler_id > 0) {
1270 		g_signal_handler_disconnect (
1271 			priv->font_settings,
1272 			priv->monospace_font_name_changed_handler_id);
1273 		priv->monospace_font_name_changed_handler_id = 0;
1274 	}
1275 
1276 	if (priv->found_text_handler_id > 0) {
1277 		g_signal_handler_disconnect (
1278 			priv->find_controller,
1279 			priv->found_text_handler_id);
1280 		priv->found_text_handler_id = 0;
1281 	}
1282 
1283 	if (priv->failed_to_find_text_handler_id > 0) {
1284 		g_signal_handler_disconnect (
1285 			priv->find_controller,
1286 			priv->failed_to_find_text_handler_id);
1287 		priv->failed_to_find_text_handler_id = 0;
1288 	}
1289 
1290 	g_hash_table_remove_all (priv->scheme_handlers);
1291 	g_hash_table_remove_all (priv->element_clicked_cbs);
1292 
1293 	g_clear_object (&priv->ui_manager);
1294 	g_clear_object (&priv->open_proxy);
1295 	g_clear_object (&priv->print_proxy);
1296 	g_clear_object (&priv->save_as_proxy);
1297 	g_clear_object (&priv->font_settings);
1298 
1299 	/* Chain up to parent's dispose() method. */
1300 	G_OBJECT_CLASS (e_web_view_parent_class)->dispose (object);
1301 }
1302 
1303 static void
web_view_finalize(GObject * object)1304 web_view_finalize (GObject *object)
1305 {
1306 	EWebViewPrivate *priv;
1307 
1308 	priv = E_WEB_VIEW_GET_PRIVATE (object);
1309 
1310 	g_clear_pointer (&priv->last_popup_iframe_src, g_free);
1311 	g_clear_pointer (&priv->last_popup_iframe_id, g_free);
1312 	g_clear_pointer (&priv->last_popup_element_id, g_free);
1313 	g_clear_pointer (&priv->last_popup_link_uri, g_free);
1314 	g_free (priv->selected_uri);
1315 	g_free (priv->cursor_image_src);
1316 
1317 	while (!g_queue_is_empty (&priv->highlights))
1318 		g_free (g_queue_pop_head (&priv->highlights));
1319 
1320 	g_clear_pointer (&priv->old_settings, g_hash_table_destroy);
1321 
1322 	g_hash_table_destroy (priv->scheme_handlers);
1323 	g_hash_table_destroy (priv->element_clicked_cbs);
1324 
1325 	/* Chain up to parent's finalize() method. */
1326 	G_OBJECT_CLASS (e_web_view_parent_class)->finalize (object);
1327 }
1328 
1329 /* 'scheme' is like "file", not "file:" */
1330 void
e_web_view_register_content_request_for_scheme(EWebView * web_view,const gchar * scheme,EContentRequest * content_request)1331 e_web_view_register_content_request_for_scheme (EWebView *web_view,
1332 						const gchar *scheme,
1333 						EContentRequest *content_request)
1334 {
1335 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
1336 	g_return_if_fail (E_IS_CONTENT_REQUEST (content_request));
1337 	g_return_if_fail (scheme != NULL);
1338 
1339 	g_hash_table_insert (web_view->priv->scheme_handlers, g_strdup (scheme), g_object_ref (content_request));
1340 
1341 	web_view_ensure_scheme_known (webkit_web_view_get_context (WEBKIT_WEB_VIEW (web_view)), scheme);
1342 }
1343 
1344 static void
web_view_initialize(WebKitWebView * web_view)1345 web_view_initialize (WebKitWebView *web_view)
1346 {
1347 	EContentRequest *content_request;
1348 	GSettings *font_settings;
1349 
1350 	content_request = e_file_request_new ();
1351 	e_web_view_register_content_request_for_scheme (E_WEB_VIEW (web_view), "evo-file", content_request);
1352 	g_object_unref (content_request);
1353 
1354 	content_request = e_stock_request_new ();
1355 	e_binding_bind_property (web_view, "scale-factor",
1356 		content_request, "scale-factor",
1357 		G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
1358 	e_web_view_register_content_request_for_scheme (E_WEB_VIEW (web_view), "gtk-stock", content_request);
1359 	g_object_unref (content_request);
1360 
1361 	font_settings = e_util_ref_settings ("org.gnome.desktop.interface");
1362 
1363 	e_web_view_update_fonts_settings (font_settings, NULL, NULL, GTK_WIDGET (web_view));
1364 
1365 	g_object_unref (font_settings);
1366 }
1367 
1368 static void
e_web_view_initialize_web_extensions_cb(WebKitWebContext * web_context,gpointer user_data)1369 e_web_view_initialize_web_extensions_cb (WebKitWebContext *web_context,
1370 					 gpointer user_data)
1371 {
1372 	EWebView *web_view = user_data;
1373 
1374 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
1375 
1376 	webkit_web_context_set_web_extensions_directory (web_context, EVOLUTION_WEB_EXTENSIONS_DIR);
1377 }
1378 
1379 static void
e_web_view_element_clicked_cb(WebKitUserContentManager * manager,WebKitJavascriptResult * js_result,gpointer user_data)1380 e_web_view_element_clicked_cb (WebKitUserContentManager *manager,
1381 			       WebKitJavascriptResult *js_result,
1382 			       gpointer user_data)
1383 {
1384 	EWebView *web_view = user_data;
1385 	GtkAllocation elem_position;
1386 	GPtrArray *listeners;
1387 	JSCValue *jsc_object;
1388 	gchar *iframe_id, *elem_id, *elem_class, *elem_value;
1389 
1390 	g_return_if_fail (web_view != NULL);
1391 	g_return_if_fail (js_result != NULL);
1392 
1393 	jsc_object = webkit_javascript_result_get_js_value (js_result);
1394 	g_return_if_fail (jsc_value_is_object (jsc_object));
1395 
1396 	iframe_id = e_web_view_jsc_get_object_property_string (jsc_object, "iframe-id", NULL);
1397 	elem_id = e_web_view_jsc_get_object_property_string (jsc_object, "elem-id", NULL);
1398 	elem_class = e_web_view_jsc_get_object_property_string (jsc_object, "elem-class", NULL);
1399 	elem_value = e_web_view_jsc_get_object_property_string (jsc_object, "elem-value", NULL);
1400 	elem_position.x = e_web_view_jsc_get_object_property_int32 (jsc_object, "left", 0);
1401 	elem_position.y = e_web_view_jsc_get_object_property_int32 (jsc_object, "top", 0);
1402 	elem_position.width = e_web_view_jsc_get_object_property_int32 (jsc_object, "width", 0);
1403 	elem_position.height = e_web_view_jsc_get_object_property_int32 (jsc_object, "height", 0);
1404 
1405 	listeners = g_hash_table_lookup (web_view->priv->element_clicked_cbs, elem_class);
1406 
1407 	if (listeners) {
1408 		guint ii;
1409 
1410 		for (ii = 0; ii < listeners->len; ii++) {
1411 			ElementClickedData *ecd = g_ptr_array_index (listeners, ii);
1412 
1413 			if (ecd && ecd->callback)
1414 				ecd->callback (web_view, iframe_id, elem_id, elem_class, elem_value, &elem_position, ecd->user_data);
1415 		}
1416 	}
1417 
1418 	g_free (iframe_id);
1419 	g_free (elem_id);
1420 	g_free (elem_class);
1421 	g_free (elem_value);
1422 }
1423 
1424 static void
web_view_call_register_element_clicked(EWebView * web_view,const gchar * iframe_id,const gchar * only_elem_class)1425 web_view_call_register_element_clicked (EWebView *web_view,
1426 					const gchar *iframe_id,
1427 					const gchar *only_elem_class)
1428 {
1429 	gchar *elem_classes = NULL;
1430 
1431 	if (!only_elem_class) {
1432 		GHashTableIter iter;
1433 		gpointer key;
1434 		GString *classes;
1435 
1436 		classes = g_string_sized_new (128);
1437 
1438 		g_hash_table_iter_init (&iter, web_view->priv->element_clicked_cbs);
1439 		while (g_hash_table_iter_next (&iter, &key, NULL)) {
1440 			if (classes->len)
1441 				g_string_append_c (classes, '\n');
1442 
1443 			g_string_append (classes, key);
1444 		}
1445 
1446 		elem_classes = g_string_free (classes, FALSE);
1447 	}
1448 
1449 	e_web_view_jsc_register_element_clicked (WEBKIT_WEB_VIEW (web_view), iframe_id,
1450 		only_elem_class ? only_elem_class : elem_classes,
1451 		web_view->priv->cancellable);
1452 
1453 	g_free (elem_classes);
1454 }
1455 
1456 static void
e_web_view_content_loaded_cb(WebKitUserContentManager * manager,WebKitJavascriptResult * js_result,gpointer user_data)1457 e_web_view_content_loaded_cb (WebKitUserContentManager *manager,
1458 			      WebKitJavascriptResult *js_result,
1459 			      gpointer user_data)
1460 {
1461 	EWebView *web_view = user_data;
1462 	JSCValue *jsc_value;
1463 	gchar *iframe_id;
1464 
1465 	g_return_if_fail (web_view != NULL);
1466 	g_return_if_fail (js_result != NULL);
1467 
1468 	jsc_value = webkit_javascript_result_get_js_value (js_result);
1469 	g_return_if_fail (jsc_value_is_string (jsc_value));
1470 
1471 	iframe_id = jsc_value_to_string (jsc_value);
1472 
1473 	if (!iframe_id || !*iframe_id)
1474 		e_web_view_update_fonts (web_view);
1475 	else
1476 		e_web_view_update_styles (web_view, iframe_id);
1477 
1478 	web_view_call_register_element_clicked (web_view, iframe_id, NULL);
1479 
1480 	g_signal_emit (web_view, signals[CONTENT_LOADED], 0, iframe_id);
1481 
1482 	g_free (iframe_id);
1483 }
1484 
1485 static void
e_web_view_set_has_selection(EWebView * web_view,gboolean has_selection)1486 e_web_view_set_has_selection (EWebView *web_view,
1487 			      gboolean has_selection)
1488 {
1489 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
1490 
1491 	if ((!web_view->priv->has_selection) == (!has_selection))
1492 		return;
1493 
1494 	web_view->priv->has_selection = has_selection;
1495 
1496 	g_object_notify (G_OBJECT (web_view), "has-selection");
1497 }
1498 
1499 
1500 static void
e_web_view_has_selection_cb(WebKitUserContentManager * manager,WebKitJavascriptResult * js_result,gpointer user_data)1501 e_web_view_has_selection_cb (WebKitUserContentManager *manager,
1502 			     WebKitJavascriptResult *js_result,
1503 			     gpointer user_data)
1504 {
1505 	EWebView *web_view = user_data;
1506 	JSCValue *jsc_value;
1507 
1508 	g_return_if_fail (web_view != NULL);
1509 	g_return_if_fail (js_result != NULL);
1510 
1511 	jsc_value = webkit_javascript_result_get_js_value (js_result);
1512 	g_return_if_fail (jsc_value_is_boolean (jsc_value));
1513 
1514 	e_web_view_set_has_selection (web_view, jsc_value_to_boolean (jsc_value));
1515 }
1516 
1517 static void
e_web_view_set_need_input(EWebView * web_view,gboolean need_input)1518 e_web_view_set_need_input (EWebView *web_view,
1519 			   gboolean need_input)
1520 {
1521 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
1522 
1523 	if ((!web_view->priv->need_input) == (!need_input))
1524 		return;
1525 
1526 	web_view->priv->need_input = need_input;
1527 
1528 	g_object_notify (G_OBJECT (web_view), "need-input");
1529 }
1530 
1531 static void
e_web_view_need_input_changed_cb(WebKitUserContentManager * manager,WebKitJavascriptResult * js_result,gpointer user_data)1532 e_web_view_need_input_changed_cb (WebKitUserContentManager *manager,
1533 				  WebKitJavascriptResult *js_result,
1534 				  gpointer user_data)
1535 {
1536 	EWebView *web_view = user_data;
1537 	JSCValue *jsc_value;
1538 
1539 	g_return_if_fail (web_view != NULL);
1540 	g_return_if_fail (js_result != NULL);
1541 
1542 	jsc_value = webkit_javascript_result_get_js_value (js_result);
1543 	g_return_if_fail (jsc_value_is_boolean (jsc_value));
1544 
1545 	e_web_view_set_need_input (web_view, jsc_value_to_boolean (jsc_value));
1546 }
1547 
1548 static void
web_view_constructed(GObject * object)1549 web_view_constructed (GObject *object)
1550 {
1551 	WebKitSettings *web_settings;
1552 	WebKitUserContentManager *manager;
1553 	EWebView *web_view = E_WEB_VIEW (object);
1554 	GSettings *settings;
1555 
1556 #ifndef G_OS_WIN32
1557 	settings = e_util_ref_settings ("org.gnome.desktop.lockdown");
1558 
1559 	g_settings_bind (
1560 		settings, "disable-printing",
1561 		object, "disable-printing",
1562 		G_SETTINGS_BIND_GET);
1563 
1564 	g_settings_bind (
1565 		settings, "disable-save-to-disk",
1566 		object, "disable-save-to-disk",
1567 		G_SETTINGS_BIND_GET);
1568 
1569 	g_object_unref (settings);
1570 #endif
1571 
1572 	settings = e_util_ref_settings ("org.gnome.evolution.shell");
1573 
1574 	g_settings_bind (
1575 		settings, "webkit-minimum-font-size",
1576 		object, "minimum-font-size",
1577 		G_SETTINGS_BIND_GET);
1578 
1579 	g_clear_object (&settings);
1580 
1581 	g_signal_connect_object (webkit_web_view_get_context (WEBKIT_WEB_VIEW (web_view)), "initialize-web-extensions",
1582 		G_CALLBACK (e_web_view_initialize_web_extensions_cb), web_view, 0);
1583 
1584 	/* Chain up to parent's constructed() method. */
1585 	G_OBJECT_CLASS (e_web_view_parent_class)->constructed (object);
1586 
1587 	e_extensible_load_extensions (E_EXTENSIBLE (object));
1588 
1589 	web_settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (object));
1590 	webkit_settings_set_enable_write_console_messages_to_stdout (web_settings, e_util_get_webkit_developer_mode_enabled ());
1591 
1592 	g_object_set (
1593 		G_OBJECT (web_settings),
1594 		"default-charset", "UTF-8",
1595 		NULL);
1596 
1597 	e_binding_bind_property (
1598 		web_settings, "enable-caret-browsing",
1599 		E_WEB_VIEW (object), "caret-mode",
1600 		G_BINDING_BIDIRECTIONAL |
1601 		G_BINDING_SYNC_CREATE);
1602 
1603 	web_view_initialize (WEBKIT_WEB_VIEW (object));
1604 
1605 	web_view_set_find_controller (web_view);
1606 
1607 	manager = webkit_web_view_get_user_content_manager (WEBKIT_WEB_VIEW (object));
1608 
1609 	g_signal_connect_object (manager, "script-message-received::elementClicked",
1610 		G_CALLBACK (e_web_view_element_clicked_cb), web_view, 0);
1611 
1612 	g_signal_connect_object (manager, "script-message-received::contentLoaded",
1613 		G_CALLBACK (e_web_view_content_loaded_cb), web_view, 0);
1614 
1615 	g_signal_connect_object (manager, "script-message-received::hasSelection",
1616 		G_CALLBACK (e_web_view_has_selection_cb), web_view, 0);
1617 
1618 	g_signal_connect_object (manager, "script-message-received::needInputChanged",
1619 		G_CALLBACK (e_web_view_need_input_changed_cb), web_view, 0);
1620 
1621 	webkit_user_content_manager_register_script_message_handler (manager, "contentLoaded");
1622 	webkit_user_content_manager_register_script_message_handler (manager, "elementClicked");
1623 	webkit_user_content_manager_register_script_message_handler (manager, "hasSelection");
1624 	webkit_user_content_manager_register_script_message_handler (manager, "needInputChanged");
1625 }
1626 
1627 static void
e_web_view_replace_load_cancellable(EWebView * web_view,gboolean create_new)1628 e_web_view_replace_load_cancellable (EWebView *web_view,
1629 				     gboolean create_new)
1630 {
1631 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
1632 
1633 	if (web_view->priv->cancellable) {
1634 		g_cancellable_cancel (web_view->priv->cancellable);
1635 		g_clear_object (&web_view->priv->cancellable);
1636 	}
1637 
1638 	if (create_new)
1639 		web_view->priv->cancellable = g_cancellable_new ();
1640 }
1641 
1642 static gboolean
web_view_scroll_event(GtkWidget * widget,GdkEventScroll * event)1643 web_view_scroll_event (GtkWidget *widget,
1644                        GdkEventScroll *event)
1645 {
1646 	if (event->state & GDK_CONTROL_MASK) {
1647 		GdkScrollDirection direction = event->direction;
1648 
1649 		if (direction == GDK_SCROLL_SMOOTH) {
1650 			static gdouble total_delta_y = 0.0;
1651 
1652 			total_delta_y += event->delta_y;
1653 
1654 			if (total_delta_y >= 1.0) {
1655 				total_delta_y = 0.0;
1656 				direction = GDK_SCROLL_DOWN;
1657 			} else if (total_delta_y <= -1.0) {
1658 				total_delta_y = 0.0;
1659 				direction = GDK_SCROLL_UP;
1660 			} else if (event->delta_y >= 1e-9 || event->delta_y <= -1e-9) {
1661 				return TRUE;
1662 			} else {
1663 				return FALSE;
1664 			}
1665 		}
1666 
1667 		switch (direction) {
1668 			case GDK_SCROLL_UP:
1669 				e_web_view_zoom_in (E_WEB_VIEW (widget));
1670 				return TRUE;
1671 			case GDK_SCROLL_DOWN:
1672 				e_web_view_zoom_out (E_WEB_VIEW (widget));
1673 				return TRUE;
1674 			default:
1675 				break;
1676 		}
1677 	}
1678 
1679 	return GTK_WIDGET_CLASS (e_web_view_parent_class)->
1680 		scroll_event (widget, event);
1681 }
1682 
1683 static gboolean
web_view_drag_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time_)1684 web_view_drag_motion (GtkWidget *widget,
1685                       GdkDragContext *context,
1686                       gint x,
1687                       gint y,
1688                       guint time_)
1689 {
1690 	/* Made this way to not pretend that this is a drop zone,
1691 	   when only FALSE had been returned, even it is supposed
1692 	   to be enough. Remove web_view_drag_leave() once fixed. */
1693 	gdk_drag_status (context, 0, time_);
1694 
1695 	return TRUE;
1696 }
1697 
1698 static gboolean
web_view_drag_drop(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time_)1699 web_view_drag_drop (GtkWidget *widget,
1700 		    GdkDragContext *context,
1701 		    gint x,
1702 		    gint y,
1703 		    guint time_)
1704 {
1705 	/* Defined to avoid crash in WebKit */
1706 	return FALSE;
1707 }
1708 
1709 static void
web_view_drag_leave(GtkWidget * widget,GdkDragContext * context,guint time_)1710 web_view_drag_leave (GtkWidget *widget,
1711 		     GdkDragContext *context,
1712 		     guint time_)
1713 {
1714 	/* Defined to avoid crash in WebKit, when the web_view_drag_motion()
1715 	   uses the other way around. */
1716 }
1717 
1718 static void
web_view_hovering_over_link(EWebView * web_view,const gchar * title,const gchar * uri)1719 web_view_hovering_over_link (EWebView *web_view,
1720                              const gchar *title,
1721                              const gchar *uri)
1722 {
1723 	gchar *message = e_util_get_uri_tooltip (uri);
1724 
1725 	e_web_view_status_message (web_view, message);
1726 
1727 	g_free (message);
1728 }
1729 
1730 static void
web_view_link_clicked(EWebView * web_view,const gchar * uri)1731 web_view_link_clicked (EWebView *web_view,
1732                        const gchar *uri)
1733 {
1734 	gpointer parent;
1735 
1736 	if (uri && g_ascii_strncasecmp (uri, "mailto:", 7) == 0) {
1737 		gboolean handled = FALSE;
1738 
1739 		g_signal_emit (
1740 			web_view, signals[PROCESS_MAILTO], 0, uri, &handled);
1741 
1742 		if (handled)
1743 			return;
1744 	}
1745 
1746 	parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
1747 	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
1748 
1749 	e_show_uri (parent, uri);
1750 }
1751 
1752 static void
web_view_load_string(EWebView * web_view,const gchar * string)1753 web_view_load_string (EWebView *web_view,
1754                       const gchar *string)
1755 {
1756 	if (!string || !*string) {
1757 		webkit_web_view_load_html (WEBKIT_WEB_VIEW (web_view), "", "evo-file:///");
1758 	} else {
1759 		GBytes *bytes;
1760 
1761 		bytes = g_bytes_new (string, strlen (string));
1762 		webkit_web_view_load_bytes (WEBKIT_WEB_VIEW (web_view), bytes, NULL, NULL, "evo-file:///");
1763 		g_bytes_unref (bytes);
1764 	}
1765 }
1766 
1767 static void
web_view_load_uri(EWebView * web_view,const gchar * uri)1768 web_view_load_uri (EWebView *web_view,
1769                    const gchar *uri)
1770 {
1771 	if (!uri)
1772 		uri = "about:blank";
1773 
1774 	webkit_web_view_load_uri (WEBKIT_WEB_VIEW (web_view), uri);
1775 }
1776 
1777 static gchar *
web_view_suggest_filename(EWebView * web_view,const gchar * uri)1778 web_view_suggest_filename (EWebView *web_view,
1779                            const gchar *uri)
1780 {
1781 	const gchar *cp;
1782 
1783 	/* Try to derive a filename from the last path segment. */
1784 
1785 	cp = strrchr (uri, '/');
1786 	if (cp != NULL) {
1787 		if (strchr (cp, '?') == NULL)
1788 			cp++;
1789 		else
1790 			cp = NULL;
1791 	}
1792 
1793 	return g_strdup (cp);
1794 }
1795 
1796 static void
web_view_before_popup_event(EWebView * web_view,const gchar * uri)1797 web_view_before_popup_event (EWebView *web_view,
1798 			     const gchar *uri)
1799 {
1800 	e_web_view_set_selected_uri (web_view, uri);
1801 	e_web_view_update_actions (web_view);
1802 }
1803 
1804 static gboolean
web_view_popup_event(EWebView * web_view,const gchar * uri,GdkEvent * event)1805 web_view_popup_event (EWebView *web_view,
1806                       const gchar *uri,
1807 		      GdkEvent *event)
1808 {
1809 	e_web_view_set_selected_uri (web_view, uri);
1810 	e_web_view_show_popup_menu (web_view, event);
1811 
1812 	return TRUE;
1813 }
1814 
1815 static void
web_view_stop_loading(EWebView * web_view)1816 web_view_stop_loading (EWebView *web_view)
1817 {
1818 	e_web_view_replace_load_cancellable (web_view, FALSE);
1819 	webkit_web_view_stop_loading (WEBKIT_WEB_VIEW (web_view));
1820 }
1821 
1822 static void
web_view_update_actions(EWebView * web_view)1823 web_view_update_actions (EWebView *web_view)
1824 {
1825 	GtkActionGroup *action_group;
1826 	gboolean can_copy;
1827 	gboolean scheme_is_http = FALSE;
1828 	gboolean scheme_is_mailto = FALSE;
1829 	gboolean uri_is_valid = FALSE;
1830 	gboolean visible;
1831 	const gchar *cursor_image_src;
1832 	const gchar *group_name;
1833 	const gchar *uri;
1834 
1835 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
1836 
1837 	uri = e_web_view_get_selected_uri (web_view);
1838 	can_copy = e_web_view_has_selection (web_view);
1839 	cursor_image_src = e_web_view_get_cursor_image_src (web_view);
1840 
1841 	/* Parse the URI early so we know if the actions will work. */
1842 	if (uri != NULL) {
1843 		CamelURL *curl;
1844 
1845 		curl = camel_url_new (uri, NULL);
1846 		uri_is_valid = (curl != NULL);
1847 		camel_url_free (curl);
1848 
1849 		scheme_is_http =
1850 			(g_ascii_strncasecmp (uri, "http:", 5) == 0) ||
1851 			(g_ascii_strncasecmp (uri, "https:", 6) == 0);
1852 
1853 		scheme_is_mailto =
1854 			(g_ascii_strncasecmp (uri, "mailto:", 7) == 0);
1855 	}
1856 
1857 	/* Allow copying the URI even if it's malformed. */
1858 	group_name = "uri";
1859 	visible = (uri != NULL) && !scheme_is_mailto;
1860 	action_group = e_web_view_get_action_group (web_view, group_name);
1861 	gtk_action_group_set_visible (action_group, visible);
1862 
1863 	group_name = "http";
1864 	visible = uri_is_valid && scheme_is_http;
1865 	action_group = e_web_view_get_action_group (web_view, group_name);
1866 	gtk_action_group_set_visible (action_group, visible);
1867 
1868 	group_name = "mailto";
1869 	visible = uri_is_valid && scheme_is_mailto;
1870 	action_group = e_web_view_get_action_group (web_view, group_name);
1871 	gtk_action_group_set_visible (action_group, visible);
1872 
1873 	if (visible) {
1874 		CamelURL *curl;
1875 
1876 		curl = camel_url_new (uri, NULL);
1877 		if (curl) {
1878 			CamelInternetAddress *inet_addr;
1879 			const gchar *name = NULL, *email = NULL;
1880 			GtkAction *action;
1881 
1882 			inet_addr = camel_internet_address_new ();
1883 			camel_address_decode (CAMEL_ADDRESS (inet_addr), curl->path);
1884 
1885 			action = gtk_action_group_get_action (action_group, "mailto-copy-raw");
1886 			gtk_action_set_visible (action,
1887 				camel_internet_address_get (inet_addr, 0, &name, &email) &&
1888 				name && *name && email && *email);
1889 
1890 			g_object_unref (inet_addr);
1891 			camel_url_free (curl);
1892 		}
1893 	}
1894 
1895 	group_name = "image";
1896 	visible = (cursor_image_src != NULL);
1897 	action_group = e_web_view_get_action_group (web_view, group_name);
1898 	gtk_action_group_set_visible (action_group, visible);
1899 
1900 	group_name = "selection";
1901 	visible = can_copy;
1902 	action_group = e_web_view_get_action_group (web_view, group_name);
1903 	gtk_action_group_set_visible (action_group, visible);
1904 
1905 	group_name = "standard";
1906 	visible = (uri == NULL);
1907 	action_group = e_web_view_get_action_group (web_view, group_name);
1908 	gtk_action_group_set_visible (action_group, visible);
1909 
1910 	group_name = "lockdown-printing";
1911 	visible = (uri == NULL) && !web_view->priv->disable_printing;
1912 	action_group = e_web_view_get_action_group (web_view, group_name);
1913 	gtk_action_group_set_visible (action_group, visible);
1914 
1915 	group_name = "lockdown-save-to-disk";
1916 	visible = (uri == NULL) && !web_view->priv->disable_save_to_disk;
1917 	action_group = e_web_view_get_action_group (web_view, group_name);
1918 	gtk_action_group_set_visible (action_group, visible);
1919 }
1920 
1921 static void
web_view_submit_alert(EAlertSink * alert_sink,EAlert * alert)1922 web_view_submit_alert (EAlertSink *alert_sink,
1923                        EAlert *alert)
1924 {
1925 	EWebView *web_view;
1926 	GtkWidget *dialog;
1927 	GString *buffer;
1928 	const gchar *icon_name = NULL;
1929 	const gchar *primary_text;
1930 	const gchar *secondary_text;
1931 	gint icon_width, icon_height;
1932 	gpointer parent;
1933 
1934 	web_view = E_WEB_VIEW (alert_sink);
1935 
1936 	parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
1937 	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
1938 
1939 	switch (e_alert_get_message_type (alert)) {
1940 		case GTK_MESSAGE_INFO:
1941 			icon_name = "dialog-information";
1942 			break;
1943 
1944 		case GTK_MESSAGE_WARNING:
1945 			icon_name = "dialog-warning";
1946 			break;
1947 
1948 		case GTK_MESSAGE_ERROR:
1949 			icon_name = "dialog-error";
1950 			break;
1951 
1952 		default:
1953 			dialog = e_alert_dialog_new (parent, alert);
1954 			gtk_dialog_run (GTK_DIALOG (dialog));
1955 			gtk_widget_destroy (dialog);
1956 			return;
1957 	}
1958 
1959 	/* Primary text is required. */
1960 	primary_text = e_alert_get_primary_text (alert);
1961 	g_return_if_fail (primary_text != NULL);
1962 
1963 	/* Secondary text is optional. */
1964 	secondary_text = e_alert_get_secondary_text (alert);
1965 	if (secondary_text == NULL)
1966 		secondary_text = "";
1967 
1968 	if (!gtk_icon_size_lookup (GTK_ICON_SIZE_DIALOG, &icon_width, &icon_height)) {
1969 		icon_width = 48;
1970 		icon_height = 48;
1971 	}
1972 
1973 	buffer = g_string_sized_new (512);
1974 
1975 	g_string_append (
1976 		buffer,
1977 		"<html>"
1978 		"<head>"
1979 		"<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">"
1980 		"<meta name=\"color-scheme\" content=\"light dark\">"
1981 		"</head>"
1982 		"<body>");
1983 
1984 	g_string_append (
1985 		buffer,
1986 		"<table bgcolor='#000000' width='100%'"
1987 		" cellpadding='1' cellspacing='0'>"
1988 		"<tr>"
1989 		"<td>"
1990 		"<table bgcolor='#dddddd' width='100%' cellpadding='6' style=\"color:#000000;\">"
1991 		"<tr>");
1992 
1993 	g_string_append_printf (
1994 		buffer,
1995 		"<tr>"
1996 		"<td valign='top'>"
1997 		"<img src='gtk-stock://%s/?size=%d' width=\"%dpx\" height=\"%dpx\"/>"
1998 		"</td>"
1999 		"<td align='left' width='100%%'>"
2000 		"<h3>%s</h3>"
2001 		"%s"
2002 		"</td>"
2003 		"</tr>",
2004 		icon_name,
2005 		GTK_ICON_SIZE_DIALOG,
2006 		icon_width,
2007 		icon_height,
2008 		primary_text,
2009 		secondary_text);
2010 
2011 	g_string_append (
2012 		buffer,
2013 		"</table>"
2014 		"</td>"
2015 		"</tr>"
2016 		"</table>"
2017 		"</body>"
2018 		"</html>");
2019 
2020 	e_web_view_load_string (web_view, buffer->str);
2021 
2022 	g_string_free (buffer, TRUE);
2023 }
2024 
2025 static void
web_view_can_execute_editing_command_cb(WebKitWebView * webkit_web_view,GAsyncResult * result,GtkAction * action)2026 web_view_can_execute_editing_command_cb (WebKitWebView *webkit_web_view,
2027                                          GAsyncResult *result,
2028                                          GtkAction *action)
2029 {
2030 	gboolean can_do_command;
2031 
2032 	can_do_command = webkit_web_view_can_execute_editing_command_finish (
2033 		webkit_web_view, result, NULL);
2034 
2035 	gtk_action_set_sensitive (action, can_do_command);
2036 	g_object_unref (action);
2037 }
2038 
2039 static void
web_view_selectable_update_actions(ESelectable * selectable,EFocusTracker * focus_tracker,GdkAtom * clipboard_targets,gint n_clipboard_targets)2040 web_view_selectable_update_actions (ESelectable *selectable,
2041                                     EFocusTracker *focus_tracker,
2042                                     GdkAtom *clipboard_targets,
2043                                     gint n_clipboard_targets)
2044 {
2045 	EWebView *web_view;
2046 	GtkAction *action;
2047 	gboolean can_copy = FALSE;
2048 
2049 	web_view = E_WEB_VIEW (selectable);
2050 
2051 	can_copy = e_web_view_has_selection (web_view);
2052 
2053 	action = e_focus_tracker_get_copy_clipboard_action (focus_tracker);
2054 	gtk_action_set_sensitive (action, can_copy);
2055 	gtk_action_set_tooltip (action, _("Copy the selection"));
2056 
2057 	action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
2058 	webkit_web_view_can_execute_editing_command (
2059 		WEBKIT_WEB_VIEW (web_view),
2060 		WEBKIT_EDITING_COMMAND_CUT,
2061 		NULL, /* cancellable */
2062 		(GAsyncReadyCallback) web_view_can_execute_editing_command_cb,
2063 		g_object_ref (action));
2064 	gtk_action_set_tooltip (action, _("Cut the selection"));
2065 
2066 	action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
2067 	webkit_web_view_can_execute_editing_command (
2068 		WEBKIT_WEB_VIEW (web_view),
2069 		WEBKIT_EDITING_COMMAND_PASTE,
2070 		NULL, /* cancellable */
2071 		(GAsyncReadyCallback) web_view_can_execute_editing_command_cb,
2072 		g_object_ref (action));
2073 	gtk_action_set_tooltip (action, _("Paste the clipboard"));
2074 
2075 	action = e_focus_tracker_get_select_all_action (focus_tracker);
2076 	gtk_action_set_sensitive (action, TRUE);
2077 	gtk_action_set_tooltip (action, _("Select all text and images"));
2078 }
2079 
2080 static void
web_view_selectable_cut_clipboard(ESelectable * selectable)2081 web_view_selectable_cut_clipboard (ESelectable *selectable)
2082 {
2083 	e_web_view_cut_clipboard (E_WEB_VIEW (selectable));
2084 }
2085 
2086 static void
web_view_selectable_copy_clipboard(ESelectable * selectable)2087 web_view_selectable_copy_clipboard (ESelectable *selectable)
2088 {
2089 	e_web_view_copy_clipboard (E_WEB_VIEW (selectable));
2090 }
2091 
2092 static void
web_view_selectable_paste_clipboard(ESelectable * selectable)2093 web_view_selectable_paste_clipboard (ESelectable *selectable)
2094 {
2095 	e_web_view_paste_clipboard (E_WEB_VIEW (selectable));
2096 }
2097 
2098 static void
web_view_selectable_select_all(ESelectable * selectable)2099 web_view_selectable_select_all (ESelectable *selectable)
2100 {
2101 	e_web_view_select_all (E_WEB_VIEW (selectable));
2102 }
2103 
2104 static void
e_web_view_test_change_and_update_fonts_cb(EWebView * web_view,const gchar * key,GSettings * settings)2105 e_web_view_test_change_and_update_fonts_cb (EWebView *web_view,
2106 					    const gchar *key,
2107 					    GSettings *settings)
2108 {
2109 	GVariant *new_value, *old_value;
2110 
2111 	new_value = g_settings_get_value (settings, key);
2112 	old_value = g_hash_table_lookup (web_view->priv->old_settings, key);
2113 
2114 	if (!new_value || !old_value || !g_variant_equal (new_value, old_value)) {
2115 		if (new_value)
2116 			g_hash_table_insert (web_view->priv->old_settings, g_strdup (key), new_value);
2117 		else
2118 			g_hash_table_remove (web_view->priv->old_settings, key);
2119 
2120 		e_web_view_update_fonts (web_view);
2121 	} else if (new_value) {
2122 		g_variant_unref (new_value);
2123 	}
2124 }
2125 
2126 static void
web_view_toplevel_event_after_cb(GtkWidget * widget,GdkEvent * event,EWebView * web_view)2127 web_view_toplevel_event_after_cb (GtkWidget *widget,
2128 				  GdkEvent *event,
2129 				  EWebView *web_view)
2130 {
2131 	if (event && event->type == GDK_MOTION_NOTIFY && web_view->priv->has_hover_link &&
2132 	    gdk_event_get_window (event) != gtk_widget_get_window (GTK_WIDGET (web_view))) {
2133 		/* This won't reset WebKitGTK's cached link the cursor stays on, but do this,
2134 		   instead of sending a fake signal to the WebKitGTK. */
2135 		e_web_view_status_message (web_view, NULL);
2136 
2137 		web_view->priv->has_hover_link = FALSE;
2138 	}
2139 }
2140 
2141 static void
web_view_map(GtkWidget * widget)2142 web_view_map (GtkWidget *widget)
2143 {
2144 	GtkWidget *toplevel;
2145 
2146 	toplevel = gtk_widget_get_toplevel (widget);
2147 
2148 	g_signal_connect (toplevel, "event-after", G_CALLBACK (web_view_toplevel_event_after_cb), widget);
2149 
2150 	GTK_WIDGET_CLASS (e_web_view_parent_class)->map (widget);
2151 }
2152 
2153 static void
web_view_unmap(GtkWidget * widget)2154 web_view_unmap (GtkWidget *widget)
2155 {
2156 	GtkWidget *toplevel;
2157 
2158 	toplevel = gtk_widget_get_toplevel (widget);
2159 
2160 	g_signal_handlers_disconnect_by_func (toplevel, G_CALLBACK (web_view_toplevel_event_after_cb), widget);
2161 
2162 	GTK_WIDGET_CLASS (e_web_view_parent_class)->unmap (widget);
2163 
2164 	e_web_view_status_message (E_WEB_VIEW (widget), NULL);
2165 }
2166 
2167 static void
e_web_view_class_init(EWebViewClass * class)2168 e_web_view_class_init (EWebViewClass *class)
2169 {
2170 	GObjectClass *object_class;
2171 	GtkWidgetClass *widget_class;
2172 
2173 	g_type_class_add_private (class, sizeof (EWebViewPrivate));
2174 
2175 	object_class = G_OBJECT_CLASS (class);
2176 	object_class->constructor = web_view_constructor;
2177 	object_class->set_property = web_view_set_property;
2178 	object_class->get_property = web_view_get_property;
2179 	object_class->dispose = web_view_dispose;
2180 	object_class->finalize = web_view_finalize;
2181 	object_class->constructed = web_view_constructed;
2182 
2183 	widget_class = GTK_WIDGET_CLASS (class);
2184 	widget_class->scroll_event = web_view_scroll_event;
2185 	widget_class->drag_motion = web_view_drag_motion;
2186 	widget_class->drag_drop = web_view_drag_drop;
2187 	widget_class->drag_leave = web_view_drag_leave;
2188 	widget_class->map = web_view_map;
2189 	widget_class->unmap = web_view_unmap;
2190 
2191 	class->hovering_over_link = web_view_hovering_over_link;
2192 	class->link_clicked = web_view_link_clicked;
2193 	class->load_string = web_view_load_string;
2194 	class->load_uri = web_view_load_uri;
2195 	class->suggest_filename = web_view_suggest_filename;
2196 	class->before_popup_event = web_view_before_popup_event;
2197 	class->popup_event = web_view_popup_event;
2198 	class->stop_loading = web_view_stop_loading;
2199 	class->update_actions = web_view_update_actions;
2200 
2201 	g_object_class_install_property (
2202 		object_class,
2203 		PROP_CARET_MODE,
2204 		g_param_spec_boolean (
2205 			"caret-mode",
2206 			"Caret Mode",
2207 			NULL,
2208 			FALSE,
2209 			G_PARAM_READWRITE));
2210 
2211 	/* Inherited from ESelectableInterface; just a fake property here */
2212 	g_object_class_override_property (
2213 		object_class,
2214 		PROP_COPY_TARGET_LIST,
2215 		"copy-target-list");
2216 
2217 	/* Inherited from ESelectableInterface; just a fake property here */
2218 	g_object_class_override_property (
2219 		object_class,
2220 		PROP_PASTE_TARGET_LIST,
2221 		"paste-target-list");
2222 
2223 	g_object_class_install_property (
2224 		object_class,
2225 		PROP_CURSOR_IMAGE_SRC,
2226 		g_param_spec_string (
2227 			"cursor-image-src",
2228 			"Image source uri at the mouse cursor",
2229 			NULL,
2230 			NULL,
2231 			G_PARAM_READWRITE));
2232 
2233 	g_object_class_install_property (
2234 		object_class,
2235 		PROP_DISABLE_PRINTING,
2236 		g_param_spec_boolean (
2237 			"disable-printing",
2238 			"Disable Printing",
2239 			NULL,
2240 			FALSE,
2241 			G_PARAM_READWRITE |
2242 			G_PARAM_CONSTRUCT));
2243 
2244 	g_object_class_install_property (
2245 		object_class,
2246 		PROP_DISABLE_SAVE_TO_DISK,
2247 		g_param_spec_boolean (
2248 			"disable-save-to-disk",
2249 			"Disable Save-to-Disk",
2250 			NULL,
2251 			FALSE,
2252 			G_PARAM_READWRITE |
2253 			G_PARAM_CONSTRUCT));
2254 
2255 	g_object_class_install_property (
2256 		object_class,
2257 		PROP_HAS_SELECTION,
2258 		g_param_spec_boolean (
2259 			"has-selection",
2260 			"Has Selection",
2261 			NULL,
2262 			FALSE,
2263 			G_PARAM_READABLE));
2264 
2265 	g_object_class_install_property (
2266 		object_class,
2267 		PROP_MINIMUM_FONT_SIZE,
2268 		g_param_spec_int (
2269 			"minimum-font-size",
2270 			"Minimum Font Size",
2271 			NULL,
2272 			G_MININT, G_MAXINT, 0,
2273 			G_PARAM_READWRITE));
2274 
2275 	g_object_class_install_property (
2276 		object_class,
2277 		PROP_NEED_INPUT,
2278 		g_param_spec_boolean (
2279 			"need-input",
2280 			"Need Input",
2281 			NULL,
2282 			FALSE,
2283 			G_PARAM_READABLE));
2284 
2285 	g_object_class_install_property (
2286 		object_class,
2287 		PROP_OPEN_PROXY,
2288 		g_param_spec_object (
2289 			"open-proxy",
2290 			"Open Proxy",
2291 			NULL,
2292 			GTK_TYPE_ACTION,
2293 			G_PARAM_READWRITE));
2294 
2295 	g_object_class_install_property (
2296 		object_class,
2297 		PROP_PRINT_PROXY,
2298 		g_param_spec_object (
2299 			"print-proxy",
2300 			"Print Proxy",
2301 			NULL,
2302 			GTK_TYPE_ACTION,
2303 			G_PARAM_READWRITE));
2304 
2305 	g_object_class_install_property (
2306 		object_class,
2307 		PROP_SAVE_AS_PROXY,
2308 		g_param_spec_object (
2309 			"save-as-proxy",
2310 			"Save As Proxy",
2311 			NULL,
2312 			GTK_TYPE_ACTION,
2313 			G_PARAM_READWRITE));
2314 
2315 	g_object_class_install_property (
2316 		object_class,
2317 		PROP_SELECTED_URI,
2318 		g_param_spec_string (
2319 			"selected-uri",
2320 			"Selected URI",
2321 			NULL,
2322 			NULL,
2323 			G_PARAM_READWRITE));
2324 
2325 	signals[NEW_ACTIVITY] = g_signal_new (
2326 		"new-activity",
2327 		G_TYPE_FROM_CLASS (class),
2328 		G_SIGNAL_RUN_LAST,
2329 		G_STRUCT_OFFSET (EWebViewClass, new_activity),
2330 		NULL, NULL,
2331 		g_cclosure_marshal_VOID__OBJECT,
2332 		G_TYPE_NONE, 1,
2333 		E_TYPE_ACTIVITY);
2334 
2335 	signals[POPUP_EVENT] = g_signal_new (
2336 		"popup-event",
2337 		G_TYPE_FROM_CLASS (class),
2338 		G_SIGNAL_RUN_LAST,
2339 		G_STRUCT_OFFSET (EWebViewClass, popup_event),
2340 		g_signal_accumulator_true_handled, NULL,
2341 		NULL,
2342 		G_TYPE_BOOLEAN, 2, G_TYPE_STRING, GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
2343 
2344 	signals[BEFORE_POPUP_EVENT] = g_signal_new (
2345 		"before-popup-event",
2346 		G_TYPE_FROM_CLASS (class),
2347 		G_SIGNAL_RUN_LAST,
2348 		G_STRUCT_OFFSET (EWebViewClass, before_popup_event),
2349 		NULL, NULL,
2350 		g_cclosure_marshal_VOID__STRING,
2351 		G_TYPE_NONE, 1, G_TYPE_STRING);
2352 
2353 	signals[STATUS_MESSAGE] = g_signal_new (
2354 		"status-message",
2355 		G_TYPE_FROM_CLASS (class),
2356 		G_SIGNAL_RUN_LAST,
2357 		G_STRUCT_OFFSET (EWebViewClass, status_message),
2358 		NULL, NULL,
2359 		g_cclosure_marshal_VOID__STRING,
2360 		G_TYPE_NONE, 1,
2361 		G_TYPE_STRING);
2362 
2363 	signals[STOP_LOADING] = g_signal_new (
2364 		"stop-loading",
2365 		G_TYPE_FROM_CLASS (class),
2366 		G_SIGNAL_RUN_LAST,
2367 		G_STRUCT_OFFSET (EWebViewClass, stop_loading),
2368 		NULL, NULL,
2369 		g_cclosure_marshal_VOID__VOID,
2370 		G_TYPE_NONE, 0);
2371 
2372 	signals[UPDATE_ACTIONS] = g_signal_new (
2373 		"update-actions",
2374 		G_TYPE_FROM_CLASS (class),
2375 		G_SIGNAL_RUN_LAST,
2376 		G_STRUCT_OFFSET (EWebViewClass, update_actions),
2377 		NULL, NULL,
2378 		g_cclosure_marshal_VOID__VOID,
2379 		G_TYPE_NONE, 0);
2380 
2381 	/* return TRUE when a signal handler processed the mailto URI */
2382 	signals[PROCESS_MAILTO] = g_signal_new (
2383 		"process-mailto",
2384 		G_TYPE_FROM_CLASS (class),
2385 		G_SIGNAL_RUN_LAST,
2386 		G_STRUCT_OFFSET (EWebViewClass, process_mailto),
2387 		NULL, NULL,
2388 		e_marshal_BOOLEAN__STRING,
2389 		G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
2390 
2391 	/* Expects an empty string to abandon the request,
2392 	   or NULL to keep the passed-in uri,
2393 	   or a new uri to load instead. */
2394 	signals[URI_REQUESTED] = g_signal_new (
2395 		"uri-requested",
2396 		G_TYPE_FROM_CLASS (class),
2397 		G_SIGNAL_RUN_LAST,
2398 		G_STRUCT_OFFSET (EWebViewClass, uri_requested),
2399 		NULL, NULL, NULL,
2400 		G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_POINTER);
2401 
2402 	signals[CONTENT_LOADED] = g_signal_new (
2403 		"content-loaded",
2404 		G_TYPE_FROM_CLASS (class),
2405 		G_SIGNAL_RUN_LAST,
2406 		G_STRUCT_OFFSET (EWebViewClass, content_loaded),
2407 		NULL, NULL, NULL,
2408 		G_TYPE_NONE, 1, G_TYPE_STRING);
2409 }
2410 
2411 static void
e_web_view_alert_sink_init(EAlertSinkInterface * iface)2412 e_web_view_alert_sink_init (EAlertSinkInterface *iface)
2413 {
2414 	iface->submit_alert = web_view_submit_alert;
2415 }
2416 
2417 static void
e_web_view_selectable_init(ESelectableInterface * iface)2418 e_web_view_selectable_init (ESelectableInterface *iface)
2419 {
2420 	iface->update_actions = web_view_selectable_update_actions;
2421 	iface->cut_clipboard = web_view_selectable_cut_clipboard;
2422 	iface->copy_clipboard = web_view_selectable_copy_clipboard;
2423 	iface->paste_clipboard = web_view_selectable_paste_clipboard;
2424 	iface->select_all = web_view_selectable_select_all;
2425 }
2426 
2427 static void
e_web_view_init(EWebView * web_view)2428 e_web_view_init (EWebView *web_view)
2429 {
2430 	GtkUIManager *ui_manager;
2431 	GtkActionGroup *action_group;
2432 	EPopupAction *popup_action;
2433 	GSettings *settings;
2434 	const gchar *domain = GETTEXT_PACKAGE;
2435 	const gchar *id;
2436 	gulong handler_id;
2437 	GError *error = NULL;
2438 
2439 	web_view->priv = E_WEB_VIEW_GET_PRIVATE (web_view);
2440 
2441 	web_view->priv->highlights_enabled = TRUE;
2442 	web_view->priv->old_settings = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
2443 	web_view->priv->scheme_handlers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
2444 
2445 	g_signal_connect (
2446 		web_view, "context-menu",
2447 		G_CALLBACK (web_view_context_menu_cb), NULL);
2448 
2449 	g_signal_connect (
2450 		web_view, "mouse-target-changed",
2451 		G_CALLBACK (web_view_mouse_target_changed_cb), NULL);
2452 
2453 	g_signal_connect (
2454 		web_view, "decide-policy",
2455 		G_CALLBACK (web_view_decide_policy_cb),
2456 		NULL);
2457 
2458 	g_signal_connect (
2459 		web_view, "load-changed",
2460 		G_CALLBACK (web_view_load_changed_cb), NULL);
2461 
2462 	g_signal_connect (
2463 		web_view, "style-updated",
2464 		G_CALLBACK (style_updated_cb), NULL);
2465 
2466 	g_signal_connect (
2467 		web_view, "state-flags-changed",
2468 		G_CALLBACK (style_updated_cb), NULL);
2469 
2470 	ui_manager = gtk_ui_manager_new ();
2471 	web_view->priv->ui_manager = ui_manager;
2472 
2473 	g_signal_connect_swapped (
2474 		ui_manager, "connect-proxy",
2475 		G_CALLBACK (web_view_connect_proxy_cb), web_view);
2476 
2477 	settings = e_util_ref_settings ("org.gnome.desktop.interface");
2478 	web_view->priv->font_settings = g_object_ref (settings);
2479 	handler_id = g_signal_connect_swapped (
2480 		settings, "changed::font-name",
2481 		G_CALLBACK (e_web_view_test_change_and_update_fonts_cb), web_view);
2482 	web_view->priv->font_name_changed_handler_id = handler_id;
2483 	handler_id = g_signal_connect_swapped (
2484 		settings, "changed::monospace-font-name",
2485 		G_CALLBACK (e_web_view_test_change_and_update_fonts_cb), web_view);
2486 	web_view->priv->monospace_font_name_changed_handler_id = handler_id;
2487 	g_object_unref (settings);
2488 
2489 	action_group = gtk_action_group_new ("uri");
2490 	gtk_action_group_set_translation_domain (action_group, domain);
2491 	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
2492 	g_object_unref (action_group);
2493 
2494 	gtk_action_group_add_actions (
2495 		action_group, uri_entries,
2496 		G_N_ELEMENTS (uri_entries), web_view);
2497 
2498 	action_group = gtk_action_group_new ("http");
2499 	gtk_action_group_set_translation_domain (action_group, domain);
2500 	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
2501 	g_object_unref (action_group);
2502 
2503 	gtk_action_group_add_actions (
2504 		action_group, http_entries,
2505 		G_N_ELEMENTS (http_entries), web_view);
2506 
2507 	action_group = gtk_action_group_new ("mailto");
2508 	gtk_action_group_set_translation_domain (action_group, domain);
2509 	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
2510 	g_object_unref (action_group);
2511 
2512 	gtk_action_group_add_actions (
2513 		action_group, mailto_entries,
2514 		G_N_ELEMENTS (mailto_entries), web_view);
2515 
2516 	action_group = gtk_action_group_new ("image");
2517 	gtk_action_group_set_translation_domain (action_group, domain);
2518 	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
2519 	g_object_unref (action_group);
2520 
2521 	gtk_action_group_add_actions (
2522 		action_group, image_entries,
2523 		G_N_ELEMENTS (image_entries), web_view);
2524 
2525 	action_group = gtk_action_group_new ("selection");
2526 	gtk_action_group_set_translation_domain (action_group, domain);
2527 	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
2528 	g_object_unref (action_group);
2529 
2530 	gtk_action_group_add_actions (
2531 		action_group, selection_entries,
2532 		G_N_ELEMENTS (selection_entries), web_view);
2533 
2534 	action_group = gtk_action_group_new ("standard");
2535 	gtk_action_group_set_translation_domain (action_group, domain);
2536 	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
2537 	g_object_unref (action_group);
2538 
2539 	gtk_action_group_add_actions (
2540 		action_group, standard_entries,
2541 		G_N_ELEMENTS (standard_entries), web_view);
2542 
2543 	popup_action = e_popup_action_new ("open");
2544 	gtk_action_group_add_action (action_group, GTK_ACTION (popup_action));
2545 	g_object_unref (popup_action);
2546 
2547 	e_binding_bind_property (
2548 		web_view, "open-proxy",
2549 		popup_action, "related-action",
2550 		G_BINDING_BIDIRECTIONAL |
2551 		G_BINDING_SYNC_CREATE);
2552 
2553 	/* Support lockdown. */
2554 
2555 	action_group = gtk_action_group_new ("lockdown-printing");
2556 	gtk_action_group_set_translation_domain (action_group, domain);
2557 	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
2558 	g_object_unref (action_group);
2559 
2560 	popup_action = e_popup_action_new ("print");
2561 	gtk_action_group_add_action (action_group, GTK_ACTION (popup_action));
2562 	g_object_unref (popup_action);
2563 
2564 	e_binding_bind_property (
2565 		web_view, "print-proxy",
2566 		popup_action, "related-action",
2567 		G_BINDING_BIDIRECTIONAL |
2568 		G_BINDING_SYNC_CREATE);
2569 
2570 	action_group = gtk_action_group_new ("lockdown-save-to-disk");
2571 	gtk_action_group_set_translation_domain (action_group, domain);
2572 	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
2573 	g_object_unref (action_group);
2574 
2575 	popup_action = e_popup_action_new ("save-as");
2576 	gtk_action_group_add_action (action_group, GTK_ACTION (popup_action));
2577 	g_object_unref (popup_action);
2578 
2579 	e_binding_bind_property (
2580 		web_view, "save-as-proxy",
2581 		popup_action, "related-action",
2582 		G_BINDING_BIDIRECTIONAL |
2583 		G_BINDING_SYNC_CREATE);
2584 
2585 	/* Because we are loading from a hard-coded string, there is
2586 	 * no chance of I/O errors.  Failure here implies a malformed
2587 	 * UI definition.  Full stop. */
2588 	gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
2589 	if (error != NULL)
2590 		g_error ("%s", error->message);
2591 
2592 	id = "org.gnome.evolution.webview";
2593 	e_plugin_ui_register_manager (ui_manager, id, web_view);
2594 	e_plugin_ui_enable_manager (ui_manager, id);
2595 
2596 	web_view->priv->element_clicked_cbs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref);
2597 
2598 	web_view->priv->cancellable = NULL;
2599 }
2600 
2601 GtkWidget *
e_web_view_new(void)2602 e_web_view_new (void)
2603 {
2604 	return g_object_new (
2605 		E_TYPE_WEB_VIEW,
2606 		NULL);
2607 }
2608 
2609 void
e_web_view_clear(EWebView * web_view)2610 e_web_view_clear (EWebView *web_view)
2611 {
2612 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
2613 
2614 	e_web_view_replace_load_cancellable (web_view, FALSE);
2615 
2616 	e_web_view_load_string (web_view,
2617 		"<html>"
2618 		"<head>"
2619 		"<meta name=\"color-scheme\" content=\"light dark\">"
2620 		"</head>"
2621 		"<body class=\"-e-web-view-background-color -e-web-view-text-color\"></body>"
2622 		"</html>");
2623 }
2624 
2625 void
e_web_view_load_string(EWebView * web_view,const gchar * string)2626 e_web_view_load_string (EWebView *web_view,
2627                         const gchar *string)
2628 {
2629 	EWebViewClass *class;
2630 
2631 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
2632 
2633 	class = E_WEB_VIEW_GET_CLASS (web_view);
2634 	g_return_if_fail (class != NULL);
2635 	g_return_if_fail (class->load_string != NULL);
2636 
2637 	e_web_view_replace_load_cancellable (web_view, TRUE);
2638 
2639 	class->load_string (web_view, string);
2640 }
2641 
2642 void
e_web_view_load_uri(EWebView * web_view,const gchar * uri)2643 e_web_view_load_uri (EWebView *web_view,
2644                      const gchar *uri)
2645 {
2646 	EWebViewClass *class;
2647 
2648 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
2649 
2650 	class = E_WEB_VIEW_GET_CLASS (web_view);
2651 	g_return_if_fail (class != NULL);
2652 	g_return_if_fail (class->load_uri != NULL);
2653 
2654 	e_web_view_replace_load_cancellable (web_view, TRUE);
2655 
2656 	class->load_uri (web_view, uri);
2657 }
2658 
2659 /**
2660  * e_web_view_suggest_filename:
2661  * @web_view: an #EWebView
2662  * @uri: a URI string
2663  *
2664  * Attempts to derive a suggested filename from the @uri for use in a
2665  * "Save As" dialog.
2666  *
2667  * By default the suggested filename is the last path segment of the @uri
2668  * (unless @uri looks like a query), but subclasses can use other mechanisms
2669  * for custom URI schemes.  For example, "cid:" URIs in an email message may
2670  * refer to a MIME part with a suggested filename in its Content-Disposition
2671  * header.
2672  *
2673  * The returned string should be freed with g_free() when finished with it,
2674  * but callers should also be prepared for the function to return %NULL if
2675  * a filename cannot be determined.
2676  *
2677  * Returns: a suggested filename, or %NULL
2678  **/
2679 gchar *
e_web_view_suggest_filename(EWebView * web_view,const gchar * uri)2680 e_web_view_suggest_filename (EWebView *web_view,
2681                              const gchar *uri)
2682 {
2683 	EWebViewClass *class;
2684 	gchar *filename;
2685 
2686 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
2687 	g_return_val_if_fail (uri != NULL, NULL);
2688 
2689 	class = E_WEB_VIEW_GET_CLASS (web_view);
2690 	g_return_val_if_fail (class != NULL, NULL);
2691 	g_return_val_if_fail (class->suggest_filename != NULL, NULL);
2692 
2693 	filename = class->suggest_filename (web_view, uri);
2694 
2695 	if (filename != NULL)
2696 		e_util_make_safe_filename (filename);
2697 
2698 	return filename;
2699 }
2700 
2701 void
e_web_view_reload(EWebView * web_view)2702 e_web_view_reload (EWebView *web_view)
2703 {
2704 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
2705 
2706 	e_web_view_replace_load_cancellable (web_view, TRUE);
2707 
2708 	webkit_web_view_reload (WEBKIT_WEB_VIEW (web_view));
2709 }
2710 
2711 gboolean
e_web_view_get_caret_mode(EWebView * web_view)2712 e_web_view_get_caret_mode (EWebView *web_view)
2713 {
2714 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
2715 
2716 	return web_view->priv->caret_mode;
2717 }
2718 
2719 void
e_web_view_set_caret_mode(EWebView * web_view,gboolean caret_mode)2720 e_web_view_set_caret_mode (EWebView *web_view,
2721                            gboolean caret_mode)
2722 {
2723 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
2724 
2725 	if (web_view->priv->caret_mode == caret_mode)
2726 		return;
2727 
2728 	web_view->priv->caret_mode = caret_mode;
2729 
2730 	g_object_notify (G_OBJECT (web_view), "caret-mode");
2731 }
2732 
2733 GtkTargetList *
e_web_view_get_copy_target_list(EWebView * web_view)2734 e_web_view_get_copy_target_list (EWebView *web_view)
2735 {
2736 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
2737 
2738 	return NULL;
2739 	/* FIXME WK2 */
2740 	/*return webkit_web_view_get_copy_target_list (
2741 		WEBKIT_WEB_VIEW (web_view));*/
2742 }
2743 
2744 gboolean
e_web_view_get_disable_printing(EWebView * web_view)2745 e_web_view_get_disable_printing (EWebView *web_view)
2746 {
2747 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
2748 
2749 	return web_view->priv->disable_printing;
2750 }
2751 
2752 void
e_web_view_set_disable_printing(EWebView * web_view,gboolean disable_printing)2753 e_web_view_set_disable_printing (EWebView *web_view,
2754                                  gboolean disable_printing)
2755 {
2756 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
2757 
2758 	if (web_view->priv->disable_printing == disable_printing)
2759 		return;
2760 
2761 	web_view->priv->disable_printing = disable_printing;
2762 
2763 	g_object_notify (G_OBJECT (web_view), "disable-printing");
2764 }
2765 
2766 gboolean
e_web_view_get_disable_save_to_disk(EWebView * web_view)2767 e_web_view_get_disable_save_to_disk (EWebView *web_view)
2768 {
2769 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
2770 
2771 	return web_view->priv->disable_save_to_disk;
2772 }
2773 
2774 void
e_web_view_set_disable_save_to_disk(EWebView * web_view,gboolean disable_save_to_disk)2775 e_web_view_set_disable_save_to_disk (EWebView *web_view,
2776                                      gboolean disable_save_to_disk)
2777 {
2778 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
2779 
2780 	if (web_view->priv->disable_save_to_disk == disable_save_to_disk)
2781 		return;
2782 
2783 	web_view->priv->disable_save_to_disk = disable_save_to_disk;
2784 
2785 	g_object_notify (G_OBJECT (web_view), "disable-save-to-disk");
2786 }
2787 
2788 gboolean
e_web_view_get_editable(EWebView * web_view)2789 e_web_view_get_editable (EWebView *web_view)
2790 {
2791 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
2792 
2793 	return webkit_web_view_is_editable (WEBKIT_WEB_VIEW (web_view));
2794 }
2795 
2796 void
e_web_view_set_editable(EWebView * web_view,gboolean editable)2797 e_web_view_set_editable (EWebView *web_view,
2798                          gboolean editable)
2799 {
2800 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
2801 
2802 	webkit_web_view_set_editable (WEBKIT_WEB_VIEW (web_view), editable);
2803 }
2804 
2805 gboolean
e_web_view_get_need_input(EWebView * web_view)2806 e_web_view_get_need_input (EWebView *web_view)
2807 {
2808 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
2809 
2810 	return web_view->priv->need_input;
2811 }
2812 
2813 const gchar *
e_web_view_get_selected_uri(EWebView * web_view)2814 e_web_view_get_selected_uri (EWebView *web_view)
2815 {
2816 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
2817 
2818 	return web_view->priv->selected_uri;
2819 }
2820 
2821 void
e_web_view_set_selected_uri(EWebView * web_view,const gchar * selected_uri)2822 e_web_view_set_selected_uri (EWebView *web_view,
2823                              const gchar *selected_uri)
2824 {
2825 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
2826 
2827 	if (g_strcmp0 (web_view->priv->selected_uri, selected_uri) == 0)
2828 		return;
2829 
2830 	g_free (web_view->priv->selected_uri);
2831 	web_view->priv->selected_uri = g_strdup (selected_uri);
2832 
2833 	g_object_notify (G_OBJECT (web_view), "selected-uri");
2834 }
2835 
2836 const gchar *
e_web_view_get_cursor_image_src(EWebView * web_view)2837 e_web_view_get_cursor_image_src (EWebView *web_view)
2838 {
2839 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
2840 
2841 	return web_view->priv->cursor_image_src;
2842 }
2843 
2844 void
e_web_view_set_cursor_image_src(EWebView * web_view,const gchar * src_uri)2845 e_web_view_set_cursor_image_src (EWebView *web_view,
2846                                  const gchar *src_uri)
2847 {
2848 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
2849 
2850 	if (g_strcmp0 (web_view->priv->cursor_image_src, src_uri) == 0)
2851 		return;
2852 
2853 	g_free (web_view->priv->cursor_image_src);
2854 	web_view->priv->cursor_image_src = g_strdup (src_uri);
2855 
2856 	g_object_notify (G_OBJECT (web_view), "cursor-image-src");
2857 }
2858 
2859 GtkAction *
e_web_view_get_open_proxy(EWebView * web_view)2860 e_web_view_get_open_proxy (EWebView *web_view)
2861 {
2862 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
2863 
2864 	return web_view->priv->open_proxy;
2865 }
2866 
2867 void
e_web_view_set_open_proxy(EWebView * web_view,GtkAction * open_proxy)2868 e_web_view_set_open_proxy (EWebView *web_view,
2869                            GtkAction *open_proxy)
2870 {
2871 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
2872 
2873 	if (web_view->priv->open_proxy == open_proxy)
2874 		return;
2875 
2876 	if (open_proxy != NULL) {
2877 		g_return_if_fail (GTK_IS_ACTION (open_proxy));
2878 		g_object_ref (open_proxy);
2879 	}
2880 
2881 	if (web_view->priv->open_proxy != NULL)
2882 		g_object_unref (web_view->priv->open_proxy);
2883 
2884 	web_view->priv->open_proxy = open_proxy;
2885 
2886 	g_object_notify (G_OBJECT (web_view), "open-proxy");
2887 }
2888 
2889 GtkTargetList *
e_web_view_get_paste_target_list(EWebView * web_view)2890 e_web_view_get_paste_target_list (EWebView *web_view)
2891 {
2892 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
2893 
2894 	/* FIXME WK2
2895 	return webkit_web_view_get_paste_target_list (
2896 		WEBKIT_WEB_VIEW (web_view)); */
2897 	return NULL;
2898 }
2899 
2900 GtkAction *
e_web_view_get_print_proxy(EWebView * web_view)2901 e_web_view_get_print_proxy (EWebView *web_view)
2902 {
2903 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
2904 
2905 	return web_view->priv->print_proxy;
2906 }
2907 
2908 void
e_web_view_set_print_proxy(EWebView * web_view,GtkAction * print_proxy)2909 e_web_view_set_print_proxy (EWebView *web_view,
2910                             GtkAction *print_proxy)
2911 {
2912 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
2913 
2914 	if (web_view->priv->print_proxy == print_proxy)
2915 		return;
2916 
2917 	if (print_proxy != NULL) {
2918 		g_return_if_fail (GTK_IS_ACTION (print_proxy));
2919 		g_object_ref (print_proxy);
2920 	}
2921 
2922 	if (web_view->priv->print_proxy != NULL)
2923 		g_object_unref (web_view->priv->print_proxy);
2924 
2925 	web_view->priv->print_proxy = print_proxy;
2926 
2927 	g_object_notify (G_OBJECT (web_view), "print-proxy");
2928 }
2929 
2930 GtkAction *
e_web_view_get_save_as_proxy(EWebView * web_view)2931 e_web_view_get_save_as_proxy (EWebView *web_view)
2932 {
2933 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
2934 
2935 	return web_view->priv->save_as_proxy;
2936 }
2937 
2938 void
e_web_view_set_save_as_proxy(EWebView * web_view,GtkAction * save_as_proxy)2939 e_web_view_set_save_as_proxy (EWebView *web_view,
2940                               GtkAction *save_as_proxy)
2941 {
2942 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
2943 
2944 	if (web_view->priv->save_as_proxy == save_as_proxy)
2945 		return;
2946 
2947 	if (save_as_proxy != NULL) {
2948 		g_return_if_fail (GTK_IS_ACTION (save_as_proxy));
2949 		g_object_ref (save_as_proxy);
2950 	}
2951 
2952 	if (web_view->priv->save_as_proxy != NULL)
2953 		g_object_unref (web_view->priv->save_as_proxy);
2954 
2955 	web_view->priv->save_as_proxy = save_as_proxy;
2956 
2957 	g_object_notify (G_OBJECT (web_view), "save-as-proxy");
2958 }
2959 
2960 void
e_web_view_get_last_popup_place(EWebView * web_view,gchar ** out_iframe_src,gchar ** out_iframe_id,gchar ** out_element_id,gchar ** out_link_uri)2961 e_web_view_get_last_popup_place (EWebView *web_view,
2962 				 gchar **out_iframe_src,
2963 				 gchar **out_iframe_id,
2964 				 gchar **out_element_id,
2965 				 gchar **out_link_uri)
2966 {
2967 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
2968 
2969 	if (out_iframe_src)
2970 		*out_iframe_src = g_strdup (web_view->priv->last_popup_iframe_src);
2971 
2972 	if (out_iframe_id)
2973 		*out_iframe_id = g_strdup (web_view->priv->last_popup_iframe_id);
2974 
2975 	if (out_element_id)
2976 		*out_element_id = g_strdup (web_view->priv->last_popup_element_id);
2977 
2978 	if (out_link_uri)
2979 		*out_link_uri = g_strdup (web_view->priv->last_popup_link_uri);
2980 }
2981 
2982 void
e_web_view_add_highlight(EWebView * web_view,const gchar * highlight)2983 e_web_view_add_highlight (EWebView *web_view,
2984                           const gchar *highlight)
2985 {
2986 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
2987 	g_return_if_fail (highlight && *highlight);
2988 
2989 	g_queue_push_tail (
2990 		&web_view->priv->highlights,
2991 		g_strdup (highlight));
2992 
2993 	webkit_find_controller_search (
2994 		web_view->priv->find_controller,
2995 		highlight,
2996 		WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE,
2997 		G_MAXUINT);
2998 }
2999 
3000 void
e_web_view_clear_highlights(EWebView * web_view)3001 e_web_view_clear_highlights (EWebView *web_view)
3002 {
3003 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3004 
3005 	webkit_find_controller_search_finish (web_view->priv->find_controller);
3006 
3007 	while (!g_queue_is_empty (&web_view->priv->highlights))
3008 		g_free (g_queue_pop_head (&web_view->priv->highlights));
3009 }
3010 
3011 void
e_web_view_update_highlights(EWebView * web_view)3012 e_web_view_update_highlights (EWebView *web_view)
3013 {
3014 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3015 
3016 	web_view->priv->highlights_enabled = TRUE;
3017 	web_view_update_document_highlights (web_view);
3018 }
3019 
3020 void
e_web_view_disable_highlights(EWebView * web_view)3021 e_web_view_disable_highlights (EWebView *web_view)
3022 {
3023 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3024 
3025 	web_view->priv->highlights_enabled = FALSE;
3026 }
3027 
3028 GtkAction *
e_web_view_get_action(EWebView * web_view,const gchar * action_name)3029 e_web_view_get_action (EWebView *web_view,
3030                        const gchar *action_name)
3031 {
3032 	GtkUIManager *ui_manager;
3033 
3034 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
3035 	g_return_val_if_fail (action_name != NULL, NULL);
3036 
3037 	ui_manager = e_web_view_get_ui_manager (web_view);
3038 
3039 	return e_lookup_action (ui_manager, action_name);
3040 }
3041 
3042 GtkActionGroup *
e_web_view_get_action_group(EWebView * web_view,const gchar * group_name)3043 e_web_view_get_action_group (EWebView *web_view,
3044                              const gchar *group_name)
3045 {
3046 	GtkUIManager *ui_manager;
3047 
3048 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
3049 	g_return_val_if_fail (group_name != NULL, NULL);
3050 
3051 	ui_manager = e_web_view_get_ui_manager (web_view);
3052 
3053 	return e_lookup_action_group (ui_manager, group_name);
3054 }
3055 
3056 void
e_web_view_copy_clipboard(EWebView * web_view)3057 e_web_view_copy_clipboard (EWebView *web_view)
3058 {
3059 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3060 
3061 	webkit_web_view_execute_editing_command (
3062 		WEBKIT_WEB_VIEW (web_view), WEBKIT_EDITING_COMMAND_COPY);
3063 }
3064 
3065 void
e_web_view_cut_clipboard(EWebView * web_view)3066 e_web_view_cut_clipboard (EWebView *web_view)
3067 {
3068 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3069 
3070 	webkit_web_view_execute_editing_command (
3071 		WEBKIT_WEB_VIEW (web_view), WEBKIT_EDITING_COMMAND_CUT);
3072 }
3073 
3074 gboolean
e_web_view_has_selection(EWebView * web_view)3075 e_web_view_has_selection (EWebView *web_view)
3076 {
3077 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
3078 
3079 	return web_view->priv->has_selection;
3080 }
3081 
3082 void
e_web_view_paste_clipboard(EWebView * web_view)3083 e_web_view_paste_clipboard (EWebView *web_view)
3084 {
3085 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3086 
3087 	webkit_web_view_execute_editing_command (
3088 		WEBKIT_WEB_VIEW (web_view), WEBKIT_EDITING_COMMAND_PASTE);
3089 }
3090 
3091 gboolean
e_web_view_scroll_forward(EWebView * web_view)3092 e_web_view_scroll_forward (EWebView *web_view)
3093 {
3094 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
3095 /* FIXME WK2
3096 	webkit_web_view_move_cursor (
3097 		WEBKIT_WEB_VIEW (web_view), GTK_MOVEMENT_PAGES, 1);
3098 */
3099 	return TRUE;  /* XXX This means nothing. */
3100 }
3101 
3102 gboolean
e_web_view_scroll_backward(EWebView * web_view)3103 e_web_view_scroll_backward (EWebView *web_view)
3104 {
3105 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
3106 /* FIXME WK2
3107 	webkit_web_view_move_cursor (
3108 		WEBKIT_WEB_VIEW (web_view), GTK_MOVEMENT_PAGES, -1);
3109 */
3110 	return TRUE;  /* XXX This means nothing. */
3111 }
3112 
3113 void
e_web_view_select_all(EWebView * web_view)3114 e_web_view_select_all (EWebView *web_view)
3115 {
3116 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3117 
3118 	webkit_web_view_execute_editing_command (
3119 		WEBKIT_WEB_VIEW (web_view), WEBKIT_EDITING_COMMAND_SELECT_ALL);
3120 }
3121 
3122 void
e_web_view_unselect_all(EWebView * web_view)3123 e_web_view_unselect_all (EWebView *web_view)
3124 {
3125 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3126 
3127 	webkit_web_view_execute_editing_command (WEBKIT_WEB_VIEW (web_view), "Unselect");
3128 }
3129 
3130 void
e_web_view_zoom_100(EWebView * web_view)3131 e_web_view_zoom_100 (EWebView *web_view)
3132 {
3133 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3134 
3135 	webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (web_view), 1.0);
3136 }
3137 
3138 void
e_web_view_zoom_in(EWebView * web_view)3139 e_web_view_zoom_in (EWebView *web_view)
3140 {
3141 	gdouble zoom_level;
3142 
3143 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3144 
3145 	/* There is no webkit_web_view_zoom_in function in WK2, so emulate it */
3146 	zoom_level = webkit_web_view_get_zoom_level (WEBKIT_WEB_VIEW (web_view));
3147 	/* zoom-step in WK1 was 0.1 */
3148 	zoom_level += 0.1;
3149 	if (zoom_level < 4.9999)
3150 		webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (web_view), zoom_level);
3151 }
3152 
3153 void
e_web_view_zoom_out(EWebView * web_view)3154 e_web_view_zoom_out (EWebView *web_view)
3155 {
3156 	gdouble zoom_level;
3157 
3158 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3159 
3160 	/* There is no webkit_web_view_zoom_out function in WK2, so emulate it */
3161 	zoom_level = webkit_web_view_get_zoom_level (WEBKIT_WEB_VIEW (web_view));
3162 	/* zoom-step in WK1 was 0.1 */
3163 	zoom_level -= 0.1;
3164 	if (zoom_level > 0.7999)
3165 		webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (web_view), zoom_level);
3166 }
3167 
3168 GtkUIManager *
e_web_view_get_ui_manager(EWebView * web_view)3169 e_web_view_get_ui_manager (EWebView *web_view)
3170 {
3171 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
3172 
3173 	return web_view->priv->ui_manager;
3174 }
3175 
3176 static void
e_web_view_popup_menu_deactivate_cb(GtkMenu * popup_menu,GtkWidget * web_view)3177 e_web_view_popup_menu_deactivate_cb (GtkMenu *popup_menu,
3178 				     GtkWidget *web_view)
3179 {
3180 	g_return_if_fail (GTK_IS_MENU (popup_menu));
3181 
3182 	g_signal_handlers_disconnect_by_func (popup_menu, e_web_view_popup_menu_deactivate_cb, web_view);
3183 	gtk_menu_detach (popup_menu);
3184 }
3185 
3186 GtkWidget *
e_web_view_get_popup_menu(EWebView * web_view)3187 e_web_view_get_popup_menu (EWebView *web_view)
3188 {
3189 	GtkUIManager *ui_manager;
3190 	GtkWidget *menu;
3191 
3192 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
3193 
3194 	ui_manager = e_web_view_get_ui_manager (web_view);
3195 	menu = gtk_ui_manager_get_widget (ui_manager, "/context");
3196 	g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
3197 
3198 	g_warn_if_fail (!gtk_menu_get_attach_widget (GTK_MENU (menu)));
3199 
3200 	gtk_menu_attach_to_widget (GTK_MENU (menu),
3201 				   GTK_WIDGET (web_view),
3202 				   NULL);
3203 
3204 	g_signal_connect (
3205 		menu, "deactivate",
3206 		G_CALLBACK (e_web_view_popup_menu_deactivate_cb), web_view);
3207 
3208 	return menu;
3209 }
3210 
3211 void
e_web_view_show_popup_menu(EWebView * web_view,GdkEvent * event)3212 e_web_view_show_popup_menu (EWebView *web_view,
3213 			    GdkEvent *event)
3214 {
3215 	GtkWidget *menu;
3216 
3217 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3218 
3219 	e_web_view_update_actions (web_view);
3220 
3221 	menu = e_web_view_get_popup_menu (web_view);
3222 
3223 	gtk_menu_popup_at_pointer (GTK_MENU (menu), event);
3224 }
3225 
3226 /**
3227  * e_web_view_new_activity:
3228  * @web_view: an #EWebView
3229  *
3230  * Returns a new #EActivity for an #EWebView-related asynchronous operation,
3231  * and emits the #EWebView::new-activity signal.  By default the #EActivity
3232  * comes loaded with a #GCancellable and sets the @web_view itself as the
3233  * #EActivity:alert-sink (which means alerts are displayed directly in the
3234  * content area).  The signal emission allows the #EActivity to be further
3235  * customized and/or tracked by the application.
3236  *
3237  * Returns: an #EActivity
3238  **/
3239 EActivity *
e_web_view_new_activity(EWebView * web_view)3240 e_web_view_new_activity (EWebView *web_view)
3241 {
3242 	EActivity *activity;
3243 	EAlertSink *alert_sink;
3244 	GCancellable *cancellable;
3245 
3246 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
3247 
3248 	activity = e_activity_new ();
3249 
3250 	alert_sink = E_ALERT_SINK (web_view);
3251 	e_activity_set_alert_sink (activity, alert_sink);
3252 
3253 	cancellable = g_cancellable_new ();
3254 	e_activity_set_cancellable (activity, cancellable);
3255 	g_object_unref (cancellable);
3256 
3257 	g_signal_emit (web_view, signals[NEW_ACTIVITY], 0, activity);
3258 
3259 	return activity;
3260 }
3261 
3262 void
e_web_view_status_message(EWebView * web_view,const gchar * status_message)3263 e_web_view_status_message (EWebView *web_view,
3264                            const gchar *status_message)
3265 {
3266 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3267 
3268 	g_signal_emit (web_view, signals[STATUS_MESSAGE], 0, status_message);
3269 }
3270 
3271 void
e_web_view_stop_loading(EWebView * web_view)3272 e_web_view_stop_loading (EWebView *web_view)
3273 {
3274 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3275 
3276 	g_signal_emit (web_view, signals[STOP_LOADING], 0);
3277 }
3278 
3279 void
e_web_view_update_actions(EWebView * web_view)3280 e_web_view_update_actions (EWebView *web_view)
3281 {
3282 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3283 
3284 	g_signal_emit (web_view, signals[UPDATE_ACTIONS], 0);
3285 }
3286 
3287 const gchar *
e_web_view_get_citation_color_for_level(gint level)3288 e_web_view_get_citation_color_for_level (gint level)
3289 {
3290 	/* Block quote border colors are borrowed from Thunderbird. */
3291 	static const gchar *citation_color_levels[5] = {
3292 		"rgb(233,185,110)",	/* level 5 - Chocolate 1 */
3293 		"rgb(114,159,207)",	/* level 1 - Sky Blue 1 */
3294 		"rgb(173,127,168)",	/* level 2 - Plum 1 */
3295 		"rgb(138,226,52)",	/* level 3 - Chameleon 1 */
3296 		"rgb(252,175,62)",	/* level 4 - Orange 1 */
3297 	};
3298 
3299 	g_return_val_if_fail (level > 0, citation_color_levels[1]);
3300 
3301 	return citation_color_levels[level % 5];
3302 }
3303 
3304 void
e_web_view_update_fonts_settings(GSettings * font_settings,PangoFontDescription * ms_font,PangoFontDescription * vw_font,GtkWidget * view_widget)3305 e_web_view_update_fonts_settings (GSettings *font_settings,
3306                                   PangoFontDescription *ms_font,
3307                                   PangoFontDescription *vw_font,
3308                                   GtkWidget *view_widget)
3309 {
3310 	gboolean clean_ms = FALSE, clean_vw = FALSE;
3311 	const gchar *styles[] = { "normal", "oblique", "italic" };
3312 	gchar fsbuff[G_ASCII_DTOSTR_BUF_SIZE];
3313 	GdkColor *link = NULL;
3314 	GdkColor *visited = NULL;
3315 	GString *stylesheet;
3316 	GtkStyleContext *context;
3317 	PangoFontDescription *ms, *vw;
3318 	WebKitSettings *wk_settings;
3319 	WebKitUserContentManager *manager;
3320 	WebKitUserStyleSheet *style_sheet;
3321 
3322 	if (!ms_font) {
3323 		gchar *font;
3324 
3325 		font = g_settings_get_string (
3326 			font_settings,
3327 			"monospace-font-name");
3328 
3329 		ms = pango_font_description_from_string (
3330 			(font != NULL) ? font : "monospace 10");
3331 
3332 		clean_ms = TRUE;
3333 
3334 		g_free (font);
3335 	} else
3336 		ms = ms_font;
3337 
3338 	if (!vw_font) {
3339 		gchar *font;
3340 
3341 		font = g_settings_get_string (
3342 			font_settings,
3343 			"font-name");
3344 
3345 		vw = pango_font_description_from_string (
3346 			(font != NULL) ? font : "serif 10");
3347 
3348 		clean_vw = TRUE;
3349 
3350 		g_free (font);
3351 	} else
3352 		vw = vw_font;
3353 
3354 	stylesheet = g_string_new ("");
3355 	g_ascii_dtostr (fsbuff, G_ASCII_DTOSTR_BUF_SIZE,
3356 		((gdouble) pango_font_description_get_size (vw)) / PANGO_SCALE);
3357 
3358 	g_string_append_printf (
3359 		stylesheet,
3360 		"body {\n"
3361 		"  font-family: '%s';\n"
3362 		"  font-size: %spt;\n"
3363 		"  font-weight: %d;\n"
3364 		"  font-style: %s;\n",
3365 		pango_font_description_get_family (vw),
3366 		fsbuff,
3367 		pango_font_description_get_weight (vw),
3368 		styles[pango_font_description_get_style (vw)]);
3369 
3370 	g_string_append (stylesheet, "}\n");
3371 
3372 	g_ascii_dtostr (fsbuff, G_ASCII_DTOSTR_BUF_SIZE,
3373 		((gdouble) pango_font_description_get_size (ms)) / PANGO_SCALE);
3374 
3375 	g_string_append_printf (
3376 		stylesheet,
3377 		"pre,code,.pre {\n"
3378 		"  font-family: '%s';\n"
3379 		"  font-size: %spt;\n"
3380 		"  font-weight: %d;\n"
3381 		"  font-style: %s;\n"
3382 		"  margin: 0px;\n"
3383 		"}\n",
3384 		pango_font_description_get_family (ms),
3385 		fsbuff,
3386 		pango_font_description_get_weight (ms),
3387 		styles[pango_font_description_get_style (ms)]);
3388 
3389 	if (view_widget) {
3390 		context = gtk_widget_get_style_context (view_widget);
3391 		gtk_style_context_get_style (
3392 			context,
3393 			"link-color", &link,
3394 			"visited-link-color", &visited,
3395 			NULL);
3396 
3397 		if (link == NULL) {
3398 			GdkRGBA rgba;
3399 			GtkStateFlags state;
3400 
3401 			link = g_slice_new0 (GdkColor);
3402 			link->blue = G_MAXINT16;
3403 
3404 			rgba.alpha = 1;
3405 			rgba.red = 0;
3406 			rgba.green = 0;
3407 			rgba.blue = 1;
3408 
3409 			state = gtk_style_context_get_state (context);
3410 			state = state & (~(GTK_STATE_FLAG_VISITED | GTK_STATE_FLAG_LINK));
3411 			state = state | GTK_STATE_FLAG_LINK;
3412 
3413 			gtk_style_context_save (context);
3414 			gtk_style_context_set_state (context, state);
3415 			gtk_style_context_get_color (context, state, &rgba);
3416 			gtk_style_context_restore (context);
3417 
3418 			e_rgba_to_color (&rgba, link);
3419 		}
3420 
3421 		if (visited == NULL) {
3422 			GdkRGBA rgba;
3423 			GtkStateFlags state;
3424 
3425 			visited = g_slice_new0 (GdkColor);
3426 			visited->red = G_MAXINT16;
3427 
3428 			rgba.alpha = 1;
3429 			rgba.red = 1;
3430 			rgba.green = 0;
3431 			rgba.blue = 0;
3432 
3433 			state = gtk_style_context_get_state (context);
3434 			state = state & (~(GTK_STATE_FLAG_VISITED | GTK_STATE_FLAG_LINK));
3435 			state = state | GTK_STATE_FLAG_VISITED;
3436 
3437 			gtk_style_context_save (context);
3438 			gtk_style_context_set_state (context, state);
3439 			gtk_style_context_get_color (context, state, &rgba);
3440 			gtk_style_context_restore (context);
3441 
3442 			e_rgba_to_color (&rgba, visited);
3443 		}
3444 
3445 		g_string_append_printf (
3446 			stylesheet,
3447 			"a {\n"
3448 			"  color: #%06x;\n"
3449 			"}\n"
3450 			"a:visited {\n"
3451 			"  color: #%06x;\n"
3452 			"}\n",
3453 			e_color_to_value (link),
3454 			e_color_to_value (visited));
3455 
3456 		gdk_color_free (link);
3457 		gdk_color_free (visited);
3458 
3459 		g_string_append (
3460 			stylesheet,
3461 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3462 			"{\n"
3463 			"  padding: 0ch 1ch 0ch 1ch;\n"
3464 			"  margin: 0ch;\n"
3465 			"  border-width: 0px 2px 0px 2px;\n"
3466 			"  border-style: none solid none solid;\n"
3467 			"  border-radius: 2px;\n"
3468 			"}\n");
3469 
3470 		g_string_append_printf (
3471 			stylesheet,
3472 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3473 			"{\n"
3474 			"  border-color: %s;\n"
3475 			"  margin: 0 0 6px 0;\n"
3476 			"}\n",
3477 			e_web_view_get_citation_color_for_level (1));
3478 
3479 		g_string_append_printf (
3480 			stylesheet,
3481 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3482 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3483 			"{\n"
3484 			"  border-color: %s;\n"
3485 			"  margin: 0ch;\n"
3486 			"}\n",
3487 			e_web_view_get_citation_color_for_level (2));
3488 
3489 		g_string_append_printf (
3490 			stylesheet,
3491 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3492 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3493 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3494 			"{\n"
3495 			"  border-color: %s;\n"
3496 			"  margin: 0ch;\n"
3497 			"}\n",
3498 			e_web_view_get_citation_color_for_level (3));
3499 
3500 		g_string_append_printf (
3501 			stylesheet,
3502 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3503 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3504 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3505 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3506 			"{\n"
3507 			"  border-color: %s;\n"
3508 			"  margin: 0ch;\n"
3509 			"}\n",
3510 			e_web_view_get_citation_color_for_level (4));
3511 
3512 		g_string_append_printf (
3513 			stylesheet,
3514 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3515 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3516 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3517 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3518 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3519 			"{\n"
3520 			"  border-color: %s;\n"
3521 			"  margin: 0ch;\n"
3522 			"}\n",
3523 			e_web_view_get_citation_color_for_level (5));
3524 
3525 		g_string_append_printf (
3526 			stylesheet,
3527 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3528 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3529 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3530 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3531 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3532 			"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
3533 			"{\n"
3534 			"  border-color: %s;\n"
3535 			"  padding: 0ch 0ch 0ch 1ch;\n"
3536 			"  margin: 0ch;\n"
3537 			"  border-width: 0px 0px 0px 2px;\n"
3538 			"  border-style: none none none solid;\n"
3539 			"}\n",
3540 			e_web_view_get_citation_color_for_level (1));
3541 	}
3542 
3543 	wk_settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view_widget));
3544 
3545 	g_object_set (
3546 		wk_settings,
3547 		"default-font-size",
3548 		e_util_normalize_font_size (
3549 			view_widget, pango_font_description_get_size (vw) / PANGO_SCALE),
3550 		"default-font-family",
3551 		pango_font_description_get_family (vw),
3552 		"monospace-font-family",
3553 		pango_font_description_get_family (ms),
3554 		"default-monospace-font-size",
3555 		e_util_normalize_font_size (
3556 			view_widget, pango_font_description_get_size (ms) / PANGO_SCALE),
3557 		NULL);
3558 
3559 	manager = webkit_web_view_get_user_content_manager (WEBKIT_WEB_VIEW (view_widget));
3560 	webkit_user_content_manager_remove_all_style_sheets (manager);
3561 
3562 	style_sheet = webkit_user_style_sheet_new (
3563 		stylesheet->str,
3564 		WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
3565 		WEBKIT_USER_STYLE_LEVEL_USER,
3566 		NULL,
3567 		NULL);
3568 
3569 	webkit_user_content_manager_add_style_sheet (manager, style_sheet);
3570 
3571 	webkit_user_style_sheet_unref (style_sheet);
3572 
3573 	g_string_free (stylesheet, TRUE);
3574 
3575 	if (clean_ms)
3576 		pango_font_description_free (ms);
3577 	if (clean_vw)
3578 		pango_font_description_free (vw);
3579 
3580 	e_web_view_update_styles (E_WEB_VIEW (view_widget), "*");
3581 }
3582 
3583 WebKitSettings *
e_web_view_get_default_webkit_settings(void)3584 e_web_view_get_default_webkit_settings (void)
3585 {
3586 	WebKitSettings *settings;
3587 
3588 	settings = webkit_settings_new_with_settings (
3589 		"auto-load-images", TRUE,
3590 		"default-charset", "utf-8",
3591 		"enable-html5-database", FALSE,
3592 		"enable-dns-prefetching", FALSE,
3593 		"enable-html5-local-storage", FALSE,
3594 		"enable-java", FALSE,
3595 		"enable-javascript", TRUE, /* Needed for JavaScriptCore API to work */
3596 		"enable-javascript-markup", FALSE, /* Discards user-provided javascript in HTML */
3597 		"enable-offline-web-application-cache", FALSE,
3598 		"enable-page-cache", FALSE,
3599 		"enable-plugins", FALSE,
3600 		"enable-smooth-scrolling", FALSE,
3601 		"media-playback-allows-inline", FALSE,
3602 		NULL);
3603 
3604 	e_web_view_utils_apply_minimum_font_size (settings);
3605 
3606 	return settings;
3607 }
3608 
3609 void
e_web_view_utils_apply_minimum_font_size(WebKitSettings * wk_settings)3610 e_web_view_utils_apply_minimum_font_size (WebKitSettings *wk_settings)
3611 {
3612 	GSettings *settings;
3613 	gint value;
3614 
3615 	g_return_if_fail (WEBKIT_IS_SETTINGS (wk_settings));
3616 
3617 	settings = e_util_ref_settings ("org.gnome.evolution.shell");
3618 	value = g_settings_get_int (settings, "webkit-minimum-font-size");
3619 	g_clear_object (&settings);
3620 
3621 	if (value < 0)
3622 		value = 0;
3623 
3624 	if (webkit_settings_get_minimum_font_size (wk_settings) != (guint32) value)
3625 		webkit_settings_set_minimum_font_size (wk_settings, value);
3626 }
3627 
3628 gint
e_web_view_get_minimum_font_size(EWebView * web_view)3629 e_web_view_get_minimum_font_size (EWebView *web_view)
3630 {
3631 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), -1);
3632 
3633 	return web_view->priv->minimum_font_size;
3634 }
3635 
3636 void
e_web_view_set_minimum_font_size(EWebView * web_view,gint pixels)3637 e_web_view_set_minimum_font_size (EWebView *web_view,
3638 				  gint pixels)
3639 {
3640 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3641 
3642 	if (web_view->priv->minimum_font_size != pixels) {
3643 		WebKitSettings *wk_settings;
3644 
3645 		web_view->priv->minimum_font_size = pixels;
3646 
3647 		wk_settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (web_view));
3648 		e_web_view_utils_apply_minimum_font_size (wk_settings);
3649 
3650 		g_object_notify (G_OBJECT (web_view), "minimum-font-size");
3651 	}
3652 }
3653 
3654 GCancellable *
e_web_view_get_cancellable(EWebView * web_view)3655 e_web_view_get_cancellable (EWebView *web_view)
3656 {
3657 	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
3658 
3659 	return web_view->priv->cancellable;
3660 }
3661 
3662 void
e_web_view_update_fonts(EWebView * web_view)3663 e_web_view_update_fonts (EWebView *web_view)
3664 {
3665 	EWebViewClass *class;
3666 	PangoFontDescription *ms = NULL, *vw = NULL;
3667 
3668 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3669 
3670 	class = E_WEB_VIEW_GET_CLASS (web_view);
3671 	g_return_if_fail (class != NULL);
3672 
3673 	if (class->set_fonts != NULL)
3674 		class->set_fonts (web_view, &ms, &vw);
3675 
3676 	e_web_view_update_fonts_settings (
3677 		web_view->priv->font_settings,
3678 		ms, vw, GTK_WIDGET (web_view));
3679 
3680 	pango_font_description_free (ms);
3681 	pango_font_description_free (vw);
3682 }
3683 
3684 /* Helper for e_web_view_cursor_image_copy() */
3685 static void
web_view_cursor_image_copy_pixbuf_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)3686 web_view_cursor_image_copy_pixbuf_cb (GObject *source_object,
3687                                       GAsyncResult *result,
3688                                       gpointer user_data)
3689 {
3690 	EActivity *activity;
3691 	EAlertSink *alert_sink;
3692 	GdkPixbuf *pixbuf;
3693 	GError *local_error = NULL;
3694 
3695 	activity = E_ACTIVITY (user_data);
3696 	alert_sink = e_activity_get_alert_sink (activity);
3697 
3698 	pixbuf = gdk_pixbuf_new_from_stream_finish (result, &local_error);
3699 
3700 	/* Sanity check. */
3701 	g_return_if_fail (
3702 		((pixbuf != NULL) && (local_error == NULL)) ||
3703 		((pixbuf == NULL) && (local_error != NULL)));
3704 
3705 	if (e_activity_handle_cancellation (activity, local_error)) {
3706 		g_error_free (local_error);
3707 
3708 	} else if (local_error != NULL) {
3709 		e_alert_submit (
3710 			alert_sink,
3711 			"widgets:no-image-copy",
3712 			local_error->message, NULL);
3713 		g_error_free (local_error);
3714 
3715 	} else {
3716 		GtkClipboard *clipboard;
3717 
3718 		clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
3719 		gtk_clipboard_set_image (clipboard, pixbuf);
3720 		gtk_clipboard_store (clipboard);
3721 
3722 		e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
3723 	}
3724 
3725 	g_clear_object (&activity);
3726 	g_clear_object (&pixbuf);
3727 }
3728 
3729 /* Helper for e_web_view_cursor_image_copy() */
3730 static void
web_view_cursor_image_copy_request_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)3731 web_view_cursor_image_copy_request_cb (GObject *source_object,
3732                                        GAsyncResult *result,
3733                                        gpointer user_data)
3734 {
3735 	EActivity *activity;
3736 	EAlertSink *alert_sink;
3737 	GCancellable *cancellable;
3738 	GInputStream *input_stream;
3739 	GError *local_error = NULL;
3740 
3741 	activity = E_ACTIVITY (user_data);
3742 	alert_sink = e_activity_get_alert_sink (activity);
3743 	cancellable = e_activity_get_cancellable (activity);
3744 
3745 	input_stream = e_web_view_request_finish (
3746 		E_WEB_VIEW (source_object), result, &local_error);
3747 
3748 	/* Sanity check. */
3749 	g_return_if_fail (
3750 		((input_stream != NULL) && (local_error == NULL)) ||
3751 		((input_stream == NULL) && (local_error != NULL)));
3752 
3753 	if (e_activity_handle_cancellation (activity, local_error)) {
3754 		g_error_free (local_error);
3755 
3756 	} else if (local_error != NULL) {
3757 		e_alert_submit (
3758 			alert_sink,
3759 			"widgets:no-image-copy",
3760 			local_error->message, NULL);
3761 		g_error_free (local_error);
3762 
3763 	} else {
3764 		gdk_pixbuf_new_from_stream_async (
3765 			input_stream, cancellable,
3766 			web_view_cursor_image_copy_pixbuf_cb,
3767 			g_object_ref (activity));
3768 	}
3769 
3770 	g_clear_object (&activity);
3771 	g_clear_object (&input_stream);
3772 }
3773 
3774 /**
3775  * e_web_view_cursor_image_copy:
3776  * @web_view: an #EWebView
3777  *
3778  * Asynchronously copies the image under the cursor to the clipboard.
3779  *
3780  * This function triggers an #EWebView::new-activity signal emission so
3781  * the asynchronous operation can be tracked and/or cancelled.
3782  **/
3783 void
e_web_view_cursor_image_copy(EWebView * web_view)3784 e_web_view_cursor_image_copy (EWebView *web_view)
3785 {
3786 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3787 
3788 	if (web_view->priv->cursor_image_src != NULL) {
3789 		EActivity *activity;
3790 		GCancellable *cancellable;
3791 		const gchar *text;
3792 
3793 		activity = e_web_view_new_activity (web_view);
3794 		cancellable = e_activity_get_cancellable (activity);
3795 
3796 		text = _("Copying image to clipboard");
3797 		e_activity_set_text (activity, text);
3798 
3799 		e_web_view_request (
3800 			web_view,
3801 			web_view->priv->cursor_image_src,
3802 			cancellable,
3803 			web_view_cursor_image_copy_request_cb,
3804 			g_object_ref (activity));
3805 
3806 		g_object_unref (activity);
3807 	}
3808 }
3809 
3810 /* Helper for e_web_view_cursor_image_save() */
3811 static void
web_view_cursor_image_save_splice_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)3812 web_view_cursor_image_save_splice_cb (GObject *source_object,
3813                                       GAsyncResult *result,
3814                                       gpointer user_data)
3815 {
3816 	EActivity *activity;
3817 	EAlertSink *alert_sink;
3818 	AsyncContext *async_context;
3819 	GError *local_error = NULL;
3820 
3821 	async_context = (AsyncContext *) user_data;
3822 
3823 	activity = async_context->activity;
3824 	alert_sink = e_activity_get_alert_sink (activity);
3825 
3826 	g_output_stream_splice_finish (
3827 		G_OUTPUT_STREAM (source_object), result, &local_error);
3828 
3829 	if (e_activity_handle_cancellation (activity, local_error)) {
3830 		g_error_free (local_error);
3831 
3832 	} else if (local_error != NULL) {
3833 		e_alert_submit (
3834 			alert_sink,
3835 			"widgets:no-image-save",
3836 			local_error->message, NULL);
3837 		g_error_free (local_error);
3838 
3839 	} else {
3840 		e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
3841 	}
3842 
3843 	async_context_free (async_context);
3844 }
3845 
3846 /* Helper for e_web_view_cursor_image_save() */
3847 static void
web_view_cursor_image_save_replace_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)3848 web_view_cursor_image_save_replace_cb (GObject *source_object,
3849                                        GAsyncResult *result,
3850                                        gpointer user_data)
3851 {
3852 	EActivity *activity;
3853 	EAlertSink *alert_sink;
3854 	GCancellable *cancellable;
3855 	GFileOutputStream *output_stream;
3856 	AsyncContext *async_context;
3857 	GError *local_error = NULL;
3858 
3859 	async_context = (AsyncContext *) user_data;
3860 
3861 	activity = async_context->activity;
3862 	alert_sink = e_activity_get_alert_sink (activity);
3863 	cancellable = e_activity_get_cancellable (activity);
3864 
3865 	output_stream = g_file_replace_finish (
3866 		G_FILE (source_object), result, &local_error);
3867 
3868 	/* Sanity check. */
3869 	g_return_if_fail (
3870 		((output_stream != NULL) && (local_error == NULL)) ||
3871 		((output_stream == NULL) && (local_error != NULL)));
3872 
3873 	if (e_activity_handle_cancellation (activity, local_error)) {
3874 		g_error_free (local_error);
3875 		async_context_free (async_context);
3876 
3877 	} else if (local_error != NULL) {
3878 		e_alert_submit (
3879 			alert_sink,
3880 			"widgets:no-image-save",
3881 			local_error->message, NULL);
3882 		g_error_free (local_error);
3883 		async_context_free (async_context);
3884 
3885 	} else {
3886 		g_output_stream_splice_async (
3887 			G_OUTPUT_STREAM (output_stream),
3888 			async_context->input_stream,
3889 			G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
3890 			G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
3891 			G_PRIORITY_DEFAULT,
3892 			cancellable,
3893 			web_view_cursor_image_save_splice_cb,
3894 			async_context);
3895 	}
3896 
3897 	g_clear_object (&output_stream);
3898 }
3899 
3900 /* Helper for e_web_view_cursor_image_save() */
3901 static void
web_view_cursor_image_save_request_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)3902 web_view_cursor_image_save_request_cb (GObject *source_object,
3903                                        GAsyncResult *result,
3904                                        gpointer user_data)
3905 {
3906 	EActivity *activity;
3907 	EAlertSink *alert_sink;
3908 	GCancellable *cancellable;
3909 	GInputStream *input_stream;
3910 	AsyncContext *async_context;
3911 	GError *local_error = NULL;
3912 
3913 	async_context = (AsyncContext *) user_data;
3914 
3915 	activity = async_context->activity;
3916 	alert_sink = e_activity_get_alert_sink (activity);
3917 	cancellable = e_activity_get_cancellable (activity);
3918 
3919 	input_stream = e_web_view_request_finish (
3920 		E_WEB_VIEW (source_object), result, &local_error);
3921 
3922 	/* Sanity check. */
3923 	g_return_if_fail (
3924 		((input_stream != NULL) && (local_error == NULL)) ||
3925 		((input_stream == NULL) && (local_error != NULL)));
3926 
3927 	if (e_activity_handle_cancellation (activity, local_error)) {
3928 		g_error_free (local_error);
3929 		async_context_free (async_context);
3930 
3931 	} else if (local_error != NULL) {
3932 		e_alert_submit (
3933 			alert_sink,
3934 			"widgets:no-image-save",
3935 			local_error->message, NULL);
3936 		g_error_free (local_error);
3937 		async_context_free (async_context);
3938 
3939 	} else {
3940 		async_context->input_stream = g_object_ref (input_stream);
3941 
3942 		/* Open an output stream to the destination file. */
3943 		g_file_replace_async (
3944 			async_context->destination,
3945 			NULL, FALSE,
3946 			G_FILE_CREATE_REPLACE_DESTINATION,
3947 			G_PRIORITY_DEFAULT,
3948 			cancellable,
3949 			web_view_cursor_image_save_replace_cb,
3950 			async_context);
3951 	}
3952 
3953 	g_clear_object (&input_stream);
3954 }
3955 
3956 /**
3957  * e_web_view_cursor_image_save:
3958  * @web_view: an #EWebView
3959  *
3960  * Prompts the user to choose a destination file and then asynchronously
3961  * saves the image under the cursor to the destination file.
3962  *
3963  * This function triggers an #EWebView::new-activity signal emission so
3964  * the asynchronous operation can be tracked and/or cancelled.
3965  **/
3966 void
e_web_view_cursor_image_save(EWebView * web_view)3967 e_web_view_cursor_image_save (EWebView *web_view)
3968 {
3969 	GtkFileChooser *file_chooser;
3970 	GtkFileChooserNative *native;
3971 	GFile *destination = NULL;
3972 	gchar *suggestion;
3973 	gpointer toplevel;
3974 
3975 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
3976 
3977 	if (web_view->priv->cursor_image_src == NULL)
3978 		return;
3979 
3980 	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
3981 	toplevel = gtk_widget_is_toplevel (toplevel) ? toplevel : NULL;
3982 
3983 	native = gtk_file_chooser_native_new (
3984 		_("Save Image"), toplevel,
3985 		GTK_FILE_CHOOSER_ACTION_SAVE,
3986 		_("_Save"), _("_Cancel"));
3987 
3988 	file_chooser = GTK_FILE_CHOOSER (native);
3989 	gtk_file_chooser_set_local_only (file_chooser, FALSE);
3990 	gtk_file_chooser_set_do_overwrite_confirmation (file_chooser, TRUE);
3991 
3992 	suggestion = e_web_view_suggest_filename (
3993 		web_view, web_view->priv->cursor_image_src);
3994 
3995 	if (suggestion != NULL) {
3996 		gtk_file_chooser_set_current_name (file_chooser, suggestion);
3997 		g_free (suggestion);
3998 	}
3999 
4000 	e_util_load_file_chooser_folder (file_chooser);
4001 
4002 	if (gtk_native_dialog_run (GTK_NATIVE_DIALOG (native)) == GTK_RESPONSE_ACCEPT) {
4003 		e_util_save_file_chooser_folder (file_chooser);
4004 
4005 		destination = gtk_file_chooser_get_file (file_chooser);
4006 	}
4007 
4008 	g_object_unref (native);
4009 
4010 	if (destination != NULL) {
4011 		EActivity *activity;
4012 		GCancellable *cancellable;
4013 		AsyncContext *async_context;
4014 		gchar *text;
4015 		gchar *uri;
4016 
4017 		activity = e_web_view_new_activity (web_view);
4018 		cancellable = e_activity_get_cancellable (activity);
4019 
4020 		uri = g_file_get_uri (destination);
4021 		text = g_strdup_printf (_("Saving image to “%s”"), uri);
4022 		e_activity_set_text (activity, text);
4023 		g_free (text);
4024 		g_free (uri);
4025 
4026 		async_context = g_slice_new0 (AsyncContext);
4027 		async_context->activity = g_object_ref (activity);
4028 		async_context->destination = g_object_ref (destination);
4029 
4030 		e_web_view_request (
4031 			web_view,
4032 			web_view->priv->cursor_image_src,
4033 			cancellable,
4034 			web_view_cursor_image_save_request_cb,
4035 			async_context);
4036 
4037 		g_object_unref (activity);
4038 		g_object_unref (destination);
4039 	}
4040 }
4041 
4042 /* Helper for e_web_view_request() */
4043 static void
web_view_request_process_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)4044 web_view_request_process_thread (GTask *task,
4045 				 gpointer source_object,
4046 				 gpointer task_data,
4047 				 GCancellable *cancellable)
4048 {
4049 	AsyncContext *async_context = task_data;
4050 	gint64 stream_length = -1;
4051 	gchar *mime_type = NULL;
4052 	GError *local_error = NULL;
4053 
4054 	if (!e_content_request_process_sync (async_context->content_request,
4055 		async_context->uri, source_object, &async_context->input_stream,
4056 		&stream_length, &mime_type, cancellable, &local_error)) {
4057 		g_task_return_error (task, local_error);
4058 	} else {
4059 		g_task_return_boolean (task, TRUE);
4060 	}
4061 
4062 	g_free (mime_type);
4063 }
4064 
4065 /**
4066  * e_web_view_request:
4067  * @web_view: an #EWebView
4068  * @uri: the URI to load
4069  * @cancellable: optional #GCancellable object, or %NULL
4070  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
4071  * @user_data: data to pass to the callback function
4072  *
4073  * Asynchronously requests data at @uri as displaed in the @web_view.
4074  *
4075  * When the operation is finished, @callback will be called.  You can then
4076  * call e_web_view_request_finish() to get the result of the operation.
4077  **/
4078 void
e_web_view_request(EWebView * web_view,const gchar * uri,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)4079 e_web_view_request (EWebView *web_view,
4080                     const gchar *uri,
4081                     GCancellable *cancellable,
4082                     GAsyncReadyCallback callback,
4083                     gpointer user_data)
4084 {
4085 	EContentRequest *content_request = NULL;
4086 	AsyncContext *async_context;
4087 	GHashTableIter iter;
4088 	GTask *task;
4089 	gboolean is_processed = FALSE;
4090 	gpointer value;
4091 
4092 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
4093 	g_return_if_fail (uri != NULL);
4094 
4095 	g_hash_table_iter_init (&iter, web_view->priv->scheme_handlers);
4096 
4097 	while (g_hash_table_iter_next (&iter, NULL, &value)) {
4098 		EContentRequest *adept = value;
4099 
4100 		if (!E_IS_CONTENT_REQUEST (adept) ||
4101 		    !e_content_request_can_process_uri (adept, uri))
4102 			continue;
4103 
4104 		content_request = adept;
4105 		break;
4106 	}
4107 
4108 	async_context = g_slice_new0 (AsyncContext);
4109 	async_context->uri = g_strdup (uri);
4110 
4111 	task = g_task_new (web_view, cancellable, callback, user_data);
4112 	g_task_set_task_data (task, async_context, async_context_free);
4113 	g_task_set_check_cancellable (task, TRUE);
4114 
4115 	if (content_request) {
4116 		async_context->content_request = g_object_ref (content_request);
4117 		g_task_run_in_thread (task, web_view_request_process_thread);
4118 		is_processed = TRUE;
4119 
4120 	/* Handle base64-encoded "data:" URIs manually */
4121 	} else if (g_ascii_strncasecmp (uri, "data:", 5) == 0) {
4122 		/* data:[<mime type>][;charset=<charset>][;base64],<encoded data> */
4123 		const gchar *ptr, *from;
4124 		gboolean is_base64 = FALSE;
4125 
4126 		ptr = uri + 5;
4127 		from = ptr;
4128 		while (*ptr && *ptr != ',') {
4129 			ptr++;
4130 
4131 			if (*ptr == ',' || *ptr == ';') {
4132 				if (g_ascii_strncasecmp (from, "base64", ptr - from) == 0)
4133 					is_base64 = TRUE;
4134 
4135 				from = ptr + 1;
4136 			}
4137 		}
4138 
4139 		if (is_base64 && *ptr == ',') {
4140 			guchar *data;
4141 			gsize len = 0;
4142 
4143 			data = g_base64_decode (ptr + 1, &len);
4144 
4145 			if (data && len > 0) {
4146 				async_context->input_stream = g_memory_input_stream_new_from_data (data, len, g_free);
4147 				g_task_return_boolean (task, TRUE);
4148 				is_processed = TRUE;
4149 			} else {
4150 				g_free (data);
4151 			}
4152 		}
4153 	}
4154 
4155 	if (!is_processed) {
4156 		GString *shorten_uri = NULL;
4157 		gint len;
4158 
4159 		len = g_utf8_strlen (uri, -1);
4160 
4161 		/* The "data:" URIs can be quite long */
4162 		if (len > 512) {
4163 			const gchar *ptr = g_utf8_offset_to_pointer (uri, 512);
4164 
4165 			shorten_uri = g_string_sized_new (ptr - uri + 16);
4166 			g_string_append_len (shorten_uri, uri, ptr - uri);
4167 			g_string_append (shorten_uri, _("…"));
4168 		}
4169 
4170 		g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
4171 			_("Cannot get URI “%s”, do not know how to download it."), shorten_uri ? shorten_uri->str : uri);
4172 
4173 		if (shorten_uri)
4174 			g_string_free (shorten_uri, TRUE);
4175 	}
4176 
4177 	g_object_unref (task);
4178 }
4179 
4180 /**
4181  * e_web_view_request_finish:
4182  * @web_view: an #EWebView
4183  * @result: a #GAsyncResult
4184  * @error: return location for a #GError, or %NULL
4185  *
4186  * Finishes the operation started with e_web_view_request().
4187  *
4188  * Unreference the returned #GInputStream with g_object_unref() when finished
4189  * with it.  If an error occurred, the function will set @error and return
4190  * %NULL.
4191  *
4192  * Returns: a #GInputStream, or %NULL
4193  **/
4194 GInputStream *
e_web_view_request_finish(EWebView * web_view,GAsyncResult * result,GError ** error)4195 e_web_view_request_finish (EWebView *web_view,
4196                            GAsyncResult *result,
4197                            GError **error)
4198 {
4199 	AsyncContext *async_context;
4200 
4201 	g_return_val_if_fail (g_task_is_valid (result, web_view), NULL);
4202 
4203 	if (!g_task_propagate_boolean (G_TASK (result), error))
4204 		return NULL;
4205 
4206 	async_context = g_task_get_task_data (G_TASK (result));
4207 
4208 	g_return_val_if_fail (async_context->input_stream != NULL, NULL);
4209 
4210 	return g_object_ref (async_context->input_stream);
4211 }
4212 
4213 /**
4214  * e_web_view_set_iframe_src:
4215  * @web_view: an #EWebView
4216  * @document_uri: a document URI for whose IFrame change the source
4217  * @src_uri: the source to change the IFrame to
4218  *
4219  * Change IFrame source for the given @document_uri IFrame
4220  * to the @new_iframe_src.
4221  *
4222  * Since: 3.22
4223  **/
4224 void
e_web_view_set_iframe_src(EWebView * web_view,const gchar * iframe_id,const gchar * src_uri)4225 e_web_view_set_iframe_src (EWebView *web_view,
4226 			   const gchar *iframe_id,
4227 			   const gchar *src_uri)
4228 {
4229 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
4230 
4231 	e_web_view_jsc_run_script (WEBKIT_WEB_VIEW (web_view), web_view->priv->cancellable,
4232 		"Evo.SetIFrameSrc(%s, %s);",
4233 		iframe_id, src_uri);
4234 }
4235 
4236 /**
4237  * EWebViewElementClickedFunc:
4238  * @web_view: an #EWebView
4239  * @iframe_id: an iframe ID in which the click happened; empty string for the main frame
4240  * @element_id: an element ID
4241  * @element_class: an element class, as set on the element which had been clicked
4242  * @element_value: a 'value' attribute content of the clicked element
4243  * @element_position: a #GtkAllocation with the position of the clicked element
4244  * @user_data: user data as provided in the e_web_view_register_element_clicked() call
4245  *
4246  * The callback is called whenever an element of class @element_class is clicked.
4247  * The @element_value is a content of the 'value' attribute of the clicked element.
4248  * The @element_position is the place of the element within the web page, already
4249  * accounting scrollbar positions.
4250  *
4251  * See: e_web_view_register_element_clicked, e_web_view_unregister_element_clicked
4252  *
4253  * Since: 3.22
4254  **/
4255 
4256 /**
4257  * e_web_view_register_element_clicked:
4258  * @web_view: an #EWebView
4259  * @element_class: an element class on which to listen for clicking
4260  * @callback: an #EWebViewElementClickedFunc to call, when the element is clicked
4261  * @user_data: user data to pass to @callback
4262  *
4263  * Registers a @callback to be called when any element of the class @element_class
4264  * is clicked. If the element contains a 'value' attribute, then it is passed to
4265  * the @callback too. These callback are valid until a new content of the @web_view
4266  * is loaded, after which all the registered callbacks are forgotten.
4267  *
4268  * Since: 3.22
4269  **/
4270 void
e_web_view_register_element_clicked(EWebView * web_view,const gchar * element_class,EWebViewElementClickedFunc callback,gpointer user_data)4271 e_web_view_register_element_clicked (EWebView *web_view,
4272 				     const gchar *element_class,
4273 				     EWebViewElementClickedFunc callback,
4274 				     gpointer user_data)
4275 {
4276 	ElementClickedData *ecd;
4277 	GPtrArray *cbs;
4278 	guint ii;
4279 
4280 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
4281 	g_return_if_fail (element_class != NULL);
4282 	g_return_if_fail (callback != NULL);
4283 
4284 	cbs = g_hash_table_lookup (web_view->priv->element_clicked_cbs, element_class);
4285 	if (cbs) {
4286 		for (ii = 0; ii < cbs->len; ii++) {
4287 			ecd = g_ptr_array_index (cbs, ii);
4288 
4289 			if (ecd && ecd->callback == callback && ecd->user_data == user_data) {
4290 				/* Callback is already registered, but re-register it, in case the page
4291 				   was changed dynamically and new elements with the given call are added. */
4292 				web_view_call_register_element_clicked (web_view, "*", element_class);
4293 				return;
4294 			}
4295 		}
4296 	}
4297 
4298 	ecd = g_new0 (ElementClickedData, 1);
4299 	ecd->callback = callback;
4300 	ecd->user_data = user_data;
4301 
4302 	if (!cbs) {
4303 		cbs = g_ptr_array_new_full (1, g_free);
4304 		g_ptr_array_add (cbs, ecd);
4305 
4306 		g_hash_table_insert (web_view->priv->element_clicked_cbs, g_strdup (element_class), cbs);
4307 	} else {
4308 		g_ptr_array_add (cbs, ecd);
4309 	}
4310 
4311 	/* Dynamically changing page can call this multiple times; re-register all classes */
4312 	web_view_call_register_element_clicked (web_view, "*", NULL);
4313 }
4314 
4315 /**
4316  * e_web_view_unregister_element_clicked:
4317  * @web_view: an #EWebView
4318  * @element_class: an element class on which to listen for clicking
4319  * @callback: an #EWebViewElementClickedFunc to call, when the element is clicked
4320  * @user_data: user data to pass to @callback
4321  *
4322  * Unregisters the @callback for the @element_class with the given @user_data, which
4323  * should be previously registered with e_web_view_register_element_clicked(). This
4324  * unregister is usually not needed, because the @web_view unregisters all callbacks
4325  * when a new content is loaded.
4326  *
4327  * Since: 3.22
4328  **/
4329 void
e_web_view_unregister_element_clicked(EWebView * web_view,const gchar * element_class,EWebViewElementClickedFunc callback,gpointer user_data)4330 e_web_view_unregister_element_clicked (EWebView *web_view,
4331 				       const gchar *element_class,
4332 				       EWebViewElementClickedFunc callback,
4333 				       gpointer user_data)
4334 {
4335 	ElementClickedData *ecd;
4336 	GPtrArray *cbs;
4337 	guint ii;
4338 
4339 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
4340 	g_return_if_fail (element_class != NULL);
4341 	g_return_if_fail (callback != NULL);
4342 
4343 	cbs = g_hash_table_lookup (web_view->priv->element_clicked_cbs, element_class);
4344 	if (!cbs)
4345 		return;
4346 
4347 	for (ii = 0; ii < cbs->len; ii++) {
4348 		ecd = g_ptr_array_index (cbs, ii);
4349 
4350 		if (ecd && ecd->callback == callback && ecd->user_data == user_data) {
4351 			g_ptr_array_remove (cbs, ecd);
4352 			if (!cbs->len)
4353 				g_hash_table_remove (web_view->priv->element_clicked_cbs, element_class);
4354 			break;
4355 		}
4356 	}
4357 }
4358 
4359 void
e_web_view_set_element_hidden(EWebView * web_view,const gchar * element_id,gboolean hidden)4360 e_web_view_set_element_hidden (EWebView *web_view,
4361 			       const gchar *element_id,
4362 			       gboolean hidden)
4363 {
4364 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
4365 	g_return_if_fail (element_id && *element_id);
4366 
4367 	e_web_view_jsc_set_element_hidden (WEBKIT_WEB_VIEW (web_view),
4368 		"*", element_id, hidden,
4369 		web_view->priv->cancellable);
4370 }
4371 
4372 void
e_web_view_set_element_style_property(EWebView * web_view,const gchar * element_id,const gchar * property_name,const gchar * value)4373 e_web_view_set_element_style_property (EWebView *web_view,
4374 				       const gchar *element_id,
4375 				       const gchar *property_name,
4376 				       const gchar *value)
4377 {
4378 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
4379 	g_return_if_fail (element_id && *element_id);
4380 	g_return_if_fail (property_name && *property_name);
4381 
4382 	e_web_view_jsc_set_element_style_property (WEBKIT_WEB_VIEW (web_view),
4383 		"*", element_id, property_name, value,
4384 		web_view->priv->cancellable);
4385 }
4386 
4387 void
e_web_view_set_element_attribute(EWebView * web_view,const gchar * element_id,const gchar * namespace_uri,const gchar * qualified_name,const gchar * value)4388 e_web_view_set_element_attribute (EWebView *web_view,
4389 				  const gchar *element_id,
4390 				  const gchar *namespace_uri,
4391 				  const gchar *qualified_name,
4392 				  const gchar *value)
4393 {
4394 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
4395 	g_return_if_fail (element_id && *element_id);
4396 	g_return_if_fail (qualified_name && *qualified_name);
4397 
4398 	e_web_view_jsc_set_element_attribute (WEBKIT_WEB_VIEW (web_view),
4399 		"*", element_id, namespace_uri, qualified_name, value,
4400 		web_view->priv->cancellable);
4401 }
4402