Bug 1574852 - part 80: Move `HTMLEditRules::AlignBlockContents()` to `HTMLEditor` r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Sun, 08 Sep 2019 05:28:56 +0000
changeset 492225 8c629098c9c9753a9fc81fee866735b8c96de883
parent 492224 4351a64b385e21772b047409a751fe6e712b57b5
child 492226 bc646246502b78044d2575a750db0047d07a67f2
push id94822
push usermasayuki@d-toybox.com
push dateMon, 09 Sep 2019 12:16:50 +0000
treeherderautoland@7a484d560816 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1574852
milestone71.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 1574852 - part 80: Move `HTMLEditRules::AlignBlockContents()` to `HTMLEditor` r=m_kato Differential Revision: https://phabricator.services.mozilla.com/D44784
editor/libeditor/HTMLEditRules.cpp
editor/libeditor/HTMLEditRules.h
editor/libeditor/HTMLEditor.h
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -6602,86 +6602,90 @@ nsresult HTMLEditRules::AlignInnerBlocks
   // Gather list of table cells or list items
   nsTArray<OwningNonNull<nsINode>> nodeArray;
   TableCellAndListItemFunctor functor;
   DOMIterator iter(aNode);
   iter.AppendList(functor, nodeArray);
 
   // Now that we have the list, align their contents as requested
   for (auto& node : nodeArray) {
-    nsresult rv = AlignBlockContents(*node, aAlignType);
+    MOZ_ASSERT(node->IsElement());
+    nsresult rv = MOZ_KnownLive(HTMLEditorRef())
+                      .AlignBlockContentsWithDivElement(
+                          MOZ_KnownLive(*node->AsElement()), aAlignType);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
-nsresult HTMLEditRules::AlignBlockContents(nsINode& aNode,
-                                           const nsAString& aAlignType) {
-  MOZ_ASSERT(IsEditorDataAvailable());
-
-  nsCOMPtr<nsIContent> firstChild =
-      HTMLEditorRef().GetFirstEditableChild(aNode);
-  if (!firstChild) {
-    // this cell has no content, nothing to align
+nsresult HTMLEditor::AlignBlockContentsWithDivElement(
+    Element& aBlockElement, const nsAString& aAlignType) {
+  MOZ_ASSERT(IsEditActionDataAvailable());
+
+  // XXX I don't understand why we should NOT align non-editable children
+  //     with modifying EDITABLE `<div>` element.
+  nsCOMPtr<nsIContent> firstEditableContent =
+      GetFirstEditableChild(aBlockElement);
+  if (!firstEditableContent) {
+    // This block has no editable content, nothing to align.
     return NS_OK;
   }
 
-  nsCOMPtr<nsIContent> lastChild = HTMLEditorRef().GetLastEditableChild(aNode);
-  if (firstChild == lastChild && firstChild->IsHTMLElement(nsGkAtoms::div)) {
-    // the cell already has a div containing all of its content: just
-    // act on this div.
-    nsresult rv =
-        MOZ_KnownLive(HTMLEditorRef())
-            .SetAttributeOrEquivalent(MOZ_KnownLive(firstChild->AsElement()),
-                                      nsGkAtoms::align, aAlignType, false);
-    if (NS_WARN_IF(!CanHandleEditAction())) {
+  // If there is only one editable content and it's a `<div>` element,
+  // just set `align` attribute of it.
+  nsCOMPtr<nsIContent> lastEditableContent =
+      GetLastEditableChild(aBlockElement);
+  if (firstEditableContent == lastEditableContent &&
+      firstEditableContent->IsHTMLElement(nsGkAtoms::div)) {
+    nsresult rv = SetAttributeOrEquivalent(
+        MOZ_KnownLive(firstEditableContent->AsElement()), nsGkAtoms::align,
+        aAlignType, false);
+    if (NS_WARN_IF(Destroyed())) {
+      return NS_ERROR_EDITOR_DESTROYED;
+    }
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetAttributeOrEquivalent() failed");
+    return rv;
+  }
+
+  // Otherwise, we need to insert a `<div>` element to set `align` attribute.
+  // XXX Don't insert the new `<div>` element until we set `align` attribute
+  //     for avoiding running mutation event listeners.
+  RefPtr<Element> divElement = CreateNodeWithTransaction(
+      *nsGkAtoms::div, EditorDOMPoint(&aBlockElement, 0));
+  if (NS_WARN_IF(Destroyed())) {
+    return NS_ERROR_EDITOR_DESTROYED;
+  }
+  if (NS_WARN_IF(!divElement)) {
+    return NS_ERROR_FAILURE;
+  }
+  nsresult rv =
+      SetAttributeOrEquivalent(divElement, nsGkAtoms::align, aAlignType, false);
+  if (NS_WARN_IF(Destroyed())) {
+    return NS_ERROR_EDITOR_DESTROYED;
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  // XXX This is tricky and does not work with mutation event listeners.
+  //     But I'm not sure what we should do if new content is inserted.
+  //     Anyway, I don't think that we should move editable contents
+  //     over non-editable contents.  Chrome does no do that.
+  while (lastEditableContent && (lastEditableContent != divElement)) {
+    nsresult rv = MoveNodeWithTransaction(*lastEditableContent,
+                                          EditorDOMPoint(divElement, 0));
+    if (NS_WARN_IF(Destroyed())) {
       return NS_ERROR_EDITOR_DESTROYED;
     }
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
-    return NS_OK;
-  }
-
-  // else we need to put in a div, set the alignment, and toss in all the
-  // children
-  RefPtr<Element> divElem = MOZ_KnownLive(HTMLEditorRef())
-                                .CreateNodeWithTransaction(
-                                    *nsGkAtoms::div, EditorDOMPoint(&aNode, 0));
-  if (NS_WARN_IF(!CanHandleEditAction())) {
-    return NS_ERROR_EDITOR_DESTROYED;
-  }
-  if (NS_WARN_IF(!divElem)) {
-    return NS_ERROR_FAILURE;
-  }
-  // set up the alignment on the div
-  nsresult rv = MOZ_KnownLive(HTMLEditorRef())
-                    .SetAttributeOrEquivalent(divElem, nsGkAtoms::align,
-                                              aAlignType, false);
-  if (NS_WARN_IF(!CanHandleEditAction())) {
-    return NS_ERROR_EDITOR_DESTROYED;
-  }
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-  // tuck the children into the end of the active div
-  while (lastChild && (lastChild != divElem)) {
-    nsresult rv =
-        MOZ_KnownLive(HTMLEditorRef())
-            .MoveNodeWithTransaction(*lastChild, EditorDOMPoint(divElem, 0));
-    if (NS_WARN_IF(!CanHandleEditAction())) {
-      return NS_ERROR_EDITOR_DESTROYED;
-    }
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-    lastChild = HTMLEditorRef().GetLastEditableChild(aNode);
+    lastEditableContent = GetLastEditableChild(aBlockElement);
   }
   return NS_OK;
 }
 
 EditActionResult HTMLEditor::MaybeDeleteTopMostEmptyAncestor(
     nsIContent& aStartContent, Element& aEditingHostElement,
     nsIEditor::EDirection aDirectionAndAmount) {
   MOZ_ASSERT(IsEditActionDataAvailable());
--- a/editor/libeditor/HTMLEditRules.h
+++ b/editor/libeditor/HTMLEditRules.h
@@ -211,31 +211,16 @@ class HTMLEditRules : public TextEditRul
    *                            to aAlignType.
    * @param aAlignType          New value of align attribute of <div>.
    */
   MOZ_CAN_RUN_SCRIPT
   MOZ_MUST_USE nsresult AlignInnerBlocks(nsINode& aNode,
                                          const nsAString& aAlignType);
 
   /**
-   * AlignBlockContents() sets align attribute of <div> element which is
-   * only child of aNode to aAlignType.  If aNode has 2 or more children or
-   * does not have a <div> element has only child, inserts a <div> element
-   * into aNode and move all children of aNode into the new <div> element.
-   *
-   * @param aNode               The node whose contents should be aligned
-   *                            to aAlignType.
-   * @param aAlignType          New value of align attribute of <div> which
-   *                            is only child of aNode.
-   */
-  MOZ_CAN_RUN_SCRIPT
-  MOZ_MUST_USE nsresult AlignBlockContents(nsINode& aNode,
-                                           const nsAString& aAlignType);
-
-  /**
    * AlignContentsAtSelection() aligns contents around Selection to aAlignType.
    * This creates AutoSelectionRestorer.  Therefore, even if this returns
    * NS_OK, CanHandleEditAction() may return false if the editor is destroyed
    * during restoring the Selection.  So, every caller needs to check if
    * CanHandleEditAction() returns true before modifying the DOM tree or
    * changing Selection.
    *
    * @param aAlignType          New align attribute value where the contents
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -2418,16 +2418,33 @@ class HTMLEditor final : public TextEdit
   MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE SplitRangeOffFromNodeResult
   HandleOutdentAtSelectionInternal();
 
   /**
    * HandleOutdentAtSelection() outdents contents around Selection.
    */
   MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE EditActionResult HandleOutdentAtSelection();
 
+  /**
+   * AlignBlockContentsWithDivElement() sets align attribute of <div> element
+   * which is only child of aBlockElement to aAlignType.  If aBlockElement
+   * has 2 or more children or does not have a `<div>` element, inserts a
+   * new `<div>` element into aBlockElement and move all children of
+   * aBlockElement into the new `<div>` element.
+   *
+   * @param aBlockElement       The element node whose contents should be
+   *                            aligned to aAlignType.  This should be
+   *                            an element which can have `<div>` element
+   *                            as its child.
+   * @param aAlignType          New value of align attribute of `<div>`
+   *                            element.
+   */
+  MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult AlignBlockContentsWithDivElement(
+      dom::Element& aBlockElement, const nsAString& aAlignType);
+
  protected:  // Called by helper classes.
   virtual void OnStartToHandleTopLevelEditSubAction(
       EditSubAction aEditSubAction, nsIEditor::EDirection aDirection) override;
   MOZ_CAN_RUN_SCRIPT
   virtual void OnEndHandlingTopLevelEditSubAction() override;
 
  protected:  // Shouldn't be used by friend classes
   virtual ~HTMLEditor();