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