editor/libeditor/HTMLEditRules.cpp
author Makoto Kato <m_kato@ga2.so-net.ne.jp>
Fri, 27 Apr 2018 21:11:31 +0900
changeset 469795 f9fc7f957bb2e648f47a7e2b252b8aa66ca7e960
parent 469542 ae8e688883d564331a142a075b801046dda18312
child 471654 89f2f9869b1a720152890ae2e6f759f58e6b6ac0
permissions -rw-r--r--
Bug 1457431 - Devirtualize mozInlineSpellChecker::SpellCheckAfterEditorChange. r=masayuki It is unnecessary to keep virtual method for mozInlineSpellChecker::SpellCheckAfterEditorChange, so we should remove virtual keyword. MozReview-Commit-ID: 2ry5uhMTFVC

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=79: */
/* 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/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 "nsIContentIterator.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 {

class RulesInfo;

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 IsBlockNode(const nsINode& node)
{
  return HTMLEditor::NodeIsBlockStatic(&node);
}

static bool IsInlineNode(const nsINode& node)
{
  return !IsBlockNode(node);
}

static bool
IsStyleCachePreservingAction(EditAction action)
{
  return action == EditAction::deleteSelection ||
         action == EditAction::insertBreak ||
         action == EditAction::makeList ||
         action == EditAction::indent ||
         action == EditAction::outdent ||
         action == EditAction::align ||
         action == EditAction::makeBasicBlock ||
         action == EditAction::removeList ||
         action == EditAction::makeDefListItem ||
         action == EditAction::insertElement ||
         action == EditAction::insertQuotation;
}

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

/********************************************************
 * 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)) {
    return NS_ERROR_INVALID_ARG;
  }

  InitFields();

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

  // call through to base class Init
  nsresult rv = TextEditRules::Init(aTextEditor);
  NS_ENSURE_SUCCESS(rv, rv);

  // 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 = mHTMLEditor->GetRoot();
  if (!node) {
    node = mHTMLEditor->GetDocument();
  }

  NS_ENSURE_STATE(node);

  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 rv;
    mDocChangeRange->SelectNode(*node, rv);
    NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
    AdjustSpecialBreaks();
  }

  StartToListenToEditActions();

  return NS_OK;
}

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

nsresult
HTMLEditRules::BeforeEdit(EditAction aAction,
                          nsIEditor::EDirection aDirection)
{
  if (mLockRulesSniffing) {
    return NS_OK;
  }

  NS_ENSURE_STATE(mHTMLEditor);
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  AutoLockRulesSniffing lockIt(this);
  mDidExplicitlySetInterline = false;

  if (!mActionNesting) {
    mActionNesting++;

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

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

    // Get selection
    RefPtr<Selection> selection = htmlEditor->GetSelection();

    // Get the selection location
    if (NS_WARN_IF(!selection) || !selection->RangeCount()) {
      return NS_ERROR_UNEXPECTED;
    }
    mRangeItem->mStartContainer = selection->GetRangeAt(0)->GetStartContainer();
    mRangeItem->mStartOffset = selection->GetRangeAt(0)->StartOffset();
    mRangeItem->mEndContainer = selection->GetRangeAt(0)->GetEndContainer();
    mRangeItem->mEndOffset = selection->GetRangeAt(0)->EndOffset();
    nsCOMPtr<nsINode> selStartNode = mRangeItem->mStartContainer;
    nsCOMPtr<nsINode> selEndNode = mRangeItem->mEndContainer;

    // Register with range updater to track this as we perturb the doc
    htmlEditor->mRangeUpdater.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 (aAction == EditAction::insertText ||
        aAction == EditAction::insertIMEText ||
        aAction == EditAction::deleteSelection ||
        IsStyleCachePreservingAction(aAction)) {
      nsCOMPtr<nsINode> selNode =
        aDirection == nsIEditor::eNext ? selEndNode : selStartNode;
      nsresult rv = CacheInlineStyles(selNode);
      NS_ENSURE_SUCCESS(rv, rv);
    }

    // Stabilize the document against contenteditable count changes
    nsHTMLDocument* htmlDoc = htmlEditor->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
    ConfirmSelectionInBody();
    // Let rules remember the top level action
    mTheAction = aAction;
  }
  return NS_OK;
}


nsresult
HTMLEditRules::AfterEdit(EditAction aAction,
                         nsIEditor::EDirection aDirection)
{
  if (mLockRulesSniffing) {
    return NS_OK;
  }

  NS_ENSURE_STATE(mHTMLEditor);
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  AutoLockRulesSniffing lockIt(this);

  MOZ_ASSERT(mActionNesting > 0);
  nsresult rv = NS_OK;
  mActionNesting--;
  if (!mActionNesting) {
    // Do all the tricky stuff
    rv = AfterEditInner(aAction, aDirection);

    // Free up selectionState range item
    htmlEditor->mRangeUpdater.DropRangeItem(mRangeItem);

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

  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

nsresult
HTMLEditRules::AfterEditInner(EditAction aAction,
                              nsIEditor::EDirection aDirection)
{
  ConfirmSelectionInBody();
  if (aAction == EditAction::ignore) {
    return NS_OK;
  }

  NS_ENSURE_STATE(mHTMLEditor);
  RefPtr<Selection> selection = mHTMLEditor->GetSelection();
  NS_ENSURE_STATE(selection);

  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 && !((aAction == EditAction::undo) ||
                         (aAction == EditAction::redo))) {
    // 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.
    NS_ENSURE_STATE(mHTMLEditor);
    AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);

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

    // 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 EditAction::loadHTML, EditAction::insertText, etc.
    // That's why this is here rather than DidDeleteSelection().
    if (aAction == EditAction::deleteSelection && mDidRangedDelete) {
      nsresult rv = InsertBRIfNeeded(selection);
      NS_ENSURE_SUCCESS(rv, rv);
    }

    // add in any needed <br>s, and remove any unneeded ones.
    AdjustSpecialBreaks();

    // merge any adjacent text nodes
    if (aAction != EditAction::insertText &&
        aAction != EditAction::insertIMEText) {
      NS_ENSURE_STATE(mHTMLEditor);
      nsresult rv = mHTMLEditor->CollapseAdjacentTextNodes(mDocChangeRange);
      NS_ENSURE_SUCCESS(rv, rv);
    }

    // clean up any empty nodes in the selection
    nsresult rv = RemoveEmptyNodes();
    NS_ENSURE_SUCCESS(rv, rv);

    // attempt to transform any unneeded nbsp's into spaces after doing various operations
    if (aAction == EditAction::insertText ||
        aAction == EditAction::insertIMEText ||
        aAction == EditAction::deleteSelection ||
        aAction == EditAction::insertBreak ||
        aAction == EditAction::htmlPaste ||
        aAction == EditAction::loadHTML) {
      rv = AdjustWhitespace(selection);
      NS_ENSURE_SUCCESS(rv, rv);

      // also do this for original selection endpoints.
      NS_ENSURE_STATE(mHTMLEditor);
      NS_ENSURE_STATE(mRangeItem->mStartContainer);
      NS_ENSURE_STATE(mRangeItem->mEndContainer);
      WSRunObject(mHTMLEditor, 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) {
        NS_ENSURE_STATE(mHTMLEditor);
        WSRunObject(mHTMLEditor, mRangeItem->mEndContainer,
                    mRangeItem->mEndOffset).AdjustWhitespace();
      }
    }

    // if we created a new block, make sure selection lands in it
    if (mNewBlock) {
      rv = PinSelectionToNewBlock(selection);
      mNewBlock = nullptr;
    }

    // adjust selection for insert text, html paste, and delete actions
    if (aAction == EditAction::insertText ||
        aAction == EditAction::insertIMEText ||
        aAction == EditAction::deleteSelection ||
        aAction == EditAction::insertBreak ||
        aAction == EditAction::htmlPaste ||
        aAction == EditAction::loadHTML) {
      rv = AdjustSelection(selection, aDirection);
      NS_ENSURE_SUCCESS(rv, rv);
    }

    // check for any styles which were removed inappropriately
    if (aAction == EditAction::insertText ||
        aAction == EditAction::insertIMEText ||
        aAction == EditAction::deleteSelection ||
        IsStyleCachePreservingAction(aAction)) {
      NS_ENSURE_STATE(mHTMLEditor);
      mHTMLEditor->mTypeInState->UpdateSelState(selection);
      rv = ReapplyCachedStyles();
      NS_ENSURE_SUCCESS(rv, rv);
      ClearCachedStyles();
    }
  }

  NS_ENSURE_STATE(mHTMLEditor);

  nsresult rv =
    mHTMLEditor->HandleInlineSpellCheck(
                   aAction, *selection,
                   mRangeItem->mStartContainer,
                   mRangeItem->mStartOffset,
                   rangeStartContainer,
                   rangeStartOffset,
                   rangeEndContainer,
                   rangeEndOffset);
  NS_ENSURE_SUCCESS(rv, rv);

  // detect empty doc
  rv = CreateBogusNodeIfNeeded(selection);
  NS_ENSURE_SUCCESS(rv, rv);

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

  return NS_OK;
}

nsresult
HTMLEditRules::WillDoAction(Selection* aSelection,
                            RulesInfo* aInfo,
                            bool* aCancel,
                            bool* aHandled)
{
  MOZ_ASSERT(aInfo && aCancel && aHandled);

  *aCancel = false;
  *aHandled = false;

  // Deal with actions for which we don't need to check whether the selection is
  // editable.
  if (aInfo->action == EditAction::outputText ||
      aInfo->action == EditAction::undo ||
      aInfo->action == EditAction::redo) {
    return TextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled);
  }

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

  RefPtr<nsRange> range = aSelection->GetRangeAt(0);
  nsCOMPtr<nsINode> selStartNode = range->GetStartContainer();

  NS_ENSURE_STATE(mHTMLEditor);
  if (!mHTMLEditor->IsModifiableNode(selStartNode)) {
    *aCancel = true;
    return NS_OK;
  }

  nsCOMPtr<nsINode> selEndNode = range->GetEndContainer();

  if (selStartNode != selEndNode) {
    NS_ENSURE_STATE(mHTMLEditor);
    if (!mHTMLEditor->IsModifiableNode(selEndNode)) {
      *aCancel = true;
      return NS_OK;
    }

    NS_ENSURE_STATE(mHTMLEditor);
    if (!mHTMLEditor->IsModifiableNode(range->GetCommonAncestor())) {
      *aCancel = true;
      return NS_OK;
    }
  }

  switch (aInfo->action) {
    case EditAction::insertText:
    case EditAction::insertIMEText:
      UndefineCaretBidiLevel(aSelection);
      return WillInsertText(aInfo->action, aSelection, aCancel, aHandled,
                            aInfo->inString, aInfo->outString,
                            aInfo->maxLength);
    case EditAction::loadHTML:
      return WillLoadHTML(aSelection, aCancel);
    case EditAction::insertBreak:
      UndefineCaretBidiLevel(aSelection);
      return WillInsertBreak(*aSelection, aCancel, aHandled);
    case EditAction::deleteSelection:
      return WillDeleteSelection(aSelection, aInfo->collapsedAction,
                                 aInfo->stripWrappers, aCancel, aHandled);
    case EditAction::makeList:
      return WillMakeList(aSelection, aInfo->blockType, aInfo->entireList,
                          aInfo->bulletType, aCancel, aHandled);
    case EditAction::indent:
      return WillIndent(aSelection, aCancel, aHandled);
    case EditAction::outdent:
      return WillOutdent(*aSelection, aCancel, aHandled);
    case EditAction::setAbsolutePosition:
      return WillAbsolutePosition(*aSelection, aCancel, aHandled);
    case EditAction::removeAbsolutePosition:
      return WillRemoveAbsolutePosition(aSelection, aCancel, aHandled);
    case EditAction::align:
      return WillAlign(*aSelection, *aInfo->alignType, aCancel, aHandled);
    case EditAction::makeBasicBlock:
      return WillMakeBasicBlock(*aSelection, *aInfo->blockType, aCancel,
                                aHandled);
    case EditAction::removeList:
      return WillRemoveList(aSelection, aInfo->bOrdered, aCancel, aHandled);
    case EditAction::makeDefListItem:
      return WillMakeDefListItem(aSelection, aInfo->blockType,
                                 aInfo->entireList, aCancel, aHandled);
    case EditAction::insertElement:
      WillInsert(*aSelection, aCancel);
      return NS_OK;
    case EditAction::decreaseZIndex:
      return WillRelativeChangeZIndex(aSelection, -1, aCancel, aHandled);
    case EditAction::increaseZIndex:
      return WillRelativeChangeZIndex(aSelection, 1, aCancel, aHandled);
    default:
      return TextEditRules::WillDoAction(aSelection, aInfo,
                                         aCancel, aHandled);
  }
}

nsresult
HTMLEditRules::DidDoAction(Selection* aSelection,
                           RulesInfo* aInfo,
                           nsresult aResult)
{
  switch (aInfo->action) {
    case EditAction::insertBreak:
      return DidInsertBreak(aSelection, aResult);
    case EditAction::deleteSelection:
      return DidDeleteSelection(aSelection, aInfo->collapsedAction, aResult);
    case EditAction::makeBasicBlock:
    case EditAction::indent:
    case EditAction::outdent:
    case EditAction::align:
      return DidMakeBasicBlock(aSelection, aInfo, aResult);
    case EditAction::setAbsolutePosition: {
      nsresult rv = DidMakeBasicBlock(aSelection, aInfo, aResult);
      NS_ENSURE_SUCCESS(rv, rv);
      return DidAbsolutePosition();
    }
    default:
      // pass through to TextEditRules
      return TextEditRules::DidDoAction(aSelection, 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;

  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  nsresult rv = GetListActionNodes(arrayOfNodes, EntireList::no,
                                   TouchContent::no);
  NS_ENSURE_SUCCESS(rv, 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;

  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  nsresult rv = GetListActionNodes(arrayOfNodes, EntireList::no,
                                   TouchContent::no);
  NS_ENSURE_SUCCESS(rv, 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);

  NS_ENSURE_STATE(mHTMLEditor);
  RefPtr<HTMLEditor> htmlEditor(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
  NS_ENSURE_STATE(htmlEditor->GetSelection());
  OwningNonNull<Selection> selection = *htmlEditor->GetSelection();

  // Get selection location
  NS_ENSURE_TRUE(htmlEditor->GetRoot(), NS_ERROR_FAILURE);
  OwningNonNull<Element> root = *htmlEditor->GetRoot();

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

  nsRange* firstRange = selection->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 (selection->Collapsed() || 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();
  } 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 = htmlEditor->GetNextEditableNode(atStartOfSelection);
  } else {
    nsTArray<RefPtr<nsRange>> arrayOfRanges;
    GetPromotedRanges(selection, arrayOfRanges, EditAction::align);

    // Use these ranges to construct a list of nodes to act on.
    nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
    nsresult rv = GetNodesForOperation(arrayOfRanges, arrayOfNodes,
                                       EditAction::align, TouchContent::no);
    NS_ENSURE_SUCCESS(rv, rv);
    nodeToExamine = arrayOfNodes.SafeElementAt(0);
  }

  NS_ENSURE_TRUE(nodeToExamine, NS_ERROR_NULL_POINTER);

  nsCOMPtr<Element> blockParent = htmlEditor->GetBlock(*nodeToExamine);

  NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE);

  if (htmlEditor->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::GetIndentState(bool* aCanIndent,
                              bool* aCanOutdent)
{
  // XXX Looks like that this is implementation of
  //     nsIHTMLEditor::getIndentState() however nobody calls this method
  //     even with the interface method.
  NS_ENSURE_TRUE(aCanIndent && aCanOutdent, NS_ERROR_FAILURE);
  *aCanIndent = true;
  *aCanOutdent = false;

  // get selection
  NS_ENSURE_STATE(mHTMLEditor && mHTMLEditor->GetSelection());
  OwningNonNull<Selection> selection = *mHTMLEditor->GetSelection();

  // contruct a list of nodes to act on.
  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  nsresult rv = GetNodesFromSelection(*selection, EditAction::indent,
                                      arrayOfNodes, TouchContent::no);
  NS_ENSURE_SUCCESS(rv, rv);

  // examine nodes in selection for blockquotes or list elements;
  // these we can outdent.  Note that we return true for canOutdent
  // if *any* of the selection is outdentable, rather than all of it.
  NS_ENSURE_STATE(mHTMLEditor);
  bool useCSS = mHTMLEditor->IsCSSEnabled();
  for (auto& curNode : Reversed(arrayOfNodes)) {
    if (HTMLEditUtils::IsNodeThatCanOutdent(curNode)) {
      *aCanOutdent = true;
      break;
    } else if (useCSS) {
      // we are in CSS mode, indentation is done using the margin-left (or margin-right) property
      nsAtom& marginProperty = MarginPropertyAtomForIndent(curNode);
      nsAutoString value;
      // retrieve its specified value
      CSSEditUtils::GetSpecifiedProperty(*curNode, marginProperty, value);
      float f;
      RefPtr<nsAtom> unit;
      // get its number part and its unit
      CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit));
      // if the number part is strictly positive, outdent is possible
      if (0 < f) {
        *aCanOutdent = true;
        break;
      }
    }
  }

  if (*aCanOutdent) {
    return NS_OK;
  }

  // if we haven't found something to outdent yet, also check the parents
  // of selection endpoints.  We might have a blockquote or list item
  // in the parent hierarchy.

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

  Element* rootElement = mHTMLEditor->GetRoot();
  if (NS_WARN_IF(!rootElement)) {
    return NS_ERROR_FAILURE;
  }

  // Test selection start container hierarchy.
  EditorRawDOMPoint selectionStartPoint(EditorBase::GetStartPoint(selection));
  if (NS_WARN_IF(!selectionStartPoint.IsSet())) {
    return NS_ERROR_FAILURE;
  }
  for (nsINode* node = selectionStartPoint.GetContainer();
       node && node != rootElement;
       node = node->GetParentNode()) {
    if (HTMLEditUtils::IsNodeThatCanOutdent(node)) {
      *aCanOutdent = true;
      return NS_OK;
    }
  }

  // Test selection end container hierarchy.
  EditorRawDOMPoint selectionEndPoint(EditorBase::GetEndPoint(selection));
  if (NS_WARN_IF(!selectionEndPoint.IsSet())) {
    return NS_ERROR_FAILURE;
  }
  for (nsINode* node = selectionEndPoint.GetContainer();
       node && node != rootElement;
       node = node->GetParentNode()) {
    if (HTMLEditUtils::IsNodeThatCanOutdent(node)) {
      *aCanOutdent = true;
      return NS_OK;
    }
  }
  return NS_OK;
}


nsresult
HTMLEditRules::GetParagraphState(bool* aMixed,
                                 nsAString& outFormat)
{
  // This routine is *heavily* tied to our ui choices in the paragraph
  // style popup.  I can't see a way around that.
  NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
  *aMixed = true;
  outFormat.Truncate(0);

  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, TouchContent::no);
  NS_ENSURE_SUCCESS(rv, 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);
      NS_ENSURE_SUCCESS(rv, rv);
    }
  }

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

  // remember root node
  NS_ENSURE_STATE(mHTMLEditor);
  nsCOMPtr<Element> rootElem = mHTMLEditor->GetRoot();
  NS_ENSURE_TRUE(rootElem, NS_ERROR_NULL_POINTER);

  // 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 == rootElem) {
          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;
}

void
HTMLEditRules::WillInsert(Selection& aSelection,
                          bool* aCancel)
{
  MOZ_ASSERT(aCancel);

  TextEditRules::WillInsert(aSelection, aCancel);

  NS_ENSURE_TRUE_VOID(mHTMLEditor);
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  // 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 (!aSelection.Collapsed()) {
    return;
  }

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

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

  // Get prior node
  nsCOMPtr<nsIContent> priorNode =
    htmlEditor->GetPreviousEditableHTMLNode(atStartOfSelection);
  if (priorNode && TextEditUtils::IsMozBR(priorNode)) {
    RefPtr<Element> block1 =
      htmlEditor->GetBlock(*atStartOfSelection.GetContainer());
    RefPtr<Element> block2 = htmlEditor->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);
      IgnoredErrorResult error;
      aSelection.Collapse(point, error);
      if (NS_WARN_IF(error.Failed())) {
        return;
      }
    }
  }

  if (mDidDeleteSelection &&
      (mTheAction == EditAction::insertText ||
       mTheAction == EditAction::insertIMEText ||
       mTheAction == EditAction::deleteSelection)) {
    nsresult rv = ReapplyCachedStyles();
    NS_ENSURE_SUCCESS_VOID(rv);
  }
  // For most actions we want to clear the cached styles, but there are
  // exceptions
  if (!IsStyleCachePreservingAction(mTheAction)) {
    ClearCachedStyles();
  }
}

nsresult
HTMLEditRules::WillInsertText(EditAction aAction,
                              Selection* aSelection,
                              bool* aCancel,
                              bool* aHandled,
                              const nsAString* inString,
                              nsAString* outString,
                              int32_t aMaxLength)
{
  if (NS_WARN_IF(!aSelection) ||
      NS_WARN_IF(!aCancel) ||
      NS_WARN_IF(!aHandled)) {
    return NS_ERROR_NULL_POINTER;
  }

  if (NS_WARN_IF(!mHTMLEditor)) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  // 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 (!aSelection->Collapsed()) {
    nsresult rv =
      htmlEditor->DeleteSelectionAsAction(nsIEditor::eNone,
                                          nsIEditor::eNoStrip);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }

  WillInsert(*aSelection, aCancel);
  // initialize out param
  // we want to ignore result of WillInsert()
  *aCancel = false;

  // we need to get the doc
  nsCOMPtr<nsIDocument> doc = htmlEditor->GetDocument();
  if (NS_WARN_IF(!doc)) {
    return NS_ERROR_FAILURE;
  }

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

  // get the (collapsed) selection location
  nsRange* firstRange = aSelection->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()) &&
      !htmlEditor->CanContainTag(*pointToInsert.GetContainer(),
                                 *nsGkAtoms::textTagName)) {
    return NS_ERROR_FAILURE;
  }

  if (aAction == EditAction::insertIMEText) {
    // 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 =
      htmlEditor->GetIMESelectionStartOffsetIn(pointToInsert.GetContainer());
    if (IMESelectionOffset >= 0) {
      pointToInsert.Set(pointToInsert.GetContainer(), IMESelectionOffset);
    }

    if (inString->IsEmpty()) {
      rv = htmlEditor->InsertTextWithTransaction(
                         *doc, *inString, EditorRawDOMPoint(pointToInsert));
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      return NS_OK;
    }

    WSRunObject wsObj(htmlEditor, pointToInsert);
    rv = wsObj.InsertText(*doc, *inString, pointToInsert);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    return NS_OK;
  }

  // aAction == 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(htmlEditor);
  nsAutoString tString(*inString);
  const char16_t *unicodeBuf = tString.get();
  int32_t pos = 0;
  NS_NAMED_LITERAL_STRING(newlineStr, LFSTR);

  {
    AutoTrackDOMPoint tracker(htmlEditor->mRangeUpdater, &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 =
            htmlEditor->InsertBrElementWithTransaction(*aSelection,
                                                       currentPoint,
                                                       nsIEditor::eNone);
          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 = htmlEditor->InsertTextWithTransaction(
                             *doc, subStr,
                             EditorRawDOMPoint(currentPoint),
                             &pointAfterInsertedString);
          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(htmlEditor, currentPoint);

        // is it a tab?
        if (subStr.Equals(tabStr)) {
          EditorRawDOMPoint pointAfterInsertedSpaces;
          rv = wsObj.InsertText(*doc, spacesStr, currentPoint,
                                &pointAfterInsertedSpaces);
          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(*aSelection, currentPoint, nsIEditor::eNone);
          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(NS_FAILED(rv))) {
            return rv;
          }
          currentPoint = pointAfterInsertedString;
          pointToInsert = pointAfterInsertedString;
        }
      }
    }

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

  aSelection->SetInterlinePosition(false);

  if (currentPoint.IsSet()) {
    ErrorResult error;
    aSelection->Collapse(currentPoint, error);
    if (error.Failed()) {
      NS_WARNING("Failed to collapse at current point");
      error.SuppressException();
    }
  }

  // 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(Selection* aSelection,
                            bool* aCancel)
{
  NS_ENSURE_TRUE(aSelection && aCancel, NS_ERROR_NULL_POINTER);

  *aCancel = false;

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

  if (mBogusNode) {
    if (NS_WARN_IF(!mHTMLEditor)) {
      return NS_ERROR_UNEXPECTED;
    }
    RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
    DebugOnly<nsresult> rv = htmlEditor->DeleteNodeWithTransaction(*mBogusNode);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
      "Failed to remove the bogus node");
    mBogusNode = nullptr;
  }

  return NS_OK;
}

bool
HTMLEditRules::CanContainParagraph(Element& aElement) const
{
  if (NS_WARN_IF(!mHTMLEditor)) {
    return false;
  }

  if (mHTMLEditor->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;
}

nsresult
HTMLEditRules::WillInsertBreak(Selection& aSelection,
                               bool* aCancel,
                               bool* aHandled)
{
  MOZ_ASSERT(aCancel && aHandled);
  *aCancel = false;
  *aHandled = false;

  NS_ENSURE_STATE(mHTMLEditor);
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  // If the selection isn't collapsed, delete it.
  if (!aSelection.Collapsed()) {
    nsresult rv =
      htmlEditor->DeleteSelectionAsAction(nsIEditor::eNone, nsIEditor::eStrip);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }

  WillInsert(aSelection, aCancel);

  // Initialize out param.  We want to ignore result of WillInsert().
  *aCancel = false;

  // Split any mailcites in the way.  Should we abort this if we encounter
  // table cell boundaries?
  if (IsMailEditor()) {
    nsresult rv = SplitMailCites(&aSelection, aHandled);
    NS_ENSURE_SUCCESS(rv, rv);
    if (*aHandled) {
      return NS_OK;
    }
  }

  // Smart splitting rules
  nsRange* firstRange = aSelection.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;
  }
  MOZ_ASSERT(atStartOfSelection.IsSetAndValid());

  // Do nothing if the node is read-only
  if (!htmlEditor->IsModifiableNode(atStartOfSelection.GetContainer())) {
    *aCancel = true;
    return NS_OK;
  }

  // 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>.
  nsCOMPtr<Element> host = htmlEditor->GetActiveEditingHost();
  if (NS_WARN_IF(!host)) {
    return 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 = htmlEditor->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(aSelection, atStartOfSelection);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    *aHandled = true;
    return NS_OK;
  }

  if (host == blockParent && separator != ParagraphSeparator::br) {
    // Insert a new block first
    MOZ_ASSERT(separator == ParagraphSeparator::div ||
               separator == ParagraphSeparator::p);
    nsresult rv = MakeBasicBlock(aSelection,
                                 ParagraphSeparatorElement(separator));
    // 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 = aSelection.GetRangeAt(0);
    if (NS_WARN_IF(!firstRange)) {
      return NS_ERROR_FAILURE;
    }

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

    blockParent =
      HTMLEditor::GetBlock(*atStartOfSelection.GetContainer(), host);
    if (NS_WARN_IF(!blockParent)) {
      return NS_ERROR_UNEXPECTED;
    }
    if (NS_WARN_IF(blockParent == host)) {
      // Didn't create a new block for some reason, fall back to <br>
      rv = InsertBRElement(aSelection, atStartOfSelection);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      *aHandled = true;
      return NS_OK;
    }
    // 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 =
      htmlEditor->InsertBrElementWithTransaction(aSelection, endOfBlockParent);
    if (NS_WARN_IF(!brElement)) {
      return NS_ERROR_FAILURE;
    }
  }

  nsCOMPtr<Element> listItem = IsInListItem(blockParent);
  if (listItem && listItem != host) {
    ReturnInListItem(aSelection, *listItem, *atStartOfSelection.GetContainer(),
                     atStartOfSelection.Offset());
    *aHandled = true;
    return NS_OK;
  }

  if (HTMLEditUtils::IsHeader(*blockParent)) {
    // Headers: close (or split) header
    ReturnInHeader(aSelection, *blockParent, *atStartOfSelection.GetContainer(),
                   atStartOfSelection.Offset());
    *aHandled = true;
    return NS_OK;
  }

  // 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(aSelection, *blockParent);
    if (NS_WARN_IF(result.Failed())) {
      return result.Rv();
    }
    *aHandled = result.Handled();
    *aCancel = result.Canceled();
    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 NS_OK;
    }
    // Fall through, if ReturnInParagraph() didn't handle it.
    MOZ_ASSERT(!*aCancel, "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.
  MOZ_ASSERT(!*aHandled, "Reached last resort of WillInsertBreak() "
                         "after the edit action is handled");
  nsresult rv = InsertBRElement(aSelection, atStartOfSelection);
  *aHandled = true;
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

nsresult
HTMLEditRules::InsertBRElement(Selection& aSelection,
                               const EditorDOMPoint& aPointToBreak)
{
  if (NS_WARN_IF(!aPointToBreak.IsSet())) {
    return NS_ERROR_INVALID_ARG;
  }

  if (NS_WARN_IF(!mHTMLEditor)) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  bool brElementIsAfterBlock = false;
  bool brElementIsBeforeBlock = false;

  // First, insert a <br> element.
  RefPtr<Element> brElement;
  if (IsPlaintextEditor()) {
    brElement =
      htmlEditor->InsertBrElementWithTransaction(aSelection, aPointToBreak);
    if (NS_WARN_IF(!brElement)) {
      return NS_ERROR_FAILURE;
    }
  } else {
    EditorDOMPoint pointToBreak(aPointToBreak);
    WSRunObject wsObj(htmlEditor, pointToBreak);
    int32_t visOffset = 0;
    WSType wsType;
    nsCOMPtr<nsINode> visNode;
    wsObj.PriorVisibleNode(pointToBreak,
                           address_of(visNode), &visOffset, &wsType);
    if (wsType & WSType::block) {
      brElementIsAfterBlock = true;
    }
    wsObj.NextVisibleNode(pointToBreak,
                          address_of(visNode), &visOffset, &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.
    nsCOMPtr<nsINode> linkDOMNode;
    if (htmlEditor->IsInLink(pointToBreak.GetContainer(),
                             address_of(linkDOMNode))) {
      nsCOMPtr<Element> linkNode = do_QueryInterface(linkDOMNode);
      if (NS_WARN_IF(!linkNode)) {
        return NS_ERROR_FAILURE;
      }
      SplitNodeResult splitLinkNodeResult =
        htmlEditor->SplitNodeDeepWithTransaction(
                      *linkNode, pointToBreak,
                      SplitAtEdges::eDoNotCreateEmptyContainer);
      if (NS_WARN_IF(splitLinkNodeResult.Failed())) {
        return splitLinkNodeResult.Rv();
      }
      pointToBreak = splitLinkNodeResult.SplitPoint();
    }
    brElement = wsObj.InsertBreak(aSelection, pointToBreak, nsIEditor::eNone);
    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.
    aSelection.SetInterlinePosition(true);
    EditorRawDOMPoint point(brElement);
    ErrorResult error;
    aSelection.Collapse(point, error);
    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(htmlEditor, afterBRElement);
  nsCOMPtr<nsINode> maybeSecondBRNode;
  int32_t visOffset = 0;
  WSType wsType;
  wsObj.NextVisibleNode(afterBRElement,
                        address_of(maybeSecondBRNode), &visOffset, &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 =
        htmlEditor->MoveNodeWithTransaction(*maybeSecondBRNode->AsContent(),
                                            afterBRElement);
      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();
  aSelection.SetInterlinePosition(!(nextSiblingOfBRElement &&
                                    IsBlockNode(*nextSiblingOfBRElement)));
  ErrorResult error;
  aSelection.Collapse(afterBRElement, error);
  if (NS_WARN_IF(error.Failed())) {
    return error.StealNSResult();
  }
  return NS_OK;
}

nsresult
HTMLEditRules::DidInsertBreak(Selection* aSelection,
                              nsresult aResult)
{
  return NS_OK;
}

nsresult
HTMLEditRules::SplitMailCites(Selection* aSelection,
                              bool* aHandled)
{
  if (NS_WARN_IF(!aSelection) || NS_WARN_IF(!aHandled)) {
    return NS_ERROR_INVALID_ARG;
  }

  if (NS_WARN_IF(!mHTMLEditor)) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

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

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

  // 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(htmlEditor, pointToSplit);
  nsCOMPtr<nsINode> visNode;
  int32_t visOffset=0;
  WSType wsType;
  wsObj.NextVisibleNode(pointToSplit, address_of(visNode), &visOffset, &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 NS_ERROR_FAILURE;
  }

  SplitNodeResult splitCiteNodeResult =
    htmlEditor->SplitNodeDeepWithTransaction(
                  *citeNode, pointToSplit,
                  SplitAtEdges::eDoNotCreateEmptyContainer);
  if (NS_WARN_IF(splitCiteNodeResult.Failed())) {
    return 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()->
                                  IsFrameOfType(nsIFrame::eBlockFrame)) {
    nsCOMPtr<nsINode> lastChild =
      previousNodeOfSplitPoint->GetLastChild();
    if (lastChild && !lastChild->IsHTMLElement(nsGkAtoms::br)) {
      // We ignore the result here.
      EditorRawDOMPoint endOfPreviousNodeOfSplitPoint;
      endOfPreviousNodeOfSplitPoint.SetToEndOf(previousNodeOfSplitPoint);
      RefPtr<Element> invisibleBrElement =
        htmlEditor->InsertBrElementWithTransaction(
                      *aSelection,
                      endOfPreviousNodeOfSplitPoint);
      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 =
    htmlEditor->InsertBrElementWithTransaction(*aSelection,
                                               pointToInsertBrNode);
  if (NS_WARN_IF(!brElement)) {
    return 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.
  aSelection->SetInterlinePosition(true);
  ErrorResult error;
  aSelection->Collapse(atBrNode, error);
  if (NS_WARN_IF(error.Failed())) {
    return 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(htmlEditor, pointToCreateNewBrNode);
    nsCOMPtr<nsINode> visNode;
    int32_t visOffset=0;
    WSType wsType;
    wsObj.PriorVisibleNode(pointToCreateNewBrNode,
                           address_of(visNode), &visOffset, &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(htmlEditor, pointAfterNewBrNode);
      wsObjAfterBR.NextVisibleNode(pointAfterNewBrNode,
                                   address_of(visNode), &visOffset, &wsType);
      if (wsType == WSType::normalWS || wsType == WSType::text ||
          wsType == WSType::special ||
          // In case we're at the very end.
          wsType == WSType::thisBlock) {
        brElement =
          htmlEditor->InsertBrElementWithTransaction(*aSelection,
                                                     pointToCreateNewBrNode);
        if (NS_WARN_IF(!brElement)) {
          return NS_ERROR_FAILURE;
        }
        // Now, those points may be invalid.
        pointToCreateNewBrNode.Clear();
        pointAfterNewBrNode.Clear();
      }
    }
  }

  // delete any empty cites
  bool bEmptyCite = false;
  if (previousNodeOfSplitPoint) {
    nsresult rv =
      htmlEditor->IsEmptyNode(previousNodeOfSplitPoint, &bEmptyCite,
                              true, false);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    if (bEmptyCite) {
      rv = htmlEditor->DeleteNodeWithTransaction(*previousNodeOfSplitPoint);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }
  }

  if (citeNode) {
    nsresult rv = htmlEditor->IsEmptyNode(citeNode, &bEmptyCite, true, false);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    if (bEmptyCite) {
      rv = htmlEditor->DeleteNodeWithTransaction(*citeNode);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }
  }

  *aHandled = true;
  return NS_OK;
}


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

  if (!aSelection || !aCancel || !aHandled) {
    return NS_ERROR_NULL_POINTER;
  }
  // 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.
  RefPtr<Element> cell;
  NS_ENSURE_STATE(mHTMLEditor);
  nsresult rv =
    mHTMLEditor->GetFirstSelectedCell(nullptr, getter_AddRefs(cell));
  if (NS_SUCCEEDED(rv) && cell) {
    NS_ENSURE_STATE(mHTMLEditor);
    rv = mHTMLEditor->DeleteTableCellContents();
    *aHandled = true;
    return rv;
  }
  cell = 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 bCollapsed = aSelection->Collapsed();
  bool join = false;
  bool origCollapsed = bCollapsed;

  nsCOMPtr<nsINode> selNode;
  int32_t selOffset;

  NS_ENSURE_STATE(aSelection->GetRangeAt(0));
  nsCOMPtr<nsINode> startNode = aSelection->GetRangeAt(0)->GetStartContainer();
  int32_t startOffset = aSelection->GetRangeAt(0)->StartOffset();
  NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);

  if (bCollapsed) {
    // If we are inside an empty block, delete it.
    NS_ENSURE_STATE(mHTMLEditor);
    nsCOMPtr<Element> host = mHTMLEditor->GetActiveEditingHost();
    NS_ENSURE_TRUE(host, NS_ERROR_FAILURE);
    rv = CheckForEmptyBlock(startNode, host, aSelection, aAction, aHandled);
    NS_ENSURE_SUCCESS(rv, rv);
    if (*aHandled) {
      return NS_OK;
    }

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

    NS_ENSURE_STATE(mHTMLEditor);
    rv = mHTMLEditor->ExtendSelectionForDelete(aSelection, &aAction);
    NS_ENSURE_SUCCESS(rv, rv);

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

    // ExtendSelectionForDelete() may have changed the selection, update it
    NS_ENSURE_STATE(aSelection->GetRangeAt(0));
    startNode = aSelection->GetRangeAt(0)->GetStartContainer();
    startOffset = aSelection->GetRangeAt(0)->StartOffset();
    NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);

    bCollapsed = aSelection->Collapsed();
  }

  if (bCollapsed) {
    // What's in the direction we are deleting?
    NS_ENSURE_STATE(mHTMLEditor);
    WSRunObject wsObj(mHTMLEditor, startNode, startOffset);
    nsCOMPtr<nsINode> visNode;
    int32_t visOffset;
    WSType wsType;

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

    if (!visNode) {
      // Can't find anything to delete!
      *aCancel = true;
      // XXX This is the result of mHTMLEditor->GetFirstSelectedCell().
      //     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(NS_FAILED(rv))) {
          return rv;
        }
      } else {
        rv = wsObj.DeleteWSBackward();
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }
      return InsertBRIfNeeded(aSelection);
    }

    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 = aSelection->GetRangeAt(0);
        NS_ENSURE_STATE(range);

        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();
      }
      if (NS_WARN_IF(!mHTMLEditor)) {
        return NS_ERROR_FAILURE;
      }
      RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
      rv = WSRunObject::PrepareToDeleteRange(htmlEditor, address_of(visNode),
                                             &so, address_of(visNode), &eo);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      *aHandled = true;
      rv = htmlEditor->DeleteTextWithTransaction(nodeAsText, std::min(so, eo),
                                                 DeprecatedAbs(eo - so));
      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.

      DeleteNodeIfCollapsedText(nodeAsText);

      rv = InsertBRIfNeeded(aSelection);
      NS_ENSURE_SUCCESS(rv, 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) &&
          (!mHTMLEditor || !mHTMLEditor->IsVisibleBRElement(visNode))) {
        if (NS_WARN_IF(!mHTMLEditor)) {
          return NS_ERROR_FAILURE;
        }
        RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
        rv = htmlEditor->DeleteNodeWithTransaction(*visNode);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
        return WillDeleteSelection(aSelection, aAction, aStripWrappers,
                                   aCancel, aHandled);
      }

      // 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 startNode and startOffset) is the position directly
        // after the <hr>, on the same line as the <hr>.
        //
        // To detect this case we check:
        // startNode == parentOfVisNode
        // and
        // startOffset -1 == visNodeOffsetToVisNodeParent
        // and
        // interline position is false (left)
        //
        // In any other case we set the position to startnode -1 and
        // interlineposition to false, only moving the caret to the
        // end-of-hr-line position.
        bool moveOnly = true;

        selNode = visNode->GetParentNode();
        selOffset = selNode ? selNode->ComputeIndexOf(visNode) : -1;

        bool interLineIsRight;
        rv = aSelection->GetInterlinePosition(&interLineIsRight);
        NS_ENSURE_SUCCESS(rv, rv);

        if (startNode == selNode && startOffset - 1 == selOffset &&
            !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.
          ++selOffset;
          aSelection->Collapse(selNode, selOffset);
          aSelection->SetInterlinePosition(false);
          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;
          int32_t otherOffset;

          wsObj.NextVisibleNode(EditorRawDOMPoint(startNode, startOffset),
                                address_of(otherNode),
                                &otherOffset, &otherWSType);

          if (otherWSType == WSType::br) {
            // Delete the <br>
            if (NS_WARN_IF(!mHTMLEditor) ||
                NS_WARN_IF(!otherNode->IsContent())) {
              return NS_ERROR_FAILURE;
            }
            nsIContent* otherContent = otherNode->AsContent();
            RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
            rv = WSRunObject::PrepareToDeleteNode(htmlEditor, otherContent);
            if (NS_WARN_IF(NS_FAILED(rv))) {
              return rv;
            }
            rv = htmlEditor->DeleteNodeWithTransaction(*otherContent);
            if (NS_WARN_IF(NS_FAILED(rv))) {
              return rv;
            }
          }

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

      if (NS_WARN_IF(!mHTMLEditor) ||
          NS_WARN_IF(!visNode->IsContent())) {
        return NS_ERROR_FAILURE;
      }
      RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
      // Found break or image, or hr.
      rv = WSRunObject::PrepareToDeleteNode(htmlEditor, visNode->AsContent());
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      // Remember sibling to visnode, if any
      nsCOMPtr<nsIContent> sibling = htmlEditor->GetPriorHTMLSibling(visNode);
      // Delete the node, and join like nodes if appropriate
      rv = htmlEditor->DeleteNodeWithTransaction(*visNode);
      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 = htmlEditor->GetNextHTMLSibling(sibling);
      }
      // Are they both text nodes?  If so, join them!
      if (startNode == stepbrother && startNode->GetAsText() &&
          sibling->GetAsText()) {
        EditorDOMPoint pt =
          JoinNearestEditableNodesWithTransaction(*sibling,
                                                  *startNode->AsContent());
        if (NS_WARN_IF(!pt.IsSet())) {
          return NS_ERROR_FAILURE;
        }
        // Fix up selection
        ErrorResult error;
        aSelection->Collapse(pt, error);
        if (NS_WARN_IF(error.Failed())) {
          return error.StealNSResult();
        }
      }
      rv = InsertBRIfNeeded(aSelection);
      NS_ENSURE_SUCCESS(rv, 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;
      int32_t otherOffset;

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

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

      if (otherNode->IsHTMLElement(nsGkAtoms::br)) {
        if (NS_WARN_IF(!mHTMLEditor)) {
          return NS_ERROR_FAILURE;
        }
        RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
        rv = htmlEditor->DeleteNodeWithTransaction(*otherNode);
        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.
        NS_ENSURE_STATE(leafNode);
        EditorDOMPoint newSel = GetGoodSelPointForNode(*leafNode, aAction);
        if (NS_WARN_IF(!newSel.IsSet())) {
          return NS_ERROR_FAILURE;
        }
        aSelection->Collapse(newSel);
        return NS_OK;
      }

      // Else we are joining content to block

      nsCOMPtr<nsINode> selPointNode = startNode;
      int32_t selPointOffset = startOffset;
      {
        NS_ENSURE_STATE(mHTMLEditor);
        AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater,
                                  address_of(selPointNode), &selPointOffset);
        NS_ENSURE_STATE(leftNode && leftNode->IsContent() &&
                        rightNode && rightNode->IsContent());
        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 != startNode) {
        int32_t offset =
          aAction == nsIEditor::ePrevious ?
            static_cast<int32_t>(leafNode->Length()) : 0;
        aSelection->Collapse(leafNode, offset);
        return WillDeleteSelection(aSelection, aAction, aStripWrappers,
                                   aCancel, aHandled);
      }

      // Otherwise, we must have deleted the selection as user expected.
      aSelection->Collapse(selPointNode, selPointOffset);
      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) {
        NS_ENSURE_STATE(mHTMLEditor);
        leftNode = mHTMLEditor->GetPreviousEditableHTMLNode(*visNode);
        rightNode = startNode;
      } else {
        NS_ENSURE_STATE(mHTMLEditor);
        rightNode = mHTMLEditor->GetNextEditableHTMLNode(*visNode);
        leftNode = startNode;
      }

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

      nsCOMPtr<nsINode> selPointNode = startNode;
      int32_t selPointOffset = startOffset;
      {
        NS_ENSURE_STATE(mHTMLEditor);
        AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater,
                                  address_of(selPointNode), &selPointOffset);
        NS_ENSURE_STATE(leftNode->IsContent() && rightNode->IsContent());
        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();
        }
      }
      aSelection->Collapse(selPointNode, selPointOffset);
      return NS_OK;
    }
  }


  // Else we have a non-collapsed selection.  First adjust the selection.
  rv = ExpandSelectionForDeletion(*aSelection);
  NS_ENSURE_SUCCESS(rv, rv);

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

  // Refresh start and end points
  NS_ENSURE_STATE(aSelection->GetRangeAt(0));
  startNode = aSelection->GetRangeAt(0)->GetStartContainer();
  startOffset = aSelection->GetRangeAt(0)->StartOffset();
  NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
  nsCOMPtr<nsINode> endNode = aSelection->GetRangeAt(0)->GetEndContainer();
  int32_t endOffset = aSelection->GetRangeAt(0)->EndOffset();
  NS_ENSURE_TRUE(endNode, NS_ERROR_FAILURE);

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

  {
    if (NS_WARN_IF(!mHTMLEditor)) {
      return NS_ERROR_FAILURE;
    }
    RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

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

    if (endNode == startNode) {
      rv = htmlEditor->DeleteSelectionWithTransaction(aAction, aStripWrappers);
      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) {
        NS_ENSURE_STATE(mHTMLEditor);
        htmlEditor->DeleteSelectionWithTransaction(aAction, aStripWrappers);
      } else {
        // Deleting across blocks.  Are the blocks of same type?
        NS_ENSURE_STATE(leftParent && rightParent);

        // 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 &&
            htmlEditor->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 = htmlEditor->DeleteSelectionWithTransaction(aAction,
                                                          aStripWrappers);
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return rv;
          }
          // Join blocks
          EditorDOMPoint pt =
            htmlEditor->JoinNodesDeepWithTransaction(*leftParent,
                                                     *rightParent);
          if (NS_WARN_IF(!pt.IsSet())) {
            return NS_ERROR_FAILURE;
          }
          // Fix up selection
          ErrorResult error;
          aSelection->Collapse(pt, error);
          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(aSelection);
        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);
          NS_ENSURE_SUCCESS(rv, 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++) {
            nsCOMPtr<nsINode> somenode = do_QueryInterface(arrayOfNodes[0]);
            NS_ENSURE_STATE(somenode);
            DeleteNonTableElements(somenode);
            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 (!somenode->IsContent()) {
                join = false;
                continue;
              }
              nsCOMPtr<nsIContent> content = somenode->AsContent();
              if (Text* text = content->GetAsText()) {
                join = !htmlEditor->IsInVisibleTextFrames(*text);
              } else {
                join = content->IsHTMLElement(nsGkAtoms::br) &&
                       !htmlEditor->IsVisibleBRElement(somenode);
              }
            }
          }
        }

        // 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 = htmlEditor->DeleteTextWithTransaction(
                             dataNode, startOffset,
                             startNode->Length() - startOffset);
          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 = htmlEditor->DeleteTextWithTransaction(dataNode, 0, endOffset);
          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(mHTMLEditor->mRangeUpdater,
                                   address_of(startNode), &startOffset);
    AutoTrackDOMPoint endTracker(mHTMLEditor->mRangeUpdater,
                                 address_of(endNode), &endOffset);

    DeleteNodeIfCollapsedText(*startNode);
    DeleteNodeIfCollapsedText(*endNode);
  }

  // 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 = aSelection->Collapse(endNode, endOffset);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  } else {
    rv = aSelection->Collapse(startNode, startOffset);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }
  return NS_OK;
}

/**
 * If aNode is a text node that contains only collapsed whitespace, delete it.
 * It doesn't serve any useful purpose, and we don't want it to confuse code
 * that doesn't correctly skip over it.
 *
 * If deleting the node fails (like if it's not editable), the caller should
 * proceed as usual, so don't return any errors.
 */
void
HTMLEditRules::DeleteNodeIfCollapsedText(nsINode& aNode)
{
  Text* text = aNode.GetAsText();
  if (!text) {
    return;
  }

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

  if (!mHTMLEditor->IsVisibleTextNode(*text)) {
    RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
    DebugOnly<nsresult> rv = htmlEditor->DeleteNodeWithTransaction(aNode);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to remove aNode");
  }
}


/**
 * InsertBRIfNeeded() determines if a br is needed for current selection to not
 * be spastic.  If so, it inserts one.  Callers responsibility to only call
 * with collapsed selection.
 *
 * @param aSelection        The collapsed selection.
 */
nsresult
HTMLEditRules::InsertBRIfNeeded(Selection* aSelection)
{
  if (NS_WARN_IF(!aSelection)) {
    return NS_ERROR_INVALID_ARG;
  }

  if (NS_WARN_IF(!mHTMLEditor)) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  EditorRawDOMPoint atStartOfSelection(EditorBase::GetStartPoint(aSelection));
  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(htmlEditor, 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 (htmlEditor->CanContainTag(*atStartOfSelection.GetContainer(),
                                  *nsGkAtoms::br)) {
      RefPtr<Element> brElement =
        htmlEditor->InsertBrElementWithTransaction(*aSelection,
                                                   atStartOfSelection,
                                                   nsIEditor::ePrevious);
      if (NS_WARN_IF(!brElement)) {
        return NS_ERROR_FAILURE;
      }
      return NS_OK;
    }
  }
  return NS_OK;
}

/**
 * GetGoodSelPointForNode() finds where at a node you would want to set the
 * selection if you were trying to have a caret next to it.  Always returns a
 * valid value (unless mHTMLEditor has gone away).
 *
 * @param aNode         The node
 * @param aAction       Which edge to find:
 *                        eNext/eNextWord/eToEndOfLine indicates beginning,
 *                        ePrevious/PreviousWord/eToBeginningOfLine ending.
 */
EditorDOMPoint
HTMLEditRules::GetGoodSelPointForNode(nsINode& aNode,
                                      nsIEditor::EDirection aAction)
{
  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 (NS_WARN_IF(!mHTMLEditor)) {
    return EditorDOMPoint();
  }
  if (aNode.GetAsText() || mHTMLEditor->IsContainer(&aNode) ||
      NS_WARN_IF(!aNode.GetParentNode())) {
    return EditorDOMPoint(&aNode, isPreviousAction ? aNode.Length() : 0);
  }

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

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

EditActionResult
HTMLEditRules::TryToJoinBlocksWithTransaction(nsIContent& aLeftNode,
                                              nsIContent& aRightNode)
{
  if (NS_WARN_IF(!mHTMLEditor)) {
    return EditActionIgnored(NS_ERROR_UNEXPECTED);
  }

  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  nsCOMPtr<Element> leftBlock = htmlEditor->GetBlock(aLeftNode);
  nsCOMPtr<Element> rightBlock = htmlEditor->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 = htmlEditor->GetBlockNodeParent(leftBlock);
    if (NS_WARN_IF(!leftBlock)) {
      return EditActionIgnored(NS_ERROR_UNEXPECTED);
    }
  }
  if (rightBlock->IsHTMLElement(nsGkAtoms::hr)) {
    rightBlock = htmlEditor->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(htmlEditor);

  // 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(htmlEditor,
                                                  WSRunObject::kBlockEnd,
                                                  leftBlock);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return EditActionIgnored(rv);
    }

    {
      // We can't just track rightBlock because it's an Element.
      AutoTrackDOMPoint tracker(htmlEditor->mRangeUpdater, &atRightBlockChild);
      rv = WSRunObject::ScrubBlockBoundary(htmlEditor,
                                           WSRunObject::kAfterBlock,
                                           atRightBlockChild.GetContainer(),
                                           atRightBlockChild.Offset());
      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 (retMoveBlock.Handled()) {
        ret.MarkAsHandled();
      }
      // Now, all children of rightBlock were moved to leftBlock.  So,
      // atRightBlockChild is now invalid.
      atRightBlockChild.Clear();
    }
    if (brNode &&
        NS_SUCCEEDED(htmlEditor->DeleteNodeWithTransaction(*brNode))) {
      ret.MarkAsHandled();
    }
    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(htmlEditor,
                                                  WSRunObject::kBlockStart,
                                                  rightBlock);
    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(htmlEditor->mRangeUpdater, &leftBlockChild);
      rv = WSRunObject::ScrubBlockBoundary(htmlEditor,
                                           WSRunObject::kBeforeBlock,
                                           leftBlock, leftBlockChild.Offset());
      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 (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.

      nsCOMPtr<Element> editorRoot = htmlEditor->GetEditorRoot();
      if (!editorRoot || &aLeftNode != editorRoot) {
        nsCOMPtr<nsIContent> splittedPreviousContent;
        nsCOMPtr<nsINode> previousContentParent =
          previousContent.GetContainer();
        int32_t previousContentOffset = previousContent.Offset();
        rv = htmlEditor->SplitStyleAbovePoint(
                           address_of(previousContentParent),
                           &previousContentOffset,
                           nullptr, nullptr, nullptr,
                           getter_AddRefs(splittedPreviousContent));
        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 &&
        NS_SUCCEEDED(htmlEditor->DeleteNodeWithTransaction(*brNode))) {
      ret.MarkAsHandled();
    }
    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(htmlEditor, leftBlock, rightBlock);
  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 =
      JoinNearestEditableNodesWithTransaction(*leftBlock, *rightBlock);
    if (pt.IsSet() && mergeLists) {
      RefPtr<Element> newBlock =
        ConvertListType(rightBlock, existingList, nsGkAtoms::li);
    }
    ret.MarkAsHandled();
  } else {
    // Nodes are dissimilar types.
    ret |= MoveBlock(*leftBlock, *rightBlock, -1, 0);
    if (NS_WARN_IF(ret.Failed())) {
      return ret;
    }
  }
  if (brNode) {
    rv = htmlEditor->DeleteNodeWithTransaction(*brNode);
    // 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)
{
  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  // GetNodesFromPoint is the workhorse that figures out what we wnat to move.
  nsresult rv = GetNodesFromPoint(EditorDOMPoint(&aRightBlock, aRightOffset),
                                  EditAction::makeList, arrayOfNodes,
                                  TouchContent::yes);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return EditActionIgnored(rv);
  }

  if (NS_WARN_IF(!mHTMLEditor)) {
    return EditActionIgnored(NS_ERROR_NOT_AVAILABLE);
  }
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
  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;
      }
      if (NS_WARN_IF(!mHTMLEditor)) {
        return ret.SetResult(NS_ERROR_UNEXPECTED);
      }
      rv = htmlEditor->DeleteNodeWithTransaction(*arrayOfNodes[i]);
      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);
    }
  }

  // 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(aInOutDestOffset);

  if (NS_WARN_IF(!mHTMLEditor)) {
    return EditActionIgnored(NS_ERROR_UNEXPECTED);
  }

  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  // Check if this node can go into the destination node
  if (htmlEditor->CanContain(aDestElement, aNode)) {
    // If it can, move it there.
    if (*aInOutDestOffset == -1) {
      nsresult rv =
        htmlEditor->MoveNodeToEndWithTransaction(aNode, aDestElement);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return EditActionIgnored(rv);
      }
    } else {
      EditorRawDOMPoint pointToInsert(&aDestElement, *aInOutDestOffset);
      nsresult rv =
        htmlEditor->MoveNodeWithTransaction(aNode, pointToInsert);
      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 = htmlEditor->DeleteNodeWithTransaction(aNode);
  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::DeleteNonTableElements(nsINode* aNode)
{
  MOZ_ASSERT(aNode);
  if (!HTMLEditUtils::IsTableElementButNotTable(aNode)) {
    if (NS_WARN_IF(!mHTMLEditor)) {
      return NS_ERROR_NOT_AVAILABLE;
    }
    RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
    nsresult rv = htmlEditor->DeleteNodeWithTransaction(*aNode);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    return NS_OK;
  }

  AutoTArray<nsCOMPtr<nsIContent>, 10> childList;
  for (nsIContent* child = aNode->GetFirstChild();
       child; child = child->GetNextSibling()) {
    childList.AppendElement(child);
  }

  for (const auto& child: childList) {
    nsresult rv = DeleteNonTableElements(child);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  return NS_OK;
}

nsresult
HTMLEditRules::DidDeleteSelection(Selection* aSelection,
                                  nsIEditor::EDirection aDir,
                                  nsresult aResult)
{
  if (!aSelection) {
    return NS_ERROR_NULL_POINTER;
  }

  if (NS_WARN_IF(!mHTMLEditor)) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  // find where we are
  EditorDOMPoint atStartOfSelection(EditorBase::GetStartPoint(aSelection));
  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;
    htmlEditor->IsEmptyNodeImpl(citeNode, &isEmpty, true, true, false,
                                &seenBR);
    if (isEmpty) {
      EditorDOMPoint atCiteNode(citeNode);
      {
        AutoEditorDOMPointChildInvalidator lockOffset(atCiteNode);
        nsresult rv = htmlEditor->DeleteNodeWithTransaction(*citeNode);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }
      if (atCiteNode.IsSet() && seenBR) {
        RefPtr<Element> brElement =
          htmlEditor->InsertBrElementWithTransaction(*aSelection, atCiteNode);
        if (NS_WARN_IF(!brElement)) {
          return NS_ERROR_FAILURE;
        }
        IgnoredErrorResult error;
        aSelection->Collapse(EditorRawDOMPoint(brElement), error);
        NS_WARNING_ASSERTION(!error.Failed(),
          "Failed to collapse selection at the new <br> element");
      }
    }
  }

  // call through to base class
  return TextEditRules::DidDeleteSelection(aSelection, aDir, aResult);
}

nsresult
HTMLEditRules::WillMakeList(Selection* aSelection,
                            const nsAString* aListType,
                            bool aEntireList,
                            const nsAString* aBulletType,
                            bool* aCancel,
                            bool* aHandled,
                            const nsAString* aItemType)
{
  if (!aSelection || !aListType || !aCancel || !aHandled) {
    return NS_ERROR_NULL_POINTER;
  }
  if (NS_WARN_IF(!mHTMLEditor)) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

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

  WillInsert(*aSelection, aCancel);

  // initialize out param
  // we want to ignore result of WillInsert()
  *aCancel = false;
  *aHandled = false;

  // deduce what tag to use for list items
  RefPtr<nsAtom> itemType;
  if (aItemType) {
    itemType = NS_Atomize(*aItemType);
    NS_ENSURE_TRUE(itemType, NS_ERROR_OUT_OF_MEMORY);
  } 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;

  nsresult rv = NormalizeSelection(aSelection);
  NS_ENSURE_SUCCESS(rv, rv);

  AutoSelectionRestorer selectionRestorer(aSelection, htmlEditor);

  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  rv = GetListActionNodes(arrayOfNodes,
                          aEntireList ? EntireList::yes : EntireList::no);
  NS_ENSURE_SUCCESS(rv, 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 = htmlEditor->DeleteNodeWithTransaction(*node);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }
    }

    nsRange* firstRange = aSelection->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 (!htmlEditor->CanContainTag(*atStartOfSelection.GetContainer(),
                                   listType)) {
      *aCancel = true;
      return NS_OK;
    }

    SplitNodeResult splitAtSelectionStartResult =
      MaybeSplitAncestorsForInsertWithTransaction(listType, atStartOfSelection);
    if (NS_WARN_IF(splitAtSelectionStartResult.Failed())) {
      return splitAtSelectionStartResult.Rv();
    }
    RefPtr<Element> theList =
      htmlEditor->CreateNodeWithTransaction(
                    *listType,
                    splitAtSelectionStartResult.SplitPoint());
    if (NS_WARN_IF(!theList)) {
      return NS_ERROR_FAILURE;
    }

    EditorRawDOMPoint atFirstListItemToInsertBefore(theList, 0);
    RefPtr<Element> theListItem =
      htmlEditor->CreateNodeWithTransaction(*itemType,
                                            atFirstListItemToInsertBefore);
    if (NS_WARN_IF(!theListItem)) {
      return NS_ERROR_FAILURE;
    }

    // remember our new block for postprocessing
    mNewBlock = theListItem;
    // put selection in new list item
    *aHandled = true;
    ErrorResult error;
    aSelection->Collapse(EditorRawDOMPoint(theListItem, 0), error);
    // Don't restore the selection
    selectionRestorer.Abort();
    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();
  nsCOMPtr<Element> curList, prevListItem;

  for (uint32_t i = 0; i < listCount; i++) {
    // here's where we actually figure out what to do
    RefPtr<Element> newBlock;
    NS_ENSURE_STATE(arrayOfNodes[i]->IsContent());
    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 (htmlEditor->IsEditable(curNode) && (TextEditUtils::IsBreak(curNode) ||
                                            IsEmptyInline(curNode))) {
      rv = htmlEditor->DeleteNodeWithTransaction(*curNode);
      NS_ENSURE_SUCCESS(rv, 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 = htmlEditor->MoveNodeToEndWithTransaction(*curNode, *curList);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
        newBlock = ConvertListType(curNode->AsElement(), listType, itemType);
        if (NS_WARN_IF(!newBlock)) {
          return NS_ERROR_FAILURE;
        }
        rv = htmlEditor->RemoveBlockContainerWithTransaction(*newBlock);
        NS_ENSURE_SUCCESS(rv, rv);
      } else {
        // replace list with new list type
        curList = ConvertListType(curNode->AsElement(), listType, itemType);
        if (NS_WARN_IF(!curList)) {
          return NS_ERROR_FAILURE;
        }
      }
      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(listType)) {
        // 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 =
            htmlEditor->SplitNodeWithTransaction(atCurNode, error);
          if (NS_WARN_IF(error.Failed())) {
            return error.StealNSResult();
          }
          newBlock = newLeftNode ? newLeftNode->AsElement() : nullptr;
          EditorRawDOMPoint atParentOfCurNode(atCurNode.GetContainer());
          curList = htmlEditor->CreateNodeWithTransaction(*listType,
                                                          atParentOfCurNode);
          if (NS_WARN_IF(!curList)) {
            return NS_ERROR_FAILURE;
          }
        }
        // move list item to new list
        rv = htmlEditor->MoveNodeToEndWithTransaction(*curNode, *curList);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
        // convert list item type if needed
        if (!curNode->IsHTMLElement(itemType)) {
          newBlock =
            htmlEditor->ReplaceContainerWithTransaction(*curNode->AsElement(),
                                                        *itemType);
          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 = htmlEditor->MoveNodeToEndWithTransaction(*curNode, *curList);
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return rv;
          }
        }
        if (!curNode->IsHTMLElement(itemType)) {
          newBlock =
            htmlEditor->ReplaceContainerWithTransaction(*curNode->AsElement(),
                                                        *itemType);
          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 = htmlEditor->SetAttributeWithTransaction(*curElement,
                                                     *nsGkAtoms::type,
                                                     *aBulletType);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      } else {
        rv = htmlEditor->RemoveAttributeWithTransaction(*curElement,
                                                        *nsGkAtoms::type);
        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 = htmlEditor->RemoveContainerWithTransaction(*curNode->AsElement());
      NS_ENSURE_SUCCESS(rv, rv);
      listCount = arrayOfNodes.Length();
      continue;
    }

    // need to make a list to put things in if we haven't already,
    if (!curList) {
      SplitNodeResult splitCurNodeResult =
        MaybeSplitAncestorsForInsertWithTransaction(listType, atCurNode);
      if (NS_WARN_IF(splitCurNodeResult.Failed())) {
        return splitCurNodeResult.Rv();
      }
      curList =
        htmlEditor->CreateNodeWithTransaction(*listType,
                                              splitCurNodeResult.SplitPoint());
      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 = htmlEditor->MoveNodeToEndWithTransaction(*curNode, *prevListItem);
        NS_ENSURE_SUCCESS(rv, rv);
      } else {
        // don't wrap li around a paragraph.  instead replace paragraph with li
        if (curNode->IsHTMLElement(nsGkAtoms::p)) {
          listItem =
            htmlEditor->ReplaceContainerWithTransaction(*curNode->AsElement(),
                                                        *itemType);
          if (NS_WARN_IF(!listItem)) {
            return NS_ERROR_FAILURE;
          }
        } else {
          listItem =
            htmlEditor->InsertContainerWithTransaction(*curNode, *itemType);
          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 = htmlEditor->MoveNodeToEndWithTransaction(*listItem, *curList);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }
  }

  return NS_OK;
}


nsresult
HTMLEditRules::WillRemoveList(Selection* aSelection,
                              bool aOrdered,
                              bool* aCancel,
                              bool* aHandled)
{
  if (!aSelection || !aCancel || !aHandled) {
    return NS_ERROR_NULL_POINTER;
  }
  // initialize out param
  *aCancel = false;
  *aHandled = true;

  nsresult rv = NormalizeSelection(aSelection);
  NS_ENSURE_SUCCESS(rv, rv);
  NS_ENSURE_STATE(mHTMLEditor);
  AutoSelectionRestorer selectionRestorer(aSelection, mHTMLEditor);

  nsTArray<RefPtr<nsRange>> arrayOfRanges;
  GetPromotedRanges(*aSelection, arrayOfRanges, EditAction::makeList);

  // use these ranges to contruct a list of nodes to act on.
  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  rv = GetListActionNodes(arrayOfNodes, EntireList::no);
  NS_ENSURE_SUCCESS(rv, rv);

  // Remove all non-editable nodes.  Leave them be.
  for (int32_t i = arrayOfNodes.Length() - 1; i >= 0; i--) {
    OwningNonNull<nsINode> testNode = arrayOfNodes[i];
    NS_ENSURE_STATE(mHTMLEditor);
    if (!mHTMLEditor->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);
        NS_ENSURE_SUCCESS(rv, 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());
      NS_ENSURE_SUCCESS(rv, rv);
    }
  }
  return NS_OK;
}

nsresult
HTMLEditRules::WillMakeDefListItem(Selection* aSelection,
                                   const nsAString *aItemType,
                                   bool aEntireList,
                                   bool* aCancel,
                                   bool* aHandled)
{
  // for now we let WillMakeList handle this
  NS_NAMED_LITERAL_STRING(listType, "dl");
  return WillMakeList(aSelection, &listType.AsString(), aEntireList, nullptr,
                      aCancel, aHandled, aItemType);
}

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

  OwningNonNull<nsAtom> blockType = NS_Atomize(aBlockType);

  WillInsert(aSelection, aCancel);
  // We want to ignore result of WillInsert()
  *aCancel = false;
  *aHandled = true;

  nsresult rv = MakeBasicBlock(aSelection, blockType);
  Unused << NS_WARN_IF(NS_FAILED(rv));
  return rv;
}

nsresult
HTMLEditRules::MakeBasicBlock(Selection& aSelection, nsAtom& blockType)
{
  NS_ENSURE_STATE(mHTMLEditor);
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  nsresult rv = NormalizeSelection(&aSelection);
  NS_ENSURE_SUCCESS(rv, rv);
  AutoSelectionRestorer selectionRestorer(&aSelection, htmlEditor);
  AutoTransactionsConserveSelection dontChangeMySelection(htmlEditor);

  // Contruct a list of nodes to act on.
  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  rv = GetNodesFromSelection(aSelection, EditAction::makeBasicBlock,
                             arrayOfNodes);
  NS_ENSURE_SUCCESS(rv, rv);

  // If nothing visible in list, make an empty block
  if (ListIsEmptyLine(arrayOfNodes)) {
    nsRange* firstRange = aSelection.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 =
        htmlEditor->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 =
        htmlEditor->GetNextEditableHTMLNode(pointToInsertBlock);
      if (brContent && brContent->IsHTMLElement(nsGkAtoms::br)) {
        AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
        rv = htmlEditor->DeleteNodeWithTransaction(*brContent);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }
      // Do the splits!
      SplitNodeResult splitNodeResult =
        htmlEditor->SplitNodeDeepWithTransaction(
                      *curBlock, pointToInsertBlock,
                      SplitAtEdges::eDoNotCreateEmptyContainer);
      if (NS_WARN_IF(splitNodeResult.Failed())) {
        return splitNodeResult.Rv();
      }
      EditorRawDOMPoint pointToInsertBrNode(splitNodeResult.SplitPoint());
      // Put a <br> element at the split point
      brContent =
        htmlEditor->InsertBrElementWithTransaction(aSelection,
                                                   pointToInsertBrNode);
      if (NS_WARN_IF(!brContent)) {
        return NS_ERROR_FAILURE;
      }
      // Put selection at the split point
      EditorRawDOMPoint atBrNode(brContent);
      ErrorResult error;
      aSelection.Collapse(atBrNode, error);
      // Don't restore the selection
      selectionRestorer.Abort();
      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 =
      htmlEditor->GetNextEditableHTMLNodeInBlock(pointToInsertBlock);
    if (brNode && brNode->IsHTMLElement(nsGkAtoms::br)) {
      AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
      rv = htmlEditor->DeleteNodeWithTransaction(*brNode);
      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 =
      htmlEditor->CreateNodeWithTransaction(blockType,
                                            splitNodeResult.SplitPoint());
    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 = htmlEditor->DeleteNodeWithTransaction(*curNode);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      arrayOfNodes.RemoveElementAt(0);
    }
    // Put selection in new block
    rv = aSelection.Collapse(block, 0);
    // Don't restore the selection
    selectionRestorer.Abort();
    NS_ENSURE_SUCCESS(rv, 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);
    NS_ENSURE_SUCCESS(rv, rv);
  } else if (&blockType == nsGkAtoms::normal ||
             &blockType == nsGkAtoms::_empty) {
    rv = RemoveBlockStyle(arrayOfNodes);
    NS_ENSURE_SUCCESS(rv, rv);
  } else {
    rv = ApplyBlockStyle(arrayOfNodes, blockType);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  return NS_OK;
}

nsresult
HTMLEditRules::DidMakeBasicBlock(Selection* aSelection,
                                 RulesInfo* aInfo,
                                 nsresult aResult)
{
  NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
  // check for empty block.  if so, put a moz br in it.
  if (!aSelection->Collapsed()) {
    return NS_OK;
  }

  NS_ENSURE_STATE(aSelection->GetRangeAt(0) &&
                  aSelection->GetRangeAt(0)->GetStartContainer());
  nsresult rv =
    InsertMozBRIfNeeded(*aSelection->GetRangeAt(0)->GetStartContainer());
  NS_ENSURE_SUCCESS(rv, rv);
  return NS_OK;
}

nsresult
HTMLEditRules::WillIndent(Selection* aSelection,
                          bool* aCancel,
                          bool* aHandled)
{
  NS_ENSURE_STATE(mHTMLEditor);
  if (mHTMLEditor->IsCSSEnabled()) {
    nsresult rv = WillCSSIndent(aSelection, aCancel, aHandled);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  } else {
    nsresult rv = WillHTMLIndent(aSelection, aCancel, aHandled);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }
  return NS_OK;
}

nsresult
HTMLEditRules::WillCSSIndent(Selection* aSelection,
                             bool* aCancel,
                             bool* aHandled)
{
  if (!aSelection || !aCancel || !aHandled) {
    return NS_ERROR_NULL_POINTER;
  }

  if (NS_WARN_IF(!mHTMLEditor)) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  WillInsert(*aSelection, aCancel);

  // initialize out param
  // we want to ignore result of WillInsert()
  *aCancel = false;
  *aHandled = true;

  nsresult rv = NormalizeSelection(aSelection);
  NS_ENSURE_SUCCESS(rv, rv);
  AutoSelectionRestorer selectionRestorer(aSelection, htmlEditor);
  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 (aSelection->Collapsed()) {
    EditorRawDOMPoint selectionStartPoint(
                        EditorBase::GetStartPoint(aSelection));
    if (NS_WARN_IF(!selectionStartPoint.IsSet())) {
      return NS_ERROR_FAILURE;
    }
    Element* block =
      htmlEditor->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
    rv = GetNodesFromSelection(*aSelection, EditAction::indent, arrayOfNodes);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // if nothing visible in list, make an empty block
  if (ListIsEmptyLine(arrayOfNodes)) {
    // get selection location
    nsRange* firstRange = aSelection->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 =
      htmlEditor->CreateNodeWithTransaction(*nsGkAtoms::div,
                                            splitNodeResult.SplitPoint());
    if (NS_WARN_IF(!theBlock)) {
      return NS_ERROR_FAILURE;
    }
    // remember our new block for postprocessing
    mNewBlock = theBlock;
    ChangeIndentation(*theBlock, Change::plus);
    // delete anything that was in the list of nodes
    while (!arrayOfNodes.IsEmpty()) {
      OwningNonNull<nsINode> curNode = arrayOfNodes[0];
      rv = htmlEditor->DeleteNodeWithTransaction(*curNode);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      arrayOfNodes.RemoveElementAt(0);
    }
    // put selection in new block
    *aHandled = true;
    EditorRawDOMPoint atStartOfTheBlock(theBlock, 0);
    ErrorResult error;
    aSelection->Collapse(atStartOfTheBlock, error);
    // Don't restore the selection
    selectionRestorer.Abort();
    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 (!htmlEditor->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 = htmlEditor->GetNextHTMLSibling(curNode);
      if (sibling && HTMLEditUtils::IsList(sibling) &&
          atCurNode.GetContainer()->NodeInfo()->NameAtom() ==
            sibling->NodeInfo()->NameAtom() &&
          atCurNode.GetContainer()->NodeInfo()->NamespaceID() ==
            sibling->NodeInfo()->NamespaceID()) {
        rv = htmlEditor->MoveNodeWithTransaction(*curNode->AsContent(),
                                                 EditorRawDOMPoint(sibling, 0));
        NS_ENSURE_SUCCESS(rv, 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 = htmlEditor->GetPriorHTMLSibling(curNode);
      if (sibling && HTMLEditUtils::IsList(sibling) &&
          atCurNode.GetContainer()->NodeInfo()->NameAtom() ==
            sibling->NodeInfo()->NameAtom() &&
          atCurNode.GetContainer()->NodeInfo()->NamespaceID() ==
            sibling->NodeInfo()->NamespaceID()) {
        rv = htmlEditor->MoveNodeToEndWithTransaction(*curNode->AsContent(),
                                                      *sibling);
        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 = htmlEditor->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 =
          htmlEditor->CreateNodeWithTransaction(*containerName,
                                                splitNodeResult.SplitPoint());
        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 = htmlEditor->MoveNodeToEndWithTransaction(*curNode->AsContent(),
                                                    *curList);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      continue;
    }

    // Not a list item.

    if (IsBlockNode(*curNode)) {
      ChangeIndentation(*curNode->AsElement(), Change::plus);
      curQuote = nullptr;
      continue;
    }

    if (!curQuote) {
      // First, check that our element can contain a div.
      if (!htmlEditor->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 =
        htmlEditor->CreateNodeWithTransaction(*nsGkAtoms::div,
                                              splitNodeResult.SplitPoint());
      if (NS_WARN_IF(!curQuote)) {
        return NS_ERROR_FAILURE;
      }
      ChangeIndentation(*curQuote, Change::plus);
      // 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 = htmlEditor->MoveNodeToEndWithTransaction(*curNode->AsContent(),
                                                  *curQuote);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }
  return NS_OK;
}

nsresult
HTMLEditRules::WillHTMLIndent(Selection* aSelection,
                              bool* aCancel,
                              bool* aHandled)
{
  if (!aSelection || !aCancel || !aHandled) {
    return NS_ERROR_NULL_POINTER;
  }

  if (NS_WARN_IF(!mHTMLEditor)) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  WillInsert(*aSelection, aCancel);

  // initialize out param
  // we want to ignore result of WillInsert()
  *aCancel = false;
  *aHandled = true;

  nsresult rv = NormalizeSelection(aSelection);
  NS_ENSURE_SUCCESS(rv, rv);

  AutoSelectionRestorer selectionRestorer(aSelection, htmlEditor);

  // 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(*aSelection, arrayOfRanges, EditAction::indent);

  // use these ranges to contruct a list of nodes to act on.
  nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
  rv = GetNodesForOperation(arrayOfRanges, arrayOfNodes, EditAction::indent);
  NS_ENSURE_SUCCESS(rv, rv);

  // if nothing visible in list, make an empty block
  if (ListIsEmptyLine(arrayOfNodes)) {
    nsRange* firstRange = aSelection->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 =
      htmlEditor->CreateNodeWithTransaction(*nsGkAtoms::blockquote,
                                            splitNodeResult.SplitPoint());
    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 = htmlEditor->DeleteNodeWithTransaction(*curNode);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      arrayOfNodes.RemoveElementAt(0);
    }
    // put selection in new block
    *aHandled = true;
    EditorRawDOMPoint atStartOfTheBlock(theBlock, 0);
    ErrorResult error;
    aSelection->Collapse(atStartOfTheBlock, error);
    // Don't restore the selection
    selectionRestorer.Abort();
    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 (!htmlEditor->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 = htmlEditor->GetNextHTMLSibling(curNode);
      if (sibling && HTMLEditUtils::IsList(sibling) &&
          atCurNode.GetContainer()->NodeInfo()->NameAtom() ==
            sibling->NodeInfo()->NameAtom() &&
          atCurNode.GetContainer()->NodeInfo()->NamespaceID() ==
            sibling->NodeInfo()->NamespaceID()) {
        rv = htmlEditor->MoveNodeWithTransaction(*curNode->AsContent(),
                                                 EditorRawDOMPoint(sibling, 0));
        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 = htmlEditor->GetPriorHTMLSibling(curNode);
      if (sibling && HTMLEditUtils::IsList(sibling) &&
          atCurNode.GetContainer()->NodeInfo()->NameAtom() ==
            sibling->NodeInfo()->NameAtom() &&
          atCurNode.GetContainer()->NodeInfo()->NamespaceID() ==
            sibling->NodeInfo()->NamespaceID()) {
        rv = htmlEditor->MoveNodeToEndWithTransaction(*curNode->AsContent(),
                                                      *sibling);
        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 = htmlEditor->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 =
          htmlEditor->CreateNodeWithTransaction(*containerName,
                                                splitNodeResult.SplitPoint());
        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 = htmlEditor->MoveNodeToEndWithTransaction(*curNode->AsContent(),
                                                    *curList);
      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 = htmlEditor->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 =
          htmlEditor->CreateNodeWithTransaction(*containerName,
                                                splitNodeResult.SplitPoint());
        if (NS_WARN_IF(!curList)) {
          return NS_ERROR_FAILURE;
        }
      }

      rv = htmlEditor->MoveNodeToEndWithTransaction(*listItem, *curList);
      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 (!htmlEditor->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 =
        htmlEditor->CreateNodeWithTransaction(*nsGkAtoms::blockquote,
                                              splitNodeResult.SplitPoint());
      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 = htmlEditor->MoveNodeToEndWithTransaction(*curNode->AsContent(),
                                                  *curQuote);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    // forget curList, if any
    curList = nullptr;
  }
  return NS_OK;
}


nsresult
HTMLEditRules::WillOutdent(Selection& aSelection,
                           bool* aCancel,
                           bool* aHandled)
{
  MOZ_ASSERT(aCancel && aHandled);
  *aCancel = false;
  *aHandled = true;
  nsCOMPtr<nsIContent> rememberedLeftBQ, rememberedRightBQ;
  NS_ENSURE_STATE(mHTMLEditor);
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
  bool useCSS = htmlEditor->IsCSSEnabled();

  nsresult rv = NormalizeSelection(&aSelection);
  NS_ENSURE_SUCCESS(rv, rv);

  // Some scoping for selection resetting - we may need to tweak it
  {
    AutoSelectionRestorer selectionRestorer(&aSelection, htmlEditor);

    // 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;
    rv = GetNodesFromSelection(aSelection, EditAction::outdent, arrayOfNodes);
    NS_ENSURE_SUCCESS(rv, rv);

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

    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) {
          rv = OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
                                  curBlockQuoteIsIndentedWithCSS,
                                  getter_AddRefs(rememberedLeftBQ),
                                  getter_AddRefs(rememberedRightBQ));
          NS_ENSURE_SUCCESS(rv, rv);
          curBlockQuote = nullptr;
          firstBQChild = nullptr;
          lastBQChild = nullptr;
          curBlockQuoteIsIndentedWithCSS = false;
        }
        rv = htmlEditor->RemoveBlockContainerWithTransaction(
                           *curNode->AsElement());
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return 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);
        float f;
        RefPtr<nsAtom> unit;
        CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit));
        if (f > 0) {
          ChangeIndentation(*curNode->AsElement(), Change::minus);
          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) {
          rv = OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
                                  curBlockQuoteIsIndentedWithCSS,
                                  getter_AddRefs(rememberedLeftBQ),
                                  getter_AddRefs(rememberedRightBQ));
          NS_ENSURE_SUCCESS(rv, rv);
          curBlockQuote = nullptr;
          firstBQChild = nullptr;
          lastBQChild = nullptr;
          curBlockQuoteIsIndentedWithCSS = false;
        }
        rv = PopListItem(*curNode->AsContent());
        NS_ENSURE_SUCCESS(rv, 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].
        rv = OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
                                curBlockQuoteIsIndentedWithCSS,
                                getter_AddRefs(rememberedLeftBQ),
                                getter_AddRefs(rememberedRightBQ));
        NS_ENSURE_SUCCESS(rv, rv);
        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) && 
             htmlEditor->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;
        } else if (useCSS) {
          nsAtom& marginProperty = MarginPropertyAtomForIndent(curNode);
          nsAutoString value;
          CSSEditUtils::GetSpecifiedProperty(*n, marginProperty, value);
          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) {
        // Couldn't find enclosing blockquote.  Handle list cases.
        if (HTMLEditUtils::IsList(curParent)) {
          // Move node out of list
          if (HTMLEditUtils::IsList(curNode)) {
            // Just unwrap this sublist
            rv = htmlEditor->RemoveBlockContainerWithTransaction(
                               *curNode->AsElement());
            if (NS_WARN_IF(NS_FAILED(rv))) {
              return rv;
            }
          }
          // handled list item case above
        } else 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);
              NS_ENSURE_SUCCESS(rv, 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 = htmlEditor->MoveNodeWithTransaction(*child,
                                                       afterCurrentList);
              if (NS_WARN_IF(NS_FAILED(rv))) {
                return rv;
              }
            } else {
              // Delete any non-list items for now
              rv = htmlEditor->DeleteNodeWithTransaction(*child);
              if (NS_WARN_IF(NS_FAILED(rv))) {
                return rv;
              }
            }
            child = curNode->GetLastChild();
          }
          // Delete the now-empty list
          rv = htmlEditor->RemoveBlockContainerWithTransaction(
                             *curNode->AsElement());
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return rv;
          }
        } else 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) {
            ChangeIndentation(*element, Change::minus);
          }
        }
      }
    }
    if (curBlockQuote) {
      // We have a blockquote we haven't finished handling
      rv = OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
                              curBlockQuoteIsIndentedWithCSS,
                              getter_AddRefs(rememberedLeftBQ),
                              getter_AddRefs(rememberedRightBQ));
      NS_ENSURE_SUCCESS(rv, rv);
    }
  }
  // Make sure selection didn't stick to last piece of content in old bq (only
  // a problem for collapsed selections)
  if (rememberedLeftBQ || rememberedRightBQ) {
    if (aSelection.Collapsed()) {
      // Push selection past end of rememberedLeftBQ
      NS_ENSURE_TRUE(aSelection.GetRangeAt(0), NS_OK);
      nsCOMPtr<nsINode> startNode =
        aSelection.GetRangeAt(0)->GetStartContainer();
      if (rememberedLeftBQ &&
          (startNode == rememberedLeftBQ ||
           EditorUtils::IsDescendantOf(*startNode, *rememberedLeftBQ))) {
        // Selection is inside rememberedLeftBQ - push it past it.
        EditorRawDOMPoint afterRememberedLeftBQ(rememberedLeftBQ);
        afterRememberedLeftBQ.AdvanceOffset();
        aSelection.Collapse(afterRememberedLeftBQ);
      }
      // And pull selection before beginning of rememberedRightBQ
      startNode = aSelection.GetRangeAt(0)->GetStartContainer();
      if (rememberedRightBQ &&
          (startNode == rememberedRightBQ ||
           EditorUtils::IsDescendantOf(*startNode, *rememberedRightBQ))) {
        // Selection is inside rememberedRightBQ - push it before it.
        EditorRawDOMPoint atRememberedRightBQ(rememberedRightBQ);
        aSelection.Collapse(atRememberedRightBQ);
      }
    }
    return NS_OK;
  }
  return NS_OK;
}


/**
 * RemovePartOfBlock() splits aBlock and move aStartChild to aEndChild out of
 * aBlock.
 */
nsresult
HTMLEditRules::RemovePartOfBlock(Element& aBlock,
                                 nsIContent& aStartChild,
                                 nsIContent& aEndChild)
{
  SplitBlock(aBlock, aStartChild, aEndChild);
  // Get rid of part of blockquote we are outdenting

  NS_ENSURE_STATE(mHTMLEditor);
  nsresult rv = mHTMLEditor->RemoveBlockContainerWithTransaction(aBlock);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

void
HTMLEditRules::SplitBlock(Element& aBlock,
                          nsIContent& aStartChild,
                          nsIContent& aEndChild,
                          nsIContent** aOutLeftNode,
                          nsIContent** aOutRightNode,
                          nsIContent** aOutMiddleNode)
{
  // aStartChild and aEndChild must be exclusive descendants of aBlock
  MOZ_ASSERT(EditorUtils::IsDescendantOf(aStartChild, aBlock) &&
             EditorUtils::IsDescendantOf(aEndChild, aBlock));
  NS_ENSURE_TRUE_VOID(mHTMLEditor);
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  // Split at the start.
  SplitNodeResult splitAtStartResult =
    htmlEditor->SplitNodeDeepWithTransaction(
                  aBlock, EditorRawDOMPoint(&aStartChild),
                  SplitAtEdges::eDoNotCreateEmptyContainer);
  NS_WARNING_ASSERTION(splitAtStartResult.Succeeded(),
    "Failed to split aBlock at start");

  // Split at after the end
  EditorRawDOMPoint atAfterEnd(&aEndChild);
  DebugOnly<bool> advanced = atAfterEnd.AdvanceOffset();
  NS_WARNING_ASSERTION(advanced,
    "Failed to advance offset after the end node");
  SplitNodeResult splitAtEndResult =
    htmlEditor->SplitNodeDeepWithTransaction(
                  aBlock, atAfterEnd,
                  SplitAtEdges::eDoNotCreateEmptyContainer);
  NS_WARNING_ASSERTION(splitAtEndResult.Succeeded(),
    "Failed to split aBlock at after end");

  if (aOutLeftNode) {
    NS_IF_ADDREF(*aOutLeftNode = splitAtStartResult.GetPreviousNode());
  }

  if (aOutRightNode) {
    NS_IF_ADDREF(*aOutRightNode = splitAtEndResult.GetNextNode());
  }

  if (aOutMiddleNode) {
    if (splitAtEndResult.GetPreviousNode()) {
      NS_IF_ADDREF(*aOutMiddleNode = splitAtEndResult.GetPreviousNode());
    } else {
      NS_IF_ADDREF(*aOutMiddleNode = splitAtStartResult.GetNextNode());
    }
  }
}

nsresult
HTMLEditRules::OutdentPartOfBlock(Element& aBlock,
                                  nsIContent& aStartChild,
                                  nsIContent& aEndChild,
                                  bool aIsBlockIndentedWithCSS,
                                  nsIContent** aOutLeftNode,
                                  nsIContent** aOutRightNode)
{
  MOZ_ASSERT(aOutLeftNode && aOutRightNode);

  nsCOMPtr<nsIContent> middleNode;
  SplitBlock(aBlock, aStartChild, aEndChild, aOutLeftNode, aOutRightNode,
             getter_AddRefs(middleNode));

  if (NS_WARN_IF(!middleNode) || NS_WARN_IF(!middleNode->IsElement())) {
    return NS_ERROR_FAILURE;
  }

  if (!aIsBlockIndentedWithCSS) {
    NS_ENSURE_STATE(mHTMLEditor);
    nsresult rv =
      mHTMLEditor->RemoveBlockContainerWithTransaction(
                     *middleNode->AsElement());
    NS_ENSURE_SUCCESS(rv, rv);
  } else if (middleNode->IsElement()) {
    // We do nothing if middleNode isn't an element
    nsresult rv = ChangeIndentation(*middleNode->AsElement(), Change::minus);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  return NS_OK;
}

/**
 * ConvertListType() converts list type and list item type.
 */
already_AddRefed<Element>
HTMLEditRules::ConvertListType(Element* aList,
                               nsAtom* aListType,
                               nsAtom* aItemType)
{
  MOZ_ASSERT(aList);
  MOZ_ASSERT(aListType);
  MOZ_ASSERT(aItemType);

  nsCOMPtr<nsINode> child = aList->GetFirstChild();
  while (child) {
    if (child->IsElement()) {
      dom::Element* element = child->AsElement();
      if (HTMLEditUtils::IsListItem(element) &&
          !element->IsHTMLElement(aItemType)) {
        child =
          mHTMLEditor->ReplaceContainerWithTransaction(*element, *aItemType);
        if (NS_WARN_IF(!child)) {
          return nullptr;
        }
      } else if (HTMLEditUtils::IsList(element) &&
                 !element->IsHTMLElement(aListType)) {
        child = ConvertListType(child->AsElement(), aListType, aItemType);
        if (NS_WARN_IF(!child)) {
          return nullptr;
        }
      }
    }
    child = child->GetNextSibling();
  }

  if (aList->IsHTMLElement(aListType)) {
    RefPtr<dom::Element> list = aList->AsElement();
    return list.forget();
  }

  return mHTMLEditor->ReplaceContainerWithTransaction(*aList, *aListType);
}


/**
 * CreateStyleForInsertText() takes care of clearing and setting appropriate
 * style nodes for text insertion.
 */
nsresult
HTMLEditRules::CreateStyleForInsertText(Selection& aSelection,
                                        nsIDocument& aDoc)
{
  if (NS_WARN_IF(!mHTMLEditor)) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  MOZ_ASSERT(htmlEditor->mTypeInState);

  bool weDidSomething = false;
  NS_ENSURE_STATE(aSelection.GetRangeAt(0));
  nsCOMPtr<nsINode> node = aSelection.GetRangeAt(0)->GetStartContainer();
  int32_t offset = aSelection.GetRangeAt(0)->StartOffset();

  nsCOMPtr<Element> rootElement = aDoc.GetRootElement();
  NS_ENSURE_STATE(rootElement);

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

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

    while (item && node != rootElement) {
      // XXX If we redesign ClearStyle(), we can use EditorDOMPoint in this
      //     method.
      nsresult rv =
        htmlEditor->ClearStyle(address_of(node), &offset,
                               item->tag, item->attr);
      NS_ENSURE_SUCCESS(rv, rv);
      item = Move(htmlEditor->mTypeInState->TakeClearProperty());
      weDidSomething = true;
    }
  }

  // then process setting any styles
  int32_t relFontSize = htmlEditor->mTypeInState->TakeRelativeFontSize();
  item = Move(htmlEditor->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 =
        htmlEditor->SplitNodeDeepWithTransaction(
                       *text, EditorRawDOMPoint(text, offset),
                       SplitAtEdges::eAllowToCreateEmptyContainer);
      if (NS_WARN_IF(splitTextNodeResult.Failed())) {
        return splitTextNodeResult.Rv();
      }
      EditorRawDOMPoint splitPoint(splitTextNodeResult.SplitPoint());
      node = splitPoint.GetContainer();
      offset = splitPoint.Offset();
    }
    if (!htmlEditor->IsContainer(node)) {
      return NS_OK;
    }
    OwningNonNull<Text> newNode =
      EditorBase::CreateTextNode(aDoc, EmptyString());
    nsresult rv =
      htmlEditor->InsertNodeWithTransaction(*newNode,
                                            EditorRawDOMPoint(node, offset));
    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 = htmlEditor->RelativeFontChangeOnTextNode(dir, newNode, 0, -1);
        NS_ENSURE_SUCCESS(rv, rv);
      }
    }

    while (item) {
      rv = htmlEditor->SetInlinePropertyOnNode(*node->AsContent(),
                                               *item->tag, item->attr,
                                               item->value);
      NS_ENSURE_SUCCESS(rv, rv);
      item = htmlEditor->mTypeInState->TakeSetProperty();
    }
  }
  if (weDidSomething) {
    return aSelection.Collapse(node, offset);
  }

  return NS_OK;
}

bool
HTMLEditRules::IsEmptyBlockElement(Element& aElement,
                                   IgnoreSingleBR aIgnoreSingleBR)
{
  if (NS_WARN_IF(!IsBlockNode(aElement))) {
    return false;
  }
  bool isEmpty = true;
  nsresult rv =
    mHTMLEditor->IsEmptyNode(&aElement, &isEmpty,
                             aIgnoreSingleBR == IgnoreSingleBR::eYes);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return false;
  }
  return isEmpty;
}

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

  NS_ENSURE_STATE(mHTMLEditor);
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  WillInsert(aSelection, aCancel);

  // Initialize out param.  We want to ignore result of WillInsert().
  *aCancel = false;
  *aHandled = false;

  nsresult rv = NormalizeSelection(&aSelection);
  NS_ENSURE_SUCCESS(rv, rv);
  AutoSelectionRestorer selectionRestorer(&aSelection, htmlEditor);

  // 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;
  nsTArray<OwningNonNull<nsINode>> nodeArray;
  rv = GetNodesFromSelection(aSelection, EditAction::align, nodeArray);
  NS_ENSURE_SUCCESS(rv, rv);

  // If we don't have any nodes, or we have only a single br, then we are
  // creating an empty alignment div.  We have to do some different things for
  // these.
  bool emptyDiv = nodeArray.IsEmpty();
  if (nodeArray.Length() == 1) {
    OwningNonNull<nsINode> node = nodeArray[0];

    if (HTMLEditUtils::SupportsAlignAttr(*node)) {
      // The node is a table element, an hr, a paragraph, a div or a section
      // header; in HTML 4, it can directly carry the ALIGN attribute and we
      // don't need to make a div! If we are in CSS mode, all the work is done
      // in AlignBlock
      rv = AlignBlock(*node->AsElement(), aAlignType, ContentsOnly::yes);
      NS_ENSURE_SUCCESS(rv, rv);
      return NS_OK;
    }

    if (TextEditUtils::IsBreak(node)) {
      // The special case emptyDiv code (below) that consumes BRs can cause
      // tables to split if the start node of the selection is not in a table
      // cell or caption, for example parent is a <tr>.  Avoid this unnecessary
      // splitting if possible by leaving emptyDiv FALSE so that we fall
      // through to the normal case alignment code.
      //
      // XXX: It seems a little error prone for the emptyDiv special case code
      // to assume that the start node of the selection is the parent of the
      // single node in the nodeArray, as the paragraph above points out. Do we
      // rely on the selection start node because of the fact that nodeArray
      // can be empty?  We should probably revisit this issue. - kin

      NS_ENSURE_STATE(aSelection.GetRangeAt(0) &&
                      aSelection.GetRangeAt(0)->GetStartContainer());
      OwningNonNull<nsINode> parent =
        *aSelection.GetRangeAt(0)->GetStartContainer();

      emptyDiv = !HTMLEditUtils::IsTableElement(parent) ||
                 HTMLEditUtils::IsTableCellOrCaption(parent);
    }
  }
  if (emptyDiv) {
    nsRange* firstRange = aSelection.GetRangeAt(0);
    if (NS_WARN_IF(!firstRange)) {
      return NS_ERROR_FAILURE;
    }

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

    SplitNodeResult splitNodeResult =
      MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div,
                                                  atStartOfSelection);
    if (NS_WARN_IF(splitNodeResult.Failed())) {
      return splitNodeResult.Rv();
    }

    // Consume a trailing br, if any.  This is to keep an alignment from
    // creating extra lines, if possible.
    nsCOMPtr<nsIContent> brContent =
      htmlEditor->GetNextEditableHTMLNodeInBlock(splitNodeResult.SplitPoint());
    EditorDOMPoint pointToInsertDiv(splitNodeResult.SplitPoint());
    if (brContent && TextEditUtils::IsBreak(brContent)) {
      // Making use of html structure... if next node after where we are
      // putting our div is not a block, then the br we found is in same block
      // we are, so it's safe to consume it.
      nsCOMPtr<nsIContent> sibling;
      if (pointToInsertDiv.GetChild()) {
        sibling = htmlEditor->GetNextHTMLSibling(pointToInsertDiv.GetChild());
      }
      if (sibling && !IsBlockNode(*sibling)) {
        AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertDiv);
        rv = htmlEditor->DeleteNodeWithTransaction(*brContent);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }
    }
    RefPtr<Element> div =
      htmlEditor->CreateNodeWithTransaction(*nsGkAtoms::div, pointToInsertDiv);
    if (NS_WARN_IF(!div)) {
      return NS_ERROR_FAILURE;
    }
    // Remember our new block for postprocessing
    mNewBlock = div;
    // Set up the alignment on the div, using HTML or CSS
    rv = AlignBlock(*div, aAlignType, ContentsOnly::yes);
    NS_ENSURE_SUCCESS(rv, rv);
    *aHandled = true;
    // Put in a moz-br so that it won't get deleted
    RefPtr<Element> brElement = CreateMozBR(EditorRawDOMPoint(div, 0));
    if (NS_WARN_IF(!brElement)) {
      return NS_ERROR_FAILURE;
    }
    EditorRawDOMPoint atStartOfDiv(div, 0);
    ErrorResult error;
    aSelection.Collapse(atStartOfDiv, error);
    // Don't restore the selection
    selectionRestorer.Abort();
    if (NS_WARN_IF(error.Failed())) {
      return error.StealNSResult();
    }
    return NS_OK;
  }

  // Next we detect all the transitions in the array, where a transition
  // means that adjacent nodes in the array don't have the same parent.

  nsTArray<bool> transitionList;
  MakeTransitionList(nodeArray, transitionList);

  // Okay, now go through all the nodes and give them an align attrib or put
  // them in a div, or whatever is appropriate.  Woohoo!

  nsCOMPtr<Element> curDiv;
  bool useCSS = htmlEditor->IsCSSEnabled();
  int32_t indexOfTransitionList = -1;
  for (OwningNonNull<nsINode>& curNode : nodeArray) {
    ++indexOfTransitionList;

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

    // The node is a table element, an hr, a paragraph, a div or a section
    // header; in HTML 4, it can directly carry the ALIGN attribute and we
    // don't need to nest it, just set the alignment.  In CSS, assign the
    // corresponding CSS styles in AlignBlock
    if (HTMLEditUtils::SupportsAlignAttr(*curNode)) {
      rv = AlignBlock(*curNode->AsElement(), aAlignType, ContentsOnly::no);
      NS_ENSURE_SUCCESS(rv, rv);
      // Clear out curDiv so that we don't put nodes after this one into it
      curDiv = nullptr;
      continue;
    }

    EditorDOMPoint atCurNode(curNode);
    if (NS_WARN_IF(!atCurNode.IsSet())) {
      continue;
    }

    // Skip insignificant formatting text nodes to prevent unnecessary
    // structure splitting!
    bool isEmptyTextNode = false;
    if (curNode->GetAsText() &&
        ((HTMLEditUtils::IsTableElement(atCurNode.GetContainer()) &&
          !HTMLEditUtils::IsTableCellOrCaption(*atCurNode.GetContainer())) ||
         HTMLEditUtils::IsList(atCurNode.GetContainer()) ||
         (NS_SUCCEEDED(htmlEditor->IsEmptyNode(curNode, &isEmptyTextNode)) &&
          isEmptyTextNode))) {
      continue;
    }

    // If it's a list item, or a list inside a list, forget any "current" div,
    // and instead put divs inside the appropriate block (td, li, etc.)
    if (HTMLEditUtils::IsListItem(curNode) ||
        HTMLEditUtils::IsList(curNode)) {
      AutoEditorDOMPointOffsetInvalidator lockChild(atCurNode);
      rv = RemoveAlignment(*curNode, aAlignType, true);
      NS_ENSURE_SUCCESS(rv, rv);
      if (useCSS) {
        htmlEditor->mCSSEditUtils->SetCSSEquivalentToHTMLStyle(
            curNode->AsElement(), nullptr,
            nsGkAtoms::align, &aAlignType, false);
        curDiv = nullptr;
        continue;
      }
      if (HTMLEditUtils::IsList(atCurNode.GetContainer())) {
        // If we don't use CSS, add a contraint to list element: they have to
        // be inside another list, i.e., >= second level of nesting
        rv = AlignInnerBlocks(*curNode, aAlignType);
        NS_ENSURE_SUCCESS(rv, rv);
        curDiv = nullptr;
        continue;
      }
      // Clear out curDiv so that we don't put nodes after this one into it
    }

    // Need to make a div to put things in if we haven't already, or if this
    // node doesn't go in div we used earlier.
    if (!curDiv || transitionList[indexOfTransitionList]) {
      // First, check that our element can contain a div.
      if (!htmlEditor->CanContainTag(*atCurNode.GetContainer(),
                                     *nsGkAtoms::div)) {
        // Cancelled
        return NS_OK;
      }

      SplitNodeResult splitNodeResult =
        MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div, atCurNode);
      if (NS_WARN_IF(splitNodeResult.Failed())) {
        return splitNodeResult.Rv();
      }
      curDiv =
        htmlEditor->CreateNodeWithTransaction(*nsGkAtoms::div,
                                              splitNodeResult.SplitPoint());
      if (NS_WARN_IF(!curDiv)) {
        return NS_ERROR_FAILURE;
      }
      // Remember our new block for postprocessing
      mNewBlock = curDiv;
      // Set up the alignment on the div
      rv = AlignBlock(*curDiv, aAlignType, ContentsOnly::yes);
    }

    // Tuck the node into the end of the active div
    rv = htmlEditor->MoveNodeToEndWithTransaction(*curNode->AsContent(),
                                                  *curDiv);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }

  return NS_OK;
}


/**
 * AlignInnerBlocks() aligns inside table cells or list items.
 */
nsresult
HTMLEditRules::AlignInnerBlocks(nsINode& aNode,
                                const nsAString& aAlignType)
{
  // Gather list of table cells or list items
  nsTArray<OwningNonNull<nsINode>> nodeArray;
  TableCellAndListItemFunctor functor;
  DOMIterator iter(aNode);
  iter.AppendList(functor, nodeArray);

  // Now that we have the list, align their contents as requested
  for (auto& node : nodeArray) {
    nsresult rv = AlignBlockContents(*node, aAlignType);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }

  return NS_OK;
}


/**
 * AlignBlockContents() aligns contents of a block element.
 */
nsresult
HTMLEditRules::AlignBlockContents(nsINode& aNode,
                                  const nsAString& aAlignType)
{
  if (NS_WARN_IF(!mHTMLEditor)) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  nsCOMPtr<nsIContent> firstChild = htmlEditor->GetFirstEditableChild(aNode);
  if (!firstChild) {
    // this cell has no content, nothing to align
    return NS_OK;
  }

  nsCOMPtr<nsIContent> lastChild = htmlEditor->GetLastEditableChild(aNode);
  if (firstChild == lastChild && firstChild->IsHTMLElement(nsGkAtoms::div)) {
    // the cell already has a div containing all of its content: just
    // act on this div.
    return htmlEditor->SetAttributeOrEquivalent(firstChild->AsElement(),
                                                nsGkAtoms::align,
                                                aAlignType, false);
  }

  // else we need to put in a div, set the alignment, and toss in all the
  // children
  EditorRawDOMPoint atStartOfNode(&aNode, 0);
  RefPtr<Element> divElem =
    htmlEditor->CreateNodeWithTransaction(*nsGkAtoms::div, atStartOfNode);
  if (NS_WARN_IF(!divElem)) {
    return NS_ERROR_FAILURE;
  }
  // set up the alignment on the div
  nsresult rv =
    htmlEditor->SetAttributeOrEquivalent(divElem, nsGkAtoms::align,
                                         aAlignType, false);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  // tuck the children into the end of the active div
  while (lastChild && (lastChild != divElem)) {
    nsresult rv =
      htmlEditor->MoveNodeWithTransaction(*lastChild,
                                          EditorRawDOMPoint(divElem, 0));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    lastChild = htmlEditor->GetLastEditableChild(aNode);
  }
  return NS_OK;
}

/**
 * CheckForEmptyBlock() is called by WillDeleteSelection() to detect and handle
 * case of deleting from inside an empty block.
 */
nsresult
HTMLEditRules::CheckForEmptyBlock(nsINode* aStartNode,
                                  Element* aBodyNode,
                                  Selection* aSelection,
                                  nsIEditor::EDirection aAction,
                                  bool* aHandled)
{
  // If the editing host is an inline element, bail out early.
  if (aBodyNode && IsInlineNode(*aBodyNode)) {
    return NS_OK;
  }

  if (NS_WARN_IF(!mHTMLEditor)) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);

  // If we are inside an empty block, delete it.  Note: do NOT delete table
  // elements this way.
  RefPtr<Element> block = htmlEditor->GetBlock(*aStartNode);
  bool bIsEmptyNode;
  RefPtr<Element> emptyBlock;
  if (block && block != aBodyNode) {
    // Efficiency hack, avoiding IsEmptyNode() call when in body
    nsresult rv = htmlEditor->IsEmptyNode(block, &bIsEmptyNode, true, false);
    NS_ENSURE_SUCCESS(rv, rv);
    while (block && bIsEmptyNode && !HTMLEditUtils::IsTableElement(block) &&
           block != aBodyNode) {
      emptyBlock = block;
      block = htmlEditor->GetBlockNodeParent(emptyBlock);
      if (block) {
        rv = htmlEditor->IsEmptyNode(block, &bIsEmptyNode, true, false);
        NS_ENSURE_SUCCESS(rv, rv);
      }
    }
  }

  if (emptyBlock && emptyBlock->IsEditable()) {
    nsCOMPtr<nsINode> blockParent = emptyBlock->GetParentNode();
    NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE);

    if (HTMLEditUtils::IsListItem(emptyBlock)) {
      // Are we the first list item in the list?
      if (htmlEditor->IsFirstEditableChild(emptyBlock)) {
        EditorDOMPoint atBlockParent(blockParent);
        if (NS_WARN_IF(!atBlockParent.IsSet())) {
          return NS_ERROR_FAILURE;
        }
        // If we are a sublist, skip the br creation
        if (!HTMLEditUtils::IsList(atBlockParent.GetContainer())) {
          // Create a br before list
          RefPtr<Element> brElement =
            htmlEditor->InsertBrElementWithTransaction(*aSelection,
                                                       atBlockParent);
          if (NS_WARN_IF(!brElement)) {
            return NS_ERROR_FAILURE;
          }
          // Adjust selection to be right before it
          ErrorResult error;
          aSelection->Collapse(EditorRawDOMPoint(brElement), error);
          if (NS_WARN_IF(error.Failed())) {
            return error.StealNSResult();
          }
        }
        // Else just let selection percolate up.  We'll adjust it in
        // AfterEdit()
      }
    } else {
      if (aAction == nsIEditor::eNext || aAction == nsIEditor::eNextWord ||
          aAction == nsIEditor::eToEndOfLine) {
        // Move to the start of the next node, if any
        EditorRawDOMPoint afterEmptyBlock(emptyBlock);
        DebugOnly<bool> advanced = afterEmptyBlock.AdvanceOffset();
        NS_WARNING_ASSERTION(advanced,
          "Failed to set selection to the after the empty block");
        nsCOMPtr<nsIContent> nextNode =
          htmlEditor->GetNextNode(afterEmptyBlock);
        if (nextNode) {
          EditorDOMPoint pt = GetGoodSelPointForNode(*nextNode, aAction);
          ErrorResult error;
          aSelection->Collapse(pt, error);
          if (NS_WARN_IF(error.Failed())) {
            return error.StealNSResult();
          }
        } else {
          // Adjust selection to be right after it.
          EditorRawDOMPoint afterEmptyBlock(emptyBlock);
          if (NS_WARN_IF(!afterEmptyBlock.AdvanceOffset())) {
            return NS_ERROR_FAILURE;
          }
          ErrorResult error;
          aSelection->Collapse(afterEmptyBlock, error);
          if (NS_WARN_IF(error.Failed())) {
            return error.StealNSResult();
          }
        }
      } else if (aAction == nsIEditor::ePrevious ||
                 aAction == nsIEditor::ePreviousWord ||
                 aAction == nsIEditor::eToBeginningOfLine) {
        // Move to the end of the previous node
        EditorRawDOMPoint atEmptyBlock(emptyBlock);
        nsCOMPtr<nsIContent> priorNode =
          htmlEditor->GetPreviousEditableNode(atEmptyBlock);
        if (priorNode) {
          EditorDOMPoint pt = GetGoodSelPointForNode(*priorNode, aAction);
          nsresult rv = aSelection->Collapse(pt);
          NS_ENSURE_SUCCESS(rv, rv);
        } else {
          EditorRawDOMPoint afterEmptyBlock(emptyBlock);
          if (NS_WARN_IF(!afterEmptyBlock.AdvanceOffset())) {
            return NS_ERROR_FAILURE;
          }
          nsresult rv = aSelection->Collapse(afterEmptyBlock);
          NS_ENSURE_SUCCESS(rv, rv);
        }
      } else if (aAction != nsIEditor::eNone) {
        MOZ_CRASH("CheckForEmptyBlock doesn't support this action yet");
      }
    }
    *aHandled = true;
    nsresult rv = htmlEditor->DeleteNodeWithTransaction(*emptyBlock);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }
  return NS_OK;
}

Element*
HTMLEditRules::CheckForInvisibleBR(Element& aBlock,
                                   BRLocation aWhere,
                                   int32_t aOffset)
{
  nsCOMPtr<nsINode> testNode;
  int32_t testOffset = 0;

  if (aWhere == BRLocation::blockEnd) {
    // No block crossing
    nsCOMPtr<nsIContent> rightmostNode =
      mHTMLEditor->GetRightmostChild(&aBlock, true);

    if (!rightmostNode) {
      return nullptr;
    }

    testNode = rightmostNode->GetParentNode();
    // Since rightmostNode is always the last child, its index is equal to the
    // child count, so instead of ComputeIndexOf() we use the faster
    // GetChildCount(), and assert the equivalence below.
    testOffset = testNode->GetChildCount();

    // Use offset + 1, so last node is included in our evaluation
    MOZ_ASSERT(testNode->ComputeIndexOf(rightmostNode) + 1 == testOffset);
  } else if (aOffset) {
    testNode = &aBlock;
    // We'll check everything to the left of the input position
    testOffset = aOffset;
  } else {
    return nullptr;
  }

  WSRunObject wsTester(mHTMLEditor, testNode, testOffset);
  if (WSType::br == wsTester.mStartReason) {
    return wsTester.mStartReasonNode->AsElement();
  }

  return nullptr;
}

/**
 * aLists and aTables allow the caller to specify what kind of content to
 * "look inside".  If aTables is Tables::yes, look inside any table content,
 * and insert the inner content into the supplied nsTArray at offset
 * aIndex.  Similarly with aLists and list content.  aIndex is updated to
 * point past inserted elements.
 */
void
HTMLEditRules::GetInnerContent(
                 nsINode& aNode,
                 nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes,
                 int32_t* aIndex,
                 Lists aLists,
                 Tables aTables)
{
  MOZ_ASSERT(aIndex);

  for (nsCOMPtr<nsIContent> node = mHTMLEditor->GetFirstEditableChild(aNode);
       node; node = node->GetNextSibling()) {
    if ((aLists == Lists::yes && (HTMLEditUtils::IsList(node) ||
                                  HTMLEditUtils::IsListItem(node))) ||
        (aTables == Tables::yes && HTMLEditUtils::IsTableElement(node))) {
      GetInnerContent(*node, aOutArrayOfNodes, aIndex, aLists, aTables);
    } else {
      aOutArrayOfNodes.InsertElementAt(*aIndex, *node);
      (*aIndex)++;
    }
  }
}

/**
 * Promotes selection to include blocks that have all their children selected.
 */
nsresult
HTMLEditRules::ExpandSelectionForDeletion(Selection& aSelection)
{
  // Don't need to touch collapsed selections
  if (aSelection.Collapsed()) {
    return NS_OK;
  }

  // We don't need to mess with cell selections, and we assume multirange
  // selections are those.
  if (aSelection.RangeCount() != 1) {
    return NS_OK;
  }

  // Find current sel start and end
  NS_ENSURE_TRUE(aSelection.GetRangeAt(0), NS_ERROR_NULL_POINTER);
  OwningNonNull<nsRange> range = *aSelection.GetRangeAt(0);

  nsCOMPtr<nsINode> selStartNode = range->GetStartContainer();
  int32_t selStartOffset = range->StartOffset();
  nsCOMPtr<nsINode> selEndNode = range->GetEndContainer();
  int32_t selEndOffset = range->EndOffset();

  // Find current selection common block parent
  nsCOMPtr<Element> selCommon =
    HTMLEditor::GetBlock(*range->GetCommonAncestor());
  NS_ENSURE_STATE(selCommon);

  // Set up for loops and cache our root element
  nsCOMPtr<nsINode> firstBRParent;
  nsCOMPtr<nsINode> unused;
  int32_t visOffset = 0, firstBROffset = 0;
  WSType wsType;
  nsCOMPtr<Element> root = mHTMLEditor->GetActiveEditingHost();
  NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);

  // Find previous visible thingy before start of selection
  if (selStartNode != selCommon && selStartNode != root) {
    while (true) {
      WSRunObject wsObj(mHTMLEditor, selStartNode, selStartOffset);
      wsObj.PriorVisibleNode(EditorRawDOMPoint(selStartNode, selStartOffset),
                             address_of(unused), &visOffset, &wsType);
      if (wsType != WSType::thisBlock) {
        break;
      }
      // We want to keep looking up.  But stop if we are crossing table
      // element boundaries, or if we hit the root.
      if (HTMLEditUtils::IsTableElement(wsObj.mStartReasonNode) ||
          selCommon == wsObj.mStartReasonNode ||
          root == wsObj.mStartReasonNode) {
        break;
      }
      selStartNode = wsObj.mStartReasonNode->GetParentNode();
      selStartOffset = selStartNode ?
        selStartNode->ComputeIndexOf(wsObj.mStartReasonNode) : -1;
    }
  }

  // Find next visible thingy after end of selection
  if (selEndNode != selCommon && selEndNode != root) {
    for (;;) {
      WSRunObject wsObj(mHTMLEditor, selEndNode, selEndOffset);
      wsObj.NextVisibleNode(EditorRawDOMPoint(selEndNode, selEndOffset),
                            address_of(unused), &visOffset, &wsType);
      if (wsType == WSType::br) {
        if (mHTMLEditor->IsVisibleBRElement(wsObj.mEndReasonNode)) {
          break;
        }
        if (!firstBRParent) {
          firstBRParent = selEndNode;
          firstBROffset = selEndOffset;
        }
        selEndNode = wsObj.mEndReasonNode->GetParentNode();
        selEndOffset = selEndNode
          ? selEndNode->ComputeIndexOf(wsObj.mEndReasonNode) + 1 : 0;
      } else if (wsType == WSType::thisBlock) {
        // We want to keep looking up.  But stop if we are crossing table
        // element boundaries, or if we hit the root.
        if (HTMLEditUtils::IsTableElement(wsObj.mEndReasonNode) ||
            selCommon == wsObj.mEndReasonNode ||
            root == wsObj.mEndReasonNode) {
          break;
        }
        selEndNode = wsObj.mEndReasonNode->GetParentNode();
        selEndOffset = 1 + selEndNode->ComputeIndexOf(wsObj.mEndReasonNode);
      } else {
        break;
      }
    }
  }
  // Now set the selection to the new range
  aSelection.Collapse(selStartNode, selStartOffset);

  // Expand selection endpoint only if we didn't pass a br, or if we really
  // needed to pass that br (i.e., its block is now totally selected)
  bool doEndExpansion = true;
  if (firstBRParent) {
    // Find block node containing br
    nsCOMPtr<Element> brBlock = HTMLEditor::GetBlock(*firstBRParent);
    bool nodeBefore = false, nodeAfter = false;

    // Create a range that represents expanded selection
    RefPtr<nsRange> range = new nsRange(selStartNode);
    nsresult rv = range->SetStartAndEnd(selStartNode, selStartOffset,
                                        selEndNode, selEndOffset);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    // Check if block is entirely inside range
    if (brBlock) {
      nsRange::CompareNodeToRange(brBlock, range, &nodeBefore, &nodeAfter);
    }

    // If block isn't contained, forgo grabbing the br in expanded selection
    if (nodeBefore || nodeAfter) {
      doEndExpansion = false;
    }