[evolution/wip/mcrha/webkit-jsc-api] Make EContentEditor::get_content() ansynchronous function (untested)



commit b790fb8e421eb9976a0a95ab8b6ebfc04a52ee9e
Author: Milan Crha <mcrha redhat com>
Date:   Fri Nov 15 12:27:31 2019 +0100

    Make EContentEditor::get_content() ansynchronous function (untested)

 data/webkit/e-editor.js                            | 142 +++++
 src/composer/e-composer-actions.c                  |  36 +-
 src/composer/e-composer-private.c                  |   2 +
 src/composer/e-composer-private.h                  |   3 +
 src/composer/e-msg-composer.c                      | 637 +++++++++++++++------
 src/composer/e-msg-composer.h                      |   2 +
 src/e-util/e-content-editor.c                      | 367 +++++++++++-
 src/e-util/e-content-editor.h                      |  84 ++-
 src/e-util/e-html-editor.c                         | 173 ++++--
 src/e-util/e-html-editor.h                         |   7 +-
 src/e-util/e-mail-signature-editor.c               |  96 ++--
 src/e-util/e-util-enums.h                          |  33 +-
 src/e-util/test-html-editor-units-utils.c          |  95 ++-
 src/e-util/test-html-editor.c                      | 140 ++++-
 src/mail/e-mail-notes.c                            | 162 ++++--
 .../composer-to-meeting/e-composer-to-meeting.c    | 120 ++--
 src/modules/webkit-editor/e-webkit-editor.c        | 290 +++++-----
 src/plugins/external-editor/external-editor.c      |  71 ++-
 18 files changed, 1832 insertions(+), 628 deletions(-)
---
diff --git a/data/webkit/e-editor.js b/data/webkit/e-editor.js
index 6e3ee6edff..8cf407a603 100644
--- a/data/webkit/e-editor.js
+++ b/data/webkit/e-editor.js
@@ -42,6 +42,14 @@ var EvoEditor = {
        E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ROMAN : 12,
        E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ALPHA : 13,
 
+       E_CONTENT_EDITOR_GET_INLINE_IMAGES : 1 << 0,
+       E_CONTENT_EDITOR_GET_RAW_BODY_HTML : 1 << 1,
+       E_CONTENT_EDITOR_GET_RAW_BODY_PLAIN : 1 << 2,
+       E_CONTENT_EDITOR_GET_RAW_BODY_STRIPPED : 1 << 3,
+       E_CONTENT_EDITOR_GET_RAW_DRAFT : 1 << 4,
+       E_CONTENT_EDITOR_GET_TO_SEND_HTML : 1 << 5,
+       E_CONTENT_EDITOR_GET_TO_SEND_PLAIN : 1 << 6,
+
        /* Flags for ClaimAffectedContent() */
        CLAIM_CONTENT_FLAG_NONE : 0,
        CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE : 1 << 0,
@@ -1233,6 +1241,140 @@ EvoEditor.SetMode = function(mode)
        }
 }
 
+EvoEditor.GetContent = function(flags, cid_uid_prefix)
+{
+       var content_data = {}, img_elems = [], elems, ii, jj;
+
+       if (!document.body)
+               return content_data;
+
+       EvoUndoRedo.Disable();
+
+       try {
+               if ((flags & EvoEditor.E_CONTENT_EDITOR_GET_RAW_BODY_STRIPPED) != 0) {
+                       var hidden_elems = [];
+
+                       try {
+                               elems = document.getElementsByClassName("-x-evo-signature-wrapper");
+                               if (elems && elems.length) {
+                                       for (ii = 0; ii < elems.length; ii++) {
+                                               var elem = elems.item(ii);
+
+                                               if (elem && !elem.hidden) {
+                                                       hidden_elems[hidden_elems.length] = elem;
+                                                       elem.hidden = true;
+                                               }
+                                       }
+                               }
+
+                               elems = document.getElementsByTagName("BLOCKQUOTE");
+                               if (elems && elems.length) {
+                                       for (ii = 0; ii < elems.length; ii++) {
+                                               var elem = elems.item(ii);
+
+                                               if (elem && !elem.hidden) {
+                                                       hidden_elems[hidden_elems.length] = elem;
+                                                       elem.hidden = true;
+                                               }
+                                       }
+                               }
+
+                               content_data["raw-body-stripped"] = document.body.innerText;
+                       } finally {
+                               for (ii = 0; ii < hidden_elems.length; ii++) {
+                                       hidden_elems[ii].hidden = false;
+                               }
+                       }
+               }
+
+               // Do these before changing image sources
+               if ((flags & EvoEditor.E_CONTENT_EDITOR_GET_RAW_BODY_HTML) != 0)
+                       content_data["raw-body-html"] = document.body.innerHTML;
+
+               if ((flags & EvoEditor.E_CONTENT_EDITOR_GET_RAW_BODY_PLAIN) != 0)
+                       content_data["raw-body-plain"] = document.body.innerText;
+
+               if (EvoEditor.mode == EvoEditor.MODE_HTML &&
+                   (flags & EvoEditor.E_CONTENT_EDITOR_GET_INLINE_IMAGES) != 0) {
+                       var images = [];
+
+                       for (ii = 0; ii < document.images.length; ii++) {
+                               var elem = document.images.item(ii);
+
+                               if (elem && elem.src && (
+                                   elem.src.toLowerCase().startsWith("data:") ||
+                                   elem.src.toLowerCase().startsWith("file://"))) {
+                                       for (jj = 0; jj < img_elems.length; jj++) {
+                                               if (elem.src == img_elems[jj].orig_src) {
+                                                       elem.subelems[elem.subelems.length] = elem;
+                                                       elem.src = img_elems[jj].cid;
+                                                       break;
+                                               }
+                                       }
+
+                                       if (jj >= img_elems.length) {
+                                               var img_obj = {
+                                                       subelems : [ elem ],
+                                                       cid : "cid:" + cid_uid_prefix + "-" + 
img_elems.length,
+                                                       orig_src : elem.src
+                                               };
+
+                                               if (elem.src.toLowerCase().startsWith("cid:"))
+                                                       img_obj.cid = elem.src;
+
+                                               img_elems[img_elems.length] = img_obj;
+                                               images[images.length] = {
+                                                       cid : img_obj.cid,
+                                                       src : elem.src
+                                               };
+                                               elem.src = img_obj.cid;
+                                       }
+                               } else if (elem && elem.src && elem.src.toLowerCase().startsWith("cid:")) {
+                                       images[images.length] = {
+                                               cid : elem.src,
+                                               src : elem.src
+                                       };
+                               }
+                       }
+
+                       if (images.length)
+                               content_data["images"] = images;
+               }
+
+               // Draft should have replaced images as well
+               if ((flags & EvoEditor.E_CONTENT_EDITOR_GET_RAW_DRAFT) != 0) {
+                       document.head.setAttribute("x-evo-selection", 
EvoSelection.ToString(EvoSelection.Store(document)));
+                       try {
+                               content_data["raw-draft"] = document.documentElement.innerHTML;
+                       } finally {
+                               document.head.removeAttribute("x-evo-selection");
+                       }
+               }
+
+               if (EvoEditor.mode == EvoEditor.MODE_HTML &&
+                   (flags & EvoEditor.E_CONTENT_EDITOR_GET_TO_SEND_HTML) != 0)
+                       content_data["to-send-html"] = EvoEditor.convertHtmlToSend();
+
+               if ((flags & EvoEditor. E_CONTENT_EDITOR_GET_TO_SEND_PLAIN) != 0) {
+                       content_data["to-send-plain"] = EvoConvertToPlainText(document.body);
+               }
+       } finally {
+               try {
+                       for (ii = 0; ii < img_elems.length; ii++) {
+                               var img_obj = img_elems[ii];
+
+                               for (jj = 0; jj < img_obj.subelems.length; jj++) {
+                                       img_obj.subelems[jj].src = img_obj.orig_src;
+                               }
+                       }
+               } finally {
+                       EvoUndoRedo.Enable();
+               }
+       }
+
+       return content_data;
+}
+
 document.onload = EvoEditor.initializeContent;
 
 document.onselectionchange = function() {
diff --git a/src/composer/e-composer-actions.c b/src/composer/e-composer-actions.c
index 6990e19023..5cb03cab94 100644
--- a/src/composer/e-composer-actions.c
+++ b/src/composer/e-composer-actions.c
@@ -162,6 +162,30 @@ action_print_preview_cb (GtkAction *action,
        e_msg_composer_print (composer, print_action);
 }
 
+static void
+action_save_ready_cb (GObject *source_object,
+                     GAsyncResult *result,
+                     gpointer user_data)
+{
+       EMsgComposer *composer = user_data;
+       GError *error = NULL;
+
+       g_return_if_fail (E_IS_MSG_COMPOSER (composer));
+       g_return_if_fail (E_IS_HTML_EDITOR (source_object));
+
+       if (!e_html_editor_save_finish (E_HTML_EDITOR (source_object), result, &error)) {
+               e_alert_submit (
+                       E_ALERT_SINK (composer),
+                       E_ALERT_NO_SAVE_FILE,
+                       e_html_editor_get_filename (E_HTML_EDITOR (source_object)), error ? error->message : 
_("Unknown error"), NULL);
+       } else {
+               composer_set_content_editor_changed (composer);
+       }
+
+       g_object_unref (composer);
+       g_clear_error (&error);
+}
+
 static void
 action_save_cb (GtkAction *action,
                 EMsgComposer *composer)
@@ -169,7 +193,6 @@ action_save_cb (GtkAction *action,
        EHTMLEditor *editor;
        const gchar *filename;
        gint fd;
-       GError *error = NULL;
 
        editor = e_msg_composer_get_editor (composer);
        filename = e_html_editor_get_filename (editor);
@@ -202,16 +225,7 @@ action_save_cb (GtkAction *action,
        } else
                close (fd);
 
-       if (!e_html_editor_save (editor, filename, TRUE, &error)) {
-               e_alert_submit (
-                       E_ALERT_SINK (composer),
-                       E_ALERT_NO_SAVE_FILE,
-                       filename, error->message, NULL);
-               g_error_free (error);
-               return;
-       }
-
-       composer_set_content_editor_changed (composer);
+       e_html_editor_save (editor, filename, TRUE, NULL, action_save_ready_cb, g_object_ref (composer));
 }
 
 static void
diff --git a/src/composer/e-composer-private.c b/src/composer/e-composer-private.c
index 8b6b8b4625..e4a1c17924 100644
--- a/src/composer/e-composer-private.c
+++ b/src/composer/e-composer-private.c
@@ -552,6 +552,8 @@ e_composer_private_finalize (EMsgComposer *composer)
        g_free (composer->priv->mime_type);
        g_free (composer->priv->mime_body);
        g_free (composer->priv->previous_identity_uid);
+
+       g_clear_pointer (&composer->priv->content_hash, e_content_editor_util_free_content_hash);
 }
 
 gchar *
diff --git a/src/composer/e-composer-private.h b/src/composer/e-composer-private.h
index 4a2e2e3e06..c415974cbc 100644
--- a/src/composer/e-composer-private.h
+++ b/src/composer/e-composer-private.h
@@ -121,6 +121,9 @@ struct _EMsgComposerPrivate {
        gulong drag_data_received_handler_id;
 
        gchar *previous_identity_uid;
+
+       guint content_hash_ref_count; /* when reaches 0, the content_hash is freed; to be able to reuse it */
+       EContentEditorContentHash *content_hash;
 };
 
 void           e_composer_private_constructed  (EMsgComposer *composer);
diff --git a/src/composer/e-msg-composer.c b/src/composer/e-msg-composer.c
index e4c9ac095e..5f20646fec 100644
--- a/src/composer/e-msg-composer.c
+++ b/src/composer/e-msg-composer.c
@@ -179,6 +179,19 @@ async_context_free (AsyncContext *context)
        g_slice_free (AsyncContext, context);
 }
 
+static void
+e_msg_composer_unref_content_hash (EMsgComposer *composer)
+{
+       g_return_if_fail (E_IS_MSG_COMPOSER (composer));
+       g_return_if_fail (composer->priv->content_hash_ref_count > 0);
+
+       composer->priv->content_hash_ref_count--;
+
+       if (!composer->priv->content_hash_ref_count) {
+               g_clear_pointer (&composer->priv->content_hash, e_content_editor_util_free_content_hash);
+       }
+}
+
 static void
 e_msg_composer_inc_soft_busy (EMsgComposer *composer)
 {
@@ -1404,7 +1417,7 @@ composer_build_message (EMsgComposer *composer,
                type = camel_content_type_decode (priv->mime_type);
 
        } else {
-               gchar *text;
+               const gchar *text;
                EHTMLEditor *editor;
                EContentEditor *cnt_editor;
 
@@ -1412,15 +1425,12 @@ composer_build_message (EMsgComposer *composer,
                cnt_editor = e_html_editor_get_content_editor (editor);
                data = g_byte_array_new ();
 
-               text = e_content_editor_get_content (
-                       cnt_editor,
-                       E_CONTENT_EDITOR_GET_TEXT_PLAIN |
-                       E_CONTENT_EDITOR_GET_PROCESSED,
-                       NULL, NULL);
+               text = e_content_editor_util_get_content_data (e_msg_composer_get_content_hash (composer),
+                       E_CONTENT_EDITOR_GET_TO_SEND_PLAIN);
 
                if (!text) {
                        g_warning ("%s: Failed to retrieve text/plain processed content", G_STRFUNC);
-                       text = g_strdup ("");
+                       text = "";
 
                        last_error = e_content_editor_dup_last_error (cnt_editor);
                }
@@ -1428,7 +1438,6 @@ composer_build_message (EMsgComposer *composer,
                g_byte_array_append (data, (guint8 *) text, strlen (text));
                if (!g_str_has_suffix (text, "\r\n"))
                        g_byte_array_append (data, (const guint8 *) "\r\n", 2);
-               g_free (text);
 
                type = camel_content_type_new ("text", "plain");
                charset = best_charset (
@@ -1488,16 +1497,11 @@ composer_build_message (EMsgComposer *composer,
 
        if ((flags & COMPOSER_FLAG_HTML_CONTENT) != 0 ||
            (flags & COMPOSER_FLAG_SAVE_DRAFT) != 0) {
-               gchar *text;
+               const gchar *text;
                gsize length;
                gboolean pre_encode;
-               EHTMLEditor *editor;
-               EContentEditor *cnt_editor;
                GSList *inline_images_parts = NULL, *link;
 
-               editor = e_msg_composer_get_editor (composer);
-               cnt_editor = e_html_editor_get_content_editor (editor);
-
                data = g_byte_array_new ();
                if ((flags & COMPOSER_FLAG_SAVE_DRAFT) != 0) {
                        /* X-Evolution-Format */
@@ -1508,39 +1512,31 @@ composer_build_message (EMsgComposer *composer,
                        composer_add_evolution_composer_mode_header (
                                CAMEL_MEDIUM (context->message), composer);
 
-                       text = e_content_editor_get_content (
-                               cnt_editor,
-                               E_CONTENT_EDITOR_GET_TEXT_HTML |
-                               E_CONTENT_EDITOR_GET_INLINE_IMAGES,
-                               from_domain, &inline_images_parts);
+                       text = e_content_editor_util_get_content_data (e_msg_composer_get_content_hash 
(composer),
+                               E_CONTENT_EDITOR_GET_RAW_DRAFT);
 
                        if (!text) {
                                g_warning ("%s: Failed to retrieve draft content", G_STRFUNC);
-                               text = g_strdup ("");
+                               text = "";
                        }
                } else {
-                       text = e_content_editor_get_content (
-                               cnt_editor,
-                               E_CONTENT_EDITOR_GET_TEXT_HTML |
-                               E_CONTENT_EDITOR_GET_PROCESSED |
-                               E_CONTENT_EDITOR_GET_INLINE_IMAGES,
-                               from_domain, &inline_images_parts);
+                       text = e_content_editor_util_get_content_data (e_msg_composer_get_content_hash 
(composer),
+                               E_CONTENT_EDITOR_GET_TO_SEND_HTML);
 
                        if (!text) {
                                g_warning ("%s: Failed to retrieve HTML processed content", G_STRFUNC);
-                               text = g_strdup ("");
+                               text = "";
                        }
                }
 
-               if (!last_error)
-                       last_error = e_content_editor_dup_last_error (cnt_editor);
+               inline_images_parts = e_content_editor_util_get_content_data (e_msg_composer_get_content_hash 
(composer),
+                       E_CONTENT_EDITOR_GET_INLINE_IMAGES);
 
                length = strlen (text);
                g_byte_array_append (data, (guint8 *) text, (guint) length);
                if (!g_str_has_suffix (text, "\r\n"))
                        g_byte_array_append (data, (const guint8 *) "\r\n", 2);
                pre_encode = text_requires_quoted_printable (text, length);
-               g_free (text);
 
                mem_stream = camel_stream_mem_new_with_byte_array (data);
                stream = camel_stream_filter_new (mem_stream);
@@ -4228,40 +4224,198 @@ e_msg_composer_get_shell (EMsgComposer *composer)
        return E_SHELL (composer->priv->shell);
 }
 
+/**
+ * e_msg_composer_get_content_hash:
+ * @composer: an #EMsgComposer
+ *
+ * Returns current #EContentEditorContentHash with content
+ * of the composer. It's valid, and available, only during
+ * operations/signals, which construct message from the @composer
+ * content. The @composer precaches the content, thus it can
+ * be accessed in a synchronous way (in constrast to EContentEditor,
+ * which allows getting the content only asynchronously).
+ * The content hash is owned by the @composer and it is freed
+ * as soon as the respective operation is finished.
+ *
+ * Returns: (transfer none) (nullable): an #EContentEditorContentHash
+ *    with current content data, or %NULL, when it is not loaded.
+ *
+ * Since: 3.36
+ **/
+EContentEditorContentHash *
+e_msg_composer_get_content_hash (EMsgComposer *composer)
+{
+       g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
+
+       /* Calling the function out of expected place should warn that something goes wrong */
+       g_warn_if_fail (composer->priv->content_hash != NULL);
+
+       return composer->priv->content_hash;
+}
+
+typedef void (* PrepareContentHashCallback) (EMsgComposer *composer,
+                                            gpointer user_data,
+                                            const GError *error);
+
+typedef struct _PrepareContentHashData {
+       EMsgComposer *composer;
+       PrepareContentHashCallback callback;
+       gpointer user_data;
+} PrepareContentHashData;
+
+static PrepareContentHashData *
+prepare_content_hash_data_new (EMsgComposer *composer,
+                              PrepareContentHashCallback callback,
+                              gpointer user_data)
+{
+       PrepareContentHashData *pchd;
+
+       pchd = g_slice_new (PrepareContentHashData);
+       pchd->composer = g_object_ref (composer);
+       pchd->callback = callback;
+       pchd->user_data = user_data;
+
+       return pchd;
+}
+
+static void
+prepare_content_hash_data_free (gpointer ptr)
+{
+       PrepareContentHashData *pchd = ptr;
+
+       if (pchd) {
+               g_clear_object (&pchd->composer);
+               g_slice_free (PrepareContentHashData, pchd);
+       }
+}
+
+static void
+e_msg_composer_prepare_content_hash_ready_cb (GObject *source_object,
+                                             GAsyncResult *result,
+                                             gpointer user_data)
+{
+       PrepareContentHashData *pchd = user_data;
+       EContentEditorContentHash *content_hash;
+       GError *error = NULL;
+
+       g_return_if_fail (pchd != NULL);
+       g_return_if_fail (E_IS_CONTENT_EDITOR (source_object));
+
+       content_hash = e_content_editor_get_content_finish (E_CONTENT_EDITOR (source_object), result, &error);
+
+       if (content_hash) {
+               g_warn_if_fail (pchd->composer->priv->content_hash == NULL);
+               g_warn_if_fail (pchd->composer->priv->content_hash_ref_count == 0);
+
+               pchd->composer->priv->content_hash = content_hash;
+               pchd->composer->priv->content_hash_ref_count = 1;
+       }
+
+       pchd->callback (pchd->composer, pchd->user_data, error);
+
+       prepare_content_hash_data_free (pchd);
+       g_clear_error (&error);
+}
+
+static void
+e_msg_composer_prepare_content_hash (EMsgComposer *composer,
+                                    GCancellable *cancellable,
+                                    EActivity *activity,
+                                    PrepareContentHashCallback callback,
+                                    gpointer user_data)
+{
+       EHTMLEditor *editor;
+       EContentEditor *cnt_editor;
+       CamelInternetAddress *from;
+       PrepareContentHashData *pchd;
+       const gchar *from_domain = NULL;
+
+       g_return_if_fail (E_IS_MSG_COMPOSER (composer));
+       g_return_if_fail (callback != NULL);
+
+       if (e_msg_composer_get_content_hash (composer)) {
+               composer->priv->content_hash_ref_count++;
+
+               callback (composer, user_data, NULL);
+               return;
+       }
+
+       if (activity)
+               e_activity_set_text (activity, _("Reading text content…"));
+
+       pchd = prepare_content_hash_data_new (composer, callback, user_data);
+       editor = e_msg_composer_get_editor (composer);
+       cnt_editor = e_html_editor_get_content_editor (editor);
+       from = e_msg_composer_get_from (composer);
+
+       if (from && camel_internet_address_get (from, 0, NULL, &from_domain)) {
+               const gchar *at = strchr (from_domain, '@');
+
+               if (at)
+                       from_domain = at + 1;
+               else
+                       from_domain = NULL;
+       }
+
+       if (!from_domain || !*from_domain)
+               from_domain = "localhost";
+
+       e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_ALL, from_domain, cancellable,
+               e_msg_composer_prepare_content_hash_ready_cb, pchd);
+
+       g_clear_object (&from);
+}
+
+static gboolean
+e_msg_composer_claim_no_build_message_error (EMsgComposer *composer,
+                                            EActivity *activity,
+                                            const GError *error,
+                                            gboolean unref_content_hash_on_error)
+{
+       g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
+
+       if (error) {
+               if (!e_activity_handle_cancellation (activity, error)) {
+                       EAlertSink *alert_sink;
+
+                       alert_sink = e_activity_get_alert_sink (activity);
+
+                       e_alert_submit (
+                               alert_sink,
+                               "mail-composer:no-build-message",
+                               error->message, NULL);
+               }
+
+               if (e_msg_composer_is_exiting (composer)) {
+                       gtk_window_present (GTK_WINDOW (composer));
+                       composer->priv->application_exiting = FALSE;
+               }
+
+               gtk_window_present (GTK_WINDOW (composer));
+
+               if (unref_content_hash_on_error)
+                       e_msg_composer_unref_content_hash (composer);
+       }
+
+       return error != NULL;
+}
+
 static void
 msg_composer_send_cb (EMsgComposer *composer,
                       GAsyncResult *result,
                       AsyncContext *context)
 {
        CamelMimeMessage *message;
-       EAlertSink *alert_sink;
        EHTMLEditor *editor;
        EContentEditor *cnt_editor;
        GError *error = NULL;
 
-       alert_sink = e_activity_get_alert_sink (context->activity);
-
        message = e_msg_composer_get_message_finish (composer, result, &error);
 
-       if (e_activity_handle_cancellation (context->activity, error)) {
-               g_warn_if_fail (message == NULL);
-               async_context_free (context);
-               g_error_free (error);
-
-               gtk_window_present (GTK_WINDOW (composer));
-               return;
-       }
-
-       if (error != NULL) {
+       if (e_msg_composer_claim_no_build_message_error (composer, context->activity, error, TRUE)) {
                g_warn_if_fail (message == NULL);
-               e_alert_submit (
-                       alert_sink,
-                       "mail-composer:no-build-message",
-                       error->message, NULL);
                async_context_free (context);
-               g_error_free (error);
-
-               gtk_window_present (GTK_WINDOW (composer));
+               g_clear_error (&error);
                return;
        }
 
@@ -4282,9 +4436,47 @@ msg_composer_send_cb (EMsgComposer *composer,
 
        g_object_unref (message);
 
+       e_msg_composer_unref_content_hash (composer);
        async_context_free (context);
 }
 
+static void
+e_msg_composer_send_content_hash_ready_cb (EMsgComposer *composer,
+                                          gpointer user_data,
+                                          const GError *error)
+{
+       AsyncContext *context = user_data;
+       gboolean proceed_with_send = TRUE;
+
+       g_return_if_fail (context != NULL);
+
+       if (e_msg_composer_claim_no_build_message_error (composer, context->activity, error, FALSE)) {
+               async_context_free (context);
+               return;
+       }
+
+       /* This gives the user a chance to abort the send. */
+       g_signal_emit (composer, signals[PRESEND], 0, &proceed_with_send);
+
+       if (!proceed_with_send) {
+               gtk_window_present (GTK_WINDOW (composer));
+               e_msg_composer_unref_content_hash (composer);
+
+               if (e_msg_composer_is_exiting (composer)) {
+                       gtk_window_present (GTK_WINDOW (composer));
+                       composer->priv->application_exiting = FALSE;
+               }
+
+               async_context_free (context);
+               return;
+       }
+
+       e_msg_composer_get_message (
+               composer, G_PRIORITY_DEFAULT, e_activity_get_cancellable (context->activity),
+               (GAsyncReadyCallback) msg_composer_send_cb,
+               context);
+}
+
 /**
  * e_msg_composer_send:
  * @composer: an #EMsgComposer
@@ -4297,18 +4489,9 @@ e_msg_composer_send (EMsgComposer *composer)
        EHTMLEditor *editor;
        AsyncContext *context;
        GCancellable *cancellable;
-       gboolean proceed_with_send = TRUE;
 
        g_return_if_fail (E_IS_MSG_COMPOSER (composer));
 
-       /* This gives the user a chance to abort the send. */
-       g_signal_emit (composer, signals[PRESEND], 0, &proceed_with_send);
-
-       if (!proceed_with_send) {
-               gtk_window_present (GTK_WINDOW (composer));
-               return;
-       }
-
        editor = e_msg_composer_get_editor (composer);
 
        context = g_slice_new0 (AsyncContext);
@@ -4316,10 +4499,8 @@ e_msg_composer_send (EMsgComposer *composer)
 
        cancellable = e_activity_get_cancellable (context->activity);
 
-       e_msg_composer_get_message (
-               composer, G_PRIORITY_DEFAULT, cancellable,
-               (GAsyncReadyCallback) msg_composer_send_cb,
-               context);
+       e_msg_composer_prepare_content_hash (composer, cancellable, context->activity,
+               e_msg_composer_send_content_hash_ready_cb, context);
 }
 
 static void
@@ -4352,43 +4533,16 @@ msg_composer_save_to_drafts_cb (EMsgComposer *composer,
                                 AsyncContext *context)
 {
        CamelMimeMessage *message;
-       EAlertSink *alert_sink;
        EHTMLEditor *editor;
        EContentEditor *cnt_editor;
        GError *error = NULL;
 
-       alert_sink = e_activity_get_alert_sink (context->activity);
-
-       message = e_msg_composer_get_message_draft_finish (
-               composer, result, &error);
-
-       if (e_activity_handle_cancellation (context->activity, error)) {
-               g_warn_if_fail (message == NULL);
-               async_context_free (context);
-               g_error_free (error);
-
-               if (e_msg_composer_is_exiting (composer)) {
-                       gtk_window_present (GTK_WINDOW (composer));
-                       composer->priv->application_exiting = FALSE;
-               }
-
-               return;
-       }
+       message = e_msg_composer_get_message_draft_finish (composer, result, &error);
 
-       if (error != NULL) {
+       if (e_msg_composer_claim_no_build_message_error (composer, context->activity, error, TRUE)) {
                g_warn_if_fail (message == NULL);
-               e_alert_submit (
-                       alert_sink,
-                       "mail-composer:no-build-message",
-                       error->message, NULL);
                async_context_free (context);
-               g_error_free (error);
-
-               if (e_msg_composer_is_exiting (composer)) {
-                       gtk_window_present (GTK_WINDOW (composer));
-                       composer->priv->application_exiting = FALSE;
-               }
-
+               g_clear_error (&error);
                return;
        }
 
@@ -4410,9 +4564,34 @@ msg_composer_save_to_drafts_cb (EMsgComposer *composer,
                        G_OBJECT (context->activity),
                        msg_composer_save_to_drafts_done_cb, composer);
 
+       e_msg_composer_unref_content_hash (composer);
        async_context_free (context);
 }
 
+static void
+e_msg_composer_save_to_drafts_content_hash_ready_cb (EMsgComposer *composer,
+                                                    gpointer user_data,
+                                                    const GError *error)
+{
+       AsyncContext *context = user_data;
+
+       g_return_if_fail (context != NULL);
+
+       if (e_msg_composer_claim_no_build_message_error (composer, context->activity, error, FALSE)) {
+               if (e_msg_composer_is_exiting (composer)) {
+                       gtk_window_present (GTK_WINDOW (composer));
+                       composer->priv->application_exiting = FALSE;
+               }
+               async_context_free (context);
+               return;
+       }
+
+       e_msg_composer_get_message_draft (
+               composer, G_PRIORITY_DEFAULT, e_activity_get_cancellable (context->activity),
+               (GAsyncReadyCallback) msg_composer_save_to_drafts_cb,
+               context);
+}
+
 /**
  * e_msg_composer_save_to_drafts:
  * @composer: an #EMsgComposer
@@ -4436,10 +4615,8 @@ e_msg_composer_save_to_drafts (EMsgComposer *composer)
 
        cancellable = e_activity_get_cancellable (context->activity);
 
-       e_msg_composer_get_message_draft (
-               composer, G_PRIORITY_DEFAULT, cancellable,
-               (GAsyncReadyCallback) msg_composer_save_to_drafts_cb,
-               context);
+       e_msg_composer_prepare_content_hash (composer, cancellable, context->activity,
+               e_msg_composer_save_to_drafts_content_hash_ready_cb, context);
 }
 
 static void
@@ -4448,30 +4625,16 @@ msg_composer_save_to_outbox_cb (EMsgComposer *composer,
                                 AsyncContext *context)
 {
        CamelMimeMessage *message;
-       EAlertSink *alert_sink;
        EHTMLEditor *editor;
        EContentEditor *cnt_editor;
        GError *error = NULL;
 
-       alert_sink = e_activity_get_alert_sink (context->activity);
-
        message = e_msg_composer_get_message_finish (composer, result, &error);
 
-       if (e_activity_handle_cancellation (context->activity, error)) {
-               g_warn_if_fail (message == NULL);
-               async_context_free (context);
-               g_error_free (error);
-               return;
-       }
-
-       if (error != NULL) {
+       if (e_msg_composer_claim_no_build_message_error (composer, context->activity, error, TRUE)) {
                g_warn_if_fail (message == NULL);
-               e_alert_submit (
-                       alert_sink,
-                       "mail-composer:no-build-message",
-                       error->message, NULL);
                async_context_free (context);
-               g_error_free (error);
+               g_clear_error (&error);
                return;
        }
 
@@ -4483,11 +4646,49 @@ msg_composer_save_to_outbox_cb (EMsgComposer *composer,
 
        g_object_unref (message);
 
-       async_context_free (context);
-
        editor = e_msg_composer_get_editor (composer);
        cnt_editor = e_html_editor_get_content_editor (editor);
        e_content_editor_set_changed (cnt_editor, TRUE);
+
+       async_context_free (context);
+}
+
+static void
+e_msg_composer_save_to_outbox_content_hash_ready_cb (EMsgComposer *composer,
+                                                    gpointer user_data,
+                                                    const GError *error)
+{
+       AsyncContext *context = user_data;
+
+       g_return_if_fail (context != NULL);
+
+       if (e_msg_composer_claim_no_build_message_error (composer, context->activity, error, FALSE)) {
+               async_context_free (context);
+               return;
+       }
+
+       if (!composer->priv->is_sending_message) {
+               gboolean proceed_with_save = TRUE;
+
+               /* This gives the user a chance to abort the save. */
+               g_signal_emit (composer, signals[PRESEND], 0, &proceed_with_save);
+
+               if (!proceed_with_save) {
+                       if (e_msg_composer_is_exiting (composer)) {
+                               gtk_window_present (GTK_WINDOW (composer));
+                               composer->priv->application_exiting = FALSE;
+                       }
+
+                       e_msg_composer_unref_content_hash (composer);
+                       async_context_free (context);
+                       return;
+               }
+       }
+
+       e_msg_composer_get_message (
+               composer, G_PRIORITY_DEFAULT, e_activity_get_cancellable (context->activity),
+               (GAsyncReadyCallback) msg_composer_save_to_outbox_cb,
+               context);
 }
 
 /**
@@ -4505,16 +4706,6 @@ e_msg_composer_save_to_outbox (EMsgComposer *composer)
 
        g_return_if_fail (E_IS_MSG_COMPOSER (composer));
 
-       if (!composer->priv->is_sending_message) {
-               gboolean proceed_with_save = TRUE;
-
-               /* This gives the user a chance to abort the save. */
-               g_signal_emit (composer, signals[PRESEND], 0, &proceed_with_save);
-
-               if (!proceed_with_save)
-                       return;
-       }
-
        editor = e_msg_composer_get_editor (composer);
 
        context = g_slice_new0 (AsyncContext);
@@ -4522,10 +4713,8 @@ e_msg_composer_save_to_outbox (EMsgComposer *composer)
 
        cancellable = e_activity_get_cancellable (context->activity);
 
-       e_msg_composer_get_message (
-               composer, G_PRIORITY_DEFAULT, cancellable,
-               (GAsyncReadyCallback) msg_composer_save_to_outbox_cb,
-               context);
+       e_msg_composer_prepare_content_hash (composer, cancellable, context->activity,
+               e_msg_composer_save_to_outbox_content_hash_ready_cb, context);
 }
 
 static void
@@ -4534,29 +4723,14 @@ msg_composer_print_cb (EMsgComposer *composer,
                        AsyncContext *context)
 {
        CamelMimeMessage *message;
-       EAlertSink *alert_sink;
        GError *error = NULL;
 
-       alert_sink = e_activity_get_alert_sink (context->activity);
-
-       message = e_msg_composer_get_message_print_finish (
-               composer, result, &error);
-
-       if (e_activity_handle_cancellation (context->activity, error)) {
-               g_warn_if_fail (message == NULL);
-               async_context_free (context);
-               g_error_free (error);
-               return;
-       }
+       message = e_msg_composer_get_message_print_finish (composer, result, &error);
 
-       if (error != NULL) {
+       if (e_msg_composer_claim_no_build_message_error (composer, context->activity, error, TRUE)) {
                g_warn_if_fail (message == NULL);
                async_context_free (context);
-               e_alert_submit (
-                       alert_sink,
-                       "mail-composer:no-build-message",
-                       error->message, NULL);
-               g_error_free (error);
+               g_clear_error (&error);
                return;
        }
 
@@ -4571,6 +4745,26 @@ msg_composer_print_cb (EMsgComposer *composer,
        async_context_free (context);
 }
 
+static void
+e_msg_composer_print_content_hash_ready_cb (EMsgComposer *composer,
+                                           gpointer user_data,
+                                           const GError *error)
+{
+       AsyncContext *context = user_data;
+
+       g_return_if_fail (context != NULL);
+
+       if (e_msg_composer_claim_no_build_message_error (composer, context->activity, error, FALSE)) {
+               async_context_free (context);
+               return;
+       }
+
+       e_msg_composer_get_message_print (
+               composer, G_PRIORITY_DEFAULT, e_activity_get_cancellable (context->activity),
+               (GAsyncReadyCallback) msg_composer_print_cb,
+               context);
+}
+
 /**
  * e_msg_composer_print:
  * @composer: an #EMsgComposer
@@ -4596,10 +4790,8 @@ e_msg_composer_print (EMsgComposer *composer,
 
        cancellable = e_activity_get_cancellable (context->activity);
 
-       e_msg_composer_get_message_print (
-               composer, G_PRIORITY_DEFAULT, cancellable,
-               (GAsyncReadyCallback) msg_composer_print_cb,
-               context);
+       e_msg_composer_prepare_content_hash (composer, cancellable, context->activity,
+               e_msg_composer_print_content_hash_ready_cb, context);
 }
 
 static GList *
@@ -5286,9 +5478,86 @@ composer_get_message_ready (EMsgComposer *composer,
 
        g_simple_async_result_complete (simple);
 
+       e_msg_composer_unref_content_hash (composer);
+
        g_object_unref (simple);
 }
 
+typedef struct _BuildMessageWrapperData {
+       EMsgComposer *composer;
+       ComposerFlags flags;
+       gint io_priority;
+       GCancellable *cancellable;
+       GSimpleAsyncResult *simple;
+} BuildMessageWrapperData;
+
+static BuildMessageWrapperData *
+build_message_wrapper_data_new (EMsgComposer *composer,
+                               ComposerFlags flags,
+                               gint io_priority,
+                               GCancellable *cancellable,
+                               GSimpleAsyncResult *simple)
+{
+       BuildMessageWrapperData *bmwd;
+
+       bmwd = g_slice_new (BuildMessageWrapperData);
+       bmwd->composer = g_object_ref (composer);
+       bmwd->flags = flags;
+       bmwd->io_priority = io_priority;
+       bmwd->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+       bmwd->simple = g_object_ref (simple);
+
+       return bmwd;
+}
+
+static void
+build_message_wrapper_data_free (gpointer ptr)
+{
+       BuildMessageWrapperData *bmwd = ptr;
+
+       if (bmwd) {
+               g_clear_object (&bmwd->composer);
+               g_clear_object (&bmwd->cancellable);
+               g_clear_object (&bmwd->simple);
+               g_slice_free (BuildMessageWrapperData, bmwd);
+       }
+}
+
+static void
+composer_build_message_wrapper_content_hash_ready_cb (EMsgComposer *composer,
+                                                     gpointer user_data,
+                                                     const GError *error)
+{
+       BuildMessageWrapperData *bmwd = user_data;
+
+       g_return_if_fail (bmwd != NULL);
+
+       if (error) {
+               g_simple_async_result_set_from_error (bmwd->simple, error);
+               g_simple_async_result_complete (bmwd->simple);
+       } else {
+               composer_build_message (composer, bmwd->flags, bmwd->io_priority,
+                       bmwd->cancellable, (GAsyncReadyCallback)
+                       composer_get_message_ready, bmwd->simple);
+       }
+
+       build_message_wrapper_data_free (bmwd);
+}
+
+static void
+composer_build_message_wrapper (EMsgComposer *composer,
+                               ComposerFlags flags,
+                               gint io_priority,
+                               GCancellable *cancellable,
+                               GSimpleAsyncResult *simple)
+{
+       BuildMessageWrapperData *bmwd;
+
+       bmwd = build_message_wrapper_data_new (composer, flags, io_priority, cancellable, simple);
+
+       e_msg_composer_prepare_content_hash (composer, cancellable, NULL, 
composer_build_message_wrapper_content_hash_ready_cb, bmwd);
+}
+
 /**
  * e_msg_composer_get_message:
  * @composer: an #EMsgComposer
@@ -5350,10 +5619,7 @@ e_msg_composer_get_message (EMsgComposer *composer,
                flags |= COMPOSER_FLAG_SMIME_ENCRYPT;
 #endif
 
-       composer_build_message (
-               composer, flags, io_priority,
-               cancellable, (GAsyncReadyCallback)
-               composer_get_message_ready, simple);
+       composer_build_message_wrapper (composer, flags, io_priority, cancellable, simple);
 }
 
 CamelMimeMessage *
@@ -5401,10 +5667,7 @@ e_msg_composer_get_message_print (EMsgComposer *composer,
        flags |= COMPOSER_FLAG_HTML_CONTENT;
        flags |= COMPOSER_FLAG_SAVE_OBJECT_DATA;
 
-       composer_build_message (
-               composer, flags, io_priority,
-               cancellable, (GAsyncReadyCallback)
-               composer_get_message_ready, simple);
+       composer_build_message_wrapper (composer, flags, io_priority, cancellable, simple);
 }
 
 CamelMimeMessage *
@@ -5486,10 +5749,7 @@ e_msg_composer_get_message_draft (EMsgComposer *composer,
                flags |= COMPOSER_FLAG_SMIME_ENCRYPT;
 #endif
 
-       composer_build_message (
-               composer, flags, io_priority,
-               cancellable, (GAsyncReadyCallback)
-               composer_get_message_ready, simple);
+       composer_build_message_wrapper (composer, flags, io_priority, cancellable, simple);
 }
 
 CamelMimeMessage *
@@ -5607,32 +5867,32 @@ e_msg_composer_get_reply_to (EMsgComposer *composer)
 GByteArray *
 e_msg_composer_get_raw_message_text_without_signature (EMsgComposer *composer)
 {
-       EHTMLEditor *editor;
-       EContentEditor *cnt_editor;
-       gchar *content;
+       EContentEditorContentHash *content_hash;
+       const gchar *content;
+       gsize content_length;
        GByteArray *bytes;
        gboolean needs_crlf;
 
        g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
 
-       editor = e_msg_composer_get_editor (composer);
-       cnt_editor = e_html_editor_get_content_editor (editor);
+       content_hash = e_msg_composer_get_content_hash (composer);
+       g_return_val_if_fail (content_hash != NULL, NULL);
 
-       content = e_content_editor_get_content (
-               cnt_editor,
-               E_CONTENT_EDITOR_GET_BODY |
-               E_CONTENT_EDITOR_GET_TEXT_PLAIN |
-               E_CONTENT_EDITOR_GET_EXCLUDE_SIGNATURE,
-               NULL, NULL);
+       content = e_content_editor_util_get_content_data (content_hash, 
E_CONTENT_EDITOR_GET_RAW_BODY_STRIPPED);
 
        if (!content) {
                g_warning ("%s: Failed to retrieve content", G_STRFUNC);
-               content = g_strdup ("");
+
+               content = "";
        }
 
        needs_crlf = !g_str_has_suffix (content, "\r\n");
 
-       bytes = g_byte_array_new_take ((guint8 *) content, strlen (content));
+       content_length = strlen (content);
+
+       bytes = g_byte_array_sized_new (content_length + 3);
+
+       g_byte_array_append (bytes, (const guint8 *) content, content_length);
 
        if (needs_crlf)
                g_byte_array_append (bytes, (const guint8 *) "\r\n", 2);
@@ -5648,31 +5908,32 @@ e_msg_composer_get_raw_message_text_without_signature (EMsgComposer *composer)
 GByteArray *
 e_msg_composer_get_raw_message_text (EMsgComposer *composer)
 {
-       EHTMLEditor *editor;
-       EContentEditor *cnt_editor;
-       gchar *content;
+       EContentEditorContentHash *content_hash;
+       const gchar *content;
+       gsize content_length;
        GByteArray *bytes;
        gboolean needs_crlf;
 
        g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
 
-       editor = e_msg_composer_get_editor (composer);
-       cnt_editor = e_html_editor_get_content_editor (editor);
+       content_hash = e_msg_composer_get_content_hash (composer);
+       g_return_val_if_fail (content_hash != NULL, NULL);
 
-       content = e_content_editor_get_content (
-               cnt_editor,
-               E_CONTENT_EDITOR_GET_BODY |
-               E_CONTENT_EDITOR_GET_TEXT_PLAIN,
-               NULL, NULL);
+       content = e_content_editor_util_get_content_data (content_hash, E_CONTENT_EDITOR_GET_RAW_BODY_PLAIN);
 
        if (!content) {
                g_warning ("%s: Failed to retrieve content", G_STRFUNC);
-               content = g_strdup ("");
+
+               content = "";
        }
 
        needs_crlf = !g_str_has_suffix (content, "\r\n");
 
-       bytes = g_byte_array_new_take ((guint8 *) content, strlen (content));
+       content_length = strlen (content);
+
+       bytes = g_byte_array_sized_new (content_length + 3);
+
+       g_byte_array_append (bytes, (const guint8 *) content, content_length);
 
        if (needs_crlf)
                g_byte_array_append (bytes, (const guint8 *) "\r\n", 2);
diff --git a/src/composer/e-msg-composer.h b/src/composer/e-msg-composer.h
index e62ca84297..f385889ba4 100644
--- a/src/composer/e-msg-composer.h
+++ b/src/composer/e-msg-composer.h
@@ -108,6 +108,8 @@ EFocusTracker *     e_msg_composer_get_focus_tracker
 CamelSession * e_msg_composer_ref_session      (EMsgComposer *composer);
 EShell *       e_msg_composer_get_shell        (EMsgComposer *composer);
 
+EContentEditorContentHash *
+               e_msg_composer_get_content_hash (EMsgComposer *composer);
 void           e_msg_composer_send             (EMsgComposer *composer);
 void           e_msg_composer_save_to_drafts   (EMsgComposer *composer);
 void           e_msg_composer_save_to_outbox   (EMsgComposer *composer);
diff --git a/src/e-util/e-content-editor.c b/src/e-util/e-content-editor.c
index 64a066cb6d..cb01a9aa9b 100644
--- a/src/e-util/e-content-editor.c
+++ b/src/e-util/e-content-editor.c
@@ -23,6 +23,7 @@
 
 #include "e-html-editor.h"
 #include "e-util-enumtypes.h"
+#include "e-misc-utils.h"
 #include "e-content-editor.h"
 
 G_DEFINE_INTERFACE (EContentEditor, e_content_editor, GTK_TYPE_WIDGET);
@@ -1574,25 +1575,344 @@ e_content_editor_insert_content (EContentEditor *editor,
        iface->insert_content (editor, content, flags);
 }
 
-gchar *
+/*
+ Finish the operation with e_content_editor_get_content_finish().
+ */
+void
 e_content_editor_get_content (EContentEditor *editor,
-                              EContentEditorGetContentFlags flags,
+                             guint32 flags,
                              const gchar *inline_images_from_domain,
-                             GSList **inline_images_parts /* newly created CamelMimePart * */)
+                             GCancellable *cancellable,
+                             GAsyncReadyCallback callback,
+                             gpointer user_data)
+{
+       EContentEditorInterface *iface;
+
+       g_return_if_fail (E_IS_CONTENT_EDITOR (editor));
+
+       if ((flags & E_CONTENT_EDITOR_GET_INLINE_IMAGES))
+               g_return_if_fail (inline_images_from_domain != NULL);
+
+       iface = E_CONTENT_EDITOR_GET_IFACE (editor);
+       g_return_if_fail (iface != NULL);
+       g_return_if_fail (iface->get_content != NULL);
+
+       iface->get_content (editor, flags, inline_images_from_domain, cancellable, callback, user_data);
+}
+
+/*
+ Finishes previous call of e_content_editor_get_content(). The implementation
+ creates the GHashTable with e_content_editor_util_new_content_hash() and fills
+ it with e_content_editor_util_put_content_data(), e_content_editor_util_take_content_data()
+ or e_content_editor_util_take_content_data_images(). The caller can access
+ the members with e_content_editor_util_get_content_data().
+
+ The returned pointer should be freed with e_content_editor_util_free_content_hash(),
+ when done with it.
+ */
+EContentEditorContentHash *
+e_content_editor_get_content_finish (EContentEditor *editor,
+                                    GAsyncResult *result,
+                                    GError **error)
 {
        EContentEditorInterface *iface;
 
        g_return_val_if_fail (E_IS_CONTENT_EDITOR (editor), NULL);
-       if ((flags & E_CONTENT_EDITOR_GET_INLINE_IMAGES)) {
-               g_return_val_if_fail (inline_images_from_domain != NULL, NULL);
-               g_return_val_if_fail (inline_images_parts != NULL, NULL);
-       }
 
        iface = E_CONTENT_EDITOR_GET_IFACE (editor);
        g_return_val_if_fail (iface != NULL, NULL);
        g_return_val_if_fail (iface->get_content != NULL, NULL);
 
-       return iface->get_content (editor, flags, inline_images_from_domain, inline_images_parts);
+       return iface->get_content_finish (editor, result, error);
+}
+
+typedef struct _ContentHashData {
+       gpointer data;
+       GDestroyNotify destroy_data;
+} ContentHashData;
+
+static ContentHashData *
+content_hash_data_new (gpointer data,
+                      GDestroyNotify destroy_data)
+{
+       ContentHashData *chd;
+
+       chd = g_slice_new (ContentHashData);
+       chd->data = data;
+       chd->destroy_data = destroy_data;
+
+       return chd;
+}
+
+static void
+content_hash_data_free (gpointer ptr)
+{
+       ContentHashData *chd = ptr;
+
+       if (ptr) {
+               if (chd->destroy_data && chd->data)
+                       chd->destroy_data (chd->data);
+
+               g_slice_free (ContentHashData, chd);
+       }
+}
+
+static void
+content_data_free_obj_slist (gpointer ptr)
+{
+       GSList *lst = ptr;
+
+       g_slist_free_full (lst, g_object_unref);
+}
+
+EContentEditorContentHash *
+e_content_editor_util_new_content_hash (void)
+{
+       return (EContentEditorContentHash *) g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, 
content_hash_data_free);
+}
+
+void
+e_content_editor_util_free_content_hash (EContentEditorContentHash *content_hash)
+{
+       if (content_hash)
+               g_hash_table_unref ((GHashTable *) content_hash);
+}
+
+void
+e_content_editor_util_put_content_data (EContentEditorContentHash *content_hash,
+                                       EContentEditorGetContentFlags flag,
+                                       const gchar *data)
+{
+       g_return_if_fail (content_hash != NULL);
+       g_return_if_fail (flag != E_CONTENT_EDITOR_GET_ALL);
+       g_return_if_fail (data != NULL);
+
+       e_content_editor_util_take_content_data (content_hash, flag, g_strdup (data), g_free);
+}
+
+void
+e_content_editor_util_take_content_data (EContentEditorContentHash *content_hash,
+                                        EContentEditorGetContentFlags flag,
+                                        gpointer data,
+                                        GDestroyNotify destroy_data)
+{
+       g_return_if_fail (content_hash != NULL);
+       g_return_if_fail (flag != E_CONTENT_EDITOR_GET_ALL);
+       g_return_if_fail (data != NULL);
+
+       g_hash_table_insert ((GHashTable *) content_hash, GUINT_TO_POINTER (flag), content_hash_data_new 
(data, destroy_data));
+}
+
+void
+e_content_editor_util_take_content_data_images (EContentEditorContentHash *content_hash,
+                                               GSList *image_parts) /* CamelMimePart * */
+{
+       g_return_if_fail (content_hash != NULL);
+       g_return_if_fail (image_parts != NULL);
+
+       g_hash_table_insert ((GHashTable *) content_hash, GUINT_TO_POINTER 
(E_CONTENT_EDITOR_GET_INLINE_IMAGES),
+               content_hash_data_new (image_parts, content_data_free_obj_slist));
+}
+
+/* The actual data type depends on the @flag. The E_CONTENT_EDITOR_GET_INLINE_IMAGES returns
+   a GSList of CamelMimePart-s of inline images. All the other flags return plain strings.
+
+   The returned pointer is owned by content_hash and cannot be freed
+   neither modified. It's freed together with the content_hash, or
+   when its key is overwritten.
+ */
+gpointer
+e_content_editor_util_get_content_data (EContentEditorContentHash *content_hash,
+                                       EContentEditorGetContentFlags flag)
+{
+       ContentHashData *chd;
+
+       g_return_val_if_fail (content_hash != NULL, NULL);
+       g_return_val_if_fail (flag != E_CONTENT_EDITOR_GET_ALL, NULL);
+
+       chd = g_hash_table_lookup ((GHashTable *) content_hash, GUINT_TO_POINTER (flag));
+
+       return chd ? chd->data : NULL;
+}
+
+/* The same rules apply as with e_content_editor_util_get_content_data(). The difference is
+   that after calling this function the data is stoled from the content_hash and the caller
+   is responsible to free it. Any following calls with the same flag will return %NULL.
+ */
+gpointer
+e_content_editor_util_steal_content_data (EContentEditorContentHash *content_hash,
+                                         EContentEditorGetContentFlags flag,
+                                         GDestroyNotify *out_destroy_data)
+{
+       ContentHashData *chd;
+       gpointer data;
+
+       if (out_destroy_data)
+               *out_destroy_data = NULL;
+
+       g_return_val_if_fail (content_hash != NULL, NULL);
+       g_return_val_if_fail (flag != E_CONTENT_EDITOR_GET_ALL, NULL);
+
+       chd = g_hash_table_lookup ((GHashTable *) content_hash, GUINT_TO_POINTER (flag));
+
+       if (!chd)
+               return NULL;
+
+       data = chd->data;
+
+       if (out_destroy_data)
+               *out_destroy_data = chd->destroy_data;
+
+       chd->data = NULL;
+       chd->destroy_data = NULL;
+
+       return data;
+}
+
+/**
+ * e_content_editor_util_create_data_mimepart:
+ * @uri: a file:// or data: URI of the data to convert to MIME part
+ * @cid: content ID to use for the MIME part, should start with "cid:"; can be %NULL
+ * @as_inline: whether to use "inline" content disposition; will use "attachment", if set to %FALSE
+ * @prefer_filename: preferred file name to use, can be %NULL
+ * @prefer_mime_type: preferred MIME type for the part, can be %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ *
+ * Converts URI into a #CamelMimePart. Supports file:// and data: URIs.
+ * The @prefer_filename can override the file name from the @uri.
+ *
+ * Free the returned pointer, if not %NULL, with g_object_unref(), when
+ * no longer needed.
+ *
+ * Returns: (transfer full) (nullable): a new #CamelMimePart containing
+ *    the referenced data, or %NULL, when cannot be converted (due to
+ *    unsupported URI, file not found or such).
+ *
+ * Since: 3.36
+ **/
+CamelMimePart *
+e_content_editor_util_create_data_mimepart (const gchar *uri,
+                                           const gchar *cid,
+                                           gboolean as_inline,
+                                           const gchar *prefer_filename,
+                                           const gchar *prefer_mime_type,
+                                           GCancellable *cancellable)
+{
+       CamelMimePart *mime_part = NULL;
+       GInputStream *input_stream = NULL;
+       GFileInfo *file_info = NULL;
+       gchar *mime_type = NULL;
+       guchar *data = NULL;
+       gsize data_length = 0;
+
+       g_return_val_if_fail (uri != NULL, NULL);
+
+       /* base64-encoded "data:" URIs */
+       if (g_ascii_strncasecmp (uri, "data:", 5) == 0) {
+               /* data:[<mime type>][;charset=<charset>][;base64],<encoded data> */
+               const gchar *ptr, *from;
+               gboolean is_base64 = FALSE;
+
+               ptr = uri + 5;
+               from = ptr;
+               while (*ptr && *ptr != ',') {
+                       ptr++;
+
+                       if (*ptr == ',' || *ptr == ';') {
+                               if (g_ascii_strncasecmp (from, "base64", ptr - from) == 0)
+                                       is_base64 = TRUE;
+
+                               if (from == uri + 5 && *ptr == ';' && !prefer_mime_type)
+                                       mime_type = g_strndup (from, ptr - from);
+
+                               from = ptr + 1;
+                       }
+               }
+
+               if (is_base64 && *ptr == ',') {
+                       data = g_base64_decode (ptr + 1, &data_length);
+
+                       if (data && data_length && !mime_type && !prefer_mime_type) {
+                               gchar *content_type;
+
+                               content_type = g_content_type_guess (NULL, data, data_length, NULL);
+
+                               if (content_type) {
+                                       mime_type = g_content_type_get_mime_type (content_type);
+                                       g_free (content_type);
+                               }
+                       }
+               }
+
+       /* files on the disk */
+       } else if (g_ascii_strncasecmp (uri, "file://", 7) == 0) {
+               GFileInputStream *file_stream;
+               GFile *file;
+
+               file = g_file_new_for_uri (uri);
+               file_stream = g_file_read (file, NULL, NULL);
+               g_clear_object (&file);
+
+               if (file_stream) {
+                       if (!prefer_filename) {
+                               file_info = g_file_input_stream_query_info (file_stream, 
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, cancellable, NULL);
+
+                               if (file_info) {
+                                       prefer_filename = g_file_info_get_display_name (file_info);
+                               }
+                       }
+
+                       if (!prefer_mime_type)
+                               mime_type = e_util_guess_mime_type (uri, TRUE);
+
+                       input_stream = (GInputStream *) file_stream;
+               }
+       }
+
+       if (data || input_stream) {
+               if (!prefer_mime_type)
+                       prefer_mime_type = mime_type;
+
+               if (!prefer_mime_type)
+                       prefer_mime_type = "application/octet-stream";
+
+               if (input_stream) {
+                       CamelDataWrapper *wrapper;
+
+                       wrapper = camel_data_wrapper_new ();
+
+                       if (camel_data_wrapper_construct_from_input_stream_sync (wrapper, input_stream, 
cancellable, NULL)) {
+                               camel_data_wrapper_set_mime_type (wrapper, prefer_mime_type);
+
+                               mime_part = camel_mime_part_new ();
+                               camel_medium_set_content (CAMEL_MEDIUM (mime_part), wrapper);
+                       }
+
+                       g_object_unref (wrapper);
+               } else {
+                       mime_part = camel_mime_part_new ();
+                       camel_mime_part_set_content (mime_part, (const gchar *) data, data_length, 
prefer_mime_type);
+               }
+
+               if (mime_part) {
+                       camel_mime_part_set_disposition (mime_part, as_inline ? "inline" : "attachment");
+
+                       if (cid)
+                               camel_mime_part_set_content_id (mime_part, cid);
+
+                       if (prefer_filename && *prefer_filename)
+                               camel_mime_part_set_filename (mime_part, prefer_filename);
+
+                       camel_mime_part_set_encoding (mime_part, CAMEL_TRANSFER_ENCODING_BASE64);
+               }
+       }
+
+       g_clear_object (&input_stream);
+       g_clear_object (&file_info);
+       g_free (mime_type);
+       g_free (data);
+
+       return mime_part;
 }
 
 void
@@ -2119,32 +2439,39 @@ e_content_editor_selection_wrap (EContentEditor *editor)
        iface->selection_wrap (editor);
 }
 
-guint
-e_content_editor_get_caret_position (EContentEditor *editor)
+void
+e_content_editor_get_caret_position (EContentEditor *editor,
+                                    GCancellable *cancellable,
+                                    GAsyncReadyCallback callback,
+                                    gpointer user_data)
 {
        EContentEditorInterface *iface;
 
-       g_return_val_if_fail (E_IS_CONTENT_EDITOR (editor), 0);
+       g_return_if_fail (E_IS_CONTENT_EDITOR (editor));
 
        iface = E_CONTENT_EDITOR_GET_IFACE (editor);
-       g_return_val_if_fail (iface != NULL, 0);
-       g_return_val_if_fail (iface->get_caret_position != NULL, 0);
+       g_return_if_fail (iface != NULL);
+       g_return_if_fail (iface->get_caret_position != NULL);
 
-       return iface->get_caret_position (editor);
+       iface->get_caret_position (editor, cancellable, callback, user_data);
 }
 
-guint
-e_content_editor_get_caret_offset (EContentEditor *editor)
+gboolean
+e_content_editor_get_caret_position_finish (EContentEditor *editor,
+                                           GAsyncResult *result,
+                                           guint *out_position,
+                                           guint *out_offset,
+                                           GError **error)
 {
        EContentEditorInterface *iface;
 
-       g_return_val_if_fail (E_IS_CONTENT_EDITOR (editor), 0);
+       g_return_val_if_fail (E_IS_CONTENT_EDITOR (editor), FALSE);
 
        iface = E_CONTENT_EDITOR_GET_IFACE (editor);
-       g_return_val_if_fail (iface != NULL, 0);
-       g_return_val_if_fail (iface->get_caret_offset != NULL, 0);
+       g_return_val_if_fail (iface != NULL, FALSE);
+       g_return_val_if_fail (iface->get_caret_position_finish != NULL, FALSE);
 
-       return iface->get_caret_offset (editor);
+       return iface->get_caret_position_finish (editor, result, out_position, out_offset, error);
 }
 
 gchar *
diff --git a/src/e-util/e-content-editor.h b/src/e-util/e-content-editor.h
index f1630522a2..9b2c971a9a 100644
--- a/src/e-util/e-content-editor.h
+++ b/src/e-util/e-content-editor.h
@@ -40,6 +40,8 @@ struct _EHTMLEditor;
 #define E_TYPE_CONTENT_EDITOR e_content_editor_get_type ()
 G_DECLARE_INTERFACE (EContentEditor, e_content_editor, E, CONTENT_EDITOR, GtkWidget)
 
+typedef GHashTable EContentEditorContentHash;
+
 typedef void (*EContentEditorInitializedCallback)      (EContentEditor *content_editor,
                                                         gpointer user_data);
 
@@ -56,10 +58,16 @@ struct _EContentEditorInterface {
                                                         const gchar *content,
                                                         EContentEditorInsertContentFlags flags);
 
-       gchar *         (*get_content)                  (EContentEditor *editor,
-                                                        EContentEditorGetContentFlags flags,
+       void            (*get_content)                  (EContentEditor *editor,
+                                                        guint32 flags, /* bit-or of 
EContentEditorGetContentFlags */
                                                         const gchar *inline_images_from_domain,
-                                                        GSList **inline_images_parts /* newly created 
CamelMimePart * */);
+                                                        GCancellable *cancellable,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer user_data);
+       EContentEditorContentHash *
+                       (*get_content_finish)           (EContentEditor *editor,
+                                                        GAsyncResult *result,
+                                                        GError **error);
 
        void            (*insert_image)                 (EContentEditor *editor,
                                                         const gchar *uri);
@@ -129,9 +137,16 @@ struct _EContentEditorInterface {
 
        void            (*selection_wrap)               (EContentEditor *editor);
 
-       guint           (*get_caret_position)           (EContentEditor *editor);
+       void            (*get_caret_position)           (EContentEditor *editor,
+                                                        GCancellable *cancellable,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer user_data);
 
-       guint           (*get_caret_offset)             (EContentEditor *editor);
+       gboolean        (*get_caret_position_finish)    (EContentEditor *editor,
+                                                        GAsyncResult *result,
+                                                        guint *out_position,
+                                                        guint *out_offset,
+                                                        GError **error);
 
        gchar *         (*get_current_signature_uid)    (EContentEditor *editor);
 
@@ -543,10 +558,48 @@ void              e_content_editor_insert_content (EContentEditor *editor,
                                                 const gchar *content,
                                                 EContentEditorInsertContentFlags flags);
 
-gchar *                e_content_editor_get_content    (EContentEditor *editor,
-                                                EContentEditorGetContentFlags flags,
+void           e_content_editor_get_content    (EContentEditor *editor,
+                                                guint32 flags, /* bit-or of EContentEditorGetContentFlags */
                                                 const gchar *inline_images_from_domain,
-                                                GSList **inline_images_parts /* newly created CamelMimePart 
* */);
+                                                GCancellable *cancellable,
+                                                GAsyncReadyCallback callback,
+                                                gpointer user_data);
+EContentEditorContentHash *
+               e_content_editor_get_content_finish
+                                               (EContentEditor *editor,
+                                                GAsyncResult *result,
+                                                GError **error);
+EContentEditorContentHash *
+               e_content_editor_util_new_content_hash
+                                               (void);
+void           e_content_editor_util_free_content_hash
+                                               (EContentEditorContentHash *content_hash);
+void           e_content_editor_util_put_content_data
+                                               (EContentEditorContentHash *content_hash,
+                                                EContentEditorGetContentFlags flag,
+                                                const gchar *data);
+void           e_content_editor_util_take_content_data
+                                               (EContentEditorContentHash *content_hash,
+                                                EContentEditorGetContentFlags flag,
+                                                gpointer data,
+                                                GDestroyNotify destroy_data);
+void           e_content_editor_util_take_content_data_images
+                                               (EContentEditorContentHash *content_hash,
+                                                GSList *image_parts); /* CamelMimePart * */
+gpointer       e_content_editor_util_get_content_data
+                                               (EContentEditorContentHash *content_hash,
+                                                EContentEditorGetContentFlags flag);
+gpointer       e_content_editor_util_steal_content_data
+                                               (EContentEditorContentHash *content_hash,
+                                                EContentEditorGetContentFlags flag,
+                                                GDestroyNotify *out_destroy_data);
+CamelMimePart *        e_content_editor_util_create_data_mimepart
+                                               (const gchar *uri,
+                                                const gchar *cid,
+                                                gboolean as_inline,
+                                                const gchar *prefer_filename,
+                                                const gchar *prefer_mime_type,
+                                                GCancellable *cancellable);
 
 void            e_content_editor_insert_image_from_mime_part
                                                (EContentEditor *editor,
@@ -627,11 +680,18 @@ void              e_content_editor_selection_restore
 
 void           e_content_editor_selection_wrap (EContentEditor *editor);
 
-guint          e_content_editor_get_caret_position
-                                               (EContentEditor *editor);
+void           e_content_editor_get_caret_position
+                                               (EContentEditor *editor,
+                                                GCancellable *cancellable,
+                                                GAsyncReadyCallback callback,
+                                                gpointer user_data);
 
-guint          e_content_editor_get_caret_offset
-                                               (EContentEditor *editor);
+gboolean       e_content_editor_get_caret_position_finish
+                                               (EContentEditor *editor,
+                                                GAsyncResult *result,
+                                                guint *out_position,
+                                                guint *out_offset,
+                                                GError **error);
 
 gchar *                e_content_editor_get_current_signature_uid
                                                (EContentEditor *editor);
diff --git a/src/e-util/e-html-editor.c b/src/e-util/e-html-editor.c
index f9c35d9a3b..00ebe50b2d 100644
--- a/src/e-util/e-html-editor.c
+++ b/src/e-util/e-html-editor.c
@@ -1334,66 +1334,157 @@ e_html_editor_pack_above (EHTMLEditor *editor,
        editor->priv->editor_layout_row++;
 }
 
+typedef struct _SaveContentData {
+       GOutputStream *stream;
+       GCancellable *cancellable;
+} SaveContentData;
+
+static SaveContentData *
+save_content_data_new (GOutputStream *stream,
+                      GCancellable *cancellable)
+{
+       SaveContentData *scd;
+
+       scd = g_slice_new (SaveContentData);
+       scd->stream = stream;
+       scd->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+
+       return scd;
+}
+
+static void
+save_content_data_free (gpointer ptr)
+{
+       SaveContentData *scd = ptr;
+
+       if (scd) {
+               g_clear_object (&scd->stream);
+               g_clear_object (&scd->cancellable);
+               g_slice_free (SaveContentData, scd);
+       }
+}
+
+static void
+e_html_editor_save_content_ready_cb (GObject *source_object,
+                                    GAsyncResult *result,
+                                    gpointer user_data)
+{
+       ESimpleAsyncResult *simple = user_data;
+       EContentEditorContentHash *content_hash;
+       GError *error = NULL;
+
+       g_return_if_fail (E_IS_CONTENT_EDITOR (source_object));
+       g_return_if_fail (E_IS_SIMPLE_ASYNC_RESULT (simple));
+
+       content_hash = e_content_editor_get_content_finish (E_CONTENT_EDITOR (source_object), result, &error);
+
+       if (content_hash) {
+               const gchar *content;
+
+               content = e_content_editor_util_get_content_data (content_hash, GPOINTER_TO_UINT 
(e_simple_async_result_get_op_pointer (simple)));
+
+               if (!content) {
+                       g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Failed to obtain 
content of editor"));
+               } else {
+                       SaveContentData *scd;
+                       gsize written;
+
+                       scd = e_simple_async_result_get_user_data (simple);
+
+                       g_output_stream_write_all (scd->stream, content, strlen (content), &written, 
scd->cancellable, &error);
+               }
+
+               e_content_editor_util_free_content_hash (content_hash);
+
+               if (error)
+                       e_simple_async_result_take_error (simple, error);
+       } else {
+               e_simple_async_result_take_error (simple, error);
+       }
+
+       e_simple_async_result_complete (simple);
+       g_object_unref (simple);
+}
+
 /**
  * e_html_editor_save:
  * @editor: an #EHTMLEditor
  * @filename: file into which to save the content
  * @as_html: whether the content should be saved as HTML or plain text
- * @error:[out] a #GError
+ * @cancellable: an optional #GCancellable, or %NULL
+ * @callback: (scope async): a #GAsyncReadyCallback to call when the save is finished
+ * @user_data: (closure callback): user data passed to @callback
  *
- * Saves current content of the #EContentEditor into given file. When @as_html
- * is @FALSE, the content is first converted into plain text.
+ * Starts an asynchronous save of the current content of the #EContentEditor
+ * into given file. When @as_html is @FALSE, the content is first converted
+ * into plain text.
  *
- * Returns: @TRUE when content is succesfully saved, @FALSE otherwise.
- */
-gboolean
+ * Finish the call with e_html_editor_save_finish() from the @callback.
+ *
+ * Since: 3.36
+ **/
+void
 e_html_editor_save (EHTMLEditor *editor,
-                    const gchar *filename,
-                    gboolean as_html,
-                    GError **error)
+                   const gchar *filename,
+                   gboolean as_html,
+                   GCancellable *cancellable,
+                   GAsyncReadyCallback callback,
+                   gpointer user_data)
 {
        EContentEditor *cnt_editor;
+       ESimpleAsyncResult *simple;
+       EContentEditorGetContentFlags flag;
+       SaveContentData *scd;
        GFile *file;
        GFileOutputStream *stream;
-       gchar *content;
-       gsize written;
+       GError *local_error = NULL;
+
+       simple = e_simple_async_result_new (G_OBJECT (editor), callback, user_data, e_html_editor_save);
 
        file = g_file_new_for_path (filename);
-       stream = g_file_replace (
-               file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error);
-       if ((error && *error) || !stream)
-               return FALSE;
+       stream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &local_error);
+       if (local_error || !stream) {
+               e_simple_async_result_take_error (simple, local_error);
+               e_simple_async_result_complete_idle (simple);
 
-       cnt_editor = e_html_editor_get_content_editor (editor);
+               g_object_unref (simple);
+               g_object_unref (file);
 
-       if (as_html)
-               content = e_content_editor_get_content (
-                       cnt_editor,
-                       E_CONTENT_EDITOR_GET_TEXT_HTML |
-                       E_CONTENT_EDITOR_GET_PROCESSED,
-                       NULL, NULL);
-       else
-               content = e_content_editor_get_content (
-                       cnt_editor,
-                       E_CONTENT_EDITOR_GET_TEXT_PLAIN |
-                       E_CONTENT_EDITOR_GET_PROCESSED,
-                       NULL, NULL);
-
-       if (!content || !*content) {
-               g_free (content);
-               g_set_error (
-                       error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                       "Failed to obtain content of editor");
-               return FALSE;
+               return;
        }
 
-       g_output_stream_write_all (
-               G_OUTPUT_STREAM (stream), content, strlen (content),
-               &written, NULL, error);
+       flag = as_html ? E_CONTENT_EDITOR_GET_TO_SEND_HTML : E_CONTENT_EDITOR_GET_TO_SEND_PLAIN;
+
+       scd = save_content_data_new (G_OUTPUT_STREAM (stream), cancellable);
+
+       e_simple_async_result_set_user_data (simple, scd, save_content_data_free);
+       e_simple_async_result_set_op_pointer (simple, GUINT_TO_POINTER (flag), NULL);
+
+       cnt_editor = e_html_editor_get_content_editor (editor);
+
+       e_content_editor_get_content (cnt_editor, flag, NULL, cancellable, 
e_html_editor_save_content_ready_cb, simple);
 
-       g_free (content);
-       g_object_unref (stream);
        g_object_unref (file);
+}
 
-       return TRUE;
+/**
+ * e_html_editor_save_finish:
+ * @editor: an #EHTMLEditor
+ * @result: a #GAsyncResult of the operation
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finished the previous call of e_html_editor_save().
+ *
+ * Returns: whether the save succeeded.
+ *
+ * Since: 3.36
+ **/
+gboolean
+e_html_editor_save_finish (EHTMLEditor *editor,
+                          GAsyncResult *result,
+                          GError **error)
+{
+       g_return_val_if_fail (e_simple_async_result_is_valid (result, G_OBJECT (editor), e_html_editor_save), 
FALSE);
+
+       return !e_simple_async_result_propagate_error (E_SIMPLE_ASYNC_RESULT (result), error);
 }
diff --git a/src/e-util/e-html-editor.h b/src/e-util/e-html-editor.h
index 6c5a63a62d..121eae7758 100644
--- a/src/e-util/e-html-editor.h
+++ b/src/e-util/e-html-editor.h
@@ -110,9 +110,14 @@ void               e_html_editor_update_spell_actions
  * High-Level Editing Interface
  *****************************************************************************/
 
-gboolean       e_html_editor_save              (EHTMLEditor *editor,
+void           e_html_editor_save              (EHTMLEditor *editor,
                                                 const gchar *filename,
                                                 gboolean as_html,
+                                                GCancellable *cancellable,
+                                                GAsyncReadyCallback callback,
+                                                gpointer user_data);
+gboolean       e_html_editor_save_finish       (EHTMLEditor *editor,
+                                                GAsyncResult *result,
                                                 GError **error);
 
 G_END_DECLS
diff --git a/src/e-util/e-mail-signature-editor.c b/src/e-util/e-mail-signature-editor.c
index 4b6c13fae3..e51e3234d5 100644
--- a/src/e-util/e-mail-signature-editor.c
+++ b/src/e-util/e-mail-signature-editor.c
@@ -46,10 +46,13 @@ struct _EMailSignatureEditorPrivate {
 };
 
 struct _AsyncContext {
+       ESourceRegistry *registry;
        ESource *source;
        GCancellable *cancellable;
+       EContentEditorGetContentFlags contents_flag;
        gchar *contents;
        gsize length;
+       GDestroyNotify destroy_contents;
 };
 
 enum {
@@ -86,13 +89,14 @@ G_DEFINE_TYPE (
 static void
 async_context_free (AsyncContext *async_context)
 {
-       if (async_context->source != NULL)
-               g_object_unref (async_context->source);
-
-       if (async_context->cancellable != NULL)
-               g_object_unref (async_context->cancellable);
+       g_clear_object (&async_context->registry);
+       g_clear_object (&async_context->source);
+       g_clear_object (&async_context->cancellable);
 
-       g_free (async_context->contents);
+       if (async_context->destroy_contents)
+               async_context->destroy_contents (async_context->contents);
+       else
+               g_free (async_context->contents);
 
        g_slice_free (AsyncContext, async_context);
 }
@@ -917,6 +921,55 @@ mail_signature_editor_commit_cb (GObject *object,
                simple);
 }
 
+static void
+mail_signature_editor_content_hash_ready_cb (GObject *source_object,
+                                            GAsyncResult *result,
+                                            gpointer user_data)
+{
+       GSimpleAsyncResult *simple = user_data;
+       EContentEditorContentHash *content_hash;
+       ESourceMailSignature *extension;
+       AsyncContext *async_context;
+       GError *error = NULL;
+
+       g_return_if_fail (E_IS_CONTENT_EDITOR (source_object));
+
+       content_hash = e_content_editor_get_content_finish (E_CONTENT_EDITOR (source_object), result, &error);
+
+       if (!content_hash) {
+               g_simple_async_result_take_error (simple, error);
+               g_simple_async_result_complete (simple);
+               g_object_unref (simple);
+               return;
+       }
+
+       async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+       async_context->contents = e_content_editor_util_steal_content_data (content_hash,
+               async_context->contents_flag, &async_context->destroy_contents);
+
+       e_content_editor_util_free_content_hash (content_hash);
+
+       if (!async_context->contents) {
+               g_warning ("%s: Failed to retrieve content", G_STRFUNC);
+
+               async_context->contents = g_strdup ("");
+               async_context->destroy_contents = NULL;
+       }
+
+       async_context->length = strlen (async_context->contents);
+
+       extension = e_source_get_extension (async_context->source, E_SOURCE_EXTENSION_MAIL_SIGNATURE);
+       e_source_mail_signature_set_mime_type (extension,
+               async_context->contents_flag == E_CONTENT_EDITOR_GET_RAW_BODY_HTML ? "text/html" : 
"text/plain");
+
+       e_source_registry_commit_source (
+               async_context->registry, async_context->source,
+               async_context->cancellable,
+               mail_signature_editor_commit_cb,
+               simple);
+}
+
 void
 e_mail_signature_editor_commit (EMailSignatureEditor *window,
                                 GCancellable *cancellable,
@@ -925,12 +978,8 @@ e_mail_signature_editor_commit (EMailSignatureEditor *window,
 {
        GSimpleAsyncResult *simple;
        AsyncContext *async_context;
-       ESourceMailSignature *extension;
        ESourceRegistry *registry;
        ESource *source;
-       const gchar *extension_name;
-       const gchar *mime_type;
-       gchar *contents;
        EHTMLEditor *editor;
        EContentEditor *cnt_editor;
 
@@ -942,26 +991,10 @@ e_mail_signature_editor_commit (EMailSignatureEditor *window,
        editor = e_mail_signature_editor_get_editor (window);
        cnt_editor = e_html_editor_get_content_editor (editor);
 
-       mime_type = "text/html";
-       contents = e_content_editor_get_content (
-               cnt_editor,
-               E_CONTENT_EDITOR_GET_TEXT_HTML |
-               E_CONTENT_EDITOR_GET_BODY,
-               NULL, NULL);
-
-       if (!contents) {
-               g_warning ("%s: Failed to retrieve content", G_STRFUNC);
-               contents = g_strdup ("");
-       }
-
-       extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
-       extension = e_source_get_extension (source, extension_name);
-       e_source_mail_signature_set_mime_type (extension, mime_type);
-
        async_context = g_slice_new0 (AsyncContext);
+       async_context->registry = g_object_ref (registry);
        async_context->source = g_object_ref (source);
-       async_context->contents = contents;  /* takes ownership */
-       async_context->length = strlen (contents);
+       async_context->contents_flag = e_content_editor_get_html_mode (cnt_editor) ? 
E_CONTENT_EDITOR_GET_RAW_BODY_HTML : E_CONTENT_EDITOR_GET_TO_SEND_PLAIN;
 
        if (G_IS_CANCELLABLE (cancellable))
                async_context->cancellable = g_object_ref (cancellable);
@@ -973,11 +1006,8 @@ e_mail_signature_editor_commit (EMailSignatureEditor *window,
        g_simple_async_result_set_op_res_gpointer (
                simple, async_context, (GDestroyNotify) async_context_free);
 
-       e_source_registry_commit_source (
-               registry, source,
-               async_context->cancellable,
-               mail_signature_editor_commit_cb,
-               simple);
+       e_content_editor_get_content (cnt_editor, async_context->contents_flag, NULL,
+               cancellable, mail_signature_editor_content_hash_ready_cb, simple);
 }
 
 gboolean
diff --git a/src/e-util/e-util-enums.h b/src/e-util/e-util-enums.h
index b4090d55a6..432bc1ccb2 100644
--- a/src/e-util/e-util-enums.h
+++ b/src/e-util/e-util-enums.h
@@ -164,22 +164,29 @@ typedef enum {
 
 /**
  * EContentEditorGetContentFlags:
- * @E_CONTENT_EDITOR_GET_BODY:
- * @E_CONTENT_EDITOR_GET_INLINE_IMAGES:
- * @E_CONTENT_EDITOR_GET_PROCESSED: raw or processed
- * @E_CONTENT_EDITOR_GET_TEXT_HTML:
- * @E_CONTENT_EDITOR_GET_TEXT_PLAIN:
- * @E_CONTENT_EDITOR_GET_EXCLUDE_SIGNATURE:
+ * @E_CONTENT_EDITOR_GET_INLINE_IMAGES: Return also list of inline images
+ * @E_CONTENT_EDITOR_GET_RAW_BODY_HTML: text/html version of the body only, as used by the editor
+ * @E_CONTENT_EDITOR_GET_RAW_BODY_PLAIN: text/plain version of the body only, as used by the editor
+ * @E_CONTENT_EDITOR_GET_RAW_BODY_STRIPPED: text/plain version of the body only, without signature, quoted 
text and such
+ * @E_CONTENT_EDITOR_GET_RAW_DRAFT: a version of the content, to use as draft message
+ * @E_CONTENT_EDITOR_GET_TO_SEND_HTML: text/html version of the content, suitable to be sent
+ * @E_CONTENT_EDITOR_GET_TO_SEND_PLAIN:        text/plain version of the content, suitable to be sent
+ * @E_CONTENT_EDITOR_GET_ALL: a shortcut for all flags
+ *
+ * Influences what content should be returned. Each flag means one
+ * version, or part, of the content.
  *
- * Since: 3.22
+ * Since: 3.36
  **/
 typedef enum {
-       E_CONTENT_EDITOR_GET_BODY               = 1 << 0,
-       E_CONTENT_EDITOR_GET_INLINE_IMAGES      = 1 << 1,
-       E_CONTENT_EDITOR_GET_PROCESSED          = 1 << 2, /* raw or processed */
-       E_CONTENT_EDITOR_GET_TEXT_HTML          = 1 << 3,
-       E_CONTENT_EDITOR_GET_TEXT_PLAIN         = 1 << 4,
-       E_CONTENT_EDITOR_GET_EXCLUDE_SIGNATURE  = 1 << 5
+       E_CONTENT_EDITOR_GET_INLINE_IMAGES      = 1 << 0,
+       E_CONTENT_EDITOR_GET_RAW_BODY_HTML      = 1 << 1,
+       E_CONTENT_EDITOR_GET_RAW_BODY_PLAIN     = 1 << 2,
+       E_CONTENT_EDITOR_GET_RAW_BODY_STRIPPED  = 1 << 3,
+       E_CONTENT_EDITOR_GET_RAW_DRAFT          = 1 << 4,
+       E_CONTENT_EDITOR_GET_TO_SEND_HTML       = 1 << 5,
+       E_CONTENT_EDITOR_GET_TO_SEND_PLAIN      = 1 << 6,
+       E_CONTENT_EDITOR_GET_ALL                = ~0
 } EContentEditorGetContentFlags;
 
 /**
diff --git a/src/e-util/test-html-editor-units-utils.c b/src/e-util/test-html-editor-units-utils.c
index 1f7e5eb4f0..a0c72ed4f7 100644
--- a/src/e-util/test-html-editor-units-utils.c
+++ b/src/e-util/test-html-editor-units-utils.c
@@ -83,6 +83,50 @@ test_utils_free_global_memory (void)
        g_clear_object (&global_web_context);
 }
 
+typedef struct _GetContentData {
+       EContentEditorContentHash *content_hash;
+       gpointer async_data;
+} GetContentData;
+
+static void
+get_editor_content_hash_ready_cb (GObject *source_object,
+                                 GAsyncResult *result,
+                                 gpointer user_data)
+{
+       GetContentData *gcd = user_data;
+       GError *error = NULL;
+
+       g_assert_nonnull (gcd);
+       g_assert (E_IS_CONTENT_EDITOR (source_object));
+
+       gcd->content_hash = e_content_editor_get_content_finish (E_CONTENT_EDITOR (source_object), result, 
&error);
+
+       g_assert_no_error (error);
+
+       g_clear_error (&error);
+
+       test_utils_async_call_finish (gcd->async_data);
+}
+
+static EContentEditorContentHash *
+test_utils_get_editor_content_hash_sync (EContentEditor *cnt_editor,
+                                        guint32 flags)
+{
+       GetContentData gcd;
+
+       g_assert (E_IS_CONTENT_EDITOR (cnt_editor));
+
+       gcd.content_hash = NULL;
+       gcd.async_data = test_utils_async_call_prepare ();
+
+       e_content_editor_get_content (cnt_editor, flags, "test-domain", NULL, 
get_editor_content_hash_ready_cb, &gcd);
+
+       g_assert (test_utils_async_call_wait (gcd.async_data, MAX (event_processing_delay_ms / 25, 1) + 1));
+       g_assert_nonnull (gcd.content_hash);
+
+       return gcd.content_hash;
+}
+
 typedef struct _UndoContent {
        gchar *html;
        gchar *plain;
@@ -92,16 +136,20 @@ static UndoContent *
 undo_content_new (TestFixture *fixture)
 {
        EContentEditor *cnt_editor;
+       EContentEditorContentHash *content_hash;
        UndoContent *uc;
 
        g_return_val_if_fail (fixture != NULL, NULL);
        g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), NULL);
 
        cnt_editor = e_html_editor_get_content_editor (fixture->editor);
+       content_hash = test_utils_get_editor_content_hash_sync (cnt_editor, E_CONTENT_EDITOR_GET_TO_SEND_HTML 
| E_CONTENT_EDITOR_GET_TO_SEND_PLAIN);
 
        uc = g_new0 (UndoContent, 1);
-       uc->html = e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_PROCESSED | 
E_CONTENT_EDITOR_GET_TEXT_HTML, NULL, NULL);
-       uc->plain = e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_PROCESSED | 
E_CONTENT_EDITOR_GET_TEXT_PLAIN, NULL, NULL);
+       uc->html = e_content_editor_util_steal_content_data (content_hash, E_CONTENT_EDITOR_GET_TO_SEND_HTML, 
NULL);
+       uc->plain = e_content_editor_util_steal_content_data (content_hash, 
E_CONTENT_EDITOR_GET_TO_SEND_PLAIN, NULL);
+
+       e_content_editor_util_free_content_hash (content_hash);
 
        g_warn_if_fail (uc->html != NULL);
        g_warn_if_fail (uc->plain != NULL);
@@ -127,15 +175,17 @@ undo_content_test (TestFixture *fixture,
                   gint cmd_index)
 {
        EContentEditor *cnt_editor;
-       gchar *text;
+       EContentEditorContentHash *content_hash;
+       const gchar *text;
 
        g_return_val_if_fail (fixture != NULL, FALSE);
        g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
        g_return_val_if_fail (uc != NULL, FALSE);
 
        cnt_editor = e_html_editor_get_content_editor (fixture->editor);
+       content_hash = test_utils_get_editor_content_hash_sync (cnt_editor, E_CONTENT_EDITOR_GET_TO_SEND_HTML 
| E_CONTENT_EDITOR_GET_TO_SEND_PLAIN);
 
-       text = e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_PROCESSED | 
E_CONTENT_EDITOR_GET_TEXT_HTML, NULL, NULL);
+       text = e_content_editor_util_get_content_data (content_hash, E_CONTENT_EDITOR_GET_TO_SEND_HTML);
        g_return_val_if_fail (text != NULL, FALSE);
 
        if (!test_utils_html_equal (fixture, text, uc->html)) {
@@ -143,13 +193,13 @@ undo_content_test (TestFixture *fixture,
                        g_printerr ("%s: returned HTML\n---%s---\n and expected HTML\n---%s---\n do not match 
at command %d\n", G_STRFUNC, text, uc->html, cmd_index);
                else
                        g_warning ("%s: returned HTML\n---%s---\n and expected HTML\n---%s---\n do not match 
at command %d", G_STRFUNC, text, uc->html, cmd_index);
-               g_free (text);
+
+               e_content_editor_util_free_content_hash (content_hash);
+
                return FALSE;
        }
 
-       g_free (text);
-
-       text = e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_PROCESSED | 
E_CONTENT_EDITOR_GET_TEXT_PLAIN, NULL, NULL);
+       text = e_content_editor_util_get_content_data (content_hash, E_CONTENT_EDITOR_GET_TO_SEND_PLAIN);
        g_return_val_if_fail (text != NULL, FALSE);
 
        if (!test_utils_html_equal (fixture, text, uc->plain)) {
@@ -157,11 +207,13 @@ undo_content_test (TestFixture *fixture,
                        g_printerr ("%s: returned Plain\n---%s---\n and expected Plain\n---%s---\n do not 
match at command %d\n", G_STRFUNC, text, uc->plain, cmd_index);
                else
                        g_warning ("%s: returned Plain\n---%s---\n and expected Plain\n---%s---\n do not 
match at command %d", G_STRFUNC, text, uc->plain, cmd_index);
-               g_free (text);
+
+               e_content_editor_util_free_content_hash (content_hash);
+
                return FALSE;
        }
 
-       g_free (text);
+       e_content_editor_util_free_content_hash (content_hash);
 
        return TRUE;
 }
@@ -1028,7 +1080,8 @@ test_utils_run_simple_test (TestFixture *fixture,
                            const gchar *expected_plain)
 {
        EContentEditor *cnt_editor;
-       gchar *text;
+       EContentEditorContentHash *content_hash;
+       const gchar *text;
 
        g_return_val_if_fail (fixture != NULL, FALSE);
        g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
@@ -1039,8 +1092,10 @@ test_utils_run_simple_test (TestFixture *fixture,
        if (!test_utils_process_commands (fixture, commands))
                return FALSE;
 
+       content_hash = test_utils_get_editor_content_hash_sync (cnt_editor, E_CONTENT_EDITOR_GET_TO_SEND_HTML 
| E_CONTENT_EDITOR_GET_TO_SEND_PLAIN);
+
        if (expected_html) {
-               text = e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_PROCESSED | 
E_CONTENT_EDITOR_GET_TEXT_HTML, NULL, NULL);
+               text = e_content_editor_util_get_content_data (content_hash, 
E_CONTENT_EDITOR_GET_TO_SEND_HTML);
                g_return_val_if_fail (text != NULL, FALSE);
 
                if (!test_utils_html_equal (fixture, text, expected_html)) {
@@ -1048,15 +1103,15 @@ test_utils_run_simple_test (TestFixture *fixture,
                                g_printerr ("%s: returned HTML\n---%s---\n and expected HTML\n---%s---\n do 
not match\n", G_STRFUNC, text, expected_html);
                        else
                                g_warning ("%s: returned HTML\n---%s---\n and expected HTML\n---%s---\n do 
not match", G_STRFUNC, text, expected_html);
-                       g_free (text);
+
+                       e_content_editor_util_free_content_hash (content_hash);
+
                        return FALSE;
                }
-
-               g_free (text);
        }
 
        if (expected_plain) {
-               text = e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_PROCESSED | 
E_CONTENT_EDITOR_GET_TEXT_PLAIN, NULL, NULL);
+               text = e_content_editor_util_get_content_data (content_hash, 
E_CONTENT_EDITOR_GET_TO_SEND_PLAIN);
                g_return_val_if_fail (text != NULL, FALSE);
 
                if (!test_utils_html_equal (fixture, text, expected_plain)) {
@@ -1064,13 +1119,15 @@ test_utils_run_simple_test (TestFixture *fixture,
                                g_printerr ("%s: returned Plain\n---%s---\n and expected Plain\n---%s---\n do 
not match\n", G_STRFUNC, text, expected_plain);
                        else
                                g_warning ("%s: returned Plain\n---%s---\n and expected Plain\n---%s---\n do 
not match", G_STRFUNC, text, expected_plain);
-                       g_free (text);
+
+                       e_content_editor_util_free_content_hash (content_hash);
+
                        return FALSE;
                }
-
-               g_free (text);
        }
 
+       e_content_editor_util_free_content_hash (content_hash);
+
        return TRUE;
 }
 
diff --git a/src/e-util/test-html-editor.c b/src/e-util/test-html-editor.c
index 84010ff899..9b7eaaf3a1 100644
--- a/src/e-util/test-html-editor.c
+++ b/src/e-util/test-html-editor.c
@@ -139,19 +139,16 @@ save_dialog (EHTMLEditor *editor)
 }
 
 static void
-view_source_dialog (EHTMLEditor *editor,
-                    const gchar *title,
-                    gboolean plain_text,
-                    gboolean show_source)
+view_source_dialog_show (EHTMLEditor *editor,
+                        const gchar *title,
+                        gboolean plain_text,
+                        gboolean show_source,
+                        const gchar *content_text)
 {
        GtkWidget *dialog;
        GtkWidget *content;
        GtkWidget *content_area;
        GtkWidget *scrolled_window;
-       EContentEditor *cnt_editor;
-       gchar * html;
-
-       cnt_editor = e_html_editor_get_content_editor (editor);
 
        dialog = gtk_dialog_new_with_buttons (
                title,
@@ -176,32 +173,17 @@ view_source_dialog (EHTMLEditor *editor,
        gtk_container_set_border_width (GTK_CONTAINER (scrolled_window), 6);
        gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 300);
 
-       if (plain_text) {
-               html = e_content_editor_get_content (cnt_editor,
-                       E_CONTENT_EDITOR_GET_PROCESSED | E_CONTENT_EDITOR_GET_TEXT_PLAIN,
-                       NULL, NULL);
-       } else {
-               GSList *inline_images = NULL;
-
-               html = e_content_editor_get_content (cnt_editor,
-                       E_CONTENT_EDITOR_GET_PROCESSED | E_CONTENT_EDITOR_GET_TEXT_HTML | 
E_CONTENT_EDITOR_GET_INLINE_IMAGES,
-                       "test-domain", &inline_images);
-
-               g_slist_free_full (inline_images, g_object_unref);
-       }
-
        if (show_source || plain_text) {
                GtkTextBuffer *buffer;
 
                content = gtk_text_view_new ();
                buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (content));
-               gtk_text_buffer_set_text (buffer, html ? html : "", -1);
+               gtk_text_buffer_set_text (buffer, content_text ? content_text : "", -1);
                gtk_text_view_set_editable (GTK_TEXT_VIEW (content), FALSE);
        } else {
                content = webkit_web_view_new ();
-               webkit_web_view_load_html (WEBKIT_WEB_VIEW (content), html ? html : "", "evo-file://");
+               webkit_web_view_load_html (WEBKIT_WEB_VIEW (content), content_text ? content_text : "", 
"evo-file://");
        }
-       g_free (html);
 
        gtk_container_add (GTK_CONTAINER (scrolled_window), content);
        gtk_widget_show_all (scrolled_window);
@@ -210,6 +192,94 @@ view_source_dialog (EHTMLEditor *editor,
        gtk_widget_destroy (dialog);
 }
 
+typedef struct _ViewSourceData {
+       EHTMLEditor *editor;
+       gchar *title;
+       gboolean plain_text;
+       gboolean show_source;
+} ViewSourceData;
+
+static ViewSourceData *
+view_source_data_new (EHTMLEditor *editor,
+                     const gchar *title,
+                     gboolean plain_text,
+                     gboolean show_source)
+{
+       ViewSourceData *vsd;
+
+       vsd = g_slice_new (ViewSourceData);
+       vsd->editor = g_object_ref (editor);
+       vsd->title = g_strdup (title);
+       vsd->plain_text = plain_text;
+       vsd->show_source = show_source;
+
+       return vsd;
+}
+
+static void
+view_source_data_free (gpointer ptr)
+{
+       ViewSourceData *vsd = ptr;
+
+       if (vsd) {
+               g_clear_object (&vsd->editor);
+               g_free (vsd->title);
+               g_slice_free (ViewSourceData, vsd);
+       }
+}
+
+static void
+view_source_dialog_content_hash_ready_cb (GObject *source_object,
+                                         GAsyncResult *result,
+                                         gpointer user_data)
+{
+       ViewSourceData *vcd = user_data;
+       EContentEditorContentHash *content_hash;
+       GError *error = NULL;
+
+       g_return_if_fail (vcd != NULL);
+       g_return_if_fail (E_IS_CONTENT_EDITOR (source_object));
+
+       content_hash = e_content_editor_get_content_finish (E_CONTENT_EDITOR (source_object), result, &error);
+
+       if (!content_hash) {
+               g_warning ("%s: Failed to get content: %s", G_STRFUNC, error ? error->message : "Unknown 
error");
+       } else {
+               view_source_dialog_show (vcd->editor, vcd->title, vcd->plain_text, vcd->show_source,
+                       e_content_editor_util_get_content_data (content_hash,
+                               vcd->plain_text ? E_CONTENT_EDITOR_GET_TO_SEND_PLAIN : 
E_CONTENT_EDITOR_GET_TO_SEND_HTML));
+
+               e_content_editor_util_free_content_hash (content_hash);
+       }
+
+       view_source_data_free (vcd);
+       g_clear_error (&error);
+}
+
+static void
+view_source_dialog (EHTMLEditor *editor,
+                    const gchar *title,
+                    gboolean plain_text,
+                    gboolean show_source)
+{
+       EContentEditor *cnt_editor;
+       ViewSourceData *vcd;
+       guint32 flags;
+
+       vcd = view_source_data_new (editor, title, plain_text, show_source);
+
+       cnt_editor = e_html_editor_get_content_editor (editor);
+
+       if (plain_text) {
+               flags = E_CONTENT_EDITOR_GET_TO_SEND_PLAIN;
+       } else {
+               flags = E_CONTENT_EDITOR_GET_INLINE_IMAGES | E_CONTENT_EDITOR_GET_TO_SEND_HTML;
+       }
+
+       e_content_editor_get_content (cnt_editor, flags, "test-domain", NULL,
+               view_source_dialog_content_hash_ready_cb, vcd);
+}
+
 static void
 action_new_editor_cb (GtkAction *action,
                      EHTMLEditor *editor)
@@ -240,13 +310,24 @@ action_quit_cb (GtkAction *action,
        gtk_main_quit ();
 }
 
+static void
+html_editor_save_done_cb (GObject *source_object,
+                         GAsyncResult *result,
+                         gpointer user_data)
+{
+       GError *error = NULL;
+
+       e_html_editor_save_finish (E_HTML_EDITOR (source_object), result, &error);
+
+       handle_error (&error);
+}
+
 static void
 action_save_cb (GtkAction *action,
                 EHTMLEditor *editor)
 {
        const gchar *filename;
        gboolean as_html;
-       GError *error = NULL;
 
        if (e_html_editor_get_filename (editor) == NULL)
                if (save_dialog (editor) == GTK_RESPONSE_CANCEL)
@@ -255,8 +336,7 @@ action_save_cb (GtkAction *action,
        filename = e_html_editor_get_filename (editor);
        as_html = (e_content_editor_get_html_mode (e_html_editor_get_content_editor (editor)));
 
-       e_html_editor_save (editor, filename, as_html, &error);
-       handle_error (&error);
+       e_html_editor_save (editor, filename, as_html, NULL, html_editor_save_done_cb, NULL);
 }
 
 static void
@@ -265,7 +345,6 @@ action_save_as_cb (GtkAction *action,
 {
        const gchar *filename;
        gboolean as_html;
-       GError *error = NULL;
 
        if (save_dialog (editor) == GTK_RESPONSE_CANCEL)
                return;
@@ -273,8 +352,7 @@ action_save_as_cb (GtkAction *action,
        filename = e_html_editor_get_filename (editor);
        as_html = (e_content_editor_get_html_mode (e_html_editor_get_content_editor (editor)));
 
-       e_html_editor_save (editor, filename, as_html, &error);
-       handle_error (&error);
+       e_html_editor_save (editor, filename, as_html, NULL, html_editor_save_done_cb, NULL);
 }
 
 static void
diff --git a/src/mail/e-mail-notes.c b/src/mail/e-mail-notes.c
index b55827d64e..28aaefec86 100644
--- a/src/mail/e-mail-notes.c
+++ b/src/mail/e-mail-notes.c
@@ -289,7 +289,8 @@ e_mail_notes_editor_extract_text_from_message (EMailNotesEditor *notes_editor,
 }
 
 static CamelMimeMessage *
-e_mail_notes_editor_encode_text_to_message (EMailNotesEditor *notes_editor)
+e_mail_notes_editor_encode_text_to_message (EMailNotesEditor *notes_editor,
+                                           EContentEditorContentHash *content_hash)
 {
        EContentEditor *cnt_editor;
        EAttachmentStore *attachment_store;
@@ -301,6 +302,7 @@ e_mail_notes_editor_encode_text_to_message (EMailNotesEditor *notes_editor)
 
        g_return_val_if_fail (E_IS_MAIL_NOTES_EDITOR (notes_editor), NULL);
        g_return_val_if_fail (notes_editor->editor, NULL);
+       g_return_val_if_fail (content_hash != NULL, NULL);
 
        cnt_editor = e_html_editor_get_content_editor (notes_editor->editor);
        g_return_val_if_fail (E_IS_CONTENT_EDITOR (cnt_editor), NULL);
@@ -330,24 +332,20 @@ e_mail_notes_editor_encode_text_to_message (EMailNotesEditor *notes_editor)
                CamelMultipart *multipart_body;
                CamelMimePart *part;
                GSList *inline_images_parts = NULL;
-               gchar *text;
+               const gchar *text;
 
                multipart_alternative = camel_multipart_new ();
                camel_data_wrapper_set_mime_type (CAMEL_DATA_WRAPPER (multipart_alternative), 
"multipart/alternative");
                camel_multipart_set_boundary (multipart_alternative, NULL);
 
-               text = e_content_editor_get_content (
-                       cnt_editor,
-                       E_CONTENT_EDITOR_GET_TEXT_PLAIN |
-                       E_CONTENT_EDITOR_GET_PROCESSED,
-                       NULL, NULL);
+               text = e_content_editor_util_get_content_data (content_hash, 
E_CONTENT_EDITOR_GET_TO_SEND_PLAIN);
 
                if (text && *text) {
-                       if (!g_str_has_suffix (text, "\r\n")) {
-                               gchar *tmp = text;
+                       gchar *tmp = NULL;
 
-                               text = g_strconcat (tmp, "\r\n", NULL);
-                               g_free (tmp);
+                       if (!g_str_has_suffix (text, "\r\n")) {
+                               tmp = g_strconcat (text, "\r\n", NULL);
+                               text = tmp;
                        }
 
                        part = camel_mime_part_new ();
@@ -355,33 +353,26 @@ e_mail_notes_editor_encode_text_to_message (EMailNotesEditor *notes_editor)
                        camel_multipart_add_part (multipart_alternative, part);
 
                        g_object_unref (part);
+                       g_free (tmp);
 
                        has_text = TRUE;
                }
 
-               g_free (text);
-
-               text = e_content_editor_get_content (
-                       cnt_editor,
-                       E_CONTENT_EDITOR_GET_PROCESSED |
-                       E_CONTENT_EDITOR_GET_TEXT_HTML |
-                       E_CONTENT_EDITOR_GET_INLINE_IMAGES,
-                       g_get_host_name (),
-                       &inline_images_parts);
+               text = e_content_editor_util_get_content_data (content_hash, 
E_CONTENT_EDITOR_GET_TO_SEND_HTML);
+               inline_images_parts = e_content_editor_util_get_content_data (content_hash, 
E_CONTENT_EDITOR_GET_INLINE_IMAGES);
 
                if (has_attachments && !has_text && (!text || !*text)) {
                        /* Text is required, thus if there are attachments,
                           but no text, then store at least a space. */
-                       g_free (text);
-                       text = g_strdup ("\r\n");
+                       text = "\r\n";
                }
 
                if (text && *text) {
-                       if (!g_str_has_suffix (text, "\r\n")) {
-                               gchar *tmp = text;
+                       gchar *tmp = NULL;
 
-                               text = g_strconcat (tmp, "\r\n", NULL);
-                               g_free (tmp);
+                       if (!g_str_has_suffix (text, "\r\n")) {
+                               tmp = g_strconcat (text, "\r\n", NULL);
+                               text = tmp;
                        }
 
                        part = camel_mime_part_new ();
@@ -389,15 +380,13 @@ e_mail_notes_editor_encode_text_to_message (EMailNotesEditor *notes_editor)
                        camel_multipart_add_part (multipart_alternative, part);
 
                        g_object_unref (part);
+                       g_free (tmp);
 
                        has_text = TRUE;
                } else {
-                       g_slist_free_full (inline_images_parts, g_object_unref);
                        inline_images_parts = NULL;
                }
 
-               g_free (text);
-
                if (inline_images_parts) {
                        GSList *link;
 
@@ -443,31 +432,25 @@ e_mail_notes_editor_encode_text_to_message (EMailNotesEditor *notes_editor)
 
                camel_medium_set_content (CAMEL_MEDIUM (message), CAMEL_DATA_WRAPPER (multipart_body));
 
-               g_slist_free_full (inline_images_parts, g_object_unref);
                g_clear_object (&multipart_alternative);
                g_clear_object (&multipart_body);
        } else {
-               gchar *text;
+               const gchar *text;
 
-               text = e_content_editor_get_content (
-                       cnt_editor,
-                       E_CONTENT_EDITOR_GET_TEXT_PLAIN |
-                       E_CONTENT_EDITOR_GET_PROCESSED,
-                       NULL, NULL);
+               text = e_content_editor_util_get_content_data (content_hash, 
E_CONTENT_EDITOR_GET_TO_SEND_PLAIN);
 
                if (has_attachments && !has_text && (!text || !*text)) {
                        /* Text is required, thus if there are attachments,
                           but no text, then store at least a space. */
-                       g_free (text);
-                       text = g_strdup ("\r\n");
+                       text = "\r\n";
                }
 
                if (text && *text) {
-                       if (!g_str_has_suffix (text, "\r\n")) {
-                               gchar *tmp = text;
+                       gchar *tmp = NULL;
 
-                               text = g_strconcat (tmp, "\r\n", NULL);
-                               g_free (tmp);
+                       if (!g_str_has_suffix (text, "\r\n")) {
+                               tmp = g_strconcat (text, "\r\n", NULL);
+                               text = tmp;
                        }
 
                        if (has_attachments) {
@@ -491,10 +474,11 @@ e_mail_notes_editor_encode_text_to_message (EMailNotesEditor *notes_editor)
                        } else {
                                camel_mime_part_set_content (CAMEL_MIME_PART (message), text, strlen (text), 
"text/plain");
                        }
+
                        has_text = TRUE;
-               }
 
-               g_free (text);
+                       g_free (tmp);
+               }
        }
 
        if (has_text) {
@@ -790,9 +774,22 @@ action_close_cb (GtkAction *action,
 typedef struct {
        EMailNotesEditor *notes_editor;
        CamelMimeMessage *inner_message;
+       EActivity *activity;
+       GError *error;
        gboolean success;
 } SaveAndCloseData;
 
+static SaveAndCloseData *
+save_and_close_data_new (EMailNotesEditor *notes_editor)
+{
+       SaveAndCloseData *scd;
+
+       scd = g_slice_new0 (SaveAndCloseData);
+       scd->notes_editor = g_object_ref (notes_editor);
+
+       return scd;
+}
+
 static void
 save_and_close_data_free (gpointer ptr)
 {
@@ -804,7 +801,9 @@ save_and_close_data_free (gpointer ptr)
                else
                        g_clear_object (&scd->notes_editor);
                g_clear_object (&scd->inner_message);
-               g_free (scd);
+               g_clear_object (&scd->activity);
+               g_clear_error (&scd->error);
+               g_slice_free (SaveAndCloseData, scd);
        }
 }
 
@@ -819,6 +818,12 @@ e_mail_notes_store_changes_thread (EAlertSinkThreadJobData *job_data,
 
        g_return_if_fail (scd != NULL);
 
+       if (scd->error) {
+               g_propagate_error (error, scd->error);
+               scd->error = NULL;
+               return;
+       }
+
        if (g_cancellable_set_error_if_cancelled (cancellable, error))
                return;
 
@@ -841,35 +846,76 @@ e_mail_notes_store_changes_thread (EAlertSinkThreadJobData *job_data,
 }
 
 static void
-action_save_and_close_cb (GtkAction *action,
-                         EMailNotesEditor *notes_editor)
+mail_notes_get_content_ready_cb (GObject *source_object,
+                                GAsyncResult *result,
+                                gpointer user_data)
 {
-       SaveAndCloseData *scd;
-       gchar *full_display_name;
+       SaveAndCloseData *scd = user_data;
+       EContentEditorContentHash *content_hash;
        EActivityBar *activity_bar;
        EActivity *activity;
+       gchar *full_display_name;
+       GError *error = NULL;
 
-       g_return_if_fail (E_IS_MAIL_NOTES_EDITOR (notes_editor));
+       g_return_if_fail (scd != NULL);
+       g_return_if_fail (E_IS_CONTENT_EDITOR (source_object));
 
-       scd = g_new0 (SaveAndCloseData, 1);
-       scd->notes_editor = g_object_ref (notes_editor);
-       scd->inner_message = e_mail_notes_editor_encode_text_to_message (notes_editor);
-       scd->success = FALSE;
+       content_hash = e_content_editor_get_content_finish (E_CONTENT_EDITOR (source_object), result, &error);
 
-       full_display_name = e_mail_folder_to_full_display_name (notes_editor->folder, NULL);
+       if (content_hash) {
+               scd->inner_message = e_mail_notes_editor_encode_text_to_message (scd->notes_editor, 
content_hash);
 
-       activity_bar = e_html_editor_get_activity_bar (notes_editor->editor);
-       activity = e_alert_sink_submit_thread_job (E_ALERT_SINK (notes_editor->editor),
+               if (!scd->inner_message)
+                       scd->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, _("Failed to convert 
text to message"));
+       } else {
+               scd->error = error;
+
+               if (!scd->error)
+                       scd->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, _("Unknown error"));
+       }
+
+       g_clear_object (&scd->activity);
+
+       full_display_name = e_mail_folder_to_full_display_name (scd->notes_editor->folder, NULL);
+
+       activity_bar = e_html_editor_get_activity_bar (scd->notes_editor->editor);
+       activity = e_alert_sink_submit_thread_job (E_ALERT_SINK (scd->notes_editor->editor),
                _("Storing changes…"), "mail:failed-store-note",
-               full_display_name ? full_display_name : camel_folder_get_display_name (notes_editor->folder),
+               full_display_name ? full_display_name : camel_folder_get_display_name 
(scd->notes_editor->folder),
                e_mail_notes_store_changes_thread,
                scd, save_and_close_data_free);
        e_activity_bar_set_activity (activity_bar, activity);
        g_clear_object (&activity);
 
+       e_content_editor_util_free_content_hash (content_hash);
        g_free (full_display_name);
 }
 
+static void
+action_save_and_close_cb (GtkAction *action,
+                         EMailNotesEditor *notes_editor)
+{
+       SaveAndCloseData *scd;
+       EActivity *activity;
+       EContentEditor *cnt_editor;
+
+       g_return_if_fail (E_IS_MAIL_NOTES_EDITOR (notes_editor));
+
+       cnt_editor = e_html_editor_get_content_editor (notes_editor->editor);
+       g_return_if_fail (E_IS_CONTENT_EDITOR (cnt_editor));
+
+       activity = e_html_editor_new_activity (notes_editor->editor);
+       e_activity_set_text (activity, _("Storing changes…"));
+
+       scd = save_and_close_data_new (notes_editor);
+       scd->activity = activity; /* takes ownership */
+
+       e_content_editor_get_content (cnt_editor,
+               E_CONTENT_EDITOR_GET_INLINE_IMAGES | E_CONTENT_EDITOR_GET_TO_SEND_HTML | 
E_CONTENT_EDITOR_GET_TO_SEND_PLAIN,
+               g_get_host_name (), e_activity_get_cancellable (activity),
+               mail_notes_get_content_ready_cb, scd);
+}
+
 static void
 e_mail_notes_editor_dispose (GObject *object)
 {
diff --git a/src/modules/composer-to-meeting/e-composer-to-meeting.c 
b/src/modules/composer-to-meeting/e-composer-to-meeting.c
index 8bc2b52bfc..04a328ce61 100644
--- a/src/modules/composer-to-meeting/e-composer-to-meeting.c
+++ b/src/modules/composer-to-meeting/e-composer-to-meeting.c
@@ -64,11 +64,10 @@ GType e_composer_to_meeting_get_type (void) G_GNUC_CONST;
 G_DEFINE_DYNAMIC_TYPE (EComposerToMeeting, e_composer_to_meeting, E_TYPE_EXTENSION)
 
 static ECalComponent *
-composer_to_meeting_component (EMsgComposer *composer)
+composer_to_meeting_component (EMsgComposer *composer,
+                              EContentEditorContentHash *content_hash)
 {
        ECalComponent *comp;
-       EHTMLEditor *html_editor;
-       EContentEditor *cnt_editor;
        EComposerHeaderTable *header_table;
        EDestination **destinations_array[3];
        ESource *source;
@@ -210,20 +209,12 @@ composer_to_meeting_component (EMsgComposer *composer)
        g_slist_free_full (attendees, e_cal_component_attendee_free);
 
        /* Description */
-       html_editor = e_msg_composer_get_editor (composer);
-       cnt_editor = e_html_editor_get_content_editor (html_editor);
-       text = e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_PROCESSED | 
E_CONTENT_EDITOR_GET_TEXT_PLAIN, NULL, NULL);
+       text = content_hash ? e_content_editor_util_get_content_data (content_hash, 
E_CONTENT_EDITOR_GET_TO_SEND_PLAIN) : NULL;
+
        if (text && *text) {
                ECalComponentText *description;
                GSList *descr_list = NULL;
 
-               if (!g_str_has_suffix (text, "\r\n")) {
-                       gchar *tmp = text;
-
-                       text = g_strconcat (tmp, "\r\n", NULL);
-                       g_free (tmp);
-               }
-
                description = e_cal_component_text_new (text, NULL);
 
                descr_list = g_slist_append (descr_list, description);
@@ -232,7 +223,6 @@ composer_to_meeting_component (EMsgComposer *composer)
 
                g_slist_free_full (descr_list, e_cal_component_text_free);
        }
-       g_free (text);
 
        return comp;
 }
@@ -270,38 +260,102 @@ composer_to_meeting_copy_attachments (EMsgComposer *composer,
        g_list_free_full (attachments, g_object_unref);
 }
 
+typedef struct _AsyncContext {
+       EMsgComposer *composer;
+       EActivity *activity;
+} AsyncContext;
+
+static AsyncContext *
+async_context_new (EMsgComposer *composer, /* adds reference */
+                  EActivity *activity) /* assumes ownership */
+{
+       AsyncContext *async_context;
+
+       async_context = g_slice_new (AsyncContext);
+       async_context->composer = g_object_ref (composer);
+       async_context->activity = activity;
+
+       return async_context;
+}
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+       if (async_context) {
+               g_clear_object (&async_context->composer);
+               g_clear_object (&async_context->activity);
+               g_slice_free (AsyncContext, async_context);
+       }
+}
+
+static void
+compose_to_meeting_content_ready_cb (GObject *source_object,
+                                    GAsyncResult *result,
+                                    gpointer user_data)
+{
+       AsyncContext *async_context = user_data;
+       EContentEditorContentHash *content_hash;
+       ECalComponent *comp;
+       GError *error = NULL;
+
+       g_return_if_fail (async_context != NULL);
+       g_return_if_fail (E_IS_CONTENT_EDITOR (source_object));
+
+       content_hash = e_content_editor_get_content_finish (E_CONTENT_EDITOR (source_object), result, &error);
+
+       comp = composer_to_meeting_component (async_context->composer, content_hash);
+
+       if (comp) {
+               ECompEditor *comp_editor;
+               ECompEditorFlags flags;
+
+               flags = E_COMP_EDITOR_FLAG_IS_NEW |
+                       E_COMP_EDITOR_FLAG_ORGANIZER_IS_USER |
+                       E_COMP_EDITOR_FLAG_WITH_ATTENDEES;
+
+               comp_editor = e_comp_editor_open_for_component (NULL, e_msg_composer_get_shell 
(async_context->composer),
+                       NULL, e_cal_component_get_icalcomponent (comp), flags);
+
+               /* Attachments */
+               composer_to_meeting_copy_attachments (async_context->composer, comp_editor);
+
+               gtk_window_present (GTK_WINDOW (comp_editor));
+
+               g_object_unref (comp);
+
+               gtk_widget_destroy (GTK_WIDGET (async_context->composer));
+       }
+
+       e_content_editor_util_free_content_hash (content_hash);
+       async_context_free (async_context);
+       g_clear_error (&error);
+}
+
 static void
 action_composer_to_meeting_cb (GtkAction *action,
                               EMsgComposer *composer)
 {
-       ECalComponent *comp;
-       ECompEditor *comp_editor;
-       ECompEditorFlags flags;
+       EHTMLEditor *editor;
+       EContentEditor *cnt_editor;
+       EActivity *activity;
+       AsyncContext *async_context;
 
        g_return_if_fail (E_IS_MSG_COMPOSER (composer));
 
        if (!e_util_prompt_user (GTK_WINDOW (composer), NULL, NULL, 
"mail-composer:prompt-composer-to-meeting", NULL))
                return;
 
-       comp = composer_to_meeting_component (composer);
-       if (!comp)
-               return;
-
-       flags = E_COMP_EDITOR_FLAG_IS_NEW |
-               E_COMP_EDITOR_FLAG_ORGANIZER_IS_USER |
-               E_COMP_EDITOR_FLAG_WITH_ATTENDEES;
-
-       comp_editor = e_comp_editor_open_for_component (NULL, e_msg_composer_get_shell (composer),
-               NULL, e_cal_component_get_icalcomponent (comp), flags);
-
-       /* Attachments */
-       composer_to_meeting_copy_attachments (composer, comp_editor);
+       editor = e_msg_composer_get_editor (composer);
+       cnt_editor = e_html_editor_get_content_editor (editor);
 
-       gtk_window_present (GTK_WINDOW (comp_editor));
+       activity = e_html_editor_new_activity (editor);
+       e_activity_set_text (activity, _("Reading text content…"));
 
-       g_object_unref (comp);
+       async_context = async_context_new (composer, activity);
 
-       gtk_widget_destroy (GTK_WIDGET (composer));
+       e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_TO_SEND_PLAIN, NULL,
+               e_activity_get_cancellable (activity),
+               compose_to_meeting_content_ready_cb, async_context);
 }
 
 static void
diff --git a/src/modules/webkit-editor/e-webkit-editor.c b/src/modules/webkit-editor/e-webkit-editor.c
index c133f8f12c..670fe30578 100644
--- a/src/modules/webkit-editor/e-webkit-editor.c
+++ b/src/modules/webkit-editor/e-webkit-editor.c
@@ -1775,147 +1775,150 @@ webkit_editor_insert_content (EContentEditor *editor,
                g_warning ("Unsupported flags combination (%d) in (%s)", flags, G_STRFUNC);
 }
 
-static CamelMimePart *
-create_part_for_inline_image_from_element_data (const gchar *element_src,
-                                                const gchar *name,
-                                                const gchar *id)
+static void
+webkit_editor_get_content (EContentEditor *editor,
+                          guint32 flags, /* bit-or of EContentEditorGetContentFlags */
+                          const gchar *inline_images_from_domain,
+                           GCancellable *cancellable,
+                          GAsyncReadyCallback callback,
+                          gpointer user_data)
 {
-       CamelStream *stream;
-       CamelDataWrapper *wrapper;
-       CamelMimePart *part = NULL;
-       gsize decoded_size;
-       gssize size;
-       gchar *mime_type = NULL;
-       const gchar *base64_encoded_data;
-       guchar *base64_decoded_data = NULL;
-
-       base64_encoded_data = strstr (element_src, ";base64,");
-       if (!base64_encoded_data)
-               goto out;
+       gchar *script, *cid_uid_prefix;
 
-       mime_type = g_strndup (
-               element_src + 5,
-               base64_encoded_data - (strstr (element_src, "data:") + 5));
+       g_return_if_fail (E_IS_WEBKIT_EDITOR (editor));
 
-       /* Move to actual data */
-       base64_encoded_data += 8;
+       cid_uid_prefix = camel_header_msgid_generate (inline_images_from_domain ? inline_images_from_domain : 
"");
+       script = e_web_view_jsc_printf_script ("EvoEditor.GetContent(%d, %s)", flags, cid_uid_prefix);
 
-       base64_decoded_data = g_base64_decode (base64_encoded_data, &decoded_size);
+       webkit_web_view_run_javascript (WEBKIT_WEB_VIEW (editor), script, cancellable, callback, user_data);
 
-       stream = camel_stream_mem_new ();
-       size = camel_stream_write (
-               stream, (gchar *) base64_decoded_data, decoded_size, NULL, NULL);
+       g_free (cid_uid_prefix);
+       g_free (script);
+}
 
-       if (size == -1)
-               goto out;
+static EContentEditorContentHash *
+webkit_editor_get_content_finish (EContentEditor *editor,
+                                 GAsyncResult *result,
+                                 GError **error)
+{
+       WebKitJavascriptResult *js_result;
+       EContentEditorContentHash *content_hash = NULL;
+       GError *local_error = NULL;
 
-       wrapper = camel_data_wrapper_new ();
-       camel_data_wrapper_construct_from_stream_sync (
-               wrapper, stream, NULL, NULL);
-       g_object_unref (stream);
+       g_return_val_if_fail (E_IS_WEBKIT_EDITOR (editor), NULL);
+       g_return_val_if_fail (result != NULL, NULL);
 
-       camel_data_wrapper_set_mime_type (wrapper, mime_type);
+       js_result = webkit_web_view_run_javascript_finish (WEBKIT_WEB_VIEW (editor), result, &local_error);
 
-       part = camel_mime_part_new ();
-       camel_medium_set_content (CAMEL_MEDIUM (part), wrapper);
-       g_object_unref (wrapper);
+       if (local_error) {
+               g_propagate_error (error, local_error);
 
-       camel_mime_part_set_content_id (part, id);
-       camel_mime_part_set_filename (part, name);
-       camel_mime_part_set_disposition (part, "inline");
-       camel_mime_part_set_encoding (part, CAMEL_TRANSFER_ENCODING_BASE64);
-out:
-       g_free (mime_type);
-       g_free (base64_decoded_data);
+               if (js_result)
+                       webkit_javascript_result_unref (js_result);
 
-       return part;
-}
+               return NULL;
+       }
 
-static GSList *
-webkit_editor_get_parts_for_inline_images (GVariant *images)
-{
-       const gchar *element_src, *name, *id;
-       GVariantIter *iter;
-       GSList *parts = NULL;
+       if (js_result) {
+               JSCException *exception;
+               JSCValue *value;
 
-       if (g_variant_check_format_string (images, "a(sss)", FALSE)) {
-               g_variant_get (images, "a(sss)", &iter);
-               while (g_variant_iter_loop (iter, "(&s&s&s)", &element_src, &name, &id)) {
-                       CamelMimePart *part;
+               value = webkit_javascript_result_get_js_value (js_result);
+               exception = jsc_context_get_exception (jsc_value_get_context (value));
 
-                       part = create_part_for_inline_image_from_element_data (
-                               element_src, name, id);
-                       parts = g_slist_prepend (parts, part);
+               if (exception) {
+                       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "EvoEditor.GetContent() call 
failed: %s", jsc_exception_get_message (exception));
+                       jsc_context_clear_exception (jsc_value_get_context (value));
+                       webkit_javascript_result_unref (js_result);
+                       return NULL;
                }
-               g_variant_iter_free (iter);
-       }
 
-       return parts ? g_slist_reverse (parts) : NULL;
-}
+               if (jsc_value_is_object (value)) {
+                       struct _formats {
+                               const gchar *name;
+                               guint32 flags;
+                       } formats[] = {
+                               { "raw-body-html", E_CONTENT_EDITOR_GET_RAW_BODY_HTML },
+                               { "raw-body-plain", E_CONTENT_EDITOR_GET_RAW_BODY_PLAIN },
+                               { "raw-body-stripped", E_CONTENT_EDITOR_GET_RAW_BODY_STRIPPED },
+                               { "raw-draft", E_CONTENT_EDITOR_GET_RAW_DRAFT },
+                               { "to-send-html", E_CONTENT_EDITOR_GET_TO_SEND_HTML },
+                               { "to-send-plain", E_CONTENT_EDITOR_GET_TO_SEND_PLAIN }
+                       };
+                       JSCValue *images_value;
+                       gint ii;
+
+                       content_hash = e_content_editor_util_new_content_hash ();
+
+                       for (ii = 0; ii < G_N_ELEMENTS (formats); ii++) {
+                               gchar *cnt;
+
+                               cnt = e_web_view_jsc_get_object_property_string (value, formats[ii].name, 
NULL);
+                               if (cnt)
+                                       e_content_editor_util_take_content_data (content_hash, 
formats[ii].flags, cnt, g_free);
+                       }
 
-static gchar *
-webkit_editor_get_content (EContentEditor *editor,
-                           EContentEditorGetContentFlags flags,
-                           const gchar *inline_images_from_domain,
-                           GSList **inline_images_parts)
-{
-       EWebKitEditor *wk_editor;
-       GVariant *result;
-       GError *local_error = NULL;
+                       images_value = jsc_value_object_get_property (value, "images");
 
-       wk_editor = E_WEBKIT_EDITOR (editor);
-       if (!wk_editor->priv->web_extension_proxy)
-               return NULL;
+                       if (images_value) {
+                               if (jsc_value_is_array (images_value)) {
+                                       GSList *image_parts = NULL;
+                                       gint length;
 
-       if ((flags & E_CONTENT_EDITOR_GET_TEXT_HTML) &&
-           !(flags & E_CONTENT_EDITOR_GET_PROCESSED) &&
-            !(flags & E_CONTENT_EDITOR_GET_BODY))
-               e_util_invoke_g_dbus_proxy_call_with_error_check (
-                       wk_editor->priv->web_extension_proxy,
-                       "DOMEmbedStyleSheet",
-                       g_variant_new (
-                               "(ts)",
-                               current_page_id (wk_editor),
-                               wk_editor->priv->current_user_stylesheet),
-                       wk_editor->priv->cancellable);
+                                       length = e_web_view_jsc_get_object_property_int32 (images_value, 
"length", 0);
 
-       result = e_util_invoke_g_dbus_proxy_call_sync_wrapper (
-               wk_editor->priv->web_extension_proxy,
-               "DOMGetContent",
-               g_variant_new (
-                       "(tsi)",
-                       current_page_id (wk_editor),
-                       inline_images_from_domain ? inline_images_from_domain : "",
-                       (gint32) flags),
-               wk_editor->priv->cancellable,
-               &local_error);
+                                       for (ii = 0; ii < length; ii++) {
+                                               JSCValue *item_value;
 
-       webkit_editor_set_last_error (wk_editor, local_error);
-       g_clear_error (&local_error);
+                                               item_value = jsc_value_object_get_property_at_index 
(images_value, ii);
 
-       if ((flags & E_CONTENT_EDITOR_GET_TEXT_HTML) &&
-           !(flags & E_CONTENT_EDITOR_GET_PROCESSED) &&
-            !(flags & E_CONTENT_EDITOR_GET_BODY))
-               webkit_editor_call_simple_extension_function (
-                       wk_editor, "DOMRemoveEmbeddedStyleSheet");
+                                               if (!item_value ||
+                                                   jsc_value_is_null (item_value) ||
+                                                   jsc_value_is_undefined (item_value)) {
+                                                       g_warn_if_reached ();
+                                                       g_clear_object (&item_value);
+                                                       break;
+                                               }
 
-       if (result) {
-               GVariant *images = NULL;
-               gchar *value = NULL;
+                                               if (jsc_value_is_object (item_value)) {
+                                                       gchar *src, *cid;
 
-               g_variant_get (result, "(sv)", &value, &images);
-               if (inline_images_parts)
-                       *inline_images_parts = webkit_editor_get_parts_for_inline_images (images);
+                                                       src = e_web_view_jsc_get_object_property_string 
(item_value, "src", NULL);
+                                                       cid = e_web_view_jsc_get_object_property_string 
(item_value, "cid", NULL);
 
-               if (images)
-                       g_variant_unref (images);
+                                                       if (src && *src && cid && *cid) {
+                                                               CamelMimePart *part;
 
-               g_variant_unref (result);
+                                                               part = 
e_content_editor_util_create_data_mimepart (src, cid, TRUE, NULL, NULL,
+                                                                       E_WEBKIT_EDITOR 
(editor)->priv->cancellable);
+
+                                                               if (part)
+                                                                       image_parts = g_slist_prepend 
(image_parts, part);
+                                                       }
+
+                                                       g_free (src);
+                                                       g_free (cid);
+                                               }
+
+                                               g_clear_object (&item_value);
+                                       }
+
+                                       if (image_parts)
+                                               e_content_editor_util_take_content_data_images (content_hash, 
image_parts);
+                               } else {
+                                       g_warn_if_reached ();
+                               }
 
-               return value;
+                               g_clear_object (&images_value);
+                       }
+               } else {
+                       g_warn_if_reached ();
+               }
+
+               webkit_javascript_result_unref (js_result);
        }
 
-       return NULL;
+       return content_hash;
 }
 
 static gboolean
@@ -2397,60 +2400,38 @@ webkit_editor_insert_signature (EContentEditor *editor,
        return ret_val;
 }
 
-static guint
-webkit_editor_get_caret_position (EContentEditor *editor)
+static void
+webkit_editor_get_caret_position (EContentEditor *editor,
+                                 GCancellable *cancellable,
+                                 GAsyncReadyCallback callback,
+                                 gpointer user_data)
 {
        EWebKitEditor *wk_editor;
-       GVariant *result;
-       guint ret_val = 0;
-
-       wk_editor = E_WEBKIT_EDITOR (editor);
-
-       if (!wk_editor->priv->web_extension_proxy) {
-               printf ("EHTMLEditorWebExtension not ready at %s!\n", G_STRFUNC);
-               return 0;
-       }
 
-       result = e_util_invoke_g_dbus_proxy_call_sync_wrapper_with_error_check (
-               wk_editor->priv->web_extension_proxy,
-               "DOMGetCaretPosition",
-               g_variant_new ("(t)", current_page_id (wk_editor)),
-               NULL);
+       g_return_if_fail (E_IS_WEBKIT_EDITOR (editor));
 
-       if (result) {
-               g_variant_get (result, "(u)", &ret_val);
-               g_variant_unref (result);
-       }
+       wk_editor = E_WEBKIT_EDITOR (editor);
 
-       return ret_val;
+       /* TODO */
 }
 
-static guint
-webkit_editor_get_caret_offset (EContentEditor *editor)
+static gboolean
+webkit_editor_get_caret_position_finish (EContentEditor *editor,
+                                        GAsyncResult *result,
+                                        guint *out_position,
+                                        guint *out_offset,
+                                        GError **error)
 {
        EWebKitEditor *wk_editor;
-       GVariant *result;
-       guint ret_val = 0;
+       gboolean success = FALSE;
 
-       wk_editor = E_WEBKIT_EDITOR (editor);
+       g_return_val_if_fail (E_IS_WEBKIT_EDITOR (editor), FALSE);
 
-       if (!wk_editor->priv->web_extension_proxy) {
-               printf ("EHTMLEditorWebExtension not ready at %s!\n", G_STRFUNC);
-               return 0;
-       }
-
-       result = e_util_invoke_g_dbus_proxy_call_sync_wrapper_with_error_check (
-               wk_editor->priv->web_extension_proxy,
-               "DOMGetCaretOffset",
-               g_variant_new ("(t)", current_page_id (wk_editor)),
-               NULL);
+       wk_editor = E_WEBKIT_EDITOR (editor);
 
-       if (result) {
-               g_variant_get (result, "(u)", &ret_val);
-               g_variant_unref (result);
-       }
+       /* TODO */
 
-       return ret_val;
+       return success;
 }
 
 static void
@@ -6498,6 +6479,7 @@ e_webkit_editor_content_editor_init (EContentEditorInterface *iface)
        iface->update_styles = webkit_editor_update_styles;
        iface->insert_content = webkit_editor_insert_content;
        iface->get_content = webkit_editor_get_content;
+       iface->get_content_finish = webkit_editor_get_content_finish;
        iface->insert_image = webkit_editor_insert_image;
        iface->insert_image_from_mime_part = webkit_editor_insert_image_from_mime_part;
        iface->insert_emoticon = webkit_editor_insert_emoticon;
@@ -6525,7 +6507,7 @@ e_webkit_editor_content_editor_init (EContentEditorInterface *iface)
        iface->selection_restore = webkit_editor_selection_restore;
        iface->selection_wrap = webkit_editor_selection_wrap;
        iface->get_caret_position = webkit_editor_get_caret_position;
-       iface->get_caret_offset = webkit_editor_get_caret_offset;
+       iface->get_caret_position_finish = webkit_editor_get_caret_position_finish;
        iface->get_current_signature_uid =  webkit_editor_get_current_signature_uid;
        iface->is_ready = webkit_editor_is_ready;
        iface->insert_signature = webkit_editor_insert_signature;
diff --git a/src/plugins/external-editor/external-editor.c b/src/plugins/external-editor/external-editor.c
index 51d91109c3..2c838250df 100644
--- a/src/plugins/external-editor/external-editor.c
+++ b/src/plugins/external-editor/external-editor.c
@@ -56,9 +56,6 @@ static gboolean       key_press_cb                    (GtkWidget *widget,
                                                 GdkEventKey *event,
                                                 EMsgComposer *composer);
 
-/* used to track when the external editor is active */
-static GThread *editor_thread;
-
 gint e_plugin_lib_enable (EPlugin *ep, gint enable);
 
 gint
@@ -201,7 +198,7 @@ enable_composer_idle (gpointer user_data)
 struct ExternalEditorData {
        EMsgComposer *composer;
        gchar *content;
-       gint cursor_position, cursor_offset;
+       guint cursor_position, cursor_offset;
 };
 
 /* needed because the new thread needs to call g_idle_add () */
@@ -410,7 +407,60 @@ finished:
        return NULL;
 }
 
-static void launch_editor (GtkAction *action, EMsgComposer *composer)
+static void
+launch_editor_caret_position_ready_cb (GObject *source_object,
+                                      GAsyncResult *result,
+                                      gpointer user_data)
+{
+       struct ExternalEditorData *eed = user_data;
+       GThread *editor_thread;
+       GError *error = NULL;
+
+       g_return_if_fail (E_IS_CONTENT_EDITOR (source_object));
+       g_return_if_fail (eed != NULL);
+
+       if (!e_content_editor_get_caret_position_finish (E_CONTENT_EDITOR (source_object),
+               result, &eed->cursor_position, &eed->cursor_offset, &error)) {
+               g_warning ("%s: Failed to get caret position: %s", G_STRFUNC, error ? error->message : 
"Unknown error");
+       }
+
+       editor_thread = g_thread_new (NULL, external_editor_thread, eed);
+       g_thread_unref (editor_thread);
+       g_clear_error (&error);
+}
+
+static void
+launch_editor_content_ready_cb (GObject *source_object,
+                               GAsyncResult *result,
+                               gpointer user_data)
+{
+       struct ExternalEditorData *eed = user_data;
+       EContentEditor *cnt_editor;
+       EContentEditorContentHash *content_hash;
+       GError *error = NULL;
+
+       g_return_if_fail (E_IS_CONTENT_EDITOR (source_object));
+       g_return_if_fail (eed != NULL);
+
+       cnt_editor = E_CONTENT_EDITOR (source_object);
+
+       content_hash = e_content_editor_get_content_finish (cnt_editor, result, &error);
+
+       if (!content_hash)
+               g_warning ("%s: Faild to get content: %s", G_STRFUNC, error ? error->message : "Unknown 
error");
+
+       eed->content = content_hash ? e_content_editor_util_get_content_data (content_hash, 
E_CONTENT_EDITOR_GET_TO_SEND_PLAIN) : NULL;
+
+       e_content_editor_get_caret_position (cnt_editor, NULL,
+               launch_editor_caret_position_ready_cb, eed);
+
+       e_content_editor_util_free_content_hash (content_hash);
+       g_clear_error (&error);
+}
+
+static void
+launch_editor (GtkAction *action,
+              EMsgComposer *composer)
 {
        struct ExternalEditorData *eed;
        EHTMLEditor *editor;
@@ -435,16 +485,9 @@ static void launch_editor (GtkAction *action, EMsgComposer *composer)
 
        eed = g_new0 (struct ExternalEditorData, 1);
        eed->composer = g_object_ref (composer);
-       eed->content = e_content_editor_get_content (cnt_editor,
-               E_CONTENT_EDITOR_GET_TEXT_PLAIN |
-               E_CONTENT_EDITOR_GET_PROCESSED,
-               NULL, NULL);
-       eed->cursor_position = e_content_editor_get_caret_position (cnt_editor);
-       if (eed->cursor_position > 0)
-               eed->cursor_offset = e_content_editor_get_caret_offset (cnt_editor);
 
-       editor_thread = g_thread_new (NULL, external_editor_thread, eed);
-       g_thread_unref (editor_thread);
+       e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_TO_SEND_PLAIN, NULL, NULL,
+               launch_editor_content_ready_cb, eed);
 }
 
 static GtkActionEntry entries[] = {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]