editor/libeditor/HTMLEditRules.cpp
author Masayuki Nakano <masayuki@d-toybox.com>
Mon, 18 Mar 2019 01:52:36 +0000
changeset 464720 d011dfe8368374923cb69a0ab510d6814cbf3ab1
parent 460126 f3471544869b9de5c9864f648d077b278200675a
child 465181 25398e555020fef80c7b2a06a0d4c667e861cd6f
permissions -rw-r--r--
Bug 1533293 - part 3: Make editor and ContentEventHandler not use Selection::Extend() due to too slow r=m_kato `Selection::Extend()` is too slow but editor and ContentEventHandler use it in some places. We should make them use `Selection::SetStartAndEndInLimiter()` or `Selection::SetBaseAndExtentInLimiter()`. The former is usable only when caller guarantees the start point is prior to the end point in the DOM tree. Otherwise, we need to use the latter even though it's slower than the former. Differential Revision: https://phabricator.services.mozilla.com/D23462

/* -*- 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.
    OwningNonNull<nsAtom> separatorTag = ParagraphSeparatorElement(separator);
    nsresult rv = MakeBasicBlock(separatorTag);
    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 =
        MOZ_KnownLive(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)) {