Bug 1385905 - part2: HTMLEditRules::SplitParagraph() should insert normal <br> element rather than moz-<br> element if split element and/or new element is empty r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 01 Aug 2017 22:38:50 +0900
changeset 424506 0d9ff39ad0e1456b8868496c63987699994fac22
parent 424505 ce9e09bdf7e38bf6cf3abd54f9c323d85afc6f3e
child 424507 864d977c8bc473c05d3602d37b284a9df7c75b9a
push id1567
push userjlorenzo@mozilla.com
push dateThu, 02 Nov 2017 12:36:05 +0000
treeherdermozilla-release@e512c14a0406 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1385905
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1385905 - part2: HTMLEditRules::SplitParagraph() should insert normal <br> element rather than moz-<br> element if split element and/or new element is empty r=m_kato Currently, HTMLEditRules::SplitParagraph() inserts moz-<br> element when split element and/or new element causes empty line. However, PlaintextSerializer ignores moz-<br> elements. Therefore, empty lines which are created by SplitParagraph() will be removed when it's converted to plaintext. So, it should insert normal <br> element for placeholder of empty lines instead. Note that moz-<br> elements are appeared as normal <br> elements in the result of Element.innerHTML. Additionally, Chromium always inserts a <br> element for empty block elements which are created by Enter key press. Therefore, using normal <br> element in this case shouldn't cause any compatibility problems. MozReview-Commit-ID: FNV41zEFWqQ
editor/libeditor/HTMLEditRules.cpp
editor/libeditor/HTMLEditRules.h
editor/libeditor/TextEditRules.cpp
editor/libeditor/TextEditRules.h
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -6611,20 +6611,25 @@ HTMLEditRules::SplitParagraph(nsIDOMNode
   }
 
   // remove ID attribute on the paragraph we just created
   RefPtr<Element> rightElt = rightPara->AsElement();
   NS_ENSURE_STATE(mHTMLEditor);
   rv = mHTMLEditor->RemoveAttribute(rightElt, nsGkAtoms::id);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // check both halves of para to see if we need mozBR
-  rv = InsertMozBRIfNeeded(*leftPara);
+  // We need to ensure to both paragraphs visible even if they are empty.
+  // However, moz-<br> element isn't useful in this case because moz-<br>
+  // elements will be ignored by PlaintextSerializer.  Additionally,
+  // moz-<br> will be exposed as <br> with Element.innerHTML.  Therefore,
+  // we can use normal <br> elements for placeholder in this case.
+  // Note that Chromium also behaves so.
+  rv = InsertBRIfNeeded(*leftPara);
   NS_ENSURE_SUCCESS(rv, rv);
-  rv = InsertMozBRIfNeeded(*rightPara);
+  rv = InsertBRIfNeeded(*rightPara);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // selection to beginning of right hand para;
   // look inside any containers that are up front.
   nsCOMPtr<nsINode> rightParaNode = do_QueryInterface(rightPara);
   NS_ENSURE_STATE(mHTMLEditor && rightParaNode);
   nsCOMPtr<nsIDOMNode> child =
     GetAsDOMNode(mHTMLEditor->GetLeftmostChild(rightParaNode, true));
@@ -8231,31 +8236,37 @@ HTMLEditRules::UpdateDocChangeRange(nsRa
       rv = mDocChangeRange->SetEnd(endNode, endOffset);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
   return NS_OK;
 }
 
 nsresult
-HTMLEditRules::InsertMozBRIfNeeded(nsINode& aNode)
+HTMLEditRules::InsertBRIfNeededInternal(nsINode& aNode,
+                                        bool aInsertMozBR)
 {
   if (!IsBlockNode(aNode)) {
     return NS_OK;
   }
 
+  if (NS_WARN_IF(!mHTMLEditor)) {
+    return NS_ERROR_UNEXPECTED;
+  }
   bool isEmpty;
-  NS_ENSURE_STATE(mHTMLEditor);
   nsresult rv = mHTMLEditor->IsEmptyNode(&aNode, &isEmpty);
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
   if (!isEmpty) {
     return NS_OK;
   }
 
-  return CreateMozBR(aNode.AsDOMNode(), 0);
+  return aInsertMozBR ? CreateMozBR(aNode.AsDOMNode(), 0) :
+                        CreateBR(aNode.AsDOMNode(), 0);
 }
 
 NS_IMETHODIMP
 HTMLEditRules::WillCreateNode(const nsAString& aTag,
                               nsIDOMNode* aParent,
                               int32_t aPosition)
 {
   return NS_OK;
--- a/editor/libeditor/HTMLEditRules.h
+++ b/editor/libeditor/HTMLEditRules.h
@@ -166,16 +166,28 @@ protected:
   nsresult WillDeleteSelection(Selection* aSelection,
                                nsIEditor::EDirection aAction,
                                nsIEditor::EStripWrappers aStripWrappers,
                                bool* aCancel, bool* aHandled);
   nsresult DidDeleteSelection(Selection* aSelection,
                               nsIEditor::EDirection aDir,
                               nsresult aResult);
   nsresult InsertBRIfNeeded(Selection* aSelection);
+
+  /**
+   * Insert a normal <br> element or a moz-<br> element to aNode when
+   * aNode is a block and it has no children.
+   *
+   * @param aNode           Reference to a block parent.
+   * @param aInsertMozBR    true if this should insert a moz-<br> element.
+   *                        Otherwise, i.e., this should insert a normal <br>
+   *                        element, false.
+   */
+  nsresult InsertBRIfNeededInternal(nsINode& aNode, bool aInsertMozBR);
+
   mozilla::EditorDOMPoint GetGoodSelPointForNode(nsINode& aNode,
                                                  nsIEditor::EDirection aAction);
 
   /**
    * TryToJoinBlocks() tries to join two block elements.  The right element is
    * always joined to the left element.  If the elements are the same type and
    * not nested within each other, JoinNodesSmart() is called (example, joining
    * two list items together into one).  If the elements are not the same type,
@@ -397,17 +409,35 @@ protected:
    * table element is its own nearest table element ancestor.
    */
   bool InDifferentTableElements(nsIDOMNode* aNode1, nsIDOMNode* aNode2);
   bool InDifferentTableElements(nsINode* aNode1, nsINode* aNode2);
   nsresult RemoveEmptyNodes();
   nsresult SelectionEndpointInNode(nsINode* aNode, bool* aResult);
   nsresult UpdateDocChangeRange(nsRange* aRange);
   nsresult ConfirmSelectionInBody();
-  nsresult InsertMozBRIfNeeded(nsINode& aNode);
+
+  /**
+   * Insert normal <br> element into aNode when aNode is a block and it has
+   * no children.
+   */
+  nsresult InsertBRIfNeeded(nsINode& aNode)
+  {
+    return InsertBRIfNeededInternal(aNode, false);
+  }
+
+  /**
+   * Insert moz-<br> element (<br type="_moz">) into aNode when aNode is a
+   * block and it has no children.
+   */
+  nsresult InsertMozBRIfNeeded(nsINode& aNode)
+  {
+    return InsertBRIfNeededInternal(aNode, true);
+  }
+
   bool IsEmptyInline(nsINode& aNode);
   bool ListIsEmptyLine(nsTArray<OwningNonNull<nsINode>>& arrayOfNodes);
   nsresult RemoveAlignment(nsINode& aNode, const nsAString& aAlignType,
                            bool aChildrenOnly);
   nsresult MakeSureElemStartsOrEndsOnCR(nsINode& aNode, bool aStarts);
   enum class ContentsOnly { no, yes };
   nsresult AlignBlock(Element& aElement,
                       const nsAString& aAlignType, ContentsOnly aContentsOnly);
--- a/editor/libeditor/TextEditRules.cpp
+++ b/editor/libeditor/TextEditRules.cpp
@@ -1643,34 +1643,32 @@ TextEditRules::FillBufWithPWChars(nsAStr
   char16_t passwordChar = LookAndFeel::GetPasswordCharacter();
 
   aOutString->Truncate();
   for (int32_t i = 0; i < aLength; i++) {
     aOutString->Append(passwordChar);
   }
 }
 
-/**
- * CreateMozBR() puts a BR node with moz attribute at {inParent, inOffset}.
- */
 nsresult
-TextEditRules::CreateMozBR(nsIDOMNode* inParent,
-                           int32_t inOffset,
-                           nsIDOMNode** outBRNode)
+TextEditRules::CreateBRInternal(nsIDOMNode* inParent,
+                                int32_t inOffset,
+                                bool aMozBR,
+                                nsIDOMNode** outBRNode)
 {
   NS_ENSURE_TRUE(inParent, NS_ERROR_NULL_POINTER);
 
   nsCOMPtr<nsIDOMNode> brNode;
   NS_ENSURE_STATE(mTextEditor);
   nsresult rv = mTextEditor->CreateBR(inParent, inOffset, address_of(brNode));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // give it special moz attr
   nsCOMPtr<Element> brElem = do_QueryInterface(brNode);
-  if (brElem) {
+  if (aMozBR && brElem) {
     rv = mTextEditor->SetAttribute(brElem, nsGkAtoms::type,
                                    NS_LITERAL_STRING("_moz"));
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (outBRNode) {
     brNode.forget(outBRNode);
   }
--- a/editor/libeditor/TextEditRules.h
+++ b/editor/libeditor/TextEditRules.h
@@ -207,18 +207,43 @@ protected:
                                      int32_t aMaxLength,
                                      bool* aTruncated);
 
   /**
    * Remove IME composition text from password buffer.
    */
   void RemoveIMETextFromPWBuf(uint32_t& aStart, nsAString* aIMEString);
 
-  nsresult CreateMozBR(nsIDOMNode* inParent, int32_t inOffset,
-                       nsIDOMNode** outBRNode = nullptr);
+  /**
+   * Create a normal <br> element and insert it to aOffset at aParent.
+   *
+   * @param aParent     The parent node which will have new <br> element.
+   * @param aOffset     The offset in aParent where the new <br> element will
+   *                    be inserted.
+   * @param aOutBRNode  Returns created <br> element.
+   */
+  nsresult CreateBR(nsIDOMNode* aParent, int32_t aOffset,
+                    nsIDOMNode** aOutBRNode = nullptr)
+  {
+    return CreateBRInternal(aParent, aOffset, false, aOutBRNode);
+  }
+
+  /**
+   * Create a moz-<br> element and insert it to aOffset at aParent.
+   *
+   * @param aParent     The parent node which will have new <br> element.
+   * @param aOffset     The offset in aParent where the new <br> element will
+   *                    be inserted.
+   * @param aOutBRNode  Returns created <br> element.
+   */
+  nsresult CreateMozBR(nsIDOMNode* aParent, int32_t aOffset,
+                       nsIDOMNode** aOutBRNode = nullptr)
+  {
+    return CreateBRInternal(aParent, aOffset, true, aOutBRNode);
+  }
 
   void UndefineCaretBidiLevel(Selection* aSelection);
 
   nsresult CheckBidiLevelForDeletion(Selection* aSelection,
                                      nsIDOMNode* aSelNode,
                                      int32_t aSelOffset,
                                      nsIEditor::EDirection aAction,
                                      bool* aCancel);
@@ -234,16 +259,33 @@ protected:
   bool IsDisabled() const;
   bool IsMailEditor() const;
   bool DontEchoPassword() const;
 
 private:
   // Note that we do not refcount the editor.
   TextEditor* mTextEditor;
 
+  /**
+   * Create a normal <br> element or a moz-<br> element and insert it to
+   * aOffset at aParent.
+   *
+   * @param aParent     The parent node which will have new <br> element.
+   * @param aOffset     The offset in aParent where the new <br> element will
+   *                    be inserted.
+   * @param aMozBR      true if the caller wants to create a moz-<br> element.
+   *                    Otherwise, false.
+   * @param aOutBRNode  Returns created <br> element.
+   */
+  nsresult CreateBRInternal(nsIDOMNode* aParent,
+                            int32_t aOffset,
+                            bool aMozBR,
+                            nsIDOMNode** aOutBRNode = nullptr);
+
+
 protected:
   // A buffer we use to store the real value of password editors.
   nsString mPasswordText;
   // A buffer we use to track the IME composition string.
   nsString mPasswordIMEText;
   uint32_t mPasswordIMEIndex;
   // Magic node acts as placeholder in empty doc.
   nsCOMPtr<nsIContent> mBogusNode;