editor/libeditor/HTMLEditRules.cpp
author Noemi Erli <nerli@mozilla.com>
Wed, 20 Mar 2019 13:29:17 +0200
changeset 465181 25398e555020fef80c7b2a06a0d4c667e861cd6f
parent 464720 d011dfe8368374923cb69a0ab510d6814cbf3ab1
child 466084 1b38317f4237a11d18276e39567819b3f493c547
permissions -rw-r--r--
Backed out 3 changesets (bug 1533293) for causing Bug 1536595 a=backout Backed out changeset d011dfe83683 (bug 1533293) Backed out changeset e536f6e123d8 (bug 1533293) Backed out changeset 19cff61f4fed (bug 1533293)

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "HTMLEditRules.h"

#include <stdlib.h>

#include "HTMLEditUtils.h"
#include "TextEditUtils.h"
#include "WSRunObject.h"
#include "mozilla/Assertions.h"
#include "mozilla/ContentIterator.h"
#include "mozilla/CSSEditUtils.h"
#include "mozilla/EditAction.h"
#include "mozilla/EditorDOMPoint.h"
#include "mozilla/EditorUtils.h"
#include "mozilla/HTMLEditor.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Move.h"
#include "mozilla/Preferences.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/RangeBinding.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/mozalloc.h"
#include "nsAString.h"
#include "nsAlgorithm.h"
#include "nsCRT.h"
#include "nsCRTGlue.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsGkAtoms.h"
#include "nsAtom.h"
#include "nsHTMLDocument.h"
#include "nsIContent.h"
#include "nsID.h"
#include "nsIFrame.h"
#include "nsIHTMLAbsPosEditor.h"
#include "nsINode.h"
#include "nsLiteralString.h"
#include "nsRange.h"
#include "nsReadableUtils.h"
#include "nsString.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "nsTextNode.h"
#include "nsThreadUtils.h"
#include "nsUnicharUtils.h"
#include <algorithm>

// Workaround for windows headers
#ifdef SetProp
#  undef SetProp
#endif

class nsISupports;

namespace mozilla {

using namespace dom;

// const static char* kMOZEditorBogusNodeAttr="MOZ_EDITOR_BOGUS_NODE";
// const static char* kMOZEditorBogusNodeValue="TRUE";

enum { kLonely = 0, kPrevSib = 1, kNextSib = 2, kBothSibs = 3 };

/********************************************************
 *  first some helpful functors we will use
 ********************************************************/

static bool IsStyleCachePreservingSubAction(EditSubAction aEditSubAction) {
  return aEditSubAction == EditSubAction::eDeleteSelectedContent ||
         aEditSubAction == EditSubAction::eInsertLineBreak ||
         aEditSubAction == EditSubAction::eInsertParagraphSeparator ||
         aEditSubAction == EditSubAction::eCreateOrChangeList ||
         aEditSubAction == EditSubAction::eIndent ||
         aEditSubAction == EditSubAction::eOutdent ||
         aEditSubAction == EditSubAction::eSetOrClearAlignment ||
         aEditSubAction == EditSubAction::eCreateOrRemoveBlock ||
         aEditSubAction == EditSubAction::eRemoveList ||
         aEditSubAction == EditSubAction::eCreateOrChangeDefinitionList ||
         aEditSubAction == EditSubAction::eInsertElement ||
         aEditSubAction == EditSubAction::eInsertQuotation;
}

static nsAtom& ParagraphSeparatorElement(ParagraphSeparator separator) {
  switch (separator) {
    default:
      MOZ_FALLTHROUGH_ASSERT("Unexpected paragraph separator!");

    case ParagraphSeparator::div:
      return *nsGkAtoms::div;

    case ParagraphSeparator::p:
      return *nsGkAtoms::p;

    case ParagraphSeparator::br:
      return *nsGkAtoms::br;
  }
}

class TableCellAndListItemFunctor final : public BoolDomIterFunctor {
 public:
  // Used to build list of all li's, td's & th's iterator covers
  virtual bool operator()(nsINode* aNode) const override {
    return HTMLEditUtils::IsTableCell(aNode) ||
           HTMLEditUtils::IsListItem(aNode);
  }
};

class BRNodeFunctor final : public BoolDomIterFunctor {
 public:
  virtual bool operator()(nsINode* aNode) const override {
    return aNode->IsHTMLElement(nsGkAtoms::br);
  }
};

class EmptyEditableFunctor final : public BoolDomIterFunctor {
 public:
  explicit EmptyEditableFunctor(HTMLEditor* aHTMLEditor)
      : mHTMLEditor(aHTMLEditor) {}

  virtual bool operator()(nsINode* aNode) const override {
    if (mHTMLEditor->IsEditable(aNode) &&
        (HTMLEditUtils::IsListItem(aNode) ||
         HTMLEditUtils::IsTableCellOrCaption(*aNode))) {
      bool bIsEmptyNode;
      nsresult rv =
          mHTMLEditor->IsEmptyNode(aNode, &bIsEmptyNode, false, false);
      NS_ENSURE_SUCCESS(rv, false);
      if (bIsEmptyNode) {
        return true;
      }
    }
    return false;
  }

 protected:
  HTMLEditor* mHTMLEditor;
};

class MOZ_RAII AutoSetTemporaryAncestorLimiter final {
  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;

 public:
  explicit AutoSetTemporaryAncestorLimiter(
      HTMLEditor& aHTMLEditor, Selection& aSelection,
      nsINode& aStartPointNode MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
    MOZ_GUARD_OBJECT_NOTIFIER_INIT;

    if (aSelection.GetAncestorLimiter()) {
      return;
    }

    Element* root = aHTMLEditor.FindSelectionRoot(&aStartPointNode);
    if (root) {
      aHTMLEditor.InitializeSelectionAncestorLimit(*root);
      mSelection = &aSelection;
    }
  }

  ~AutoSetTemporaryAncestorLimiter() {
    if (mSelection) {
      mSelection->SetAncestorLimiter(nullptr);
    }
  }

 private:
  RefPtr<Selection> mSelection;
};

/********************************************************
 * mozilla::HTMLEditRules
 ********************************************************/

HTMLEditRules::HTMLEditRules()
    : mHTMLEditor(nullptr),
      mListenerEnabled(false),
      mReturnInEmptyLIKillsList(false),
      mDidDeleteSelection(false),
      mDidRangedDelete(false),
      mRestoreContentEditableCount(false),
      mJoinOffset(0) {
  mIsHTMLEditRules = true;
  InitFields();
}

void HTMLEditRules::InitFields() {
  mHTMLEditor = nullptr;
  mDocChangeRange = nullptr;
  mReturnInEmptyLIKillsList = true;
  mDidDeleteSelection = false;
  mDidRangedDelete = false;
  mRestoreContentEditableCount = false;
  mUtilRange = nullptr;
  mJoinOffset = 0;
  mNewBlock = nullptr;
  mRangeItem = new RangeItem();

  InitStyleCacheArray(mCachedStyles);
}

void HTMLEditRules::InitStyleCacheArray(
    StyleCache aStyleCache[SIZE_STYLE_TABLE]) {
  aStyleCache[0] = StyleCache(nsGkAtoms::b, nullptr);
  aStyleCache[1] = StyleCache(nsGkAtoms::i, nullptr);
  aStyleCache[2] = StyleCache(nsGkAtoms::u, nullptr);
  aStyleCache[3] = StyleCache(nsGkAtoms::font, nsGkAtoms::face);
  aStyleCache[4] = StyleCache(nsGkAtoms::font, nsGkAtoms::size);
  aStyleCache[5] = StyleCache(nsGkAtoms::font, nsGkAtoms::color);
  aStyleCache[6] = StyleCache(nsGkAtoms::tt, nullptr);
  aStyleCache[7] = StyleCache(nsGkAtoms::em, nullptr);
  aStyleCache[8] = StyleCache(nsGkAtoms::strong, nullptr);
  aStyleCache[9] = StyleCache(nsGkAtoms::dfn, nullptr);
  aStyleCache[10] = StyleCache(nsGkAtoms::code, nullptr);
  aStyleCache[11] = StyleCache(nsGkAtoms::samp, nullptr);
  aStyleCache[12] = StyleCache(nsGkAtoms::var, nullptr);
  aStyleCache[13] = StyleCache(nsGkAtoms::cite, nullptr);
  aStyleCache[14] = StyleCache(nsGkAtoms::abbr, nullptr);
  aStyleCache[15] = StyleCache(nsGkAtoms::acronym, nullptr);
  aStyleCache[16] = StyleCache(nsGkAtoms::backgroundColor, nullptr);
  aStyleCache[17] = StyleCache(nsGkAtoms::sub, nullptr);
  aStyleCache[18] = StyleCache(nsGkAtoms::sup, nullptr);
}

HTMLEditRules::~HTMLEditRules() {}

NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLEditRules, TextEditRules)

NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLEditRules, TextEditRules,
                                   mDocChangeRange, mUtilRange, mNewBlock,
                                   mRangeItem)

nsresult HTMLEditRules::Init(TextEditor* aTextEditor) {
  if (NS_WARN_IF(!aTextEditor) || NS_WARN_IF(!aTextEditor->AsHTMLEditor())) {
    return NS_ERROR_INVALID_ARG;
  }

  InitFields();

  mHTMLEditor = aTextEditor->AsHTMLEditor();
  if (NS_WARN_IF(!mHTMLEditor)) {
    return NS_ERROR_FAILURE;
  }

  AutoSafeEditorData setData(*this, *mHTMLEditor);

  nsresult rv = TextEditRules::Init(aTextEditor);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  if (NS_WARN_IF(!mHTMLEditor)) {
    return NS_ERROR_FAILURE;
  }

  // cache any prefs we care about
  static const char kPrefName[] =
      "editor.html.typing.returnInEmptyListItemClosesList";
  nsAutoCString returnInEmptyLIKillsList;
  Preferences::GetCString(kPrefName, returnInEmptyLIKillsList);

  // only when "false", becomes FALSE.  Otherwise (including empty), TRUE.
  // XXX Why was this pref designed as a string and not bool?
  mReturnInEmptyLIKillsList = !returnInEmptyLIKillsList.EqualsLiteral("false");

  // make a utility range for use by the listenter
  nsCOMPtr<nsINode> node = HTMLEditorRef().GetRoot();
  if (!node) {
    node = HTMLEditorRef().GetDocument();
    if (NS_WARN_IF(!node)) {
      return NS_ERROR_FAILURE;
    }
  }

  mUtilRange = new nsRange(node);

  // set up mDocChangeRange to be whole doc
  // temporarily turn off rules sniffing
  AutoLockRulesSniffing lockIt(this);
  if (!mDocChangeRange) {
    mDocChangeRange = new nsRange(node);
  }

  if (node->IsElement()) {
    ErrorResult error;
    mDocChangeRange->SelectNode(*node, error);
    if (NS_WARN_IF(error.Failed())) {
      return error.StealNSResult();
    }
    nsresult rv = InsertBRElementToEmptyListItemsAndTableCellsInChangedRange();
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rv),
        "Failed to insert <br> elements to empty list items and table cells");
  }

  StartToListenToEditSubActions();

  return NS_OK;
}

nsresult HTMLEditRules::DetachEditor() {
  EndListeningToEditSubActions();
  mHTMLEditor = nullptr;
  return TextEditRules::DetachEditor();
}

nsresult HTMLEditRules::BeforeEdit(EditSubAction aEditSubAction,
                                   nsIEditor::EDirection aDirection) {
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }

  if (mLockRulesSniffing) {
    return NS_OK;
  }

  AutoLockRulesSniffing lockIt(this);
  mDidExplicitlySetInterline = false;

  if (!mActionNesting) {
    mActionNesting++;

    // Clear our flag about if just deleted a range
    mDidRangedDelete = false;

    AutoSafeEditorData setData(*this, *mHTMLEditor);

    // Remember where our selection was before edit action took place:

    // Get the selection location
    if (!SelectionRefPtr()->RangeCount()) {
      return NS_ERROR_UNEXPECTED;
    }
    mRangeItem->StoreRange(SelectionRefPtr()->GetRangeAt(0));
    nsCOMPtr<nsINode> selStartNode = mRangeItem->mStartContainer;
    nsCOMPtr<nsINode> selEndNode = mRangeItem->mEndContainer;

    // Register with range updater to track this as we perturb the doc
    HTMLEditorRef().RangeUpdaterRef().RegisterRangeItem(mRangeItem);

    // Clear deletion state bool
    mDidDeleteSelection = false;

    // Clear out mDocChangeRange and mUtilRange
    if (mDocChangeRange) {
      // Clear out our accounting of what changed
      mDocChangeRange->Reset();
    }
    if (mUtilRange) {
      // Ditto for mUtilRange.
      mUtilRange->Reset();
    }

    // Remember current inline styles for deletion and normal insertion ops
    if (aEditSubAction == EditSubAction::eInsertText ||
        aEditSubAction == EditSubAction::eInsertTextComingFromIME ||
        aEditSubAction == EditSubAction::eDeleteSelectedContent ||
        IsStyleCachePreservingSubAction(aEditSubAction)) {
      nsCOMPtr<nsINode> selNode =
          aDirection == nsIEditor::eNext ? selEndNode : selStartNode;
      nsresult rv = CacheInlineStyles(selNode);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }

    // Stabilize the document against contenteditable count changes
    nsHTMLDocument* htmlDoc = HTMLEditorRef().GetHTMLDocument();
    if (NS_WARN_IF(!htmlDoc)) {
      return NS_ERROR_FAILURE;
    }
    if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) {
      htmlDoc->ChangeContentEditableCount(nullptr, +1);
      mRestoreContentEditableCount = true;
    }

    // Check that selection is in subtree defined by body node
    nsresult rv = ConfirmSelectionInBody();
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    // Let rules remember the top level action
    mTopLevelEditSubAction = aEditSubAction;
  }
  return NS_OK;
}

nsresult HTMLEditRules::AfterEdit(EditSubAction aEditSubAction,
                                  nsIEditor::EDirection aDirection) {
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }

  if (mLockRulesSniffing) {
    return NS_OK;
  }

  AutoLockRulesSniffing lockIt(this);

  MOZ_ASSERT(mActionNesting > 0);
  nsresult rv = NS_OK;
  mActionNesting--;
  if (!mActionNesting) {
    AutoSafeEditorData setData(*this, *mHTMLEditor);

    // Do all the tricky stuff
    rv = AfterEditInner(aEditSubAction, aDirection);
    // Perhaps, we need to do the following jobs even if the editor has been
    // destroyed since they adjust some states of HTML document but don't
    // modify the DOM tree nor Selection.

    // Free up selectionState range item
    HTMLEditorRef().RangeUpdaterRef().DropRangeItem(mRangeItem);

    // Reset the contenteditable count to its previous value
    if (mRestoreContentEditableCount) {
      nsHTMLDocument* htmlDoc = HTMLEditorRef().GetHTMLDocument();
      if (NS_WARN_IF(!htmlDoc)) {
        return NS_ERROR_FAILURE;
      }
      if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) {
        htmlDoc->ChangeContentEditableCount(nullptr, -1);
      }
      mRestoreContentEditableCount = false;
    }
  }

  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

nsresult HTMLEditRules::AfterEditInner(EditSubAction aEditSubAction,
                                       nsIEditor::EDirection aDirection) {
  MOZ_ASSERT(IsEditorDataAvailable());

  nsresult rv = ConfirmSelectionInBody();
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    return NS_ERROR_EDITOR_DESTROYED;
  }
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to normalize Selection");
  if (aEditSubAction == EditSubAction::eReplaceHeadWithHTMLSource ||
      aEditSubAction == EditSubAction::eCreateBogusNode) {
    return NS_OK;
  }

  nsCOMPtr<nsINode> rangeStartContainer, rangeEndContainer;
  uint32_t rangeStartOffset = 0, rangeEndOffset = 0;
  // do we have a real range to act on?
  bool bDamagedRange = false;
  if (mDocChangeRange) {
    rangeStartContainer = mDocChangeRange->GetStartContainer();
    rangeEndContainer = mDocChangeRange->GetEndContainer();
    rangeStartOffset = mDocChangeRange->StartOffset();
    rangeEndOffset = mDocChangeRange->EndOffset();
    if (rangeStartContainer && rangeEndContainer) {
      bDamagedRange = true;
    }
  }

  if (bDamagedRange && !((aEditSubAction == EditSubAction::eUndo) ||
                         (aEditSubAction == EditSubAction::eRedo))) {
    // don't let any txns in here move the selection around behind our back.
    // Note that this won't prevent explicit selection setting from working.
    AutoTransactionsConserveSelection dontChangeMySelection(HTMLEditorRef());

    // expand the "changed doc range" as needed
    PromoteRange(*mDocChangeRange, aEditSubAction);

    // if we did a ranged deletion or handling backspace key, make sure we have
    // a place to put caret.
    // Note we only want to do this if the overall operation was deletion,
    // not if deletion was done along the way for
    // EditSubAction::eInsertHTMLSource, EditSubAction::eInsertText, etc.
    // That's why this is here rather than DidDeleteSelection().
    if (aEditSubAction == EditSubAction::eDeleteSelectedContent &&
        mDidRangedDelete) {
      nsresult rv = InsertBRIfNeeded();
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }

    // add in any needed <br>s, and remove any unneeded ones.
    nsresult rv = InsertBRElementToEmptyListItemsAndTableCellsInChangedRange();
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rv),
        "Failed to insert <br> elements to empty list items and table cells");

    // merge any adjacent text nodes
    if (aEditSubAction != EditSubAction::eInsertText &&
        aEditSubAction != EditSubAction::eInsertTextComingFromIME) {
      nsresult rv = HTMLEditorRef().CollapseAdjacentTextNodes(mDocChangeRange);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }

    // clean up any empty nodes in the selection
    rv = RemoveEmptyNodesInChangedRange();
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    // attempt to transform any unneeded nbsp's into spaces after doing various
    // operations
    if (aEditSubAction == EditSubAction::eInsertText ||
        aEditSubAction == EditSubAction::eInsertTextComingFromIME ||
        aEditSubAction == EditSubAction::eDeleteSelectedContent ||
        aEditSubAction == EditSubAction::eInsertLineBreak ||
        aEditSubAction == EditSubAction::eInsertParagraphSeparator ||
        aEditSubAction == EditSubAction::ePasteHTMLContent ||
        aEditSubAction == EditSubAction::eInsertHTMLSource) {
      rv = AdjustWhitespace();
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      // also do this for original selection endpoints.
      NS_ENSURE_STATE(mRangeItem->mStartContainer);
      NS_ENSURE_STATE(mRangeItem->mEndContainer);
      WSRunObject(&HTMLEditorRef(), mRangeItem->mStartContainer,
                  mRangeItem->mStartOffset)
          .AdjustWhitespace();
      // we only need to handle old selection endpoint if it was different from
      // start
      if (mRangeItem->mStartContainer != mRangeItem->mEndContainer ||
          mRangeItem->mStartOffset != mRangeItem->mEndOffset) {
        WSRunObject(&HTMLEditorRef(), mRangeItem->mEndContainer,
                    mRangeItem->mEndOffset)
            .AdjustWhitespace();
      }
    }

    // if we created a new block, make sure selection lands in it
    if (mNewBlock) {
      rv = PinSelectionToNewBlock();
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                           "Failed to pin selection to the new block");
      mNewBlock = nullptr;
    }

    // adjust selection for insert text, html paste, and delete actions
    if (aEditSubAction == EditSubAction::eInsertText ||
        aEditSubAction == EditSubAction::eInsertTextComingFromIME ||
        aEditSubAction == EditSubAction::eDeleteSelectedContent ||
        aEditSubAction == EditSubAction::eInsertLineBreak ||
        aEditSubAction == EditSubAction::eInsertParagraphSeparator ||
        aEditSubAction == EditSubAction::ePasteHTMLContent ||
        aEditSubAction == EditSubAction::eInsertHTMLSource) {
      rv = AdjustSelection(aDirection);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }

    // check for any styles which were removed inappropriately
    if (aEditSubAction == EditSubAction::eInsertText ||
        aEditSubAction == EditSubAction::eInsertTextComingFromIME ||
        aEditSubAction == EditSubAction::eDeleteSelectedContent ||
        IsStyleCachePreservingSubAction(aEditSubAction)) {
      HTMLEditorRef().mTypeInState->UpdateSelState(SelectionRefPtr());
      rv = ReapplyCachedStyles();
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      ClearCachedStyles();
    }
  }

  rv = HTMLEditorRef().HandleInlineSpellCheck(
      aEditSubAction, mRangeItem->mStartContainer, mRangeItem->mStartOffset,
      rangeStartContainer, rangeStartOffset, rangeEndContainer, rangeEndOffset);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // detect empty doc
  rv = CreateBogusNodeIfNeeded();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // adjust selection HINT if needed
  if (!mDidExplicitlySetInterline) {
    CheckInterlinePosition();
  }

  return NS_OK;
}

nsresult HTMLEditRules::WillDoAction(EditSubActionInfo& aInfo, bool* aCancel,
                                     bool* aHandled) {
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }

  MOZ_ASSERT(aCancel);
  MOZ_ASSERT(aHandled);

  *aCancel = false;
  *aHandled = false;

  // Deal with actions for which we don't need to check whether the selection is
  // editable.
  if (aInfo.mEditSubAction == EditSubAction::eComputeTextToOutput ||
      aInfo.mEditSubAction == EditSubAction::eUndo ||
      aInfo.mEditSubAction == EditSubAction::eRedo) {
    return TextEditRules::WillDoAction(aInfo, aCancel, aHandled);
  }

  AutoSafeEditorData setData(*this, *mHTMLEditor);

  // Nothing to do if there's no selection to act on
  if (NS_WARN_IF(!SelectionRefPtr()->RangeCount())) {
    return NS_OK;
  }

  RefPtr<nsRange> range = SelectionRefPtr()->GetRangeAt(0);
  nsCOMPtr<nsINode> selStartNode = range->GetStartContainer();
  if (NS_WARN_IF(!selStartNode)) {
    return NS_ERROR_FAILURE;
  }

  if (!HTMLEditorRef().IsModifiableNode(*selStartNode)) {
    *aCancel = true;
    return NS_OK;
  }

  nsCOMPtr<nsINode> selEndNode = range->GetEndContainer();
  if (NS_WARN_IF(!selEndNode)) {
    return NS_ERROR_FAILURE;
  }

  if (selStartNode != selEndNode) {
    if (!HTMLEditorRef().IsModifiableNode(*selEndNode)) {
      *aCancel = true;
      return NS_OK;
    }

    nsINode* commonAncestor = range->GetCommonAncestor();
    if (NS_WARN_IF(!commonAncestor)) {
      return NS_ERROR_FAILURE;
    }
    if (!HTMLEditorRef().IsModifiableNode(*commonAncestor)) {
      *aCancel = true;
      return NS_OK;
    }
  }

  switch (aInfo.mEditSubAction) {
    case EditSubAction::eInsertText:
    case EditSubAction::eInsertTextComingFromIME:
      UndefineCaretBidiLevel();
      return WillInsertText(aInfo.mEditSubAction, aCancel, aHandled,
                            aInfo.inString, aInfo.outString, aInfo.maxLength);
    case EditSubAction::eInsertHTMLSource:
      return WillLoadHTML();
    case EditSubAction::eInsertParagraphSeparator: {
      UndefineCaretBidiLevel();
      EditActionResult result = WillInsertParagraphSeparator();
      if (NS_WARN_IF(result.Failed())) {
        return result.Rv();
      }
      *aCancel = result.Canceled();
      *aHandled = result.Handled();
      MOZ_ASSERT(!result.Ignored());
      return NS_OK;
    }
    case EditSubAction::eDeleteSelectedContent:
      return WillDeleteSelection(aInfo.collapsedAction, aInfo.stripWrappers,
                                 aCancel, aHandled);
    case EditSubAction::eCreateOrChangeList:
      return WillMakeList(aInfo.blockType, aInfo.entireList, aInfo.bulletType,
                          aCancel, aHandled);
    case EditSubAction::eIndent:
      return WillIndent(aCancel, aHandled);
    case EditSubAction::eOutdent:
      return WillOutdent(aCancel, aHandled);
    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::eCreateOrRemoveBlock:
      return WillMakeBasicBlock(*aInfo.blockType, aCancel, aHandled);
    case EditSubAction::eRemoveList: {
      nsresult rv = WillRemoveList(aCancel, aHandled);
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED) ||
          NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      return NS_OK;
    }
    case EditSubAction::eCreateOrChangeDefinitionList:
      return WillMakeDefListItem(aInfo.blockType, aInfo.entireList, aCancel,
                                 aHandled);
    case EditSubAction::eInsertElement: {
      nsresult rv = 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;
    }
    case EditSubAction::eDecreaseZIndex:
      return WillRelativeChangeZIndex(-1, aCancel, aHandled);
    case EditSubAction::eIncreaseZIndex:
      return WillRelativeChangeZIndex(1, aCancel, aHandled);
    default:
      return TextEditRules::WillDoAction(aInfo, aCancel, aHandled);
  }
}

nsresult HTMLEditRules::DidDoAction(EditSubActionInfo& aInfo,
                                    nsresult aResult) {
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }

  AutoSafeEditorData setData(*this, *mHTMLEditor);

  switch (aInfo.mEditSubAction) {
    case EditSubAction::eInsertText:
    case EditSubAction::eInsertLineBreak:
    case EditSubAction::eInsertParagraphSeparator:
    case EditSubAction::eInsertTextComingFromIME:
      return NS_OK;
    case EditSubAction::eDeleteSelectedContent:
      return DidDeleteSelection();
    case EditSubAction::eCreateOrRemoveBlock:
    case EditSubAction::eIndent:
    case EditSubAction::eOutdent:
    case EditSubAction::eSetOrClearAlignment:
      return DidMakeBasicBlock();
    case EditSubAction::eSetPositionToAbsolute: {
      nsresult rv = DidMakeBasicBlock();
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      return DidAbsolutePosition();
    }
    default:
      return TextEditRules::DidDoAction(aInfo, aResult);
  }
}

bool HTMLEditRules::DocumentIsEmpty() { return !!mBogusNode; }

nsresult HTMLEditRules::GetListState(bool* aMixed, bool* aOL, bool* aUL,
                                     bool* aDL) {
  NS_ENSURE_TRUE(aMixed && aOL && aUL && aDL, NS_ERROR_NULL_POINTER);
  *aMixed = false;
  *aOL = false;
  *aUL = false;
  *aDL = false;
  bool bNonList = false;

  if (NS_WARN_IF(!CanHandleEditAction())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }

  AutoSafeEditorData setData(*this, *mHTMLEditor);

  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  nsresult rv =
      GetListActionNodes(arrayOfNodes, EntireList::no, TouchContent::no);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Examine list type for nodes in selection.
  for (const auto& curNode : arrayOfNodes) {
    if (!curNode->IsElement()) {
      bNonList = true;
    } else if (curNode->IsHTMLElement(nsGkAtoms::ul)) {
      *aUL = true;
    } else if (curNode->IsHTMLElement(nsGkAtoms::ol)) {
      *aOL = true;
    } else if (curNode->IsHTMLElement(nsGkAtoms::li)) {
      if (dom::Element* parent = curNode->GetParentElement()) {
        if (parent->IsHTMLElement(nsGkAtoms::ul)) {
          *aUL = true;
        } else if (parent->IsHTMLElement(nsGkAtoms::ol)) {
          *aOL = true;
        }
      }
    } else if (curNode->IsAnyOfHTMLElements(nsGkAtoms::dl, nsGkAtoms::dt,
                                            nsGkAtoms::dd)) {
      *aDL = true;
    } else {
      bNonList = true;
    }
  }

  // hokey arithmetic with booleans
  if ((*aUL + *aOL + *aDL + bNonList) > 1) {
    *aMixed = true;
  }

  return NS_OK;
}

nsresult HTMLEditRules::GetListItemState(bool* aMixed, bool* aLI, bool* aDT,
                                         bool* aDD) {
  NS_ENSURE_TRUE(aMixed && aLI && aDT && aDD, NS_ERROR_NULL_POINTER);
  *aMixed = false;
  *aLI = false;
  *aDT = false;
  *aDD = false;
  bool bNonList = false;

  if (NS_WARN_IF(!CanHandleEditAction())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }

  AutoSafeEditorData setData(*this, *mHTMLEditor);

  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  nsresult rv =
      GetListActionNodes(arrayOfNodes, EntireList::no, TouchContent::no);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // examine list type for nodes in selection
  for (const auto& node : arrayOfNodes) {
    if (!node->IsElement()) {
      bNonList = true;
    } else if (node->IsAnyOfHTMLElements(nsGkAtoms::ul, nsGkAtoms::ol,
                                         nsGkAtoms::li)) {
      *aLI = true;
    } else if (node->IsHTMLElement(nsGkAtoms::dt)) {
      *aDT = true;
    } else if (node->IsHTMLElement(nsGkAtoms::dd)) {
      *aDD = true;
    } else if (node->IsHTMLElement(nsGkAtoms::dl)) {
      // need to look inside dl and see which types of items it has
      bool bDT, bDD;
      GetDefinitionListItemTypes(node->AsElement(), &bDT, &bDD);
      *aDT |= bDT;
      *aDD |= bDD;
    } else {
      bNonList = true;
    }
  }

  // hokey arithmetic with booleans
  if (*aDT + *aDD + bNonList > 1) {
    *aMixed = true;
  }

  return NS_OK;
}

nsresult HTMLEditRules::GetAlignment(bool* aMixed,
                                     nsIHTMLEditor::EAlignment* aAlign) {
  MOZ_ASSERT(aMixed && aAlign);

  if (NS_WARN_IF(!CanHandleEditAction())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }

  AutoSafeEditorData setData(*this, *mHTMLEditor);

  // For now, just return first alignment.  We'll lie about if it's mixed.
  // This is for efficiency given that our current ui doesn't care if it's
  // mixed.
  // cmanske: NOT TRUE! We would like to pay attention to mixed state in Format
  // | Align submenu!

  // This routine assumes that alignment is done ONLY via divs

  // Default alignment is left
  *aMixed = false;
  *aAlign = nsIHTMLEditor::eLeft;

  // Get selection location
  if (NS_WARN_IF(!HTMLEditorRef().GetRoot())) {
    return NS_ERROR_FAILURE;
  }
  OwningNonNull<Element> root = *HTMLEditorRef().GetRoot();

  int32_t rootOffset =
      root->GetParentNode() ? root->GetParentNode()->ComputeIndexOf(root) : -1;

  nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
  if (NS_WARN_IF(!firstRange)) {
    return NS_ERROR_FAILURE;
  }
  EditorRawDOMPoint atStartOfSelection(firstRange->StartRef());
  if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
    return NS_ERROR_FAILURE;
  }
  MOZ_ASSERT(atStartOfSelection.IsSetAndValid());

  // Is the selection collapsed?
  nsCOMPtr<nsINode> nodeToExamine;
  if (SelectionRefPtr()->IsCollapsed() ||
      atStartOfSelection.GetContainerAsText()) {
    // If selection is collapsed, we want to look at the container of selection
    // start and its ancestors for divs with alignment on them.  If we are in a
    // text node, then that is the node of interest.
    nodeToExamine = atStartOfSelection.GetContainer();
    if (NS_WARN_IF(!nodeToExamine)) {
      return NS_ERROR_FAILURE;
    }
  } else if (atStartOfSelection.IsContainerHTMLElement(nsGkAtoms::html) &&
             atStartOfSelection.Offset() == static_cast<uint32_t>(rootOffset)) {
    // If we have selected the body, let's look at the first editable node
    nodeToExamine = HTMLEditorRef().GetNextEditableNode(atStartOfSelection);
    if (NS_WARN_IF(!nodeToExamine)) {
      return NS_ERROR_FAILURE;
    }
  } else {
    nsTArray<RefPtr<nsRange>> arrayOfRanges;
    GetPromotedRanges(arrayOfRanges, EditSubAction::eSetOrClearAlignment);

    // Use these ranges to construct a list of nodes to act on.
    nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
    nsresult rv = GetNodesForOperation(arrayOfRanges, arrayOfNodes,
                                       EditSubAction::eSetOrClearAlignment,
                                       TouchContent::no);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    nodeToExamine = arrayOfNodes.SafeElementAt(0);
    if (NS_WARN_IF(!nodeToExamine)) {
      return NS_ERROR_FAILURE;
    }
  }

  RefPtr<Element> blockParent = HTMLEditorRef().GetBlock(*nodeToExamine);
  if (NS_WARN_IF(!blockParent)) {
    return NS_ERROR_FAILURE;
  }

  if (HTMLEditorRef().IsCSSEnabled() &&
      CSSEditUtils::IsCSSEditableProperty(blockParent, nullptr,
                                          nsGkAtoms::align)) {
    // We are in CSS mode and we know how to align this element with CSS
    nsAutoString value;
    // Let's get the value(s) of text-align or margin-left/margin-right
    CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSet(
        blockParent, nullptr, nsGkAtoms::align, value, CSSEditUtils::eComputed);
    if (value.EqualsLiteral("center") || value.EqualsLiteral("-moz-center") ||
        value.EqualsLiteral("auto auto")) {
      *aAlign = nsIHTMLEditor::eCenter;
      return NS_OK;
    }
    if (value.EqualsLiteral("right") || value.EqualsLiteral("-moz-right") ||
        value.EqualsLiteral("auto 0px")) {
      *aAlign = nsIHTMLEditor::eRight;
      return NS_OK;
    }
    if (value.EqualsLiteral("justify")) {
      *aAlign = nsIHTMLEditor::eJustify;
      return NS_OK;
    }
    *aAlign = nsIHTMLEditor::eLeft;
    return NS_OK;
  }

  // Check up the ladder for divs with alignment
  bool isFirstNodeToExamine = true;
  for (; nodeToExamine; nodeToExamine = nodeToExamine->GetParentNode()) {
    if (!isFirstNodeToExamine &&
        nodeToExamine->IsHTMLElement(nsGkAtoms::table)) {
      // The node to examine is a table and this is not the first node we
      // examine; let's break here to materialize the 'inline-block' behaviour
      // of html tables regarding to text alignment
      return NS_OK;
    }

    if (CSSEditUtils::IsCSSEditableProperty(nodeToExamine, nullptr,
                                            nsGkAtoms::align)) {
      nsAutoString value;
      CSSEditUtils::GetSpecifiedProperty(*nodeToExamine, *nsGkAtoms::textAlign,
                                         value);
      if (!value.IsEmpty()) {
        if (value.EqualsLiteral("center")) {
          *aAlign = nsIHTMLEditor::eCenter;
          return NS_OK;
        }
        if (value.EqualsLiteral("right")) {
          *aAlign = nsIHTMLEditor::eRight;
          return NS_OK;
        }
        if (value.EqualsLiteral("justify")) {
          *aAlign = nsIHTMLEditor::eJustify;
          return NS_OK;
        }
        if (value.EqualsLiteral("left")) {
          *aAlign = nsIHTMLEditor::eLeft;
          return NS_OK;
        }
        // XXX
        // text-align: start and end aren't supported yet
      }
    }

    if (HTMLEditUtils::SupportsAlignAttr(*nodeToExamine)) {
      // Check for alignment
      nsAutoString typeAttrVal;
      nodeToExamine->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::align,
                                          typeAttrVal);
      ToLowerCase(typeAttrVal);
      if (!typeAttrVal.IsEmpty()) {
        if (typeAttrVal.EqualsLiteral("center")) {
          *aAlign = nsIHTMLEditor::eCenter;
        } else if (typeAttrVal.EqualsLiteral("right")) {
          *aAlign = nsIHTMLEditor::eRight;
        } else if (typeAttrVal.EqualsLiteral("justify")) {
          *aAlign = nsIHTMLEditor::eJustify;
        } else {
          *aAlign = nsIHTMLEditor::eLeft;
        }
        return NS_OK;
      }
    }
    isFirstNodeToExamine = false;
  }
  return NS_OK;
}

static nsAtom& MarginPropertyAtomForIndent(nsINode& aNode) {
  nsAutoString direction;
  CSSEditUtils::GetComputedProperty(aNode, *nsGkAtoms::direction, direction);
  return direction.EqualsLiteral("rtl") ? *nsGkAtoms::marginRight
                                        : *nsGkAtoms::marginLeft;
}

nsresult HTMLEditRules::GetParagraphState(bool* aMixed, nsAString& outFormat) {
  if (NS_WARN_IF(!aMixed)) {
    return NS_ERROR_INVALID_ARG;
  }

  if (NS_WARN_IF(!CanHandleEditAction())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }

  // This routine is *heavily* tied to our ui choices in the paragraph
  // style popup.  I can't see a way around that.
  *aMixed = true;
  outFormat.Truncate(0);

  AutoSafeEditorData setData(*this, *mHTMLEditor);

  bool bMixed = false;
  // using "x" as an uninitialized value, since "" is meaningful
  nsAutoString formatStr(NS_LITERAL_STRING("x"));

  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  nsresult rv = GetParagraphFormatNodes(arrayOfNodes);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // post process list.  We need to replace any block nodes that are not format
  // nodes with their content.  This is so we only have to look "up" the
  // hierarchy to find format nodes, instead of both up and down.
  for (int32_t i = arrayOfNodes.Length() - 1; i >= 0; i--) {
    auto& curNode = arrayOfNodes[i];
    nsAutoString format;
    // if it is a known format node we have it easy
    if (IsBlockNode(curNode) && !HTMLEditUtils::IsFormatNode(curNode)) {
      // arrayOfNodes.RemoveObject(curNode);
      rv = AppendInnerFormatNodes(arrayOfNodes, curNode);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }
  }

  // we might have an empty node list.  if so, find selection parent
  // and put that on the list
  if (arrayOfNodes.IsEmpty()) {
    EditorRawDOMPoint selectionStartPoint(
        EditorBase::GetStartPoint(*SelectionRefPtr()));
    if (NS_WARN_IF(!selectionStartPoint.IsSet())) {
      return NS_ERROR_FAILURE;
    }
    arrayOfNodes.AppendElement(*selectionStartPoint.GetContainer());
  }

  // remember root node
  Element* rootElement = HTMLEditorRef().GetRoot();
  if (NS_WARN_IF(!rootElement)) {
    return NS_ERROR_FAILURE;
  }

  // loop through the nodes in selection and examine their paragraph format
  for (auto& curNode : Reversed(arrayOfNodes)) {
    nsAutoString format;
    // if it is a known format node we have it easy
    if (HTMLEditUtils::IsFormatNode(curNode)) {
      GetFormatString(curNode, format);
    } else if (IsBlockNode(curNode)) {
      // this is a div or some other non-format block.
      // we should ignore it.  Its children were appended to this list
      // by AppendInnerFormatNodes() call above.  We will get needed
      // info when we examine them instead.
      continue;
    } else {
      nsINode* node = curNode->GetParentNode();
      while (node) {
        if (node == rootElement) {
          format.Truncate(0);
          break;
        } else if (HTMLEditUtils::IsFormatNode(node)) {
          GetFormatString(node, format);
          break;
        }
        // else keep looking up
        node = node->GetParentNode();
      }
    }

    // if this is the first node, we've found, remember it as the format
    if (formatStr.EqualsLiteral("x")) {
      formatStr = format;
    }
    // else make sure it matches previously found format
    else if (format != formatStr) {
      bMixed = true;
      break;
    }
  }

  *aMixed = bMixed;
  outFormat = formatStr;
  return NS_OK;
}

nsresult HTMLEditRules::AppendInnerFormatNodes(
    nsTArray<OwningNonNull<nsINode>>& aArray, nsINode* aNode) {
  MOZ_ASSERT(aNode);

  // we only need to place any one inline inside this node onto
  // the list.  They are all the same for purposes of determining
  // paragraph style.  We use foundInline to track this as we are
  // going through the children in the loop below.
  bool foundInline = false;
  for (nsIContent* child = aNode->GetFirstChild(); child;
       child = child->GetNextSibling()) {
    bool isBlock = IsBlockNode(*child);
    bool isFormat = HTMLEditUtils::IsFormatNode(child);
    if (isBlock && !isFormat) {
      // if it's a div, etc., recurse
      AppendInnerFormatNodes(aArray, child);
    } else if (isFormat) {
      aArray.AppendElement(*child);
    } else if (!foundInline) {
      // if this is the first inline we've found, use it
      foundInline = true;
      aArray.AppendElement(*child);
    }
  }
  return NS_OK;
}

nsresult HTMLEditRules::GetFormatString(nsINode* aNode, nsAString& outFormat) {
  NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);

  if (HTMLEditUtils::IsFormatNode(aNode)) {
    aNode->NodeInfo()->NameAtom()->ToString(outFormat);
  } else {
    outFormat.Truncate();
  }
  return NS_OK;
}

nsresult HTMLEditRules::WillInsert(bool* aCancel) {
  MOZ_ASSERT(IsEditorDataAvailable());

  nsresult rv = TextEditRules::WillInsert(aCancel);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Adjust selection to prevent insertion after a moz-BR.  This next only
  // works for collapsed selections right now, because selection is a pain to
  // work with when not collapsed.  (no good way to extend start or end of
  // selection), so we ignore those types of selections.
  if (!SelectionRefPtr()->IsCollapsed()) {
    return NS_OK;
  }

  // If we are after a mozBR in the same block, then move selection to be
  // before it
  nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
  if (NS_WARN_IF(!firstRange)) {
    return NS_ERROR_FAILURE;
  }

  EditorRawDOMPoint atStartOfSelection(firstRange->StartRef());
  if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
    return NS_ERROR_FAILURE;
  }
  MOZ_ASSERT(atStartOfSelection.IsSetAndValid());

  // Get prior node
  nsCOMPtr<nsIContent> priorNode =
      HTMLEditorRef().GetPreviousEditableHTMLNode(atStartOfSelection);
  if (priorNode && TextEditUtils::IsMozBR(priorNode)) {
    RefPtr<Element> block1 =
        HTMLEditorRef().GetBlock(*atStartOfSelection.GetContainer());
    RefPtr<Element> block2 = HTMLEditorRef().GetBlockNodeParent(priorNode);

    if (block1 && block1 == block2) {
      // If we are here then the selection is right after a mozBR that is in
      // the same block as the selection.  We need to move the selection start
      // to be before the mozBR.
      EditorRawDOMPoint point(priorNode);
      ErrorResult error;
      SelectionRefPtr()->Collapse(point, error);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        error.SuppressException();
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(error.Failed())) {
        return error.StealNSResult();
      }
    }
  }

  if (mDidDeleteSelection &&
      (mTopLevelEditSubAction == EditSubAction::eInsertText ||
       mTopLevelEditSubAction == EditSubAction::eInsertTextComingFromIME ||
       mTopLevelEditSubAction == EditSubAction::eDeleteSelectedContent)) {
    nsresult rv = ReapplyCachedStyles();
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }
  // For most actions we want to clear the cached styles, but there are
  // exceptions
  if (!IsStyleCachePreservingSubAction(mTopLevelEditSubAction)) {
    ClearCachedStyles();
  }
  return NS_OK;
}

nsresult HTMLEditRules::WillInsertText(EditSubAction aEditSubAction,
                                       bool* aCancel, bool* aHandled,
                                       const nsAString* inString,
                                       nsAString* outString,
                                       int32_t aMaxLength) {
  MOZ_ASSERT(IsEditorDataAvailable());

  if (NS_WARN_IF(!aCancel) || NS_WARN_IF(!aHandled)) {
    return NS_ERROR_NULL_POINTER;
  }

  // initialize out param
  *aCancel = false;
  *aHandled = true;
  // If the selection isn't collapsed, delete it.  Don't delete existing inline
  // tags, because we're hopefully going to insert text (bug 787432).
  if (!SelectionRefPtr()->IsCollapsed()) {
    nsresult rv = HTMLEditorRef().DeleteSelectionAsSubAction(
        nsIEditor::eNone, nsIEditor::eNoStrip);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }

  // FYI: Ignore cancel result of 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");

  // we need to get the doc
  RefPtr<Document> doc = HTMLEditorRef().GetDocument();
  if (NS_WARN_IF(!doc)) {
    return NS_ERROR_FAILURE;
  }

  // for every property that is set, insert a new inline style node
  rv = CreateStyleForInsertText(*doc);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // get the (collapsed) selection location
  nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
  if (NS_WARN_IF(!firstRange)) {
    return NS_ERROR_FAILURE;
  }
  EditorDOMPoint pointToInsert(firstRange->StartRef());
  if (NS_WARN_IF(!pointToInsert.IsSet())) {
    return NS_ERROR_FAILURE;
  }
  MOZ_ASSERT(pointToInsert.IsSetAndValid());

  // dont put text in places that can't have it
  if (!EditorBase::IsTextNode(pointToInsert.GetContainer()) &&
      !HTMLEditorRef().CanContainTag(*pointToInsert.GetContainer(),
                                     *nsGkAtoms::textTagName)) {
    return NS_ERROR_FAILURE;
  }

  if (aEditSubAction == EditSubAction::eInsertTextComingFromIME) {
    // Right now the WSRunObject code bails on empty strings, but IME needs
    // the InsertTextWithTransaction() call to still happen since empty strings
    // are meaningful there.
    // If there is one or more IME selections, its minimum offset should be
    // the insertion point.
    int32_t IMESelectionOffset = HTMLEditorRef().GetIMESelectionStartOffsetIn(
        pointToInsert.GetContainer());
    if (IMESelectionOffset >= 0) {
      pointToInsert.Set(pointToInsert.GetContainer(), IMESelectionOffset);
    }

    if (inString->IsEmpty()) {
      rv = HTMLEditorRef().InsertTextWithTransaction(
          *doc, *inString, EditorRawDOMPoint(pointToInsert));
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      return NS_OK;
    }

    WSRunObject wsObj(&HTMLEditorRef(), pointToInsert);
    rv = wsObj.InsertText(*doc, *inString, pointToInsert);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    return NS_OK;
  }

  // aEditSubAction == kInsertText

  // find where we are
  EditorDOMPoint currentPoint(pointToInsert);

  // is our text going to be PREformatted?
  // We remember this so that we know how to handle tabs.
  bool isPRE = EditorBase::IsPreformatted(pointToInsert.GetContainer());

  // turn off the edit listener: we know how to
  // build the "doc changed range" ourselves, and it's
  // must faster to do it once here than to track all
  // the changes one at a time.
  AutoLockListener lockit(&mListenerEnabled);

  // don't change my selection in subtransactions
  AutoTransactionsConserveSelection dontChangeMySelection(HTMLEditorRef());
  nsAutoString tString(*inString);
  const char16_t* unicodeBuf = tString.get();
  int32_t pos = 0;
  NS_NAMED_LITERAL_STRING(newlineStr, LFSTR);

  {
    AutoTrackDOMPoint tracker(HTMLEditorRef().RangeUpdaterRef(),
                              &pointToInsert);

    // for efficiency, break out the pre case separately.  This is because
    // its a lot cheaper to search the input string for only newlines than
    // it is to search for both tabs and newlines.
    if (isPRE || IsPlaintextEditor()) {
      while (unicodeBuf && pos != -1 &&
             pos < static_cast<int32_t>(inString->Length())) {
        int32_t oldPos = pos;
        int32_t subStrLen;
        pos = tString.FindChar(nsCRT::LF, oldPos);

        if (pos != -1) {
          subStrLen = pos - oldPos;
          // if first char is newline, then use just it
          if (!subStrLen) {
            subStrLen = 1;
          }
        } else {
          subStrLen = tString.Length() - oldPos;
          pos = tString.Length();
        }

        nsDependentSubstring subStr(tString, oldPos, subStrLen);

        // is it a return?
        if (subStr.Equals(newlineStr)) {
          RefPtr<Element> brElement =
              HTMLEditorRef().InsertBrElementWithTransaction(currentPoint,
                                                             nsIEditor::eNone);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          if (NS_WARN_IF(!brElement)) {
            return NS_ERROR_FAILURE;
          }
          pos++;
          if (brElement->GetNextSibling()) {
            pointToInsert.Set(brElement->GetNextSibling());
          } else {
            pointToInsert.SetToEndOf(currentPoint.GetContainer());
          }
          // XXX In most cases, pointToInsert and currentPoint are same here.
          //     But if the <br> element has been moved to different point by
          //     mutation observer, those points become different.
          currentPoint.Set(brElement);
          DebugOnly<bool> advanced = currentPoint.AdvanceOffset();
          NS_WARNING_ASSERTION(
              advanced, "Failed to advance offset after the new <br> element");
          NS_WARNING_ASSERTION(currentPoint == pointToInsert,
                               "Perhaps, <br> element position has been moved "
                               "to different point "
                               "by mutation observer");
        } else {
          EditorRawDOMPoint pointAfterInsertedString;
          rv = HTMLEditorRef().InsertTextWithTransaction(
              *doc, subStr, EditorRawDOMPoint(currentPoint),
              &pointAfterInsertedString);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return rv;
          }
          currentPoint = pointAfterInsertedString;
          pointToInsert = pointAfterInsertedString;
        }
      }
    } else {
      NS_NAMED_LITERAL_STRING(tabStr, "\t");
      NS_NAMED_LITERAL_STRING(spacesStr, "    ");
      char specialChars[] = {TAB, nsCRT::LF, 0};
      while (unicodeBuf && pos != -1 &&
             pos < static_cast<int32_t>(inString->Length())) {
        int32_t oldPos = pos;
        int32_t subStrLen;
        pos = tString.FindCharInSet(specialChars, oldPos);

        if (pos != -1) {
          subStrLen = pos - oldPos;
          // if first char is newline, then use just it
          if (!subStrLen) {
            subStrLen = 1;
          }
        } else {
          subStrLen = tString.Length() - oldPos;
          pos = tString.Length();
        }

        nsDependentSubstring subStr(tString, oldPos, subStrLen);
        WSRunObject wsObj(&HTMLEditorRef(), currentPoint);

        // is it a tab?
        if (subStr.Equals(tabStr)) {
          EditorRawDOMPoint pointAfterInsertedSpaces;
          rv = wsObj.InsertText(*doc, spacesStr, currentPoint,
                                &pointAfterInsertedSpaces);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return rv;
          }
          pos++;
          currentPoint = pointAfterInsertedSpaces;
          pointToInsert = pointAfterInsertedSpaces;
        }
        // is it a return?
        else if (subStr.Equals(newlineStr)) {
          RefPtr<Element> newBRElement = wsObj.InsertBreak(
              *SelectionRefPtr(), currentPoint, nsIEditor::eNone);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          if (NS_WARN_IF(!newBRElement)) {
            return NS_ERROR_FAILURE;
          }
          pos++;
          if (newBRElement->GetNextSibling()) {
            pointToInsert.Set(newBRElement->GetNextSibling());
          } else {
            pointToInsert.SetToEndOf(currentPoint.GetContainer());
          }
          currentPoint.Set(newBRElement);
          DebugOnly<bool> advanced = currentPoint.AdvanceOffset();
          NS_WARNING_ASSERTION(
              advanced, "Failed to advance offset to after the new <br> node");
          // XXX If the newBRElement has been moved or removed by mutation
          //     observer, we hit this assert.  We need to check if
          //     newBRElement is in expected point, though, we must have
          //     a lot of same bugs...
          NS_WARNING_ASSERTION(
              currentPoint == pointToInsert,
              "Perhaps, newBRElement has been moved or removed unexpectedly");
        } else {
          EditorRawDOMPoint pointAfterInsertedString;
          rv = wsObj.InsertText(*doc, subStr, currentPoint,
                                &pointAfterInsertedString);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return rv;
          }
          currentPoint = pointAfterInsertedString;
          pointToInsert = pointAfterInsertedString;
        }
      }
    }

    // After this block, pointToInsert is updated by AutoTrackDOMPoint.
  }

  IgnoredErrorResult ignoredError;
  SelectionRefPtr()->SetInterlinePosition(false, ignoredError);
  NS_WARNING_ASSERTION(!ignoredError.Failed(),
                       "Failed to unset interline position");

  if (currentPoint.IsSet()) {
    IgnoredErrorResult ignoredError;
    SelectionRefPtr()->Collapse(currentPoint, ignoredError);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    NS_WARNING_ASSERTION(!ignoredError.Failed(),
                         "Failed to collapse at current point");
  }

  // manually update the doc changed range so that AfterEdit will clean up
  // the correct portion of the document.
  if (!mDocChangeRange) {
    mDocChangeRange = new nsRange(pointToInsert.GetContainer());
  }

  if (currentPoint.IsSet()) {
    rv = mDocChangeRange->SetStartAndEnd(pointToInsert, currentPoint);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    return NS_OK;
  }

  rv = mDocChangeRange->CollapseTo(pointToInsert);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

nsresult HTMLEditRules::WillLoadHTML() {
  MOZ_ASSERT(IsEditorDataAvailable());

  // Delete mBogusNode if it exists. If we really need one,
  // it will be added during post-processing in AfterEditInner().

  if (mBogusNode) {
    DebugOnly<nsresult> rv =
        HTMLEditorRef().DeleteNodeWithTransaction(*mBogusNode);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to remove the bogus node");
    mBogusNode = nullptr;
  }

  return NS_OK;
}

bool HTMLEditRules::CanContainParagraph(Element& aElement) const {
  MOZ_ASSERT(IsEditorDataAvailable());

  if (HTMLEditorRef().CanContainTag(aElement, *nsGkAtoms::p)) {
    return true;
  }

  // Even if the element cannot have a <p> element as a child, it can contain
  // <p> element as a descendant if it's one of the following elements.
  if (aElement.IsAnyOfHTMLElements(nsGkAtoms::ol, nsGkAtoms::ul, nsGkAtoms::dl,
                                   nsGkAtoms::table, nsGkAtoms::thead,
                                   nsGkAtoms::tbody, nsGkAtoms::tfoot,
                                   nsGkAtoms::tr)) {
    return true;
  }

  // XXX Otherwise, Chromium checks the CSS box is a block, but we don't do it
  //     for now.
  return false;
}

EditActionResult HTMLEditRules::WillInsertParagraphSeparator() {
  MOZ_ASSERT(IsEditorDataAvailable());

  // If the selection isn't collapsed, delete it.
  if (!SelectionRefPtr()->IsCollapsed()) {
    nsresult rv = HTMLEditorRef().DeleteSelectionAsSubAction(nsIEditor::eNone,
                                                             nsIEditor::eStrip);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
    }
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return EditActionIgnored(rv);
    }
  }

  // FYI: Ignore cancel result of WillInsert().
  nsresult rv = WillInsert();
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
  }
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed");

  // Split any mailcites in the way.  Should we abort this if we encounter
  // table cell boundaries?
  if (IsMailEditor()) {
    EditActionResult result = SplitMailCites();
    if (NS_WARN_IF(result.Failed())) {
      return result;
    }
    if (result.Handled()) {
      return result;
    }
  }

  // Smart splitting rules
  nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
  if (NS_WARN_IF(!firstRange)) {
    return EditActionIgnored(NS_ERROR_FAILURE);
  }

  EditorDOMPoint atStartOfSelection(firstRange->StartRef());
  if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
    return EditActionIgnored(NS_ERROR_FAILURE);
  }
  MOZ_ASSERT(atStartOfSelection.IsSetAndValid());

  // Do nothing if the node is read-only
  if (!HTMLEditorRef().IsModifiableNode(*atStartOfSelection.GetContainer())) {
    return EditActionCanceled();
  }

  // If the active editing host is an inline element, or if the active editing
  // host is the block parent itself and we're configured to use <br> as a
  // paragraph separator, just append a <br>.
  RefPtr<Element> host = HTMLEditorRef().GetActiveEditingHost();
  if (NS_WARN_IF(!host)) {
    return EditActionIgnored(NS_ERROR_FAILURE);
  }

  // Look for the nearest parent block.  However, don't return error even if
  // there is no block parent here because in such case, i.e., editing host
  // is an inline element, we should insert <br> simply.
  RefPtr<Element> blockParent =
      HTMLEditor::GetBlock(*atStartOfSelection.GetContainer(), host);

  ParagraphSeparator separator = HTMLEditorRef().GetDefaultParagraphSeparator();
  bool insertBRElement;
  // If there is no block parent in the editing host, i.e., the editing host
  // itself is also a non-block element, we should insert a <br> element.
  if (!blockParent) {
    // XXX Chromium checks if the CSS box of the editing host is block.
    insertBRElement = true;
  }
  // If only the editing host is block, and the default paragraph separator
  // is <br> or the editing host cannot contain a <p> element, we should
  // insert a <br> element.
  else if (host == blockParent) {
    insertBRElement =
        separator == ParagraphSeparator::br || !CanContainParagraph(*host);
  }
  // If the nearest block parent is a single-line container declared in
  // the execCommand spec and not the editing host, we should separate the
  // block even if the default paragraph separator is <br> element.
  else if (HTMLEditUtils::IsSingleLineContainer(*blockParent)) {
    insertBRElement = false;
  }
  // Otherwise, unless there is no block ancestor which can contain <p>
  // element, we shouldn't insert a <br> element here.
  else {
    insertBRElement = true;
    for (Element* blockAncestor = blockParent; blockAncestor && insertBRElement;
         blockAncestor = HTMLEditor::GetBlockNodeParent(blockAncestor, host)) {
      insertBRElement = !CanContainParagraph(*blockAncestor);
    }
  }

  // If we cannot insert a <p>/<div> element at the selection, we should insert
  // a <br> element instead.
  if (insertBRElement) {
    nsresult rv = InsertBRElement(atStartOfSelection);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return EditActionIgnored(rv);
    }
    return EditActionHandled();
  }

  if (host == blockParent && separator != ParagraphSeparator::br) {
    // Insert a new block first
    MOZ_ASSERT(separator == ParagraphSeparator::div ||
               separator == ParagraphSeparator::p);
    // MakeBasicBlock() creates AutoSelectionRestorer.
    // Therefore, even if it returns NS_OK, editor might have been destroyed
    // at restoring Selection.
    nsresult rv = MakeBasicBlock(ParagraphSeparatorElement(separator));
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED) ||
        NS_WARN_IF(!CanHandleEditAction())) {
      return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
    }
    // We warn on failure, but don't handle it, because it might be harmless.
    // Instead we just check that a new block was actually created.
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                         "HTMLEditRules::MakeBasicBlock() failed");

    firstRange = SelectionRefPtr()->GetRangeAt(0);
    if (NS_WARN_IF(!firstRange)) {
      return EditActionIgnored(NS_ERROR_FAILURE);
    }

    atStartOfSelection = firstRange->StartRef();
    if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
      return EditActionIgnored(NS_ERROR_FAILURE);
    }
    MOZ_ASSERT(atStartOfSelection.IsSetAndValid());

    blockParent =
        HTMLEditor::GetBlock(*atStartOfSelection.GetContainer(), host);
    if (NS_WARN_IF(!blockParent)) {
      return EditActionIgnored(NS_ERROR_UNEXPECTED);
    }
    if (NS_WARN_IF(blockParent == host)) {
      // Didn't create a new block for some reason, fall back to <br>
      rv = InsertBRElement(atStartOfSelection);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return EditActionIgnored(rv);
      }
      return EditActionHandled();
    }
    // Now, mNewBlock is last created block element for wrapping inline
    // elements around the caret position and AfterEditInner() will move
    // caret into it.  However, it may be different from block parent of
    // the caret position.  E.g., MakeBasicBlock() may wrap following
    // inline elements of a <br> element which is next sibling of container
    // of the caret.  So, we need to adjust mNewBlock here for avoiding
    // jumping caret to odd position.
    mNewBlock = blockParent;
  }

  // If block is empty, populate with br.  (For example, imagine a div that
  // contains the word "text".  The user selects "text" and types return.
  // "Text" is deleted leaving an empty block.  We want to put in one br to
  // make block have a line.  Then code further below will put in a second br.)
  if (IsEmptyBlockElement(*blockParent, IgnoreSingleBR::eNo)) {
    AutoEditorDOMPointChildInvalidator lockOffset(atStartOfSelection);
    EditorRawDOMPoint endOfBlockParent;
    endOfBlockParent.SetToEndOf(blockParent);
    RefPtr<Element> brElement =
        HTMLEditorRef().InsertBrElementWithTransaction(endOfBlockParent);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
    }
    if (NS_WARN_IF(!brElement)) {
      return EditActionIgnored(NS_ERROR_FAILURE);
    }
  }

  nsCOMPtr<Element> listItem = IsInListItem(blockParent);
  if (listItem && listItem != host) {
    nsresult rv =
        ReturnInListItem(*listItem, *atStartOfSelection.GetContainer(),
                         atStartOfSelection.Offset());
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
      return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
    }
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                         "Failed to insert break into list item");
    return EditActionHandled();
  }

  if (HTMLEditUtils::IsHeader(*blockParent)) {
    // Headers: close (or split) header
    nsresult rv =
        ReturnInHeader(*blockParent, *atStartOfSelection.GetContainer(),
                       atStartOfSelection.Offset());
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
      return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
    }
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rv),
        "Failed to handle insertParagraph in the heading element");
    return EditActionHandled();
  }

  // XXX Ideally, we should take same behavior with both <p> container and
  //     <div> container.  However, we are still using <br> as default
  //     paragraph separator (non-standard) and we've split only <p> container
  //     long time.  Therefore, some web apps may depend on this behavior like
  //     Gmail.  So, let's use traditional odd behavior only when the default
  //     paragraph separator is <br>.  Otherwise, take consistent behavior
  //     between <p> container and <div> container.
  if ((separator == ParagraphSeparator::br &&
       blockParent->IsHTMLElement(nsGkAtoms::p)) ||
      (separator != ParagraphSeparator::br &&
       blockParent->IsAnyOfHTMLElements(nsGkAtoms::p, nsGkAtoms::div))) {
    AutoEditorDOMPointChildInvalidator lockOffset(atStartOfSelection);
    // Paragraphs: special rules to look for <br>s
    EditActionResult result = ReturnInParagraph(*blockParent);
    if (NS_WARN_IF(result.Failed())) {
      return result;
    }
    if (result.Handled()) {
      // Now, atStartOfSelection may be invalid because the left paragraph
      // may have less children than its offset.  For avoiding warnings of
      // validation of EditorDOMPoint, we should not touch it anymore.
      lockOffset.Cancel();
      return result;
    }
    // Fall through, if ReturnInParagraph() didn't handle it.
    MOZ_ASSERT(!result.Canceled(),
               "ReturnInParagraph canceled this edit action, "
               "WillInsertBreak() needs to handle such case");
  }

  // If nobody handles this edit action, let's insert new <br> at the selection.
  rv = InsertBRElement(atStartOfSelection);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return EditActionIgnored(rv);
  }
  return EditActionHandled();
}

nsresult HTMLEditRules::InsertBRElement(const EditorDOMPoint& aPointToBreak) {
  MOZ_ASSERT(IsEditorDataAvailable());

  if (NS_WARN_IF(!aPointToBreak.IsSet())) {
    return NS_ERROR_INVALID_ARG;
  }

  bool brElementIsAfterBlock = false;
  bool brElementIsBeforeBlock = false;

  // First, insert a <br> element.
  RefPtr<Element> brElement;
  if (IsPlaintextEditor()) {
    brElement = HTMLEditorRef().InsertBrElementWithTransaction(aPointToBreak);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(!brElement)) {
      return NS_ERROR_FAILURE;
    }
  } else {
    EditorDOMPoint pointToBreak(aPointToBreak);
    WSRunObject wsObj(&HTMLEditorRef(), pointToBreak);
    WSType wsType;
    wsObj.PriorVisibleNode(pointToBreak, &wsType);
    if (wsType & WSType::block) {
      brElementIsAfterBlock = true;
    }
    wsObj.NextVisibleNode(pointToBreak, &wsType);
    if (wsType & WSType::block) {
      brElementIsBeforeBlock = true;
    }
    // If the container of the break is a link, we need to split it and
    // insert new <br> between the split links.
    RefPtr<Element> linkNode =
        HTMLEditor::GetLinkElement(pointToBreak.GetContainer());
    if (linkNode) {
      SplitNodeResult splitLinkNodeResult =
          HTMLEditorRef().SplitNodeDeepWithTransaction(
              *linkNode, pointToBreak,
              SplitAtEdges::eDoNotCreateEmptyContainer);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(splitLinkNodeResult.Failed())) {
        return splitLinkNodeResult.Rv();
      }
      pointToBreak = splitLinkNodeResult.SplitPoint();
    }
    brElement =
        wsObj.InsertBreak(*SelectionRefPtr(), pointToBreak, nsIEditor::eNone);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(!brElement)) {
      return NS_ERROR_FAILURE;
    }
  }

  // If the <br> element has already been removed from the DOM tree by a
  // mutation observer, don't continue handling this.
  if (NS_WARN_IF(!brElement->GetParentNode())) {
    return NS_ERROR_FAILURE;
  }

  if (brElementIsAfterBlock && brElementIsBeforeBlock) {
    // We just placed a <br> between block boundaries.  This is the one case
    // where we want the selection to be before the br we just placed, as the
    // br will be on a new line, rather than at end of prior line.
    // XXX brElementIsAfterBlock and brElementIsBeforeBlock were set before
    //     modifying the DOM tree.  So, now, the <br> element may not be
    //     between blocks.
    ErrorResult error;
    SelectionRefPtr()->SetInterlinePosition(true, error);
    NS_WARNING_ASSERTION(!error.Failed(), "Failed to set interline position");
    EditorRawDOMPoint point(brElement);
    error = NS_OK;
    SelectionRefPtr()->Collapse(point, 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;
  }

  EditorDOMPoint afterBRElement(brElement);
  DebugOnly<bool> advanced = afterBRElement.AdvanceOffset();
  NS_WARNING_ASSERTION(advanced,
                       "Failed to advance offset after the new <br> element");
  WSRunObject wsObj(&HTMLEditorRef(), afterBRElement);
  nsCOMPtr<nsINode> maybeSecondBRNode;
  WSType wsType;
  wsObj.NextVisibleNode(afterBRElement, address_of(maybeSecondBRNode), nullptr,
                        &wsType);
  if (wsType == WSType::br) {
    // The next thing after the break we inserted is another break.  Move the
    // second break to be the first break's sibling.  This will prevent them
    // from being in different inline nodes, which would break
    // SetInterlinePosition().  It will also assure that if the user clicks
    // away and then clicks back on their new blank line, they will still get
    // the style from the line above.
    EditorDOMPoint atSecondBRElement(maybeSecondBRNode);
    if (brElement->GetNextSibling() != maybeSecondBRNode) {
      nsresult rv = HTMLEditorRef().MoveNodeWithTransaction(
          *maybeSecondBRNode->AsContent(), afterBRElement);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }
  }

  // SetInterlinePosition(true) means we want the caret to stick to the
  // content on the "right".  We want the caret to stick to whatever is past
  // the break.  This is because the break is on the same line we were on,
  // but the next content will be on the following line.

  // An exception to this is if the break has a next sibling that is a block
  // node.  Then we stick to the left to avoid an uber caret.
  nsIContent* nextSiblingOfBRElement = brElement->GetNextSibling();
  ErrorResult error;
  SelectionRefPtr()->SetInterlinePosition(
      !(nextSiblingOfBRElement && IsBlockNode(*nextSiblingOfBRElement)), error);
  NS_WARNING_ASSERTION(!error.Failed(),
                       "Failed to set or unset interline position");
  error = NS_OK;
  SelectionRefPtr()->Collapse(afterBRElement, 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;
}

EditActionResult HTMLEditRules::SplitMailCites() {
  MOZ_ASSERT(IsEditorDataAvailable());

  EditorRawDOMPoint pointToSplit(EditorBase::GetStartPoint(*SelectionRefPtr()));
  if (NS_WARN_IF(!pointToSplit.IsSet())) {
    return EditActionIgnored(NS_ERROR_FAILURE);
  }

  RefPtr<Element> citeNode =
      GetTopEnclosingMailCite(*pointToSplit.GetContainer());
  if (!citeNode) {
    return EditActionIgnored();
  }

  // If our selection is just before a break, nudge it to be just after it.
  // This does two things for us.  It saves us the trouble of having to add
  // a break here ourselves to preserve the "blockness" of the inline span
  // mailquote (in the inline case), and :
  // it means the break won't end up making an empty line that happens to be
  // inside a mailquote (in either inline or block case).
  // The latter can confuse a user if they click there and start typing,
  // because being in the mailquote may affect wrapping behavior, or font
  // color, etc.
  WSRunObject wsObj(&HTMLEditorRef(), pointToSplit);
  nsCOMPtr<nsINode> visNode;
  WSType wsType;
  wsObj.NextVisibleNode(pointToSplit, address_of(visNode), nullptr, &wsType);
  // If selection start point is before a break and it's inside the mailquote,
  // let's split it after the visible node.
  if (wsType == WSType::br && visNode != citeNode &&
      citeNode->Contains(visNode)) {
    pointToSplit.Set(visNode);
    DebugOnly<bool> advanced = pointToSplit.AdvanceOffset();
    NS_WARNING_ASSERTION(advanced,
                         "Failed to advance offset to after the visible node");
  }

  if (NS_WARN_IF(!pointToSplit.GetContainerAsContent())) {
    return EditActionIgnored(NS_ERROR_FAILURE);
  }

  SplitNodeResult splitCiteNodeResult =
      HTMLEditorRef().SplitNodeDeepWithTransaction(
          *citeNode, pointToSplit, SplitAtEdges::eDoNotCreateEmptyContainer);
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
  }
  if (NS_WARN_IF(splitCiteNodeResult.Failed())) {
    return EditActionIgnored(splitCiteNodeResult.Rv());
  }
  pointToSplit.Clear();

  // Add an invisible <br> to the end of current cite node (If new left cite
  // has not been created, we're at the end of it.  Otherwise, we're still at
  // the right node) if it was a <span> of style="display: block". This is
  // important, since when serializing the cite to plain text, the span which
  // caused the visual break is discarded.  So the added <br> will guarantee
  // that the serializer will insert a break where the user saw one.
  // FYI: splitCiteNodeResult grabs the previous node with nsCOMPtr.  So, it's
  //      safe to access previousNodeOfSplitPoint even after changing the DOM
  //      tree and/or selection even though it's raw pointer.
  nsIContent* previousNodeOfSplitPoint = splitCiteNodeResult.GetPreviousNode();
  if (previousNodeOfSplitPoint &&
      previousNodeOfSplitPoint->IsHTMLElement(nsGkAtoms::span) &&
      previousNodeOfSplitPoint->GetPrimaryFrame() &&
      previousNodeOfSplitPoint->GetPrimaryFrame()->IsBlockFrameOrSubclass()) {
    nsCOMPtr<nsINode> lastChild = previousNodeOfSplitPoint->GetLastChild();
    if (lastChild && !lastChild->IsHTMLElement(nsGkAtoms::br)) {
      // We ignore the result here.
      EditorRawDOMPoint endOfPreviousNodeOfSplitPoint;
      endOfPreviousNodeOfSplitPoint.SetToEndOf(previousNodeOfSplitPoint);
      RefPtr<Element> invisibleBrElement =
          HTMLEditorRef().InsertBrElementWithTransaction(
              endOfPreviousNodeOfSplitPoint);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
      }
      NS_WARNING_ASSERTION(invisibleBrElement,
                           "Failed to create an invisible <br> element");
    }
  }

  // In most cases, <br> should be inserted after current cite.  However, if
  // left cite hasn't been created because the split point was start of the
  // cite node, <br> should be inserted before the current cite.
  EditorRawDOMPoint pointToInsertBrNode(splitCiteNodeResult.SplitPoint());
  RefPtr<Element> brElement =
      HTMLEditorRef().InsertBrElementWithTransaction(pointToInsertBrNode);
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
  }
  if (NS_WARN_IF(!brElement)) {
    return EditActionIgnored(NS_ERROR_FAILURE);
  }
  // Now, offset of pointToInsertBrNode is invalid.  Let's clear it.
  pointToInsertBrNode.Clear();

  // Want selection before the break, and on same line.
  EditorDOMPoint atBrNode(brElement);
  Unused << atBrNode.Offset();  // Needs offset after collapsing the selection.
  ErrorResult error;
  SelectionRefPtr()->SetInterlinePosition(true, error);
  NS_WARNING_ASSERTION(!error.Failed(), "Failed to set interline position");
  error = NS_OK;
  SelectionRefPtr()->Collapse(atBrNode, error);
  if (NS_WARN_IF(!CanHandleEditAction())) {
    error.SuppressException();
    return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
  }
  if (NS_WARN_IF(error.Failed())) {
    return EditActionIgnored(error.StealNSResult());
  }

  // if citeNode wasn't a block, we might also want another break before it.
  // We need to examine the content both before the br we just added and also
  // just after it.  If we don't have another br or block boundary adjacent,
  // then we will need a 2nd br added to achieve blank line that user expects.
  if (IsInlineNode(*citeNode)) {
    // Use DOM point which we tried to collapse to.
    EditorRawDOMPoint pointToCreateNewBrNode(atBrNode.GetContainer(),
                                             atBrNode.Offset());

    WSRunObject wsObj(&HTMLEditorRef(), pointToCreateNewBrNode);
    WSType wsType;
    wsObj.PriorVisibleNode(pointToCreateNewBrNode, nullptr, nullptr, &wsType);
    if (wsType == WSType::normalWS || wsType == WSType::text ||
        wsType == WSType::special) {
      EditorRawDOMPoint pointAfterNewBrNode(pointToCreateNewBrNode);
      DebugOnly<bool> advanced = pointAfterNewBrNode.AdvanceOffset();
      NS_WARNING_ASSERTION(advanced,
                           "Failed to advance offset after the <br> node");
      WSRunObject wsObjAfterBR(&HTMLEditorRef(), pointAfterNewBrNode);
      wsObjAfterBR.NextVisibleNode(pointAfterNewBrNode, &wsType);
      if (wsType == WSType::normalWS || wsType == WSType::text ||
          wsType == WSType::special ||
          // In case we're at the very end.
          wsType == WSType::thisBlock) {
        brElement = HTMLEditorRef().InsertBrElementWithTransaction(
            pointToCreateNewBrNode);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
        }
        if (NS_WARN_IF(!brElement)) {
          return EditActionIgnored(NS_ERROR_FAILURE);
        }
        // Now, those points may be invalid.
        pointToCreateNewBrNode.Clear();
        pointAfterNewBrNode.Clear();
      }
    }
  }

  // delete any empty cites
  bool bEmptyCite = false;
  if (previousNodeOfSplitPoint) {
    nsresult rv = HTMLEditorRef().IsEmptyNode(previousNodeOfSplitPoint,
                                              &bEmptyCite, true, false);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return EditActionIgnored(rv);
    }
    if (bEmptyCite) {
      rv = HTMLEditorRef().DeleteNodeWithTransaction(*previousNodeOfSplitPoint);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return EditActionIgnored(rv);
      }
    }
  }

  if (citeNode) {
    nsresult rv =
        HTMLEditorRef().IsEmptyNode(citeNode, &bEmptyCite, true, false);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return EditActionIgnored(rv);
    }
    if (bEmptyCite) {
      rv = HTMLEditorRef().DeleteNodeWithTransaction(*citeNode);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return EditActionIgnored(rv);
      }
    }
  }

  return EditActionHandled();
}

nsresult HTMLEditRules::WillDeleteSelection(
    nsIEditor::EDirection aAction, nsIEditor::EStripWrappers aStripWrappers,
    bool* aCancel, bool* aHandled) {
  MOZ_ASSERT(IsEditorDataAvailable());
  MOZ_ASSERT(aStripWrappers == nsIEditor::eStrip ||
             aStripWrappers == nsIEditor::eNoStrip);

  if (NS_WARN_IF(!aCancel) || NS_WARN_IF(!aHandled)) {
    return NS_ERROR_INVALID_ARG;
  }
  // Initialize out params
  *aCancel = false;
  *aHandled = false;

  // Remember that we did a selection deletion.  Used by
  // CreateStyleForInsertText()
  mDidDeleteSelection = true;

  // If there is only bogus content, cancel the operation
  if (mBogusNode) {
    *aCancel = true;
    return NS_OK;
  }

  // First check for table selection mode.  If so, hand off to table editor.
  ErrorResult error;
  RefPtr<Element> cellElement =
      HTMLEditorRef().GetFirstSelectedTableCellElement(error);
  if (cellElement) {
    error.SuppressException();
    nsresult rv = HTMLEditorRef().DeleteTableCellContentsWithTransaction();
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    *aHandled = true;
    return rv;
  }
  nsresult rv = error.StealNSResult();
  cellElement = nullptr;

  // origCollapsed is used later to determine whether we should join blocks. We
  // don't really care about bCollapsed because it will be modified by
  // ExtendSelectionForDelete later. TryToJoinBlocksWithTransaction() should
  // happen if the original selection is collapsed and the cursor is at the end
  // of a block element, in which case ExtendSelectionForDelete would always
  // make the selection not collapsed.
  bool join = false;
  bool origCollapsed = SelectionRefPtr()->IsCollapsed();

  if (origCollapsed) {
    EditorDOMPoint startPoint(EditorBase::GetStartPoint(*SelectionRefPtr()));
    if (NS_WARN_IF(!startPoint.IsSet())) {
      return NS_ERROR_FAILURE;
    }

    // If we are inside an empty block, delete it.
    RefPtr<Element> host = HTMLEditorRef().GetActiveEditingHost();
    if (NS_WARN_IF(!host)) {
      return NS_ERROR_FAILURE;
    }

    {
      AutoEditorDOMPointChildInvalidator lockOffset(startPoint);

      rv = MaybeDeleteTopMostEmptyAncestor(*startPoint.GetContainer(), *host,
                                           aAction, aHandled);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      if (*aHandled) {
        return NS_OK;
      }
    }

    // Test for distance between caret and text that will be deleted
    rv = CheckBidiLevelForDeletion(startPoint, aAction, aCancel);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    if (*aCancel) {
      return NS_OK;
    }

    // ExtendSelectionForDelete will use selection controller to set
    // selection for delete.  But if focus event doesn't receive yet,
    // ancestor isn't set.  So we must set root eleement of editor to
    // ancestor.
    AutoSetTemporaryAncestorLimiter autoSetter(
        HTMLEditorRef(), *SelectionRefPtr(), *startPoint.GetContainer());

    rv = HTMLEditorRef().ExtendSelectionForDelete(&aAction);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    // We should delete nothing.
    if (aAction == nsIEditor::eNone) {
      return NS_OK;
    }
  }

  if (SelectionRefPtr()->IsCollapsed()) {
    // ExtendSelectionForDelete() won't change the selection.

    EditorDOMPoint startPoint(EditorBase::GetStartPoint(*SelectionRefPtr()));
    if (NS_WARN_IF(!startPoint.IsSet())) {
      return NS_ERROR_FAILURE;
    }

    // What's in the direction we are deleting?
    WSRunObject wsObj(&HTMLEditorRef(), startPoint);
    nsCOMPtr<nsINode> visNode;
    int32_t visOffset;
    WSType wsType;

    // Find next visible node
    if (aAction == nsIEditor::eNext) {
      wsObj.NextVisibleNode(startPoint, address_of(visNode), &visOffset,
                            &wsType);
    } else {
      wsObj.PriorVisibleNode(startPoint, address_of(visNode), &visOffset,
                             &wsType);
    }

    if (!visNode) {
      // Can't find anything to delete!
      *aCancel = true;
      // XXX This is the result of
      //     HTMLEditorRef().GetFirstSelectedTableCellElement().
      //     The value could be both an error and NS_OK.
      return rv;
    }

    if (wsType == WSType::normalWS) {
      // We found some visible ws to delete.  Let ws code handle it.
      *aHandled = true;
      if (aAction == nsIEditor::eNext) {
        rv = wsObj.DeleteWSForward();
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      } else {
        rv = wsObj.DeleteWSBackward();
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }
      rv = InsertBRIfNeeded();
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      return NS_OK;
    }

    if (wsType == WSType::text) {
      // Found normal text to delete.
      OwningNonNull<Text> nodeAsText = *visNode->GetAsText();
      int32_t so = visOffset;
      int32_t eo = visOffset + 1;
      if (aAction == nsIEditor::ePrevious) {
        if (!so) {
          return NS_ERROR_UNEXPECTED;
        }
        so--;
        eo--;
        // Bug 1068979: delete both codepoints if surrogate pair
        if (so > 0) {
          const nsTextFragment* text = nodeAsText->GetText();
          if (NS_IS_LOW_SURROGATE(text->CharAt(so)) &&
              NS_IS_HIGH_SURROGATE(text->CharAt(so - 1))) {
            so--;
          }
        }
      } else {
        RefPtr<nsRange> range = SelectionRefPtr()->GetRangeAt(0);
        if (NS_WARN_IF(!range)) {
          return NS_ERROR_FAILURE;
        }

        NS_ASSERTION(range->GetStartContainer() == visNode,
                     "selection start not in visNode");
        NS_ASSERTION(range->GetEndContainer() == visNode,
                     "selection end not in visNode");

        so = range->StartOffset();
        eo = range->EndOffset();
      }
      rv = WSRunObject::PrepareToDeleteRange(
          &HTMLEditorRef(), address_of(visNode), &so, address_of(visNode), &eo);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      *aHandled = true;
      rv = HTMLEditorRef().DeleteTextWithTransaction(
          nodeAsText, std::min(so, eo), DeprecatedAbs(eo - so));
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      // XXX When Backspace key is pressed, Chromium removes following empty
      //     text nodes when removing the last character of the non-empty text
      //     node.  However, Edge never removes empty text nodes even if
      //     selection is in the following empty text node(s).  For now, we
      //     should keep our traditional behavior same as Edge for backward
      //     compatibility.
      // XXX When Delete key is pressed, Edge removes all preceding empty
      //     text nodes when removing the first character of the non-empty
      //     text node.  Chromium removes only selected empty text node and
      //     following empty text nodes and the first character of the
      //     non-empty text node.  For now, we should keep our traditional
      //     behavior same as Chromium for backward compatibility.

      rv = DeleteNodeIfCollapsedText(nodeAsText);
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to remove collapsed text");

      rv = InsertBRIfNeeded();
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      // Remember that we did a ranged delete for the benefit of
      // AfterEditInner().
      mDidRangedDelete = true;

      return NS_OK;
    }

    if (wsType == WSType::special || wsType == WSType::br ||
        visNode->IsHTMLElement(nsGkAtoms::hr)) {
      // Short circuit for invisible breaks.  delete them and recurse.
      if (visNode->IsHTMLElement(nsGkAtoms::br) &&
          !HTMLEditorRef().IsVisibleBRElement(visNode)) {
        rv = HTMLEditorRef().DeleteNodeWithTransaction(*visNode);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
        rv = WillDeleteSelection(aAction, aStripWrappers, aCancel, aHandled);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
        return NS_OK;
      }

      // Special handling for backspace when positioned after <hr>
      if (aAction == nsIEditor::ePrevious &&
          visNode->IsHTMLElement(nsGkAtoms::hr)) {
        // Only if the caret is positioned at the end-of-hr-line position, we
        // want to delete the <hr>.
        //
        // In other words, we only want to delete, if our selection position
        // (indicated by startPoint) is the position directly
        // after the <hr>, on the same line as the <hr>.
        //
        // To detect this case we check:
        // startPoint's container == parentOfVisNode
        // and
        // startPoint's offset -1 == visNodeOffsetToVisNodeParent
        // and
        // interline position is false (left)
        //
        // In any other case we set the position to startPoint's container -1
        // and interlineposition to false, only moving the caret to the
        // end-of-hr-line position.
        bool moveOnly = true;

        EditorDOMPoint selPoint(visNode);

        ErrorResult err;
        bool interLineIsRight = SelectionRefPtr()->GetInterlinePosition(err);
        if (NS_WARN_IF(err.Failed())) {
          return err.StealNSResult();
        }

        if (startPoint.GetContainer() == selPoint.GetContainer() &&
            startPoint.Offset() - 1 == selPoint.Offset() && !interLineIsRight) {
          moveOnly = false;
        }

        if (moveOnly) {
          // Go to the position after the <hr>, but to the end of the <hr> line
          // by setting the interline position to left.
          DebugOnly<bool> advanced = selPoint.AdvanceOffset();
          NS_WARNING_ASSERTION(advanced,
                               "Failed to advance offset after <hr> element");

          {
            AutoEditorDOMPointChildInvalidator lockOffset(selPoint);

            IgnoredErrorResult ignoredError;
            SelectionRefPtr()->Collapse(selPoint, ignoredError);
            if (NS_WARN_IF(!CanHandleEditAction())) {
              return NS_ERROR_EDITOR_DESTROYED;
            }
            NS_WARNING_ASSERTION(
                !ignoredError.Failed(),
                "Failed to collapse selection at after the <hr>");
          }

          IgnoredErrorResult ignoredError;
          SelectionRefPtr()->SetInterlinePosition(false, ignoredError);
          NS_WARNING_ASSERTION(!ignoredError.Failed(),
                               "Failed to unset interline position");
          mDidExplicitlySetInterline = true;
          *aHandled = true;

          // There is one exception to the move only case.  If the <hr> is
          // followed by a <br> we want to delete the <br>.

          WSType otherWSType;
          nsCOMPtr<nsINode> otherNode;

          wsObj.NextVisibleNode(startPoint, address_of(otherNode), nullptr,
                                &otherWSType);

          if (otherWSType == WSType::br) {
            // Delete the <br>
            if (NS_WARN_IF(!otherNode->IsContent())) {
              return NS_ERROR_FAILURE;
            }
            nsIContent* otherContent = otherNode->AsContent();
            rv = WSRunObject::PrepareToDeleteNode(&HTMLEditorRef(),
                                                  otherContent);
            if (NS_WARN_IF(!CanHandleEditAction())) {
              return NS_ERROR_EDITOR_DESTROYED;
            }
            if (NS_WARN_IF(NS_FAILED(rv))) {
              return rv;
            }
            rv = HTMLEditorRef().DeleteNodeWithTransaction(*otherContent);
            if (NS_WARN_IF(!CanHandleEditAction())) {
              return NS_ERROR_EDITOR_DESTROYED;
            }
            if (NS_WARN_IF(NS_FAILED(rv))) {
              return rv;
            }
          }

          return NS_OK;
        }
        // Else continue with normal delete code
      }

      if (NS_WARN_IF(!visNode->IsContent())) {
        return NS_ERROR_FAILURE;
      }
      // Found break or image, or hr.
      rv = WSRunObject::PrepareToDeleteNode(&HTMLEditorRef(),
                                            visNode->AsContent());
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      // Remember sibling to visnode, if any
      nsCOMPtr<nsIContent> sibling =
          HTMLEditorRef().GetPriorHTMLSibling(visNode);
      // Delete the node, and join like nodes if appropriate
      rv = HTMLEditorRef().DeleteNodeWithTransaction(*visNode);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      // We did something, so let's say so.
      *aHandled = true;
      // Is there a prior node and are they siblings?
      nsCOMPtr<nsINode> stepbrother;
      if (sibling) {
        stepbrother = HTMLEditorRef().GetNextHTMLSibling(sibling);
      }
      // Are they both text nodes?  If so, join them!
      if (startPoint.GetContainer() == stepbrother &&
          startPoint.GetContainerAsText() && sibling->GetAsText()) {
        EditorDOMPoint pt;
        nsresult rv = JoinNearestEditableNodesWithTransaction(
            *sibling, *startPoint.GetContainerAsContent(), &pt);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
        if (NS_WARN_IF(!pt.IsSet())) {
          return NS_ERROR_FAILURE;
        }
        // Fix up selection
        ErrorResult error;
        SelectionRefPtr()->Collapse(pt, error);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          error.SuppressException();
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(error.Failed())) {
          return error.StealNSResult();
        }
      }
      rv = InsertBRIfNeeded();
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      return NS_OK;
    }

    if (wsType == WSType::otherBlock) {
      // Make sure it's not a table element.  If so, cancel the operation
      // (translation: users cannot backspace or delete across table cells)
      if (HTMLEditUtils::IsTableElement(visNode)) {
        *aCancel = true;
        return NS_OK;
      }

      // Next to a block.  See if we are between a block and a br.  If so, we
      // really want to delete the br.  Else join content at selection to the
      // block.
      bool bDeletedBR = false;
      WSType otherWSType;
      nsCOMPtr<nsINode> otherNode;

      // Find node in other direction
      if (aAction == nsIEditor::eNext) {
        wsObj.PriorVisibleNode(startPoint, address_of(otherNode), nullptr,
                               &otherWSType);
      } else {
        wsObj.NextVisibleNode(startPoint, address_of(otherNode), nullptr,
                              &otherWSType);
      }

      // First find the adjacent node in the block
      nsCOMPtr<nsIContent> leafNode;
      nsCOMPtr<nsINode> leftNode, rightNode;
      if (aAction == nsIEditor::ePrevious) {
        leafNode = HTMLEditorRef().GetLastEditableLeaf(*visNode);
        leftNode = leafNode;
        rightNode = startPoint.GetContainer();
      } else {
        leafNode = HTMLEditorRef().GetFirstEditableLeaf(*visNode);
        leftNode = startPoint.GetContainer();
        rightNode = leafNode;
      }

      if (otherNode->IsHTMLElement(nsGkAtoms::br)) {
        rv = HTMLEditorRef().DeleteNodeWithTransaction(*otherNode);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
        // XXX Only in this case, setting "handled" to true only when it
        //     succeeds?
        *aHandled = true;
        bDeletedBR = true;
      }

      // Don't cross table boundaries
      if (leftNode && rightNode &&
          InDifferentTableElements(leftNode, rightNode)) {
        return NS_OK;
      }

      if (bDeletedBR) {
        // Put selection at edge of block and we are done.
        if (NS_WARN_IF(!leafNode)) {
          return NS_ERROR_FAILURE;
        }
        EditorDOMPoint newSel = GetGoodSelPointForNode(*leafNode, aAction);
        if (NS_WARN_IF(!newSel.IsSet())) {
          return NS_ERROR_FAILURE;
        }
        IgnoredErrorResult error;
        SelectionRefPtr()->Collapse(newSel, error);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        NS_WARNING_ASSERTION(
            !error.Failed(),
            "Failed to collapse selection at edge of the block");
        return NS_OK;
      }

      // Else we are joining content to block

      EditorDOMPoint selPoint(startPoint);
      {
        AutoTrackDOMPoint tracker(HTMLEditorRef().RangeUpdaterRef(), &selPoint);
        if (NS_WARN_IF(!leftNode) || NS_WARN_IF(!leftNode->IsContent()) ||
            NS_WARN_IF(!rightNode) || NS_WARN_IF(!rightNode->IsContent())) {
          return NS_ERROR_FAILURE;
        }
        EditActionResult ret = TryToJoinBlocksWithTransaction(
            *leftNode->AsContent(), *rightNode->AsContent());
        *aHandled |= ret.Handled();
        *aCancel |= ret.Canceled();
        if (NS_WARN_IF(ret.Failed())) {
          return ret.Rv();
        }
      }

      // If TryToJoinBlocksWithTransaction() didn't handle it  and it's not
      // canceled, user may want to modify the start leaf node or the last leaf
      // node of the block.
      if (!*aHandled && !*aCancel && leafNode != startPoint.GetContainer()) {
        int32_t offset = aAction == nsIEditor::ePrevious
                             ? static_cast<int32_t>(leafNode->Length())
                             : 0;
        rv = SelectionRefPtr()->Collapse(leafNode, offset);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                             "Failed to collapse selection at the leaf node");
        rv = WillDeleteSelection(aAction, aStripWrappers, aCancel, aHandled);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
        return NS_OK;
      }

      // Otherwise, we must have deleted the selection as user expected.
      IgnoredErrorResult ignored;
      SelectionRefPtr()->Collapse(selPoint, ignored);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      NS_WARNING_ASSERTION(!ignored.Failed(),
                           "Failed to selection at deleted point");
      return NS_OK;
    }

    if (wsType == WSType::thisBlock) {
      // At edge of our block.  Look beside it and see if we can join to an
      // adjacent block

      // Make sure it's not a table element.  If so, cancel the operation
      // (translation: users cannot backspace or delete across table cells)
      if (HTMLEditUtils::IsTableElement(visNode)) {
        *aCancel = true;
        return NS_OK;
      }

      // First find the relevant nodes
      nsCOMPtr<nsINode> leftNode, rightNode;
      if (aAction == nsIEditor::ePrevious) {
        leftNode = HTMLEditorRef().GetPreviousEditableHTMLNode(*visNode);
        rightNode = startPoint.GetContainer();
      } else {
        rightNode = HTMLEditorRef().GetNextEditableHTMLNode(*visNode);
        leftNode = startPoint.GetContainer();
      }

      // Nothing to join
      if (!leftNode || !rightNode) {
        *aCancel = true;
        return NS_OK;
      }

      // Don't cross table boundaries -- cancel it
      if (InDifferentTableElements(leftNode, rightNode)) {
        *aCancel = true;
        return NS_OK;
      }

      EditorDOMPoint selPoint(startPoint);
      {
        AutoTrackDOMPoint tracker(HTMLEditorRef().RangeUpdaterRef(), &selPoint);
        if (NS_WARN_IF(!leftNode->IsContent()) ||
            NS_WARN_IF(!rightNode->IsContent())) {
          return NS_ERROR_FAILURE;
        }
        EditActionResult ret = TryToJoinBlocksWithTransaction(
            *leftNode->AsContent(), *rightNode->AsContent());
        // This should claim that trying to join the block means that
        // this handles the action because the caller shouldn't do anything
        // anymore in this case.
        *aHandled = true;
        *aCancel |= ret.Canceled();
        if (NS_WARN_IF(ret.Failed())) {
          return ret.Rv();
        }
      }
      IgnoredErrorResult ignored;
      SelectionRefPtr()->Collapse(selPoint, ignored);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      NS_WARNING_ASSERTION(!ignored.Failed(), "Failed to collapse selection");
      return NS_OK;
    }
  }

  // Else we have a non-collapsed selection.  First adjust the selection.
  rv = ExpandSelectionForDeletion();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Remember that we did a ranged delete for the benefit of AfterEditInner().
  mDidRangedDelete = true;

  // Refresh start and end points
  nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
  if (NS_WARN_IF(!firstRange)) {
    return NS_ERROR_FAILURE;
  }
  nsCOMPtr<nsINode> startNode = firstRange->GetStartContainer();
  if (NS_WARN_IF(!startNode)) {
    return NS_ERROR_FAILURE;
  }
  int32_t startOffset = firstRange->StartOffset();
  nsCOMPtr<nsINode> endNode = firstRange->GetEndContainer();
  if (NS_WARN_IF(!endNode)) {
    return NS_ERROR_FAILURE;
  }
  int32_t endOffset = firstRange->EndOffset();

  // Figure out if the endpoints are in nodes that can be merged.  Adjust
  // surrounding whitespace in preparation to delete selection.
  if (!IsPlaintextEditor()) {
    AutoTransactionsConserveSelection dontChangeMySelection(HTMLEditorRef());
    rv = WSRunObject::PrepareToDeleteRange(&HTMLEditorRef(),
                                           address_of(startNode), &startOffset,
                                           address_of(endNode), &endOffset);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }

  {
    // Track location of where we are deleting
    AutoTrackDOMPoint startTracker(HTMLEditorRef().RangeUpdaterRef(),
                                   address_of(startNode), &startOffset);
    AutoTrackDOMPoint endTracker(HTMLEditorRef().RangeUpdaterRef(),
                                 address_of(endNode), &endOffset);
    // We are handling all ranged deletions directly now.
    *aHandled = true;

    if (endNode == startNode) {
      rv = HTMLEditorRef().DeleteSelectionWithTransaction(aAction,
                                                          aStripWrappers);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    } else {
      // Figure out mailcite ancestors
      nsCOMPtr<Element> startCiteNode = GetTopEnclosingMailCite(*startNode);
      nsCOMPtr<Element> endCiteNode = GetTopEnclosingMailCite(*endNode);

      // If we only have a mailcite at one of the two endpoints, set the
      // directionality of the deletion so that the selection will end up
      // outside the mailcite.
      if (startCiteNode && !endCiteNode) {
        aAction = nsIEditor::eNext;
      } else if (!startCiteNode && endCiteNode) {
        aAction = nsIEditor::ePrevious;
      }

      // Figure out block parents
      nsCOMPtr<Element> leftParent = HTMLEditor::GetBlock(*startNode);
      nsCOMPtr<Element> rightParent = HTMLEditor::GetBlock(*endNode);

      // Are endpoint block parents the same?  Use default deletion
      if (leftParent && leftParent == rightParent) {
        HTMLEditorRef().DeleteSelectionWithTransaction(aAction, aStripWrappers);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
      } else {
        // Deleting across blocks.  Are the blocks of same type?
        if (NS_WARN_IF(!leftParent) || NS_WARN_IF(!rightParent)) {
          return NS_ERROR_FAILURE;
        }

        // Are the blocks siblings?
        nsCOMPtr<nsINode> leftBlockParent = leftParent->GetParentNode();
        nsCOMPtr<nsINode> rightBlockParent = rightParent->GetParentNode();

        // MOOSE: this could conceivably screw up a table.. fix me.
        if (leftBlockParent == rightBlockParent &&
            HTMLEditorRef().AreNodesSameType(*leftParent, *rightParent) &&
            // XXX What's special about these three types of block?
            (leftParent->IsHTMLElement(nsGkAtoms::p) ||
             HTMLEditUtils::IsListItem(leftParent) ||
             HTMLEditUtils::IsHeader(*leftParent))) {
          // First delete the selection
          rv = HTMLEditorRef().DeleteSelectionWithTransaction(aAction,
                                                              aStripWrappers);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return rv;
          }
          // Join blocks
          EditorDOMPoint pt = HTMLEditorRef().JoinNodesDeepWithTransaction(
              *leftParent, *rightParent);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          if (NS_WARN_IF(!pt.IsSet())) {
            return NS_ERROR_FAILURE;
          }
          // Fix up selection
          ErrorResult error;
          SelectionRefPtr()->Collapse(pt, 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;
        }

        // Else blocks not same type, or not siblings.  Delete everything
        // except table elements.
        join = true;

        AutoRangeArray arrayOfRanges(SelectionRefPtr());
        for (auto& range : arrayOfRanges.mRanges) {
          // Build a list of nodes in the range
          nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
          TrivialFunctor functor;
          DOMSubtreeIterator iter;
          nsresult rv = iter.Init(*range);
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return rv;
          }
          iter.AppendList(functor, arrayOfNodes);

          // Now that we have the list, delete non-table elements
          int32_t listCount = arrayOfNodes.Length();
          for (int32_t j = 0; j < listCount; j++) {
            OwningNonNull<nsINode> node = arrayOfNodes[0];
            nsresult rv = DeleteElementsExceptTableRelatedElements(node);
            if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
              return NS_ERROR_EDITOR_DESTROYED;
            }
            NS_WARNING_ASSERTION(
                NS_SUCCEEDED(rv),
                "Failed to elements except table related elements");
            arrayOfNodes.RemoveElementAt(0);
            // If something visible is deleted, no need to join.  Visible means
            // all nodes except non-visible textnodes and breaks.
            if (join && origCollapsed) {
              if (!node->IsContent()) {
                join = false;
                continue;
              }
              nsIContent* content = node->AsContent();
              if (Text* text = content->GetAsText()) {
                join = !HTMLEditorRef().IsInVisibleTextFrames(*text);
              } else {
                join = content->IsHTMLElement(nsGkAtoms::br) &&
                       !HTMLEditorRef().IsVisibleBRElement(node);
              }
            }
          }
        }

        // Check endpoints for possible text deletion.  We can assume that if
        // text node is found, we can delete to end or to begining as
        // appropriate, since the case where both sel endpoints in same text
        // node was already handled (we wouldn't be here)
        if (startNode->GetAsText() &&
            startNode->Length() > static_cast<uint32_t>(startOffset)) {
          // Delete to last character
          OwningNonNull<CharacterData> dataNode =
              *static_cast<CharacterData*>(startNode.get());
          rv = HTMLEditorRef().DeleteTextWithTransaction(
              dataNode, startOffset, startNode->Length() - startOffset);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return rv;
          }
        }
        if (endNode->GetAsText() && endOffset) {
          // Delete to first character
          OwningNonNull<CharacterData> dataNode =
              *static_cast<CharacterData*>(endNode.get());
          rv =
              HTMLEditorRef().DeleteTextWithTransaction(dataNode, 0, endOffset);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return rv;
          }
        }

        if (join) {
          EditActionResult ret =
              TryToJoinBlocksWithTransaction(*leftParent, *rightParent);
          MOZ_ASSERT(*aHandled);
          *aCancel |= ret.Canceled();
          if (NS_WARN_IF(ret.Failed())) {
            return ret.Rv();
          }
        }
      }
    }
  }

  // We might have left only collapsed whitespace in the start/end nodes
  {
    AutoTrackDOMPoint startTracker(HTMLEditorRef().RangeUpdaterRef(),
                                   address_of(startNode), &startOffset);
    AutoTrackDOMPoint endTracker(HTMLEditorRef().RangeUpdaterRef(),
                                 address_of(endNode), &endOffset);

    nsresult rv = DeleteNodeIfCollapsedText(*startNode);
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rv),
        "Failed to delete start node even though it's collapsed text");
    rv = DeleteNodeIfCollapsedText(*endNode);
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rv),
        "Failed to delete end node even though it's collapsed text");
  }

  // If we're joining blocks: if deleting forward the selection should be
  // collapsed to the end of the selection, if deleting backward the selection
  // should be collapsed to the beginning of the selection. But if we're not
  // joining then the selection should collapse to the beginning of the
  // selection if we'redeleting forward, because the end of the selection will
  // still be in the next block. And same thing for deleting backwards
  // (selection should collapse to the end, because the beginning will still be
  // in the first block). See Bug 507936
  if (aAction == (join ? nsIEditor::eNext : nsIEditor::ePrevious)) {
    rv = SelectionRefPtr()->Collapse(endNode, endOffset);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  } else {
    rv = SelectionRefPtr()->Collapse(startNode, startOffset);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }
  return NS_OK;
}

nsresult HTMLEditRules::DeleteNodeIfCollapsedText(nsINode& aNode) {
  MOZ_ASSERT(IsEditorDataAvailable());

  Text* text = aNode.GetAsText();
  if (!text) {
    return NS_OK;
  }

  if (HTMLEditorRef().IsVisibleTextNode(*text)) {
    return NS_OK;
  }

  nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(aNode);
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

nsresult HTMLEditRules::InsertBRIfNeeded() {
  MOZ_ASSERT(IsEditorDataAvailable());

  EditorRawDOMPoint atStartOfSelection(
      EditorBase::GetStartPoint(*SelectionRefPtr()));
  if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
    return NS_ERROR_FAILURE;
  }

  // inline elements don't need any br
  if (!IsBlockNode(*atStartOfSelection.GetContainer())) {
    return NS_OK;
  }

  // examine selection
  WSRunObject wsObj(&HTMLEditorRef(), atStartOfSelection);
  if (((wsObj.mStartReason & WSType::block) ||
       (wsObj.mStartReason & WSType::br)) &&
      (wsObj.mEndReason & WSType::block)) {
    // if we are tucked between block boundaries then insert a br
    // first check that we are allowed to
    if (HTMLEditorRef().CanContainTag(*atStartOfSelection.GetContainer(),
                                      *nsGkAtoms::br)) {
      RefPtr<Element> brElement =
          HTMLEditorRef().InsertBrElementWithTransaction(atStartOfSelection,
                                                         nsIEditor::ePrevious);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(!brElement)) {
        return NS_ERROR_FAILURE;
      }
      return NS_OK;
    }
  }
  return NS_OK;
}

EditorDOMPoint HTMLEditRules::GetGoodSelPointForNode(
    nsINode& aNode, nsIEditor::EDirection aAction) {
  MOZ_ASSERT(IsEditorDataAvailable());
  MOZ_ASSERT(aAction == nsIEditor::eNext || aAction == nsIEditor::eNextWord ||
             aAction == nsIEditor::ePrevious ||
             aAction == nsIEditor::ePreviousWord ||
             aAction == nsIEditor::eToBeginningOfLine ||
             aAction == nsIEditor::eToEndOfLine);

  bool isPreviousAction =
      (aAction == nsIEditor::ePrevious || aAction == nsIEditor::ePreviousWord ||
       aAction == nsIEditor::eToBeginningOfLine);

  if (aNode.GetAsText() || HTMLEditorRef().IsContainer(&aNode) ||
      NS_WARN_IF(!aNode.GetParentNode())) {
    return EditorDOMPoint(&aNode, isPreviousAction ? aNode.Length() : 0);
  }

  if (NS_WARN_IF(!aNode.IsContent())) {
    return EditorDOMPoint();
  }

  EditorDOMPoint ret(&aNode);
  if ((!aNode.IsHTMLElement(nsGkAtoms::br) ||
       HTMLEditorRef().IsVisibleBRElement(&aNode)) &&
      isPreviousAction) {
    ret.AdvanceOffset();
  }
  return ret;
}

EditActionResult HTMLEditRules::TryToJoinBlocksWithTransaction(
    nsIContent& aLeftNode, nsIContent& aRightNode) {
  MOZ_ASSERT(IsEditorDataAvailable());

  RefPtr<Element> leftBlock = HTMLEditorRef().GetBlock(aLeftNode);
  RefPtr<Element> rightBlock = HTMLEditorRef().GetBlock(aRightNode);

  // Sanity checks
  if (NS_WARN_IF(!leftBlock) || NS_WARN_IF(!rightBlock)) {
    return EditActionIgnored(NS_ERROR_NULL_POINTER);
  }
  if (NS_WARN_IF(leftBlock == rightBlock)) {
    return EditActionIgnored(NS_ERROR_UNEXPECTED);
  }

  if (HTMLEditUtils::IsTableElement(leftBlock) ||
      HTMLEditUtils::IsTableElement(rightBlock)) {
    // Do not try to merge table elements
    return EditActionCanceled();
  }

  // Make sure we don't try to move things into HR's, which look like blocks
  // but aren't containers
  if (leftBlock->IsHTMLElement(nsGkAtoms::hr)) {
    leftBlock = HTMLEditorRef().GetBlockNodeParent(leftBlock);
    if (NS_WARN_IF(!leftBlock)) {
      return EditActionIgnored(NS_ERROR_UNEXPECTED);
    }
  }
  if (rightBlock->IsHTMLElement(nsGkAtoms::hr)) {
    rightBlock = HTMLEditorRef().GetBlockNodeParent(rightBlock);
    if (NS_WARN_IF(!rightBlock)) {
      return EditActionIgnored(NS_ERROR_UNEXPECTED);
    }
  }

  // Bail if both blocks the same
  if (leftBlock == rightBlock) {
    return EditActionIgnored();
  }

  // Joining a list item to its parent is a NOP.
  if (HTMLEditUtils::IsList(leftBlock) &&
      HTMLEditUtils::IsListItem(rightBlock) &&
      rightBlock->GetParentNode() == leftBlock) {
    return EditActionHandled();
  }

  // Special rule here: if we are trying to join list items, and they are in
  // different lists, join the lists instead.
  bool mergeLists = false;
  nsAtom* existingList = nsGkAtoms::_empty;
  EditorDOMPoint atChildInBlock;
  nsCOMPtr<Element> leftList, rightList;
  if (HTMLEditUtils::IsListItem(leftBlock) &&
      HTMLEditUtils::IsListItem(rightBlock)) {
    leftList = leftBlock->GetParentElement();
    rightList = rightBlock->GetParentElement();
    if (leftList && rightList && leftList != rightList &&
        !EditorUtils::IsDescendantOf(*leftList, *rightBlock, &atChildInBlock) &&
        !EditorUtils::IsDescendantOf(*rightList, *leftBlock, &atChildInBlock)) {
      // There are some special complications if the lists are descendants of
      // the other lists' items.  Note that it is okay for them to be
      // descendants of the other lists themselves, which is the usual case for
      // sublists in our implementation.
      MOZ_DIAGNOSTIC_ASSERT(!atChildInBlock.IsSet());
      leftBlock = leftList;
      rightBlock = rightList;
      mergeLists = true;
      existingList = leftList->NodeInfo()->NameAtom();
    }
  }

  AutoTransactionsConserveSelection dontChangeMySelection(HTMLEditorRef());

  // offset below is where you find yourself in rightBlock when you traverse
  // upwards from leftBlock
  EditorDOMPoint atRightBlockChild;
  if (EditorUtils::IsDescendantOf(*leftBlock, *rightBlock,
                                  &atRightBlockChild)) {
    // Tricky case.  Left block is inside right block.  Do ws adjustment.  This
    // just destroys non-visible ws at boundaries we will be joining.
    DebugOnly<bool> advanced = atRightBlockChild.AdvanceOffset();
    NS_WARNING_ASSERTION(
        advanced,
        "Failed to advance offset to after child of rightBlock, "
        "leftBlock is a descendant of the child");
    nsresult rv = WSRunObject::ScrubBlockBoundary(
        &HTMLEditorRef(), WSRunObject::kBlockEnd, leftBlock);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
    }
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return EditActionIgnored(rv);
    }

    {
      // We can't just track rightBlock because it's an Element.
      AutoTrackDOMPoint tracker(HTMLEditorRef().RangeUpdaterRef(),
                                &atRightBlockChild);
      rv = WSRunObject::ScrubBlockBoundary(
          &HTMLEditorRef(), WSRunObject::kAfterBlock,
          atRightBlockChild.GetContainer(), atRightBlockChild.Offset());
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return EditActionIgnored(rv);
      }

      // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here.
      //     Do we really need to do update rightBlock here??
      MOZ_ASSERT(rightBlock == atRightBlockChild.GetContainer());
      if (atRightBlockChild.GetContainerAsElement()) {
        rightBlock = atRightBlockChild.GetContainerAsElement();
      } else {
        if (NS_WARN_IF(!atRightBlockChild.GetContainer()->GetParentElement())) {
          return EditActionIgnored(NS_ERROR_UNEXPECTED);
        }
        rightBlock = atRightBlockChild.GetContainer()->GetParentElement();
      }
    }

    // Do br adjustment.
    RefPtr<Element> brNode =
        CheckForInvisibleBR(*leftBlock, BRLocation::blockEnd);
    EditActionResult ret(NS_OK);
    if (NS_WARN_IF(mergeLists)) {
      // Since 2002, here was the following comment:
      // > The idea here is to take all children in rightList that are past
      // > offset, and pull them into leftlist.
      // However, this has never been performed because we are here only when
      // neither left list nor right list is a descendant of the other but
      // in such case, getting a list item in the right list node almost
      // always failed since a variable for offset of rightList->GetChildAt()
      // was not initialized.  So, it might be a bug, but we should keep this
      // traditional behavior for now.  If you find when we get here, please
      // remove this comment if we don't need to do it.  Otherwise, please
      // move children of the right list node to the end of the left list node.
      MOZ_DIAGNOSTIC_ASSERT(!atChildInBlock.IsSet());

      // XXX Although, we don't do nothing here, but for keeping traditional
      //     behavior, we should mark as handled.
      ret.MarkAsHandled();
    } else {
      // XXX Why do we ignore the result of MoveBlock()?
      EditActionResult retMoveBlock =
          MoveBlock(*leftBlock, *rightBlock, -1, atRightBlockChild.Offset());
      if (NS_WARN_IF(retMoveBlock.Rv() == NS_ERROR_EDITOR_DESTROYED)) {
        return ret;
      }
      NS_WARNING_ASSERTION(
          retMoveBlock.Succeeded(),
          "Failed to move contents of the right block to the left block");
      if (retMoveBlock.Handled()) {
        ret.MarkAsHandled();
      }
      // Now, all children of rightBlock were moved to leftBlock.  So,
      // atRightBlockChild is now invalid.
      atRightBlockChild.Clear();
    }
    if (brNode) {
      nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(*brNode);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
      }
      if (NS_SUCCEEDED(rv)) {
        ret.MarkAsHandled();
      } else {
        NS_WARNING("Failed to remove the <br> element");
      }
    }
    return ret;
  }

  MOZ_DIAGNOSTIC_ASSERT(!atRightBlockChild.IsSet());

  // Offset below is where you find yourself in leftBlock when you traverse
  // upwards from rightBlock
  EditorDOMPoint leftBlockChild;
  if (EditorUtils::IsDescendantOf(*rightBlock, *leftBlock, &leftBlockChild)) {
    // Tricky case.  Right block is inside left block.  Do ws adjustment.  This
    // just destroys non-visible ws at boundaries we will be joining.
    nsresult rv = WSRunObject::ScrubBlockBoundary(
        &HTMLEditorRef(), WSRunObject::kBlockStart, rightBlock);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
    }
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return EditActionIgnored(rv);
    }

    {
      // We can't just track leftBlock because it's an Element, so track
      // something else.
      AutoTrackDOMPoint tracker(HTMLEditorRef().RangeUpdaterRef(),
                                &leftBlockChild);
      rv = WSRunObject::ScrubBlockBoundary(&HTMLEditorRef(),
                                           WSRunObject::kBeforeBlock, leftBlock,
                                           leftBlockChild.Offset());
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return EditActionIgnored(rv);
      }
      // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here.
      //     Do we really need to do update rightBlock here??
      MOZ_DIAGNOSTIC_ASSERT(leftBlock == leftBlockChild.GetContainer());
      if (leftBlockChild.GetContainerAsElement()) {
        leftBlock = leftBlockChild.GetContainerAsElement();
      } else {
        if (NS_WARN_IF(!leftBlockChild.GetContainer()->GetParentElement())) {
          return EditActionIgnored(NS_ERROR_UNEXPECTED);
        }
        leftBlock = leftBlockChild.GetContainer()->GetParentElement();
      }
    }
    // Do br adjustment.
    RefPtr<Element> brNode = CheckForInvisibleBR(
        *leftBlock, BRLocation::beforeBlock, leftBlockChild.Offset());
    EditActionResult ret(NS_OK);
    if (mergeLists) {
      // XXX Why do we ignore the result of MoveContents()?
      int32_t offset = leftBlockChild.Offset();
      EditActionResult retMoveContents =
          MoveContents(*rightList, *leftList, &offset);
      if (NS_WARN_IF(retMoveContents.Rv() == NS_ERROR_EDITOR_DESTROYED)) {
        return ret;
      }
      NS_WARNING_ASSERTION(
          retMoveContents.Succeeded(),
          "Failed to move contents from the right list to the left list");
      if (retMoveContents.Handled()) {
        ret.MarkAsHandled();
      }
      // leftBlockChild was moved to rightList.  So, it's invalid now.
      leftBlockChild.Clear();
    } else {
      // Left block is a parent of right block, and the parent of the previous
      // visible content.  Right block is a child and contains the contents we
      // want to move.

      EditorDOMPoint previousContent;
      if (&aLeftNode == leftBlock) {
        // We are working with valid HTML, aLeftNode is a block node, and is
        // therefore allowed to contain rightBlock.  This is the simple case,
        // we will simply move the content in rightBlock out of its block.
        previousContent = leftBlockChild;
      } else {
        // We try to work as well as possible with HTML that's already invalid.
        // Although "right block" is a block, and a block must not be contained
        // in inline elements, reality is that broken documents do exist.  The
        // DIRECT parent of "left NODE" might be an inline element.  Previous
        // versions of this code skipped inline parents until the first block
        // parent was found (and used "left block" as the destination).
        // However, in some situations this strategy moves the content to an
        // unexpected position.  (see bug 200416) The new idea is to make the
        // moving content a sibling, next to the previous visible content.
        previousContent.Set(&aLeftNode);

        // We want to move our content just after the previous visible node.
        previousContent.AdvanceOffset();
      }

      // Because we don't want the moving content to receive the style of the
      // previous content, we split the previous content's style.

      RefPtr<Element> editorRoot = HTMLEditorRef().GetEditorRoot();
      if (!editorRoot || &aLeftNode != editorRoot) {
        nsCOMPtr<nsIContent> splittedPreviousContent;
        nsCOMPtr<nsINode> previousContentParent =
            previousContent.GetContainer();
        int32_t previousContentOffset = previousContent.Offset();
        rv = HTMLEditorRef().SplitStyleAbovePoint(
            address_of(previousContentParent), &previousContentOffset, nullptr,
            nullptr, nullptr, getter_AddRefs(splittedPreviousContent));
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return EditActionIgnored(rv);
        }

        if (splittedPreviousContent) {
          previousContent.Set(splittedPreviousContent);
        } else {
          previousContent.Set(previousContentParent, previousContentOffset);
        }
      }

      if (NS_WARN_IF(!previousContent.IsSet())) {
        return EditActionIgnored(NS_ERROR_NULL_POINTER);
      }

      ret |= MoveBlock(*previousContent.GetContainerAsElement(), *rightBlock,
                       previousContent.Offset(), 0);
      if (NS_WARN_IF(ret.Failed())) {
        return ret;
      }
    }
    if (brNode) {
      nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(*brNode);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return ret.SetResult(NS_ERROR_EDITOR_DESTROYED);
      }
      if (NS_SUCCEEDED(rv)) {
        ret.MarkAsHandled();
      } else {
        NS_WARNING("Failed to remove the <br> element");
      }
    }
    return ret;
  }

  MOZ_DIAGNOSTIC_ASSERT(!atRightBlockChild.IsSet());
  MOZ_DIAGNOSTIC_ASSERT(!leftBlockChild.IsSet());

  // Normal case.  Blocks are siblings, or at least close enough.  An example
  // of the latter is <p>paragraph</p><ul><li>one<li>two<li>three</ul>.  The
  // first li and the p are not true siblings, but we still want to join them
  // if you backspace from li into p.

  // Adjust whitespace at block boundaries
  nsresult rv =
      WSRunObject::PrepareToJoinBlocks(&HTMLEditorRef(), leftBlock, rightBlock);
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
  }
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return EditActionIgnored(rv);
  }
  // Do br adjustment.
  nsCOMPtr<Element> brNode =
      CheckForInvisibleBR(*leftBlock, BRLocation::blockEnd);
  EditActionResult ret(NS_OK);
  if (mergeLists ||
      leftBlock->NodeInfo()->NameAtom() == rightBlock->NodeInfo()->NameAtom()) {
    // Nodes are same type.  merge them.
    EditorDOMPoint pt;
    nsresult rv =
        JoinNearestEditableNodesWithTransaction(*leftBlock, *rightBlock, &pt);
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
      return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
    }
    if (pt.IsSet() && mergeLists) {
      CreateElementResult convertListTypeResult =
          ConvertListType(*rightBlock, *existingList, *nsGkAtoms::li);
      if (NS_WARN_IF(convertListTypeResult.Rv() == NS_ERROR_EDITOR_DESTROYED)) {
        return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
      }
    }
    ret.MarkAsHandled();
  } else {
    // Nodes are dissimilar types.
    ret |= MoveBlock(*leftBlock, *rightBlock, -1, 0);
    if (NS_WARN_IF(ret.Failed())) {
      return ret;
    }
  }
  if (brNode) {
    rv = HTMLEditorRef().DeleteNodeWithTransaction(*brNode);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return ret.SetResult(NS_ERROR_EDITOR_DESTROYED);
    }
    // XXX In other top level if blocks, the result of
    //     DeleteNodeWithTransaction() is ignored.  Why does only this result
    //     is respected?
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return ret.SetResult(rv);
    }
    ret.MarkAsHandled();
  }
  return ret;
}

EditActionResult HTMLEditRules::MoveBlock(Element& aLeftBlock,
                                          Element& aRightBlock,
                                          int32_t aLeftOffset,
                                          int32_t aRightOffset) {
  MOZ_ASSERT(IsEditorDataAvailable());

  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  // GetNodesFromPoint is the workhorse that figures out what we wnat to move.
  nsresult rv = GetNodesFromPoint(EditorDOMPoint(&aRightBlock, aRightOffset),
                                  EditSubAction::eCreateOrChangeList,
                                  arrayOfNodes, TouchContent::yes);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return EditActionIgnored(rv);
  }

  EditActionResult ret(NS_OK);
  for (uint32_t i = 0; i < arrayOfNodes.Length(); i++) {
    // get the node to act on
    if (IsBlockNode(arrayOfNodes[i])) {
      // For block nodes, move their contents only, then delete block.
      ret |=
          MoveContents(*arrayOfNodes[i]->AsElement(), aLeftBlock, &aLeftOffset);
      if (NS_WARN_IF(ret.Failed())) {
        return ret;
      }
      rv = HTMLEditorRef().DeleteNodeWithTransaction(*arrayOfNodes[i]);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return ret.SetResult(NS_ERROR_EDITOR_DESTROYED);
      }
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to remove a block node");
      ret.MarkAsHandled();
    } else {
      // Otherwise move the content as is, checking against the DTD.
      ret |= MoveNodeSmart(*arrayOfNodes[i]->AsContent(), aLeftBlock,
                           &aLeftOffset);
      if (NS_WARN_IF(ret.Rv() == NS_ERROR_EDITOR_DESTROYED)) {
        return ret;
      }
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                           "Failed to move current node to the left block");
    }
  }

  // XXX We're only checking return value of the last iteration
  if (NS_WARN_IF(ret.Failed())) {
    return ret;
  }

  return ret;
}

EditActionResult HTMLEditRules::MoveNodeSmart(nsIContent& aNode,
                                              Element& aDestElement,
                                              int32_t* aInOutDestOffset) {
  MOZ_ASSERT(IsEditorDataAvailable());
  MOZ_ASSERT(aInOutDestOffset);

  // Check if this node can go into the destination node
  if (HTMLEditorRef().CanContain(aDestElement, aNode)) {
    // If it can, move it there.
    if (*aInOutDestOffset == -1) {
      nsresult rv =
          HTMLEditorRef().MoveNodeToEndWithTransaction(aNode, aDestElement);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return EditActionIgnored(rv);
      }
    } else {
      EditorRawDOMPoint pointToInsert(&aDestElement, *aInOutDestOffset);
      nsresult rv =
          HTMLEditorRef().MoveNodeWithTransaction(aNode, pointToInsert);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return EditActionIgnored(rv);
      }
    }
    if (*aInOutDestOffset != -1) {
      (*aInOutDestOffset)++;
    }
    // XXX Should we check if the node is actually moved in this case?
    return EditActionHandled();
  }

  // If it can't, move its children (if any), and then delete it.
  EditActionResult ret(NS_OK);
  if (aNode.IsElement()) {
    ret = MoveContents(*aNode.AsElement(), aDestElement, aInOutDestOffset);
    if (NS_WARN_IF(ret.Failed())) {
      return ret;
    }
  }

  nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(aNode);
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return ret.SetResult(NS_ERROR_EDITOR_DESTROYED);
  }
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return ret.SetResult(rv);
  }
  return ret.MarkAsHandled();
}

EditActionResult HTMLEditRules::MoveContents(Element& aElement,
                                             Element& aDestElement,
                                             int32_t* aInOutDestOffset) {
  MOZ_ASSERT(aInOutDestOffset);

  if (NS_WARN_IF(&aElement == &aDestElement)) {
    return EditActionIgnored(NS_ERROR_ILLEGAL_VALUE);
  }

  EditActionResult ret(NS_OK);
  while (aElement.GetFirstChild()) {
    ret |= MoveNodeSmart(*aElement.GetFirstChild(), aDestElement,
                         aInOutDestOffset);
    if (NS_WARN_IF(ret.Failed())) {
      return ret;
    }
  }
  return ret;
}

nsresult HTMLEditRules::DeleteElementsExceptTableRelatedElements(
    nsINode& aNode) {
  MOZ_ASSERT(IsEditorDataAvailable());

  if (!HTMLEditUtils::IsTableElementButNotTable(&aNode)) {
    nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(aNode);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    return NS_OK;
  }

  // XXX For performance, this should just call
  //     DeleteElementsExceptTableRelatedElements() while there are children
  //     in aNode.  If we need to avoid infinite loop because mutation event
  //     listeners can add unexpected nodes into aNode, we should just loop
  //     only original count of the children.
  AutoTArray<OwningNonNull<nsIContent>, 10> childList;
  for (nsIContent* child = aNode.GetFirstChild(); child;
       child = child->GetNextSibling()) {
    childList.AppendElement(*child);
  }

  for (const auto& child : childList) {
    nsresult rv = DeleteElementsExceptTableRelatedElements(child);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }
  return NS_OK;
}

nsresult HTMLEditRules::DidDeleteSelection() {
  MOZ_ASSERT(IsEditorDataAvailable());

  // find where we are
  EditorDOMPoint atStartOfSelection(
      EditorBase::GetStartPoint(*SelectionRefPtr()));
  if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
    return NS_ERROR_FAILURE;
  }

  // find any enclosing mailcite
  RefPtr<Element> citeNode =
      GetTopEnclosingMailCite(*atStartOfSelection.GetContainer());
  if (citeNode) {
    bool isEmpty = true, seenBR = false;
    HTMLEditorRef().IsEmptyNodeImpl(citeNode, &isEmpty, true, true, false,
                                    &seenBR);
    if (isEmpty) {
      EditorDOMPoint atCiteNode(citeNode);
      {
        AutoEditorDOMPointChildInvalidator lockOffset(atCiteNode);
        nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(*citeNode);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }
      if (atCiteNode.IsSet() && seenBR) {
        RefPtr<Element> brElement =
            HTMLEditorRef().InsertBrElementWithTransaction(atCiteNode);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(!brElement)) {
          return NS_ERROR_FAILURE;
        }
        IgnoredErrorResult error;
        SelectionRefPtr()->Collapse(EditorRawDOMPoint(brElement), error);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        NS_WARNING_ASSERTION(
            !error.Failed(),
            "Failed to collapse selection at the new <br> element");
      }
    }
  }

  // call through to base class
  nsresult rv = TextEditRules::DidDeleteSelection();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

nsresult HTMLEditRules::WillMakeList(const nsAString* aListType,
                                     bool aEntireList,
                                     const nsAString* aBulletType,
                                     bool* aCancel, bool* aHandled,
                                     const nsAString* aItemType) {
  MOZ_ASSERT(IsEditorDataAvailable());

  if (NS_WARN_IF(!aListType) || NS_WARN_IF(!aCancel) || NS_WARN_IF(!aHandled)) {
    return NS_ERROR_INVALID_ARG;
  }

  *aCancel = false;
  *aHandled = false;

  OwningNonNull<nsAtom> listType = NS_Atomize(*aListType);

  // FYI: Ignore cancel result of 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");

  // deduce what tag to use for list items
  RefPtr<nsAtom> itemType;
  if (aItemType) {
    itemType = NS_Atomize(*aItemType);
  } else if (listType == nsGkAtoms::dl) {
    itemType = nsGkAtoms::dd;
  } else {
    itemType = nsGkAtoms::li;
  }

  // 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

  *aHandled = true;

  rv = NormalizeSelection();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // MakeList() creates AutoSelectionRestorer.
  // Therefore, even if it returns NS_OK, editor might have been destroyed
  // at restoring Selection.
  rv = MakeList(listType, aEntireList, aBulletType, aCancel, *itemType);
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED) ||
      NS_WARN_IF(!CanHandleEditAction())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

nsresult HTMLEditRules::MakeList(nsAtom& aListType, bool aEntireList,
                                 const nsAString* aBulletType, bool* aCancel,
                                 nsAtom& aItemType) {
  AutoSelectionRestorer restoreSelectionLater(HTMLEditorRef());

  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  nsresult rv = GetListActionNodes(
      arrayOfNodes, aEntireList ? EntireList::yes : EntireList::no,
      TouchContent::yes);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // check if all our nodes are <br>s, or empty inlines
  bool bOnlyBreaks = true;
  for (auto& curNode : arrayOfNodes) {
    // if curNode is not a Break or empty inline, we're done
    if (!TextEditUtils::IsBreak(curNode) && !IsEmptyInline(curNode)) {
      bOnlyBreaks = false;
      break;
    }
  }

  // if no nodes, we make empty list.  Ditto if the user tried to make a list
  // of some # of breaks.
  if (arrayOfNodes.IsEmpty() || bOnlyBreaks) {
    // if only breaks, delete them
    if (bOnlyBreaks) {
      for (auto& node : arrayOfNodes) {
        rv = HTMLEditorRef().DeleteNodeWithTransaction(*node);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }
    }

    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;
    }

    // Make sure we can put a list here.
    if (!HTMLEditorRef().CanContainTag(*atStartOfSelection.GetContainer(),
                                       aListType)) {
      *aCancel = true;
      return NS_OK;
    }

    SplitNodeResult splitAtSelectionStartResult =
        MaybeSplitAncestorsForInsertWithTransaction(aListType,
                                                    atStartOfSelection);
    if (NS_WARN_IF(splitAtSelectionStartResult.Failed())) {
      return splitAtSelectionStartResult.Rv();
    }
    RefPtr<Element> theList = HTMLEditorRef().CreateNodeWithTransaction(
        aListType, splitAtSelectionStartResult.SplitPoint());
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(!theList)) {
      return NS_ERROR_FAILURE;
    }

    EditorRawDOMPoint atFirstListItemToInsertBefore(theList, 0);
    RefPtr<Element> theListItem = HTMLEditorRef().CreateNodeWithTransaction(
        aItemType, atFirstListItemToInsertBefore);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(!theListItem)) {
      return NS_ERROR_FAILURE;
    }

    // remember our new block for postprocessing
    mNewBlock = theListItem;
    // Put selection in new list item and don't restore the Selection.
    restoreSelectionLater.Abort();
    ErrorResult error;
    SelectionRefPtr()->Collapse(EditorRawDOMPoint(theListItem, 0), 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;
  }

  // if there is only one node in the array, and it is a list, div, or
  // blockquote, then look inside of it until we find inner list or content.

  LookInsideDivBQandList(arrayOfNodes);

  // Ok, now go through all the nodes and put then in the list,
  // or whatever is approriate.  Wohoo!

  uint32_t listCount = arrayOfNodes.Length();
  RefPtr<Element> curList, prevListItem;

  for (uint32_t i = 0; i < listCount; i++) {
    // here's where we actually figure out what to do
    RefPtr<Element> newBlock;
    if (NS_WARN_IF(!arrayOfNodes[i]->IsContent())) {
      return NS_ERROR_FAILURE;
    }
    OwningNonNull<nsIContent> curNode = *arrayOfNodes[i]->AsContent();

    // make sure we don't assemble content that is in different table cells
    // into the same list.  respect table cell boundaries when listifying.
    if (curList && InDifferentTableElements(curList, curNode)) {
      curList = nullptr;
    }

    // If curNode is a break, delete it, and quit remembering prev list item.
    // If an empty inline container, delete it, but still remember the previous
    // item.
    if (HTMLEditorRef().IsEditable(curNode) &&
        (TextEditUtils::IsBreak(curNode) || IsEmptyInline(curNode))) {
      rv = HTMLEditorRef().DeleteNodeWithTransaction(*curNode);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      if (TextEditUtils::IsBreak(curNode)) {
        prevListItem = nullptr;
      }
      continue;
    }

    if (HTMLEditUtils::IsList(curNode)) {
      // do we have a curList already?
      if (curList && !EditorUtils::IsDescendantOf(*curNode, *curList)) {
        // move all of our children into curList.  cheezy way to do it: move
        // whole list and then RemoveContainerWithTransaction() on the list.
        // ConvertListType first: that routine handles converting the list
        // item types, if needed.
        rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode, *curList);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
        CreateElementResult convertListTypeResult =
            ConvertListType(*curNode->AsElement(), aListType, aItemType);
        if (NS_WARN_IF(convertListTypeResult.Failed())) {
          return convertListTypeResult.Rv();
        }
        rv = HTMLEditorRef().RemoveBlockContainerWithTransaction(
            *convertListTypeResult.GetNewNode());
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
        newBlock = convertListTypeResult.forget();
      } else {
        // replace list with new list type
        CreateElementResult convertListTypeResult =
            ConvertListType(*curNode->AsElement(), aListType, aItemType);
        if (NS_WARN_IF(convertListTypeResult.Failed())) {
          return convertListTypeResult.Rv();
        }
        curList = convertListTypeResult.forget();
      }
      prevListItem = nullptr;
      continue;
    }

    EditorRawDOMPoint atCurNode(curNode);
    if (NS_WARN_IF(!atCurNode.IsSet())) {
      return NS_ERROR_FAILURE;
    }
    MOZ_ASSERT(atCurNode.IsSetAndValid());
    if (HTMLEditUtils::IsListItem(curNode)) {
      if (!atCurNode.IsContainerHTMLElement(&aListType)) {
        // list item is in wrong type of list. if we don't have a curList,
        // split the old list and make a new list of correct type.
        if (!curList || EditorUtils::IsDescendantOf(*curNode, *curList)) {
          if (NS_WARN_IF(!atCurNode.GetContainerAsContent())) {
            return NS_ERROR_FAILURE;
          }
          ErrorResult error;
          nsCOMPtr<nsIContent> newLeftNode =
              HTMLEditorRef().SplitNodeWithTransaction(atCurNode, error);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            error.SuppressException();
            return NS_ERROR_EDITOR_DESTROYED;
          }
          if (NS_WARN_IF(error.Failed())) {
            return error.StealNSResult();
          }
          newBlock = newLeftNode ? newLeftNode->AsElement() : nullptr;
          EditorRawDOMPoint atParentOfCurNode(atCurNode.GetContainer());
          curList = HTMLEditorRef().CreateNodeWithTransaction(
              aListType, atParentOfCurNode);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          if (NS_WARN_IF(!curList)) {
            return NS_ERROR_FAILURE;
          }
        }
        // move list item to new list
        rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode, *curList);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
        // convert list item type if needed
        if (!curNode->IsHTMLElement(&aItemType)) {
          newBlock = HTMLEditorRef().ReplaceContainerWithTransaction(
              *curNode->AsElement(), aItemType);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          if (NS_WARN_IF(!newBlock)) {
            return NS_ERROR_FAILURE;
          }
        }
      } else {
        // item is in right type of list.  But we might still have to move it.
        // and we might need to convert list item types.
        if (!curList) {
          curList = atCurNode.GetContainerAsElement();
        } else if (atCurNode.GetContainer() != curList) {
          // move list item to new list
          rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode, *curList);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return rv;
          }
        }
        if (!curNode->IsHTMLElement(&aItemType)) {
          newBlock = HTMLEditorRef().ReplaceContainerWithTransaction(
              *curNode->AsElement(), aItemType);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          if (NS_WARN_IF(!newBlock)) {
            return NS_ERROR_FAILURE;
          }
        }
      }
      nsCOMPtr<Element> curElement = do_QueryInterface(curNode);
      if (NS_WARN_IF(!curElement)) {
        return NS_ERROR_FAILURE;
      }
      if (aBulletType && !aBulletType->IsEmpty()) {
        rv = HTMLEditorRef().SetAttributeWithTransaction(
            *curElement, *nsGkAtoms::type, *aBulletType);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      } else {
        rv = HTMLEditorRef().RemoveAttributeWithTransaction(*curElement,
                                                            *nsGkAtoms::type);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }
      continue;
    }

    // if we hit a div clear our prevListItem, insert divs contents
    // into our node array, and remove the div
    if (curNode->IsHTMLElement(nsGkAtoms::div)) {
      prevListItem = nullptr;
      int32_t j = i + 1;
      GetInnerContent(*curNode, arrayOfNodes, &j);
      rv =
          HTMLEditorRef().RemoveContainerWithTransaction(*curNode->AsElement());
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      listCount = arrayOfNodes.Length();
      continue;
    }

    // need to make a list to put things in if we haven't already,
    if (!curList) {
      SplitNodeResult splitCurNodeResult =
          MaybeSplitAncestorsForInsertWithTransaction(aListType, atCurNode);
      if (NS_WARN_IF(splitCurNodeResult.Failed())) {
        return splitCurNodeResult.Rv();
      }
      curList = HTMLEditorRef().CreateNodeWithTransaction(
          aListType, splitCurNodeResult.SplitPoint());
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(!curList)) {
        return NS_ERROR_FAILURE;
      }
      // remember our new block for postprocessing
      mNewBlock = curList;
      // curList is now the correct thing to put curNode in
      prevListItem = nullptr;

      // atCurNode is now referring the right node with mOffset but
      // referring the left node with mRef.  So, invalidate it now.
      atCurNode.Clear();
    }

    // if curNode isn't a list item, we must wrap it in one
    nsCOMPtr<Element> listItem;
    if (!HTMLEditUtils::IsListItem(curNode)) {
      if (IsInlineNode(curNode) && prevListItem) {
        // this is a continuation of some inline nodes that belong together in
        // the same list item.  use prevListItem
        rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode,
                                                          *prevListItem);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      } else {
        // don't wrap li around a paragraph.  instead replace paragraph with li
        if (curNode->IsHTMLElement(nsGkAtoms::p)) {
          listItem = HTMLEditorRef().ReplaceContainerWithTransaction(
              *curNode->AsElement(), aItemType);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          if (NS_WARN_IF(!listItem)) {
            return NS_ERROR_FAILURE;
          }
        } else {
          listItem = HTMLEditorRef().InsertContainerWithTransaction(*curNode,
                                                                    aItemType);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          if (NS_WARN_IF(!listItem)) {
            return NS_ERROR_FAILURE;
          }
        }
        if (IsInlineNode(curNode)) {
          prevListItem = listItem;
        } else {
          prevListItem = nullptr;
        }
      }
    } else {
      listItem = curNode->AsElement();
    }

    if (listItem) {
      // if we made a new list item, deal with it: tuck the listItem into the
      // end of the active list
      rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*listItem, *curList);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }
  }

  return NS_OK;
}

nsresult HTMLEditRules::WillRemoveList(bool* aCancel, bool* aHandled) {
  MOZ_ASSERT(IsEditorDataAvailable());

  if (NS_WARN_IF(!aCancel) || NS_WARN_IF(!aHandled)) {
    return NS_ERROR_INVALID_ARG;
  }
  // initialize out param
  *aCancel = false;
  *aHandled = true;

  nsresult rv = NormalizeSelection();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  AutoSelectionRestorer restoreSelectionLater(HTMLEditorRef());

  nsTArray<RefPtr<nsRange>> arrayOfRanges;
  GetPromotedRanges(arrayOfRanges, EditSubAction::eCreateOrChangeList);

  // use these ranges to contruct a list of nodes to act on.
  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  rv = GetListActionNodes(arrayOfNodes, EntireList::no, TouchContent::yes);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Remove all non-editable nodes.  Leave them be.
  for (int32_t i = arrayOfNodes.Length() - 1; i >= 0; i--) {
    OwningNonNull<nsINode> testNode = arrayOfNodes[i];
    if (!HTMLEditorRef().IsEditable(testNode)) {
      arrayOfNodes.RemoveElementAt(i);
    }
  }

  // Only act on lists or list items in the array
  for (auto& curNode : arrayOfNodes) {
    // here's where we actually figure out what to do
    if (HTMLEditUtils::IsListItem(curNode)) {
      // unlist this listitem
      bool bOutOfList;
      do {
        rv = PopListItem(*curNode->AsContent(), &bOutOfList);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      } while (
          !bOutOfList);  // keep popping it out until it's not in a list anymore
    } else if (HTMLEditUtils::IsList(curNode)) {
      // node is a list, move list items out
      rv = RemoveListStructure(*curNode->AsElement());
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }
  }
  return NS_OK;
}

nsresult HTMLEditRules::WillMakeDefListItem(const nsAString* aItemType,
                                            bool aEntireList, bool* aCancel,
                                            bool* aHandled) {
  MOZ_ASSERT(IsEditorDataAvailable());

  // for now we let WillMakeList handle this
  NS_NAMED_LITERAL_STRING(listType, "dl");
  nsresult rv = WillMakeList(&listType.AsString(), aEntireList, nullptr,
                             aCancel, aHandled, aItemType);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

nsresult HTMLEditRules::WillMakeBasicBlock(const nsAString& aBlockType,
                                           bool* aCancel, bool* aHandled) {
  MOZ_ASSERT(IsEditorDataAvailable());
  MOZ_ASSERT(aCancel && aHandled);

  OwningNonNull<nsAtom> blockType = NS_Atomize(aBlockType);

  // FYI: Ignore cancel result of 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");

  *aCancel = false;
  *aHandled = true;

  // MakeBasicBlock() creates AutoSelectionRestorer.
  // Therefore, even if it returns NS_OK, editor might have been destroyed
  // at restoring Selection.
  rv = MakeBasicBlock(blockType);
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

nsresult HTMLEditRules::MakeBasicBlock(nsAtom& blockType) {
  MOZ_ASSERT(IsEditorDataAvailable());

  nsresult rv = NormalizeSelection();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  AutoSelectionRestorer restoreSelectionLater(HTMLEditorRef());
  AutoTransactionsConserveSelection dontChangeMySelection(HTMLEditorRef());

  // Contruct a list of nodes to act on.
  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  rv = GetNodesFromSelection(EditSubAction::eCreateOrRemoveBlock, arrayOfNodes,
                             TouchContent::yes);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // If nothing visible in list, make an empty block
  if (ListIsEmptyLine(arrayOfNodes)) {
    nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
    if (NS_WARN_IF(!firstRange)) {
      return NS_ERROR_FAILURE;
    }

    EditorDOMPoint pointToInsertBlock(firstRange->StartRef());
    if (&blockType == nsGkAtoms::normal || &blockType == nsGkAtoms::_empty) {
      // We are removing blocks (going to "body text")
      RefPtr<Element> curBlock =
          HTMLEditorRef().GetBlock(*pointToInsertBlock.GetContainer());
      if (NS_WARN_IF(!curBlock)) {
        return NS_ERROR_FAILURE;
      }
      if (!HTMLEditUtils::IsFormatNode(curBlock)) {
        return NS_OK;
      }

      // If the first editable node after selection is a br, consume it.
      // Otherwise it gets pushed into a following block after the split,
      // which is visually bad.
      nsCOMPtr<nsIContent> brContent =
          HTMLEditorRef().GetNextEditableHTMLNode(pointToInsertBlock);
      if (brContent && brContent->IsHTMLElement(nsGkAtoms::br)) {
        AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
        rv = HTMLEditorRef().DeleteNodeWithTransaction(*brContent);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }
      // Do the splits!
      SplitNodeResult splitNodeResult =
          HTMLEditorRef().SplitNodeDeepWithTransaction(
              *curBlock, pointToInsertBlock,
              SplitAtEdges::eDoNotCreateEmptyContainer);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(splitNodeResult.Failed())) {
        return splitNodeResult.Rv();
      }
      EditorRawDOMPoint pointToInsertBrNode(splitNodeResult.SplitPoint());
      // Put a <br> element at the split point
      brContent =
          HTMLEditorRef().InsertBrElementWithTransaction(pointToInsertBrNode);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(!brContent)) {
        return NS_ERROR_FAILURE;
      }
      // Put selection at the split point
      EditorRawDOMPoint atBrNode(brContent);
      // Don't restore the selection
      restoreSelectionLater.Abort();
      ErrorResult error;
      SelectionRefPtr()->Collapse(atBrNode, 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;
    }

    // We are making a block.  Consume a br, if needed.
    nsCOMPtr<nsIContent> brNode =
        HTMLEditorRef().GetNextEditableHTMLNodeInBlock(pointToInsertBlock);
    if (brNode && brNode->IsHTMLElement(nsGkAtoms::br)) {
      AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
      rv = HTMLEditorRef().DeleteNodeWithTransaction(*brNode);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      // We don't need to act on this node any more
      arrayOfNodes.RemoveElement(brNode);
    }
    // Make sure we can put a block here.
    SplitNodeResult splitNodeResult =
        MaybeSplitAncestorsForInsertWithTransaction(blockType,
                                                    pointToInsertBlock);
    if (NS_WARN_IF(splitNodeResult.Failed())) {
      return splitNodeResult.Rv();
    }
    RefPtr<Element> block = HTMLEditorRef().CreateNodeWithTransaction(
        blockType, splitNodeResult.SplitPoint());
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(!block)) {
      return NS_ERROR_FAILURE;
    }
    // Remember our new block for postprocessing
    mNewBlock = block;
    // Delete anything that was in the list of nodes
    while (!arrayOfNodes.IsEmpty()) {
      OwningNonNull<nsINode> curNode = arrayOfNodes[0];
      rv = HTMLEditorRef().DeleteNodeWithTransaction(*curNode);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      arrayOfNodes.RemoveElementAt(0);
    }
    // Don't restore the selection
    restoreSelectionLater.Abort();
    // Put selection in new block
    rv = SelectionRefPtr()->Collapse(block, 0);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    return NS_OK;
  }
  // Okay, now go through all the nodes and make the right kind of blocks, or
  // whatever is approriate.  Woohoo!  Note: blockquote is handled a little
  // differently.
  if (&blockType == nsGkAtoms::blockquote) {
    rv = MakeBlockquote(arrayOfNodes);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  } else if (&blockType == nsGkAtoms::normal ||
             &blockType == nsGkAtoms::_empty) {
    rv = RemoveBlockStyle(arrayOfNodes);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  } else {
    rv = ApplyBlockStyle(arrayOfNodes, blockType);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }
  return NS_OK;
}

nsresult HTMLEditRules::DidMakeBasicBlock() {
  MOZ_ASSERT(IsEditorDataAvailable());

  // check for empty block.  if so, put a moz br in it.
  if (!SelectionRefPtr()->IsCollapsed()) {
    return NS_OK;
  }

  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;
  }
  nsresult rv = InsertMozBRIfNeeded(*atStartOfSelection.Container());
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

nsresult HTMLEditRules::WillIndent(bool* aCancel, bool* aHandled) {
  MOZ_ASSERT(IsEditorDataAvailable());

  if (HTMLEditorRef().IsCSSEnabled()) {
    nsresult rv = WillCSSIndent(aCancel, aHandled);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  } else {
    nsresult rv = WillHTMLIndent(aCancel, aHandled);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }
  return NS_OK;
}

nsresult HTMLEditRules::WillCSSIndent(bool* aCancel, bool* aHandled) {
  MOZ_ASSERT(IsEditorDataAvailable());

  if (NS_WARN_IF(!aCancel) || NS_WARN_IF(!aHandled)) {
    return NS_ERROR_INVALID_ARG;
  }

  // FYI: Ignore cancel result of 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");

  *aCancel = false;
  *aHandled = true;

  rv = NormalizeSelection();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // IndentAroundSelectionWithCSS() creates AutoSelectionRestorer.
  // Therefore, even if it returns NS_OK, editor might have been destroyed
  // at restoring Selection.
  rv = IndentAroundSelectionWithCSS();
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

nsresult HTMLEditRules::IndentAroundSelectionWithCSS() {
  MOZ_ASSERT(IsEditorDataAvailable());

  AutoSelectionRestorer restoreSelectionLater(HTMLEditorRef());
  nsTArray<OwningNonNull<nsRange>> arrayOfRanges;
  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;

  // short circuit: detect case of collapsed selection inside an <li>.
  // just sublist that <li>.  This prevents bug 97797.

  nsCOMPtr<Element> liNode;
  if (SelectionRefPtr()->IsCollapsed()) {
    EditorRawDOMPoint selectionStartPoint(
        EditorBase::GetStartPoint(*SelectionRefPtr()));
    if (NS_WARN_IF(!selectionStartPoint.IsSet())) {
      return NS_ERROR_FAILURE;
    }
    Element* block =
        HTMLEditorRef().GetBlock(*selectionStartPoint.GetContainer());
    if (block && HTMLEditUtils::IsListItem(block)) {
      liNode = block;
    }
  }

  if (liNode) {
    arrayOfNodes.AppendElement(*liNode);
  } else {
    // 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
    nsresult rv = GetNodesFromSelection(EditSubAction::eIndent, arrayOfNodes,
                                        TouchContent::yes);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }

  // if nothing visible in list, make an empty block
  if (ListIsEmptyLine(arrayOfNodes)) {
    // get selection location
    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;
    }

    // make sure we can put a block here
    SplitNodeResult splitNodeResult =
        MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div,
                                                    atStartOfSelection);
    if (NS_WARN_IF(splitNodeResult.Failed())) {
      return splitNodeResult.Rv();
    }
    RefPtr<Element> theBlock = HTMLEditorRef().CreateNodeWithTransaction(
        *nsGkAtoms::div, splitNodeResult.SplitPoint());
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(!theBlock)) {
      return NS_ERROR_FAILURE;
    }
    // remember our new block for postprocessing
    mNewBlock = theBlock;
    nsresult rv = IncreaseMarginToIndent(*theBlock);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to increase indentation");
    // delete anything that was in the list of nodes
    while (!arrayOfNodes.IsEmpty()) {
      OwningNonNull<nsINode> curNode = arrayOfNodes[0];
      rv = HTMLEditorRef().DeleteNodeWithTransaction(*curNode);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      arrayOfNodes.RemoveElementAt(0);
    }
    // put selection in new block
    EditorRawDOMPoint atStartOfTheBlock(theBlock, 0);
    // Don't restore the selection
    restoreSelectionLater.Abort();
    ErrorResult error;
    SelectionRefPtr()->Collapse(atStartOfTheBlock, 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;
  }

  // Ok, now go through all the nodes and put them in a blockquote,
  // or whatever is appropriate.  Wohoo!
  nsCOMPtr<Element> curList, curQuote;
  nsCOMPtr<nsIContent> sibling;
  for (OwningNonNull<nsINode>& curNode : arrayOfNodes) {
    // Here's where we actually figure out what to do.
    EditorDOMPoint atCurNode(curNode);
    if (NS_WARN_IF(!atCurNode.IsSet())) {
      continue;
    }

    // Ignore all non-editable nodes.  Leave them be.
    if (!HTMLEditorRef().IsEditable(curNode)) {
      continue;
    }

    // some logic for putting list items into nested lists...
    if (HTMLEditUtils::IsList(atCurNode.GetContainer())) {
      // Check for whether we should join a list that follows curNode.
      // We do this if the next element is a list, and the list is of the
      // same type (li/ol) as curNode was a part it.
      sibling = HTMLEditorRef().GetNextHTMLSibling(curNode);
      if (sibling && HTMLEditUtils::IsList(sibling) &&
          atCurNode.GetContainer()->NodeInfo()->NameAtom() ==
              sibling->NodeInfo()->NameAtom() &&
          atCurNode.GetContainer()->NodeInfo()->NamespaceID() ==
              sibling->NodeInfo()->NamespaceID()) {
        nsresult rv = HTMLEditorRef().MoveNodeWithTransaction(
            *curNode->AsContent(), EditorRawDOMPoint(sibling, 0));
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
        continue;
      }

      // Check for whether we should join a list that preceeds curNode.
      // We do this if the previous element is a list, and the list is of
      // the same type (li/ol) as curNode was a part of.
      sibling = HTMLEditorRef().GetPriorHTMLSibling(curNode);
      if (sibling && HTMLEditUtils::IsList(sibling) &&
          atCurNode.GetContainer()->NodeInfo()->NameAtom() ==
              sibling->NodeInfo()->NameAtom() &&
          atCurNode.GetContainer()->NodeInfo()->NamespaceID() ==
              sibling->NodeInfo()->NamespaceID()) {
        nsresult rv = HTMLEditorRef().MoveNodeToEndWithTransaction(
            *curNode->AsContent(), *sibling);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
        continue;
      }

      // check to see if curList is still appropriate.  Which it is if
      // curNode is still right after it in the same list.
      sibling = nullptr;
      if (curList) {
        sibling = HTMLEditorRef().GetPriorHTMLSibling(curNode);
      }

      if (!curList || (sibling && sibling != curList)) {
        nsAtom* containerName =
            atCurNode.GetContainer()->NodeInfo()->NameAtom();
        // Create a new nested list of correct type.
        SplitNodeResult splitNodeResult =
            MaybeSplitAncestorsForInsertWithTransaction(*containerName,
                                                        atCurNode);
        if (NS_WARN_IF(splitNodeResult.Failed())) {
          return splitNodeResult.Rv();
        }
        curList = HTMLEditorRef().CreateNodeWithTransaction(
            *containerName, splitNodeResult.SplitPoint());
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(!curList)) {
          return NS_ERROR_FAILURE;
        }
        // curList is now the correct thing to put curNode in
        // remember our new block for postprocessing
        mNewBlock = curList;
      }
      // tuck the node into the end of the active list
      nsresult rv = HTMLEditorRef().MoveNodeToEndWithTransaction(
          *curNode->AsContent(), *curList);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      continue;
    }

    // Not a list item.

    if (IsBlockNode(*curNode)) {
      nsresult rv = IncreaseMarginToIndent(*curNode->AsElement());
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to inrease indentation");
      curQuote = nullptr;
      continue;
    }

    if (!curQuote) {
      // First, check that our element can contain a div.
      if (!HTMLEditorRef().CanContainTag(*atCurNode.GetContainer(),
                                         *nsGkAtoms::div)) {
        return NS_OK;  // cancelled
      }

      SplitNodeResult splitNodeResult =
          MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div,
                                                      atCurNode);
      if (NS_WARN_IF(splitNodeResult.Failed())) {
        return splitNodeResult.Rv();
      }
      curQuote = HTMLEditorRef().CreateNodeWithTransaction(
          *nsGkAtoms::div, splitNodeResult.SplitPoint());
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(!curQuote)) {
        return NS_ERROR_FAILURE;
      }
      nsresult rv = IncreaseMarginToIndent(*curQuote);
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to increase indentation");
      // remember our new block for postprocessing
      mNewBlock = curQuote;
      // curQuote is now the correct thing to put curNode in
    }

    // tuck the node into the end of the active blockquote
    nsresult rv = HTMLEditorRef().MoveNodeToEndWithTransaction(
        *curNode->AsContent(), *curQuote);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }
  return NS_OK;
}

nsresult HTMLEditRules::WillHTMLIndent(bool* aCancel, bool* aHandled) {
  MOZ_ASSERT(IsEditorDataAvailable());

  if (NS_WARN_IF(!aCancel) || NS_WARN_IF(!aHandled)) {
    return NS_ERROR_INVALID_ARG;
  }

  // FYI: Ignore cancel result of 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");

  *aCancel = false;
  *aHandled = true;

  rv = NormalizeSelection();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // IndentAroundSelectionWithHTML() creates AutoSelectionRestorer.
  // Therefore, even if it returns NS_OK, editor might have been destroyed
  // at restoring Selection.
  rv = IndentAroundSelectionWithHTML();
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

nsresult HTMLEditRules::IndentAroundSelectionWithHTML() {
  MOZ_ASSERT(IsEditorDataAvailable());

  AutoSelectionRestorer restoreSelectionLater(HTMLEditorRef());

  // 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

  nsTArray<RefPtr<nsRange>> arrayOfRanges;
  GetPromotedRanges(arrayOfRanges, EditSubAction::eIndent);

  // use these ranges to contruct a list of nodes to act on.
  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  nsresult rv = GetNodesForOperation(arrayOfRanges, arrayOfNodes,
                                     EditSubAction::eIndent, TouchContent::yes);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // if nothing visible in list, make an empty block
  if (ListIsEmptyLine(arrayOfNodes)) {
    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;
    }

    // Make sure we can put a block here.
    SplitNodeResult splitNodeResult =
        MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::blockquote,
                                                    atStartOfSelection);
    if (NS_WARN_IF(splitNodeResult.Failed())) {
      return splitNodeResult.Rv();
    }
    RefPtr<Element> theBlock = HTMLEditorRef().CreateNodeWithTransaction(
        *nsGkAtoms::blockquote, splitNodeResult.SplitPoint());
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(!theBlock)) {
      return NS_ERROR_FAILURE;
    }
    // remember our new block for postprocessing
    mNewBlock = theBlock;
    // delete anything that was in the list of nodes
    while (!arrayOfNodes.IsEmpty()) {
      OwningNonNull<nsINode> curNode = arrayOfNodes[0];
      rv = HTMLEditorRef().DeleteNodeWithTransaction(*curNode);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      arrayOfNodes.RemoveElementAt(0);
    }
    EditorRawDOMPoint atStartOfTheBlock(theBlock, 0);
    // Don't restore the selection
    restoreSelectionLater.Abort();
    ErrorResult error;
    SelectionRefPtr()->Collapse(atStartOfTheBlock, 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;
  }

  // Ok, now go through all the nodes and put them in a blockquote,
  // or whatever is appropriate.  Wohoo!
  nsCOMPtr<nsIContent> sibling;
  nsCOMPtr<Element> curList, curQuote, indentedLI;
  for (OwningNonNull<nsINode>& curNode : arrayOfNodes) {
    // Here's where we actually figure out what to do.
    EditorDOMPoint atCurNode(curNode);
    if (NS_WARN_IF(!atCurNode.IsSet())) {
      continue;
    }

    // Ignore all non-editable nodes.  Leave them be.
    if (!HTMLEditorRef().IsEditable(curNode)) {
      continue;
    }

    // some logic for putting list items into nested lists...
    if (HTMLEditUtils::IsList(atCurNode.GetContainer())) {
      // Check for whether we should join a list that follows curNode.
      // We do this if the next element is a list, and the list is of the
      // same type (li/ol) as curNode was a part it.
      sibling = HTMLEditorRef().GetNextHTMLSibling(curNode);
      if (sibling && HTMLEditUtils::IsList(sibling) &&
          atCurNode.GetContainer()->NodeInfo()->NameAtom() ==
              sibling->NodeInfo()->NameAtom() &&
          atCurNode.GetContainer()->NodeInfo()->NamespaceID() ==
              sibling->NodeInfo()->NamespaceID()) {
        rv = HTMLEditorRef().MoveNodeWithTransaction(
            *curNode->AsContent(), EditorRawDOMPoint(sibling, 0));
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
        continue;
      }

      // Check for whether we should join a list that preceeds curNode.
      // We do this if the previous element is a list, and the list is of
      // the same type (li/ol) as curNode was a part of.
      sibling = HTMLEditorRef().GetPriorHTMLSibling(curNode);
      if (sibling && HTMLEditUtils::IsList(sibling) &&
          atCurNode.GetContainer()->NodeInfo()->NameAtom() ==
              sibling->NodeInfo()->NameAtom() &&
          atCurNode.GetContainer()->NodeInfo()->NamespaceID() ==
              sibling->NodeInfo()->NamespaceID()) {
        rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode->AsContent(),
                                                          *sibling);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
        continue;
      }

      // check to see if curList is still appropriate.  Which it is if
      // curNode is still right after it in the same list.
      sibling = nullptr;
      if (curList) {
        sibling = HTMLEditorRef().GetPriorHTMLSibling(curNode);
      }

      if (!curList || (sibling && sibling != curList)) {
        nsAtom* containerName =
            atCurNode.GetContainer()->NodeInfo()->NameAtom();
        // Create a new nested list of correct type.
        SplitNodeResult splitNodeResult =
            MaybeSplitAncestorsForInsertWithTransaction(*containerName,
                                                        atCurNode);
        if (NS_WARN_IF(splitNodeResult.Failed())) {
          return splitNodeResult.Rv();
        }
        curList = HTMLEditorRef().CreateNodeWithTransaction(
            *containerName, splitNodeResult.SplitPoint());
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(!curList)) {
          return NS_ERROR_FAILURE;
        }
        // curList is now the correct thing to put curNode in
        // remember our new block for postprocessing
        mNewBlock = curList;
      }
      // tuck the node into the end of the active list
      rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode->AsContent(),
                                                        *curList);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      // forget curQuote, if any
      curQuote = nullptr;

      continue;
    }

    // Not a list item, use blockquote?

    // if we are inside a list item, we don't want to blockquote, we want
    // to sublist the list item.  We may have several nodes listed in the
    // array of nodes to act on, that are in the same list item.  Since
    // we only want to indent that li once, we must keep track of the most
    // recent indented list item, and not indent it if we find another node
    // to act on that is still inside the same li.
    RefPtr<Element> listItem = IsInListItem(curNode);
    if (listItem) {
      if (indentedLI == listItem) {
        // already indented this list item
        continue;
      }
      // check to see if curList is still appropriate.  Which it is if
      // curNode is still right after it in the same list.
      if (curList) {
        sibling = HTMLEditorRef().GetPriorHTMLSibling(listItem);
      }

      if (!curList || (sibling && sibling != curList)) {
        EditorDOMPoint atListItem(listItem);
        if (NS_WARN_IF(!listItem)) {
          return NS_ERROR_FAILURE;
        }
        nsAtom* containerName =
            atListItem.GetContainer()->NodeInfo()->NameAtom();
        // Create a new nested list of correct type.
        SplitNodeResult splitNodeResult =
            MaybeSplitAncestorsForInsertWithTransaction(*containerName,
                                                        atListItem);
        if (NS_WARN_IF(splitNodeResult.Failed())) {
          return splitNodeResult.Rv();
        }
        curList = HTMLEditorRef().CreateNodeWithTransaction(
            *containerName, splitNodeResult.SplitPoint());
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(!curList)) {
          return NS_ERROR_FAILURE;
        }
      }

      rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*listItem, *curList);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      // remember we indented this li
      indentedLI = listItem;

      continue;
    }

    // need to make a blockquote to put things in if we haven't already,
    // or if this node doesn't go in blockquote we used earlier.
    // One reason it might not go in prio blockquote is if we are now
    // in a different table cell.
    if (curQuote && InDifferentTableElements(curQuote, curNode)) {
      curQuote = nullptr;
    }

    if (!curQuote) {
      // First, check that our element can contain a blockquote.
      if (!HTMLEditorRef().CanContainTag(*atCurNode.GetContainer(),
                                         *nsGkAtoms::blockquote)) {
        return NS_OK;  // cancelled
      }

      SplitNodeResult splitNodeResult =
          MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::blockquote,
                                                      atCurNode);
      if (NS_WARN_IF(splitNodeResult.Failed())) {
        return splitNodeResult.Rv();
      }
      curQuote = HTMLEditorRef().CreateNodeWithTransaction(
          *nsGkAtoms::blockquote, splitNodeResult.SplitPoint());
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(!curQuote)) {
        return NS_ERROR_FAILURE;
      }
      // remember our new block for postprocessing
      mNewBlock = curQuote;
      // curQuote is now the correct thing to put curNode in
    }

    // tuck the node into the end of the active blockquote
    rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode->AsContent(),
                                                      *curQuote);
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    // forget curList, if any
    curList = nullptr;
  }
  return NS_OK;
}

nsresult HTMLEditRules::WillOutdent(bool* aCancel, bool* aHandled) {
  MOZ_ASSERT(IsEditorDataAvailable());
  MOZ_ASSERT(aCancel && aHandled);
  *aCancel = false;
  *aHandled = true;

  nsresult rv = NormalizeSelection();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // OutdentAroundSelection() creates AutoSelectionRestorer.  Therefore,
  // even if it returns NS_OK, the editor might have been destroyed at
  // restoring Selection.
  SplitRangeOffFromNodeResult outdentResult = OutdentAroundSelection();
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }
  if (NS_WARN_IF(outdentResult.Failed())) {
    return outdentResult.Rv();
  }

  // Make sure selection didn't stick to last piece of content in old bq (only
  // a problem for collapsed selections)
  if (!outdentResult.GetLeftContent() && !outdentResult.GetRightContent()) {
    return NS_OK;
  }

  if (!SelectionRefPtr()->IsCollapsed()) {
    return NS_OK;
  }

  // Push selection past end of left element of last split indented element.
  if (outdentResult.GetLeftContent()) {
    nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
    if (NS_WARN_IF(!firstRange)) {
      return NS_OK;
    }
    const RangeBoundary& atStartOfSelection = firstRange->StartRef();
    if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
      return NS_ERROR_FAILURE;
    }
    if (atStartOfSelection.Container() == outdentResult.GetLeftContent() ||
        EditorUtils::IsDescendantOf(*atStartOfSelection.Container(),
                                    *outdentResult.GetLeftContent())) {
      // Selection is inside the left node - push it past it.
      EditorRawDOMPoint afterRememberedLeftBQ(outdentResult.GetLeftContent());
      afterRememberedLeftBQ.AdvanceOffset();
      IgnoredErrorResult error;
      SelectionRefPtr()->Collapse(afterRememberedLeftBQ, error);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      NS_WARNING_ASSERTION(
          !error.Failed(),
          "Failed to collapse selection after the left <blockquote>");
    }
  }
  // And pull selection before beginning of right element of last split
  // indented element.
  if (outdentResult.GetRightContent()) {
    nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
    const RangeBoundary& atStartOfSelection = firstRange->StartRef();
    if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
      return NS_ERROR_FAILURE;
    }
    if (atStartOfSelection.Container() == outdentResult.GetRightContent() ||
        EditorUtils::IsDescendantOf(*atStartOfSelection.Container(),
                                    *outdentResult.GetRightContent())) {
      // Selection is inside the right element - push it before it.
      EditorRawDOMPoint atRememberedRightBQ(outdentResult.GetRightContent());
      IgnoredErrorResult error;
      SelectionRefPtr()->Collapse(atRememberedRightBQ, error);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      NS_WARNING_ASSERTION(
          !error.Failed(),
          "Failed to collapse selection after the right <blockquote>");
    }
  }
  return NS_OK;
}

SplitRangeOffFromNodeResult HTMLEditRules::OutdentAroundSelection() {
  MOZ_ASSERT(IsEditorDataAvailable());

  AutoSelectionRestorer restoreSelectionLater(HTMLEditorRef());

  bool useCSS = HTMLEditorRef().IsCSSEnabled();

  // 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
  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  nsresult rv = GetNodesFromSelection(EditSubAction::eOutdent, arrayOfNodes,
                                      TouchContent::yes);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return SplitRangeOffFromNodeResult(rv);
  }

  // Okay, now go through all the nodes and remove a level of blockquoting,
  // or whatever is appropriate.  Wohoo!

  nsCOMPtr<nsIContent> leftContentOfLastOutdented;
  nsCOMPtr<nsIContent> middleContentOfLastOutdented;
  nsCOMPtr<nsIContent> rightContentOfLastOutdented;
  nsCOMPtr<Element> curBlockQuote;
  nsCOMPtr<nsIContent> firstBQChild, lastBQChild;
  bool curBlockQuoteIsIndentedWithCSS = false;
  for (uint32_t i = 0; i < arrayOfNodes.Length(); i++) {
    if (!arrayOfNodes[i]->IsContent()) {
      continue;
    }
    OwningNonNull<nsIContent> curNode = *arrayOfNodes[i]->AsContent();

    // Here's where we actually figure out what to do
    int32_t offset;
    nsCOMPtr<nsINode> curParent = EditorBase::GetNodeLocation(curNode, &offset);
    if (!curParent) {
      continue;
    }

    // Is it a blockquote?
    if (curNode->IsHTMLElement(nsGkAtoms::blockquote)) {
      // If it is a blockquote, remove it.  So we need to finish up dealng
      // with any curBlockQuote first.
      if (curBlockQuote) {
        SplitRangeOffFromNodeResult outdentResult =
            OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
                               curBlockQuoteIsIndentedWithCSS);
        if (NS_WARN_IF(outdentResult.Failed())) {
          return outdentResult;
        }
        leftContentOfLastOutdented = outdentResult.GetLeftContent();
        middleContentOfLastOutdented = outdentResult.GetMiddleContent();
        rightContentOfLastOutdented = outdentResult.GetRightContent();
        curBlockQuote = nullptr;
        firstBQChild = nullptr;
        lastBQChild = nullptr;
        curBlockQuoteIsIndentedWithCSS = false;
      }
      rv = HTMLEditorRef().RemoveBlockContainerWithTransaction(
          *curNode->AsElement());
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return SplitRangeOffFromNodeResult(rv);
      }
      continue;
    }

    // Is it a block with a 'margin' property?
    if (useCSS && IsBlockNode(curNode)) {
      nsAtom& marginProperty = MarginPropertyAtomForIndent(curNode);
      nsAutoString value;
      CSSEditUtils::GetSpecifiedProperty(curNode, marginProperty, value);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
      }
      float f;
      RefPtr<nsAtom> unit;
      CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit));
      if (f > 0) {
        nsresult rv = DecreaseMarginToOutdent(*curNode->AsElement());
        if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
          return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
        }
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                             "Failed to decrease indentation");
        continue;
      }
    }

    // Is it a list item?
    if (HTMLEditUtils::IsListItem(curNode)) {
      // If it is a list item, that means we are not outdenting whole list.
      // So we need to finish up dealing with any curBlockQuote, and then pop
      // this list item.
      if (curBlockQuote) {
        SplitRangeOffFromNodeResult outdentResult =
            OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
                               curBlockQuoteIsIndentedWithCSS);
        if (NS_WARN_IF(outdentResult.Failed())) {
          return outdentResult;
        }
        leftContentOfLastOutdented = outdentResult.GetLeftContent();
        middleContentOfLastOutdented = outdentResult.GetMiddleContent();
        rightContentOfLastOutdented = outdentResult.GetRightContent();
        curBlockQuote = nullptr;
        firstBQChild = nullptr;
        lastBQChild = nullptr;
        curBlockQuoteIsIndentedWithCSS = false;
      }
      rv = PopListItem(*curNode->AsContent());
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return SplitRangeOffFromNodeResult(rv);
      }
      continue;
    }

    // Do we have a blockquote that we are already committed to removing?
    if (curBlockQuote) {
      // If so, is this node a descendant?
      if (EditorUtils::IsDescendantOf(*curNode, *curBlockQuote)) {
        lastBQChild = curNode;
        // Then we don't need to do anything different for this node
        continue;
      }
      // Otherwise, we have progressed beyond end of curBlockQuote, so
      // let's handle it now.  We need to remove the portion of
      // curBlockQuote that contains [firstBQChild - lastBQChild].
      SplitRangeOffFromNodeResult outdentResult =
          OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
                             curBlockQuoteIsIndentedWithCSS);
      if (NS_WARN_IF(outdentResult.Failed())) {
        return outdentResult;
      }
      leftContentOfLastOutdented = outdentResult.GetLeftContent();
      middleContentOfLastOutdented = outdentResult.GetMiddleContent();
      rightContentOfLastOutdented = outdentResult.GetRightContent();
      curBlockQuote = nullptr;
      firstBQChild = nullptr;
      lastBQChild = nullptr;
      curBlockQuoteIsIndentedWithCSS = false;
      // Fall out and handle curNode
    }

    // Are we inside a blockquote?
    OwningNonNull<nsINode> n = curNode;
    curBlockQuoteIsIndentedWithCSS = false;
    // Keep looking up the hierarchy as long as we don't hit the body or the
    // active editing host or a table element (other than an entire table)
    while (!n->IsHTMLElement(nsGkAtoms::body) &&
           HTMLEditorRef().IsDescendantOfEditorRoot(n) &&
           (n->IsHTMLElement(nsGkAtoms::table) ||
            !HTMLEditUtils::IsTableElement(n))) {
      if (!n->GetParentNode()) {
        break;
      }
      n = *n->GetParentNode();
      if (n->IsHTMLElement(nsGkAtoms::blockquote)) {
        // If so, remember it and the first node we are taking out of it.
        curBlockQuote = n->AsElement();
        firstBQChild = curNode;
        lastBQChild = curNode;
        break;
      }

      if (!useCSS) {
        continue;
      }

      nsAtom& marginProperty = MarginPropertyAtomForIndent(curNode);
      nsAutoString value;
      CSSEditUtils::GetSpecifiedProperty(*n, marginProperty, value);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
      }
      float f;
      RefPtr<nsAtom> unit;
      CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit));
      if (f > 0 && !(HTMLEditUtils::IsList(curParent) &&
                     HTMLEditUtils::IsList(curNode))) {
        curBlockQuote = n->AsElement();
        firstBQChild = curNode;
        lastBQChild = curNode;
        curBlockQuoteIsIndentedWithCSS = true;
        break;
      }
    }

    if (curBlockQuote) {
      continue;
    }

    // Couldn't find enclosing blockquote.
    if (HTMLEditUtils::IsList(curParent)) {
      // Move node out of list
      if (HTMLEditUtils::IsList(curNode)) {
        // Just unwrap this sublist
        rv = HTMLEditorRef().RemoveBlockContainerWithTransaction(
            *curNode->AsElement());
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return SplitRangeOffFromNodeResult(rv);
        }
      }
      continue;
    }

    if (HTMLEditUtils::IsList(curNode)) {
      // node is a list, but parent is non-list: move list items out
      nsCOMPtr<nsIContent> child = curNode->GetLastChild();
      while (child) {
        if (HTMLEditUtils::IsListItem(child)) {
          rv = PopListItem(*child);
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return SplitRangeOffFromNodeResult(rv);
          }
        } else if (HTMLEditUtils::IsList(child)) {
          // We have an embedded list, so move it out from under the parent
          // list. Be sure to put it after the parent list because this
          // loop iterates backwards through the parent's list of children.
          EditorRawDOMPoint afterCurrentList(curParent, offset + 1);
          rv =
              HTMLEditorRef().MoveNodeWithTransaction(*child, afterCurrentList);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
          }
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return SplitRangeOffFromNodeResult(rv);
          }
        } else {
          // Delete any non-list items for now
          rv = HTMLEditorRef().DeleteNodeWithTransaction(*child);
          if (NS_WARN_IF(!CanHandleEditAction())) {
            return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
          }
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return SplitRangeOffFromNodeResult(rv);
          }
        }
        child = curNode->GetLastChild();
      }
      // Delete the now-empty list
      rv = HTMLEditorRef().RemoveBlockContainerWithTransaction(
          *curNode->AsElement());
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return SplitRangeOffFromNodeResult(rv);
      }
      continue;
    }

    if (useCSS) {
      nsCOMPtr<Element> element;
      if (curNode->GetAsText()) {
        // We want to outdent the parent of text nodes
        element = curNode->GetParentElement();
      } else if (curNode->IsElement()) {
        element = curNode->AsElement();
      }
      if (element) {
        nsresult rv = DecreaseMarginToOutdent(*element);
        if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
          return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
        }
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                             "Failed to decrease indentation");
      }
      continue;
    }
  }

  if (!curBlockQuote) {
    return SplitRangeOffFromNodeResult(leftContentOfLastOutdented,
                                       middleContentOfLastOutdented,
                                       rightContentOfLastOutdented);
  }

  // We have a <blockquote> we haven't finished handling.
  SplitRangeOffFromNodeResult outdentResult =
      OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
                         curBlockQuoteIsIndentedWithCSS);
  if (NS_WARN_IF(outdentResult.Failed())) {
    return outdentResult;
  }
  return outdentResult;
}

SplitRangeOffFromNodeResult
HTMLEditRules::SplitRangeOffFromBlockAndRemoveMiddleContainer(
    Element& aBlockElement, nsIContent& aStartOfRange,
    nsIContent& aEndOfRange) {
  MOZ_ASSERT(IsEditorDataAvailable());

  SplitRangeOffFromNodeResult splitResult =
      SplitRangeOffFromBlock(aBlockElement, aStartOfRange, aEndOfRange);
  if (NS_WARN_IF(splitResult.Rv() == NS_ERROR_EDITOR_DESTROYED)) {
    return splitResult;
  }
  NS_WARNING_ASSERTION(splitResult.Succeeded(),
                       "Failed to split the range off from the block element");
  nsresult rv =
      HTMLEditorRef().RemoveBlockContainerWithTransaction(aBlockElement);
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
  }
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return SplitRangeOffFromNodeResult(rv);
  }
  return SplitRangeOffFromNodeResult(splitResult.GetLeftContent(), nullptr,
                                     splitResult.GetRightContent());
}

SplitRangeOffFromNodeResult HTMLEditRules::SplitRangeOffFromBlock(
    Element& aBlockElement, nsIContent& aStartOfMiddleElement,
    nsIContent& aEndOfMiddleElement) {
  MOZ_ASSERT(IsEditorDataAvailable());

  // aStartOfMiddleElement and aEndOfMiddleElement must be exclusive
  // descendants of aBlockElement.
  MOZ_ASSERT(EditorUtils::IsDescendantOf(aStartOfMiddleElement, aBlockElement));
  MOZ_ASSERT(EditorUtils::IsDescendantOf(aEndOfMiddleElement, aBlockElement));

  // Split at the start.
  SplitNodeResult splitAtStartResult =
      HTMLEditorRef().SplitNodeDeepWithTransaction(
          aBlockElement, EditorRawDOMPoint(&aStartOfMiddleElement),
          SplitAtEdges::eDoNotCreateEmptyContainer);
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
  }
  NS_WARNING_ASSERTION(splitAtStartResult.Succeeded(),
                       "Failed to split aBlockElement at start");

  // Split at after the end
  EditorRawDOMPoint atAfterEnd(&aEndOfMiddleElement);
  DebugOnly<bool> advanced = atAfterEnd.AdvanceOffset();
  NS_WARNING_ASSERTION(advanced, "Failed to advance offset after the end node");
  SplitNodeResult splitAtEndResult =
      HTMLEditorRef().SplitNodeDeepWithTransaction(
          aBlockElement, atAfterEnd, SplitAtEdges::eDoNotCreateEmptyContainer);
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
  }
  NS_WARNING_ASSERTION(splitAtEndResult.Succeeded(),
                       "Failed to split aBlockElement at after end");

  return SplitRangeOffFromNodeResult(splitAtStartResult, splitAtEndResult);
}

SplitRangeOffFromNodeResult HTMLEditRules::OutdentPartOfBlock(
    Element& aBlockElement, nsIContent& aStartOfOutdent,
    nsIContent& aEndOfOutdent, bool aIsBlockIndentedWithCSS) {
  MOZ_ASSERT(IsEditorDataAvailable());

  SplitRangeOffFromNodeResult splitResult =
      SplitRangeOffFromBlock(aBlockElement, aStartOfOutdent, aEndOfOutdent);
  if (NS_WARN_IF(splitResult.Rv() == NS_ERROR_EDITOR_DESTROYED)) {
    return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
  }

  if (NS_WARN_IF(!splitResult.GetMiddleContentAsElement())) {
    return SplitRangeOffFromNodeResult(NS_ERROR_FAILURE);
  }

  if (!aIsBlockIndentedWithCSS) {
    nsresult rv = HTMLEditorRef().RemoveBlockContainerWithTransaction(
        *splitResult.GetMiddleContentAsElement());
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
    }
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return SplitRangeOffFromNodeResult(rv);
    }
    return SplitRangeOffFromNodeResult(splitResult.GetLeftContent(), nullptr,
                                       splitResult.GetRightContent());
  }

  if (splitResult.GetMiddleContentAsElement()) {
    nsresult rv =
        DecreaseMarginToOutdent(*splitResult.GetMiddleContentAsElement());
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return SplitRangeOffFromNodeResult(rv);
    }
    return splitResult;
  }

  return splitResult;
}

CreateElementResult HTMLEditRules::ConvertListType(Element& aListElement,
                                                   nsAtom& aNewListTag,
                                                   nsAtom& aNewListItemTag) {
  MOZ_ASSERT(IsEditorDataAvailable());

  nsCOMPtr<nsINode> child = aListElement.GetFirstChild();
  while (child) {
    if (child->IsElement()) {
      Element* element = child->AsElement();
      if (HTMLEditUtils::IsListItem(element) &&
          !element->IsHTMLElement(&aNewListItemTag)) {
        child = HTMLEditorRef().ReplaceContainerWithTransaction(
            *element, aNewListItemTag);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return CreateElementResult(NS_ERROR_EDITOR_DESTROYED);
        }
        if (NS_WARN_IF(!child)) {
          return CreateElementResult(NS_ERROR_FAILURE);
        }
      } else if (HTMLEditUtils::IsList(element) &&
                 !element->IsHTMLElement(&aNewListTag)) {
        // XXX List elements shouldn't have other list elements as their
        //     child.  Why do we handle such invalid tree?
        CreateElementResult convertListTypeResult =
            ConvertListType(*child->AsElement(), aNewListTag, aNewListItemTag);
        if (NS_WARN_IF(convertListTypeResult.Failed())) {
          return convertListTypeResult;
        }
        child = convertListTypeResult.forget();
      }
    }
    child = child->GetNextSibling();
  }

  if (aListElement.IsHTMLElement(&aNewListTag)) {
    return CreateElementResult(&aListElement);
  }

  RefPtr<Element> listElement = HTMLEditorRef().ReplaceContainerWithTransaction(
      aListElement, aNewListTag);
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return CreateElementResult(NS_ERROR_EDITOR_DESTROYED);
  }
  NS_WARNING_ASSERTION(listElement != nullptr, "Failed to create list element");
  return CreateElementResult(listElement.forget());
}

nsresult HTMLEditRules::CreateStyleForInsertText(Document& aDocument) {
  MOZ_ASSERT(IsEditorDataAvailable());
  MOZ_ASSERT(HTMLEditorRef().mTypeInState);

  bool weDidSomething = false;
  nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
  if (NS_WARN_IF(!firstRange)) {
    return NS_ERROR_FAILURE;
  }
  nsCOMPtr<nsINode> node = firstRange->GetStartContainer();
  int32_t offset = firstRange->StartOffset();

  RefPtr<Element> rootElement = aDocument.GetRootElement();
  if (NS_WARN_IF(!rootElement)) {
    return NS_ERROR_FAILURE;
  }

  // process clearing any styles first
  UniquePtr<PropItem> item = HTMLEditorRef().mTypeInState->TakeClearProperty();

  {
    // Transactions may set selection, but we will set selection if necessary.
    AutoTransactionsConserveSelection dontChangeMySelection(HTMLEditorRef());

    while (item && node != rootElement) {
      // XXX If we redesign ClearStyle(), we can use EditorDOMPoint in this
      //     method.
      nsresult rv = HTMLEditorRef().ClearStyle(address_of(node), &offset,
                                               item->tag, item->attr);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      item = HTMLEditorRef().mTypeInState->TakeClearProperty();
      weDidSomething = true;
    }
  }

  // then process setting any styles
  int32_t relFontSize = HTMLEditorRef().mTypeInState->TakeRelativeFontSize();
  item = HTMLEditorRef().mTypeInState->TakeSetProperty();

  if (item || relFontSize) {
    // we have at least one style to add; make a new text node to insert style
    // nodes above.
    if (RefPtr<Text> text = node->GetAsText()) {
      // if we are in a text node, split it
      SplitNodeResult splitTextNodeResult =
          HTMLEditorRef().SplitNodeDeepWithTransaction(
              *text, EditorRawDOMPoint(text, offset),
              SplitAtEdges::eAllowToCreateEmptyContainer);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(splitTextNodeResult.Failed())) {
        return splitTextNodeResult.Rv();
      }
      EditorRawDOMPoint splitPoint(splitTextNodeResult.SplitPoint());
      node = splitPoint.GetContainer();
      offset = splitPoint.Offset();
    }
    if (!HTMLEditorRef().IsContainer(node)) {
      return NS_OK;
    }
    OwningNonNull<Text> newNode =
        EditorBase::CreateTextNode(aDocument, EmptyString());
    nsresult rv = HTMLEditorRef().InsertNodeWithTransaction(
        *newNode, EditorRawDOMPoint(node, offset));
    if (NS_WARN_IF(!CanHandleEditAction())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    node = newNode;
    offset = 0;
    weDidSomething = true;

    if (relFontSize) {
      // dir indicated bigger versus smaller.  1 = bigger, -1 = smaller
      HTMLEditor::FontSize dir = relFontSize > 0 ? HTMLEditor::FontSize::incr
                                                 : HTMLEditor::FontSize::decr;
      for (int32_t j = 0; j < DeprecatedAbs(relFontSize); j++) {
        rv = HTMLEditorRef().RelativeFontChangeOnTextNode(dir, newNode, 0, -1);
        if (NS_WARN_IF(!CanHandleEditAction())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }
    }

    while (item) {
      rv = HTMLEditorRef().SetInlinePropertyOnNode(
          *node->AsContent(), *item->tag, item->attr, item->value);
      if (NS_WARN_IF(!CanHandleEditAction())) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      item = HTMLEditorRef().mTypeInState->TakeSetProperty();
    }
  }

  if (!weDidSomething) {
    return NS_OK;
  }

  nsresult rv = SelectionRefPtr()->Collapse(node, offset);
  if (NS_WARN_IF(!CanHandleEditAction())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

bool HTMLEditRules::IsEmptyBlockElement(Element& aElement,
                                        IgnoreSingleBR aIgnoreSingleBR) {
  MOZ_ASSERT(IsEditorDataAvailable());

  if (NS_WARN_IF(!IsBlockNode(aElement))) {
    return false;
  }
  bool isEmpty = true;
  nsresult rv = HTMLEditorRef().IsEmptyNode(
      &aElement, &isEmpty, aIgnoreSingleBR == IgnoreSingleBR::eYes);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return false;
  }
  return isEmpty;
}

nsresult HTMLEditRules::WillAlign(const nsAString& aAlignType, bool* aCancel,
                                  bool* aHandled) {
  MOZ_ASSERT(IsEditorDataAvailable());
  MOZ_ASSERT(aCancel && aHandled);

  *aCancel = false;
  *aHandled = false;

  // FYI: Ignore cancel result of 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");

  rv = NormalizeSelection();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  *aHandled = true;
  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(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

nsresult HTMLEditRules::AlignContentsAtSelection(const nsAString& aAlignType) {
  AutoSelectionRestorer restoreSelectionLater(HTMLEditorRef());

  // 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
  nsTArray<OwningNonNull<nsINode>> nodeArray;
  nsresult rv = GetNodesFromSelection(EditSubAction::eSetOrClearAlignment,
                                      nodeArray, TouchContent::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();
  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 AlignBlock
      rv =