4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with the program; if not, see <http://www.gnu.org/licenses/>
19 #include "evolution-config.h"
24 #include <glib/gstdio.h>
27 #include <camel/camel.h>
28 #include <libedataserver/libedataserver.h>
30 #include "e-web-extension.h"
31 #include "e-dom-utils.h"
32 #include "e-web-extension-names.h"
34 #include <webkitdom/webkitdom.h>
36 #define WEB_EXTENSION_PAGE_ID_KEY "web-extension-page-id"
38 #define E_WEB_EXTENSION_GET_PRIVATE(obj) \
39 (G_TYPE_INSTANCE_GET_PRIVATE \
40 ((obj), E_TYPE_WEB_EXTENSION, EWebExtensionPrivate))
42 typedef struct _EWebPageData
{
43 WebKitWebPage
*web_page
; /* not referenced */
45 guint32 clipboard_flags
;
48 struct _EWebExtensionPrivate
{
49 WebKitWebExtension
*wk_extension
;
51 GDBusConnection
*dbus_connection
;
52 guint registration_id
;
56 GHashTable
*pages
; /* guint64 *webpage_id ~> EWebPageData * */
59 static const char introspection_xml
[] =
61 " <interface name='" E_WEB_EXTENSION_INTERFACE
"'>"
62 " <method name='RegisterElementClicked'>"
63 " <arg type='t' name='page_id' direction='in'/>"
64 " <arg type='s' name='element_class' direction='in'/>"
66 " <signal name='ElementClicked'>"
67 " <arg type='t' name='page_id' direction='out'/>"
68 " <arg type='s' name='element_class' direction='out'/>"
69 " <arg type='s' name='element_value' direction='out'/>"
70 " <arg type='i' name='position_left' direction='out'/>"
71 " <arg type='i' name='position_top' direction='out'/>"
72 " <arg type='i' name='position_width' direction='out'/>"
73 " <arg type='i' name='position_height' direction='out'/>"
75 " <method name='SetElementHidden'>"
76 " <arg type='t' name='page_id' direction='in'/>"
77 " <arg type='s' name='element_id' direction='in'/>"
78 " <arg type='b' name='hidden' direction='in'/>"
80 " <method name='SetElementStyleProperty'>"
81 " <arg type='t' name='page_id' direction='in'/>"
82 " <arg type='s' name='element_id' direction='in'/>"
83 " <arg type='s' name='property_name' direction='in'/>"
84 " <arg type='s' name='value' direction='in'/>"
85 " <arg type='s' name='priority' direction='in'/>"
87 " <method name='SetElementAttribute'>"
88 " <arg type='t' name='page_id' direction='in'/>"
89 " <arg type='s' name='element_id' direction='in'/>"
90 " <arg type='s' name='namespace_uri' direction='in'/>"
91 " <arg type='s' name='qualified_name' direction='in'/>"
92 " <arg type='s' name='value' direction='in'/>"
94 " <signal name='HeadersCollapsed'>"
95 " <arg type='b' name='expanded' direction='out'/>"
97 " <method name='DocumentHasSelection'>"
98 " <arg type='t' name='page_id' direction='in'/>"
99 " <arg type='b' name='has_selection' direction='out'/>"
101 " <method name='GetDocumentContentHTML'>"
102 " <arg type='t' name='page_id' direction='in'/>"
103 " <arg type='s' name='html_content' direction='out'/>"
105 " <method name='GetSelectionContentHTML'>"
106 " <arg type='t' name='page_id' direction='in'/>"
107 " <arg type='s' name='html_content' direction='out'/>"
109 " <method name='GetSelectionContentText'>"
110 " <arg type='t' name='page_id' direction='in'/>"
111 " <arg type='s' name='text_content' direction='out'/>"
113 " <method name='GetSelectionContentMultipart'>"
114 " <arg type='t' name='page_id' direction='in'/>"
115 " <arg type='s' name='content' direction='out'/>"
116 " <arg type='b' name='is_html' direction='out'/>"
118 " <method name='CreateAndAddCSSStyleSheet'>"
119 " <arg type='t' name='page_id' direction='in'/>"
120 " <arg type='s' name='style_sheet_id' direction='in'/>"
122 " <method name='AddCSSRuleIntoStyleSheet'>"
123 " <arg type='t' name='page_id' direction='in'/>"
124 " <arg type='s' name='style_sheet_id' direction='in'/>"
125 " <arg type='s' name='selector' direction='in'/>"
126 " <arg type='s' name='style' direction='in'/>"
128 " <method name='EABContactFormatterBindDOM'>"
129 " <arg type='t' name='page_id' direction='in'/>"
131 " <method name='EMailDisplayBindDOM'>"
132 " <arg type='t' name='page_id' direction='in'/>"
134 " <method name='ElementExists'>"
135 " <arg type='t' name='page_id' direction='in'/>"
136 " <arg type='s' name='element_id' direction='in'/>"
137 " <arg type='b' name='element_exists' direction='out'/>"
138 " <arg type='t' name='page_id' direction='out'/>"
140 " <method name='GetActiveElementName'>"
141 " <arg type='t' name='page_id' direction='in'/>"
142 " <arg type='s' name='element_name' direction='out'/>"
144 " <method name='EMailPartHeadersBindDOMElement'>"
145 " <arg type='t' name='page_id' direction='in'/>"
146 " <arg type='s' name='element_id' direction='in'/>"
148 " <signal name='VCardInlineDisplayModeToggled'>"
149 " <arg type='s' name='button_id' direction='out'/>"
151 " <signal name='VCardInlineSaveButtonPressed'>"
152 " <arg type='s' name='button_value' direction='out'/>"
154 " <method name='VCardInlineBindDOM'>"
155 " <arg type='t' name='page_id' direction='in'/>"
156 " <arg type='s' name='element_id' direction='in'/>"
158 " <method name='VCardInlineUpdateButton'>"
159 " <arg type='t' name='page_id' direction='in'/>"
160 " <arg type='s' name='button_id' direction='in'/>"
161 " <arg type='s' name='html_label' direction='in'/>"
162 " <arg type='s' name='access_key' direction='in'/>"
164 " <method name='VCardInlineSetIFrameSrc'>"
165 " <arg type='t' name='page_id' direction='in'/>"
166 " <arg type='s' name='button_id' direction='in'/>"
167 " <arg type='s' name='src' direction='in'/>"
169 " <method name='GetDocumentURIFromPoint'>"
170 " <arg type='t' name='page_id' direction='in'/>"
171 " <arg type='i' name='x' direction='in'/>"
172 " <arg type='i' name='y' direction='in'/>"
173 " <arg type='s' name='document_uri' direction='out'/>"
175 " <method name='SetDocumentIFrameSrc'>"
176 " <arg type='t' name='page_id' direction='in'/>"
177 " <arg type='s' name='document_uri' direction='in'/>"
178 " <arg type='s' name='new_iframe_src' direction='in'/>"
180 " <method name='ProcessMagicSpacebar'>"
181 " <arg type='t' name='page_id' direction='in'/>"
182 " <arg type='b' name='towards_bottom' direction='in'/>"
183 " <arg type='b' name='processed' direction='out'/>"
185 " <method name='EWebViewEnsureBodyClass'>"
186 " <arg type='t' name='page_id' direction='in'/>"
187 " <arg type='s' name='body_class' direction='in'/>"
189 " <signal name='NeedInputChanged'>"
190 " <arg type='t' name='page_id' direction='out'/>"
191 " <arg type='b' name='need_input' direction='out'/>"
193 " <signal name='ClipboardFlagsChanged'>"
194 " <arg type='t' name='page_id' direction='out'/>"
195 " <arg type='u' name='flags' direction='out'/>"
197 " <signal name='MailPartAppeared'>"
198 " <arg type='t' name='page_id' direction='out'/>"
199 " <arg type='s' name='part_id' direction='out'/>"
204 G_DEFINE_TYPE (EWebExtension
, e_web_extension
, G_TYPE_OBJECT
)
206 static WebKitWebPage
*
207 get_webkit_web_page_or_return_dbus_error (GDBusMethodInvocation
*invocation
,
208 WebKitWebExtension
*web_extension
,
211 WebKitWebPage
*web_page
= webkit_web_extension_get_page (web_extension
, page_id
);
213 g_dbus_method_invocation_return_error (
214 invocation
, G_DBUS_ERROR
, G_DBUS_ERROR_INVALID_ARGS
,
215 "Invalid page ID: %" G_GUINT64_FORMAT
, page_id
);
221 element_clicked_cb (WebKitDOMElement
*element
,
222 WebKitDOMEvent
*event
,
225 EWebExtension
*extension
= user_data
;
226 WebKitDOMElement
*offset_parent
;
227 WebKitDOMDOMWindow
*dom_window
= NULL
;
228 gchar
*attr_class
, *attr_value
;
229 const guint64
*ppage_id
;
230 gdouble with_parents_left
, with_parents_top
;
231 glong scroll_x
= 0, scroll_y
= 0;
232 GError
*error
= NULL
;
234 g_return_if_fail (E_IS_WEB_EXTENSION (extension
));
235 g_return_if_fail (G_IS_OBJECT (element
));
237 ppage_id
= g_object_get_data (G_OBJECT (element
), WEB_EXTENSION_PAGE_ID_KEY
);
238 g_return_if_fail (ppage_id
!= NULL
);
240 with_parents_left
= webkit_dom_element_get_offset_left (element
);
241 with_parents_top
= webkit_dom_element_get_offset_top (element
);
243 offset_parent
= element
;
244 while (offset_parent
= webkit_dom_element_get_offset_parent (offset_parent
), offset_parent
) {
245 with_parents_left
+= webkit_dom_element_get_offset_left (offset_parent
);
246 with_parents_top
+= webkit_dom_element_get_offset_top (offset_parent
);
249 dom_window
= webkit_dom_document_get_default_view (webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element
)));
250 if (WEBKIT_DOM_IS_DOM_WINDOW (dom_window
)) {
251 g_object_get (G_OBJECT (dom_window
),
252 "scroll-x", &scroll_x
,
253 "scroll-y", &scroll_y
,
256 g_clear_object (&dom_window
);
258 attr_class
= webkit_dom_element_get_class_name (element
);
259 attr_value
= webkit_dom_element_get_attribute (element
, "value");
261 g_dbus_connection_emit_signal (
262 extension
->priv
->dbus_connection
,
264 E_WEB_EXTENSION_OBJECT_PATH
,
265 E_WEB_EXTENSION_INTERFACE
,
267 g_variant_new ("(tssiiii)", *ppage_id
, attr_class
? attr_class
: "", attr_value
? attr_value
: "",
268 (gint
) (with_parents_left
- scroll_x
),
269 (gint
) (with_parents_top
- scroll_y
),
270 (gint
) webkit_dom_element_get_offset_width (element
),
271 (gint
) webkit_dom_element_get_offset_height (element
)),
275 g_warning ("Error emitting signal ElementClicked: %s\n", error
->message
);
276 g_error_free (error
);
284 web_extension_register_element_clicked_in_document (EWebExtension
*extension
,
286 WebKitDOMDocument
*document
,
287 const gchar
*element_class
)
289 WebKitDOMHTMLCollection
*collection
= NULL
;
292 g_return_if_fail (E_IS_WEB_EXTENSION (extension
));
293 g_return_if_fail (WEBKIT_DOM_IS_DOCUMENT (document
));
294 g_return_if_fail (element_class
&& *element_class
);
296 collection
= webkit_dom_document_get_elements_by_class_name_as_html_collection (document
, element_class
);
298 len
= webkit_dom_html_collection_get_length (collection
);
299 for (ii
= 0; ii
< len
; ii
++) {
302 node
= webkit_dom_html_collection_item (collection
, ii
);
303 if (WEBKIT_DOM_IS_EVENT_TARGET (node
)) {
306 ppage_id
= g_new0 (guint64
, 1);
309 g_object_set_data_full (G_OBJECT (node
), WEB_EXTENSION_PAGE_ID_KEY
, ppage_id
, g_free
);
311 /* Remove first, in case there was a listener already (it's when
312 the page is dynamically filled and not all the elements are
313 available in time of the first call. */
314 webkit_dom_event_target_remove_event_listener (
315 WEBKIT_DOM_EVENT_TARGET (node
), "click",
316 G_CALLBACK (element_clicked_cb
), FALSE
);
318 webkit_dom_event_target_add_event_listener (
319 WEBKIT_DOM_EVENT_TARGET (node
), "click",
320 G_CALLBACK (element_clicked_cb
), FALSE
, extension
);
324 g_clear_object (&collection
);
326 /* Traverse also iframe-s */
327 collection
= webkit_dom_document_get_elements_by_tag_name_as_html_collection (document
, "iframe");
329 len
= webkit_dom_html_collection_get_length (collection
);
330 for (ii
= 0; ii
< len
; ii
++) {
333 node
= webkit_dom_html_collection_item (collection
, ii
);
334 if (WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (node
)) {
335 WebKitDOMDocument
*content
;
337 content
= webkit_dom_html_iframe_element_get_content_document (WEBKIT_DOM_HTML_IFRAME_ELEMENT (node
));
339 web_extension_register_element_clicked_in_document (extension
, page_id
, content
, element_class
);
343 g_clear_object (&collection
);
347 e_web_extension_find_page_id_from_document (WebKitDOMDocument
*document
)
351 g_return_val_if_fail (WEBKIT_DOM_IS_DOCUMENT (document
), 0);
354 WebKitDOMDocument
*prev_document
= document
;
356 ppage_id
= g_object_get_data (G_OBJECT (document
), WEB_EXTENSION_PAGE_ID_KEY
);
360 document
= webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (document
));
361 if (prev_document
== document
)
369 e_web_extension_set_need_input (EWebExtension
*extension
,
373 EWebPageData
*page_data
;
374 GError
*error
= NULL
;
376 g_return_if_fail (E_IS_WEB_EXTENSION (extension
));
377 g_return_if_fail (page_id
!= 0);
379 page_data
= g_hash_table_lookup (extension
->priv
->pages
, &page_id
);
381 if (!page_data
|| (!page_data
->need_input
) == (!need_input
))
384 page_data
->need_input
= need_input
;
386 g_dbus_connection_emit_signal (
387 extension
->priv
->dbus_connection
,
389 E_WEB_EXTENSION_OBJECT_PATH
,
390 E_WEB_EXTENSION_INTERFACE
,
392 g_variant_new ("(tb)", page_id
, need_input
),
396 g_warning ("Error emitting signal NeedInputChanged: %s\n", error
->message
);
397 g_error_free (error
);
402 element_focus_cb (WebKitDOMElement
*element
,
403 WebKitDOMEvent
*event
,
404 EWebExtension
*extension
)
408 g_return_if_fail (E_IS_WEB_EXTENSION (extension
));
410 ppage_id
= g_object_get_data (G_OBJECT (element
), WEB_EXTENSION_PAGE_ID_KEY
);
411 g_return_if_fail (ppage_id
!= NULL
);
413 e_web_extension_set_need_input (extension
, *ppage_id
, TRUE
);
417 element_blur_cb (WebKitDOMElement
*element
,
418 WebKitDOMEvent
*event
,
419 EWebExtension
*extension
)
423 g_return_if_fail (E_IS_WEB_EXTENSION (extension
));
425 ppage_id
= g_object_get_data (G_OBJECT (element
), WEB_EXTENSION_PAGE_ID_KEY
);
426 g_return_if_fail (ppage_id
!= NULL
);
428 e_web_extension_set_need_input (extension
, *ppage_id
, FALSE
);
432 e_web_extension_bind_focus_and_blur_recursively (EWebExtension
*extension
,
433 WebKitDOMDocument
*document
,
434 const gchar
*selector
,
437 WebKitDOMNodeList
*nodes
= NULL
;
438 WebKitDOMHTMLCollection
*frames
= NULL
;
441 g_return_if_fail (E_IS_WEB_EXTENSION (extension
));
443 nodes
= webkit_dom_document_query_selector_all (document
, selector
, NULL
);
445 length
= webkit_dom_node_list_get_length (nodes
);
446 for (ii
= 0; ii
< length
; ii
++) {
450 node
= webkit_dom_node_list_item (nodes
, ii
);
452 ppage_id
= g_new (guint64
, 1);
455 g_object_set_data_full (G_OBJECT (node
), WEB_EXTENSION_PAGE_ID_KEY
, ppage_id
, g_free
);
457 webkit_dom_event_target_add_event_listener (
458 WEBKIT_DOM_EVENT_TARGET (node
), "focus",
459 G_CALLBACK (element_focus_cb
), FALSE
, extension
);
461 webkit_dom_event_target_add_event_listener (
462 WEBKIT_DOM_EVENT_TARGET (node
), "blur",
463 G_CALLBACK (element_blur_cb
), FALSE
, extension
);
465 g_clear_object (&nodes
);
467 frames
= webkit_dom_document_get_elements_by_tag_name_as_html_collection (document
, "iframe");
468 length
= webkit_dom_html_collection_get_length (frames
);
470 /* Add rules to every sub document */
471 for (ii
= 0; ii
< length
; ii
++) {
472 WebKitDOMDocument
*content_document
= NULL
;
475 node
= webkit_dom_html_collection_item (frames
, ii
);
477 webkit_dom_html_iframe_element_get_content_document (
478 WEBKIT_DOM_HTML_IFRAME_ELEMENT (node
));
480 if (!content_document
)
483 e_web_extension_bind_focus_and_blur_recursively (
489 g_clear_object (&frames
);
493 e_web_extension_bind_focus_on_elements (EWebExtension
*extension
,
494 WebKitDOMDocument
*document
)
496 const gchar
*elements
= "input, textarea, select, button, label";
499 g_return_if_fail (E_IS_WEB_EXTENSION (extension
));
500 g_return_if_fail (WEBKIT_DOM_IS_DOCUMENT (document
));
502 page_id
= e_web_extension_find_page_id_from_document (document
);
503 g_return_if_fail (page_id
!= 0);
505 e_web_extension_bind_focus_and_blur_recursively (
513 handle_method_call (GDBusConnection
*connection
,
515 const char *object_path
,
516 const char *interface_name
,
517 const char *method_name
,
518 GVariant
*parameters
,
519 GDBusMethodInvocation
*invocation
,
523 EWebExtension
*extension
= E_WEB_EXTENSION (user_data
);
524 WebKitDOMDocument
*document
;
525 WebKitWebExtension
*web_extension
= extension
->priv
->wk_extension
;
526 WebKitWebPage
*web_page
;
528 if (g_strcmp0 (interface_name
, E_WEB_EXTENSION_INTERFACE
) != 0)
531 if (camel_debug ("webkit:preview"))
532 printf ("EWebExtension - %s - %s\n", G_STRFUNC
, method_name
);
534 if (g_strcmp0 (method_name
, "RegisterElementClicked") == 0) {
535 const gchar
*element_class
= NULL
;
537 g_variant_get (parameters
, "(t&s)", &page_id
, &element_class
);
539 web_page
= get_webkit_web_page_or_return_dbus_error (invocation
, web_extension
, page_id
);
543 if (!element_class
|| !*element_class
) {
544 g_warn_if_fail (element_class
&& *element_class
);
546 document
= webkit_web_page_get_dom_document (web_page
);
547 web_extension_register_element_clicked_in_document (extension
, page_id
, document
, element_class
);
550 g_dbus_method_invocation_return_value (invocation
, NULL
);
551 } else if (g_strcmp0 (method_name
, "SetElementHidden") == 0) {
552 const gchar
*element_id
= NULL
;
553 gboolean hidden
= FALSE
;
555 g_variant_get (parameters
, "(t&sb)", &page_id
, &element_id
, &hidden
);
557 web_page
= get_webkit_web_page_or_return_dbus_error (invocation
, web_extension
, page_id
);
561 if (!element_id
|| !*element_id
) {
562 g_warn_if_fail (element_id
&& *element_id
);
564 gboolean expand_inner_data
= FALSE
;
566 document
= webkit_web_page_get_dom_document (web_page
);
567 /* A secret short-cut, to not have two functions for basically the same thing ("hide attachment" and "hide element") */
568 if (!hidden
&& g_str_has_prefix (element_id
, "attachment-wrapper-")) {
569 WebKitDOMElement
*element
;
571 element
= e_dom_utils_find_element_by_id (document
, element_id
);
573 if (WEBKIT_DOM_IS_HTML_ELEMENT (element
) &&
574 webkit_dom_element_get_child_element_count (element
) == 0) {
575 gchar
*inner_html_data
;
577 expand_inner_data
= TRUE
;
579 inner_html_data
= webkit_dom_element_get_attribute (element
, "inner-html-data");
580 if (inner_html_data
&& *inner_html_data
) {
581 gchar
*related_part_id
;
583 webkit_dom_element_set_inner_html (element
, inner_html_data
, NULL
);
584 webkit_dom_element_remove_attribute (element
, "inner-html-data");
586 related_part_id
= webkit_dom_element_get_attribute (element
, "related-part-id");
587 webkit_dom_element_remove_attribute (element
, "related-part-id");
589 if (related_part_id
&& *related_part_id
) {
590 GError
*error
= NULL
;
592 g_dbus_connection_emit_signal (
593 extension
->priv
->dbus_connection
,
595 E_WEB_EXTENSION_OBJECT_PATH
,
596 E_WEB_EXTENSION_INTERFACE
,
598 g_variant_new ("(ts)", page_id
, related_part_id
),
602 g_warning ("Error emitting signal MailPartAppeared: %s", error
->message
);
603 g_error_free (error
);
607 g_free (related_part_id
);
610 g_free (inner_html_data
);
614 e_dom_utils_hide_element (document
, element_id
, hidden
);
616 if (expand_inner_data
)
617 e_dom_resize_document_content_to_preview_width (document
);
620 g_dbus_method_invocation_return_value (invocation
, NULL
);
621 } else if (g_strcmp0 (method_name
, "SetElementStyleProperty") == 0) {
622 const gchar
*element_id
= NULL
, *property_name
= NULL
, *value
= NULL
, *priority
= NULL
;
624 g_variant_get (parameters
, "(t&s&s&s&s)", &page_id
, &element_id
, &property_name
, &value
, &priority
);
626 web_page
= get_webkit_web_page_or_return_dbus_error (invocation
, web_extension
, page_id
);
630 if (!element_id
|| !*element_id
|| !property_name
|| !*property_name
) {
631 g_warn_if_fail (element_id
&& *element_id
);
632 g_warn_if_fail (property_name
&& *property_name
);
634 WebKitDOMElement
*element
;
635 gboolean use_child
= FALSE
;
638 /* element_id can be also of the form: "id::child", where the change will
639 be done on the first child of it */
640 use_child
= g_str_has_suffix (element_id
, "::child");
642 tmp
= g_strdup (element_id
);
643 tmp
[strlen (tmp
) - 7] = '\0';
648 document
= webkit_web_page_get_dom_document (web_page
);
649 element
= e_dom_utils_find_element_by_id (document
, element_id
);
651 if (use_child
&& element
)
652 element
= webkit_dom_element_get_first_element_child (element
);
655 WebKitDOMCSSStyleDeclaration
*css
;
657 css
= webkit_dom_element_get_style (element
);
660 webkit_dom_css_style_declaration_set_property (css
, property_name
, value
, priority
, NULL
);
662 g_free (webkit_dom_css_style_declaration_remove_property (css
, property_name
, NULL
));
664 g_clear_object (&css
);
670 g_dbus_method_invocation_return_value (invocation
, NULL
);
671 } else if (g_strcmp0 (method_name
, "SetElementAttribute") == 0) {
672 const gchar
*element_id
= NULL
, *namespace_uri
= NULL
, *qualified_name
= NULL
, *value
= NULL
;
674 g_variant_get (parameters
, "(t&s&s&s&s)", &page_id
, &element_id
, &namespace_uri
, &qualified_name
, &value
);
676 web_page
= get_webkit_web_page_or_return_dbus_error (invocation
, web_extension
, page_id
);
680 if (!element_id
|| !*element_id
|| !qualified_name
|| !*qualified_name
) {
681 g_warn_if_fail (element_id
&& *element_id
);
682 g_warn_if_fail (qualified_name
&& *qualified_name
);
684 WebKitDOMElement
*element
;
685 gboolean use_child
= FALSE
;
688 /* element_id can be also of the form: "id::child", where the change will
689 be done on the first child of it */
690 use_child
= g_str_has_suffix (element_id
, "::child");
692 tmp
= g_strdup (element_id
);
693 tmp
[strlen (tmp
) - 7] = '\0';
698 if (namespace_uri
&& !*namespace_uri
)
699 namespace_uri
= NULL
;
701 document
= webkit_web_page_get_dom_document (web_page
);
702 element
= e_dom_utils_find_element_by_id (document
, element_id
);
704 if (use_child
&& element
)
705 element
= webkit_dom_element_get_first_element_child (element
);
709 webkit_dom_element_set_attribute_ns (element
, namespace_uri
, qualified_name
, value
, NULL
);
711 webkit_dom_element_remove_attribute_ns (element
, namespace_uri
, qualified_name
);
717 g_dbus_method_invocation_return_value (invocation
, NULL
);
718 } else if (g_strcmp0 (method_name
, "DocumentHasSelection") == 0) {
719 gboolean has_selection
;
721 g_variant_get (parameters
, "(t)", &page_id
);
722 web_page
= get_webkit_web_page_or_return_dbus_error (
723 invocation
, web_extension
, page_id
);
727 document
= webkit_web_page_get_dom_document (web_page
);
728 has_selection
= e_dom_utils_document_has_selection (document
);
730 g_dbus_method_invocation_return_value (
731 invocation
, g_variant_new ("(b)", has_selection
));
732 } else if (g_strcmp0 (method_name
, "GetDocumentContentHTML") == 0) {
735 g_variant_get (parameters
, "(t)", &page_id
);
736 web_page
= get_webkit_web_page_or_return_dbus_error (
737 invocation
, web_extension
, page_id
);
741 document
= webkit_web_page_get_dom_document (web_page
);
742 html_content
= e_dom_utils_get_document_content_html (document
);
744 g_dbus_method_invocation_return_value (
748 g_variant_new_take_string (
749 html_content
? html_content
: g_strdup (""))));
750 } else if (g_strcmp0 (method_name
, "GetSelectionContentHTML") == 0) {
753 g_variant_get (parameters
, "(t)", &page_id
);
754 web_page
= get_webkit_web_page_or_return_dbus_error (
755 invocation
, web_extension
, page_id
);
759 document
= webkit_web_page_get_dom_document (web_page
);
760 html_content
= e_dom_utils_get_selection_content_html (document
);
762 g_dbus_method_invocation_return_value (
766 g_variant_new_take_string (
767 html_content
? html_content
: g_strdup (""))));
768 } else if (g_strcmp0 (method_name
, "GetSelectionContentMultipart") == 0) {
770 gboolean is_html
= FALSE
;
772 g_variant_get (parameters
, "(t)", &page_id
);
773 web_page
= get_webkit_web_page_or_return_dbus_error (
774 invocation
, web_extension
, page_id
);
778 document
= webkit_web_page_get_dom_document (web_page
);
779 text_content
= e_dom_utils_get_selection_content_multipart (document
, &is_html
);
781 g_dbus_method_invocation_return_value (
785 g_variant_new_take_string (
786 text_content
? text_content
: g_strdup ("")),
788 } else if (g_strcmp0 (method_name
, "GetSelectionContentText") == 0) {
791 g_variant_get (parameters
, "(t)", &page_id
);
792 web_page
= get_webkit_web_page_or_return_dbus_error (
793 invocation
, web_extension
, page_id
);
797 document
= webkit_web_page_get_dom_document (web_page
);
798 text_content
= e_dom_utils_get_selection_content_text (document
);
800 g_dbus_method_invocation_return_value (
804 g_variant_new_take_string (
805 text_content
? text_content
: g_strdup (""))));
806 } else if (g_strcmp0 (method_name
, "AddCSSRuleIntoStyleSheet") == 0) {
807 const gchar
*style_sheet_id
, *selector
, *style
;
812 &page_id
, &style_sheet_id
, &selector
, &style
);
814 web_page
= get_webkit_web_page_or_return_dbus_error (
815 invocation
, web_extension
, page_id
);
819 document
= webkit_web_page_get_dom_document (web_page
);
820 e_dom_utils_add_css_rule_into_style_sheet (document
, style_sheet_id
, selector
, style
);
822 g_dbus_method_invocation_return_value (invocation
, NULL
);
823 } else if (g_strcmp0 (method_name
, "CreateAndAddCSSStyleSheet") == 0) {
824 const gchar
*style_sheet_id
;
826 g_variant_get (parameters
, "(t&s)", &page_id
, &style_sheet_id
);
827 web_page
= get_webkit_web_page_or_return_dbus_error (
828 invocation
, web_extension
, page_id
);
832 document
= webkit_web_page_get_dom_document (web_page
);
833 e_dom_utils_create_and_add_css_style_sheet (document
, style_sheet_id
);
835 g_dbus_method_invocation_return_value (invocation
, NULL
);
836 } else if (g_strcmp0 (method_name
, "EABContactFormatterBindDOM") == 0) {
837 g_variant_get (parameters
, "(t)", &page_id
);
838 web_page
= get_webkit_web_page_or_return_dbus_error (
839 invocation
, web_extension
, page_id
);
843 document
= webkit_web_page_get_dom_document (web_page
);
844 e_dom_utils_eab_contact_formatter_bind_dom (document
);
846 g_dbus_method_invocation_return_value (invocation
, NULL
);
847 } else if (g_strcmp0 (method_name
, "EMailDisplayBindDOM") == 0) {
848 g_variant_get (parameters
, "(t)", &page_id
);
849 web_page
= get_webkit_web_page_or_return_dbus_error (
850 invocation
, web_extension
, page_id
);
854 document
= webkit_web_page_get_dom_document (web_page
);
855 e_dom_utils_e_mail_display_unstyle_blockquotes (document
);
856 e_dom_utils_e_mail_display_bind_dom (document
, connection
);
857 e_web_extension_bind_focus_on_elements (extension
, document
);
859 g_dbus_method_invocation_return_value (invocation
, NULL
);
860 } else if (g_strcmp0 (method_name
, "ElementExists") == 0) {
861 const gchar
*element_id
;
862 gboolean element_exists
;
864 g_variant_get (parameters
, "(t&s)", &page_id
, &element_id
);
865 web_page
= get_webkit_web_page_or_return_dbus_error (
866 invocation
, web_extension
, page_id
);
870 document
= webkit_web_page_get_dom_document (web_page
);
871 element_exists
= e_dom_utils_element_exists (document
, element_id
);
873 g_dbus_method_invocation_return_value (
874 invocation
, g_variant_new ("(bt)", element_exists
, page_id
));
875 } else if (g_strcmp0 (method_name
, "GetActiveElementName") == 0) {
878 g_variant_get (parameters
, "(t)", &page_id
);
879 web_page
= get_webkit_web_page_or_return_dbus_error (
880 invocation
, web_extension
, page_id
);
884 document
= webkit_web_page_get_dom_document (web_page
);
885 element_name
= e_dom_utils_get_active_element_name (document
);
887 g_dbus_method_invocation_return_value (
891 g_variant_new_take_string (
892 element_name
? element_name
: g_strdup (""))));
893 } else if (g_strcmp0 (method_name
, "EMailPartHeadersBindDOMElement") == 0) {
894 const gchar
*element_id
;
896 g_variant_get (parameters
, "(t&s)", &page_id
, &element_id
);
897 web_page
= get_webkit_web_page_or_return_dbus_error (
898 invocation
, web_extension
, page_id
);
902 document
= webkit_web_page_get_dom_document (web_page
);
903 e_dom_utils_e_mail_part_headers_bind_dom_element (document
, element_id
);
905 g_dbus_method_invocation_return_value (invocation
, NULL
);
906 } else if (g_strcmp0 (method_name
, "VCardInlineBindDOM") == 0) {
907 const gchar
*element_id
;
909 g_variant_get (parameters
, "(t&s)", &page_id
, &element_id
);
910 web_page
= get_webkit_web_page_or_return_dbus_error (
911 invocation
, web_extension
, page_id
);
915 document
= webkit_web_page_get_dom_document (web_page
);
916 e_dom_utils_module_vcard_inline_bind_dom (
917 document
, element_id
, connection
);
919 g_dbus_method_invocation_return_value (invocation
, NULL
);
920 } else if (g_strcmp0 (method_name
, "VCardInlineUpdateButton") == 0) {
921 const gchar
*button_id
, *html_label
, *access_key
;
926 &page_id
, &button_id
, &html_label
, &access_key
);
928 web_page
= get_webkit_web_page_or_return_dbus_error (
929 invocation
, web_extension
, page_id
);
933 document
= webkit_web_page_get_dom_document (web_page
);
934 e_dom_utils_module_vcard_inline_update_button (
935 document
, button_id
, html_label
, access_key
);
937 g_dbus_method_invocation_return_value (invocation
, NULL
);
938 } else if (g_strcmp0 (method_name
, "VCardInlineSetIFrameSrc") == 0) {
939 const gchar
*src
, *button_id
;
941 g_variant_get (parameters
, "(t&s&s)", &page_id
, &button_id
, &src
);
942 web_page
= get_webkit_web_page_or_return_dbus_error (
943 invocation
, web_extension
, page_id
);
947 document
= webkit_web_page_get_dom_document (web_page
);
948 e_dom_utils_module_vcard_inline_set_iframe_src (document
, button_id
, src
);
950 g_dbus_method_invocation_return_value (invocation
, NULL
);
951 } else if (g_strcmp0 (method_name
, "GetDocumentURIFromPoint") == 0) {
952 WebKitDOMDocument
*document_at_point
;
953 gchar
*document_uri
= NULL
;
954 gint32 xx
= 0, yy
= 0;
956 g_variant_get (parameters
, "(tii)", &page_id
, &xx
, &yy
);
957 web_page
= get_webkit_web_page_or_return_dbus_error (invocation
, web_extension
, page_id
);
961 document
= webkit_web_page_get_dom_document (web_page
);
962 document_at_point
= e_dom_utils_get_document_from_point (document
, xx
, yy
);
964 if (document_at_point
)
965 document_uri
= webkit_dom_document_get_document_uri (document_at_point
);
967 g_dbus_method_invocation_return_value (
969 g_variant_new ("(@s)", g_variant_new_take_string (document_uri
? document_uri
: g_strdup (""))));
970 } else if (g_strcmp0 (method_name
, "SetDocumentIFrameSrc") == 0) {
971 const gchar
*document_uri
= NULL
, *new_iframe_src
= NULL
;
972 WebKitDOMDocument
*iframe_document
;
974 g_variant_get (parameters
, "(t&s&s)", &page_id
, &document_uri
, &new_iframe_src
);
975 web_page
= get_webkit_web_page_or_return_dbus_error (invocation
, web_extension
, page_id
);
979 document
= webkit_web_page_get_dom_document (web_page
);
980 iframe_document
= e_dom_utils_find_document_with_uri (document
, document_uri
);
982 if (iframe_document
) {
983 WebKitDOMDOMWindow
*dom_window
;
984 WebKitDOMElement
*frame_element
;
986 /* Get frame's window and from the window the actual <iframe> element */
987 dom_window
= webkit_dom_document_get_default_view (iframe_document
);
988 frame_element
= webkit_dom_dom_window_get_frame_element (dom_window
);
989 webkit_dom_html_iframe_element_set_src (
990 WEBKIT_DOM_HTML_IFRAME_ELEMENT (frame_element
), new_iframe_src
);
991 g_clear_object (&dom_window
);
994 g_dbus_method_invocation_return_value (invocation
, NULL
);
995 } else if (g_strcmp0 (method_name
, "ProcessMagicSpacebar") == 0) {
996 gboolean towards_bottom
= FALSE
, processed
= FALSE
;
997 WebKitDOMDOMWindow
*dom_window
;
998 glong inner_height
= -1, scroll_y_before
= -1, scroll_y_after
= -1;
1000 g_variant_get (parameters
, "(tb)", &page_id
, &towards_bottom
);
1001 web_page
= get_webkit_web_page_or_return_dbus_error (invocation
, web_extension
, page_id
);
1005 document
= webkit_web_page_get_dom_document (web_page
);
1006 dom_window
= webkit_dom_document_get_default_view (document
);
1008 g_object_get (G_OBJECT (dom_window
),
1009 "inner-height", &inner_height
,
1010 "scroll-y", &scroll_y_before
,
1014 webkit_dom_dom_window_scroll_by (dom_window
, 0, towards_bottom
? inner_height
: -inner_height
);
1016 g_object_get (G_OBJECT (dom_window
),
1017 "scroll-y", &scroll_y_after
,
1020 processed
= scroll_y_before
!= scroll_y_after
;
1023 g_dbus_method_invocation_return_value (invocation
, g_variant_new ("(b)", processed
));
1024 } else if (g_strcmp0 (method_name
, "EWebViewEnsureBodyClass") == 0) {
1025 const gchar
*body_class
= NULL
;
1026 WebKitDOMHTMLElement
*body
;
1028 g_variant_get (parameters
, "(t&s)", &page_id
, &body_class
);
1029 web_page
= get_webkit_web_page_or_return_dbus_error (invocation
, web_extension
, page_id
);
1033 document
= webkit_web_page_get_dom_document (web_page
);
1035 body
= webkit_dom_document_get_body (document
);
1036 if (body
&& !webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (body
), "class"))
1037 webkit_dom_element_set_class_name (WEBKIT_DOM_ELEMENT (body
), body_class
);
1039 g_dbus_method_invocation_return_value (invocation
, NULL
);
1044 handle_get_property (GDBusConnection
*connection
,
1045 const gchar
*sender
,
1046 const gchar
*object_path
,
1047 const gchar
*interface_name
,
1048 const gchar
*property_name
,
1052 /* EWebExtension *extension = E_WEB_EXTENSION (user_data); */
1053 GVariant
*variant
= NULL
;
1055 g_warn_if_reached ();
1061 handle_set_property (GDBusConnection
*connection
,
1062 const gchar
*sender
,
1063 const gchar
*object_path
,
1064 const gchar
*interface_name
,
1065 const gchar
*property_name
,
1070 /* EWebExtension *extension = E_WEB_EXTENSION (user_data); */
1072 g_warn_if_reached ();
1077 static const GDBusInterfaceVTable interface_vtable
= {
1079 handle_get_property
,
1084 web_page_gone_cb (gpointer user_data
,
1085 GObject
*gone_web_page
)
1087 EWebExtension
*extension
= user_data
;
1088 GHashTableIter iter
;
1089 gpointer key
, value
;
1091 g_return_if_fail (E_IS_WEB_EXTENSION (extension
));
1093 g_hash_table_iter_init (&iter
, extension
->priv
->pages
);
1094 while (g_hash_table_iter_next (&iter
, &key
, &value
)) {
1095 EWebPageData
*page_data
= value
;
1097 if (page_data
->web_page
== (gpointer
) gone_web_page
) {
1098 g_hash_table_remove (extension
->priv
->pages
, key
);
1105 e_web_extension_dispose (GObject
*object
)
1107 EWebExtension
*extension
= E_WEB_EXTENSION (object
);
1109 if (extension
->priv
->dbus_connection
) {
1110 g_dbus_connection_unregister_object (
1111 extension
->priv
->dbus_connection
,
1112 extension
->priv
->registration_id
);
1113 extension
->priv
->registration_id
= 0;
1114 extension
->priv
->dbus_connection
= NULL
;
1117 g_hash_table_remove_all (extension
->priv
->pages
);
1119 g_clear_object (&extension
->priv
->wk_extension
);
1121 G_OBJECT_CLASS (e_web_extension_parent_class
)->dispose (object
);
1125 e_web_extension_finalize (GObject
*object
)
1127 EWebExtension
*extension
= E_WEB_EXTENSION (object
);
1129 if (extension
->priv
->pages
) {
1130 g_hash_table_destroy (extension
->priv
->pages
);
1131 extension
->priv
->pages
= NULL
;
1134 G_OBJECT_CLASS (e_web_extension_parent_class
)->finalize (object
);
1138 e_web_extension_class_init (EWebExtensionClass
*class)
1140 GObjectClass
*object_class
= G_OBJECT_CLASS (class);
1142 g_type_class_add_private (object_class
, sizeof (EWebExtensionPrivate
));
1144 object_class
->dispose
= e_web_extension_dispose
;
1145 object_class
->finalize
= e_web_extension_finalize
;
1149 e_web_extension_init (EWebExtension
*extension
)
1151 extension
->priv
= G_TYPE_INSTANCE_GET_PRIVATE (extension
, E_TYPE_WEB_EXTENSION
, EWebExtensionPrivate
);
1153 extension
->priv
->initialized
= FALSE
;
1154 extension
->priv
->pages
= g_hash_table_new_full (g_int64_hash
, g_int64_equal
, g_free
, g_free
);
1158 e_web_extension_create_instance(gpointer data
)
1160 return g_object_new (E_TYPE_WEB_EXTENSION
, NULL
);
1164 e_web_extension_get (void)
1166 static GOnce once_init
= G_ONCE_INIT
;
1167 return E_WEB_EXTENSION (g_once (&once_init
, e_web_extension_create_instance
, NULL
));
1171 web_page_send_request_cb (WebKitWebPage
*web_page
,
1172 WebKitURIRequest
*request
,
1173 WebKitURIResponse
*redirected_response
,
1174 EWebExtension
*extension
)
1176 const gchar
*request_uri
;
1177 const gchar
*page_uri
;
1179 request_uri
= webkit_uri_request_get_uri (request
);
1180 page_uri
= webkit_web_page_get_uri (web_page
);
1182 /* Always load the main resource. */
1183 if (g_strcmp0 (request_uri
, page_uri
) == 0 ||
1184 /* Do not influence real pages, like those with eds OAuth sign-in */
1185 g_str_has_prefix (page_uri
, "http:") ||
1186 g_str_has_prefix (page_uri
, "https:"))
1189 if (g_str_has_prefix (request_uri
, "http:") ||
1190 g_str_has_prefix (request_uri
, "https:")) {
1193 new_uri
= g_strconcat ("evo-", request_uri
, NULL
);
1195 webkit_uri_request_set_uri (request
, new_uri
);
1204 e_web_extension_store_page_id_on_document (WebKitWebPage
*web_page
)
1206 WebKitDOMDocument
*document
;
1209 g_return_if_fail (WEBKIT_IS_WEB_PAGE (web_page
));
1211 ppage_id
= g_new (guint64
, 1);
1212 *ppage_id
= webkit_web_page_get_id (web_page
);
1214 document
= webkit_web_page_get_dom_document (web_page
);
1216 g_object_set_data_full (G_OBJECT (document
), WEB_EXTENSION_PAGE_ID_KEY
, ppage_id
, g_free
);
1220 web_page_document_loaded_cb (WebKitWebPage
*web_page
,
1223 WebKitDOMDocument
*document
;
1225 e_web_extension_store_page_id_on_document (web_page
);
1227 document
= webkit_web_page_get_dom_document (web_page
);
1229 e_dom_utils_replace_local_image_links (document
);
1231 if ((webkit_dom_document_query_selector (
1232 document
, "[data-evo-signature-plain-text-mode]", NULL
))) {
1234 WebKitDOMHTMLElement
*body
;
1236 body
= webkit_dom_document_get_body (document
);
1238 webkit_dom_element_set_attribute (
1239 WEBKIT_DOM_ELEMENT (body
),
1241 "font-family: Monospace;",
1247 e_web_extension_set_clipboard_flags (EWebExtension
*extension
,
1248 WebKitDOMDocument
*document
,
1249 guint32 clipboard_flags
)
1251 EWebPageData
*page_data
;
1253 GError
*error
= NULL
;
1255 g_return_if_fail (E_IS_WEB_EXTENSION (extension
));
1256 g_return_if_fail (WEBKIT_DOM_IS_DOCUMENT (document
));
1258 page_id
= e_web_extension_find_page_id_from_document (document
);
1259 g_return_if_fail (page_id
!= 0);
1261 page_data
= g_hash_table_lookup (extension
->priv
->pages
, &page_id
);
1263 if (!page_data
|| page_data
->clipboard_flags
== clipboard_flags
)
1266 page_data
->clipboard_flags
= clipboard_flags
;
1268 g_dbus_connection_emit_signal (
1269 extension
->priv
->dbus_connection
,
1271 E_WEB_EXTENSION_OBJECT_PATH
,
1272 E_WEB_EXTENSION_INTERFACE
,
1273 "ClipboardFlagsChanged",
1274 g_variant_new ("(tu)", page_id
, clipboard_flags
),
1278 g_warning ("Error emitting signal ClipboardFlagsChanged: %s\n", error
->message
);
1279 g_error_free (error
);
1284 web_editor_selection_changed_cb (WebKitWebEditor
*web_editor
,
1285 EWebExtension
*extension
)
1287 WebKitWebPage
*web_page
;
1288 WebKitDOMDocument
*document
;
1289 guint32 clipboard_flags
= 0;
1291 web_page
= webkit_web_editor_get_page (web_editor
);
1293 document
= webkit_web_page_get_dom_document (web_page
);
1295 if (e_dom_utils_document_has_selection (document
))
1296 clipboard_flags
|= E_CLIPBOARD_CAN_COPY
;
1298 e_web_extension_set_clipboard_flags (extension
, document
, clipboard_flags
);
1302 web_page_created_cb (WebKitWebExtension
*wk_extension
,
1303 WebKitWebPage
*web_page
,
1304 EWebExtension
*extension
)
1306 EWebPageData
*page_data
;
1309 ppage_id
= g_new (guint64
, 1);
1310 *ppage_id
= webkit_web_page_get_id (web_page
);
1312 page_data
= g_new0 (EWebPageData
, 1);
1313 page_data
->web_page
= web_page
;
1314 page_data
->need_input
= FALSE
;
1315 page_data
->clipboard_flags
= 0;
1317 e_web_extension_store_page_id_on_document (web_page
);
1319 g_hash_table_insert (extension
->priv
->pages
, ppage_id
, page_data
);
1321 g_object_weak_ref (G_OBJECT (web_page
), web_page_gone_cb
, extension
);
1323 g_signal_connect_object (
1324 web_page
, "send-request",
1325 G_CALLBACK (web_page_send_request_cb
),
1328 g_signal_connect_object (
1329 web_page
, "document-loaded",
1330 G_CALLBACK (web_page_document_loaded_cb
),
1333 g_signal_connect_object (
1334 webkit_web_page_get_editor (web_page
), "selection-changed",
1335 G_CALLBACK (web_editor_selection_changed_cb
),
1340 e_web_extension_initialize (EWebExtension
*extension
,
1341 WebKitWebExtension
*wk_extension
)
1343 g_return_if_fail (E_IS_WEB_EXTENSION (extension
));
1345 if (extension
->priv
->initialized
)
1348 extension
->priv
->initialized
= TRUE
;
1350 extension
->priv
->wk_extension
= g_object_ref (wk_extension
);
1353 wk_extension
, "page-created",
1354 G_CALLBACK (web_page_created_cb
), extension
);
1358 e_web_extension_dbus_register (EWebExtension
*extension
,
1359 GDBusConnection
*connection
)
1361 GError
*error
= NULL
;
1362 static GDBusNodeInfo
*introspection_data
= NULL
;
1364 g_return_if_fail (E_IS_WEB_EXTENSION (extension
));
1365 g_return_if_fail (G_IS_DBUS_CONNECTION (connection
));
1367 if (!introspection_data
) {
1368 introspection_data
=
1369 g_dbus_node_info_new_for_xml (introspection_xml
, NULL
);
1371 extension
->priv
->registration_id
=
1372 g_dbus_connection_register_object (
1374 E_WEB_EXTENSION_OBJECT_PATH
,
1375 introspection_data
->interfaces
[0],
1381 if (!extension
->priv
->registration_id
) {
1382 g_warning ("Failed to register object: %s\n", error
->message
);
1383 g_error_free (error
);
1385 extension
->priv
->dbus_connection
= connection
;
1386 g_object_add_weak_pointer (
1387 G_OBJECT (connection
),
1388 (gpointer
*)&extension
->priv
->dbus_connection
);