Bug 1231902 - Fix nsMsgCompose::SetIdentity() to replace signature better. r=aceman a=jorgk
authorJorg K
Sat, 13 Feb 2016 08:51:27 +0100
changeset 26768 f44ae06d861353ed780afbcac0434774e74c0ba7
parent 26767 3b1107f78560cef8b6f09708380778dee1f5cc60
child 26769 fa693e739abc79b46893d80dc9614201606beee5
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)
reviewersaceman, jorgk
bugs1231902
Bug 1231902 - Fix nsMsgCompose::SetIdentity() to replace signature better. r=aceman a=jorgk
mailnews/compose/src/nsMsgCompose.cpp
mailnews/compose/src/nsMsgCompose.h
--- a/mailnews/compose/src/nsMsgCompose.cpp
+++ b/mailnews/compose/src/nsMsgCompose.cpp
@@ -522,17 +522,16 @@ nsMsgCompose::InsertDivWrappedTextAtSele
                                               const nsAString &classStr)
 {
   NS_ASSERTION(m_editor, "InsertDivWrappedTextAtSelection called, but no editor exists\n");
   if (!m_editor)
     return;
 
   nsCOMPtr<nsIDOMElement> divElem;
   nsCOMPtr<nsIHTMLEditor> htmlEditor(do_QueryInterface(m_editor));
-  nsCOMPtr<nsIPlaintextEditor> textEditor(do_QueryInterface(m_editor));
 
   nsresult rv = htmlEditor->CreateElementWithDefaults(NS_LITERAL_STRING("div"),
                                                       getter_AddRefs(divElem));
 
   NS_ENSURE_SUCCESS_VOID(rv);
 
   nsCOMPtr<nsIDOMNode> divNode (do_QueryInterface(divElem));
 
@@ -4421,22 +4420,22 @@ nsMsgCompose::BuildBodyMessageAndSignatu
   // Now, we have the body so we can just blast it into the
   // composition editor window.
   //
   nsAutoString   body;
   m_compFields->GetBody(body);
 
   /* Some time we want to add a signature and sometime we wont. Let's figure that now...*/
   bool addSignature;
-  bool addDashes = false;
+  bool isQuoted = false;
   switch (mType)
   {
     case nsIMsgCompType::ForwardInline :
       addSignature = true;
-      addDashes = true;
+      isQuoted = true;
       break;
     case nsIMsgCompType::New :
     case nsIMsgCompType::MailToUrl :    /* same as New */
     case nsIMsgCompType::Reply :        /* should not happen! but just in case */
     case nsIMsgCompType::ReplyAll :       /* should not happen! but just in case */
     case nsIMsgCompType::ReplyToList :    /* should not happen! but just in case */
     case nsIMsgCompType::ForwardAsAttachment :  /* should not happen! but just in case */
     case nsIMsgCompType::NewsPost :
@@ -4454,17 +4453,17 @@ nsMsgCompose::BuildBodyMessageAndSignatu
 
     default :
       addSignature = false;
       break;
   }
 
   nsAutoString tSignature;
   if (addSignature)
-    ProcessSignature(m_identity, addDashes, &tSignature);
+    ProcessSignature(m_identity, isQuoted, &tSignature);
 
   // if type is new, but we have body, this is probably a mapi send, so we need to
   // replace '\n' with <br> so that the line breaks won't be lost by html.
   // if mailtourl, do the same.
   if (m_composeHTML && (mType == nsIMsgCompType::New || mType == nsIMsgCompType::MailToUrl))
     MsgReplaceSubstring(body, NS_LITERAL_STRING("\n"), NS_LITERAL_STRING("<br>"));
 
   // Restore flowed text wrapping for Drafts/Templates.
@@ -5374,41 +5373,149 @@ nsMsgCompose::BodyConvertible(int32_t *_
 NS_IMETHODIMP
 nsMsgCompose::GetIdentity(nsIMsgIdentity **aIdentity)
 {
   NS_ENSURE_ARG_POINTER(aIdentity);
   NS_IF_ADDREF(*aIdentity = m_identity);
   return NS_OK;
 }
 
+/**
+ * Position above the quote, that is either <blockquote> or
+ * <div class="moz-cite-prefix"> or <div class="moz-forward-container">
+ * in an inline-forwarded message.
+ */
+nsresult
+nsMsgCompose::MoveToAboveQuote(void)
+{
+  nsCOMPtr<nsIDOMElement> rootElement;
+  nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement));
+  if (NS_FAILED(rv) || !rootElement) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIDOMNode> node;
+  nsAutoString attributeName;
+  nsAutoString attributeValue;
+  nsAutoString tagLocalName;
+  attributeName.AssignLiteral("class");
+
+  rv = rootElement->GetFirstChild(getter_AddRefs(node));
+  while (NS_SUCCEEDED(rv) && node) {
+    nsCOMPtr<nsIDOMElement> element = do_QueryInterface(node);
+    if (element) {
+      // First check for <blockquote>. This will most likely not trigger
+      // since well-behaved quotes are preceded by a cite prefix.
+      node->GetLocalName(tagLocalName);
+      if (tagLocalName.EqualsLiteral("blockquote")) {
+        break;
+      }
+
+      // Get the class value.
+      element->GetAttribute(attributeName, attributeValue);
+
+      // Now check for the cite prefix, so an element with
+      // class="moz-cite-prefix".
+      if (attributeValue.Find("moz-cite-prefix", true) != kNotFound) {
+        break;
+      }
+
+      // Next check for forwarded content.
+      // In HTML, the forwarded part is inside an element with
+      // class="moz-forward-container".
+      if (attributeValue.Find("moz-forward-container", true) != kNotFound) {
+        break;
+      }
+
+      rv = node->GetNextSibling(getter_AddRefs(node));
+      if (NS_FAILED(rv) || !node) {
+        // No further siblings found, so we didn't find what we were looking for.
+        rv = NS_OK;
+        node = nullptr;
+        break;
+      }
+    }
+  }
+
+  // Now position. If no quote was found, we position to the very front.
+  int32_t offset = 0;
+  if (node) {
+    rv = GetChildOffset(node, rootElement, offset);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  }
+  nsCOMPtr<nsISelection> selection;
+  m_editor->GetSelection(getter_AddRefs(selection));
+  if (selection)
+    rv = selection->Collapse(rootElement, offset);
+
+  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)
+{
+  int32_t offset;
+  nsCOMPtr<nsIDOMElement> rootElement;
+  nsCOMPtr<nsIDOMNode> lastNode;
+  nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement));
+  if (NS_FAILED(rv) || !rootElement) {
+    return rv;
+  }
+
+  rv = rootElement->GetLastChild(getter_AddRefs(lastNode));
+  if (NS_FAILED(rv) || !lastNode) {
+    return rv;
+  }
+
+  rv = GetChildOffset(lastNode, rootElement, offset);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  nsCOMPtr<nsISelection> selection;
+  m_editor->GetSelection(getter_AddRefs(selection));
+  if (selection)
+    rv = selection->Collapse(rootElement, offset + 1);
+
+  return rv;
+}
+
 NS_IMETHODIMP
 nsMsgCompose::SetIdentity(nsIMsgIdentity *aIdentity)
 {
   NS_ENSURE_ARG_POINTER(aIdentity);
 
   m_identity = aIdentity;
 
   nsresult rv;
 
   if (! m_editor)
     return NS_ERROR_FAILURE;
 
   nsCOMPtr<nsIDOMElement> rootElement;
   rv = m_editor->GetRootElement(getter_AddRefs(rootElement));
-  if (NS_FAILED(rv) || nullptr == rootElement)
+  if (NS_FAILED(rv) || !rootElement)
     return rv;
 
   //First look for the current signature, if we have one
   nsCOMPtr<nsIDOMNode> lastNode;
   nsCOMPtr<nsIDOMNode> node;
   nsCOMPtr<nsIDOMNode> tempNode;
   nsAutoString tagLocalName;
 
   rv = rootElement->GetLastChild(getter_AddRefs(lastNode));
-  if (NS_SUCCEEDED(rv) && nullptr != lastNode)
+  if (NS_SUCCEEDED(rv) && lastNode)
   {
     node = lastNode;
     // In html, the signature is inside an element with
     // class="moz-signature"
     bool signatureFound = false;
     nsAutoString attributeName;
     attributeName.AssignLiteral("class");
 
@@ -5436,17 +5543,17 @@ nsMsgCompose::SetIdentity(nsIMsgIdentity
       node->GetPreviousSibling(getter_AddRefs(tempNode));
       rv = m_editor->DeleteNode(node);
       if (NS_FAILED(rv))
       {
         m_editor->EndTransaction();
         return rv;
       }
 
-      //Also, remove the <br> right before the signature.
+      // Also, remove the <br> right before the signature.
       if (tempNode)
       {
         tempNode->GetLocalName(tagLocalName);
         if (tagLocalName.EqualsLiteral("br"))
           m_editor->DeleteNode(tempNode);
       }
       m_editor->EndTransaction();
     }
@@ -5454,59 +5561,59 @@ nsMsgCompose::SetIdentity(nsIMsgIdentity
 
   if (!CheckIncludeSignaturePrefs(aIdentity))
     return NS_OK;
 
   // Then add the new one if needed
   nsAutoString aSignature;
 
   // No delimiter needed if not a compose window
-  bool noDelimiter;
+  bool isQuoted;
   switch (mType)
   {
     case nsIMsgCompType::New :
     case nsIMsgCompType::NewsPost :
     case nsIMsgCompType::MailToUrl :
     case nsIMsgCompType::ForwardAsAttachment :
-      noDelimiter = false;
+      isQuoted = false;
       break;
     default :
-      noDelimiter = true;
+      isQuoted = true;
       break;
   }
 
-  ProcessSignature(aIdentity, noDelimiter, &aSignature);
+  ProcessSignature(aIdentity, isQuoted, &aSignature);
 
   if (!aSignature.IsEmpty())
   {
     TranslateLineEnding(aSignature);
 
     m_editor->BeginTransaction();
     int32_t reply_on_top = 0;
     bool sig_bottom = true;
     aIdentity->GetReplyOnTop(&reply_on_top);
     aIdentity->GetSigBottom(&sig_bottom);
     bool sigOnTop = (reply_on_top == 1 && !sig_bottom);
-    if (sigOnTop && noDelimiter)
-      m_editor->BeginningOfDocument();
-    else
-      m_editor->EndOfDocument();
-
-    nsCOMPtr<nsIHTMLEditor> htmlEditor (do_QueryInterface(m_editor));
-    nsCOMPtr<nsIPlaintextEditor> textEditor (do_QueryInterface(m_editor));
-
-    if (m_composeHTML)
-      rv = htmlEditor->InsertHTML(aSignature);
-    else {
-      rv = textEditor->InsertLineBreak();
-      InsertDivWrappedTextAtSelection(aSignature, NS_LITERAL_STRING("moz-signature"));
+    if (sigOnTop && isQuoted) {
+      rv = MoveToAboveQuote();
+    } else {
+      // Note: New messages aren't quoted so we always move to the end.
+      rv = MoveToEndOfDocument();
     }
 
-    if (sigOnTop && noDelimiter)
-      m_editor->EndOfDocument();
+    if (NS_SUCCEEDED(rv)) {
+      if (m_composeHTML) {
+        nsCOMPtr<nsIHTMLEditor> htmlEditor (do_QueryInterface(m_editor));
+        rv = htmlEditor->InsertHTML(aSignature);
+      } else {
+        nsCOMPtr<nsIPlaintextEditor> textEditor (do_QueryInterface(m_editor));
+        rv = textEditor->InsertLineBreak();
+        InsertDivWrappedTextAtSelection(aSignature, NS_LITERAL_STRING("moz-signature"));
+      }
+    }
     m_editor->EndTransaction();
   }
 
   return rv;
 }
 
 NS_IMETHODIMP nsMsgCompose::CheckCharsetConversion(nsIMsgIdentity *identity, char **fallbackCharset, bool *_retval)
 {
--- a/mailnews/compose/src/nsMsgCompose.h
+++ b/mailnews/compose/src/nsMsgCompose.h
@@ -80,16 +80,18 @@ protected:
   nsresult CreateMessage(const char * originalMsgURI, MSG_ComposeType type, nsIMsgCompFields* compFields);
   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 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.
    */