/*
 * e-dom-utils.c
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 */

#include "evolution-config.h"

#include <string.h>

#include <webkitdom/webkitdom.h>

#include "e-web-extension.h"
#include "e-web-extension-names.h"

#include "e-dom-utils.h"

void
e_dom_utils_replace_local_image_links (WebKitDOMDocument *document)
{
	gint ii, length;
	WebKitDOMNodeList *list = NULL;

	list = webkit_dom_document_query_selector_all (
		document, "img[src^=\"file://\"]", NULL);
	length = webkit_dom_node_list_get_length (list);

	for (ii = 0; ii < length; ii++) {
		gchar *src, *new_src;
		WebKitDOMHTMLImageElement *img;

		img = WEBKIT_DOM_HTML_IMAGE_ELEMENT (
			webkit_dom_node_list_item (list, ii));
		src = webkit_dom_html_image_element_get_src (img);

		/* this forms "evo-file://", which can be loaded,
		 * while "file://" cannot be, due to WebKit policy */
		new_src = g_strconcat ("evo-", src, NULL);
		webkit_dom_html_image_element_set_src (img, new_src);
		g_free (new_src);
		g_free (src);
	}
	g_clear_object (&list);

	list = webkit_dom_document_query_selector_all (
		document, "iframe", NULL);
	length = webkit_dom_node_list_get_length (list);
	for (ii = 0; ii < length; ii++) {
		WebKitDOMDocument *content_document;
		WebKitDOMHTMLIFrameElement *iframe;

		iframe = WEBKIT_DOM_HTML_IFRAME_ELEMENT (
			webkit_dom_node_list_item (list, ii));

		content_document =
			webkit_dom_html_iframe_element_get_content_document (iframe);

		if (content_document && WEBKIT_DOM_IS_DOCUMENT (content_document))
			e_dom_utils_replace_local_image_links (content_document);
	}
	g_clear_object (&list);
}

gboolean
e_dom_utils_document_has_selection (WebKitDOMDocument *document)
{
	gboolean ret_val = FALSE;
	WebKitDOMDOMWindow *dom_window = NULL;
	WebKitDOMDOMSelection *dom_selection = NULL;

	if (!document)
		return FALSE;

	dom_window = webkit_dom_document_get_default_view (document);
	if (!dom_window)
		goto out;

	dom_selection = webkit_dom_dom_window_get_selection (dom_window);
	if (!WEBKIT_DOM_IS_DOM_SELECTION (dom_selection))
		goto out;

	if (webkit_dom_dom_selection_get_is_collapsed (dom_selection))
		goto out;

	ret_val = TRUE;
 out:
	g_clear_object (&dom_window);
	g_clear_object (&dom_selection);

	if (!ret_val) {
		WebKitDOMHTMLCollection *frames = NULL;
		gulong ii, length;

		frames = webkit_dom_document_get_elements_by_tag_name_as_html_collection (document, "iframe");
		length = webkit_dom_html_collection_get_length (frames);
		for (ii = 0; ii < length; ii++) {
			WebKitDOMNode *node;
			WebKitDOMDocument *content_document;

			node = webkit_dom_html_collection_item (frames, ii);
			content_document = webkit_dom_html_iframe_element_get_content_document (
				WEBKIT_DOM_HTML_IFRAME_ELEMENT (node));

			if ((ret_val = e_dom_utils_document_has_selection (content_document)))
				break;
		}

		g_clear_object (&frames);
	}

	return ret_val;
}


gchar *
e_dom_utils_get_document_content_html (WebKitDOMDocument *document)
{
	WebKitDOMElement *element;

	element = webkit_dom_document_get_document_element (document);

	return webkit_dom_element_get_outer_html (element);
}

static gboolean
element_is_in_pre_tag (WebKitDOMNode *node)
{
	WebKitDOMElement *element;

	if (!node)
		return FALSE;

	while (element = webkit_dom_node_get_parent_element (node), element) {
		node = WEBKIT_DOM_NODE (element);

		if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (element)) {
			return TRUE;
		} else if (WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (element)) {
			break;
		}
	}

	return FALSE;
}

static gchar *
dom_selection_get_content_html (WebKitDOMDOMSelection *dom_selection,
                                WebKitDOMDocument *content_document)
{
	gchar *inner_html;
	WebKitDOMDocumentFragment *fragment;
	WebKitDOMNode *node;
	WebKitDOMElement *element;
	WebKitDOMRange *range = NULL;

	range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
	fragment = webkit_dom_range_clone_contents (range, NULL);
	element = webkit_dom_document_create_element (content_document, "DIV", NULL);
	webkit_dom_node_append_child (
		WEBKIT_DOM_NODE (element),
		WEBKIT_DOM_NODE (fragment), NULL);

	inner_html = webkit_dom_element_get_inner_html (element);

	node = webkit_dom_range_get_start_container (range, NULL);
	if (element_is_in_pre_tag (node)) {
		gchar *tmp = inner_html;
		inner_html = g_strconcat ("<pre>", tmp, "</pre>", NULL);
		g_free (tmp);
	}

	g_clear_object (&range);
	return inner_html;
}

static gchar *
get_frame_selection_html (WebKitDOMElement *iframe)
{
	WebKitDOMDocument *content_document;
	WebKitDOMDOMWindow *dom_window = NULL;
	WebKitDOMDOMSelection *dom_selection = NULL;
	WebKitDOMHTMLCollection *frames = NULL;
	gulong ii, length;

	content_document = webkit_dom_html_iframe_element_get_content_document (
		WEBKIT_DOM_HTML_IFRAME_ELEMENT (iframe));

	if (!content_document)
		return NULL;

	dom_window = webkit_dom_document_get_default_view (content_document);
	dom_selection = webkit_dom_dom_window_get_selection (dom_window);
	g_clear_object (&dom_window);
	if (dom_selection && (webkit_dom_dom_selection_get_range_count (dom_selection) > 0)) {
		gchar *html = dom_selection_get_content_html (dom_selection, content_document);
		g_clear_object (&dom_selection);

		return html;
	}

	g_clear_object (&dom_selection);

	frames = webkit_dom_document_get_elements_by_tag_name_as_html_collection (content_document, "iframe");
	length = webkit_dom_html_collection_get_length (frames);
	for (ii = 0; ii < length; ii++) {
		WebKitDOMNode *node;
		gchar *html;

		node = webkit_dom_html_collection_item (frames, ii);

		html = get_frame_selection_html (WEBKIT_DOM_ELEMENT (node));

		if (html != NULL) {
			g_clear_object (&frames);
			return html;
		}
	}

	g_clear_object (&frames);

	return NULL;
}

gchar *
e_dom_utils_get_selection_content_html (WebKitDOMDocument *document)
{
	WebKitDOMHTMLCollection *frames = NULL;
	gulong ii, length;

	if (!e_dom_utils_document_has_selection (document))
		return NULL;

	frames = webkit_dom_document_get_elements_by_tag_name_as_html_collection (document, "iframe");
	length = webkit_dom_html_collection_get_length (frames);

	for (ii = 0; ii < length; ii++) {
		gchar *text;
		WebKitDOMNode *node;

		node = webkit_dom_html_collection_item (frames, ii);

		text = get_frame_selection_html (
			WEBKIT_DOM_ELEMENT (node));

		if (text != NULL) {
			g_clear_object (&frames);
			return text;
		}
	}

	g_clear_object (&frames);
	return NULL;
}

static gchar *
dom_selection_get_content_text (WebKitDOMDOMSelection *dom_selection)
{
	WebKitDOMRange *range = NULL;
	gchar *text = NULL;

	range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
	if (range)
		text = webkit_dom_range_to_string (range, NULL);
	g_clear_object (&range);

	return text;
}

static gchar *
get_frame_selection_content_text (WebKitDOMElement *iframe)
{
	WebKitDOMDocument *content_document;
	WebKitDOMDOMWindow *dom_window = NULL;
	WebKitDOMDOMSelection *dom_selection = NULL;
	WebKitDOMHTMLCollection *frames = NULL;
	gulong ii, length;

	content_document = webkit_dom_html_iframe_element_get_content_document (
		WEBKIT_DOM_HTML_IFRAME_ELEMENT (iframe));

	if (!content_document)
		return NULL;

	dom_window = webkit_dom_document_get_default_view (content_document);
	dom_selection = webkit_dom_dom_window_get_selection (dom_window);
	g_clear_object (&dom_window);
	if (dom_selection && (webkit_dom_dom_selection_get_range_count (dom_selection) > 0)) {
		gchar *text = dom_selection_get_content_text (dom_selection);
		g_clear_object (&dom_selection);
		return text;
	}
	g_clear_object (&dom_selection);

	frames = webkit_dom_document_get_elements_by_tag_name_as_html_collection (content_document, "iframe");
	length = webkit_dom_html_collection_get_length (frames);
	for (ii = 0; ii < length; ii++) {
		WebKitDOMNode *node;
		gchar *text;

		node = webkit_dom_html_collection_item (frames, ii);

		text = get_frame_selection_content_text (
			WEBKIT_DOM_ELEMENT (node));

		if (text != NULL) {
			g_clear_object (&frames);
			return text;
		}
	}

	g_clear_object (&frames);
	return NULL;
}

gchar *
e_dom_utils_get_selection_content_text (WebKitDOMDocument *document)
{
	WebKitDOMHTMLCollection *frames = NULL;
	gulong ii, length;

	frames = webkit_dom_document_get_elements_by_tag_name_as_html_collection (document, "iframe");
	length = webkit_dom_html_collection_get_length (frames);

	for (ii = 0; ii < length; ii++) {
		gchar *text;
		WebKitDOMNode *node;

		node = webkit_dom_html_collection_item (frames, ii);

		text = get_frame_selection_content_text (
			WEBKIT_DOM_ELEMENT (node));

		if (text != NULL) {
			g_clear_object (&frames);
			return text;
		}
	}

	g_clear_object (&frames);
	return NULL;
}

static gchar *
get_frame_selection_content_multipart (WebKitDOMElement *iframe,
                                       gboolean *is_html)
{
	WebKitDOMDocument *content_document;
	WebKitDOMDOMWindow *dom_window = NULL;
	WebKitDOMDOMSelection *dom_selection = NULL;
	WebKitDOMHTMLCollection *frames = NULL;
	gulong ii, length;

	content_document = webkit_dom_html_iframe_element_get_content_document (
		WEBKIT_DOM_HTML_IFRAME_ELEMENT (iframe));

	if (!content_document)
		return NULL;

	dom_window = webkit_dom_document_get_default_view (content_document);
	dom_selection = webkit_dom_dom_window_get_selection (dom_window);
	g_clear_object (&dom_window);
	if (dom_selection && (webkit_dom_dom_selection_get_range_count (dom_selection) > 0)) {
		gchar *content;
		gchar *uri = webkit_dom_document_get_document_uri (content_document);

		/* The URI is url encoded.. */
		if (strstr (uri, "mime_type=text%2Fplain")) {
			content = dom_selection_get_content_text (dom_selection);
			if (is_html)
				*is_html = FALSE;
		} else {
			content = dom_selection_get_content_html (dom_selection, content_document);
			if (is_html)
				*is_html = TRUE;
		}

		g_clear_object (&dom_selection);
		return content;
	}
	g_clear_object (&dom_selection);

	frames = webkit_dom_document_get_elements_by_tag_name_as_html_collection (content_document, "iframe");
	length = webkit_dom_html_collection_get_length (frames);
	for (ii = 0; ii < length; ii++) {
		WebKitDOMNode *node;
		gchar *content;

		node = webkit_dom_html_collection_item (frames, ii);

		content = get_frame_selection_content_multipart (WEBKIT_DOM_ELEMENT (node), is_html);

		if (content != NULL) {
			g_clear_object (&frames);
			return content;
		}
	}

	g_clear_object (&frames);
	return NULL;
}

gchar *
e_dom_utils_get_selection_content_multipart (WebKitDOMDocument *document,
                                             gboolean *is_html)
{
	WebKitDOMHTMLCollection *frames = NULL;
	gulong ii, length;

	frames = webkit_dom_document_get_elements_by_tag_name_as_html_collection (document, "iframe");
	length = webkit_dom_html_collection_get_length (frames);

	for (ii = 0; ii < length; ii++) {
		gchar *text;
		WebKitDOMNode *node;

		node = webkit_dom_html_collection_item (frames, ii);

		text = get_frame_selection_content_multipart (
			WEBKIT_DOM_ELEMENT (node), is_html);

		if (text != NULL) {
			g_clear_object (&frames);
			return text;
		}
	}

	g_clear_object (&frames);
	return NULL;
}

void
e_dom_utils_create_and_add_css_style_sheet (WebKitDOMDocument *document,
                                            const gchar *style_sheet_id)
{
	WebKitDOMElement *style_element;

	style_element = webkit_dom_document_get_element_by_id (document, style_sheet_id);

	if (!style_element) {
		WebKitDOMText *dom_text;
		WebKitDOMHTMLHeadElement *head;

		dom_text = webkit_dom_document_create_text_node (document, "");

		/* Create new <style> element */
		style_element = webkit_dom_document_create_element (document, "style", NULL);
		webkit_dom_element_set_id (
			style_element,
			style_sheet_id);
		webkit_dom_html_style_element_set_media (
			WEBKIT_DOM_HTML_STYLE_ELEMENT (style_element),
			"screen");
		webkit_dom_node_append_child (
			WEBKIT_DOM_NODE (style_element),
			/* WebKit hack - we have to insert empty TextNode into style element */
			WEBKIT_DOM_NODE (dom_text),
			NULL);

		head = webkit_dom_document_get_head (document);

		webkit_dom_node_append_child (
			WEBKIT_DOM_NODE (head),
			WEBKIT_DOM_NODE (style_element),
			NULL);
	}
}

void
e_dom_utils_add_css_rule_into_style_sheet_in_document (WebKitDOMDocument *document,
                                                       const gchar *style_sheet_id,
                                                       const gchar *selector,
                                                       const gchar *style)
{
	WebKitDOMElement *style_element;
	WebKitDOMStyleSheet *sheet = NULL;
	WebKitDOMCSSRuleList *rules_list = NULL;
	gint length, ii, selector_length;
	gboolean removed = FALSE;

	g_return_if_fail (WEBKIT_DOM_IS_HTML_DOCUMENT (document));
	g_return_if_fail (style_sheet_id && *style_sheet_id);
	g_return_if_fail (selector && *selector);

	selector_length = strlen (selector);
	style_element = webkit_dom_document_get_element_by_id (document, style_sheet_id);

	if (!style_element) {
		e_dom_utils_create_and_add_css_style_sheet (document, style_sheet_id);
		style_element = webkit_dom_document_get_element_by_id (document, style_sheet_id);
	}

	/* Get sheet that is associated with style element */
	sheet = webkit_dom_html_style_element_get_sheet (WEBKIT_DOM_HTML_STYLE_ELEMENT (style_element));

	rules_list = webkit_dom_css_style_sheet_get_css_rules (WEBKIT_DOM_CSS_STYLE_SHEET (sheet));
	length = webkit_dom_css_rule_list_get_length (rules_list);

	/* Check if rule exists */
	for (ii = 0; ii < length && !removed; ii++) {
		WebKitDOMCSSRule *rule;
		gchar *rule_text = NULL;

		rule = webkit_dom_css_rule_list_item (rules_list, ii);

		g_return_if_fail (WEBKIT_DOM_IS_CSS_RULE (rule));

		rule_text = webkit_dom_css_rule_get_css_text (rule);

		/* Find the start of the style => end of the selector */
		if (rule_text && g_str_has_prefix (rule_text, selector) &&
		    rule_text[selector_length] == ' ' && rule_text[selector_length + 1] == '{') {
			/* If exists remove it */
			webkit_dom_css_style_sheet_remove_rule (
				WEBKIT_DOM_CSS_STYLE_SHEET (sheet),
				ii, NULL);
			length--;
			removed = TRUE;
		}

		g_free (rule_text);
		g_object_unref (rule);
	}

	g_clear_object (&rules_list);

	/* Insert the rule at the end, so it will override previously inserted */
	webkit_dom_css_style_sheet_add_rule (
		WEBKIT_DOM_CSS_STYLE_SHEET (sheet), selector, style, length, NULL);

	g_clear_object (&sheet);
}

static void
add_css_rule_into_style_sheet_recursive (WebKitDOMDocument *document,
                                         const gchar *style_sheet_id,
                                         const gchar *selector,
                                         const gchar *style)
{
	WebKitDOMHTMLCollection *frames = NULL;
	gint ii, length;

	/* Add rule to document */
	e_dom_utils_add_css_rule_into_style_sheet_in_document (
		document,
		style_sheet_id,
		selector,
		style);

	frames = webkit_dom_document_get_elements_by_tag_name_as_html_collection (document, "iframe");
	length = webkit_dom_html_collection_get_length (frames);

	/* Add rules to every sub document */
	for (ii = 0; ii < length; ii++) {
		WebKitDOMDocument *content_document = NULL;
		WebKitDOMNode *node;

		node = webkit_dom_html_collection_item (frames, ii);
		content_document =
			webkit_dom_html_iframe_element_get_content_document (
				WEBKIT_DOM_HTML_IFRAME_ELEMENT (node));

		if (!content_document)
			continue;

		add_css_rule_into_style_sheet_recursive (
			content_document,
			style_sheet_id,
			selector,
			style);
	}
	g_clear_object (&frames);
}

void
e_dom_utils_add_css_rule_into_style_sheet (WebKitDOMDocument *document,
                                           const gchar *style_sheet_id,
                                           const gchar *selector,
                                           const gchar *style)
{
	g_return_if_fail (style_sheet_id && *style_sheet_id);
	g_return_if_fail (selector && *selector);
	g_return_if_fail (style && *style);

	add_css_rule_into_style_sheet_recursive (
		document,
		style_sheet_id,
		selector,
		style);
}

static void
collapse_contacts_list (WebKitDOMEventTarget *event_target,
                        WebKitDOMEvent *event,
                        gpointer user_data)
{
	WebKitDOMDocument *document;
	WebKitDOMElement *list;
	gchar *id, *list_id;
	gchar *imagesdir, *src;
	gboolean hidden;

	document = user_data;
	id = webkit_dom_element_get_id (WEBKIT_DOM_ELEMENT (event_target));

	if (!id)
		return;

	list_id = g_strconcat ("list-", id, NULL);
	list = webkit_dom_document_get_element_by_id (document, list_id);
	g_free (id);
	g_free (list_id);

	if (list == NULL)
		return;

	imagesdir = g_filename_to_uri (EVOLUTION_IMAGESDIR, NULL, NULL);
	hidden = webkit_dom_html_element_get_hidden (WEBKIT_DOM_HTML_ELEMENT (list));

	if (hidden)
		src = g_strdup_printf ("evo-file://%s/minus.png", imagesdir);
	else
		src = g_strdup_printf ("evo-file://%s/plus.png", imagesdir);

	webkit_dom_html_element_set_hidden (
		WEBKIT_DOM_HTML_ELEMENT (list), !hidden);
	webkit_dom_html_image_element_set_src (
		WEBKIT_DOM_HTML_IMAGE_ELEMENT (event_target), src);

	g_free (src);
	g_free (imagesdir);
}

static void
toggle_headers_visibility (WebKitDOMElement *button,
                           WebKitDOMEvent *event,
                           WebKitDOMDocument *document)
{
	WebKitDOMElement *short_headers = NULL, *full_headers = NULL;
	WebKitDOMCSSStyleDeclaration *css_short = NULL, *css_full = NULL;
	GSettings *settings;
	gboolean expanded;
	const gchar *path;
	gchar *css_value;

	short_headers = webkit_dom_document_get_element_by_id (
		document, "__evo-short-headers");
	if (short_headers == NULL)
		return;

	css_short = webkit_dom_element_get_style (short_headers);

	full_headers = webkit_dom_document_get_element_by_id (
		document, "__evo-full-headers");
	if (full_headers == NULL)
		goto clean;

	css_full = webkit_dom_element_get_style (full_headers);
	css_value = webkit_dom_css_style_declaration_get_property_value (
		css_full, "display");
	expanded = (g_strcmp0 (css_value, "table") == 0);
	g_free (css_value);

	webkit_dom_css_style_declaration_set_property (
		css_full, "display",
		expanded ? "none" : "table", "", NULL);
	webkit_dom_css_style_declaration_set_property (
		css_short, "display",
		expanded ? "table" : "none", "", NULL);

	if (expanded)
		path = "evo-file://" EVOLUTION_IMAGESDIR "/plus.png";
	else
		path = "evo-file://" EVOLUTION_IMAGESDIR "/minus.png";

	webkit_dom_html_image_element_set_src (
		WEBKIT_DOM_HTML_IMAGE_ELEMENT (button), path);

	settings = e_util_ref_settings ("org.gnome.evolution.mail");
	g_settings_set_boolean (settings, "headers-collapsed", expanded);
	g_clear_object (&settings);

 clean:
	g_clear_object (&short_headers);
	g_clear_object (&css_short);
	g_clear_object (&full_headers);
	g_clear_object (&css_full);
}

static void
toggle_address_visibility (WebKitDOMElement *button,
                           WebKitDOMEvent *event,
                           gpointer user_data)
{
	WebKitDOMElement *full_addr = NULL, *ellipsis = NULL;
	WebKitDOMElement *parent = NULL, *bold = NULL;
	WebKitDOMCSSStyleDeclaration *css_full = NULL, *css_ellipsis = NULL;
	const gchar *path;
	gchar *property_value;
	gboolean expanded;

	/* <b> element */
	bold = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (button));
	/* <td> element */
	parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (bold));

	full_addr = webkit_dom_element_query_selector (parent, "#__evo-moreaddr", NULL);

	if (!full_addr)
		goto clean;

	css_full = webkit_dom_element_get_style (full_addr);

	ellipsis = webkit_dom_element_query_selector (parent, "#__evo-moreaddr-ellipsis", NULL);

	if (!ellipsis)
		goto clean;

	css_ellipsis = webkit_dom_element_get_style (ellipsis);

	property_value = webkit_dom_css_style_declaration_get_property_value (css_full, "display");
	expanded = g_strcmp0 (property_value, "inline") == 0;
	g_free (property_value);

	webkit_dom_css_style_declaration_set_property (
		css_full, "display", (expanded ? "none" : "inline"), "", NULL);
	webkit_dom_css_style_declaration_set_property (
		css_ellipsis, "display", (expanded ? "inline" : "none"), "", NULL);

	if (expanded)
		path = "evo-file://" EVOLUTION_IMAGESDIR "/plus.png";
	else
		path = "evo-file://" EVOLUTION_IMAGESDIR "/minus.png";

	if (!WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (button)) {
		WebKitDOMElement *element;

		element = webkit_dom_element_query_selector (parent, "#__evo-moreaddr-img", NULL);
		if (!element)
			goto clean;

		webkit_dom_html_image_element_set_src (WEBKIT_DOM_HTML_IMAGE_ELEMENT (element), path);
	} else
		webkit_dom_html_image_element_set_src (WEBKIT_DOM_HTML_IMAGE_ELEMENT (button), path);

 clean:
	g_clear_object (&css_full);
	g_clear_object (&css_ellipsis);
	g_clear_object (&full_addr);
	g_clear_object (&ellipsis);
	g_clear_object (&parent);
}

static void
e_dom_utils_bind_dom (WebKitDOMDocument *document,
                      const gchar *selector,
                      const gchar *event,
                      gpointer callback,
                      gpointer user_data)
{
	WebKitDOMNodeList *nodes = NULL;
	gulong ii, length;

	nodes = webkit_dom_document_query_selector_all (
			document, selector, NULL);

	length = webkit_dom_node_list_get_length (nodes);
	for (ii = 0; ii < length; ii++) {
		WebKitDOMNode *node;

		node = webkit_dom_node_list_item (nodes, ii);
		webkit_dom_event_target_remove_event_listener (
			WEBKIT_DOM_EVENT_TARGET (node), event,
			G_CALLBACK (callback), FALSE);

		webkit_dom_event_target_add_event_listener (
			WEBKIT_DOM_EVENT_TARGET (node), event,
			G_CALLBACK (callback), FALSE, user_data);
	}
	g_clear_object (&nodes);
}

static gboolean
force_width_is_valid_element (WebKitDOMElement *element)
{
	gboolean ret_val;
	gchar *tmp;

	tmp = webkit_dom_element_get_id (element);
	/* We can force the width on every message that was not formatted
	 * by text-highlight module. */
	ret_val = tmp && strstr (tmp, "text-highlight");
	g_free (tmp);

	if (!ret_val)
		return TRUE;

	tmp = webkit_dom_element_get_attribute (element, "src");
	/* If the message was formatted with text-highlight we can adjust the
	 * width just for the messages that were formatted as plain text. */
	ret_val = tmp && strstr (tmp, "__formatas=txt");
	g_free (tmp);

	return ret_val;
}

static void
set_iframe_and_body_width (WebKitDOMDocument *document,
                           gint64 width,
                           gint64 original_width,
                           guint level)
{
	gint ii, length;
	gint64 local_width = width;
	WebKitDOMHTMLCollection *frames = NULL;

	if (!document || !WEBKIT_DOM_IS_HTML_DOCUMENT (document))
		return;

	frames = webkit_dom_document_get_elements_by_tag_name_as_html_collection (document, "iframe");
	length = webkit_dom_html_collection_get_length (frames);

	if (level == 0) {
		local_width -= 2; /* 1 + 1 frame borders */
	} else if (!length) {
		gchar *style;

		/* Message main body */
		local_width -= 8; /* 8 + 8 margins of body without iframes */
		if (level > 1)
			local_width -= 8;

		style = g_strdup_printf ("width: %" G_GINT64_FORMAT "px;", local_width);
		e_dom_utils_add_css_rule_into_style_sheet_in_document (
			document,
			"-e-mail-formatter-style-sheet",
			"body",
			style);

		e_dom_utils_add_css_rule_into_style_sheet_in_document (
			document,
			"-e-mail-formatter-style-sheet",
			".part-container",
			style);
		g_free (style);
	} else if (level == 1) {
		gchar *style;

		local_width -= 20; /* 10 + 10 margins of body with iframes */

		style = g_strdup_printf ("width: %" G_GINT64_FORMAT "px;", local_width);
		e_dom_utils_add_css_rule_into_style_sheet_in_document (
			document,
			"-e-mail-formatter-style-sheet",
			"body",
			style);
		g_free (style);

		local_width -= 2; /* 1 + 1 frame borders */

		style = g_strdup_printf ("width: %" G_GINT64_FORMAT "px;", local_width);
		e_dom_utils_add_css_rule_into_style_sheet_in_document (
			document,
			"-e-mail-formatter-style-sheet",
			".part-container-nostyle iframe",
			style);
		g_free (style);

		/* We need to subtract another 10 pixels from the iframe width to
		 * have the iframe's borders on the correct place. We can't subtract
		 * it from local_width as we don't want to propagate this change
		 * further. */
		style = g_strdup_printf ("width: %" G_GINT64_FORMAT "px;", local_width - 10);
		e_dom_utils_add_css_rule_into_style_sheet_in_document (
			document,
			"-e-mail-formatter-style-sheet",
			".part-container iframe",
			style);
		g_free (style);
	} else {
		gchar *style;

		local_width -= 20; /* 10 + 10 margins of body with iframes */
		local_width -= 8; /* attachment margin */
		local_width -= 2; /* 1 + 1 frame borders */

		/* We need to subtract another 10 pixels from the iframe width to
		 * have the iframe's borders on the correct place. We can't subtract
		 * it from local_width as we don't want to propagate this change
		 * further. */
		style = g_strdup_printf ("width: %" G_GINT64_FORMAT "px;", local_width - 10);
		e_dom_utils_add_css_rule_into_style_sheet_in_document (
			document,
			"-e-mail-formatter-style-sheet",
			".part-container-nostyle iframe",
			style);

		e_dom_utils_add_css_rule_into_style_sheet_in_document (
			document,
			"-e-mail-formatter-style-sheet",
			"body > .part-container-nostyle iframe",
			style);
		g_free (style);
	}

	/* Add rules to every sub document */
	for (ii = 0; ii < length; ii++) {
		gint64 tmp_local_width = local_width;
		WebKitDOMDocument *iframe_document;
		WebKitDOMNode *node;

		node = webkit_dom_html_collection_item (frames, ii);
		if (!force_width_is_valid_element (WEBKIT_DOM_ELEMENT (node)))
			continue;

		iframe_document = webkit_dom_html_iframe_element_get_content_document (
			WEBKIT_DOM_HTML_IFRAME_ELEMENT (node));

		if (!iframe_document)
			continue;

		if (level == 0) {
			gchar *style = NULL;

			tmp_local_width -= 8; /* attachment's margin */

			style = g_strdup_printf ("width: %" G_GINT64_FORMAT "px;", tmp_local_width);
			e_dom_utils_add_css_rule_into_style_sheet_in_document (
				document,
				"-e-mail-formatter-style-sheet",
				".attachment-wrapper iframe:not([src*=\"__formatas=\"])",
				style);

			e_dom_utils_add_css_rule_into_style_sheet_in_document (
				document,
				"-e-mail-formatter-style-sheet",
				".attachment-wrapper iframe[src*=\"__formatas=txt\"]",
				style);
			g_free (style);

			style = g_strdup_printf ("width: %" G_GINT64_FORMAT "px;", local_width);
			e_dom_utils_add_css_rule_into_style_sheet_in_document (
				document,
				"-e-mail-formatter-style-sheet",
				"body > .part-container-nostyle iframe",
				style);
			g_free (style);
		}

		set_iframe_and_body_width (iframe_document, tmp_local_width, original_width, level + 1);
	}
	g_object_unref (frames);
}

void
e_dom_resize_document_content_to_preview_width (WebKitDOMDocument *document)
{
	gint64 width, scroll_width;
	WebKitDOMElement *document_element;

	if (!document)
		return;

	document_element = webkit_dom_document_get_document_element (document);
	width = webkit_dom_element_get_client_width (document_element);

	/* Check if we have horizontal scrollbar. */
	scroll_width = webkit_dom_element_get_scroll_width (document_element);
	if (scroll_width >= width) {
		width -= 20; /* 10 + 10 margins of body */
		set_iframe_and_body_width (document, width, width, 0);
	}
}

static void
dom_window_resize_cb (WebKitDOMDOMWindow *dom_window,
                      WebKitDOMEvent *event,
                      gpointer user_data)
{
	WebKitDOMDocument *document;

	document = webkit_dom_dom_window_get_document (dom_window);

	if (document)
		e_dom_resize_document_content_to_preview_width (document);
}

static gboolean
e_dom_text_requires_wrap (const gchar *text)
{
	gint cnt;

	if (!text || !*text)
		return FALSE;

	for (cnt = -1; *text; text++) {
		cnt++;

		if (g_ascii_isspace (*text))
			cnt = -1;
		else if (cnt > 80)
			return TRUE;
	}

	return FALSE;
}

static void
e_dom_wrap_long_anchors (WebKitDOMDocument *document)
{
	WebKitDOMHTMLCollection *frames;
	WebKitDOMHTMLCollection *anchors;
	WebKitDOMNode *node;
	gint ii, length;

	if (!document || !WEBKIT_DOM_IS_HTML_DOCUMENT (document))
		return;

	anchors = webkit_dom_document_get_elements_by_tag_name_as_html_collection (document, "a");
	length = webkit_dom_html_collection_get_length (anchors);

	for (ii = 0; ii < length; ii++) {
		node = webkit_dom_html_collection_item (anchors, ii);

		if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node)) {
			gchar *inner_text;

			inner_text = webkit_dom_html_element_get_inner_text (WEBKIT_DOM_HTML_ELEMENT (node));

			if (e_dom_text_requires_wrap (inner_text))
				element_add_class (WEBKIT_DOM_ELEMENT (node), "evo-awrap");
			else
				element_remove_class (WEBKIT_DOM_ELEMENT (node), "evo-awrap");

			g_free (inner_text);
		}
	}

	g_object_unref (anchors);

	frames = webkit_dom_document_get_elements_by_tag_name_as_html_collection (document, "iframe");
	length = webkit_dom_html_collection_get_length (frames);

	for (ii = 0; ii < length; ii++) {
		WebKitDOMDocument *iframe_document;

		node = webkit_dom_html_collection_item (frames, ii);
		iframe_document = webkit_dom_html_iframe_element_get_content_document (WEBKIT_DOM_HTML_IFRAME_ELEMENT (node));

		if (iframe_document)
			e_dom_wrap_long_anchors (iframe_document);
	}

	g_object_unref (frames);
}

void
e_dom_utils_e_mail_display_bind_dom (WebKitDOMDocument *document,
                                     GDBusConnection *connection)
{
	WebKitDOMDOMWindow *dom_window;

	e_dom_utils_bind_dom (
		document,
		"#__evo-collapse-headers-img",
		"click",
		toggle_headers_visibility,
		document);

	e_dom_utils_bind_dom (
		document,
		"*[id^=__evo-moreaddr-]",
		"click",
		toggle_address_visibility,
		NULL);

	dom_window = webkit_dom_document_get_default_view (document);

	webkit_dom_event_target_remove_event_listener (
		WEBKIT_DOM_EVENT_TARGET (dom_window), "resize",
		G_CALLBACK (dom_window_resize_cb), FALSE);

	webkit_dom_event_target_add_event_listener (
		WEBKIT_DOM_EVENT_TARGET (dom_window),
		"resize",
		G_CALLBACK (dom_window_resize_cb),
		FALSE, NULL);

	e_dom_utils_add_css_rule_into_style_sheet (
		document,
		"-e-mail-formatter-style-sheet",
		"a.evo-awrap",
		"white-space: normal; word-break: break-all;");

	e_dom_wrap_long_anchors (document);

	e_dom_resize_document_content_to_preview_width (document);
}

void
e_dom_utils_e_mail_display_unstyle_blockquotes (WebKitDOMDocument *document)
{
	WebKitDOMHTMLCollection *collection;
	gulong ii;

	g_return_if_fail (WEBKIT_DOM_IS_DOCUMENT (document));

	collection = webkit_dom_document_get_elements_by_tag_name_as_html_collection (document, "blockquote");
	for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
		WebKitDOMNode *node = webkit_dom_html_collection_item (collection, ii);
		WebKitDOMElement *elem;
		gchar *tmp;

		if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node))
			continue;

		elem = WEBKIT_DOM_ELEMENT (node);

		if (!webkit_dom_element_has_attribute (elem, "type")) {
			webkit_dom_element_set_attribute (elem, "type", "cite", NULL);
			webkit_dom_element_remove_attribute (elem, "style");
		} else {
			tmp = webkit_dom_element_get_attribute (elem, "type");
			if (g_strcmp0 (tmp, "cite") == 0)
				webkit_dom_element_remove_attribute (elem, "style");
			g_free (tmp);
		}

		tmp = webkit_dom_element_get_attribute (elem, "style");
		if (g_strcmp0 (tmp, E_EVOLUTION_BLOCKQUOTE_STYLE) == 0)
			webkit_dom_element_remove_attribute (elem, "style");
		g_free (tmp);
	}
	g_clear_object (&collection);

	collection = webkit_dom_document_get_elements_by_tag_name_as_html_collection (document, "iframe");
	for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
		WebKitDOMHTMLIFrameElement *iframe;
		WebKitDOMDocument *content_document;

		iframe = WEBKIT_DOM_HTML_IFRAME_ELEMENT (webkit_dom_html_collection_item (collection, ii));

		content_document = webkit_dom_html_iframe_element_get_content_document (iframe);
		if (!content_document)
			continue;

		e_dom_utils_e_mail_display_unstyle_blockquotes (content_document);
	}
	g_clear_object (&collection);
}

void
e_dom_utils_eab_contact_formatter_bind_dom (WebKitDOMDocument *document)
{
	e_dom_utils_bind_dom (
		document,
		"._evo_collapse_button",
		"click",
		collapse_contacts_list,
		document);
}

/* ! This function can be called only from WK2 web-extension ! */
WebKitDOMElement *
e_dom_utils_find_element_by_selector (WebKitDOMDocument *document,
                                      const gchar *selector)
{
	WebKitDOMHTMLCollection *frames = NULL;
	WebKitDOMElement *element;
	gulong ii, length;

	/* Try to look up the element in this DOM document */
	element = webkit_dom_document_query_selector (document, selector, NULL);
	if (element != NULL)
		return element;

	/* If the element is not here then recursively scan all frames */
	frames = webkit_dom_document_get_elements_by_tag_name_as_html_collection (document, "iframe");
	length = webkit_dom_html_collection_get_length (frames);
	for (ii = 0; ii < length; ii++) {
		WebKitDOMHTMLIFrameElement *iframe;
		WebKitDOMDocument *content_document;

		iframe = WEBKIT_DOM_HTML_IFRAME_ELEMENT (
			webkit_dom_html_collection_item (frames, ii));

		content_document = webkit_dom_html_iframe_element_get_content_document (iframe);
		if (!content_document)
			continue;

		element = e_dom_utils_find_element_by_id (content_document, selector);

		if (element != NULL)
			break;
	}

	g_clear_object (&frames);
	return element;
}

/* ! This function can be called only from WK2 web-extension ! */
WebKitDOMElement *
e_dom_utils_find_element_by_id (WebKitDOMDocument *document,
                                const gchar *id)
{
	WebKitDOMHTMLCollection *frames = NULL;
	WebKitDOMElement *element;
	gulong ii, length;

	/* Try to look up the element in this DOM document */
	element = webkit_dom_document_get_element_by_id (document, id);
	if (element != NULL)
		return element;

	/* If the element is not here then recursively scan all frames */
	frames = webkit_dom_document_get_elements_by_tag_name_as_html_collection (document, "iframe");
	length = webkit_dom_html_collection_get_length (frames);
	for (ii = 0; ii < length; ii++) {
		WebKitDOMHTMLIFrameElement *iframe;
		WebKitDOMDocument *content_document;

		iframe = WEBKIT_DOM_HTML_IFRAME_ELEMENT (
			webkit_dom_html_collection_item (frames, ii));

		content_document = webkit_dom_html_iframe_element_get_content_document (iframe);
		if (!content_document)
			continue;

		element = e_dom_utils_find_element_by_id (content_document, id);

		if (element != NULL)
			break;
	}

	g_clear_object (&frames);
	return element;
}

gboolean
e_dom_utils_element_exists (WebKitDOMDocument *document,
                            const gchar *element_id)
{
	WebKitDOMElement *element;

	element = e_dom_utils_find_element_by_id (document, element_id);

	return element != NULL;
}

gchar *
e_dom_utils_get_active_element_name (WebKitDOMDocument *document)
{
	WebKitDOMElement *element;

	element = webkit_dom_document_get_active_element (document);

	if (!element)
		return NULL;

	while (WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (element)) {
		WebKitDOMDocument *content_document;

		content_document =
			webkit_dom_html_iframe_element_get_content_document (
				WEBKIT_DOM_HTML_IFRAME_ELEMENT (element));

		if (!content_document)
			break;

		element = webkit_dom_document_get_active_element (content_document);
	}

	return webkit_dom_element_get_local_name (element);
}

void
e_dom_utils_e_mail_part_headers_bind_dom_element (WebKitDOMDocument *document,
                                                  const gchar *element_id)
{
	WebKitDOMDocument *element_document;
	WebKitDOMElement *element;
	WebKitDOMElement *photo;
	gchar *addr;

	element = e_dom_utils_find_element_by_id (document, element_id);
	if (!element)
		return;

	element_document = webkit_dom_node_get_owner_document (
		WEBKIT_DOM_NODE (element));
	photo = webkit_dom_document_get_element_by_id (
		element_document, "__evo-contact-photo");

	/* Contact photos disabled, the <img> tag is not there. */
	if (!photo)
		return;

	addr = webkit_dom_element_get_attribute (photo, "data-mailaddr");
	if (addr) {
		gchar *uri;

		uri = g_strdup_printf ("mail://contact-photo?mailaddr=%s", addr);

		webkit_dom_html_image_element_set_src (
			WEBKIT_DOM_HTML_IMAGE_ELEMENT (photo), uri);

		g_free (uri);
	}

	g_free (addr);
}

void
e_dom_utils_element_set_inner_html (WebKitDOMDocument *document,
                                    const gchar *element_id,
                                    const gchar *inner_html)
{
	WebKitDOMElement *element;

	element = e_dom_utils_find_element_by_id (document, element_id);

	if (!element)
		return;

	webkit_dom_element_set_inner_html (element, inner_html, NULL);
}

void
e_dom_utils_remove_element (WebKitDOMDocument *document,
                            const gchar *element_id)
{
	WebKitDOMElement *element;

	element = e_dom_utils_find_element_by_id (document, element_id);

	if (!element)
		return;

	webkit_dom_node_remove_child (
		webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
		WEBKIT_DOM_NODE (element),
		NULL);
}

void
e_dom_utils_element_remove_child_nodes (WebKitDOMDocument *document,
                                        const gchar *element_id)
{
	WebKitDOMNode *node;
	WebKitDOMElement *element;

	element = e_dom_utils_find_element_by_id (document, element_id);
	if (!element)
		return;

	node = WEBKIT_DOM_NODE (element);

	if (!node)
		return;

	while (webkit_dom_node_has_child_nodes (node)) {
		webkit_dom_node_remove_child (
			node,
			webkit_dom_node_get_last_child (node),
			NULL);
	}
}

void
e_dom_utils_hide_element (WebKitDOMDocument *document,
                          const gchar *element_id,
                          gboolean hide)
{
	WebKitDOMElement *element;

	element = e_dom_utils_find_element_by_id (document, element_id);

	if (!element)
		return;

	webkit_dom_html_element_set_hidden (
		WEBKIT_DOM_HTML_ELEMENT (element), hide);
}

gboolean
e_dom_utils_element_is_hidden (WebKitDOMDocument *document,
                               const gchar *element_id)
{
	WebKitDOMElement *element;

	element = e_dom_utils_find_element_by_id (document, element_id);

	if (!element)
		return FALSE;

	return webkit_dom_html_element_get_hidden (WEBKIT_DOM_HTML_ELEMENT (element));
}

static void
get_total_offsets (WebKitDOMElement *element,
                   glong *left,
                   glong *top)
{
	WebKitDOMElement *offset_parent;

	if (left)
		*left = 0;

	if (top)
		*top = 0;

	offset_parent = element;
	do {
		if (left) {
			*left += webkit_dom_element_get_offset_left (offset_parent);
			*left -= webkit_dom_element_get_scroll_left (offset_parent);
		}
		if (top) {
			*top += webkit_dom_element_get_offset_top (offset_parent);
			*top -= webkit_dom_element_get_scroll_top (offset_parent);
		}
		offset_parent = webkit_dom_element_get_offset_parent (offset_parent);
	} while (offset_parent);
}

static WebKitDOMElement *
find_element_from_point (WebKitDOMDocument *document,
                         gint32 x,
                         gint32 y,
                         WebKitDOMElement *element_on_point)
{
	WebKitDOMDocument *content_document;
	WebKitDOMElement *element;

	if (!element_on_point)
		element = webkit_dom_document_element_from_point (document, x, y);
	else {
		glong left, top;
		get_total_offsets (element_on_point, &left, &top);

		element = webkit_dom_document_element_from_point (
			document, x - left, y - top);
	}

	if (!element)
		return element_on_point;
	else if (!WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (element))
		element_on_point = element;

	if (element_on_point && webkit_dom_node_is_equal_node (
		WEBKIT_DOM_NODE (element),
	        WEBKIT_DOM_NODE (element_on_point))) {
		return element_on_point;
	}

	if (!WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (element))
		return element_on_point;

	content_document =
		webkit_dom_html_iframe_element_get_content_document (
			WEBKIT_DOM_HTML_IFRAME_ELEMENT (element));

	if (!content_document)
		return element_on_point;

	return find_element_from_point (content_document, x, y, element);
}

/* ! This function can be called only from WK2 web-extension ! */
WebKitDOMElement *
e_dom_utils_get_element_from_point (WebKitDOMDocument *document,
                                    gint32 x,
                                    gint32 y)
{
	return find_element_from_point (document, x, y, NULL);
}

/* ! This function can be called only from WK2 web-extension ! */
WebKitDOMDocument *
e_dom_utils_get_document_from_point (WebKitDOMDocument *document,
                                     gint32 x,
                                     gint32 y)
{
	WebKitDOMElement *element;

	if (x == 0 && y == 0)
		element = webkit_dom_document_get_active_element (document);
	else
		element = find_element_from_point (document, x, y, NULL);

	if (WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (element))
		return webkit_dom_html_iframe_element_get_content_document (
			WEBKIT_DOM_HTML_IFRAME_ELEMENT (element));
	else
		return webkit_dom_node_get_owner_document (
			WEBKIT_DOM_NODE (element));
}

/* VCard Inline Module DOM functions */

static void
display_mode_toggle_button_cb (WebKitDOMElement *button,
                               WebKitDOMEvent *event,
                               GDBusConnection *connection)
{
	GError *error = NULL;
	gchar *element_id;

	element_id = webkit_dom_element_get_id (button);

	g_dbus_connection_emit_signal (
		connection,
		NULL,
		E_WEB_EXTENSION_OBJECT_PATH,
		E_WEB_EXTENSION_INTERFACE,
		"VCardInlineDisplayModeToggled",
		g_variant_new ("(s)", element_id ? element_id : ""),
		&error);

	if (error) {
		g_warning ("Error emitting signal DisplayModeToggled: %s\n", error->message);
		g_error_free (error);
	}

	g_free (element_id);
}

static void
save_vcard_button_cb (WebKitDOMElement *button,
                      WebKitDOMEvent *event,
                      GDBusConnection *connection)
{
	GError *error = NULL;
	gchar *button_value;

	button_value = webkit_dom_html_button_element_get_value (
		WEBKIT_DOM_HTML_BUTTON_ELEMENT (button));

	g_dbus_connection_emit_signal (
		connection,
		NULL,
		E_WEB_EXTENSION_OBJECT_PATH,
		E_WEB_EXTENSION_INTERFACE,
		"VCardInlineSaveButtonPressed",
		g_variant_new ("(s)", button_value),
		&error);

	if (error) {
		g_warning ("Error emitting signal SaveVCardButtonPressed: %s\n", error->message);
		g_error_free (error);
	}

	g_free (button_value);
}

void
e_dom_utils_module_vcard_inline_bind_dom (WebKitDOMDocument *document,
                                          const gchar *element_id,
                                          GDBusConnection *connection)
{
	WebKitDOMElement *element;
	WebKitDOMDocument *element_document;
	gchar *selector;

	element = e_dom_utils_find_element_by_id (document, element_id);
	if (!element)
		return;

	element_document = webkit_dom_node_get_owner_document (
		WEBKIT_DOM_NODE (element));

	selector = g_strconcat ("button[id='", element_id, "']", NULL);
	e_dom_utils_bind_dom (
		element_document,
		selector,
		"click",
		display_mode_toggle_button_cb,
		connection);
	g_free (selector);

	selector = g_strconcat ("button[value='", element_id, "']", NULL);
	e_dom_utils_bind_dom (
		element_document,
		selector,
		"click",
		save_vcard_button_cb,
		connection);
	g_free (selector);

	e_dom_utils_eab_contact_formatter_bind_dom (element_document);
}

void
e_dom_utils_module_vcard_inline_update_button (WebKitDOMDocument *document,
                                               const gchar *button_id,
                                               const gchar *html_label,
                                               const gchar *access_key)
{
	WebKitDOMElement *element;
	gchar *selector;

	selector = g_strconcat ("button[id='", button_id, "']", NULL);
	element = e_dom_utils_find_element_by_selector (document, selector);
	g_free (selector);

	if (!element)
		return;

	webkit_dom_element_set_inner_html (element, html_label, NULL);

	if (access_key) {
		webkit_dom_html_element_set_access_key (
			WEBKIT_DOM_HTML_ELEMENT (element), access_key);
	}
}

void
e_dom_utils_module_vcard_inline_set_iframe_src (WebKitDOMDocument *document,
                                                const gchar *button_id,
                                                const gchar *src)
{
	WebKitDOMElement *element, *parent, *iframe;
	gchar *selector;

	selector = g_strconcat ("button[id='", button_id, "']", NULL);
	element = e_dom_utils_find_element_by_selector (document, selector);
	g_free (selector);

	parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (element));
	if (!parent)
		return;

	iframe = webkit_dom_element_query_selector (parent, "iframe", NULL);
	if (!iframe)
		return;

	webkit_dom_html_iframe_element_set_src (
		WEBKIT_DOM_HTML_IFRAME_ELEMENT (iframe), src);
}

/**
 * e_html_editor_dom_node_find_parent_element:
 * @node: Start node
 * @tagname: Tag name of element to search
 *
 * Recursively searches for first occurance of element with given @tagname
 * that is parent of given @node.
 *
 * Returns: A #WebKitDOMElement with @tagname representing parent of @node or
 * @NULL when @node has no parent with given @tagname. When @node matches @tagname,
 * then the @node is returned.
 */
WebKitDOMElement *
dom_node_find_parent_element (WebKitDOMNode *node,
                              const gchar *tagname)
{
	WebKitDOMNode *tmp_node = node;
	gint taglen = strlen (tagname);

	while (tmp_node) {
		if (WEBKIT_DOM_IS_ELEMENT (tmp_node)) {
			gchar *node_tagname;

			node_tagname = webkit_dom_element_get_tag_name (
				WEBKIT_DOM_ELEMENT (tmp_node));

			if (node_tagname &&
			    (strlen (node_tagname) == taglen) &&
			    (g_ascii_strncasecmp (node_tagname, tagname, taglen) == 0)) {
				g_free (node_tagname);
				return WEBKIT_DOM_ELEMENT (tmp_node);
			}

			g_free (node_tagname);
		}

		tmp_node = webkit_dom_node_get_parent_node (tmp_node);
	}

	return NULL;
}

/**
 * e_html_editor_dom_node_find_child_element:
 * @node: Start node
 * @tagname: Tag name of element to search.
 *
 * Recursively searches for first occurrence of element with given @tagname that
 * is a child of @node.
 *
 * Returns: A #WebKitDOMElement with @tagname representing a child of @node or
 * @NULL when @node has no child with given @tagname. When @node matches @tagname,
 * then the @node is returned.
 */
WebKitDOMElement *
dom_node_find_child_element (WebKitDOMNode *node,
                             const gchar *tagname)
{
	WebKitDOMNode *start_node = node;
	gint taglen = strlen (tagname);

	do {
		if (WEBKIT_DOM_IS_ELEMENT (node)) {
			gchar *node_tagname;

			node_tagname = webkit_dom_element_get_tag_name (
					WEBKIT_DOM_ELEMENT (node));

			if (node_tagname &&
			    (strlen (node_tagname) == taglen) &&
			    (g_ascii_strncasecmp (node_tagname, tagname, taglen) == 0)) {
				g_free (node_tagname);
				return WEBKIT_DOM_ELEMENT (node);
			}

			g_free (node_tagname);
		}

		if (webkit_dom_node_has_child_nodes (node)) {
			node = webkit_dom_node_get_first_child (node);
		} else if (webkit_dom_node_get_next_sibling (node)) {
			node = webkit_dom_node_get_next_sibling (node);
		} else {
			node = webkit_dom_node_get_parent_node (node);
		}
	} while (!webkit_dom_node_is_same_node (node, start_node));

	return NULL;
}

gboolean
element_has_id (WebKitDOMElement *element,
                const gchar* id)
{
	gchar *element_id;

	if (!element)
		return FALSE;

	if (!WEBKIT_DOM_IS_ELEMENT (element))
		return FALSE;

	element_id = webkit_dom_element_get_id (element);

	if (element_id && g_ascii_strcasecmp (element_id, id) == 0) {
		g_free (element_id);
		return TRUE;
	}
	g_free (element_id);

	return FALSE;
}

gboolean
element_has_tag (WebKitDOMElement *element,
                 const gchar* tag)
{
	gchar *element_tag;

	if (!WEBKIT_DOM_IS_ELEMENT (element))
		return FALSE;

	element_tag = webkit_dom_element_get_tag_name (element);

	if (g_ascii_strcasecmp (element_tag, tag) != 0) {
		g_free (element_tag);
		return FALSE;
	}
	g_free (element_tag);

	return TRUE;
}

gboolean
element_has_class (WebKitDOMElement *element,
                   const gchar* class)
{
	gchar *element_class;

	if (!element)
		return FALSE;

	if (!WEBKIT_DOM_IS_ELEMENT (element))
		return FALSE;

	element_class = webkit_dom_element_get_class_name (element);

	if (element_class && g_strstr_len (element_class, -1, class)) {
		g_free (element_class);
		return TRUE;
	}
	g_free (element_class);

	return FALSE;
}

void
element_add_class (WebKitDOMElement *element,
                   const gchar* class)
{
	gchar *element_class;
	gchar *new_class;

	if (!WEBKIT_DOM_IS_ELEMENT (element))
		return;

	if (element_has_class (element, class))
		return;

	element_class = webkit_dom_element_get_class_name (element);

	if (!element_class)
		new_class = g_strdup (class);
	else
		new_class = g_strconcat (element_class, " ", class, NULL);

	webkit_dom_element_set_class_name (element, new_class);

	g_free (element_class);
	g_free (new_class);
}

void
element_remove_class (WebKitDOMElement *element,
                      const gchar* class)
{
	gchar *element_class, *final_class;
	GRegex *regex;
	gchar *pattern = NULL;

	if (!WEBKIT_DOM_IS_ELEMENT (element))
		return;

	if (!element_has_class (element, class))
		return;

	element_class = webkit_dom_element_get_class_name (element);

	pattern = g_strconcat ("[\\s]*", class, "[\\s]*", NULL);
	regex = g_regex_new (pattern, 0, 0, NULL);
	final_class = g_regex_replace (regex, element_class, -1, 0, " ", 0, NULL);

	if (g_strcmp0 (final_class, " ") != 0)
		webkit_dom_element_set_class_name (element, final_class);
	else
		webkit_dom_element_remove_attribute (element, "class");

	g_free (element_class);
	g_free (final_class);
	g_free (pattern);
	g_regex_unref (regex);
}

void
element_rename_attribute (WebKitDOMElement *element,
                      const gchar *from,
                      const gchar *to)
{
	gchar *value;

	if (!webkit_dom_element_has_attribute (element, from))
		return;

	value = webkit_dom_element_get_attribute (element, from);
	webkit_dom_element_set_attribute (element, to, (value && *value) ? value : "", NULL);
	webkit_dom_element_remove_attribute (element, from);
	g_free (value);
}

void
remove_node (WebKitDOMNode *node)
{
	WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);

	if (parent)
		webkit_dom_node_remove_child (parent, node, NULL);
}

void
remove_node_if_empty (WebKitDOMNode *node)
{
	WebKitDOMNode *child;

	if (!WEBKIT_DOM_IS_NODE (node))
		return;

	if ((child = webkit_dom_node_get_first_child (node))) {
		WebKitDOMNode *prev_sibling, *next_sibling;

		prev_sibling = webkit_dom_node_get_previous_sibling (child);
		next_sibling = webkit_dom_node_get_next_sibling (child);
		/* Empty or BR as sibling, but no sibling after it. */
		if (!webkit_dom_node_get_first_child (child) &&
		    !WEBKIT_DOM_IS_TEXT (child) &&
		    (!prev_sibling ||
		     (WEBKIT_DOM_IS_HTML_BR_ELEMENT (prev_sibling) &&
		      !webkit_dom_node_get_previous_sibling (prev_sibling))) &&
		    (!next_sibling ||
		     (WEBKIT_DOM_IS_HTML_BR_ELEMENT (next_sibling) &&
		      !webkit_dom_node_get_next_sibling (next_sibling)))) {

			remove_node (node);
		} else {
			gchar *text_content;

			text_content = webkit_dom_node_get_text_content (node);
			if (!text_content)
				remove_node (node);

			if (text_content && !*text_content)
				remove_node (node);

			if (g_strcmp0 (text_content, UNICODE_ZERO_WIDTH_SPACE) == 0)
				remove_node (node);

			g_free (text_content);
		}
	} else
		remove_node (node);
}

WebKitDOMNode *
split_list_into_two (WebKitDOMNode *item,
		     gint level)
{
	gint current_level = 1;
	WebKitDOMDocument *document;
	WebKitDOMDocumentFragment *fragment;
	WebKitDOMNode *parent, *prev_parent = NULL, *tmp;

	document = webkit_dom_node_get_owner_document (item);
	fragment = webkit_dom_document_create_document_fragment (document);

	tmp = item;
	parent = webkit_dom_node_get_parent_node (item);
	while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
		WebKitDOMNode *clone, *first_child, *insert_before = NULL, *sibling;

		first_child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment));
		clone = webkit_dom_node_clone_node_with_error (parent, FALSE, NULL);
		webkit_dom_node_insert_before (
			WEBKIT_DOM_NODE (fragment), clone, first_child, NULL);

		if (first_child)
			insert_before = webkit_dom_node_get_first_child (first_child);

		while (first_child && (sibling = webkit_dom_node_get_next_sibling (first_child)))
			webkit_dom_node_insert_before (first_child, sibling, insert_before, NULL);

		while (tmp && (sibling = webkit_dom_node_get_next_sibling (tmp)))
			webkit_dom_node_append_child (clone, sibling, NULL);

		if (tmp)
			webkit_dom_node_insert_before (
				clone, tmp, webkit_dom_node_get_first_child (clone), NULL);

		prev_parent = parent;
		tmp = webkit_dom_node_get_next_sibling (parent);
		parent = webkit_dom_node_get_parent_node (parent);
		if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
			first_child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment));
			insert_before = webkit_dom_node_get_first_child (first_child);
			while (first_child && (sibling = webkit_dom_node_get_next_sibling (first_child))) {
				webkit_dom_node_insert_before (
					first_child, sibling, insert_before, NULL);
			}
		}

		if (current_level >= level && level >= 0)
			break;

		current_level++;
	}

	tmp = webkit_dom_node_insert_before (
		parent,
		webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment)),
		prev_parent ? webkit_dom_node_get_next_sibling (prev_parent) : NULL,
		NULL);
	remove_node_if_empty (prev_parent);

	return tmp;
}

WebKitDOMElement *
dom_create_selection_marker (WebKitDOMDocument *document,
                             gboolean selection_start_marker)
{
	WebKitDOMElement *element;

	element = webkit_dom_document_create_element (
		document, "SPAN", NULL);
	webkit_dom_element_set_id (
		element,
		selection_start_marker ?
			"-x-evo-selection-start-marker" :
			"-x-evo-selection-end-marker");

	return element;
}

void
dom_remove_selection_markers (WebKitDOMDocument *document)
{
	WebKitDOMElement *marker;

	marker = webkit_dom_document_get_element_by_id (
		document, "-x-evo-selection-start-marker");
	if (marker)
		remove_node (WEBKIT_DOM_NODE (marker));
	marker = webkit_dom_document_get_element_by_id (
		document, "-x-evo-selection-end-marker");
	if (marker)
		remove_node (WEBKIT_DOM_NODE (marker));
}

void
dom_add_selection_markers_into_element_start (WebKitDOMDocument *document,
                                              WebKitDOMElement *element,
                                              WebKitDOMElement **selection_start_marker,
                                              WebKitDOMElement **selection_end_marker)
{
	WebKitDOMElement *marker;

	dom_remove_selection_markers (document);
	marker = dom_create_selection_marker (document, FALSE);
	webkit_dom_node_insert_before (
		WEBKIT_DOM_NODE (element),
		WEBKIT_DOM_NODE (marker),
		webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)),
		NULL);
	if (selection_end_marker)
		*selection_end_marker = marker;

	marker = dom_create_selection_marker (document, TRUE);
	webkit_dom_node_insert_before (
		WEBKIT_DOM_NODE (element),
		WEBKIT_DOM_NODE (marker),
		webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)),
		NULL);
	if (selection_start_marker)
		*selection_start_marker = marker;
}

void
dom_add_selection_markers_into_element_end (WebKitDOMDocument *document,
                                            WebKitDOMElement *element,
                                            WebKitDOMElement **selection_start_marker,
                                            WebKitDOMElement **selection_end_marker)
{
	WebKitDOMElement *marker;

	dom_remove_selection_markers (document);
	marker = dom_create_selection_marker (document, TRUE);
	webkit_dom_node_append_child (
		WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (marker), NULL);
	if (selection_start_marker)
		*selection_start_marker = marker;

	marker = dom_create_selection_marker (document, FALSE);
	webkit_dom_node_append_child (
		WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (marker), NULL);
	if (selection_end_marker)
		*selection_end_marker = marker;
}

gboolean
node_is_list_or_item (WebKitDOMNode *node)
{
	return node && (
		WEBKIT_DOM_IS_HTML_O_LIST_ELEMENT (node) ||
		WEBKIT_DOM_IS_HTML_U_LIST_ELEMENT (node) ||
		WEBKIT_DOM_IS_HTML_LI_ELEMENT (node));
}

gboolean
node_is_list (WebKitDOMNode *node)
{
	return node && (
		WEBKIT_DOM_IS_HTML_O_LIST_ELEMENT (node) ||
		WEBKIT_DOM_IS_HTML_U_LIST_ELEMENT (node));
}

/**
 * e_html_editor_selection_get_list_format_from_node:
 * @node: an #WebKitDOMNode
 *
 * Returns block format of given list.
 *
 * Returns: #EContentEditorBlockFormat
 */
EContentEditorBlockFormat
dom_get_list_format_from_node (WebKitDOMNode *node)
{
	EContentEditorBlockFormat format =
		E_CONTENT_EDITOR_BLOCK_FORMAT_UNORDERED_LIST;

	if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (node))
		return E_CONTENT_EDITOR_BLOCK_FORMAT_NONE;

	if (WEBKIT_DOM_IS_HTML_U_LIST_ELEMENT (node))
		return format;

	if (WEBKIT_DOM_IS_HTML_O_LIST_ELEMENT (node)) {
		gchar *type_value = webkit_dom_element_get_attribute (
			WEBKIT_DOM_ELEMENT (node), "type");

		if (!type_value)
			return E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST;

		if (!*type_value)
			format = E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST;
		else if (g_ascii_strcasecmp (type_value, "A") == 0)
			format = E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ALPHA;
		else if (g_ascii_strcasecmp (type_value, "I") == 0)
			format = E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ROMAN;
		g_free (type_value);

		return format;
	}

	return E_CONTENT_EDITOR_BLOCK_FORMAT_NONE;
}

void
merge_list_into_list (WebKitDOMNode *from,
                      WebKitDOMNode *to,
                      gboolean insert_before)
{
	WebKitDOMNode *item, *insert_before_node;

	if (!(to && from))
		return;

	insert_before_node = webkit_dom_node_get_first_child (to);
	while ((item = webkit_dom_node_get_first_child (from)) != NULL) {
		if (insert_before)
			webkit_dom_node_insert_before (
				to, item, insert_before_node, NULL);
		else
			webkit_dom_node_append_child (to, item, NULL);
	}

	if (!webkit_dom_node_has_child_nodes (from))
		remove_node (from);

}

void
merge_lists_if_possible (WebKitDOMNode *list)
{
	EContentEditorBlockFormat format, prev, next;
	gint ii, length;
	WebKitDOMNode *prev_sibling, *next_sibling;
	WebKitDOMNodeList *lists = NULL;

	prev_sibling = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (list));
	next_sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (list));

	format = dom_get_list_format_from_node (list),
	prev = dom_get_list_format_from_node (prev_sibling);
	next = dom_get_list_format_from_node (next_sibling);

	if (format != E_CONTENT_EDITOR_BLOCK_FORMAT_NONE) {
		if (format == prev && prev != E_CONTENT_EDITOR_BLOCK_FORMAT_NONE)
			merge_list_into_list (prev_sibling, list, TRUE);

		if (format == next && next != E_CONTENT_EDITOR_BLOCK_FORMAT_NONE)
			merge_list_into_list (next_sibling, list, FALSE);
	}

	lists = webkit_dom_element_query_selector_all (
		WEBKIT_DOM_ELEMENT (list), "ol + ol, ul + ul", NULL);
	length = webkit_dom_node_list_get_length (lists);
	for (ii = 0; ii < length; ii++) {
		WebKitDOMNode *node;

		node = webkit_dom_node_list_item (lists, ii);
		merge_lists_if_possible (node);
	}
	g_clear_object (&lists);
}

WebKitDOMElement *
get_parent_block_element (WebKitDOMNode *node)
{
	WebKitDOMElement *parent = webkit_dom_node_get_parent_element (node);

	if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent))
		return WEBKIT_DOM_IS_ELEMENT (node) ? WEBKIT_DOM_ELEMENT (node) : NULL;

	while (parent &&
	       !WEBKIT_DOM_IS_HTML_PARAGRAPH_ELEMENT (parent) &&
	       !WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent) &&
	       !WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent) &&
	       !WEBKIT_DOM_IS_HTML_U_LIST_ELEMENT (parent) &&
	       !WEBKIT_DOM_IS_HTML_O_LIST_ELEMENT (parent) &&
	       !WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent) &&
	       !WEBKIT_DOM_IS_HTML_HEADING_ELEMENT (parent) &&
	       !WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (parent) &&
	       !element_has_tag (parent, "address")) {
		parent = webkit_dom_node_get_parent_element (
			WEBKIT_DOM_NODE (parent));
	}

	return parent;
}

gchar *
dom_get_node_inner_html (WebKitDOMNode *node)
{
	gchar *inner_html;
	WebKitDOMDocument *document;
	WebKitDOMElement *div;

	document = webkit_dom_node_get_owner_document (node);
	div = webkit_dom_document_create_element (document, "div", NULL);
	webkit_dom_node_append_child (
			WEBKIT_DOM_NODE (div),
			webkit_dom_node_clone_node_with_error (node, TRUE, NULL),
			NULL);

	inner_html = webkit_dom_element_get_inner_html (div);
	remove_node (WEBKIT_DOM_NODE (div));

	return inner_html;
}

WebKitDOMDocument *
e_dom_utils_find_document_with_uri (WebKitDOMDocument *root_document,
				    const gchar *find_document_uri)
{
	WebKitDOMDocument *res_document = NULL;
	GSList *todo;

	g_return_val_if_fail (WEBKIT_DOM_IS_DOCUMENT (root_document), NULL);
	g_return_val_if_fail (find_document_uri != NULL, NULL);

	todo = g_slist_append (NULL, root_document);

	while (todo) {
		WebKitDOMDocument *document;
		WebKitDOMHTMLCollection *frames = NULL;
		gchar *document_uri;
		gint ii, length;

		document = todo->data;
		todo = g_slist_remove (todo, document);

		document_uri = webkit_dom_document_get_document_uri (document);
		if (g_strcmp0 (document_uri, find_document_uri) == 0) {
			g_free (document_uri);
			res_document = document;
			break;
		}

		g_free (document_uri);

		frames = webkit_dom_document_get_elements_by_tag_name_as_html_collection (document, "iframe");
		length = webkit_dom_html_collection_get_length (frames);

		/* Add rules to every sub document */
		for (ii = 0; ii < length; ii++) {
			WebKitDOMDocument *content_document;
			WebKitDOMNode *node;

			node = webkit_dom_html_collection_item (frames, ii);
			content_document =
				webkit_dom_html_iframe_element_get_content_document (
					WEBKIT_DOM_HTML_IFRAME_ELEMENT (node));

			if (!content_document)
				continue;

			todo = g_slist_prepend (todo, content_document);
		}

		g_clear_object (&frames);
	}

	g_slist_free (todo);

	return res_document;
}

void
dom_element_swap_attributes (WebKitDOMElement *element,
                             const gchar *from,
                             const gchar *to)
{
	gchar *value_from, *value_to;

	if (!webkit_dom_element_has_attribute (element, from) ||
	    !webkit_dom_element_has_attribute (element, to))
		return;

	value_from = webkit_dom_element_get_attribute (element, from);
	value_to = webkit_dom_element_get_attribute (element, to);
	webkit_dom_element_set_attribute (element, to, (value_from && *value_from) ? value_from : "", NULL);
	webkit_dom_element_set_attribute (element, from, (value_to && *value_to) ? value_to : "", NULL);
	g_free (value_from);
	g_free (value_to);
}
