Bug 1574852 - part 86: Move `HTMLEditRules::WillAlign()` and `HTMLEditRules::AlignContentsAtSelection()` to `HTMLEditor` r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Mon, 09 Sep 2019 04:57:50 +0000
changeset 492420 a25bf7589eb60de6044b38c8089236ae208c8492
parent 492419 220c2d3cd5e020d6a693cbc9756d297502ea9e76
child 492421 8edfd974fc63dcd13bd0b501bebdba963e439da9
push id36555
push usermalexandru@mozilla.com
push dateTue, 10 Sep 2019 09:53:18 +0000
treeherdermozilla-central@07f1d247491f [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 86: Move `HTMLEditRules::WillAlign()` and `HTMLEditRules::AlignContentsAtSelection()` to `HTMLEditor` r=m_kato And also this splits large 2 blocks of `HTMLEditRules::AlignContentsAtSelection()` to 2 methods. Differential Revision: https://phabricator.services.mozilla.com/D44790
editor/libeditor/HTMLEditRules.cpp
editor/libeditor/HTMLEditRules.h
editor/libeditor/HTMLEditor.cpp
editor/libeditor/HTMLEditor.h
editor/nsIHTMLEditor.idl
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -783,18 +783,16 @@ nsresult HTMLEditRules::WillDoAction(Edi
       *aCancel = result.Canceled();
       NS_WARNING_ASSERTION(result.Succeeded(),
                            "HandleDeleteSelection() failed");
       return result.Rv();
     case EditSubAction::eSetPositionToAbsolute:
       return WillAbsolutePosition(aCancel, aHandled);
     case EditSubAction::eSetPositionToStatic:
       return WillRemoveAbsolutePosition(aCancel, aHandled);
-    case EditSubAction::eSetOrClearAlignment:
-      return WillAlign(*aInfo.alignType, aCancel, aHandled);
     case EditSubAction::eInsertElement:
     case EditSubAction::eInsertQuotedText: {
       nsresult rv = MOZ_KnownLive(HTMLEditorRef()).WillInsert(aCancel);
       if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
         return NS_ERROR_EDITOR_DESTROYED;
       }
       NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed");
       return NS_OK;
@@ -808,16 +806,17 @@ nsresult HTMLEditRules::WillDoAction(Edi
     case EditSubAction::eCreateOrRemoveBlock:
     case EditSubAction::eIndent:
     case EditSubAction::eInsertHTMLSource:
     case EditSubAction::eInsertParagraphSeparator:
     case EditSubAction::eOutdent:
     case EditSubAction::eUndo:
     case EditSubAction::eRedo:
     case EditSubAction::eRemoveList:
+    case EditSubAction::eSetOrClearAlignment:
       MOZ_ASSERT_UNREACHABLE("This path should've been dead code");
       return NS_ERROR_UNEXPECTED;
     default:
       return TextEditRules::WillDoAction(aInfo, aCancel, aHandled);
   }
 }
 
 nsresult HTMLEditRules::DidDoAction(EditSubActionInfo& aInfo,
@@ -830,19 +829,16 @@ nsresult HTMLEditRules::DidDoAction(Edit
 
   switch (aInfo.mEditSubAction) {
     case EditSubAction::eInsertText:
     case EditSubAction::eInsertLineBreak:
     case EditSubAction::eInsertTextComingFromIME:
       return NS_OK;
     case EditSubAction::eDeleteSelectedContent:
       return DidDeleteSelection();
-    case EditSubAction::eSetOrClearAlignment:
-      return MOZ_KnownLive(HTMLEditorRef())
-          .MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
     case EditSubAction::eSetPositionToAbsolute: {
       nsresult rv =
           MOZ_KnownLive(HTMLEditorRef())
               .MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
       return DidAbsolutePosition();
@@ -855,16 +851,17 @@ nsresult HTMLEditRules::DidDoAction(Edit
     case EditSubAction::eCreateOrRemoveBlock:
     case EditSubAction::eIndent:
     case EditSubAction::eInsertHTMLSource:
     case EditSubAction::eInsertParagraphSeparator:
     case EditSubAction::eOutdent:
     case EditSubAction::eUndo:
     case EditSubAction::eRedo:
     case EditSubAction::eRemoveList:
+    case EditSubAction::eSetOrClearAlignment:
       MOZ_ASSERT_UNREACHABLE("This path should've been dead code");
       return NS_ERROR_UNEXPECTED;
     default:
       return TextEditRules::DidDoAction(aInfo, aResult);
   }
 }
 
 bool HTMLEditRules::DocumentIsEmpty() const {
@@ -6268,344 +6265,360 @@ bool HTMLEditor::IsEmptyBlockElement(Ele
   }
   bool isEmpty = true;
   nsresult rv =
       IsEmptyNode(&aElement, &isEmpty, aIgnoreSingleBR == IgnoreSingleBR::Yes);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "IsEmptyNode() failed");
   return NS_SUCCEEDED(rv) && isEmpty;
 }
 
-nsresult HTMLEditRules::WillAlign(const nsAString& aAlignType, bool* aCancel,
-                                  bool* aHandled) {
-  MOZ_ASSERT(IsEditorDataAvailable());
-  MOZ_ASSERT(aCancel && aHandled);
-
-  *aCancel = false;
-  *aHandled = false;
+EditActionResult HTMLEditor::AlignAsSubAction(const nsAString& aAlignType) {
+  MOZ_ASSERT(IsEditActionDataAvailable());
+
+  AutoPlaceholderBatch treatAsOneTransaction(*this);
+  AutoEditSubActionNotifier startToHandleEditSubAction(
+      *this, EditSubAction::eSetOrClearAlignment, nsIEditor::eNext);
+
+  EditActionResult result = CanHandleHTMLEditSubAction();
+  if (NS_WARN_IF(result.Failed()) || result.Canceled()) {
+    return result;
+  }
 
   // FYI: Ignore cancel result of WillInsert().
-  nsresult rv = MOZ_KnownLive(HTMLEditorRef()).WillInsert();
+  nsresult rv = WillInsert();
   if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
-    return NS_ERROR_EDITOR_DESTROYED;
-  }
-  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed");
+    return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+  }
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed, but ignored");
 
   if (!SelectionRefPtr()->IsCollapsed()) {
-    nsresult rv = MOZ_KnownLive(HTMLEditorRef())
-                      .MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
+    nsresult rv = MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
-  *aHandled = true;
+      return EditActionResult(rv);
+    }
+  }
+
+  // AlignContentsAtSelection() creates AutoSelectionRestorer.  Therefore,
+  // we need to check whether we've been destroyed or not even if it returns
+  // NS_OK.
   rv = AlignContentsAtSelection(aAlignType);
-  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED) ||
-      NS_WARN_IF(!CanHandleEditAction())) {
-    return NS_ERROR_EDITOR_DESTROYED;
+  if (NS_WARN_IF(Destroyed())) {
+    return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
   }
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-  return NS_OK;
-}
-
-nsresult HTMLEditRules::AlignContentsAtSelection(const nsAString& aAlignType) {
-  AutoSelectionRestorer restoreSelectionLater(HTMLEditorRef());
+    return EditActionHandled(rv);
+  }
+
+  rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
+  NS_WARNING_ASSERTION(
+      NS_SUCCEEDED(rv),
+      "MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() failed");
+  return EditActionHandled(rv);
+}
+
+nsresult HTMLEditor::AlignContentsAtSelection(const nsAString& aAlignType) {
+  AutoSelectionRestorer restoreSelectionLater(*this);
 
   // Convert the selection ranges into "promoted" selection ranges: This
   // basically just expands the range to include the immediate block parent,
   // and then further expands to include any ancestors whose children are all
   // in the range
   AutoTArray<OwningNonNull<nsINode>, 64> nodeArray;
-  nsresult rv =
-      MOZ_KnownLive(HTMLEditorRef())
-          .SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
-              nodeArray, EditSubAction::eSetOrClearAlignment,
-              HTMLEditor::CollectNonEditableNodes::Yes);
+  nsresult rv = SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
+      nodeArray, EditSubAction::eSetOrClearAlignment,
+      CollectNonEditableNodes::Yes);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // If we don't have any nodes, or we have only a single br, then we are
   // creating an empty alignment div.  We have to do some different things for
   // these.
-  bool emptyDiv = nodeArray.IsEmpty();
+  bool createEmptyDivElement = nodeArray.IsEmpty();
   if (nodeArray.Length() == 1) {
     OwningNonNull<nsINode> node = nodeArray[0];
 
     if (HTMLEditUtils::SupportsAlignAttr(*node)) {
       // The node is a table element, an hr, a paragraph, a div or a section
       // header; in HTML 4, it can directly carry the ALIGN attribute and we
       // don't need to make a div! If we are in CSS mode, all the work is done
       // in SetBlockElementAlign().
-      nsresult rv = MOZ_KnownLive(HTMLEditorRef())
-                        .SetBlockElementAlign(
-                            MOZ_KnownLive(*node->AsElement()), aAlignType,
-                            HTMLEditor::EditTarget::OnlyDescendantsExceptTable);
+      nsresult rv =
+          SetBlockElementAlign(MOZ_KnownLive(*node->AsElement()), aAlignType,
+                               EditTarget::OnlyDescendantsExceptTable);
       NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetBlockElementAlign() failed");
       return rv;
     }
 
     if (TextEditUtils::IsBreak(node)) {
-      // The special case emptyDiv code (below) that consumes BRs can cause
-      // tables to split if the start node of the selection is not in a table
-      // cell or caption, for example parent is a <tr>.  Avoid this unnecessary
-      // splitting if possible by leaving emptyDiv FALSE so that we fall
-      // through to the normal case alignment code.
+      // The special case createEmptyDivElement code (below) that consumes
+      // `<br>` elements can cause tables to split if the start node of the
+      // selection is not in a table cell or caption, for example parent is a
+      // `<tr>`.  Avoid this unnecessary splitting if possible by leaving
+      // createEmptyDivElement false so that we fall through to the normal case
+      // alignment code.
       //
-      // XXX: It seems a little error prone for the emptyDiv special case code
-      // to assume that the start node of the selection is the parent of the
-      // single node in the nodeArray, as the paragraph above points out. Do we
-      // rely on the selection start node because of the fact that nodeArray
-      // can be empty?  We should probably revisit this issue. - kin
+      // XXX: It seems a little error prone for the createEmptyDivElement
+      //      special case code to assume that the start node of the selection
+      //      is the parent of the single node in the nodeArray, as the
+      //      paragraph above points out. Do we rely on the selection start
+      //      node because of the fact that nodeArray can be empty?  We should
+      //      probably revisit this issue. - kin
 
       nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
       if (NS_WARN_IF(!firstRange)) {
         return NS_ERROR_FAILURE;
       }
       const RangeBoundary& atStartOfSelection = firstRange->StartRef();
       if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
         return NS_ERROR_FAILURE;
       }
       nsINode* parent = atStartOfSelection.Container();
-      emptyDiv = !HTMLEditUtils::IsTableElement(parent) ||
-                 HTMLEditUtils::IsTableCellOrCaption(*parent);
-    }
-  }
-  if (emptyDiv) {
-    nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
-    if (NS_WARN_IF(!firstRange)) {
-      return NS_ERROR_FAILURE;
-    }
-
-    EditorDOMPoint atStartOfSelection(firstRange->StartRef());
-    if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
-      return NS_ERROR_FAILURE;
-    }
-
-    SplitNodeResult splitNodeResult =
-        MOZ_KnownLive(HTMLEditorRef())
-            .MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div,
-                                                         atStartOfSelection);
-    if (NS_WARN_IF(splitNodeResult.Failed())) {
-      return splitNodeResult.Rv();
-    }
-
-    // Consume a trailing br, if any.  This is to keep an alignment from
-    // creating extra lines, if possible.
-    nsCOMPtr<nsIContent> brContent =
-        HTMLEditorRef().GetNextEditableHTMLNodeInBlock(
-            splitNodeResult.SplitPoint());
-    EditorDOMPoint pointToInsertDiv(splitNodeResult.SplitPoint());
-    if (brContent && TextEditUtils::IsBreak(brContent)) {
+      createEmptyDivElement = !HTMLEditUtils::IsTableElement(parent) ||
+                              HTMLEditUtils::IsTableCellOrCaption(*parent);
+    }
+  }
+
+  if (createEmptyDivElement) {
+    EditActionResult result =
+        AlignContentsAtSelectionWithEmptyDivElement(aAlignType);
+    NS_WARNING_ASSERTION(
+        result.Succeeded(),
+        "AlignContentsAtSelectionWithEmptyDivElement() failed");
+    if (result.Handled()) {
+      restoreSelectionLater.Abort();
+    }
+    return rv;
+  }
+
+  rv = AlignNodesAndDescendants(nodeArray, aAlignType);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AlignNodesAndDescendants() failed");
+  return rv;
+}
+
+EditActionResult HTMLEditor::AlignContentsAtSelectionWithEmptyDivElement(
+    const nsAString& aAlignType) {
+  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+
+  nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
+  if (NS_WARN_IF(!firstRange)) {
+    return EditActionResult(NS_ERROR_FAILURE);
+  }
+
+  EditorDOMPoint atStartOfSelection(firstRange->StartRef());
+  if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
+    return EditActionResult(NS_ERROR_FAILURE);
+  }
+
+  SplitNodeResult splitNodeResult = MaybeSplitAncestorsForInsertWithTransaction(
+      *nsGkAtoms::div, atStartOfSelection);
+  if (NS_WARN_IF(splitNodeResult.Failed())) {
+    return EditActionResult(splitNodeResult.Rv());
+  }
+
+  EditorDOMPoint pointToInsertDiv(splitNodeResult.SplitPoint());
+
+  // Consume a trailing br, if any.  This is to keep an alignment from
+  // creating extra lines, if possible.
+  if (nsCOMPtr<nsIContent> maybeBRContent =
+          GetNextEditableHTMLNodeInBlock(splitNodeResult.SplitPoint())) {
+    if (TextEditUtils::IsBreak(maybeBRContent) && pointToInsertDiv.GetChild()) {
       // Making use of html structure... if next node after where we are
       // putting our div is not a block, then the br we found is in same block
       // we are, so it's safe to consume it.
-      nsCOMPtr<nsIContent> sibling;
-      if (pointToInsertDiv.GetChild()) {
-        sibling =
-            HTMLEditorRef().GetNextHTMLSibling(pointToInsertDiv.GetChild());
-      }
-      if (sibling && !HTMLEditor::NodeIsBlockStatic(*sibling)) {
-        AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertDiv);
-        rv = MOZ_KnownLive(HTMLEditorRef())
-                 .DeleteNodeWithTransaction(*brContent);
-        if (NS_WARN_IF(!CanHandleEditAction())) {
-          return NS_ERROR_EDITOR_DESTROYED;
-        }
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          return rv;
-        }
-      }
-    }
-    RefPtr<Element> div =
-        MOZ_KnownLive(HTMLEditorRef())
-            .CreateNodeWithTransaction(*nsGkAtoms::div, pointToInsertDiv);
-    if (NS_WARN_IF(!CanHandleEditAction())) {
-      return NS_ERROR_EDITOR_DESTROYED;
-    }
-    if (NS_WARN_IF(!div)) {
-      return NS_ERROR_FAILURE;
-    }
-    // Remember our new block for postprocessing
-    HTMLEditorRef().TopLevelEditSubActionDataRef().mNewBlockElement = div;
-    // Set up the alignment on the div, using HTML or CSS
-    rv = MOZ_KnownLive(HTMLEditorRef())
-             .SetBlockElementAlign(
-                 *div, aAlignType,
-                 HTMLEditor::EditTarget::OnlyDescendantsExceptTable);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-    // Put in a padding <br> element for empty last line so that it won't get
-    // deleted.
-    CreateElementResult createPaddingBRResult =
-        MOZ_KnownLive(HTMLEditorRef())
-            .InsertPaddingBRElementForEmptyLastLineWithTransaction(
-                EditorDOMPoint(div, 0));
-    if (NS_WARN_IF(createPaddingBRResult.Failed())) {
-      return createPaddingBRResult.Rv();
-    }
-    EditorRawDOMPoint atStartOfDiv(div, 0);
-    // Don't restore the selection
-    restoreSelectionLater.Abort();
-    ErrorResult error;
-    SelectionRefPtr()->Collapse(atStartOfDiv, error);
-    if (NS_WARN_IF(!CanHandleEditAction())) {
-      error.SuppressException();
-      return NS_ERROR_EDITOR_DESTROYED;
-    }
-    if (NS_WARN_IF(error.Failed())) {
-      return error.StealNSResult();
-    }
-    return NS_OK;
-  }
-
-  // Next we detect all the transitions in the array, where a transition
-  // means that adjacent nodes in the array don't have the same parent.
-
+      if (nsIContent* nextEditableSibling =
+              GetNextHTMLSibling(pointToInsertDiv.GetChild())) {
+        if (!HTMLEditor::NodeIsBlockStatic(*nextEditableSibling)) {
+          AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertDiv);
+          nsresult rv = DeleteNodeWithTransaction(*maybeBRContent);
+          if (NS_WARN_IF(Destroyed())) {
+            return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+          }
+          if (NS_WARN_IF(NS_FAILED(rv))) {
+            return EditActionResult(rv);
+          }
+        }
+      }
+    }
+  }
+
+  RefPtr<Element> divElement =
+      CreateNodeWithTransaction(*nsGkAtoms::div, pointToInsertDiv);
+  if (NS_WARN_IF(Destroyed())) {
+    return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+  }
+  if (NS_WARN_IF(!divElement)) {
+    return EditActionResult(NS_ERROR_FAILURE);
+  }
+  // Remember our new block for postprocessing
+  TopLevelEditSubActionDataRef().mNewBlockElement = divElement;
+  // Set up the alignment on the div, using HTML or CSS
+  nsresult rv = SetBlockElementAlign(*divElement, aAlignType,
+                                     EditTarget::OnlyDescendantsExceptTable);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return EditActionResult(rv);
+  }
+  // Put in a padding <br> element for empty last line so that it won't get
+  // deleted.
+  CreateElementResult createPaddingBRResult =
+      InsertPaddingBRElementForEmptyLastLineWithTransaction(
+          EditorDOMPoint(divElement, 0));
+  if (NS_WARN_IF(createPaddingBRResult.Failed())) {
+    return EditActionResult(createPaddingBRResult.Rv());
+  }
+  EditorRawDOMPoint atStartOfDiv(divElement, 0);
+  ErrorResult error;
+  SelectionRefPtr()->Collapse(atStartOfDiv, error);
+  if (NS_WARN_IF(Destroyed())) {
+    error.SuppressException();
+    return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+  }
+  NS_WARNING_ASSERTION(!error.Failed(), "Selection::Collapse() failed");
+  return EditActionHandled(error.StealNSResult());
+}
+
+nsresult HTMLEditor::AlignNodesAndDescendants(
+    nsTArray<OwningNonNull<nsINode>>& aArrayOfNodes,
+    const nsAString& aAlignType) {
+  // Detect all the transitions in the array, where a transition means that
+  // adjacent nodes in the array don't have the same parent.
   AutoTArray<bool, 64> transitionList;
-  HTMLEditor::MakeTransitionList(nodeArray, transitionList);
+  HTMLEditor::MakeTransitionList(aArrayOfNodes, transitionList);
 
   // Okay, now go through all the nodes and give them an align attrib or put
   // them in a div, or whatever is appropriate.  Woohoo!
 
-  nsCOMPtr<Element> curDiv;
-  bool useCSS = HTMLEditorRef().IsCSSEnabled();
+  RefPtr<Element> createdDivElement;
+  bool useCSS = IsCSSEnabled();
   int32_t indexOfTransitionList = -1;
-  for (OwningNonNull<nsINode>& curNode : nodeArray) {
+  for (OwningNonNull<nsINode>& curNode : aArrayOfNodes) {
     ++indexOfTransitionList;
 
     // Ignore all non-editable nodes.  Leave them be.
-    if (!HTMLEditorRef().IsEditable(curNode)) {
+    if (!IsEditable(curNode)) {
       continue;
     }
 
     // The node is a table element, an hr, a paragraph, a div or a section
     // header; in HTML 4, it can directly carry the ALIGN attribute and we
     // don't need to nest it, just set the alignment.  In CSS, assign the
     // corresponding CSS styles in SetBlockElementAlign().
     if (HTMLEditUtils::SupportsAlignAttr(*curNode)) {
       nsresult rv =
-          MOZ_KnownLive(HTMLEditorRef())
-              .SetBlockElementAlign(
-                  MOZ_KnownLive(*curNode->AsElement()), aAlignType,
-                  HTMLEditor::EditTarget::NodeAndDescendantsExceptTable);
+          SetBlockElementAlign(MOZ_KnownLive(*curNode->AsElement()), aAlignType,
+                               EditTarget::NodeAndDescendantsExceptTable);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
-      // Clear out curDiv so that we don't put nodes after this one into it
-      curDiv = nullptr;
+      // Clear out createdDivElement so that we don't put nodes after this one
+      // into it
+      createdDivElement = nullptr;
       continue;
     }
 
     EditorDOMPoint atCurNode(curNode);
     if (NS_WARN_IF(!atCurNode.IsSet())) {
       continue;
     }
 
     // Skip insignificant formatting text nodes to prevent unnecessary
     // structure splitting!
     bool isEmptyTextNode = false;
-    if (curNode->GetAsText() &&
+    if (curNode->IsText() &&
         ((HTMLEditUtils::IsTableElement(atCurNode.GetContainer()) &&
           !HTMLEditUtils::IsTableCellOrCaption(*atCurNode.GetContainer())) ||
          HTMLEditUtils::IsList(atCurNode.GetContainer()) ||
-         (NS_SUCCEEDED(
-              HTMLEditorRef().IsEmptyNode(curNode, &isEmptyTextNode)) &&
+         (NS_SUCCEEDED(IsEmptyNode(curNode, &isEmptyTextNode)) &&
           isEmptyTextNode))) {
       continue;
     }
 
     // If it's a list item, or a list inside a list, forget any "current" div,
     // and instead put divs inside the appropriate block (td, li, etc.)
     if (HTMLEditUtils::IsListItem(curNode) || HTMLEditUtils::IsList(curNode)) {
       Element* listOrListItemElement = curNode->AsElement();
       AutoEditorDOMPointOffsetInvalidator lockChild(atCurNode);
-      rv = MOZ_KnownLive(HTMLEditorRef())
-               .RemoveAlignFromDescendants(
-                   MOZ_KnownLive(*listOrListItemElement), aAlignType,
-                   HTMLEditor::EditTarget::OnlyDescendantsExceptTable);
+      nsresult rv = RemoveAlignFromDescendants(
+          MOZ_KnownLive(*listOrListItemElement), aAlignType,
+          EditTarget::OnlyDescendantsExceptTable);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
+
       if (useCSS) {
-        HTMLEditorRef().mCSSEditUtils->SetCSSEquivalentToHTMLStyle(
+        mCSSEditUtils->SetCSSEquivalentToHTMLStyle(
             MOZ_KnownLive(listOrListItemElement), nullptr, nsGkAtoms::align,
             &aAlignType, false);
-        if (NS_WARN_IF(!CanHandleEditAction())) {
+        if (NS_WARN_IF(Destroyed())) {
           return NS_ERROR_EDITOR_DESTROYED;
         }
-        curDiv = nullptr;
+        createdDivElement = nullptr;
         continue;
       }
+
       if (HTMLEditUtils::IsList(atCurNode.GetContainer())) {
         // If we don't use CSS, add a content to list element: they have to
         // be inside another list, i.e., >= second level of nesting.
         // XXX AlignContentsInAllTableCellsAndListItems() handles only list
         //     item elements and table cells.  Is it intentional?  Why don't
         //     we need to align contents in other type blocks?
-        rv = MOZ_KnownLive(HTMLEditorRef())
-                 .AlignContentsInAllTableCellsAndListItems(
-                     MOZ_KnownLive(*listOrListItemElement), aAlignType);
+        nsresult rv = AlignContentsInAllTableCellsAndListItems(
+            MOZ_KnownLive(*listOrListItemElement), aAlignType);
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return rv;
         }
-        curDiv = nullptr;
+        createdDivElement = nullptr;
         continue;
       }
-      // Clear out curDiv so that we don't put nodes after this one into it
+
+      // Clear out createdDivElement so that we don't put nodes after this one
+      // into it
     }
 
     // Need to make a div to put things in if we haven't already, or if this
     // node doesn't go in div we used earlier.
-    if (!curDiv || transitionList[indexOfTransitionList]) {
+    if (!createdDivElement || transitionList[indexOfTransitionList]) {
       // First, check that our element can contain a div.
-      if (!HTMLEditorRef().CanContainTag(*atCurNode.GetContainer(),
-                                         *nsGkAtoms::div)) {
-        // Cancelled
+      if (!CanContainTag(*atCurNode.GetContainer(), *nsGkAtoms::div)) {
+        // XXX Why do we return NS_OK here rather than returning error or
+        //     doing continue?
         return NS_OK;
       }
 
       SplitNodeResult splitNodeResult =
-          MOZ_KnownLive(HTMLEditorRef())
-              .MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div,
-                                                           atCurNode);
+          MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div,
+                                                      atCurNode);
       if (NS_WARN_IF(splitNodeResult.Failed())) {
         return splitNodeResult.Rv();
       }
-      curDiv = MOZ_KnownLive(HTMLEditorRef())
-                   .CreateNodeWithTransaction(*nsGkAtoms::div,
-                                              splitNodeResult.SplitPoint());
-      if (NS_WARN_IF(!CanHandleEditAction())) {
+      createdDivElement = CreateNodeWithTransaction(
+          *nsGkAtoms::div, splitNodeResult.SplitPoint());
+      if (NS_WARN_IF(Destroyed())) {
         return NS_ERROR_EDITOR_DESTROYED;
       }
-      if (NS_WARN_IF(!curDiv)) {
+      if (NS_WARN_IF(!createdDivElement)) {
         return NS_ERROR_FAILURE;
       }
       // Remember our new block for postprocessing
-      HTMLEditorRef().TopLevelEditSubActionDataRef().mNewBlockElement = curDiv;
+      TopLevelEditSubActionDataRef().mNewBlockElement = createdDivElement;
       // Set up the alignment on the div
-      rv = MOZ_KnownLive(HTMLEditorRef())
-               .SetBlockElementAlign(
-                   *curDiv, aAlignType,
-                   HTMLEditor::EditTarget::OnlyDescendantsExceptTable);
+      nsresult rv =
+          SetBlockElementAlign(*createdDivElement, aAlignType,
+                               EditTarget::OnlyDescendantsExceptTable);
       if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
         return NS_ERROR_EDITOR_DESTROYED;
       }
       NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                            "SetBlockElementAlign() failed, but ignored");
     }
 
     // Tuck the node into the end of the active div
-    rv = MOZ_KnownLive(HTMLEditorRef())
-             .MoveNodeToEndWithTransaction(MOZ_KnownLive(*curNode->AsContent()),
-                                           *curDiv);
-    if (NS_WARN_IF(!CanHandleEditAction())) {
+    nsresult rv = MoveNodeToEndWithTransaction(
+        MOZ_KnownLive(*curNode->AsContent()), *createdDivElement);
+    if (NS_WARN_IF(Destroyed())) {
       return NS_ERROR_EDITOR_DESTROYED;
     }
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   return NS_OK;
--- a/editor/libeditor/HTMLEditRules.h
+++ b/editor/libeditor/HTMLEditRules.h
@@ -109,29 +109,16 @@ class HTMLEditRules : public TextEditRul
   /**
    * Called after deleting selected content.
    * This method removes unnecessary empty nodes and/or inserts <br> if
    * necessary.
    */
   MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult DidDeleteSelection();
 
   /**
-   * Called before aligning contents around Selection.  This method actually
-   * sets align attributes to align contents.
-   *
-   * @param aAlignType          New align attribute value where the contents
-   *                            should be aligned to.
-   * @param aCancel             Returns true if the operation is canceled.
-   * @param aHandled            Returns true if the edit action is handled.
-   */
-  MOZ_CAN_RUN_SCRIPT
-  nsresult WillAlign(const nsAString& aAlignType, bool* aCancel,
-                     bool* aHandled);
-
-  /**
    * Called before changing absolute positioned element to static positioned.
    * This method actually changes the position property of nearest absolute
    * positioned element.  Therefore, this might cause destroying the HTML
    * editor.
    *
    * @param aCancel             Returns true if the operation is canceled.
    * @param aHandled            Returns true if the edit action is handled.
    */
@@ -186,30 +173,16 @@ class HTMLEditRules : public TextEditRul
    * positioned.
    * This method actually changes the element which is computed by
    * WillAbsolutePosition() to absolute positioned.
    * Therefore, this might cause destroying the HTML editor.
    */
   MOZ_CAN_RUN_SCRIPT
   MOZ_MUST_USE nsresult DidAbsolutePosition();
 
-  /**
-   * 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
-   *                            should be aligned to.
-   */
-  MOZ_CAN_RUN_SCRIPT
-  MOZ_MUST_USE nsresult AlignContentsAtSelection(const nsAString& aAlignType);
-
   nsresult AppendInnerFormatNodes(nsTArray<OwningNonNull<nsINode>>& aArray,
                                   nsINode* aNode);
   nsresult GetFormatString(nsINode* aNode, nsAString& outFormat);
 
   /**
    * Called after handling edit action.  This may adjust Selection, remove
    * unnecessary empty nodes, create <br> elements if needed, etc.
    */
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -2200,38 +2200,19 @@ HTMLEditor::Align(const nsAString& aAlig
 nsresult HTMLEditor::AlignAsAction(const nsAString& aAlignType,
                                    nsIPrincipal* aPrincipal) {
   AutoEditActionDataSetter editActionData(
       *this, HTMLEditUtils::GetEditActionForAlignment(aAlignType), aPrincipal);
   if (NS_WARN_IF(!editActionData.CanHandle())) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
-  // Protect the edit rules object from dying
-  RefPtr<TextEditRules> rules(mRules);
-
-  AutoPlaceholderBatch treatAsOneTransaction(*this);
-  AutoEditSubActionNotifier startToHandleEditSubAction(
-      *this, EditSubAction::eSetOrClearAlignment, nsIEditor::eNext);
-
-  bool cancel, handled;
-
-  // Find out if the selection is collapsed:
-  EditSubActionInfo subActionInfo(EditSubAction::eSetOrClearAlignment);
-  subActionInfo.alignType = &aAlignType;
-  nsresult rv = rules->WillDoAction(subActionInfo, &cancel, &handled);
-  if (cancel || NS_FAILED(rv)) {
-    return EditorBase::ToGenericNSResult(rv);
-  }
-
-  rv = rules->DidDoAction(subActionInfo, rv);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return EditorBase::ToGenericNSResult(rv);
-  }
-  return NS_OK;
+  EditActionResult result = AlignAsSubAction(aAlignType);
+  NS_WARNING_ASSERTION(result.Succeeded(), "AlignAsSubAction() failed");
+  return EditorBase::ToGenericNSResult(result.Rv());
 }
 
 Element* HTMLEditor::GetElementOrParentByTagName(const nsAtom& aTagName,
                                                  nsINode* aNode) const {
   MOZ_ASSERT(&aTagName != nsGkAtoms::_empty);
 
   AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
   if (NS_WARN_IF(!editActionData.CanHandle())) {
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -254,18 +254,18 @@ class HTMLEditor final : public TextEdit
   MOZ_CAN_RUN_SCRIPT nsresult
   IndentAsAction(nsIPrincipal* aPrincipal = nullptr);
   MOZ_CAN_RUN_SCRIPT nsresult
   OutdentAsAction(nsIPrincipal* aPrincipal = nullptr);
 
   MOZ_CAN_RUN_SCRIPT nsresult SetParagraphFormatAsAction(
       const nsAString& aParagraphFormat, nsIPrincipal* aPrincipal = nullptr);
 
-  nsresult AlignAsAction(const nsAString& aAlignType,
-                         nsIPrincipal* aPrincipal = nullptr);
+  MOZ_CAN_RUN_SCRIPT nsresult AlignAsAction(const nsAString& aAlignType,
+                                            nsIPrincipal* aPrincipal = nullptr);
 
   MOZ_CAN_RUN_SCRIPT nsresult RemoveListAsAction(
       const nsAString& aListType, nsIPrincipal* aPrincipal = nullptr);
 
   /**
    * MakeOrChangeListAsAction() makes selected hard lines list element(s).
    *
    * @param aListElementTagName         The new list element tag name.  Must be
@@ -2518,16 +2518,62 @@ class HTMLEditor final : public TextEdit
    *                            descendants of aBlockOrHRElement.
    *                            If `NodeAndDescendantsExceptTable`, modifies
    *                            aBlockOrHRElement and its descendants.
    */
   MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult
   SetBlockElementAlign(Element& aBlockOrHRElement, const nsAString& aAlignType,
                        EditTarget aEditTarget);
 
+  /**
+   * AlignContentsAtSelectionWithEmptyDivElement() inserts new `<div>` element
+   * at `Selection` to align selected contents.  This returns as "handled"
+   * if this modifies `Selection` so that callers shouldn't modify `Selection`
+   * in such case especially when using AutoSelectionRestorer.
+   *
+   * @param aAlignType          New align attribute value where the contents
+   *                            should be aligned to.
+   */
+  MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE EditActionResult
+  AlignContentsAtSelectionWithEmptyDivElement(const nsAString& aAlignType);
+
+  /**
+   * AlignNodesAndDescendants() make contents of nodes in aArrayOfNodes and
+   * their descendants aligned to aAlignType.
+   *
+   * @param aAlignType          New align attribute value where the contents
+   *                            should be aligned to.
+   */
+  MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult
+  AlignNodesAndDescendants(nsTArray<OwningNonNull<nsINode>>& aArrayOfNodes,
+                           const nsAString& aAlignType);
+
+  /**
+   * AlignContentsAtSelection() aligns contents around Selection to aAlignType.
+   * This creates AutoSelectionRestorer.  Therefore, even if this returns
+   * NS_OK, we might have been destroyed.  So, every caller needs to check if
+   * Destroyed() returns false before modifying the DOM tree or changing
+   * Selection.
+   * NOTE: Call AlignAsSubAction() instead.
+   *
+   * @param aAlignType          New align attribute value where the contents
+   *                            should be aligned to.
+   */
+  MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult
+  AlignContentsAtSelection(const nsAString& aAlignType);
+
+  /**
+   * AlignAsSubAction() handles "align" command with `Selection`.
+   *
+   * @param aAlignType          New align attribute value where the contents
+   *                            should be aligned to.
+   */
+  MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE EditActionResult
+  AlignAsSubAction(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();
--- a/editor/nsIHTMLEditor.idl
+++ b/editor/nsIHTMLEditor.idl
@@ -302,16 +302,17 @@ interface nsIHTMLEditor : nsISupports
    */
   [can_run_script]
   void indent(in AString aIndent);
 
   /**
    * Document me!
    *
    */
+  [can_run_script]
   void  align(in AString aAlign);
 
   /**
    * GetElementOrParentByTagName() looks for an element node whose name matches
    * aTagName from aNode or anchor node of Selection to <body> element.
    *
    * @param aTagName        The tag name which you want to look for.
    *                        Must not be empty string.