Bug 736584 - Move inline-forward DIV wrapping code out of cloudAttachmentLinkManager.js and into nsMsgCompose.cpp. r=mkmelin a=rkent THUNDERBIRD452b1_2016051723_RELBRANCH
authorJorg K
Fri, 19 Feb 2016 12:28:00 +0100
branchTHUNDERBIRD452b1_2016051723_RELBRANCH
changeset 27044 6fef5bf716007a217e32cc9a5ce65acdac99fc4a
parent 27043 bf436666bca1acfb2054e21db32f1eb965de1dcc
child 27045 00f00490e3b8fb1d1520fa65053c75c03db1368c
push id1850
push userclokep@gmail.com
push dateWed, 08 Mar 2017 19:29:12 +0000
treeherdercomm-esr52@028df196b2d9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmkmelin, rkent
bugs736584
Bug 736584 - Move inline-forward DIV wrapping code out of cloudAttachmentLinkManager.js and into nsMsgCompose.cpp. r=mkmelin a=rkent
mail/components/compose/content/cloudAttachmentLinkManager.js
mailnews/compose/src/nsMsgCompose.cpp
mailnews/compose/src/nsMsgCompose.h
mailnews/mime/src/mimedrft.cpp
mailnews/mime/src/nsStreamConverter.h
--- a/mail/components/compose/content/cloudAttachmentLinkManager.js
+++ b/mail/components/compose/content/cloudAttachmentLinkManager.js
@@ -134,52 +134,41 @@ var gCloudAttachmentLinkManager = {
       editor.enableUndo(true);
       editor.resetModificationCount();
     } else {
       document.getElementById("cmd_paragraphState").setAttribute("state", "");
     }
   },
 
   NotifyComposeBodyReadyForwardInline: function() {
-    // If we're doing an inline-forward, let's take all of the current
-    // message text, and wrap it up into its own DIV.
-    // This is kind of mad, since an added signature will also be wrapped
-    // into the DIV.
     let mailDoc = document.getElementById("content-frame").contentDocument;
     let mailBody = mailDoc.querySelector("body");
     let editor = GetCurrentEditor();
     let selection = editor.selection;
 
-    let container = editor.createElementWithDefaults("div");
-    container.setAttribute("class", "moz-forward-container");
-
     editor.enableUndo(false);
 
-    while (mailBody.hasChildNodes()) {
-      container.appendChild(mailBody.removeChild(mailBody.firstChild));
-    }
-
     // Control insertion of line breaks.
     selection.collapse(mailBody, 0);
     let useParagraph = Services.prefs.getBoolPref("editor.CR_creates_new_p");
     if (gMsgCompose.composeHTML && useParagraph) {
       let pElement = editor.createElementWithDefaults("p");
       let brElement = editor.createElementWithDefaults("br");
       pElement.appendChild(brElement);
-      editor.insertElementAtSelection(pElement,true);
+      editor.insertElementAtSelection(pElement, false);
       document.getElementById("cmd_paragraphState").setAttribute("state", "p");
     } else {
-      editor.insertLineBreak();
+      // insertLineBreak() has been observed to insert two <br> elements
+      // instead of one before a <div>, so we'll do it ourselves here.
+      let brElement = editor.createElementWithDefaults("br");
+      editor.insertElementAtSelection(brElement, false);
       document.getElementById("cmd_paragraphState").setAttribute("state", "");
     }
 
-    selection.collapse(mailBody, 1);
-    editor.insertElementAtSelection(container, false);
     editor.beginningOfDocument();
-
     editor.enableUndo(true);
     editor.resetModificationCount();
   },
 
   ComposeProcessDone: function() {},
   SaveInFolderDone: function() {},
 
   handleEvent: function(event) {
--- a/mailnews/compose/src/nsMsgCompose.cpp
+++ b/mailnews/compose/src/nsMsgCompose.cpp
@@ -71,16 +71,17 @@
 #include "nsIMutableArray.h"
 #include "nsArrayUtils.h"
 #include "nsIMsgWindow.h"
 #include "nsITextToSubURI.h"
 #include "nsIAbManager.h"
 #include "nsCRT.h"
 #include "mozilla/Services.h"
 #include "mozilla/mailnews/MimeHeaderParser.h"
+#include "nsStreamConverter.h"
 #include "nsISelection.h"
 #include "nsJSEnvironment.h"
 #include "nsIObserverService.h"
 
 using namespace mozilla::mailnews;
 
 static nsresult GetReplyHeaderInfo(int32_t* reply_header_type,
                                    nsString& reply_header_locale,
@@ -713,55 +714,137 @@ nsMsgCompose::ConvertAndLoadComposeWindo
         m_editor->EndOfDocument();
     }
   }
   else
   {
     if (aHTMLEditor && htmlEditor)
     {
       mInsertingQuotedContent = true;
-      htmlEditor->RebuildDocumentFromSource(aBuf);
+      if (isForwarded && Substring(aBuf, 0, sizeof(MIME_FORWARD_HTML_PREFIX)-1)
+                         .EqualsLiteral(MIME_FORWARD_HTML_PREFIX)) {
+        // We assign the opening tag inside "<HTML><BODY><BR><BR>" before the
+        // two <br> elements.
+        // This is a bit hacky but we know that the MIME code prepares the
+        // forwarded content like this:
+        // <HTML><BODY><BR><BR> + forwarded header + header table.
+        // Note: We only do this when we prepare the message to be forwarded,
+        // a re-opened saved draft of a forwarded message does not repeat this.
+        nsString newBody(aBuf);
+        nsString divTag;
+        divTag.AssignLiteral("<div class=\"moz-forward-container\">");
+        newBody.Insert(divTag, sizeof(MIME_FORWARD_HTML_PREFIX)-1-8);
+        htmlEditor->RebuildDocumentFromSource(newBody);
+      } else {
+        htmlEditor->RebuildDocumentFromSource(aBuf);
+      }
       mInsertingQuotedContent = false;
 
       // when forwarding a message as inline, tag any embedded objects
       // which refer to local images or files so we know not to include
       // send them
       if (isForwarded)
         (void)TagEmbeddedObjects(mailEditor);
 
       if (!aSignature.IsEmpty())
       {
-        if (isForwarded && sigOnTop)
-          m_editor->BeginningOfDocument();
-        else
-          m_editor->EndOfDocument();
+        if (isForwarded && sigOnTop) {
+          // Use our own function, nsEditor::BeginningOfDocument() would position
+          // into the <div class="moz-forward-container"> we've just created.
+          MoveToBeginningOfDocument();
+        } else {
+          // Use our own function, nsEditor::EndOfDocument() would position
+          // into the <div class="moz-forward-container"> we've just created.
+          MoveToEndOfDocument();
+        }
         htmlEditor->InsertHTML(aSignature);
         if (isForwarded && sigOnTop)
           m_editor->EndOfDocument();
       }
       else
         m_editor->EndOfDocument();
     }
     else if (htmlEditor)
     {
+      bool sigOnTopInserted = false;
       if (isForwarded && sigOnTop && !aSignature.IsEmpty())
       {
         textEditor->InsertLineBreak();
         InsertDivWrappedTextAtSelection(aSignature,
                                         NS_LITERAL_STRING("moz-signature"));
         m_editor->EndOfDocument();
+        sigOnTopInserted = true;
       }
 
       if (!aBuf.IsEmpty())
       {
-        if (mailEditor)
-          mailEditor->InsertTextWithQuotations(aBuf);
-        else
-          textEditor->InsertText(aBuf);
-        m_editor->EndOfDocument();
+        nsresult rv;
+
+        // Create a <div> of the required class.
+        nsCOMPtr<nsIDOMElement> divElem;
+        rv = htmlEditor->CreateElementWithDefaults(NS_LITERAL_STRING("div"),
+                                                   getter_AddRefs(divElem));
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        nsAutoString attributeName;
+        nsAutoString attributeValue;
+        attributeName.AssignLiteral("class");
+        attributeValue.AssignLiteral("moz-forward-container");
+        divElem->SetAttribute(attributeName, attributeValue);
+
+        // We can't insert an empty <div>, so fill it with something.
+        nsCOMPtr<nsIDOMElement> brElem;
+        nsCOMPtr<nsIDOMNode> resultNode;
+        rv = htmlEditor->CreateElementWithDefaults(NS_LITERAL_STRING("br"),
+                                                   getter_AddRefs(brElem));
+        NS_ENSURE_SUCCESS(rv, rv);
+        rv = divElem->AppendChild(brElem, getter_AddRefs(resultNode));
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        // Insert the non-empty <div> into the DOM.
+        rv = htmlEditor->InsertElementAtSelection(divElem, false);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        // Position into the div, so out content goes there.
+        nsCOMPtr<nsISelection> selection;
+        m_editor->GetSelection(getter_AddRefs(selection));
+        rv = selection->Collapse(divElem, 0);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        if (mailEditor) {
+          rv = mailEditor->InsertTextWithQuotations(aBuf);
+        } else {
+          // Will we ever get here?
+          rv = textEditor->InsertText(aBuf);
+        }
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        if (sigOnTopInserted) {
+          // Sadly the M-C editor inserts a <br> between the <div> for the signature
+          // and this <div>, so remove the <br> we don't want.
+          nsCOMPtr<nsIDOMNode> brBeforeDiv;
+          nsAutoString tagLocalName;
+          rv = divElem->GetPreviousSibling(getter_AddRefs(brBeforeDiv));
+          if (NS_SUCCEEDED(rv) && brBeforeDiv) {
+            brBeforeDiv->GetLocalName(tagLocalName);
+            if (tagLocalName.EqualsLiteral("br")) {
+              rv = m_editor->DeleteNode(brBeforeDiv);
+              NS_ENSURE_SUCCESS(rv, rv);
+            }
+          }
+        }
+
+        // Clean up the <br> we inserted.
+        rv = m_editor->DeleteNode(resultNode);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        // Use our own function instead of nsEditor::EndOfDocument() because
+        // we don't want to position at the end of the div we've just created.
+        rv = MoveToEndOfDocument();
+        NS_ENSURE_SUCCESS(rv, rv);
       }
 
       if ((!isForwarded || !sigOnTop) && !aSignature.IsEmpty()) {
         textEditor->InsertLineBreak();
         InsertDivWrappedTextAtSelection(aSignature,
                                         NS_LITERAL_STRING("moz-signature"));
       }
     }
@@ -5469,16 +5552,38 @@ nsMsgCompose::MoveToAboveQuote(void)
   m_editor->GetSelection(getter_AddRefs(selection));
   if (selection)
     rv = selection->Collapse(rootElement, offset);
 
   return rv;
 }
 
 /**
+ * nsEditor::BeginningOfDocument() will position to the beginning of the document
+ * before the first editable element. It will position into a container.
+ * We need to be at the very front.
+ */
+nsresult
+nsMsgCompose::MoveToBeginningOfDocument(void)
+{
+  nsCOMPtr<nsIDOMElement> rootElement;
+  nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement));
+  if (NS_FAILED(rv) || !rootElement) {
+    return rv;
+  }
+
+  nsCOMPtr<nsISelection> selection;
+  m_editor->GetSelection(getter_AddRefs(selection));
+  if (selection)
+    rv = selection->Collapse(rootElement, 0);
+
+  return rv;
+}
+
+/**
  * M-C's nsEditor::EndOfDocument() will position to the end of the document
  * but it will position into a container. We really need to position
  * after the last container so we don't accidentally position into a
  * <blockquote>. That's why we use our own function.
  */
 nsresult
 nsMsgCompose::MoveToEndOfDocument(void)
 {
--- a/mailnews/compose/src/nsMsgCompose.h
+++ b/mailnews/compose/src/nsMsgCompose.h
@@ -81,16 +81,17 @@ protected:
   void CleanUpRecipients(nsString& recipients);
   nsresult GetABDirectories(const nsACString& aDirUri,
                             nsCOMArray<nsIAbDirectory> &aDirArray);
   nsresult BuildMailListArray(nsIAbDirectory* parentDir,
                               nsTArray<nsMsgMailList>& array);
   nsresult TagConvertible(nsIDOMElement *node,  int32_t *_retval);
   nsresult _NodeTreeConvertible(nsIDOMElement *node, int32_t *_retval);
   nsresult MoveToAboveQuote(void);
+  nsresult MoveToBeginningOfDocument(void);
   nsresult MoveToEndOfDocument(void);
 
 // 3 = To, Cc, Bcc
 #define MAX_OF_RECIPIENT_ARRAY 3
   typedef nsTArray<nsMsgRecipient> RecipientsArray[MAX_OF_RECIPIENT_ARRAY];
   /**
    * This method parses the compose fields and associates email addresses with
    * the relevant cards from the address books.
--- a/mailnews/mime/src/mimedrft.cpp
+++ b/mailnews/mime/src/mimedrft.cpp
@@ -662,17 +662,17 @@ mime_insert_all_headers(char            
     MimeHeaders_build_heads_list(headers);
     headers->done_p = true;
   }
 
   nsCString replyHeader;
   MimeGetForwardHeaderDelimiter(replyHeader);
   if (htmlEdit)
   {
-    NS_MsgSACopy(&(newBody), "<HTML><BODY><BR><BR>");
+    NS_MsgSACopy(&(newBody), MIME_FORWARD_HTML_PREFIX);
     NS_MsgSACat(&newBody, replyHeader.get());
     NS_MsgSACat(&newBody, MIME_HEADER_TABLE);
   }
   else
   {
     NS_MsgSACopy(&(newBody), MSG_LINEBREAK MSG_LINEBREAK);
     NS_MsgSACat(&newBody, replyHeader.get());
   }
@@ -806,17 +806,17 @@ mime_insert_normal_headers(char         
   UnquoteMimeAddress(from, mailcharset);
   UnquoteMimeAddress(to, mailcharset);
   UnquoteMimeAddress(cc, mailcharset);
 
   nsCString replyHeader;
   MimeGetForwardHeaderDelimiter(replyHeader);
   if (htmlEdit)
   {
-    NS_MsgSACopy(&(newBody), "<HTML><BODY><BR><BR>");
+    NS_MsgSACopy(&(newBody), MIME_FORWARD_HTML_PREFIX);
     NS_MsgSACat(&newBody, replyHeader.get());
     NS_MsgSACat(&newBody, MIME_HEADER_TABLE);
   }
   else
   {
     NS_MsgSACopy(&(newBody), MSG_LINEBREAK MSG_LINEBREAK);
     NS_MsgSACat(&newBody, replyHeader.get());
   }
@@ -972,17 +972,17 @@ mime_insert_micro_headers(char          
   UnquoteMimeAddress(from, mailcharset);
   UnquoteMimeAddress(to, mailcharset);
   UnquoteMimeAddress(cc, mailcharset);
 
   nsCString replyHeader;
   MimeGetForwardHeaderDelimiter(replyHeader);
   if (htmlEdit)
   {
-    NS_MsgSACopy(&(newBody), "<HTML><BODY><BR><BR>");
+    NS_MsgSACopy(&(newBody), MIME_FORWARD_HTML_PREFIX);
     NS_MsgSACat(&newBody, replyHeader.get());
     NS_MsgSACat(&newBody, MIME_HEADER_TABLE);
   }
   else
   {
     NS_MsgSACopy(&(newBody), MSG_LINEBREAK MSG_LINEBREAK);
     NS_MsgSACat(&newBody, replyHeader.get());
   }
--- a/mailnews/mime/src/nsStreamConverter.h
+++ b/mailnews/mime/src/nsStreamConverter.h
@@ -10,16 +10,18 @@
 #include "nsIMimeEmitter.h"
 #include "nsIURI.h"
 #include "nsIAsyncInputStream.h"
 #include "nsIAsyncOutputStream.h"
 #include "nsIChannel.h"
 #include "nsStringGlue.h"
 #include "nsCOMPtr.h"
 
+#define MIME_FORWARD_HTML_PREFIX "<HTML><BODY><BR><BR>"
+
 class nsStreamConverter : public nsIStreamConverter, public nsIMimeStreamConverter {
 public:
   nsStreamConverter();
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
   // nsIMimeStreamConverter support
   NS_DECL_NSIMIMESTREAMCONVERTER