/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "WSRunObject.h"

#include "TextEditUtils.h"

#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "mozilla/EditorDOMPoint.h"
#include "mozilla/EditorUtils.h"
#include "mozilla/HTMLEditor.h"
#include "mozilla/mozalloc.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/SelectionState.h"

#include "nsAString.h"
#include "nsCRT.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsIContent.h"
#include "nsIDOMDocument.h"
#include "nsIDOMNode.h"
#include "nsISupportsImpl.h"
#include "nsRange.h"
#include "nsString.h"
#include "nsTextFragment.h"

namespace mozilla {

using namespace dom;

const char16_t kNBSP = 160;

WSRunObject::WSRunObject(HTMLEditor* aHTMLEditor,
                         nsINode* aNode,
                         int32_t aOffset)
  : mNode(aNode)
  , mOffset(aOffset)
  , mPRE(false)
  , mStartOffset(0)
  , mEndOffset(0)
  , mFirstNBSPOffset(0)
  , mLastNBSPOffset(0)
  , mStartRun(nullptr)
  , mEndRun(nullptr)
  , mHTMLEditor(aHTMLEditor)
{
  GetWSNodes();
  GetRuns();
}

WSRunObject::WSRunObject(HTMLEditor* aHTMLEditor,
                         nsIDOMNode* aNode,
                         int32_t aOffset)
  : mNode(do_QueryInterface(aNode))
  , mOffset(aOffset)
  , mPRE(false)
  , mStartOffset(0)
  , mEndOffset(0)
  , mFirstNBSPOffset(0)
  , mLastNBSPOffset(0)
  , mStartRun(nullptr)
  , mEndRun(nullptr)
  , mHTMLEditor(aHTMLEditor)
{
  GetWSNodes();
  GetRuns();
}

WSRunObject::~WSRunObject()
{
  ClearRuns();
}

nsresult
WSRunObject::ScrubBlockBoundary(HTMLEditor* aHTMLEditor,
                                BlockBoundary aBoundary,
                                nsINode* aBlock,
                                int32_t aOffset)
{
  NS_ENSURE_TRUE(aHTMLEditor && aBlock, NS_ERROR_NULL_POINTER);

  int32_t offset;
  if (aBoundary == kBlockStart) {
    offset = 0;
  } else if (aBoundary == kBlockEnd) {
    offset = aBlock->Length();
  } else {
    // Else we are scrubbing an outer boundary - just before or after a block
    // element.
    NS_ENSURE_STATE(aOffset >= 0);
    offset = aOffset;
  }

  WSRunObject theWSObj(aHTMLEditor, aBlock, offset);
  return theWSObj.Scrub();
}

nsresult
WSRunObject::PrepareToJoinBlocks(HTMLEditor* aHTMLEditor,
                                 Element* aLeftBlock,
                                 Element* aRightBlock)
{
  NS_ENSURE_TRUE(aLeftBlock && aRightBlock && aHTMLEditor,
                 NS_ERROR_NULL_POINTER);

  WSRunObject leftWSObj(aHTMLEditor, aLeftBlock, aLeftBlock->Length());
  WSRunObject rightWSObj(aHTMLEditor, aRightBlock, 0);

  return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
}

nsresult
WSRunObject::PrepareToDeleteRange(HTMLEditor* aHTMLEditor,
                                  nsCOMPtr<nsINode>* aStartNode,
                                  int32_t* aStartOffset,
                                  nsCOMPtr<nsINode>* aEndNode,
                                  int32_t* aEndOffset)
{
  NS_ENSURE_TRUE(aHTMLEditor && aStartNode && *aStartNode && aStartOffset &&
                 aEndNode && *aEndNode && aEndOffset, NS_ERROR_NULL_POINTER);

  AutoTrackDOMPoint trackerStart(aHTMLEditor->mRangeUpdater,
                                 aStartNode, aStartOffset);
  AutoTrackDOMPoint trackerEnd(aHTMLEditor->mRangeUpdater,
                               aEndNode, aEndOffset);

  WSRunObject leftWSObj(aHTMLEditor, *aStartNode, *aStartOffset);
  WSRunObject rightWSObj(aHTMLEditor, *aEndNode, *aEndOffset);

  return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
}

nsresult
WSRunObject::PrepareToDeleteNode(HTMLEditor* aHTMLEditor,
                                 nsIContent* aContent)
{
  NS_ENSURE_TRUE(aContent && aHTMLEditor, NS_ERROR_NULL_POINTER);

  nsCOMPtr<nsINode> parent = aContent->GetParentNode();
  NS_ENSURE_STATE(parent);
  int32_t offset = parent->IndexOf(aContent);

  WSRunObject leftWSObj(aHTMLEditor, parent, offset);
  WSRunObject rightWSObj(aHTMLEditor, parent, offset + 1);

  return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
}

nsresult
WSRunObject::PrepareToSplitAcrossBlocks(HTMLEditor* aHTMLEditor,
                                        nsCOMPtr<nsINode>* aSplitNode,
                                        int32_t* aSplitOffset)
{
  NS_ENSURE_TRUE(aHTMLEditor && aSplitNode && *aSplitNode && aSplitOffset,
                 NS_ERROR_NULL_POINTER);

  AutoTrackDOMPoint tracker(aHTMLEditor->mRangeUpdater,
                            aSplitNode, aSplitOffset);

  WSRunObject wsObj(aHTMLEditor, *aSplitNode, *aSplitOffset);

  return wsObj.PrepareToSplitAcrossBlocksPriv();
}

already_AddRefed<Element>
WSRunObject::InsertBreak(Selection& aSelection,
                         const EditorRawDOMPoint& aPointToInsert,
                         nsIEditor::EDirection aSelect)
{
  if (NS_WARN_IF(!aPointToInsert.IsSet())) {
    return nullptr;
  }

  // MOOSE: for now, we always assume non-PRE formatting.  Fix this later.
  // meanwhile, the pre case is handled in WillInsertText in
  // HTMLEditRules.cpp

  WSFragment* beforeRun = FindNearestRun(aPointToInsert, false);
  WSFragment* afterRun = FindNearestRun(aPointToInsert, true);

  EditorDOMPoint pointToInsert(aPointToInsert);
  {
    // Some scoping for AutoTrackDOMPoint.  This will track our insertion
    // point while we tweak any surrounding whitespace
    AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, &pointToInsert);

    // Handle any changes needed to ws run after inserted br
    if (!afterRun || (afterRun->mType & WSType::trailingWS)) {
      // Don't need to do anything.  Just insert break.  ws won't change.
    } else if (afterRun->mType & WSType::leadingWS) {
      // Delete the leading ws that is after insertion point.  We don't
      // have to (it would still not be significant after br), but it's
      // just more aesthetically pleasing to.
      nsresult rv = DeleteRange(pointToInsert.AsRaw(), afterRun->EndPoint());
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return nullptr;
      }
    } else if (afterRun->mType == WSType::normalWS) {
      // Need to determine if break at front of non-nbsp run.  If so, convert
      // run to nbsp.
      WSPoint thePoint = GetNextCharPoint(pointToInsert.AsRaw());
      if (thePoint.mTextNode && nsCRT::IsAsciiSpace(thePoint.mChar)) {
        WSPoint prevPoint = GetPreviousCharPoint(thePoint);
        if (!prevPoint.mTextNode ||
            (prevPoint.mTextNode && !nsCRT::IsAsciiSpace(prevPoint.mChar))) {
          // We are at start of non-nbsps.  Convert to a single nbsp.
          nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(thePoint);
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return nullptr;
          }
        }
      }
    }

    // Handle any changes needed to ws run before inserted br
    if (!beforeRun || (beforeRun->mType & WSType::leadingWS)) {
      // Don't need to do anything.  Just insert break.  ws won't change.
    } else if (beforeRun->mType & WSType::trailingWS) {
      // Need to delete the trailing ws that is before insertion point, because it
      // would become significant after break inserted.
      nsresult rv = DeleteRange(beforeRun->StartPoint(), pointToInsert.AsRaw());
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return nullptr;
      }
    } else if (beforeRun->mType == WSType::normalWS) {
      // Try to change an nbsp to a space, just to prevent nbsp proliferation
      nsresult rv =
        ReplacePreviousNBSPIfUnncessary(beforeRun, pointToInsert.AsRaw());
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return nullptr;
      }
    }
  }

  RefPtr<Element> newBRElement =
    mHTMLEditor->CreateBRImpl(aSelection, pointToInsert.AsRaw(), aSelect);
  if (NS_WARN_IF(!newBRElement)) {
    return nullptr;
  }
  return newBRElement.forget();
}

nsresult
WSRunObject::InsertText(nsIDocument& aDocument,
                        const nsAString& aStringToInsert,
                        const EditorRawDOMPoint& aPointToInsert,
                        EditorRawDOMPoint* aPointAfterInsertedString)
{
  // MOOSE: for now, we always assume non-PRE formatting.  Fix this later.
  // meanwhile, the pre case is handled in WillInsertText in
  // HTMLEditRules.cpp

  // MOOSE: for now, just getting the ws logic straight.  This implementation
  // is very slow.  Will need to replace edit rules impl with a more efficient
  // text sink here that does the minimal amount of searching/replacing/copying

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


  if (aStringToInsert.IsEmpty()) {
    if (aPointAfterInsertedString) {
      *aPointAfterInsertedString = aPointToInsert;
    }
    return NS_OK;
  }

  WSFragment* beforeRun = FindNearestRun(aPointToInsert, false);
  WSFragment* afterRun = FindNearestRun(aPointToInsert, true);

  EditorDOMPoint pointToInsert(aPointToInsert);
  nsAutoString theString(aStringToInsert);
  {
    // Some scoping for AutoTrackDOMPoint.  This will track our insertion
    // point while we tweak any surrounding whitespace
    AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, &pointToInsert);

    // Handle any changes needed to ws run after inserted text
    if (!afterRun || afterRun->mType & WSType::trailingWS) {
      // Don't need to do anything.  Just insert text.  ws won't change.
    } else if (afterRun->mType & WSType::leadingWS) {
      // Delete the leading ws that is after insertion point, because it
      // would become significant after text inserted.
      nsresult rv = DeleteRange(pointToInsert.AsRaw(), afterRun->EndPoint());
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    } else if (afterRun->mType == WSType::normalWS) {
      // Try to change an nbsp to a space, if possible, just to prevent nbsp
      // proliferation
      nsresult rv = CheckLeadingNBSP(afterRun, pointToInsert.GetContainer(),
                                     pointToInsert.Offset());
      NS_ENSURE_SUCCESS(rv, rv);
    }

    // Handle any changes needed to ws run before inserted text
    if (!beforeRun || beforeRun->mType & WSType::leadingWS) {
      // Don't need to do anything.  Just insert text.  ws won't change.
    } else if (beforeRun->mType & WSType::trailingWS) {
      // Need to delete the trailing ws that is before insertion point, because
      // it would become significant after text inserted.
      nsresult rv = DeleteRange(beforeRun->StartPoint(), pointToInsert.AsRaw());
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    } else if (beforeRun->mType == WSType::normalWS) {
      // Try to change an nbsp to a space, if possible, just to prevent nbsp
      // proliferation
      nsresult rv =
        ReplacePreviousNBSPIfUnncessary(beforeRun, pointToInsert.AsRaw());
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }

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

  // Next up, tweak head and tail of string as needed.  First the head: there
  // are a variety of circumstances that would require us to convert a leading
  // ws char into an nbsp:

  if (nsCRT::IsAsciiSpace(theString[0])) {
    // We have a leading space
    if (beforeRun) {
      if (beforeRun->mType & WSType::leadingWS) {
        theString.SetCharAt(kNBSP, 0);
      } else if (beforeRun->mType & WSType::normalWS) {
        WSPoint wspoint = GetPreviousCharPoint(pointToInsert.AsRaw());
        if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) {
          theString.SetCharAt(kNBSP, 0);
        }
      }
    } else if (mStartReason & WSType::block || mStartReason == WSType::br) {
      theString.SetCharAt(kNBSP, 0);
    }
  }

  // Then the tail
  uint32_t lastCharIndex = theString.Length() - 1;

  if (nsCRT::IsAsciiSpace(theString[lastCharIndex])) {
    // We have a leading space
    if (afterRun) {
      if (afterRun->mType & WSType::trailingWS) {
        theString.SetCharAt(kNBSP, lastCharIndex);
      } else if (afterRun->mType & WSType::normalWS) {
        WSPoint wspoint = GetNextCharPoint(pointToInsert.AsRaw());
        if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) {
          theString.SetCharAt(kNBSP, lastCharIndex);
        }
      }
    } else if (mEndReason & WSType::block) {
      theString.SetCharAt(kNBSP, lastCharIndex);
    }
  }

  // Next, scan string for adjacent ws and convert to nbsp/space combos
  // MOOSE: don't need to convert tabs here since that is done by
  // WillInsertText() before we are called.  Eventually, all that logic will be
  // pushed down into here and made more efficient.
  bool prevWS = false;
  for (uint32_t i = 0; i <= lastCharIndex; i++) {
    if (nsCRT::IsAsciiSpace(theString[i])) {
      if (prevWS) {
        // i - 1 can't be negative because prevWS starts out false
        theString.SetCharAt(kNBSP, i - 1);
      } else {
        prevWS = true;
      }
    } else {
      prevWS = false;
    }
  }

  // Ready, aim, fire!
  nsresult rv =
    mHTMLEditor->InsertTextImpl(aDocument, theString, pointToInsert.AsRaw(),
                                aPointAfterInsertedString);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return NS_OK;
  }
  return NS_OK;
}

nsresult
WSRunObject::DeleteWSBackward()
{
  WSPoint point = GetPreviousCharPoint(Point());
  NS_ENSURE_TRUE(point.mTextNode, NS_OK);  // nothing to delete

  // Easy case, preformatted ws.
  if (mPRE &&  (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP)) {
    nsresult rv =
      DeleteRange(EditorRawDOMPoint(point.mTextNode, point.mOffset),
                  EditorRawDOMPoint(point.mTextNode, point.mOffset + 1));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    return NS_OK;
  }

  // Caller's job to ensure that previous char is really ws.  If it is normal
  // ws, we need to delete the whole run.
  if (nsCRT::IsAsciiSpace(point.mChar)) {
    RefPtr<Text> startNodeText, endNodeText;
    int32_t startOffset, endOffset;
    GetASCIIWhitespacesBounds(eBoth, point.mTextNode, point.mOffset + 1,
                              getter_AddRefs(startNodeText), &startOffset,
                              getter_AddRefs(endNodeText), &endOffset);

    // adjust surrounding ws
    nsCOMPtr<nsINode> startNode = startNodeText.get();
    nsCOMPtr<nsINode> endNode = endNodeText.get();
    nsresult rv =
      WSRunObject::PrepareToDeleteRange(mHTMLEditor,
                                        address_of(startNode), &startOffset,
                                        address_of(endNode), &endOffset);
    NS_ENSURE_SUCCESS(rv, rv);

    // finally, delete that ws
    rv = DeleteRange(EditorRawDOMPoint(startNode, startOffset),
                     EditorRawDOMPoint(endNode, endOffset));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    return NS_OK;
  }

  if (point.mChar == kNBSP) {
    nsCOMPtr<nsINode> node(point.mTextNode);
    // adjust surrounding ws
    int32_t startOffset = point.mOffset;
    int32_t endOffset = point.mOffset + 1;
    nsresult rv =
      WSRunObject::PrepareToDeleteRange(mHTMLEditor,
                                        address_of(node), &startOffset,
                                        address_of(node), &endOffset);
    NS_ENSURE_SUCCESS(rv, rv);

    // finally, delete that ws
    rv = DeleteRange(EditorRawDOMPoint(node, startOffset),
                     EditorRawDOMPoint(node, endOffset));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    return NS_OK;
  }

  return NS_OK;
}

nsresult
WSRunObject::DeleteWSForward()
{
  WSPoint point = GetNextCharPoint(Point());
  NS_ENSURE_TRUE(point.mTextNode, NS_OK); // nothing to delete

  // Easy case, preformatted ws.
  if (mPRE && (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP)) {
    nsresult rv =
      DeleteRange(EditorRawDOMPoint(point.mTextNode, point.mOffset),
                  EditorRawDOMPoint(point.mTextNode, point.mOffset + 1));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    return NS_OK;
  }

  // Caller's job to ensure that next char is really ws.  If it is normal ws,
  // we need to delete the whole run.
  if (nsCRT::IsAsciiSpace(point.mChar)) {
    RefPtr<Text> startNodeText, endNodeText;
    int32_t startOffset, endOffset;
    GetASCIIWhitespacesBounds(eBoth, point.mTextNode, point.mOffset + 1,
                              getter_AddRefs(startNodeText), &startOffset,
                              getter_AddRefs(endNodeText), &endOffset);

    // Adjust surrounding ws
    nsCOMPtr<nsINode> startNode(startNodeText), endNode(endNodeText);
    nsresult rv =
      WSRunObject::PrepareToDeleteRange(mHTMLEditor,
                                        address_of(startNode), &startOffset,
                                        address_of(endNode), &endOffset);
    NS_ENSURE_SUCCESS(rv, rv);

    // Finally, delete that ws
    rv = DeleteRange(EditorRawDOMPoint(startNode, startOffset),
                     EditorRawDOMPoint(endNode, endOffset));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    return NS_OK;
  }

  if (point.mChar == kNBSP) {
    nsCOMPtr<nsINode> node(point.mTextNode);
    // Adjust surrounding ws
    int32_t startOffset = point.mOffset;
    int32_t endOffset = point.mOffset+1;
    nsresult rv =
      WSRunObject::PrepareToDeleteRange(mHTMLEditor,
                                        address_of(node), &startOffset,
                                        address_of(node), &endOffset);
    NS_ENSURE_SUCCESS(rv, rv);

    // Finally, delete that ws
    rv = DeleteRange(EditorRawDOMPoint(node, startOffset),
                     EditorRawDOMPoint(node, endOffset));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    return NS_OK;
  }

  return NS_OK;
}

void
WSRunObject::PriorVisibleNode(nsINode* aNode,
                              int32_t aOffset,
                              nsCOMPtr<nsINode>* outVisNode,
                              int32_t* outVisOffset,
                              WSType* outType)
{
  // Find first visible thing before the point.  Position
  // outVisNode/outVisOffset just _after_ that thing.  If we don't find
  // anything return start of ws.
  MOZ_ASSERT(aNode && outVisNode && outVisOffset && outType);

  WSFragment* run = FindNearestRun(EditorRawDOMPoint(aNode, aOffset), false);

  // Is there a visible run there or earlier?
  for (; run; run = run->mLeft) {
    if (run->mType == WSType::normalWS) {
      WSPoint point = GetPreviousCharPoint(EditorRawDOMPoint(aNode, aOffset));
      // When it's a non-empty text node, return it.
      if (point.mTextNode && point.mTextNode->Length()) {
        *outVisNode = point.mTextNode;
        *outVisOffset = point.mOffset + 1;
        if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP) {
          *outType = WSType::normalWS;
        } else {
          *outType = WSType::text;
        }
        return;
      }
      // If no text node, keep looking.  We should eventually fall out of loop
    }
  }

  // If we get here, then nothing in ws data to find.  Return start reason.
  *outVisNode = mStartReasonNode;
  // This really isn't meaningful if mStartReasonNode != mStartNode
  *outVisOffset = mStartOffset;
  *outType = mStartReason;
}


void
WSRunObject::NextVisibleNode(nsINode* aNode,
                             int32_t aOffset,
                             nsCOMPtr<nsINode>* outVisNode,
                             int32_t* outVisOffset,
                             WSType* outType)
{
  // Find first visible thing after the point.  Position
  // outVisNode/outVisOffset just _before_ that thing.  If we don't find
  // anything return end of ws.
  MOZ_ASSERT(aNode && outVisNode && outVisOffset && outType);

  WSFragment* run = FindNearestRun(EditorRawDOMPoint(aNode, aOffset), true);

  // Is there a visible run there or later?
  for (; run; run = run->mRight) {
    if (run->mType == WSType::normalWS) {
      WSPoint point = GetNextCharPoint(EditorRawDOMPoint(aNode, aOffset));
      // When it's a non-empty text node, return it.
      if (point.mTextNode && point.mTextNode->Length()) {
        *outVisNode = point.mTextNode;
        *outVisOffset = point.mOffset;
        if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP) {
          *outType = WSType::normalWS;
        } else {
          *outType = WSType::text;
        }
        return;
      }
      // If no text node, keep looking.  We should eventually fall out of loop
    }
  }

  // If we get here, then nothing in ws data to find.  Return end reason
  *outVisNode = mEndReasonNode;
  // This really isn't meaningful if mEndReasonNode != mEndNode
  *outVisOffset = mEndOffset;
  *outType = mEndReason;
}

nsresult
WSRunObject::AdjustWhitespace()
{
  // this routine examines a run of ws and tries to get rid of some unneeded nbsp's,
  // replacing them with regualr ascii space if possible.  Keeping things simple
  // for now and just trying to fix up the trailing ws in the run.
  if (!mLastNBSPNode) {
    // nothing to do!
    return NS_OK;
  }
  WSFragment *curRun = mStartRun;
  while (curRun) {
    // look for normal ws run
    if (curRun->mType == WSType::normalWS) {
      nsresult rv = CheckTrailingNBSPOfRun(curRun);
      if (NS_FAILED(rv)) {
        return rv;
      }
    }
    curRun = curRun->mRight;
  }
  return NS_OK;
}


//--------------------------------------------------------------------------------------------
//   protected methods
//--------------------------------------------------------------------------------------------

nsINode*
WSRunObject::GetWSBoundingParent()
{
  NS_ENSURE_TRUE(mNode, nullptr);
  OwningNonNull<nsINode> wsBoundingParent = *mNode;
  while (!IsBlockNode(wsBoundingParent)) {
    nsCOMPtr<nsINode> parent = wsBoundingParent->GetParentNode();
    if (!parent || !mHTMLEditor->IsEditable(parent)) {
      break;
    }
    wsBoundingParent = parent;
  }
  return wsBoundingParent;
}

nsresult
WSRunObject::GetWSNodes()
{
  // collect up an array of nodes that are contiguous with the insertion point
  // and which contain only whitespace.  Stop if you reach non-ws text or a new
  // block boundary.
  EditorDOMPoint start(mNode, mOffset), end(mNode, mOffset);
  nsCOMPtr<nsINode> wsBoundingParent = GetWSBoundingParent();

  // first look backwards to find preceding ws nodes
  if (RefPtr<Text> textNode = mNode->GetAsText()) {
    const nsTextFragment* textFrag = textNode->GetText();

    mNodeArray.InsertElementAt(0, textNode);
    if (mOffset) {
      for (int32_t pos = mOffset - 1; pos >= 0; pos--) {
        // sanity bounds check the char position.  bug 136165
        if (uint32_t(pos) >= textFrag->GetLength()) {
          NS_NOTREACHED("looking beyond end of text fragment");
          continue;
        }
        char16_t theChar = textFrag->CharAt(pos);
        if (!nsCRT::IsAsciiSpace(theChar)) {
          if (theChar != kNBSP) {
            mStartNode = textNode;
            mStartOffset = pos + 1;
            mStartReason = WSType::text;
            mStartReasonNode = textNode;
            break;
          }
          // as we look backwards update our earliest found nbsp
          mFirstNBSPNode = textNode;
          mFirstNBSPOffset = pos;
          // also keep track of latest nbsp so far
          if (!mLastNBSPNode) {
            mLastNBSPNode = textNode;
            mLastNBSPOffset = pos;
          }
        }
        start.Set(textNode, pos);
      }
    }
  }

  while (!mStartNode) {
    // we haven't found the start of ws yet.  Keep looking
    nsCOMPtr<nsIContent> priorNode = GetPreviousWSNode(start, wsBoundingParent);
    if (priorNode) {
      if (IsBlockNode(priorNode)) {
        mStartNode = start.GetContainer();
        mStartOffset = start.Offset();
        mStartReason = WSType::otherBlock;
        mStartReasonNode = priorNode;
      } else if (priorNode->IsNodeOfType(nsINode::eTEXT) &&
                 priorNode->IsEditable()) {
        RefPtr<Text> textNode = priorNode->GetAsText();
        mNodeArray.InsertElementAt(0, textNode);
        const nsTextFragment *textFrag;
        if (!textNode || !(textFrag = textNode->GetText())) {
          return NS_ERROR_NULL_POINTER;
        }
        uint32_t len = textNode->TextLength();

        if (len < 1) {
          // Zero length text node. Set start point to it
          // so we can get past it!
          start.Set(priorNode, 0);
        } else {
          for (int32_t pos = len - 1; pos >= 0; pos--) {
            // sanity bounds check the char position.  bug 136165
            if (uint32_t(pos) >= textFrag->GetLength()) {
              NS_NOTREACHED("looking beyond end of text fragment");
              continue;
            }
            char16_t theChar = textFrag->CharAt(pos);
            if (!nsCRT::IsAsciiSpace(theChar)) {
              if (theChar != kNBSP) {
                mStartNode = textNode;
                mStartOffset = pos + 1;
                mStartReason = WSType::text;
                mStartReasonNode = textNode;
                break;
              }
              // as we look backwards update our earliest found nbsp
              mFirstNBSPNode = textNode;
              mFirstNBSPOffset = pos;
              // also keep track of latest nbsp so far
              if (!mLastNBSPNode) {
                mLastNBSPNode = textNode;
                mLastNBSPOffset = pos;
              }
            }
            start.Set(textNode, pos);
          }
        }
      } else {
        // it's a break or a special node, like <img>, that is not a block and not
        // a break but still serves as a terminator to ws runs.
        mStartNode = start.GetContainer();
        mStartOffset = start.Offset();
        if (TextEditUtils::IsBreak(priorNode)) {
          mStartReason = WSType::br;
        } else {
          mStartReason = WSType::special;
        }
        mStartReasonNode = priorNode;
      }
    } else {
      // no prior node means we exhausted wsBoundingParent
      mStartNode = start.GetContainer();
      mStartOffset = start.Offset();
      mStartReason = WSType::thisBlock;
      mStartReasonNode = wsBoundingParent;
    }
  }

  // then look ahead to find following ws nodes
  if (RefPtr<Text> textNode = mNode->GetAsText()) {
    // don't need to put it on list. it already is from code above
    const nsTextFragment *textFrag = textNode->GetText();

    uint32_t len = textNode->TextLength();
    if (uint16_t(mOffset)<len) {
      for (uint32_t pos = mOffset; pos < len; pos++) {
        // sanity bounds check the char position.  bug 136165
        if (pos >= textFrag->GetLength()) {
          NS_NOTREACHED("looking beyond end of text fragment");
          continue;
        }
        char16_t theChar = textFrag->CharAt(pos);
        if (!nsCRT::IsAsciiSpace(theChar)) {
          if (theChar != kNBSP) {
            mEndNode = textNode;
            mEndOffset = pos;
            mEndReason = WSType::text;
            mEndReasonNode = textNode;
            break;
          }
          // as we look forwards update our latest found nbsp
          mLastNBSPNode = textNode;
          mLastNBSPOffset = pos;
          // also keep track of earliest nbsp so far
          if (!mFirstNBSPNode) {
            mFirstNBSPNode = textNode;
            mFirstNBSPOffset = pos;
          }
        }
        end.Set(textNode, pos + 1);
      }
    }
  }

  while (!mEndNode) {
    // we haven't found the end of ws yet.  Keep looking
    nsCOMPtr<nsIContent> nextNode = GetNextWSNode(end, wsBoundingParent);
    if (nextNode) {
      if (IsBlockNode(nextNode)) {
        // we encountered a new block.  therefore no more ws.
        mEndNode = end.GetContainer();
        mEndOffset = end.Offset();
        mEndReason = WSType::otherBlock;
        mEndReasonNode = nextNode;
      } else if (nextNode->IsNodeOfType(nsINode::eTEXT) &&
                 nextNode->IsEditable()) {
        RefPtr<Text> textNode = nextNode->GetAsText();
        mNodeArray.AppendElement(textNode);
        const nsTextFragment *textFrag;
        if (!textNode || !(textFrag = textNode->GetText())) {
          return NS_ERROR_NULL_POINTER;
        }
        uint32_t len = textNode->TextLength();

        if (len < 1) {
          // Zero length text node. Set end point to it
          // so we can get past it!
          end.Set(textNode, 0);
        } else {
          for (uint32_t pos = 0; pos < len; pos++) {
            // sanity bounds check the char position.  bug 136165
            if (pos >= textFrag->GetLength()) {
              NS_NOTREACHED("looking beyond end of text fragment");
              continue;
            }
            char16_t theChar = textFrag->CharAt(pos);
            if (!nsCRT::IsAsciiSpace(theChar)) {
              if (theChar != kNBSP) {
                mEndNode = textNode;
                mEndOffset = pos;
                mEndReason = WSType::text;
                mEndReasonNode = textNode;
                break;
              }
              // as we look forwards update our latest found nbsp
              mLastNBSPNode = textNode;
              mLastNBSPOffset = pos;
              // also keep track of earliest nbsp so far
              if (!mFirstNBSPNode) {
                mFirstNBSPNode = textNode;
                mFirstNBSPOffset = pos;
              }
            }
            end.Set(textNode, pos + 1);
          }
        }
      } else {
        // we encountered a break or a special node, like <img>,
        // that is not a block and not a break but still
        // serves as a terminator to ws runs.
        mEndNode = end.GetContainer();
        mEndOffset = end.Offset();
        if (TextEditUtils::IsBreak(nextNode)) {
          mEndReason = WSType::br;
        } else {
          mEndReason = WSType::special;
        }
        mEndReasonNode = nextNode;
      }
    } else {
      // no next node means we exhausted wsBoundingParent
      mEndNode = end.GetContainer();
      mEndOffset = end.Offset();
      mEndReason = WSType::thisBlock;
      mEndReasonNode = wsBoundingParent;
    }
  }

  return NS_OK;
}

void
WSRunObject::GetRuns()
{
  ClearRuns();

  // handle some easy cases first
  mHTMLEditor->IsPreformatted(GetAsDOMNode(mNode), &mPRE);
  // if it's preformatedd, or if we are surrounded by text or special, it's all one
  // big normal ws run
  if (mPRE ||
      ((mStartReason == WSType::text || mStartReason == WSType::special) &&
       (mEndReason == WSType::text || mEndReason == WSType::special ||
        mEndReason == WSType::br))) {
    MakeSingleWSRun(WSType::normalWS);
    return;
  }

  // if we are before or after a block (or after a break), and there are no nbsp's,
  // then it's all non-rendering ws.
  if (!mFirstNBSPNode && !mLastNBSPNode &&
      ((mStartReason & WSType::block) || mStartReason == WSType::br ||
       (mEndReason & WSType::block))) {
    WSType wstype;
    if ((mStartReason & WSType::block) || mStartReason == WSType::br) {
      wstype = WSType::leadingWS;
    }
    if (mEndReason & WSType::block) {
      wstype |= WSType::trailingWS;
    }
    MakeSingleWSRun(wstype);
    return;
  }

  // otherwise a little trickier.  shucks.
  mStartRun = new WSFragment();
  mStartRun->mStartNode = mStartNode;
  mStartRun->mStartOffset = mStartOffset;

  if (mStartReason & WSType::block || mStartReason == WSType::br) {
    // set up mStartRun
    mStartRun->mType = WSType::leadingWS;
    mStartRun->mEndNode = mFirstNBSPNode;
    mStartRun->mEndOffset = mFirstNBSPOffset;
    mStartRun->mLeftType = mStartReason;
    mStartRun->mRightType = WSType::normalWS;

    // set up next run
    WSFragment *normalRun = new WSFragment();
    mStartRun->mRight = normalRun;
    normalRun->mType = WSType::normalWS;
    normalRun->mStartNode = mFirstNBSPNode;
    normalRun->mStartOffset = mFirstNBSPOffset;
    normalRun->mLeftType = WSType::leadingWS;
    normalRun->mLeft = mStartRun;
    if (mEndReason != WSType::block) {
      // then no trailing ws.  this normal run ends the overall ws run.
      normalRun->mRightType = mEndReason;
      normalRun->mEndNode   = mEndNode;
      normalRun->mEndOffset = mEndOffset;
      mEndRun = normalRun;
    } else {
      // we might have trailing ws.
      // it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1}
      // will point to it, even though in general start/end points not
      // guaranteed to be in text nodes.
      if (mLastNBSPNode == mEndNode && mLastNBSPOffset == mEndOffset - 1) {
        // normal ws runs right up to adjacent block (nbsp next to block)
        normalRun->mRightType = mEndReason;
        normalRun->mEndNode   = mEndNode;
        normalRun->mEndOffset = mEndOffset;
        mEndRun = normalRun;
      } else {
        normalRun->mEndNode = mLastNBSPNode;
        normalRun->mEndOffset = mLastNBSPOffset+1;
        normalRun->mRightType = WSType::trailingWS;

        // set up next run
        WSFragment *lastRun = new WSFragment();
        lastRun->mType = WSType::trailingWS;
        lastRun->mStartNode = mLastNBSPNode;
        lastRun->mStartOffset = mLastNBSPOffset+1;
        lastRun->mEndNode = mEndNode;
        lastRun->mEndOffset = mEndOffset;
        lastRun->mLeftType = WSType::normalWS;
        lastRun->mLeft = normalRun;
        lastRun->mRightType = mEndReason;
        mEndRun = lastRun;
        normalRun->mRight = lastRun;
      }
    }
  } else {
    // mStartReason is not WSType::block or WSType::br; set up mStartRun
    mStartRun->mType = WSType::normalWS;
    mStartRun->mEndNode = mLastNBSPNode;
    mStartRun->mEndOffset = mLastNBSPOffset+1;
    mStartRun->mLeftType = mStartReason;

    // we might have trailing ws.
    // it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1}
    // will point to it, even though in general start/end points not
    // guaranteed to be in text nodes.
    if (mLastNBSPNode == mEndNode && mLastNBSPOffset == (mEndOffset - 1)) {
      mStartRun->mRightType = mEndReason;
      mStartRun->mEndNode   = mEndNode;
      mStartRun->mEndOffset = mEndOffset;
      mEndRun = mStartRun;
    } else {
      // set up next run
      WSFragment *lastRun = new WSFragment();
      lastRun->mType = WSType::trailingWS;
      lastRun->mStartNode = mLastNBSPNode;
      lastRun->mStartOffset = mLastNBSPOffset+1;
      lastRun->mLeftType = WSType::normalWS;
      lastRun->mLeft = mStartRun;
      lastRun->mRightType = mEndReason;
      mEndRun = lastRun;
      mStartRun->mRight = lastRun;
      mStartRun->mRightType = WSType::trailingWS;
    }
  }
}

void
WSRunObject::ClearRuns()
{
  WSFragment *tmp, *run;
  run = mStartRun;
  while (run) {
    tmp = run->mRight;
    delete run;
    run = tmp;
  }
  mStartRun = 0;
  mEndRun = 0;
}

void
WSRunObject::MakeSingleWSRun(WSType aType)
{
  mStartRun = new WSFragment();

  mStartRun->mStartNode   = mStartNode;
  mStartRun->mStartOffset = mStartOffset;
  mStartRun->mType        = aType;
  mStartRun->mEndNode     = mEndNode;
  mStartRun->mEndOffset   = mEndOffset;
  mStartRun->mLeftType    = mStartReason;
  mStartRun->mRightType   = mEndReason;

  mEndRun  = mStartRun;
}

nsIContent*
WSRunObject::GetPreviousWSNodeInner(nsINode* aStartNode,
                                    nsINode* aBlockParent)
{
  // Can't really recycle various getnext/prior routines because we have
  // special needs here.  Need to step into inline containers but not block
  // containers.
  MOZ_ASSERT(aStartNode && aBlockParent);

  nsCOMPtr<nsIContent> priorNode = aStartNode->GetPreviousSibling();
  OwningNonNull<nsINode> curNode = *aStartNode;
  while (!priorNode) {
    // We have exhausted nodes in parent of aStartNode.
    nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
    NS_ENSURE_TRUE(curParent, nullptr);
    if (curParent == aBlockParent) {
      // We have exhausted nodes in the block parent.  The convention here is
      // to return null.
      return nullptr;
    }
    // We have a parent: look for previous sibling
    priorNode = curParent->GetPreviousSibling();
    curNode = curParent;
  }
  // We have a prior node.  If it's a block, return it.
  if (IsBlockNode(priorNode)) {
    return priorNode;
  }
  if (mHTMLEditor->IsContainer(priorNode)) {
    // Else if it's a container, get deep rightmost child
    nsCOMPtr<nsIContent> child = mHTMLEditor->GetRightmostChild(priorNode);
    if (child) {
      return child;
    }
  }
  // Else return the node itself
  return priorNode;
}

nsIContent*
WSRunObject::GetPreviousWSNode(const EditorDOMPoint& aPoint,
                               nsINode* aBlockParent)
{
  // Can't really recycle various getnext/prior routines because we
  // have special needs here.  Need to step into inline containers but
  // not block containers.
  MOZ_ASSERT(aPoint.IsSet() && aBlockParent);

  if (aPoint.IsInTextNode()) {
    return GetPreviousWSNodeInner(aPoint.GetContainer(), aBlockParent);
  }
  if (!mHTMLEditor->IsContainer(aPoint.GetContainer())) {
    return GetPreviousWSNodeInner(aPoint.GetContainer(), aBlockParent);
  }

  if (!aPoint.Offset()) {
    if (aPoint.GetContainer() == aBlockParent) {
      // We are at start of the block.
      return nullptr;
    }

    // We are at start of non-block container
    return GetPreviousWSNodeInner(aPoint.GetContainer(), aBlockParent);
  }

  if (NS_WARN_IF(!aPoint.GetContainerAsContent())) {
    return nullptr;
  }

  nsCOMPtr<nsIContent> priorNode = aPoint.GetPreviousSiblingOfChild();
  if (NS_WARN_IF(!priorNode)) {
    return nullptr;
  }

  // We have a prior node.  If it's a block, return it.
  if (IsBlockNode(priorNode)) {
    return priorNode;
  }
  if (mHTMLEditor->IsContainer(priorNode)) {
    // Else if it's a container, get deep rightmost child
    nsCOMPtr<nsIContent> child = mHTMLEditor->GetRightmostChild(priorNode);
    if (child) {
      return child;
    }
  }
  // Else return the node itself
  return priorNode;
}

nsIContent*
WSRunObject::GetNextWSNodeInner(nsINode* aStartNode,
                                nsINode* aBlockParent)
{
  // Can't really recycle various getnext/prior routines because we have
  // special needs here.  Need to step into inline containers but not block
  // containers.
  MOZ_ASSERT(aStartNode && aBlockParent);

  nsCOMPtr<nsIContent> nextNode = aStartNode->GetNextSibling();
  nsCOMPtr<nsINode> curNode = aStartNode;
  while (!nextNode) {
    // We have exhausted nodes in parent of aStartNode.
    nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
    NS_ENSURE_TRUE(curParent, nullptr);
    if (curParent == aBlockParent) {
      // We have exhausted nodes in the block parent.  The convention here is
      // to return null.
      return nullptr;
    }
    // We have a parent: look for next sibling
    nextNode = curParent->GetNextSibling();
    curNode = curParent;
  }
  // We have a next node.  If it's a block, return it.
  if (IsBlockNode(nextNode)) {
    return nextNode;
  }
  if (mHTMLEditor->IsContainer(nextNode)) {
    // Else if it's a container, get deep leftmost child
    nsCOMPtr<nsIContent> child = mHTMLEditor->GetLeftmostChild(nextNode);
    if (child) {
      return child;
    }
  }
  // Else return the node itself
  return nextNode;
}

nsIContent*
WSRunObject::GetNextWSNode(const EditorDOMPoint& aPoint,
                           nsINode* aBlockParent)
{
  // Can't really recycle various getnext/prior routines because we have
  // special needs here.  Need to step into inline containers but not block
  // containers.
  MOZ_ASSERT(aPoint.IsSet() && aBlockParent);

  if (aPoint.IsInTextNode()) {
    return GetNextWSNodeInner(aPoint.GetContainer(), aBlockParent);
  }
  if (!mHTMLEditor->IsContainer(aPoint.GetContainer())) {
    return GetNextWSNodeInner(aPoint.GetContainer(), aBlockParent);
  }

  if (NS_WARN_IF(!aPoint.GetContainerAsContent())) {
    return nullptr;
  }

  nsCOMPtr<nsIContent> nextNode = aPoint.GetChild();
  if (!nextNode) {
    if (aPoint.GetContainer() == aBlockParent) {
      // We are at end of the block.
      return nullptr;
    }

    // We are at end of non-block container
    return GetNextWSNodeInner(aPoint.GetContainer(), aBlockParent);
  }

  // We have a next node.  If it's a block, return it.
  if (IsBlockNode(nextNode)) {
    return nextNode;
  }
  if (mHTMLEditor->IsContainer(nextNode)) {
    // else if it's a container, get deep leftmost child
    nsCOMPtr<nsIContent> child = mHTMLEditor->GetLeftmostChild(nextNode);
    if (child) {
      return child;
    }
  }
  // Else return the node itself
  return nextNode;
}

nsresult
WSRunObject::PrepareToDeleteRangePriv(WSRunObject* aEndObject)
{
  // this routine adjust whitespace before *this* and after aEndObject
  // in preperation for the two areas to become adjacent after the
  // intervening content is deleted.  It's overly agressive right
  // now.  There might be a block boundary remaining between them after
  // the deletion, in which case these adjstments are unneeded (though
  // I don't think they can ever be harmful?)

  NS_ENSURE_TRUE(aEndObject, NS_ERROR_NULL_POINTER);

  // get the runs before and after selection
  WSFragment* beforeRun = FindNearestRun(Point(), false);
  WSFragment* afterRun = aEndObject->FindNearestRun(aEndObject->Point(), true);

  // trim after run of any leading ws
  if (afterRun && (afterRun->mType & WSType::leadingWS)) {
    nsresult rv =
      aEndObject->DeleteRange(aEndObject->Point(), afterRun->EndPoint());
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }
  // adjust normal ws in afterRun if needed
  if (afterRun && afterRun->mType == WSType::normalWS && !aEndObject->mPRE) {
    if ((beforeRun && (beforeRun->mType & WSType::leadingWS)) ||
        (!beforeRun && ((mStartReason & WSType::block) ||
                        mStartReason == WSType::br))) {
      // make sure leading char of following ws is an nbsp, so that it will show up
      WSPoint point = aEndObject->GetNextCharPoint(aEndObject->Point());
      if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
        nsresult rv =
          aEndObject->InsertNBSPAndRemoveFollowingASCIIWhitespaces(point);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }
    }
  }
  // trim before run of any trailing ws
  if (beforeRun && (beforeRun->mType & WSType::trailingWS)) {
    nsresult rv = DeleteRange(beforeRun->StartPoint(), Point());
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  } else if (beforeRun && beforeRun->mType == WSType::normalWS && !mPRE) {
    if ((afterRun && (afterRun->mType & WSType::trailingWS)) ||
        (afterRun && afterRun->mType == WSType::normalWS) ||
        (!afterRun && (aEndObject->mEndReason & WSType::block))) {
      // make sure trailing char of starting ws is an nbsp, so that it will show up
      WSPoint point = GetPreviousCharPoint(Point());
      if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
        RefPtr<Text> wsStartNode, wsEndNode;
        int32_t wsStartOffset, wsEndOffset;
        GetASCIIWhitespacesBounds(eBoth, mNode, mOffset,
                                  getter_AddRefs(wsStartNode), &wsStartOffset,
                                  getter_AddRefs(wsEndNode), &wsEndOffset);
        point.mTextNode = wsStartNode;
        point.mOffset = wsStartOffset;
        nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(point);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }
    }
  }
  return NS_OK;
}

nsresult
WSRunObject::PrepareToSplitAcrossBlocksPriv()
{
  // used to prepare ws to be split across two blocks.  The main issue
  // here is make sure normalWS doesn't end up becoming non-significant
  // leading or trailing ws after the split.

  // get the runs before and after selection
  WSFragment* beforeRun = FindNearestRun(Point(), false);
  WSFragment* afterRun = FindNearestRun(Point(), true);

  // adjust normal ws in afterRun if needed
  if (afterRun && afterRun->mType == WSType::normalWS) {
    // make sure leading char of following ws is an nbsp, so that it will show up
    WSPoint point = GetNextCharPoint(Point());
    if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
      nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(point);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }
  }

  // adjust normal ws in beforeRun if needed
  if (beforeRun && beforeRun->mType == WSType::normalWS) {
    // make sure trailing char of starting ws is an nbsp, so that it will show up
    WSPoint point = GetPreviousCharPoint(Point());
    if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
      RefPtr<Text> wsStartNode, wsEndNode;
      int32_t wsStartOffset, wsEndOffset;
      GetASCIIWhitespacesBounds(eBoth, mNode, mOffset,
                                getter_AddRefs(wsStartNode), &wsStartOffset,
                                getter_AddRefs(wsEndNode), &wsEndOffset);
      point.mTextNode = wsStartNode;
      point.mOffset = wsStartOffset;
      nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(point);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }
  }
  return NS_OK;
}

nsresult
WSRunObject::DeleteRange(const EditorRawDOMPoint& aStartPoint,
                         const EditorRawDOMPoint& aEndPoint)
{
  if (NS_WARN_IF(!aStartPoint.IsSet()) ||
      NS_WARN_IF(!aEndPoint.IsSet())) {
    return NS_ERROR_INVALID_ARG;
  }
  MOZ_ASSERT(aStartPoint.IsSetAndValid());
  MOZ_ASSERT(aEndPoint.IsSetAndValid());

  // MOOSE: this routine needs to be modified to preserve the integrity of the
  // wsFragment info.

  if (aStartPoint == aEndPoint) {
    // Nothing to delete
    return NS_OK;
  }

  if (aStartPoint.GetContainer() == aEndPoint.GetContainer() &&
      aStartPoint.IsInTextNode()) {
    return mHTMLEditor->DeleteText(*aStartPoint.GetContainerAsText(),
                                   aStartPoint.Offset(),
                                   aEndPoint.Offset() - aStartPoint.Offset());
  }

  RefPtr<nsRange> range;
  int32_t count = mNodeArray.Length();
  int32_t idx = mNodeArray.IndexOf(aStartPoint.GetContainer());
  if (idx == -1) {
    // If our starting point wasn't one of our ws text nodes, then just go
    // through them from the beginning.
    idx = 0;
  }
  for (; idx < count; idx++) {
    RefPtr<Text> node = mNodeArray[idx];
    if (!node) {
      // We ran out of ws nodes; must have been deleting to end
      return NS_OK;
    }
    if (node == aStartPoint.GetContainer()) {
      if (!aStartPoint.IsEndOfContainer()) {
        nsresult rv =
          mHTMLEditor->DeleteText(*node, aStartPoint.Offset(),
                                  aStartPoint.GetContainer()->Length() -
                                    aStartPoint.Offset());
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }
    } else if (node == aEndPoint.GetContainer()) {
      if (!aEndPoint.IsStartOfContainer()) {
        nsresult rv = mHTMLEditor->DeleteText(*node, 0, aEndPoint.Offset());
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }
      break;
    } else {
      if (!range) {
        range = new nsRange(aStartPoint.GetContainer());
        nsresult rv = range->SetStartAndEnd(aStartPoint, aEndPoint);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }
      bool nodeBefore, nodeAfter;
      nsresult rv =
        nsRange::CompareNodeToRange(node, range, &nodeBefore, &nodeAfter);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      if (nodeAfter) {
        break;
      }
      if (!nodeBefore) {
        rv = mHTMLEditor->DeleteNode(node);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
        mNodeArray.RemoveElement(node);
        --count;
        --idx;
      }
    }
  }
  return NS_OK;
}

WSRunObject::WSPoint
WSRunObject::GetNextCharPoint(const EditorRawDOMPoint& aPoint)
{
  MOZ_ASSERT(aPoint.IsSetAndValid());

  int32_t idx = mNodeArray.IndexOf(aPoint.GetContainer());
  if (idx == -1) {
    // Use range comparisons to get next text node which is in mNodeArray.
    return GetNextCharPointInternal(aPoint);
  }
  // Use WSPoint version of GetNextCharPoint()
  return GetNextCharPoint(WSPoint(mNodeArray[idx], aPoint.Offset(), 0));
}

WSRunObject::WSPoint
WSRunObject::GetPreviousCharPoint(const EditorRawDOMPoint& aPoint)
{
  MOZ_ASSERT(aPoint.IsSetAndValid());

  int32_t idx = mNodeArray.IndexOf(aPoint.GetContainer());
  if (idx == -1) {
    // Use range comparisons to get previous text node which is in mNodeArray.
    return GetPreviousCharPointInternal(aPoint);
  }
  // Use WSPoint version of GetPreviousCharPoint()
  return GetPreviousCharPoint(WSPoint(mNodeArray[idx], aPoint.Offset(), 0));
}

WSRunObject::WSPoint
WSRunObject::GetNextCharPoint(const WSPoint &aPoint)
{
  MOZ_ASSERT(aPoint.mTextNode);

  WSPoint outPoint;
  outPoint.mTextNode = nullptr;
  outPoint.mOffset = 0;
  outPoint.mChar = 0;

  int32_t idx = mNodeArray.IndexOf(aPoint.mTextNode);
  if (idx == -1) {
    // Can't find point, but it's not an error
    return outPoint;
  }

  if (static_cast<uint16_t>(aPoint.mOffset) < aPoint.mTextNode->TextLength()) {
    outPoint = aPoint;
    outPoint.mChar = GetCharAt(aPoint.mTextNode, aPoint.mOffset);
    return outPoint;
  }

  int32_t numNodes = mNodeArray.Length();
  if (idx + 1 < numNodes) {
    outPoint.mTextNode = mNodeArray[idx + 1];
    MOZ_ASSERT(outPoint.mTextNode);
    outPoint.mOffset = 0;
    outPoint.mChar = GetCharAt(outPoint.mTextNode, 0);
  }

  return outPoint;
}

WSRunObject::WSPoint
WSRunObject::GetPreviousCharPoint(const WSPoint &aPoint)
{
  MOZ_ASSERT(aPoint.mTextNode);

  WSPoint outPoint;
  outPoint.mTextNode = nullptr;
  outPoint.mOffset = 0;
  outPoint.mChar = 0;

  int32_t idx = mNodeArray.IndexOf(aPoint.mTextNode);
  if (idx == -1) {
    // Can't find point, but it's not an error
    return outPoint;
  }

  if (aPoint.mOffset) {
    outPoint = aPoint;
    outPoint.mOffset--;
    outPoint.mChar = GetCharAt(aPoint.mTextNode, aPoint.mOffset - 1);
    return outPoint;
  }

  if (idx) {
    outPoint.mTextNode = mNodeArray[idx - 1];

    uint32_t len = outPoint.mTextNode->TextLength();
    if (len) {
      outPoint.mOffset = len - 1;
      outPoint.mChar = GetCharAt(outPoint.mTextNode, len - 1);
    }
  }
  return outPoint;
}

nsresult
WSRunObject::InsertNBSPAndRemoveFollowingASCIIWhitespaces(WSPoint aPoint)
{
  // MOOSE: this routine needs to be modified to preserve the integrity of the
  // wsFragment info.
  if (NS_WARN_IF(!aPoint.mTextNode)) {
    return NS_ERROR_NULL_POINTER;
  }

 // First, insert an NBSP.
  AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
  nsresult rv =
    mHTMLEditor->InsertTextIntoTextNodeImpl(nsDependentSubstring(&kNBSP, 1),
                                            *aPoint.mTextNode, aPoint.mOffset,
                                            true);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Now, the text node may have been modified by mutation observer.
  // So, the NBSP may have gone.
  if (aPoint.mTextNode->TextDataLength() <= aPoint.mOffset ||
      aPoint.mTextNode->GetText()->CharAt(aPoint.mOffset) != kNBSP) {
    // This is just preparation of an edit action.  Let's return NS_OK.
    // XXX Perhaps, we should return another success code which indicates
    //     mutation observer touched the DOM tree.  However, that should
    //     be returned from each transaction's DoTransaction.
    return NS_OK;
  }

  // Next, find range of whitespaces it will be replaced.
  RefPtr<Text> startNode, endNode;
  int32_t startOffset = 0, endOffset = 0;

  GetASCIIWhitespacesBounds(eAfter, aPoint.mTextNode, aPoint.mOffset + 1,
                            getter_AddRefs(startNode), &startOffset,
                            getter_AddRefs(endNode), &endOffset);

  // Finally, delete that replaced ws, if any
  if (startNode) {
    rv = DeleteRange(EditorRawDOMPoint(startNode, startOffset),
                     EditorRawDOMPoint(endNode, endOffset));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }

  return NS_OK;
}

void
WSRunObject::GetASCIIWhitespacesBounds(int16_t aDir,
                                       nsINode* aNode,
                                       int32_t aOffset,
                                       Text** outStartNode,
                                       int32_t* outStartOffset,
                                       Text** outEndNode,
                                       int32_t* outEndOffset)
{
  MOZ_ASSERT(aNode && outStartNode && outStartOffset && outEndNode &&
             outEndOffset);

  RefPtr<Text> startNode, endNode;
  int32_t startOffset = 0, endOffset = 0;

  if (aDir & eAfter) {
    WSPoint point = GetNextCharPoint(EditorRawDOMPoint(aNode, aOffset));
    if (point.mTextNode) {
      // We found a text node, at least
      startNode = endNode = point.mTextNode;
      startOffset = endOffset = point.mOffset;

      // Scan ahead to end of ASCII ws
      for (; nsCRT::IsAsciiSpace(point.mChar) && point.mTextNode;
           point = GetNextCharPoint(point)) {
        endNode = point.mTextNode;
        // endOffset is _after_ ws
        point.mOffset++;
        endOffset = point.mOffset;
      }
    }
  }

  if (aDir & eBefore) {
    WSPoint point = GetPreviousCharPoint(EditorRawDOMPoint(aNode, aOffset));
    if (point.mTextNode) {
      // We found a text node, at least
      startNode = point.mTextNode;
      startOffset = point.mOffset + 1;
      if (!endNode) {
        endNode = startNode;
        endOffset = startOffset;
      }

      // Scan back to start of ASCII ws
      for (; nsCRT::IsAsciiSpace(point.mChar) && point.mTextNode;
           point = GetPreviousCharPoint(point)) {
        startNode = point.mTextNode;
        startOffset = point.mOffset;
      }
    }
  }

  startNode.forget(outStartNode);
  *outStartOffset = startOffset;
  endNode.forget(outEndNode);
  *outEndOffset = endOffset;
}

WSRunObject::WSFragment*
WSRunObject::FindNearestRun(const EditorRawDOMPoint& aPoint,
                            bool aForward)
{
  MOZ_ASSERT(aPoint.IsSetAndValid());

  for (WSFragment* run = mStartRun; run; run = run->mRight) {
    int32_t comp = run->mStartNode ?
      nsContentUtils::ComparePoints(aPoint, run->StartPoint()) : -1;
    if (comp <= 0) {
      // aPoint equals or before start of the run.  Return the run if we're
      // scanning forward, otherwise, nullptr.
      return aForward ? run : nullptr;
    }

    comp = run->mEndNode ?
      nsContentUtils::ComparePoints(aPoint, run->EndPoint()) : -1;
    if (comp < 0) {
      // If aPoint is in the run, return the run.
      return run;
    }

    if (!comp) {
      // If aPoint is at end of the run, return next run if we're scanning
      // forward, otherwise, return the run.
      return aForward ? run->mRight : run;
    }

    if (!run->mRight) {
      // If the run is the last run and aPoint is after end of the last run,
      // return nullptr if we're scanning forward, otherwise, return this
      // last run.
      return aForward ? nullptr : run;
    }
  }

  return nullptr;
}

char16_t
WSRunObject::GetCharAt(Text* aTextNode,
                       int32_t aOffset)
{
  // return 0 if we can't get a char, for whatever reason
  NS_ENSURE_TRUE(aTextNode, 0);

  int32_t len = int32_t(aTextNode->TextLength());
  if (aOffset < 0 || aOffset >= len) {
    return 0;
  }
  return aTextNode->GetText()->CharAt(aOffset);
}

WSRunObject::WSPoint
WSRunObject::GetNextCharPointInternal(const EditorRawDOMPoint& aPoint)
{
  // Note: only to be called if aPoint.GetContainer() is not a ws node.

  // Binary search on wsnodes
  uint32_t numNodes = mNodeArray.Length();

  if (!numNodes) {
    // Do nothing if there are no nodes to search
    WSPoint outPoint;
    return outPoint;
  }

  // Begin binary search.  We do this because we need to minimize calls to
  // ComparePoints(), which is expensive.
  uint32_t firstNum = 0, curNum = numNodes / 2, lastNum = numNodes;
  while (curNum != lastNum) {
    RefPtr<Text> curNode = mNodeArray[curNum];
    int16_t cmp =
      nsContentUtils::ComparePoints(aPoint, EditorRawDOMPoint(curNode, 0));
    if (cmp < 0) {
      lastNum = curNum;
    } else {
      firstNum = curNum + 1;
    }
    curNum = (lastNum - firstNum) / 2 + firstNum;
    MOZ_ASSERT(firstNum <= curNum && curNum <= lastNum, "Bad binary search");
  }

  // When the binary search is complete, we always know that the current node
  // is the same as the end node, which is always past our range.  Therefore,
  // we've found the node immediately after the point of interest.
  if (curNum == mNodeArray.Length()) {
    // hey asked for past our range (it's after the last node).
    // GetNextCharPoint() will do the work for us when we pass it the last
    // index of the last node.
    RefPtr<Text> textNode(mNodeArray[curNum - 1]);
    WSPoint point(textNode, textNode->TextLength(), 0);
    return GetNextCharPoint(point);
  }

  // The char after the point is the first character of our range.
  RefPtr<Text> textNode(mNodeArray[curNum]);
  WSPoint point(textNode, 0, 0);
  return GetNextCharPoint(point);
}

WSRunObject::WSPoint
WSRunObject::GetPreviousCharPointInternal(const EditorRawDOMPoint& aPoint)
{
  // Note: only to be called if aNode is not a ws node.

  // Binary search on wsnodes
  uint32_t numNodes = mNodeArray.Length();

  if (!numNodes) {
    // Do nothing if there are no nodes to search
    WSPoint outPoint;
    return outPoint;
  }

  uint32_t firstNum = 0, curNum = numNodes/2, lastNum = numNodes;
  int16_t cmp = 0;
  RefPtr<Text>  curNode;

  // Begin binary search.  We do this because we need to minimize calls to
  // ComparePoints(), which is expensive.
  while (curNum != lastNum) {
    curNode = mNodeArray[curNum];
    cmp = nsContentUtils::ComparePoints(aPoint, EditorRawDOMPoint(curNode, 0));
    if (cmp < 0) {
      lastNum = curNum;
    } else {
      firstNum = curNum + 1;
    }
    curNum = (lastNum - firstNum)/2 + firstNum;
    MOZ_ASSERT(firstNum <= curNum && curNum <= lastNum, "Bad binary search");
  }

  // When the binary search is complete, we always know that the current node
  // is the same as the end node, which is always past our range. Therefore,
  // we've found the node immediately after the point of interest.
  if (curNum == mNodeArray.Length()) {
    // Get the point before the end of the last node, we can pass the length of
    // the node into GetPreviousCharPoint(), and it will return the last
    // character.
    RefPtr<Text> textNode(mNodeArray[curNum - 1]);
    WSPoint point(textNode, textNode->TextLength(), 0);
    return GetPreviousCharPoint(point);
  }

  // We can just ask the current node for the point immediately before it,
  // it will handle moving to the previous node (if any) and returning the
  // appropriate character
  RefPtr<Text> textNode(mNodeArray[curNum]);
  WSPoint point(textNode, 0, 0);
  return GetPreviousCharPoint(point);
}

nsresult
WSRunObject::CheckTrailingNBSPOfRun(WSFragment *aRun)
{
  // Try to change an nbsp to a space, if possible, just to prevent nbsp
  // proliferation.  Examine what is before and after the trailing nbsp, if
  // any.
  NS_ENSURE_TRUE(aRun, NS_ERROR_NULL_POINTER);
  bool leftCheck = false;
  bool spaceNBSP = false;
  bool rightCheck = false;

  // confirm run is normalWS
  if (aRun->mType != WSType::normalWS) {
    return NS_ERROR_FAILURE;
  }

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

  // first check for trailing nbsp
  WSPoint thePoint = GetPreviousCharPoint(aRun->EndPoint());
  if (thePoint.mTextNode && thePoint.mChar == kNBSP) {
    // now check that what is to the left of it is compatible with replacing nbsp with space
    WSPoint prevPoint = GetPreviousCharPoint(thePoint);
    if (prevPoint.mTextNode) {
      if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) {
        leftCheck = true;
      } else {
        spaceNBSP = true;
      }
    } else if (aRun->mLeftType == WSType::text ||
               aRun->mLeftType == WSType::special) {
      leftCheck = true;
    }
    if (leftCheck || spaceNBSP) {
      // now check that what is to the right of it is compatible with replacing
      // nbsp with space
      if (aRun->mRightType == WSType::text ||
          aRun->mRightType == WSType::special ||
          aRun->mRightType == WSType::br) {
        rightCheck = true;
      }
      if ((aRun->mRightType & WSType::block) &&
          IsBlockNode(GetWSBoundingParent())) {
        // We are at a block boundary.  Insert a <br>.  Why?  Well, first note
        // that the br will have no visible effect since it is up against a
        // block boundary.  |foo<br><p>bar| renders like |foo<p>bar| and
        // similarly |<p>foo<br></p>bar| renders like |<p>foo</p>bar|.  What
        // this <br> addition gets us is the ability to convert a trailing nbsp
        // to a space.  Consider: |<body>foo. '</body>|, where ' represents
        // selection.  User types space attempting to put 2 spaces after the
        // end of their sentence.  We used to do this as: |<body>foo.
        // &nbsp</body>|  This caused problems with soft wrapping: the nbsp
        // would wrap to the next line, which looked attrocious.  If you try to
        // do: |<body>foo.&nbsp </body>| instead, the trailing space is
        // invisible because it is against a block boundary.  If you do:
        // |<body>foo.&nbsp&nbsp</body>| then you get an even uglier soft
        // wrapping problem, where foo is on one line until you type the final
        // space, and then "foo  " jumps down to the next line.  Ugh.  The best
        // way I can find out of this is to throw in a harmless <br> here,
        // which allows us to do: |<body>foo.&nbsp <br></body>|, which doesn't
        // cause foo to jump lines, doesn't cause spaces to show up at the
        // beginning of soft wrapped lines, and lets the user see 2 spaces when
        // they type 2 spaces.

        RefPtr<Element> brNode = htmlEditor->CreateBR(aRun->EndPoint());
        if (NS_WARN_IF(!brNode)) {
          return NS_ERROR_FAILURE;
        }

        // Refresh thePoint, prevPoint
        thePoint = GetPreviousCharPoint(aRun->EndPoint());
        prevPoint = GetPreviousCharPoint(thePoint);
        rightCheck = true;
      }
    }
    if (leftCheck && rightCheck) {
      // Now replace nbsp with space.  First, insert a space
      AutoTransactionsConserveSelection dontChangeMySelection(htmlEditor);
      nsAutoString spaceStr(char16_t(32));
      nsresult rv =
        htmlEditor->InsertTextIntoTextNodeImpl(spaceStr, *thePoint.mTextNode,
                                               thePoint.mOffset, true);
      NS_ENSURE_SUCCESS(rv, rv);

      // Finally, delete that nbsp
      rv = DeleteRange(EditorRawDOMPoint(thePoint.mTextNode,
                                         thePoint.mOffset + 1),
                       EditorRawDOMPoint(thePoint.mTextNode,
                                         thePoint.mOffset + 2));
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    } else if (!mPRE && spaceNBSP && rightCheck) {
      // Don't mess with this preformatted for now.  We have a run of ASCII
      // whitespace (which will render as one space) followed by an nbsp (which
      // is at the end of the whitespace run).  Let's switch their order.  This
      // will ensure that if someone types two spaces after a sentence, and the
      // editor softwraps at this point, the spaces won't be split across lines,
      // which looks ugly and is bad for the moose.

      RefPtr<Text> startNode, endNode;
      int32_t startOffset, endOffset;
      GetASCIIWhitespacesBounds(eBoth, prevPoint.mTextNode,
                                prevPoint.mOffset + 1,
                                getter_AddRefs(startNode), &startOffset,
                                getter_AddRefs(endNode), &endOffset);

      // Delete that nbsp
      nsresult rv = DeleteRange(EditorRawDOMPoint(thePoint.mTextNode,
                                                  thePoint.mOffset),
                                EditorRawDOMPoint(thePoint.mTextNode,
                                                  thePoint.mOffset + 1));
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      // Finally, insert that nbsp before the ASCII ws run
      AutoTransactionsConserveSelection dontChangeMySelection(htmlEditor);
      rv =
        htmlEditor->InsertTextIntoTextNodeImpl(nsDependentSubstring(&kNBSP, 1),
                                               *startNode, startOffset, true);
      NS_ENSURE_SUCCESS(rv, rv);
    }
  }
  return NS_OK;
}

nsresult
WSRunObject::ReplacePreviousNBSPIfUnncessary(WSFragment* aRun,
                                             const EditorRawDOMPoint& aPoint)
{
  if (NS_WARN_IF(!aRun) ||
      NS_WARN_IF(!aPoint.IsSet())) {
    return NS_ERROR_INVALID_ARG;
  }
  MOZ_ASSERT(aPoint.IsSetAndValid());

  // Try to change an NBSP to a space, if possible, just to prevent NBSP
  // proliferation.  This routine is called when we are about to make this
  // point in the ws abut an inserted break or text, so we don't have to worry
  // about what is after it.  What is after it now will end up after the
  // inserted object.
  bool canConvert = false;
  WSPoint thePoint = GetPreviousCharPoint(aPoint);
  if (thePoint.mTextNode && thePoint.mChar == kNBSP) {
    WSPoint prevPoint = GetPreviousCharPoint(thePoint);
    if (prevPoint.mTextNode) {
      if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) {
        // If previous character is a NBSP and its previous character isn't
        // ASCII space, we can replace the NBSP with ASCII space.
        canConvert = true;
      }
    } else if (aRun->mLeftType == WSType::text ||
               aRun->mLeftType == WSType::special) {
      // If previous character is a NBSP and it's the first character of the
      // text node, additionally, if its previous node is a text node including
      // non-whitespace characters or <img> node or something inline
      // non-container element node, we can replace the NBSP with ASCII space.
      canConvert = true;
    }
  }

  if (!canConvert) {
    return NS_OK;
  }

  // First, insert a space before the previous NBSP.
  AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
  nsAutoString spaceStr(char16_t(32));
  nsresult rv =
    mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, *thePoint.mTextNode,
                                            thePoint.mOffset, true);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Finally, delete the previous NBSP.
  rv = DeleteRange(EditorRawDOMPoint(thePoint.mTextNode,
                                     thePoint.mOffset + 1),
                   EditorRawDOMPoint(thePoint.mTextNode,
                                     thePoint.mOffset + 2));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

nsresult
WSRunObject::CheckLeadingNBSP(WSFragment* aRun,
                              nsINode* aNode,
                              int32_t aOffset)
{
  // Try to change an nbsp to a space, if possible, just to prevent nbsp
  // proliferation This routine is called when we are about to make this point
  // in the ws abut an inserted text, so we don't have to worry about what is
  // before it.  What is before it now will end up before the inserted text.
  bool canConvert = false;
  WSPoint thePoint = GetNextCharPoint(EditorRawDOMPoint(aNode, aOffset));
  if (thePoint.mChar == kNBSP) {
    WSPoint tmp = thePoint;
    // we want to be after thePoint
    tmp.mOffset++;
    WSPoint nextPoint = GetNextCharPoint(tmp);
    if (nextPoint.mTextNode) {
      if (!nsCRT::IsAsciiSpace(nextPoint.mChar)) {
        canConvert = true;
      }
    } else if (aRun->mRightType == WSType::text ||
               aRun->mRightType == WSType::special ||
               aRun->mRightType == WSType::br) {
      canConvert = true;
    }
  }
  if (canConvert) {
    // First, insert a space
    AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
    nsAutoString spaceStr(char16_t(32));
    nsresult rv =
      mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, *thePoint.mTextNode,
                                              thePoint.mOffset, true);
    NS_ENSURE_SUCCESS(rv, rv);

    // Finally, delete that nbsp
    rv = DeleteRange(EditorRawDOMPoint(thePoint.mTextNode,
                                       thePoint.mOffset + 1),
                     EditorRawDOMPoint(thePoint.mTextNode,
                                       thePoint.mOffset + 2));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }
  return NS_OK;
}


nsresult
WSRunObject::Scrub()
{
  WSFragment *run = mStartRun;
  while (run) {
    if (run->mType & (WSType::leadingWS | WSType::trailingWS)) {
      nsresult rv = DeleteRange(run->StartPoint(), run->EndPoint());
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }
    run = run->mRight;
  }
  return NS_OK;
}

bool
WSRunObject::IsBlockNode(nsINode* aNode)
{
  return aNode && aNode->IsElement() &&
         HTMLEditor::NodeIsBlockStatic(aNode->AsElement());
}

} // namespace mozilla
