tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TextServicesDocument.cpp (90619B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "TextServicesDocument.h"
      7 
      8 #include "EditorBase.h"               // for EditorBase
      9 #include "EditorUtils.h"              // for AutoTransactionBatchExternal
     10 #include "FilteredContentIterator.h"  // for FilteredContentIterator
     11 #include "HTMLEditHelpers.h"          // for BlockInlineCheck
     12 #include "HTMLEditUtils.h"            // for HTMLEditUtils
     13 
     14 #include "mozilla/Assertions.h"    // for MOZ_ASSERT, etc
     15 #include "mozilla/IntegerRange.h"  // for IntegerRange
     16 #include "mozilla/mozalloc.h"      // for operator new, etc
     17 #include "mozilla/OwningNonNull.h"
     18 #include "mozilla/UniquePtr.h"          // for UniquePtr
     19 #include "mozilla/dom/AbstractRange.h"  // for AbstractRange
     20 #include "mozilla/dom/Element.h"
     21 #include "mozilla/dom/Selection.h"
     22 #include "mozilla/dom/StaticRange.h"  // for StaticRange
     23 #include "mozilla/dom/Text.h"
     24 #include "mozilla/intl/WordBreaker.h"  // for WordRange, WordBreaker
     25 
     26 #include "nsAString.h"       // for nsAString::Length, etc
     27 #include "nsContentUtils.h"  // for nsContentUtils
     28 #include "nsComposeTxtSrvFilter.h"
     29 #include "nsDebug.h"                 // for NS_ENSURE_TRUE, etc
     30 #include "nsDependentSubstring.h"    // for Substring
     31 #include "nsError.h"                 // for NS_OK, NS_ERROR_FAILURE, etc
     32 #include "nsGenericHTMLElement.h"    // for nsGenericHTMLElement
     33 #include "nsIContent.h"              // for nsIContent, etc
     34 #include "nsID.h"                    // for NS_GET_IID
     35 #include "nsIEditor.h"               // for nsIEditor, etc
     36 #include "nsIEditorSpellCheck.h"     // for nsIEditorSpellCheck, etc
     37 #include "nsINode.h"                 // for nsINode
     38 #include "nsISelectionController.h"  // for nsISelectionController, etc
     39 #include "nsISupports.h"             // for nsISupports
     40 #include "nsISupportsUtils.h"        // for NS_IF_ADDREF, NS_ADDREF, etc
     41 #include "nsRange.h"                 // for nsRange
     42 #include "nsString.h"                // for nsString, nsAutoString
     43 #include "nscore.h"                  // for nsresult, NS_IMETHODIMP, etc
     44 
     45 namespace mozilla {
     46 
     47 using namespace dom;
     48 
     49 /**
     50 * OffsetEntry manages a range in a text node.  It stores 2 offset values,
     51 * one is offset in the text node, the other is offset in all text in
     52 * the ancestor block of the text node.  And the length is managing length
     53 * in the text node, starting from the offset in text node.
     54 * In other words, a text node may be managed by multiple instances of this
     55 * class.
     56 */
     57 class OffsetEntry final {
     58 public:
     59  OffsetEntry() = delete;
     60 
     61  /**
     62   * @param aTextNode   The text node which will be manged by the instance.
     63   * @param aOffsetInTextInBlock
     64   *                    Start offset in the text node which will be managed by
     65   *                    the instance.
     66   * @param aLength     Length in the text node which will be managed by the
     67   *                    instance.
     68   */
     69  OffsetEntry(Text& aTextNode, uint32_t aOffsetInTextInBlock, uint32_t aLength)
     70      : mTextNode(aTextNode),
     71        mOffsetInTextNode(0),
     72        mOffsetInTextInBlock(aOffsetInTextInBlock),
     73        mLength(aLength),
     74        mIsInsertedText(false),
     75        mIsValid(true) {}
     76 
     77  /**
     78   * EndOffsetInTextNode() returns end offset in the text node, which is
     79   * managed by the instance.
     80   */
     81  uint32_t EndOffsetInTextNode() const { return mOffsetInTextNode + mLength; }
     82 
     83  /**
     84   * OffsetInTextNodeIsInRangeOrEndOffset() checks whether the offset in
     85   * the text node is managed by the instance or not.
     86   */
     87  bool OffsetInTextNodeIsInRangeOrEndOffset(uint32_t aOffsetInTextNode) const {
     88    return aOffsetInTextNode >= mOffsetInTextNode &&
     89           aOffsetInTextNode <= EndOffsetInTextNode();
     90  }
     91 
     92  /**
     93   * EndOffsetInTextInBlock() returns end offset in the all text in ancestor
     94   * block of the text node, which is managed by the instance.
     95   */
     96  uint32_t EndOffsetInTextInBlock() const {
     97    return mOffsetInTextInBlock + mLength;
     98  }
     99 
    100  /**
    101   * OffsetInTextNodeIsInRangeOrEndOffset() checks whether the offset in
    102   * the all text in ancestor block of the text node is managed by the instance
    103   * or not.
    104   */
    105  bool OffsetInTextInBlockIsInRangeOrEndOffset(
    106      uint32_t aOffsetInTextInBlock) const {
    107    return aOffsetInTextInBlock >= mOffsetInTextInBlock &&
    108           aOffsetInTextInBlock <= EndOffsetInTextInBlock();
    109  }
    110 
    111  OwningNonNull<Text> mTextNode;
    112  uint32_t mOffsetInTextNode;
    113  // Offset in all text in the closest ancestor block of mTextNode.
    114  uint32_t mOffsetInTextInBlock;
    115  uint32_t mLength;
    116  bool mIsInsertedText;
    117  bool mIsValid;
    118 };
    119 
    120 template <typename ElementType>
    121 struct MOZ_STACK_CLASS ArrayLengthMutationGuard final {
    122  ArrayLengthMutationGuard() = delete;
    123  explicit ArrayLengthMutationGuard(const nsTArray<ElementType>& aArray)
    124      : mArray(aArray), mOldLength(aArray.Length()) {}
    125  ~ArrayLengthMutationGuard() {
    126    if (mArray.Length() != mOldLength) {
    127      MOZ_CRASH("The array length was changed unexpectedly");
    128    }
    129  }
    130 
    131 private:
    132  const nsTArray<ElementType>& mArray;
    133  size_t mOldLength;
    134 };
    135 
    136 #define LockOffsetEntryArrayLengthInDebugBuild(aName, aArray)               \
    137  DebugOnly<ArrayLengthMutationGuard<UniquePtr<OffsetEntry>>> const aName = \
    138      ArrayLengthMutationGuard<UniquePtr<OffsetEntry>>(aArray);
    139 
    140 TextServicesDocument::TextServicesDocument()
    141    : mTxtSvcFilterType(0), mIteratorStatus(IteratorStatus::eDone) {}
    142 
    143 NS_IMPL_CYCLE_COLLECTING_ADDREF(TextServicesDocument)
    144 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextServicesDocument)
    145 
    146 NS_INTERFACE_MAP_BEGIN(TextServicesDocument)
    147  NS_INTERFACE_MAP_ENTRY(nsIEditActionListener)
    148  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditActionListener)
    149  NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(TextServicesDocument)
    150 NS_INTERFACE_MAP_END
    151 
    152 NS_IMPL_CYCLE_COLLECTION(TextServicesDocument, mDocument, mSelCon, mEditorBase,
    153                         mFilteredIter, mPrevTextBlock, mNextTextBlock, mExtent)
    154 
    155 nsresult TextServicesDocument::InitWithEditor(nsIEditor* aEditor) {
    156  nsCOMPtr<nsISelectionController> selCon;
    157 
    158  NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER);
    159 
    160  // Check to see if we already have an mSelCon. If we do, it
    161  // better be the same one the editor uses!
    162 
    163  nsresult rv = aEditor->GetSelectionController(getter_AddRefs(selCon));
    164 
    165  if (NS_FAILED(rv)) {
    166    return rv;
    167  }
    168 
    169  if (!selCon || (mSelCon && selCon != mSelCon)) {
    170    return NS_ERROR_FAILURE;
    171  }
    172 
    173  if (!mSelCon) {
    174    mSelCon = selCon;
    175  }
    176 
    177  // Check to see if we already have an mDocument. If we do, it
    178  // better be the same one the editor uses!
    179 
    180  RefPtr<Document> doc = aEditor->AsEditorBase()->GetDocument();
    181  if (!doc || (mDocument && doc != mDocument)) {
    182    return NS_ERROR_FAILURE;
    183  }
    184 
    185  if (!mDocument) {
    186    mDocument = doc;
    187 
    188    rv = CreateDocumentContentIterator(getter_AddRefs(mFilteredIter));
    189 
    190    if (NS_FAILED(rv)) {
    191      return rv;
    192    }
    193 
    194    mIteratorStatus = IteratorStatus::eDone;
    195 
    196    rv = FirstBlock();
    197 
    198    if (NS_FAILED(rv)) {
    199      return rv;
    200    }
    201  }
    202 
    203  mEditorBase = aEditor->AsEditorBase();
    204 
    205  rv = aEditor->AddEditActionListener(this);
    206 
    207  return rv;
    208 }
    209 
    210 nsresult TextServicesDocument::SetExtent(const AbstractRange* aAbstractRange) {
    211  MOZ_ASSERT(aAbstractRange);
    212 
    213  if (NS_WARN_IF(!mDocument)) {
    214    return NS_ERROR_FAILURE;
    215  }
    216 
    217  // We need to store a copy of aAbstractRange since we don't know where it
    218  // came from.
    219  mExtent = nsRange::Create(aAbstractRange, IgnoreErrors());
    220  if (NS_WARN_IF(!mExtent)) {
    221    return NS_ERROR_FAILURE;
    222  }
    223 
    224  // Create a new iterator based on our new extent range.
    225  nsresult rv =
    226      CreateFilteredContentIterator(mExtent, getter_AddRefs(mFilteredIter));
    227  if (NS_WARN_IF(NS_FAILED(rv))) {
    228    return rv;
    229  }
    230 
    231  // Now position the iterator at the start of the first block
    232  // in the range.
    233  mIteratorStatus = IteratorStatus::eDone;
    234 
    235  rv = FirstBlock();
    236  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "FirstBlock() failed");
    237  return rv;
    238 }
    239 
    240 nsresult TextServicesDocument::ExpandRangeToWordBoundaries(
    241    StaticRange* aStaticRange) {
    242  MOZ_ASSERT(aStaticRange);
    243 
    244  // Get the end points of the range.
    245 
    246  nsCOMPtr<nsINode> rngStartNode, rngEndNode;
    247  uint32_t rngStartOffset, rngEndOffset;
    248 
    249  nsresult rv = GetRangeEndPoints(aStaticRange, getter_AddRefs(rngStartNode),
    250                                  &rngStartOffset, getter_AddRefs(rngEndNode),
    251                                  &rngEndOffset);
    252  if (NS_WARN_IF(NS_FAILED(rv))) {
    253    return rv;
    254  }
    255 
    256  // Create a content iterator based on the range.
    257  RefPtr<FilteredContentIterator> filteredIter;
    258  rv =
    259      CreateFilteredContentIterator(aStaticRange, getter_AddRefs(filteredIter));
    260  if (NS_WARN_IF(NS_FAILED(rv))) {
    261    return rv;
    262  }
    263 
    264  // Find the first text node in the range.
    265  IteratorStatus iterStatus = IteratorStatus::eDone;
    266  rv = FirstTextNode(filteredIter, &iterStatus);
    267  if (NS_WARN_IF(NS_FAILED(rv))) {
    268    return rv;
    269  }
    270 
    271  if (iterStatus == IteratorStatus::eDone) {
    272    // No text was found so there's no adjustment necessary!
    273    return NS_OK;
    274  }
    275 
    276  nsINode* firstText = filteredIter->GetCurrentNode();
    277  if (NS_WARN_IF(!firstText)) {
    278    return NS_ERROR_FAILURE;
    279  }
    280 
    281  // Find the last text node in the range.
    282 
    283  rv = LastTextNode(filteredIter, &iterStatus);
    284  if (NS_WARN_IF(NS_FAILED(rv))) {
    285    return rv;
    286  }
    287 
    288  if (iterStatus == IteratorStatus::eDone) {
    289    // We should never get here because a first text block
    290    // was found above.
    291    NS_ASSERTION(false, "Found a first without a last!");
    292    return NS_ERROR_FAILURE;
    293  }
    294 
    295  nsINode* lastText = filteredIter->GetCurrentNode();
    296  if (NS_WARN_IF(!lastText)) {
    297    return NS_ERROR_FAILURE;
    298  }
    299 
    300  // Now make sure our end points are in terms of text nodes in the range!
    301 
    302  if (rngStartNode != firstText) {
    303    // The range includes the start of the first text node!
    304    rngStartNode = firstText;
    305    rngStartOffset = 0;
    306  }
    307 
    308  if (rngEndNode != lastText) {
    309    // The range includes the end of the last text node!
    310    rngEndNode = lastText;
    311    rngEndOffset = lastText->Length();
    312  }
    313 
    314  // Create a doc iterator so that we can scan beyond
    315  // the bounds of the extent range.
    316 
    317  RefPtr<FilteredContentIterator> docFilteredIter;
    318  rv = CreateDocumentContentIterator(getter_AddRefs(docFilteredIter));
    319  if (NS_WARN_IF(NS_FAILED(rv))) {
    320    return rv;
    321  }
    322 
    323  // Grab all the text in the block containing our
    324  // first text node.
    325  rv = docFilteredIter->PositionAt(firstText);
    326  if (NS_WARN_IF(NS_FAILED(rv))) {
    327    return rv;
    328  }
    329 
    330  iterStatus = IteratorStatus::eValid;
    331 
    332  OffsetEntryArray offsetTable;
    333  nsAutoString blockStr;
    334  Result<IteratorStatus, nsresult> result = offsetTable.Init(
    335      *docFilteredIter, IteratorStatus::eValid, nullptr, &blockStr);
    336  if (result.isErr()) {
    337    return result.unwrapErr();
    338  }
    339 
    340  Result<EditorDOMRangeInTexts, nsresult> maybeWordRange =
    341      offsetTable.FindWordRange(
    342          blockStr, EditorRawDOMPoint(rngStartNode, rngStartOffset));
    343  offsetTable.Clear();
    344  if (maybeWordRange.isErr()) {
    345    NS_WARNING(
    346        "TextServicesDocument::OffsetEntryArray::FindWordRange() failed");
    347    return maybeWordRange.unwrapErr();
    348  }
    349  rngStartNode = maybeWordRange.inspect().StartRef().GetContainerAs<Text>();
    350  rngStartOffset = maybeWordRange.inspect().StartRef().Offset();
    351 
    352  // Grab all the text in the block containing our
    353  // last text node.
    354 
    355  rv = docFilteredIter->PositionAt(lastText);
    356  if (NS_WARN_IF(NS_FAILED(rv))) {
    357    return rv;
    358  }
    359 
    360  result = offsetTable.Init(*docFilteredIter, IteratorStatus::eValid, nullptr,
    361                            &blockStr);
    362  if (result.isErr()) {
    363    return result.unwrapErr();
    364  }
    365 
    366  maybeWordRange = offsetTable.FindWordRange(
    367      blockStr, EditorRawDOMPoint(rngEndNode, rngEndOffset));
    368  offsetTable.Clear();
    369  if (maybeWordRange.isErr()) {
    370    NS_WARNING(
    371        "TextServicesDocument::OffsetEntryArray::FindWordRange() failed");
    372    return maybeWordRange.unwrapErr();
    373  }
    374 
    375  // To prevent expanding the range too much, we only change
    376  // rngEndNode and rngEndOffset if it isn't already at the start of the
    377  // word and isn't equivalent to rngStartNode and rngStartOffset.
    378 
    379  if (rngEndNode !=
    380          maybeWordRange.inspect().StartRef().GetContainerAs<Text>() ||
    381      rngEndOffset != maybeWordRange.inspect().StartRef().Offset() ||
    382      (rngEndNode == rngStartNode && rngEndOffset == rngStartOffset)) {
    383    rngEndNode = maybeWordRange.inspect().EndRef().GetContainerAs<Text>();
    384    rngEndOffset = maybeWordRange.inspect().EndRef().Offset();
    385  }
    386 
    387  // Now adjust the range so that it uses our new end points.
    388  rv = aStaticRange->SetStartAndEnd(rngStartNode, rngStartOffset, rngEndNode,
    389                                    rngEndOffset);
    390  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to update the given range");
    391  return rv;
    392 }
    393 
    394 nsresult TextServicesDocument::SetFilterType(uint32_t aFilterType) {
    395  mTxtSvcFilterType = aFilterType;
    396 
    397  return NS_OK;
    398 }
    399 
    400 nsresult TextServicesDocument::GetCurrentTextBlock(nsAString& aStr) {
    401  aStr.Truncate();
    402 
    403  NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
    404 
    405  Result<IteratorStatus, nsresult> result =
    406      mOffsetTable.Init(*mFilteredIter, mIteratorStatus, mExtent, &aStr);
    407  if (result.isErr()) {
    408    NS_WARNING("OffsetEntryArray::Init() failed");
    409    return result.unwrapErr();
    410  }
    411  mIteratorStatus = result.unwrap();
    412  return NS_OK;
    413 }
    414 
    415 nsresult TextServicesDocument::FirstBlock() {
    416  NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
    417 
    418  nsresult rv = FirstTextNode(mFilteredIter, &mIteratorStatus);
    419 
    420  if (NS_FAILED(rv)) {
    421    return rv;
    422  }
    423 
    424  // Keep track of prev and next blocks, just in case
    425  // the text service blows away the current block.
    426 
    427  if (mIteratorStatus == IteratorStatus::eValid) {
    428    mPrevTextBlock = nullptr;
    429    rv = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock));
    430  } else {
    431    // There's no text block in the document!
    432 
    433    mPrevTextBlock = nullptr;
    434    mNextTextBlock = nullptr;
    435  }
    436 
    437  // XXX Result of FirstTextNode() or GetFirstTextNodeInNextBlock().
    438  return rv;
    439 }
    440 
    441 nsresult TextServicesDocument::LastSelectedBlock(
    442    BlockSelectionStatus* aSelStatus, uint32_t* aSelOffset,
    443    uint32_t* aSelLength) {
    444  NS_ENSURE_TRUE(aSelStatus && aSelOffset && aSelLength, NS_ERROR_NULL_POINTER);
    445 
    446  mIteratorStatus = IteratorStatus::eDone;
    447 
    448  *aSelStatus = BlockSelectionStatus::eBlockNotFound;
    449  *aSelOffset = *aSelLength = UINT32_MAX;
    450 
    451  if (!mSelCon || !mFilteredIter) {
    452    return NS_ERROR_FAILURE;
    453  }
    454 
    455  RefPtr<Selection> selection =
    456      mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
    457  if (NS_WARN_IF(!selection)) {
    458    return NS_ERROR_FAILURE;
    459  }
    460 
    461  RefPtr<const nsRange> range;
    462  nsCOMPtr<nsINode> parent;
    463 
    464  if (selection->IsCollapsed()) {
    465    // We have a caret. Check if the caret is in a text node.
    466    // If it is, make the text node's block the current block.
    467    // If the caret isn't in a text node, search forwards in
    468    // the document, till we find a text node.
    469 
    470    range = selection->GetRangeAt(0);
    471    if (!range) {
    472      return NS_ERROR_FAILURE;
    473    }
    474 
    475    parent = range->GetStartContainer();
    476    if (!parent) {
    477      return NS_ERROR_FAILURE;
    478    }
    479 
    480    nsresult rv;
    481    if (parent->IsText()) {
    482      // The caret is in a text node. Find the beginning
    483      // of the text block containing this text node and
    484      // return.
    485 
    486      rv = mFilteredIter->PositionAt(parent->AsText());
    487      if (NS_FAILED(rv)) {
    488        return rv;
    489      }
    490 
    491      rv = FirstTextNodeInCurrentBlock(mFilteredIter);
    492      if (NS_FAILED(rv)) {
    493        return rv;
    494      }
    495 
    496      Result<IteratorStatus, nsresult> result =
    497          mOffsetTable.Init(*mFilteredIter, IteratorStatus::eValid, mExtent);
    498      if (result.isErr()) {
    499        NS_WARNING("OffsetEntryArray::Init() failed");
    500        mIteratorStatus = IteratorStatus::eValid;  // XXX
    501        return result.unwrapErr();
    502      }
    503      mIteratorStatus = result.unwrap();
    504 
    505      rv = GetSelection(aSelStatus, aSelOffset, aSelLength);
    506      if (NS_FAILED(rv)) {
    507        return rv;
    508      }
    509 
    510      if (*aSelStatus == BlockSelectionStatus::eBlockContains) {
    511        rv = SetSelectionInternal(*aSelOffset, *aSelLength, false);
    512      }
    513    } else {
    514      // The caret isn't in a text node. Create an iterator
    515      // based on a range that extends from the current caret
    516      // position to the end of the document, then walk forwards
    517      // till you find a text node, then find the beginning of it's block.
    518 
    519      range = CreateDocumentContentRootToNodeOffsetRange(
    520          parent, range->StartOffset(), false);
    521      if (NS_WARN_IF(!range)) {
    522        return NS_ERROR_FAILURE;
    523      }
    524 
    525      if (range->Collapsed()) {
    526        // If we get here, the range is collapsed because there is nothing after
    527        // the caret! Just return NS_OK;
    528        return NS_OK;
    529      }
    530 
    531      RefPtr<FilteredContentIterator> filteredIter;
    532      rv = CreateFilteredContentIterator(range, getter_AddRefs(filteredIter));
    533      if (NS_FAILED(rv)) {
    534        return rv;
    535      }
    536 
    537      filteredIter->First();
    538 
    539      Text* textNode = nullptr;
    540      for (; !filteredIter->IsDone(); filteredIter->Next()) {
    541        nsINode* currentNode = filteredIter->GetCurrentNode();
    542        if (currentNode->IsText()) {
    543          textNode = currentNode->AsText();
    544          break;
    545        }
    546      }
    547 
    548      if (!textNode) {
    549        return NS_OK;
    550      }
    551 
    552      rv = mFilteredIter->PositionAt(textNode);
    553      if (NS_FAILED(rv)) {
    554        return rv;
    555      }
    556 
    557      rv = FirstTextNodeInCurrentBlock(mFilteredIter);
    558      if (NS_FAILED(rv)) {
    559        return rv;
    560      }
    561 
    562      Result<IteratorStatus, nsresult> result = mOffsetTable.Init(
    563          *mFilteredIter, IteratorStatus::eValid, mExtent, nullptr);
    564      if (result.isErr()) {
    565        NS_WARNING("OffsetEntryArray::Init() failed");
    566        mIteratorStatus = IteratorStatus::eValid;  // XXX
    567        return result.unwrapErr();
    568      }
    569      mIteratorStatus = result.inspect();
    570 
    571      rv = GetSelection(aSelStatus, aSelOffset, aSelLength);
    572      if (NS_FAILED(rv)) {
    573        return rv;
    574      }
    575    }
    576 
    577    // Result of SetSelectionInternal() in the |if| block or NS_OK.
    578    return rv;
    579  }
    580 
    581  // If we get here, we have an uncollapsed selection!
    582  // Look backwards through each range in the selection till you
    583  // find the first text node. If you find one, find the
    584  // beginning of its text block, and make it the current
    585  // block.
    586 
    587  const uint32_t rangeCount = selection->RangeCount();
    588  MOZ_ASSERT(
    589      rangeCount,
    590      "Selection is not collapsed, so, the range count should be 1 or larger");
    591 
    592  // XXX: We may need to add some code here to make sure
    593  //      the ranges are sorted in document appearance order!
    594 
    595  for (const uint32_t i : Reversed(IntegerRange(rangeCount))) {
    596    MOZ_ASSERT(selection->RangeCount() == rangeCount);
    597    range = selection->GetRangeAt(i);
    598    if (MOZ_UNLIKELY(!range)) {
    599      return NS_OK;  // XXX Really?
    600    }
    601 
    602    // Create an iterator for the range.
    603 
    604    RefPtr<FilteredContentIterator> filteredIter;
    605    nsresult rv =
    606        CreateFilteredContentIterator(range, getter_AddRefs(filteredIter));
    607    if (NS_FAILED(rv)) {
    608      return rv;
    609    }
    610 
    611    filteredIter->Last();
    612 
    613    // Now walk through the range till we find a text node.
    614 
    615    for (; !filteredIter->IsDone(); filteredIter->Prev()) {
    616      if (filteredIter->GetCurrentNode()->NodeType() == nsINode::TEXT_NODE) {
    617        // We found a text node, so position the document's
    618        // iterator at the beginning of the block, then get
    619        // the selection in terms of the string offset.
    620 
    621        nsresult rv = mFilteredIter->PositionAt(filteredIter->GetCurrentNode());
    622        if (NS_FAILED(rv)) {
    623          return rv;
    624        }
    625 
    626        rv = FirstTextNodeInCurrentBlock(mFilteredIter);
    627        if (NS_FAILED(rv)) {
    628          return rv;
    629        }
    630 
    631        mIteratorStatus = IteratorStatus::eValid;
    632 
    633        Result<IteratorStatus, nsresult> result =
    634            mOffsetTable.Init(*mFilteredIter, IteratorStatus::eValid, mExtent);
    635        if (result.isErr()) {
    636          NS_WARNING("OffsetEntryArray::Init() failed");
    637          mIteratorStatus = IteratorStatus::eValid;  // XXX
    638          return result.unwrapErr();
    639        }
    640        mIteratorStatus = result.unwrap();
    641 
    642        return GetSelection(aSelStatus, aSelOffset, aSelLength);
    643      }
    644    }
    645  }
    646 
    647  // If we get here, we didn't find any text node in the selection!
    648  // Create a range that extends from the end of the selection,
    649  // to the end of the document, then iterate forwards through
    650  // it till you find a text node!
    651  range = rangeCount > 0 ? selection->GetRangeAt(rangeCount - 1) : nullptr;
    652  if (!range) {
    653    return NS_ERROR_FAILURE;
    654  }
    655 
    656  parent = range->GetEndContainer();
    657  if (!parent) {
    658    return NS_ERROR_FAILURE;
    659  }
    660 
    661  range = CreateDocumentContentRootToNodeOffsetRange(parent, range->EndOffset(),
    662                                                     false);
    663  if (NS_WARN_IF(!range)) {
    664    return NS_ERROR_FAILURE;
    665  }
    666 
    667  if (range->Collapsed()) {
    668    // If we get here, the range is collapsed because there is nothing after
    669    // the current selection! Just return NS_OK;
    670    return NS_OK;
    671  }
    672 
    673  RefPtr<FilteredContentIterator> filteredIter;
    674  nsresult rv =
    675      CreateFilteredContentIterator(range, getter_AddRefs(filteredIter));
    676  if (NS_FAILED(rv)) {
    677    return rv;
    678  }
    679 
    680  filteredIter->First();
    681 
    682  for (; !filteredIter->IsDone(); filteredIter->Next()) {
    683    if (filteredIter->GetCurrentNode()->NodeType() == nsINode::TEXT_NODE) {
    684      // We found a text node! Adjust the document's iterator to point
    685      // to the beginning of its text block, then get the current selection.
    686      nsresult rv = mFilteredIter->PositionAt(filteredIter->GetCurrentNode());
    687      if (NS_FAILED(rv)) {
    688        return rv;
    689      }
    690 
    691      rv = FirstTextNodeInCurrentBlock(mFilteredIter);
    692      if (NS_FAILED(rv)) {
    693        return rv;
    694      }
    695 
    696      Result<IteratorStatus, nsresult> result =
    697          mOffsetTable.Init(*mFilteredIter, IteratorStatus::eValid, mExtent);
    698      if (result.isErr()) {
    699        NS_WARNING("OffsetEntryArray::Init() failed");
    700        mIteratorStatus = IteratorStatus::eValid;  // XXX
    701        return result.unwrapErr();
    702      }
    703      mIteratorStatus = result.unwrap();
    704 
    705      rv = GetSelection(aSelStatus, aSelOffset, aSelLength);
    706      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    707                           "TextServicesDocument::GetSelection() failed");
    708      return rv;
    709    }
    710  }
    711 
    712  // If we get here, we didn't find any block before or inside
    713  // the selection! Just return OK.
    714  return NS_OK;
    715 }
    716 
    717 nsresult TextServicesDocument::PrevBlock() {
    718  NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
    719 
    720  if (mIteratorStatus == IteratorStatus::eDone) {
    721    return NS_OK;
    722  }
    723 
    724  switch (mIteratorStatus) {
    725    case IteratorStatus::eValid:
    726    case IteratorStatus::eNext: {
    727      nsresult rv = FirstTextNodeInPrevBlock(mFilteredIter);
    728 
    729      if (NS_FAILED(rv)) {
    730        mIteratorStatus = IteratorStatus::eDone;
    731        return rv;
    732      }
    733 
    734      if (mFilteredIter->IsDone()) {
    735        mIteratorStatus = IteratorStatus::eDone;
    736        return NS_OK;
    737      }
    738 
    739      mIteratorStatus = IteratorStatus::eValid;
    740      break;
    741    }
    742    case IteratorStatus::ePrev:
    743 
    744      // The iterator already points to the previous
    745      // block, so don't do anything.
    746 
    747      mIteratorStatus = IteratorStatus::eValid;
    748      break;
    749 
    750    default:
    751 
    752      mIteratorStatus = IteratorStatus::eDone;
    753      break;
    754  }
    755 
    756  // Keep track of prev and next blocks, just in case
    757  // the text service blows away the current block.
    758  nsresult rv = NS_OK;
    759  if (mIteratorStatus == IteratorStatus::eValid) {
    760    GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock));
    761    rv = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock));
    762  } else {
    763    // We must be done!
    764    mPrevTextBlock = nullptr;
    765    mNextTextBlock = nullptr;
    766  }
    767 
    768  // XXX The result of GetFirstTextNodeInNextBlock() or NS_OK.
    769  return rv;
    770 }
    771 
    772 nsresult TextServicesDocument::NextBlock() {
    773  NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
    774 
    775  if (mIteratorStatus == IteratorStatus::eDone) {
    776    return NS_OK;
    777  }
    778 
    779  switch (mIteratorStatus) {
    780    case IteratorStatus::eValid: {
    781      // Advance the iterator to the next text block.
    782 
    783      nsresult rv = FirstTextNodeInNextBlock(mFilteredIter);
    784 
    785      if (NS_FAILED(rv)) {
    786        mIteratorStatus = IteratorStatus::eDone;
    787        return rv;
    788      }
    789 
    790      if (mFilteredIter->IsDone()) {
    791        mIteratorStatus = IteratorStatus::eDone;
    792        return NS_OK;
    793      }
    794 
    795      mIteratorStatus = IteratorStatus::eValid;
    796      break;
    797    }
    798    case IteratorStatus::eNext:
    799 
    800      // The iterator already points to the next block,
    801      // so don't do anything to it!
    802 
    803      mIteratorStatus = IteratorStatus::eValid;
    804      break;
    805 
    806    case IteratorStatus::ePrev:
    807 
    808      // If the iterator is pointing to the previous block,
    809      // we know that there is no next text block! Just
    810      // fall through to the default case!
    811 
    812    default:
    813 
    814      mIteratorStatus = IteratorStatus::eDone;
    815      break;
    816  }
    817 
    818  // Keep track of prev and next blocks, just in case
    819  // the text service blows away the current block.
    820  nsresult rv = NS_OK;
    821  if (mIteratorStatus == IteratorStatus::eValid) {
    822    GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock));
    823    rv = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock));
    824  } else {
    825    // We must be done.
    826    mPrevTextBlock = nullptr;
    827    mNextTextBlock = nullptr;
    828  }
    829 
    830  // The result of GetFirstTextNodeInNextBlock() or NS_OK.
    831  return rv;
    832 }
    833 
    834 nsresult TextServicesDocument::IsDone(bool* aIsDone) {
    835  NS_ENSURE_TRUE(aIsDone, NS_ERROR_NULL_POINTER);
    836 
    837  *aIsDone = false;
    838 
    839  NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
    840 
    841  *aIsDone = mIteratorStatus == IteratorStatus::eDone;
    842 
    843  return NS_OK;
    844 }
    845 
    846 nsresult TextServicesDocument::SetSelection(uint32_t aOffset,
    847                                            uint32_t aLength) {
    848  NS_ENSURE_TRUE(mSelCon, NS_ERROR_FAILURE);
    849 
    850  return SetSelectionInternal(aOffset, aLength, true);
    851 }
    852 
    853 nsresult TextServicesDocument::ScrollSelectionIntoView() {
    854  NS_ENSURE_TRUE(mSelCon, NS_ERROR_FAILURE);
    855 
    856  // After ScrollSelectionIntoView(), the pending notifications might be flushed
    857  // and PresShell/PresContext/Frames may be dead. See bug 418470.
    858  const nsCOMPtr selCon = mSelCon;
    859  return selCon->ScrollSelectionIntoView(
    860      SelectionType::eNormal, nsISelectionController::SELECTION_FOCUS_REGION,
    861      ScrollAxis(), ScrollAxis(), ScrollFlags::None,
    862      SelectionScrollMode::SyncFlush);
    863 }
    864 
    865 nsresult TextServicesDocument::OffsetEntryArray::WillDeleteSelection() {
    866  MOZ_ASSERT(mSelection.IsSet());
    867  MOZ_ASSERT(!mSelection.IsCollapsed());
    868 
    869  for (size_t i = mSelection.StartIndex(); i <= mSelection.EndIndex(); i++) {
    870    OffsetEntry* entry = ElementAt(i).get();
    871    if (i == mSelection.StartIndex()) {
    872      // Calculate the length of the selection. Note that the
    873      // selection length can be zero if the start of the selection
    874      // is at the very end of a text node entry.
    875      uint32_t selLength;
    876      if (entry->mIsInsertedText) {
    877        // Inserted text offset entries have no width when
    878        // talking in terms of string offsets! If the beginning
    879        // of the selection is in an inserted text offset entry,
    880        // the caret is always at the end of the entry!
    881        selLength = 0;
    882      } else {
    883        selLength = entry->EndOffsetInTextInBlock() -
    884                    mSelection.StartOffsetInTextInBlock();
    885      }
    886 
    887      if (selLength > 0) {
    888        if (mSelection.StartOffsetInTextInBlock() >
    889            entry->mOffsetInTextInBlock) {
    890          // Selection doesn't start at the beginning of the
    891          // text node entry. We need to split this entry into
    892          // two pieces, the piece before the selection, and
    893          // the piece inside the selection.
    894          nsresult rv = SplitElementAt(i, selLength);
    895          if (NS_FAILED(rv)) {
    896            NS_WARNING("selLength was invalid for the OffsetEntry");
    897            return rv;
    898          }
    899 
    900          // Adjust selection indexes to account for new entry:
    901          MOZ_DIAGNOSTIC_ASSERT(mSelection.StartIndex() + 1 < Length());
    902          MOZ_DIAGNOSTIC_ASSERT(mSelection.EndIndex() + 1 < Length());
    903          mSelection.SetIndexes(mSelection.StartIndex() + 1,
    904                                mSelection.EndIndex() + 1);
    905          entry = ElementAt(++i).get();
    906        }
    907 
    908        if (mSelection.StartIndex() < mSelection.EndIndex()) {
    909          // The entire entry is contained in the selection. Mark the
    910          // entry invalid.
    911          entry->mIsValid = false;
    912        }
    913      }
    914    }
    915 
    916    if (i == mSelection.EndIndex()) {
    917      if (entry->mIsInsertedText) {
    918        // Inserted text offset entries have no width when
    919        // talking in terms of string offsets! If the end
    920        // of the selection is in an inserted text offset entry,
    921        // the selection includes the entire entry!
    922        entry->mIsValid = false;
    923      } else {
    924        // Calculate the length of the selection. Note that the
    925        // selection length can be zero if the end of the selection
    926        // is at the very beginning of a text node entry.
    927 
    928        const uint32_t selLength =
    929            mSelection.EndOffsetInTextInBlock() - entry->mOffsetInTextInBlock;
    930        if (selLength) {
    931          if (mSelection.EndOffsetInTextInBlock() <
    932              entry->EndOffsetInTextInBlock()) {
    933            // mOffsetInTextInBlock is guaranteed to be inside the selection,
    934            // even when mSelection.IsInSameElement() is true.
    935            nsresult rv = SplitElementAt(i, entry->mLength - selLength);
    936            if (NS_FAILED(rv)) {
    937              NS_WARNING(
    938                  "entry->mLength - selLength was invalid for the OffsetEntry");
    939              return rv;
    940            }
    941 
    942            // Update the entry fields:
    943            ElementAt(i + 1)->mOffsetInTextNode = entry->mOffsetInTextNode;
    944          }
    945 
    946          if (mSelection.EndOffsetInTextInBlock() ==
    947              entry->EndOffsetInTextInBlock()) {
    948            // The entire entry is contained in the selection. Mark the
    949            // entry invalid.
    950            entry->mIsValid = false;
    951          }
    952        }
    953      }
    954    }
    955 
    956    if (i != mSelection.StartIndex() && i != mSelection.EndIndex()) {
    957      // The entire entry is contained in the selection. Mark the
    958      // entry invalid.
    959      entry->mIsValid = false;
    960    }
    961  }
    962 
    963  return NS_OK;
    964 }
    965 
    966 nsresult TextServicesDocument::DeleteSelection() {
    967  if (NS_WARN_IF(!mEditorBase) ||
    968      NS_WARN_IF(!mOffsetTable.mSelection.IsSet())) {
    969    return NS_ERROR_FAILURE;
    970  }
    971 
    972  if (mOffsetTable.mSelection.IsCollapsed()) {
    973    return NS_OK;
    974  }
    975 
    976  // If we have an mExtent, save off its current set of
    977  // end points so we can compare them against mExtent's
    978  // set after the deletion of the content.
    979 
    980  nsCOMPtr<nsINode> origStartNode, origEndNode;
    981  uint32_t origStartOffset = 0, origEndOffset = 0;
    982 
    983  if (mExtent) {
    984    nsresult rv = GetRangeEndPoints(
    985        mExtent, getter_AddRefs(origStartNode), &origStartOffset,
    986        getter_AddRefs(origEndNode), &origEndOffset);
    987 
    988    if (NS_FAILED(rv)) {
    989      return rv;
    990    }
    991  }
    992 
    993  if (NS_FAILED(mOffsetTable.WillDeleteSelection())) {
    994    NS_WARNING(
    995        "TextServicesDocument::OffsetEntryTable::WillDeleteSelection() failed");
    996    return NS_ERROR_FAILURE;
    997  }
    998 
    999  // Make sure mFilteredIter always points to something valid!
   1000  AdjustContentIterator();
   1001 
   1002  // Now delete the actual content!
   1003  OwningNonNull<EditorBase> editorBase = *mEditorBase;
   1004  nsresult rv = editorBase->DeleteSelectionAsAction(nsIEditor::ePrevious,
   1005                                                    nsIEditor::eStrip);
   1006  if (NS_FAILED(rv)) {
   1007    return rv;
   1008  }
   1009 
   1010  // Now that we've actually deleted the selected content,
   1011  // check to see if our mExtent has changed, if so, then
   1012  // we have to create a new content iterator!
   1013 
   1014  if (origStartNode && origEndNode) {
   1015    nsCOMPtr<nsINode> curStartNode, curEndNode;
   1016    uint32_t curStartOffset = 0, curEndOffset = 0;
   1017 
   1018    rv = GetRangeEndPoints(mExtent, getter_AddRefs(curStartNode),
   1019                           &curStartOffset, getter_AddRefs(curEndNode),
   1020                           &curEndOffset);
   1021 
   1022    if (NS_FAILED(rv)) {
   1023      return rv;
   1024    }
   1025 
   1026    if (origStartNode != curStartNode || origEndNode != curEndNode) {
   1027      // The range has changed, so we need to create a new content
   1028      // iterator based on the new range.
   1029      nsCOMPtr<nsIContent> curContent;
   1030      if (mIteratorStatus != IteratorStatus::eDone) {
   1031        // The old iterator is still pointing to something valid,
   1032        // so get its current node so we can restore it after we
   1033        // create the new iterator!
   1034        curContent = mFilteredIter->GetCurrentNode()
   1035                         ? mFilteredIter->GetCurrentNode()->AsContent()
   1036                         : nullptr;
   1037      }
   1038 
   1039      // Create the new iterator.
   1040      rv =
   1041          CreateFilteredContentIterator(mExtent, getter_AddRefs(mFilteredIter));
   1042      if (NS_FAILED(rv)) {
   1043        return rv;
   1044      }
   1045 
   1046      // Now make the new iterator point to the content node
   1047      // the old one was pointing at.
   1048      if (curContent) {
   1049        rv = mFilteredIter->PositionAt(curContent);
   1050        if (NS_FAILED(rv)) {
   1051          mIteratorStatus = IteratorStatus::eDone;
   1052        } else {
   1053          mIteratorStatus = IteratorStatus::eValid;
   1054        }
   1055      }
   1056    }
   1057  }
   1058 
   1059  OffsetEntry* entry = mOffsetTable.DidDeleteSelection();
   1060  if (entry) {
   1061    SetSelection(mOffsetTable.mSelection.StartOffsetInTextInBlock(), 0);
   1062  }
   1063 
   1064  // Now remove any invalid entries from the offset table.
   1065  mOffsetTable.RemoveInvalidElements();
   1066  return NS_OK;
   1067 }
   1068 
   1069 OffsetEntry* TextServicesDocument::OffsetEntryArray::DidDeleteSelection() {
   1070  MOZ_ASSERT(mSelection.IsSet());
   1071 
   1072  // Move the caret to the end of the first valid entry.
   1073  // Start with SelectionStartIndex() since it may still be valid.
   1074  OffsetEntry* entry = nullptr;
   1075  for (size_t i = mSelection.StartIndex() + 1; !entry && i > 0; i--) {
   1076    entry = ElementAt(i - 1).get();
   1077    if (!entry->mIsValid) {
   1078      entry = nullptr;
   1079    } else {
   1080      MOZ_DIAGNOSTIC_ASSERT(i - 1 < Length());
   1081      mSelection.Set(i - 1, entry->EndOffsetInTextInBlock());
   1082    }
   1083  }
   1084 
   1085  // If we still don't have a valid entry, move the caret
   1086  // to the next valid entry after the selection:
   1087  for (size_t i = mSelection.EndIndex(); !entry && i < Length(); i++) {
   1088    entry = ElementAt(i).get();
   1089    if (!entry->mIsValid) {
   1090      entry = nullptr;
   1091    } else {
   1092      MOZ_DIAGNOSTIC_ASSERT(i < Length());
   1093      mSelection.Set(i, entry->mOffsetInTextInBlock);
   1094    }
   1095  }
   1096 
   1097  if (!entry) {
   1098    // Uuughh we have no valid offset entry to place our
   1099    // caret ... just mark the selection invalid.
   1100    mSelection.Reset();
   1101  }
   1102 
   1103  return entry;
   1104 }
   1105 
   1106 nsresult TextServicesDocument::InsertText(const nsAString& aText) {
   1107  if (NS_WARN_IF(!mEditorBase) ||
   1108      NS_WARN_IF(!mOffsetTable.mSelection.IsSet())) {
   1109    return NS_ERROR_FAILURE;
   1110  }
   1111 
   1112  // If the selection is not collapsed, we need to save
   1113  // off the selection offsets so we can restore the
   1114  // selection and delete the selected content after we've
   1115  // inserted the new text. This is necessary to try and
   1116  // retain as much of the original style of the content
   1117  // being deleted.
   1118 
   1119  const bool wasSelectionCollapsed = mOffsetTable.mSelection.IsCollapsed();
   1120  const uint32_t savedSelOffset =
   1121      mOffsetTable.mSelection.StartOffsetInTextInBlock();
   1122  const uint32_t savedSelLength = mOffsetTable.mSelection.LengthInTextInBlock();
   1123 
   1124  if (!wasSelectionCollapsed) {
   1125    // Collapse to the start of the current selection
   1126    // for the insert!
   1127    nsresult rv =
   1128        SetSelection(mOffsetTable.mSelection.StartOffsetInTextInBlock(), 0);
   1129    NS_ENSURE_SUCCESS(rv, rv);
   1130  }
   1131 
   1132  // AutoTransactionBatchExternal grabs mEditorBase, so, we don't need to grab
   1133  // the instance with local variable here.
   1134  OwningNonNull<EditorBase> editorBase = *mEditorBase;
   1135  AutoTransactionBatchExternal treatAsOneTransaction(editorBase);
   1136 
   1137  nsresult rv = editorBase->InsertTextAsAction(aText);
   1138  if (NS_FAILED(rv)) {
   1139    NS_WARNING("InsertTextAsAction() failed");
   1140    return rv;
   1141  }
   1142 
   1143  RefPtr<Selection> selection =
   1144      mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
   1145  rv = mOffsetTable.DidInsertText(selection, aText);
   1146  if (NS_FAILED(rv)) {
   1147    NS_WARNING("TextServicesDocument::OffsetEntry::DidInsertText() failed");
   1148    return rv;
   1149  }
   1150 
   1151  if (!wasSelectionCollapsed) {
   1152    nsresult rv = SetSelection(savedSelOffset, savedSelLength);
   1153    if (NS_FAILED(rv)) {
   1154      return rv;
   1155    }
   1156 
   1157    rv = DeleteSelection();
   1158    if (NS_FAILED(rv)) {
   1159      return rv;
   1160    }
   1161  }
   1162 
   1163  return NS_OK;
   1164 }
   1165 
   1166 nsresult TextServicesDocument::OffsetEntryArray::DidInsertText(
   1167    dom::Selection* aSelection, const nsAString& aInsertedString) {
   1168  MOZ_ASSERT(mSelection.IsSet());
   1169 
   1170  // When you touch this method, please make sure that the entry instance
   1171  // won't be deleted.  If you know it'll be deleted, you should set it to
   1172  // `nullptr`.
   1173  OffsetEntry* entry = ElementAt(mSelection.StartIndex()).get();
   1174  OwningNonNull<Text> const textNodeAtStartEntry = entry->mTextNode;
   1175 
   1176  NS_ASSERTION((entry->mIsValid), "Invalid insertion point!");
   1177 
   1178  if (entry->mOffsetInTextInBlock == mSelection.StartOffsetInTextInBlock()) {
   1179    if (entry->mIsInsertedText) {
   1180      // If the caret is in an inserted text offset entry,
   1181      // we simply insert the text at the end of the entry.
   1182      entry->mLength += aInsertedString.Length();
   1183    } else {
   1184      // Insert an inserted text offset entry before the current
   1185      // entry!
   1186      UniquePtr<OffsetEntry> newInsertedTextEntry =
   1187          MakeUnique<OffsetEntry>(entry->mTextNode, entry->mOffsetInTextInBlock,
   1188                                  aInsertedString.Length());
   1189      newInsertedTextEntry->mIsInsertedText = true;
   1190      newInsertedTextEntry->mOffsetInTextNode = entry->mOffsetInTextNode;
   1191      // XXX(Bug 1631371) Check if this should use a fallible operation as it
   1192      // pretended earlier.
   1193      InsertElementAt(mSelection.StartIndex(), std::move(newInsertedTextEntry));
   1194    }
   1195  } else if (entry->EndOffsetInTextInBlock() ==
   1196             mSelection.EndOffsetInTextInBlock()) {
   1197    // We are inserting text at the end of the current offset entry.
   1198    // Look at the next valid entry in the table. If it's an inserted
   1199    // text entry, add to its length and adjust its node offset. If
   1200    // it isn't, add a new inserted text entry.
   1201    uint32_t nextIndex = mSelection.StartIndex() + 1;
   1202    OffsetEntry* insertedTextEntry = nullptr;
   1203    if (Length() > nextIndex) {
   1204      insertedTextEntry = ElementAt(nextIndex).get();
   1205      if (!insertedTextEntry) {
   1206        return NS_ERROR_FAILURE;
   1207      }
   1208 
   1209      // Check if the entry is a match. If it isn't, set
   1210      // iEntry to zero.
   1211      if (!insertedTextEntry->mIsInsertedText ||
   1212          insertedTextEntry->mOffsetInTextInBlock !=
   1213              mSelection.StartOffsetInTextInBlock()) {
   1214        insertedTextEntry = nullptr;
   1215      }
   1216    }
   1217 
   1218    if (!insertedTextEntry) {
   1219      // We didn't find an inserted text offset entry, so
   1220      // create one.
   1221      UniquePtr<OffsetEntry> newInsertedTextEntry = MakeUnique<OffsetEntry>(
   1222          entry->mTextNode, mSelection.StartOffsetInTextInBlock(), 0);
   1223      newInsertedTextEntry->mOffsetInTextNode = entry->EndOffsetInTextNode();
   1224      newInsertedTextEntry->mIsInsertedText = true;
   1225      // XXX(Bug 1631371) Check if this should use a fallible operation as it
   1226      // pretended earlier.
   1227      insertedTextEntry =
   1228          InsertElementAt(nextIndex, std::move(newInsertedTextEntry))->get();
   1229    }
   1230 
   1231    // We have a valid inserted text offset entry. Update its
   1232    // length, adjust the selection indexes, and make sure the
   1233    // caret is properly placed!
   1234 
   1235    insertedTextEntry->mLength += aInsertedString.Length();
   1236 
   1237    MOZ_DIAGNOSTIC_ASSERT(nextIndex < Length());
   1238    mSelection.SetIndex(nextIndex);
   1239 
   1240    if (!aSelection) {
   1241      return NS_OK;
   1242    }
   1243 
   1244    OwningNonNull<Text> textNode = insertedTextEntry->mTextNode;
   1245    nsresult rv = aSelection->CollapseInLimiter(
   1246        textNode, insertedTextEntry->EndOffsetInTextNode());
   1247    if (NS_FAILED(rv)) {
   1248      NS_WARNING("Selection::CollapseInLimiter() failed");
   1249      return rv;
   1250    }
   1251  } else if (entry->EndOffsetInTextInBlock() >
   1252             mSelection.StartOffsetInTextInBlock()) {
   1253    // We are inserting text into the middle of the current offset entry.
   1254    // split the current entry into two parts, then insert an inserted text
   1255    // entry between them!
   1256    nsresult rv = SplitElementAt(mSelection.StartIndex(),
   1257                                 entry->EndOffsetInTextInBlock() -
   1258                                     mSelection.StartOffsetInTextInBlock());
   1259    if (NS_FAILED(rv)) {
   1260      NS_WARNING(
   1261          "entry->EndOffsetInTextInBlock() - "
   1262          "mSelection.StartOffsetInTextInBlock() was invalid for the "
   1263          "OffsetEntry");
   1264      return rv;
   1265    }
   1266 
   1267    // XXX(Bug 1631371) Check if this should use a fallible operation as it
   1268    // pretended earlier.
   1269    UniquePtr<OffsetEntry>& insertedTextEntry = *InsertElementAt(
   1270        mSelection.StartIndex() + 1,
   1271        MakeUnique<OffsetEntry>(entry->mTextNode,
   1272                                mSelection.StartOffsetInTextInBlock(),
   1273                                aInsertedString.Length()));
   1274    LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
   1275    insertedTextEntry->mIsInsertedText = true;
   1276    insertedTextEntry->mOffsetInTextNode = entry->EndOffsetInTextNode();
   1277    MOZ_DIAGNOSTIC_ASSERT(mSelection.StartIndex() + 1 < Length());
   1278    mSelection.SetIndex(mSelection.StartIndex() + 1);
   1279  }
   1280 
   1281  // We've just finished inserting an inserted text offset entry.
   1282  // update all entries with the same mTextNode pointer that follow
   1283  // it in the table!
   1284 
   1285  for (size_t i = mSelection.StartIndex() + 1; i < Length(); i++) {
   1286    const UniquePtr<OffsetEntry>& entry = ElementAt(i);
   1287    LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
   1288    if (entry->mTextNode != textNodeAtStartEntry) {
   1289      break;
   1290    }
   1291    if (entry->mIsValid) {
   1292      entry->mOffsetInTextNode += aInsertedString.Length();
   1293    }
   1294  }
   1295 
   1296  return NS_OK;
   1297 }
   1298 
   1299 void TextServicesDocument::DidDeleteContent(const nsIContent& aChildContent) {
   1300  if (NS_WARN_IF(!mFilteredIter) || !aChildContent.IsText()) {
   1301    return;
   1302  }
   1303 
   1304  Maybe<size_t> maybeNodeIndex =
   1305      mOffsetTable.FirstIndexOf(*aChildContent.AsText());
   1306  if (maybeNodeIndex.isNothing()) {
   1307    // It's okay if the node isn't in the offset table, the
   1308    // editor could be cleaning house.
   1309    return;
   1310  }
   1311 
   1312  nsINode* node = mFilteredIter->GetCurrentNode();
   1313  if (node && node == &aChildContent &&
   1314      mIteratorStatus != IteratorStatus::eDone) {
   1315    // XXX: This should never really happen because
   1316    // AdjustContentIterator() should have been called prior
   1317    // to the delete to try and position the iterator on the
   1318    // next valid text node in the offset table, and if there
   1319    // wasn't a next, it would've set mIteratorStatus to eIsDone.
   1320 
   1321    NS_ERROR("DeleteNode called for current iterator node.");
   1322  }
   1323 
   1324  for (size_t nodeIndex = *maybeNodeIndex; nodeIndex < mOffsetTable.Length();
   1325       nodeIndex++) {
   1326    const UniquePtr<OffsetEntry>& entry = mOffsetTable[nodeIndex];
   1327    LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
   1328    if (!entry) {
   1329      return;
   1330    }
   1331 
   1332    if (entry->mTextNode == &aChildContent) {
   1333      entry->mIsValid = false;
   1334    }
   1335  }
   1336 }
   1337 
   1338 void TextServicesDocument::DidJoinContents(
   1339    const EditorRawDOMPoint& aJoinedPoint, const nsIContent& aRemovedContent) {
   1340  // Make sure that both nodes are text nodes -- otherwise we don't care.
   1341  if (!aJoinedPoint.IsInTextNode() || !aRemovedContent.IsText()) {
   1342    return;
   1343  }
   1344 
   1345  // Note: The editor merges the contents of the left node into the
   1346  //       contents of the right.
   1347 
   1348  Maybe<size_t> maybeRemovedIndex =
   1349      mOffsetTable.FirstIndexOf(*aRemovedContent.AsText());
   1350  if (maybeRemovedIndex.isNothing()) {
   1351    // It's okay if the node isn't in the offset table, the
   1352    // editor could be cleaning house.
   1353    return;
   1354  }
   1355 
   1356  Maybe<size_t> maybeJoinedIndex =
   1357      mOffsetTable.FirstIndexOf(*aJoinedPoint.ContainerAs<Text>());
   1358  if (maybeJoinedIndex.isNothing()) {
   1359    // It's okay if the node isn't in the offset table, the
   1360    // editor could be cleaning house.
   1361    return;
   1362  }
   1363 
   1364  const size_t removedIndex = *maybeRemovedIndex;
   1365  const size_t joinedIndex = *maybeJoinedIndex;
   1366 
   1367  if (MOZ_UNLIKELY(joinedIndex > removedIndex)) {
   1368    NS_ASSERTION(joinedIndex < removedIndex, "Indexes out of order.");
   1369    return;
   1370  }
   1371  NS_ASSERTION(mOffsetTable[removedIndex]->mOffsetInTextNode == 0,
   1372               "Unexpected offset value for rightIndex.");
   1373 
   1374  // Run through the table and change all entries referring to
   1375  // the removed node so that they now refer to the joined node,
   1376  // and adjust offsets if necessary.
   1377  const uint32_t movedTextDataLength =
   1378      aJoinedPoint.ContainerAs<Text>()->TextDataLength() -
   1379      aJoinedPoint.Offset();
   1380  for (uint32_t i = removedIndex; i < mOffsetTable.Length(); i++) {
   1381    const UniquePtr<OffsetEntry>& entry = mOffsetTable[i];
   1382    LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
   1383    if (entry->mTextNode != aRemovedContent.AsText()) {
   1384      break;
   1385    }
   1386    if (entry->mIsValid) {
   1387      entry->mTextNode = aJoinedPoint.ContainerAs<Text>();
   1388      // The text was moved from aRemovedContent to end of the container of
   1389      // aJoinedPoint.
   1390      entry->mOffsetInTextNode += movedTextDataLength;
   1391    }
   1392  }
   1393 
   1394  // Now check to see if the iterator is pointing to the
   1395  // left node. If it is, make it point to the joined node!
   1396  if (mFilteredIter->GetCurrentNode() == aRemovedContent.AsText()) {
   1397    mFilteredIter->PositionAt(aJoinedPoint.ContainerAs<Text>());
   1398  }
   1399 }
   1400 
   1401 nsresult TextServicesDocument::CreateFilteredContentIterator(
   1402    const AbstractRange* aAbstractRange,
   1403    FilteredContentIterator** aFilteredIter) {
   1404  if (NS_WARN_IF(!aAbstractRange) || NS_WARN_IF(!aFilteredIter)) {
   1405    return NS_ERROR_INVALID_ARG;
   1406  }
   1407 
   1408  *aFilteredIter = nullptr;
   1409 
   1410  UniquePtr<nsComposeTxtSrvFilter> composeFilter;
   1411  switch (mTxtSvcFilterType) {
   1412    case nsIEditorSpellCheck::FILTERTYPE_NORMAL:
   1413      composeFilter = nsComposeTxtSrvFilter::CreateNormalFilter();
   1414      break;
   1415    case nsIEditorSpellCheck::FILTERTYPE_MAIL:
   1416      composeFilter = nsComposeTxtSrvFilter::CreateMailFilter();
   1417      break;
   1418  }
   1419 
   1420  // Create a FilteredContentIterator
   1421  // This class wraps the ContentIterator in order to give itself a chance
   1422  // to filter out certain content nodes
   1423  RefPtr<FilteredContentIterator> filter =
   1424      new FilteredContentIterator(std::move(composeFilter));
   1425  nsresult rv = filter->Init(aAbstractRange);
   1426  if (NS_FAILED(rv)) {
   1427    return rv;
   1428  }
   1429 
   1430  filter.forget(aFilteredIter);
   1431  return NS_OK;
   1432 }
   1433 
   1434 Element* TextServicesDocument::GetDocumentContentRootNode() const {
   1435  if (NS_WARN_IF(!mDocument)) {
   1436    return nullptr;
   1437  }
   1438 
   1439  if (mDocument->IsHTMLOrXHTML()) {
   1440    Element* rootElement = mDocument->GetRootElement();
   1441    if (rootElement && rootElement->IsXULElement()) {
   1442      // HTML documents with root XUL elements should eventually be transitioned
   1443      // to a regular document structure, but for now the content root node will
   1444      // be the document element.
   1445      return mDocument->GetDocumentElement();
   1446    }
   1447    // For HTML documents, the content root node is the body.
   1448    return mDocument->GetBody();
   1449  }
   1450 
   1451  // For non-HTML documents, the content root node will be the document element.
   1452  return mDocument->GetDocumentElement();
   1453 }
   1454 
   1455 already_AddRefed<nsRange> TextServicesDocument::CreateDocumentContentRange() {
   1456  nsCOMPtr<nsINode> node = GetDocumentContentRootNode();
   1457  if (NS_WARN_IF(!node)) {
   1458    return nullptr;
   1459  }
   1460 
   1461  RefPtr<nsRange> range = nsRange::Create(node);
   1462  IgnoredErrorResult ignoredError;
   1463  range->SelectNodeContents(*node, ignoredError);
   1464  NS_WARNING_ASSERTION(!ignoredError.Failed(), "SelectNodeContents() failed");
   1465  return range.forget();
   1466 }
   1467 
   1468 already_AddRefed<nsRange>
   1469 TextServicesDocument::CreateDocumentContentRootToNodeOffsetRange(
   1470    nsINode* aParent, uint32_t aOffset, bool aToStart) {
   1471  if (NS_WARN_IF(!aParent)) {
   1472    return nullptr;
   1473  }
   1474 
   1475  nsCOMPtr<nsINode> bodyNode = GetDocumentContentRootNode();
   1476  if (NS_WARN_IF(!bodyNode)) {
   1477    return nullptr;
   1478  }
   1479 
   1480  nsCOMPtr<nsINode> startNode;
   1481  nsCOMPtr<nsINode> endNode;
   1482  uint32_t startOffset, endOffset;
   1483 
   1484  if (aToStart) {
   1485    // The range should begin at the start of the document
   1486    // and extend up until (aParent, aOffset).
   1487    startNode = bodyNode;
   1488    startOffset = 0;
   1489    endNode = aParent;
   1490    endOffset = aOffset;
   1491  } else {
   1492    // The range should begin at (aParent, aOffset) and
   1493    // extend to the end of the document.
   1494    startNode = aParent;
   1495    startOffset = aOffset;
   1496    endNode = bodyNode;
   1497    endOffset = endNode ? endNode->GetChildCount() : 0;
   1498  }
   1499 
   1500  RefPtr<nsRange> range = nsRange::Create(startNode, startOffset, endNode,
   1501                                          endOffset, IgnoreErrors());
   1502  NS_WARNING_ASSERTION(range,
   1503                       "nsRange::Create() failed to create new valid range");
   1504  return range.forget();
   1505 }
   1506 
   1507 nsresult TextServicesDocument::CreateDocumentContentIterator(
   1508    FilteredContentIterator** aFilteredIter) {
   1509  NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER);
   1510 
   1511  RefPtr<nsRange> range = CreateDocumentContentRange();
   1512  if (NS_WARN_IF(!range)) {
   1513    *aFilteredIter = nullptr;
   1514    return NS_ERROR_FAILURE;
   1515  }
   1516 
   1517  return CreateFilteredContentIterator(range, aFilteredIter);
   1518 }
   1519 
   1520 nsresult TextServicesDocument::AdjustContentIterator() {
   1521  NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
   1522 
   1523  nsCOMPtr<nsINode> node = mFilteredIter->GetCurrentNode();
   1524  NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
   1525 
   1526  Text* prevValidTextNode = nullptr;
   1527  Text* nextValidTextNode = nullptr;
   1528  bool foundEntry = false;
   1529 
   1530  const size_t tableLength = mOffsetTable.Length();
   1531  for (size_t i = 0; i < tableLength && !nextValidTextNode; i++) {
   1532    UniquePtr<OffsetEntry>& entry = mOffsetTable[i];
   1533    LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
   1534    if (entry->mTextNode == node) {
   1535      if (entry->mIsValid) {
   1536        // The iterator is still pointing to something valid!
   1537        // Do nothing!
   1538        return NS_OK;
   1539      }
   1540      // We found an invalid entry that points to
   1541      // the current iterator node. Stop looking for
   1542      // a previous valid node!
   1543      foundEntry = true;
   1544    }
   1545 
   1546    if (entry->mIsValid) {
   1547      if (!foundEntry) {
   1548        prevValidTextNode = entry->mTextNode;
   1549      } else {
   1550        nextValidTextNode = entry->mTextNode;
   1551      }
   1552    }
   1553  }
   1554 
   1555  Text* validTextNode = nullptr;
   1556  if (prevValidTextNode) {
   1557    validTextNode = prevValidTextNode;
   1558  } else if (nextValidTextNode) {
   1559    validTextNode = nextValidTextNode;
   1560  }
   1561 
   1562  if (validTextNode) {
   1563    nsresult rv = mFilteredIter->PositionAt(validTextNode);
   1564    if (NS_FAILED(rv)) {
   1565      mIteratorStatus = IteratorStatus::eDone;
   1566    } else {
   1567      mIteratorStatus = IteratorStatus::eValid;
   1568    }
   1569    return rv;
   1570  }
   1571 
   1572  // If we get here, there aren't any valid entries
   1573  // in the offset table! Try to position the iterator
   1574  // on the next text block first, then previous if
   1575  // one doesn't exist!
   1576 
   1577  if (mNextTextBlock) {
   1578    nsresult rv = mFilteredIter->PositionAt(mNextTextBlock);
   1579    if (NS_FAILED(rv)) {
   1580      mIteratorStatus = IteratorStatus::eDone;
   1581      return rv;
   1582    }
   1583 
   1584    mIteratorStatus = IteratorStatus::eNext;
   1585  } else if (mPrevTextBlock) {
   1586    nsresult rv = mFilteredIter->PositionAt(mPrevTextBlock);
   1587    if (NS_FAILED(rv)) {
   1588      mIteratorStatus = IteratorStatus::eDone;
   1589      return rv;
   1590    }
   1591 
   1592    mIteratorStatus = IteratorStatus::ePrev;
   1593  } else {
   1594    mIteratorStatus = IteratorStatus::eDone;
   1595  }
   1596  return NS_OK;
   1597 }
   1598 
   1599 // static
   1600 bool TextServicesDocument::DidSkip(FilteredContentIterator* aFilteredIter) {
   1601  return aFilteredIter && aFilteredIter->DidSkip();
   1602 }
   1603 
   1604 // static
   1605 void TextServicesDocument::ClearDidSkip(
   1606    FilteredContentIterator* aFilteredIter) {
   1607  // Clear filter's skip flag
   1608  if (aFilteredIter) {
   1609    aFilteredIter->ClearDidSkip();
   1610  }
   1611 }
   1612 
   1613 // static
   1614 bool TextServicesDocument::HasSameBlockNodeParent(Text& aTextNode1,
   1615                                                  Text& aTextNode2) {
   1616  // XXX How about the case that both text nodes are orphan nodes?
   1617  if (aTextNode1.GetParent() == aTextNode2.GetParent()) {
   1618    return true;
   1619  }
   1620 
   1621  // I think that spellcheck should be available only in editable nodes.
   1622  // So, we also need to check whether they are in same editing host.
   1623  const Element* editableBlockElementOrInlineEditingHost1 =
   1624      HTMLEditUtils::GetAncestorElement(
   1625          aTextNode1,
   1626          HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost,
   1627          BlockInlineCheck::UseHTMLDefaultStyle);
   1628  const Element* editableBlockElementOrInlineEditingHost2 =
   1629      HTMLEditUtils::GetAncestorElement(
   1630          aTextNode2,
   1631          HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost,
   1632          BlockInlineCheck::UseHTMLDefaultStyle);
   1633  return editableBlockElementOrInlineEditingHost1 &&
   1634         editableBlockElementOrInlineEditingHost1 ==
   1635             editableBlockElementOrInlineEditingHost2;
   1636 }
   1637 
   1638 Result<EditorRawDOMRangeInTexts, nsresult>
   1639 TextServicesDocument::OffsetEntryArray::WillSetSelection(
   1640    uint32_t aOffsetInTextInBlock, uint32_t aLength) {
   1641  // Find start of selection in node offset terms:
   1642  EditorRawDOMPointInText newStart;
   1643  for (size_t i = 0; !newStart.IsSet() && i < Length(); i++) {
   1644    const UniquePtr<OffsetEntry>& entry = ElementAt(i);
   1645    LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
   1646    if (entry->mIsValid) {
   1647      if (entry->mIsInsertedText) {
   1648        // Caret can only be placed at the end of an
   1649        // inserted text offset entry, if the offsets
   1650        // match exactly!
   1651        if (entry->mOffsetInTextInBlock == aOffsetInTextInBlock) {
   1652          newStart.Set(entry->mTextNode, entry->EndOffsetInTextNode());
   1653        }
   1654      } else if (aOffsetInTextInBlock >= entry->mOffsetInTextInBlock) {
   1655        bool foundEntry = false;
   1656        if (aOffsetInTextInBlock < entry->EndOffsetInTextInBlock()) {
   1657          foundEntry = true;
   1658        } else if (aOffsetInTextInBlock == entry->EndOffsetInTextInBlock()) {
   1659          // Peek after this entry to see if we have any
   1660          // inserted text entries belonging to the same
   1661          // entry->mTextNode. If so, we have to place the selection
   1662          // after it!
   1663          if (i + 1 < Length()) {
   1664            const UniquePtr<OffsetEntry>& nextEntry = ElementAt(i + 1);
   1665            LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
   1666            if (!nextEntry->mIsValid ||
   1667                nextEntry->mOffsetInTextInBlock != aOffsetInTextInBlock) {
   1668              // Next offset entry isn't an exact match, so we'll
   1669              // just use the current entry.
   1670              foundEntry = true;
   1671            }
   1672          }
   1673        }
   1674 
   1675        if (foundEntry) {
   1676          newStart.Set(entry->mTextNode, entry->mOffsetInTextNode +
   1677                                             aOffsetInTextInBlock -
   1678                                             entry->mOffsetInTextInBlock);
   1679        }
   1680      }
   1681 
   1682      if (newStart.IsSet()) {
   1683        MOZ_DIAGNOSTIC_ASSERT(i < Length());
   1684        mSelection.Set(i, aOffsetInTextInBlock);
   1685      }
   1686    }
   1687  }
   1688 
   1689  if (NS_WARN_IF(!newStart.IsSet())) {
   1690    return Err(NS_ERROR_FAILURE);
   1691  }
   1692 
   1693  if (!aLength) {
   1694    mSelection.CollapseToStart();
   1695    return EditorRawDOMRangeInTexts(newStart);
   1696  }
   1697 
   1698  // Find the end of the selection in node offset terms:
   1699  EditorRawDOMPointInText newEnd;
   1700  const uint32_t endOffset = aOffsetInTextInBlock + aLength;
   1701  for (uint32_t i = Length(); !newEnd.IsSet() && i > 0; i--) {
   1702    const UniquePtr<OffsetEntry>& entry = ElementAt(i - 1);
   1703    LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
   1704    if (entry->mIsValid) {
   1705      if (entry->mIsInsertedText) {
   1706        if (entry->mOffsetInTextInBlock ==
   1707            (newEnd.IsSet() ? newEnd.Offset() : 0)) {
   1708          // If the selection ends on an inserted text offset entry,
   1709          // the selection includes the entire entry!
   1710          newEnd.Set(entry->mTextNode, entry->EndOffsetInTextNode());
   1711        }
   1712      } else if (entry->OffsetInTextInBlockIsInRangeOrEndOffset(endOffset)) {
   1713        newEnd.Set(entry->mTextNode, entry->mOffsetInTextNode + endOffset -
   1714                                         entry->mOffsetInTextInBlock);
   1715      }
   1716 
   1717      if (newEnd.IsSet()) {
   1718        MOZ_DIAGNOSTIC_ASSERT(mSelection.StartIndex() < Length());
   1719        MOZ_DIAGNOSTIC_ASSERT(i - 1 < Length());
   1720        mSelection.Set(mSelection.StartIndex(), i - 1,
   1721                       mSelection.StartOffsetInTextInBlock(), endOffset);
   1722      }
   1723    }
   1724  }
   1725 
   1726  return newEnd.IsSet() ? EditorRawDOMRangeInTexts(newStart, newEnd)
   1727                        : EditorRawDOMRangeInTexts(newStart);
   1728 }
   1729 
   1730 nsresult TextServicesDocument::SetSelectionInternal(
   1731    uint32_t aOffsetInTextInBlock, uint32_t aLength, bool aDoUpdate) {
   1732  if (NS_WARN_IF(!mSelCon)) {
   1733    return NS_ERROR_INVALID_ARG;
   1734  }
   1735 
   1736  Result<EditorRawDOMRangeInTexts, nsresult> newSelectionRange =
   1737      mOffsetTable.WillSetSelection(aOffsetInTextInBlock, aLength);
   1738  if (newSelectionRange.isErr()) {
   1739    NS_WARNING(
   1740        "TextServicesDocument::OffsetEntryArray::WillSetSelection() failed");
   1741    return newSelectionRange.unwrapErr();
   1742  }
   1743 
   1744  if (!aDoUpdate) {
   1745    return NS_OK;
   1746  }
   1747 
   1748  // XXX: If we ever get a SetSelection() method in nsIEditor, we should
   1749  //      use it.
   1750  RefPtr<Selection> selection =
   1751      mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
   1752  if (NS_WARN_IF(!selection)) {
   1753    return NS_ERROR_FAILURE;
   1754  }
   1755 
   1756  if (newSelectionRange.inspect().Collapsed()) {
   1757    nsresult rv =
   1758        selection->CollapseInLimiter(newSelectionRange.inspect().StartRef());
   1759    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   1760                         "Selection::CollapseInLimiter() failed");
   1761    return rv;
   1762  }
   1763 
   1764  ErrorResult error;
   1765  selection->SetStartAndEndInLimiter(newSelectionRange.inspect().StartRef(),
   1766                                     newSelectionRange.inspect().EndRef(),
   1767                                     error);
   1768  NS_WARNING_ASSERTION(!error.Failed(),
   1769                       "Selection::SetStartAndEndInLimiter() failed");
   1770  return error.StealNSResult();
   1771 }
   1772 
   1773 nsresult TextServicesDocument::GetSelection(BlockSelectionStatus* aSelStatus,
   1774                                            uint32_t* aSelOffset,
   1775                                            uint32_t* aSelLength) {
   1776  NS_ENSURE_TRUE(aSelStatus && aSelOffset && aSelLength, NS_ERROR_NULL_POINTER);
   1777 
   1778  *aSelStatus = BlockSelectionStatus::eBlockNotFound;
   1779  *aSelOffset = UINT32_MAX;
   1780  *aSelLength = UINT32_MAX;
   1781 
   1782  NS_ENSURE_TRUE(mDocument && mSelCon, NS_ERROR_FAILURE);
   1783 
   1784  if (mIteratorStatus == IteratorStatus::eDone) {
   1785    return NS_OK;
   1786  }
   1787 
   1788  RefPtr<Selection> selection =
   1789      mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
   1790  NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
   1791 
   1792  if (selection->IsCollapsed()) {
   1793    return GetCollapsedSelection(aSelStatus, aSelOffset, aSelLength);
   1794  }
   1795 
   1796  return GetUncollapsedSelection(aSelStatus, aSelOffset, aSelLength);
   1797 }
   1798 
   1799 nsresult TextServicesDocument::GetCollapsedSelection(
   1800    BlockSelectionStatus* aSelStatus, uint32_t* aSelOffset,
   1801    uint32_t* aSelLength) {
   1802  RefPtr<Selection> selection =
   1803      mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
   1804  NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
   1805 
   1806  // The calling function should have done the GetIsCollapsed()
   1807  // check already. Just assume it's collapsed!
   1808  *aSelStatus = BlockSelectionStatus::eBlockOutside;
   1809  *aSelOffset = *aSelLength = UINT32_MAX;
   1810 
   1811  const uint32_t tableCount = mOffsetTable.Length();
   1812  if (!tableCount) {
   1813    return NS_OK;
   1814  }
   1815 
   1816  // Get pointers to the first and last offset entries
   1817  // in the table.
   1818 
   1819  UniquePtr<OffsetEntry>& eStart = mOffsetTable[0];
   1820  UniquePtr<OffsetEntry>& eEnd =
   1821      tableCount > 1 ? mOffsetTable[tableCount - 1] : eStart;
   1822  LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
   1823 
   1824  const uint32_t eStartOffset = eStart->mOffsetInTextNode;
   1825  const uint32_t eEndOffset = eEnd->EndOffsetInTextNode();
   1826 
   1827  RefPtr<const nsRange> range = selection->GetRangeAt(0);
   1828  NS_ENSURE_STATE(range);
   1829 
   1830  nsCOMPtr<nsINode> parent = range->GetStartContainer();
   1831  MOZ_ASSERT(parent);
   1832 
   1833  uint32_t offset = range->StartOffset();
   1834 
   1835  const Maybe<int32_t> e1s1 = nsContentUtils::ComparePointsWithIndices(
   1836      eStart->mTextNode, eStartOffset, parent, offset);
   1837  const Maybe<int32_t> e2s1 = nsContentUtils::ComparePointsWithIndices(
   1838      eEnd->mTextNode, eEndOffset, parent, offset);
   1839 
   1840  if (MOZ_UNLIKELY(NS_WARN_IF(!e1s1) || NS_WARN_IF(!e2s1))) {
   1841    return NS_ERROR_FAILURE;
   1842  }
   1843 
   1844  if (*e1s1 > 0 || *e2s1 < 0) {
   1845    // We're done if the caret is outside the current text block.
   1846    return NS_OK;
   1847  }
   1848 
   1849  if (parent->IsText()) {
   1850    // Good news, the caret is in a text node. Look
   1851    // through the offset table for the entry that
   1852    // matches its parent and offset.
   1853 
   1854    for (uint32_t i = 0; i < tableCount; i++) {
   1855      const UniquePtr<OffsetEntry>& entry = mOffsetTable[i];
   1856      LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
   1857      if (entry->mTextNode == parent->AsText() &&
   1858          entry->OffsetInTextNodeIsInRangeOrEndOffset(offset)) {
   1859        *aSelStatus = BlockSelectionStatus::eBlockContains;
   1860        *aSelOffset =
   1861            entry->mOffsetInTextInBlock + (offset - entry->mOffsetInTextNode);
   1862        *aSelLength = 0;
   1863        return NS_OK;
   1864      }
   1865    }
   1866 
   1867    // If we get here, we didn't find a text node entry
   1868    // in our offset table that matched.
   1869    return NS_ERROR_FAILURE;
   1870  }
   1871 
   1872  // The caret is in our text block, but it's positioned in some
   1873  // non-text node (ex. <b>). Create a range based on the start
   1874  // and end of the text block, then create an iterator based on
   1875  // this range, with its initial position set to the closest
   1876  // child of this non-text node. Then look for the closest text
   1877  // node.
   1878 
   1879  range = nsRange::Create(eStart->mTextNode, eStartOffset, eEnd->mTextNode,
   1880                          eEndOffset, IgnoreErrors());
   1881  if (NS_WARN_IF(!range)) {
   1882    return NS_ERROR_FAILURE;
   1883  }
   1884 
   1885  RefPtr<FilteredContentIterator> filteredIter;
   1886  nsresult rv =
   1887      CreateFilteredContentIterator(range, getter_AddRefs(filteredIter));
   1888  NS_ENSURE_SUCCESS(rv, rv);
   1889 
   1890  nsIContent* saveNode;
   1891  if (parent->HasChildren()) {
   1892    // XXX: We need to make sure that all of parent's
   1893    //      children are in the text block.
   1894 
   1895    // If the parent has children, position the iterator
   1896    // on the child that is to the left of the offset.
   1897 
   1898    nsIContent* content = range->GetChildAtStartOffset();
   1899    if (content && parent->GetFirstChild() != content) {
   1900      content = content->GetPreviousSibling();
   1901    }
   1902    NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
   1903 
   1904    nsresult rv = filteredIter->PositionAt(content);
   1905    NS_ENSURE_SUCCESS(rv, rv);
   1906 
   1907    saveNode = content;
   1908  } else {
   1909    // The parent has no children, so position the iterator
   1910    // on the parent.
   1911    NS_ENSURE_TRUE(parent->IsContent(), NS_ERROR_FAILURE);
   1912    nsCOMPtr<nsIContent> content = parent->AsContent();
   1913 
   1914    nsresult rv = filteredIter->PositionAt(content);
   1915    NS_ENSURE_SUCCESS(rv, rv);
   1916 
   1917    saveNode = content;
   1918  }
   1919 
   1920  // Now iterate to the left, towards the beginning of
   1921  // the text block, to find the first text node you
   1922  // come across.
   1923 
   1924  Text* textNode = nullptr;
   1925  for (; !filteredIter->IsDone(); filteredIter->Prev()) {
   1926    nsINode* current = filteredIter->GetCurrentNode();
   1927    if (current->IsText()) {
   1928      textNode = current->AsText();
   1929      break;
   1930    }
   1931  }
   1932 
   1933  if (textNode) {
   1934    // We found a node, now set the offset to the end
   1935    // of the text node.
   1936    offset = textNode->TextLength();
   1937  } else {
   1938    // We should never really get here, but I'm paranoid.
   1939 
   1940    // We didn't find a text node above, so iterate to
   1941    // the right, towards the end of the text block, looking
   1942    // for a text node.
   1943 
   1944    nsresult rv = filteredIter->PositionAt(saveNode);
   1945    NS_ENSURE_SUCCESS(rv, rv);
   1946 
   1947    textNode = nullptr;
   1948    for (; !filteredIter->IsDone(); filteredIter->Next()) {
   1949      nsINode* current = filteredIter->GetCurrentNode();
   1950      if (current->IsText()) {
   1951        textNode = current->AsText();
   1952        break;
   1953      }
   1954    }
   1955    NS_ENSURE_TRUE(textNode, NS_ERROR_FAILURE);
   1956 
   1957    // We found a text node, so set the offset to
   1958    // the beginning of the node.
   1959    offset = 0;
   1960  }
   1961 
   1962  for (size_t i = 0; i < tableCount; i++) {
   1963    const UniquePtr<OffsetEntry>& entry = mOffsetTable[i];
   1964    LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
   1965    if (entry->mTextNode == textNode &&
   1966        entry->OffsetInTextNodeIsInRangeOrEndOffset(offset)) {
   1967      *aSelStatus = BlockSelectionStatus::eBlockContains;
   1968      *aSelOffset =
   1969          entry->mOffsetInTextInBlock + (offset - entry->mOffsetInTextNode);
   1970      *aSelLength = 0;
   1971 
   1972      // Now move the caret so that it is actually in the text node.
   1973      // We do this to keep things in sync.
   1974      //
   1975      // In most cases, the user shouldn't see any movement in the caret
   1976      // on screen.
   1977      return SetSelectionInternal(*aSelOffset, *aSelLength, true);
   1978    }
   1979  }
   1980 
   1981  return NS_ERROR_FAILURE;
   1982 }
   1983 
   1984 nsresult TextServicesDocument::GetUncollapsedSelection(
   1985    BlockSelectionStatus* aSelStatus, uint32_t* aSelOffset,
   1986    uint32_t* aSelLength) {
   1987  RefPtr<const nsRange> range;
   1988  RefPtr<Selection> selection =
   1989      mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
   1990  NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
   1991 
   1992  // It is assumed that the calling function has made sure that the
   1993  // selection is not collapsed, and that the input params to this
   1994  // method are initialized to some defaults.
   1995 
   1996  nsCOMPtr<nsINode> startContainer, endContainer;
   1997 
   1998  const size_t tableCount = mOffsetTable.Length();
   1999 
   2000  // Get pointers to the first and last offset entries
   2001  // in the table.
   2002 
   2003  UniquePtr<OffsetEntry>& eStart = mOffsetTable[0];
   2004  UniquePtr<OffsetEntry>& eEnd =
   2005      tableCount > 1 ? mOffsetTable[tableCount - 1] : eStart;
   2006  LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
   2007 
   2008  const uint32_t eStartOffset = eStart->mOffsetInTextNode;
   2009  const uint32_t eEndOffset = eEnd->EndOffsetInTextNode();
   2010 
   2011  const uint32_t rangeCount = selection->RangeCount();
   2012  MOZ_ASSERT(rangeCount);
   2013 
   2014  // Find the first range in the selection that intersects
   2015  // the current text block.
   2016  Maybe<int32_t> e1s2;
   2017  Maybe<int32_t> e2s1;
   2018  uint32_t startOffset, endOffset;
   2019  for (const uint32_t i : IntegerRange(rangeCount)) {
   2020    MOZ_ASSERT(selection->RangeCount() == rangeCount);
   2021    range = selection->GetRangeAt(i);
   2022    if (MOZ_UNLIKELY(NS_WARN_IF(!range))) {
   2023      return NS_ERROR_FAILURE;
   2024    }
   2025 
   2026    nsresult rv =
   2027        GetRangeEndPoints(range, getter_AddRefs(startContainer), &startOffset,
   2028                          getter_AddRefs(endContainer), &endOffset);
   2029 
   2030    NS_ENSURE_SUCCESS(rv, rv);
   2031 
   2032    e1s2 = nsContentUtils::ComparePointsWithIndices(
   2033        eStart->mTextNode, eStartOffset, endContainer, endOffset);
   2034    if (NS_WARN_IF(!e1s2)) {
   2035      return NS_ERROR_FAILURE;
   2036    }
   2037 
   2038    e2s1 = nsContentUtils::ComparePointsWithIndices(
   2039        eEnd->mTextNode, eEndOffset, startContainer, startOffset);
   2040    if (NS_WARN_IF(!e2s1)) {
   2041      return NS_ERROR_FAILURE;
   2042    }
   2043 
   2044    // Break out of the loop if the text block intersects the current range.
   2045 
   2046    if (*e1s2 <= 0 && *e2s1 >= 0) {
   2047      break;
   2048    }
   2049  }
   2050 
   2051  // We're done if we didn't find an intersecting range.
   2052 
   2053  if (rangeCount < 1 || *e1s2 > 0 || *e2s1 < 0) {
   2054    *aSelStatus = BlockSelectionStatus::eBlockOutside;
   2055    *aSelOffset = *aSelLength = UINT32_MAX;
   2056    return NS_OK;
   2057  }
   2058 
   2059  // Now that we have an intersecting range, find out more info:
   2060  const Maybe<int32_t> e1s1 = nsContentUtils::ComparePointsWithIndices(
   2061      eStart->mTextNode, eStartOffset, startContainer, startOffset);
   2062  if (NS_WARN_IF(!e1s1)) {
   2063    return NS_ERROR_FAILURE;
   2064  }
   2065 
   2066  const Maybe<int32_t> e2s2 = nsContentUtils::ComparePointsWithIndices(
   2067      eEnd->mTextNode, eEndOffset, endContainer, endOffset);
   2068  if (NS_WARN_IF(!e2s2)) {
   2069    return NS_ERROR_FAILURE;
   2070  }
   2071 
   2072  if (rangeCount > 1) {
   2073    // There are multiple selection ranges, we only deal
   2074    // with the first one that intersects the current,
   2075    // text block, so mark this a as a partial.
   2076    *aSelStatus = BlockSelectionStatus::eBlockPartial;
   2077  } else if (*e1s1 > 0 && *e2s2 < 0) {
   2078    // The range extends beyond the start and
   2079    // end of the current text block.
   2080    *aSelStatus = BlockSelectionStatus::eBlockInside;
   2081  } else if (*e1s1 <= 0 && *e2s2 >= 0) {
   2082    // The current text block contains the entire
   2083    // range.
   2084    *aSelStatus = BlockSelectionStatus::eBlockContains;
   2085  } else {
   2086    // The range partially intersects the block.
   2087    *aSelStatus = BlockSelectionStatus::eBlockPartial;
   2088  }
   2089 
   2090  // Now create a range based on the intersection of the
   2091  // text block and range:
   2092 
   2093  nsCOMPtr<nsINode> p1, p2;
   2094  uint32_t o1, o2;
   2095 
   2096  // The start of the range will be the rightmost
   2097  // start node.
   2098 
   2099  if (*e1s1 >= 0) {
   2100    p1 = eStart->mTextNode;
   2101    o1 = eStartOffset;
   2102  } else {
   2103    p1 = startContainer;
   2104    o1 = startOffset;
   2105  }
   2106 
   2107  // The end of the range will be the leftmost
   2108  // end node.
   2109 
   2110  if (*e2s2 <= 0) {
   2111    p2 = eEnd->mTextNode;
   2112    o2 = eEndOffset;
   2113  } else {
   2114    p2 = endContainer;
   2115    o2 = endOffset;
   2116  }
   2117 
   2118  range = nsRange::Create(p1, o1, p2, o2, IgnoreErrors());
   2119  if (NS_WARN_IF(!range)) {
   2120    return NS_ERROR_FAILURE;
   2121  }
   2122 
   2123  // Now iterate over this range to figure out the selection's
   2124  // block offset and length.
   2125 
   2126  RefPtr<FilteredContentIterator> filteredIter;
   2127  nsresult rv =
   2128      CreateFilteredContentIterator(range, getter_AddRefs(filteredIter));
   2129  NS_ENSURE_SUCCESS(rv, rv);
   2130 
   2131  // Find the first text node in the range.
   2132  nsCOMPtr<nsIContent> content;
   2133  filteredIter->First();
   2134  if (!p1->IsText()) {
   2135    bool found = false;
   2136    for (; !filteredIter->IsDone(); filteredIter->Next()) {
   2137      nsINode* node = filteredIter->GetCurrentNode();
   2138      if (node->IsText()) {
   2139        p1 = node->AsText();
   2140        o1 = 0;
   2141        found = true;
   2142        break;
   2143      }
   2144    }
   2145    NS_ENSURE_TRUE(found, NS_ERROR_FAILURE);
   2146  }
   2147 
   2148  // Find the last text node in the range.
   2149  filteredIter->Last();
   2150  if (!p2->IsText()) {
   2151    bool found = false;
   2152    for (; !filteredIter->IsDone(); filteredIter->Prev()) {
   2153      nsINode* node = filteredIter->GetCurrentNode();
   2154      if (node->IsText()) {
   2155        p2 = node->AsText();
   2156        o2 = p2->AsText()->Length();
   2157        found = true;
   2158 
   2159        break;
   2160      }
   2161    }
   2162    NS_ENSURE_TRUE(found, NS_ERROR_FAILURE);
   2163  }
   2164 
   2165  bool found = false;
   2166  *aSelLength = 0;
   2167 
   2168  for (size_t i = 0; i < tableCount; i++) {
   2169    const UniquePtr<OffsetEntry>& entry = mOffsetTable[i];
   2170    LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
   2171    if (!found) {
   2172      if (entry->mTextNode == p1.get() &&
   2173          entry->OffsetInTextNodeIsInRangeOrEndOffset(o1)) {
   2174        *aSelOffset =
   2175            entry->mOffsetInTextInBlock + (o1 - entry->mOffsetInTextNode);
   2176        if (p1 == p2 && entry->OffsetInTextNodeIsInRangeOrEndOffset(o2)) {
   2177          // The start and end of the range are in the same offset
   2178          // entry. Calculate the length of the range then we're done.
   2179          *aSelLength = o2 - o1;
   2180          break;
   2181        }
   2182        // Add the length of the sub string in this offset entry
   2183        // that follows the start of the range.
   2184        *aSelLength = entry->EndOffsetInTextNode() - o1;
   2185        found = true;
   2186      }
   2187    } else {  // Found.
   2188      if (entry->mTextNode == p2.get() &&
   2189          entry->OffsetInTextNodeIsInRangeOrEndOffset(o2)) {
   2190        // We found the end of the range. Calculate the length of the
   2191        // sub string that is before the end of the range, then we're done.
   2192        *aSelLength += o2 - entry->mOffsetInTextNode;
   2193        break;
   2194      }
   2195      // The entire entry must be in the range.
   2196      *aSelLength += entry->mLength;
   2197    }
   2198  }
   2199 
   2200  return NS_OK;
   2201 }
   2202 
   2203 // static
   2204 nsresult TextServicesDocument::GetRangeEndPoints(
   2205    const AbstractRange* aAbstractRange, nsINode** aStartContainer,
   2206    uint32_t* aStartOffset, nsINode** aEndContainer, uint32_t* aEndOffset) {
   2207  if (NS_WARN_IF(!aAbstractRange) || NS_WARN_IF(!aStartContainer) ||
   2208      NS_WARN_IF(!aEndContainer) || NS_WARN_IF(!aEndOffset)) {
   2209    return NS_ERROR_INVALID_ARG;
   2210  }
   2211 
   2212  nsCOMPtr<nsINode> startContainer = aAbstractRange->GetStartContainer();
   2213  if (NS_WARN_IF(!startContainer)) {
   2214    return NS_ERROR_FAILURE;
   2215  }
   2216  nsCOMPtr<nsINode> endContainer = aAbstractRange->GetEndContainer();
   2217  if (NS_WARN_IF(!endContainer)) {
   2218    return NS_ERROR_FAILURE;
   2219  }
   2220 
   2221  startContainer.forget(aStartContainer);
   2222  endContainer.forget(aEndContainer);
   2223  *aStartOffset = aAbstractRange->StartOffset();
   2224  *aEndOffset = aAbstractRange->EndOffset();
   2225  return NS_OK;
   2226 }
   2227 
   2228 // static
   2229 nsresult TextServicesDocument::FirstTextNode(
   2230    FilteredContentIterator* aFilteredIter, IteratorStatus* aIteratorStatus) {
   2231  if (aIteratorStatus) {
   2232    *aIteratorStatus = IteratorStatus::eDone;
   2233  }
   2234 
   2235  for (aFilteredIter->First(); !aFilteredIter->IsDone();
   2236       aFilteredIter->Next()) {
   2237    if (aFilteredIter->GetCurrentNode()->NodeType() == nsINode::TEXT_NODE) {
   2238      if (aIteratorStatus) {
   2239        *aIteratorStatus = IteratorStatus::eValid;
   2240      }
   2241      break;
   2242    }
   2243  }
   2244 
   2245  return NS_OK;
   2246 }
   2247 
   2248 // static
   2249 nsresult TextServicesDocument::LastTextNode(
   2250    FilteredContentIterator* aFilteredIter, IteratorStatus* aIteratorStatus) {
   2251  if (aIteratorStatus) {
   2252    *aIteratorStatus = IteratorStatus::eDone;
   2253  }
   2254 
   2255  for (aFilteredIter->Last(); !aFilteredIter->IsDone(); aFilteredIter->Prev()) {
   2256    if (aFilteredIter->GetCurrentNode()->NodeType() == nsINode::TEXT_NODE) {
   2257      if (aIteratorStatus) {
   2258        *aIteratorStatus = IteratorStatus::eValid;
   2259      }
   2260      break;
   2261    }
   2262  }
   2263 
   2264  return NS_OK;
   2265 }
   2266 
   2267 // static
   2268 nsresult TextServicesDocument::FirstTextNodeInCurrentBlock(
   2269    FilteredContentIterator* aFilteredIter) {
   2270  NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER);
   2271 
   2272  ClearDidSkip(aFilteredIter);
   2273 
   2274  // Walk backwards over adjacent text nodes until
   2275  // we hit a block boundary:
   2276  RefPtr<Text> lastTextNode;
   2277  while (!aFilteredIter->IsDone()) {
   2278    nsCOMPtr<nsIContent> content =
   2279        aFilteredIter->GetCurrentNode()->IsContent()
   2280            ? aFilteredIter->GetCurrentNode()->AsContent()
   2281            : nullptr;
   2282    // We don't observe layout updates, therefore, we should consider whether
   2283    // block or inline only with the default definition of the element.
   2284    if (lastTextNode && content &&
   2285        (HTMLEditUtils::IsBlockElement(*content,
   2286                                       BlockInlineCheck::UseHTMLDefaultStyle) ||
   2287         content->IsHTMLElement(nsGkAtoms::br))) {
   2288      break;
   2289    }
   2290    if (content && content->IsText()) {
   2291      if (lastTextNode && !TextServicesDocument::HasSameBlockNodeParent(
   2292                              *content->AsText(), *lastTextNode)) {
   2293        // We're done, the current text node is in a
   2294        // different block.
   2295        break;
   2296      }
   2297      lastTextNode = content->AsText();
   2298    }
   2299 
   2300    aFilteredIter->Prev();
   2301 
   2302    if (DidSkip(aFilteredIter)) {
   2303      break;
   2304    }
   2305  }
   2306 
   2307  if (lastTextNode) {
   2308    aFilteredIter->PositionAt(lastTextNode);
   2309  }
   2310 
   2311  // XXX: What should we return if last is null?
   2312 
   2313  return NS_OK;
   2314 }
   2315 
   2316 // static
   2317 nsresult TextServicesDocument::FirstTextNodeInPrevBlock(
   2318    FilteredContentIterator* aFilteredIter) {
   2319  NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER);
   2320 
   2321  // XXX: What if mFilteredIter is not currently on a text node?
   2322 
   2323  // Make sure mFilteredIter is pointing to the first text node in the
   2324  // current block:
   2325 
   2326  nsresult rv = FirstTextNodeInCurrentBlock(aFilteredIter);
   2327 
   2328  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
   2329 
   2330  // Point mFilteredIter to the first node before the first text node:
   2331 
   2332  aFilteredIter->Prev();
   2333 
   2334  if (aFilteredIter->IsDone()) {
   2335    return NS_ERROR_FAILURE;
   2336  }
   2337 
   2338  // Now find the first text node of the next block:
   2339 
   2340  return FirstTextNodeInCurrentBlock(aFilteredIter);
   2341 }
   2342 
   2343 // static
   2344 nsresult TextServicesDocument::FirstTextNodeInNextBlock(
   2345    FilteredContentIterator* aFilteredIter) {
   2346  bool crossedBlockBoundary = false;
   2347 
   2348  NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER);
   2349 
   2350  ClearDidSkip(aFilteredIter);
   2351 
   2352  RefPtr<Text> previousTextNode;
   2353  while (!aFilteredIter->IsDone()) {
   2354    if (nsCOMPtr<nsIContent> content =
   2355            aFilteredIter->GetCurrentNode()->IsContent()
   2356                ? aFilteredIter->GetCurrentNode()->AsContent()
   2357                : nullptr) {
   2358      if (content->IsText()) {
   2359        if (crossedBlockBoundary ||
   2360            (previousTextNode && !TextServicesDocument::HasSameBlockNodeParent(
   2361                                     *previousTextNode, *content->AsText()))) {
   2362          break;
   2363        }
   2364        previousTextNode = content->AsText();
   2365      }
   2366      // We don't observe layout updates, therefore, we should consider whether
   2367      // block or inline only with the default definition of the element.
   2368      else if (!crossedBlockBoundary &&
   2369               (HTMLEditUtils::IsBlockElement(
   2370                    *content, BlockInlineCheck::UseHTMLDefaultStyle) ||
   2371                content->IsHTMLElement(nsGkAtoms::br))) {
   2372        crossedBlockBoundary = true;
   2373      }
   2374    }
   2375 
   2376    aFilteredIter->Next();
   2377 
   2378    if (!crossedBlockBoundary && DidSkip(aFilteredIter)) {
   2379      crossedBlockBoundary = true;
   2380    }
   2381  }
   2382 
   2383  return NS_OK;
   2384 }
   2385 
   2386 nsresult TextServicesDocument::GetFirstTextNodeInPrevBlock(
   2387    nsIContent** aContent) {
   2388  NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER);
   2389 
   2390  *aContent = 0;
   2391 
   2392  // Save the iterator's current content node so we can restore
   2393  // it when we are done:
   2394 
   2395  nsINode* node = mFilteredIter->GetCurrentNode();
   2396 
   2397  nsresult rv = FirstTextNodeInPrevBlock(mFilteredIter);
   2398 
   2399  if (NS_FAILED(rv)) {
   2400    // Try to restore the iterator before returning.
   2401    mFilteredIter->PositionAt(node);
   2402    return rv;
   2403  }
   2404 
   2405  if (!mFilteredIter->IsDone()) {
   2406    nsCOMPtr<nsIContent> current =
   2407        mFilteredIter->GetCurrentNode()->IsContent()
   2408            ? mFilteredIter->GetCurrentNode()->AsContent()
   2409            : nullptr;
   2410    current.forget(aContent);
   2411  }
   2412 
   2413  // Restore the iterator:
   2414 
   2415  return mFilteredIter->PositionAt(node);
   2416 }
   2417 
   2418 nsresult TextServicesDocument::GetFirstTextNodeInNextBlock(
   2419    nsIContent** aContent) {
   2420  NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER);
   2421 
   2422  *aContent = 0;
   2423 
   2424  // Save the iterator's current content node so we can restore
   2425  // it when we are done:
   2426 
   2427  nsINode* node = mFilteredIter->GetCurrentNode();
   2428 
   2429  nsresult rv = FirstTextNodeInNextBlock(mFilteredIter);
   2430 
   2431  if (NS_FAILED(rv)) {
   2432    // Try to restore the iterator before returning.
   2433    mFilteredIter->PositionAt(node);
   2434    return rv;
   2435  }
   2436 
   2437  if (!mFilteredIter->IsDone()) {
   2438    nsCOMPtr<nsIContent> current =
   2439        mFilteredIter->GetCurrentNode()->IsContent()
   2440            ? mFilteredIter->GetCurrentNode()->AsContent()
   2441            : nullptr;
   2442    current.forget(aContent);
   2443  }
   2444 
   2445  // Restore the iterator:
   2446  return mFilteredIter->PositionAt(node);
   2447 }
   2448 
   2449 Result<TextServicesDocument::IteratorStatus, nsresult>
   2450 TextServicesDocument::OffsetEntryArray::Init(
   2451    FilteredContentIterator& aFilteredIter, IteratorStatus aIteratorStatus,
   2452    nsRange* aIterRange, nsAString* aAllTextInBlock /* = nullptr */) {
   2453  Clear();
   2454 
   2455  if (aAllTextInBlock) {
   2456    aAllTextInBlock->Truncate();
   2457  }
   2458 
   2459  if (aIteratorStatus == IteratorStatus::eDone) {
   2460    return IteratorStatus::eDone;
   2461  }
   2462 
   2463  // If we have an aIterRange, retrieve the endpoints so
   2464  // they can be used in the while loop below to trim entries
   2465  // for text nodes that are partially selected by aIterRange.
   2466 
   2467  nsCOMPtr<nsINode> rngStartNode, rngEndNode;
   2468  uint32_t rngStartOffset = 0, rngEndOffset = 0;
   2469  if (aIterRange) {
   2470    nsresult rv = TextServicesDocument::GetRangeEndPoints(
   2471        aIterRange, getter_AddRefs(rngStartNode), &rngStartOffset,
   2472        getter_AddRefs(rngEndNode), &rngEndOffset);
   2473    if (NS_FAILED(rv)) {
   2474      NS_WARNING("TextServicesDocument::GetRangeEndPoints() failed");
   2475      return Err(rv);
   2476    }
   2477  }
   2478 
   2479  // The text service could have added text nodes to the beginning
   2480  // of the current block and called this method again. Make sure
   2481  // we really are at the beginning of the current block:
   2482 
   2483  nsresult rv =
   2484      TextServicesDocument::FirstTextNodeInCurrentBlock(&aFilteredIter);
   2485  if (NS_FAILED(rv)) {
   2486    NS_WARNING("TextServicesDocument::FirstTextNodeInCurrentBlock() failed");
   2487    return Err(rv);
   2488  }
   2489 
   2490  TextServicesDocument::ClearDidSkip(&aFilteredIter);
   2491 
   2492  uint32_t offset = 0;
   2493  RefPtr<Text> firstTextNode, previousTextNode;
   2494  while (!aFilteredIter.IsDone()) {
   2495    if (nsCOMPtr<nsIContent> content =
   2496            aFilteredIter.GetCurrentNode()->IsContent()
   2497                ? aFilteredIter.GetCurrentNode()->AsContent()
   2498                : nullptr) {
   2499      // We don't observe layout updates, therefore, we should consider whether
   2500      // block or inline only with the default definition of the element.
   2501      if (HTMLEditUtils::IsBlockElement(
   2502              *content, BlockInlineCheck::UseHTMLDefaultStyle) ||
   2503          content->IsHTMLElement(nsGkAtoms::br)) {
   2504        break;
   2505      }
   2506      if (content->IsText()) {
   2507        if (previousTextNode && !TextServicesDocument::HasSameBlockNodeParent(
   2508                                    *previousTextNode, *content->AsText())) {
   2509          break;
   2510        }
   2511 
   2512        nsString str;
   2513        content->AsText()->GetNodeValue(str);
   2514 
   2515        // Add an entry for this text node into the offset table:
   2516 
   2517        UniquePtr<OffsetEntry>& entry = *AppendElement(
   2518            MakeUnique<OffsetEntry>(*content->AsText(), offset, str.Length()));
   2519        LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
   2520 
   2521        // If one or both of the endpoints of the iteration range
   2522        // are in the text node for this entry, make sure the entry
   2523        // only accounts for the portion of the text node that is
   2524        // in the range.
   2525 
   2526        uint32_t startOffset = 0;
   2527        uint32_t endOffset = str.Length();
   2528        bool adjustStr = false;
   2529 
   2530        if (entry->mTextNode == rngStartNode) {
   2531          entry->mOffsetInTextNode = startOffset = rngStartOffset;
   2532          adjustStr = true;
   2533        }
   2534 
   2535        if (entry->mTextNode == rngEndNode) {
   2536          endOffset = rngEndOffset;
   2537          adjustStr = true;
   2538        }
   2539 
   2540        if (adjustStr) {
   2541          entry->mLength = endOffset - startOffset;
   2542          str = Substring(str, startOffset, entry->mLength);
   2543        }
   2544 
   2545        offset += str.Length();
   2546 
   2547        if (aAllTextInBlock) {
   2548          // Append the text node's string to the output string:
   2549          if (!firstTextNode) {
   2550            *aAllTextInBlock = str;
   2551          } else {
   2552            *aAllTextInBlock += str;
   2553          }
   2554        }
   2555 
   2556        previousTextNode = content->AsText();
   2557 
   2558        if (!firstTextNode) {
   2559          firstTextNode = content->AsText();
   2560        }
   2561      }
   2562    }
   2563 
   2564    aFilteredIter.Next();
   2565 
   2566    if (TextServicesDocument::DidSkip(&aFilteredIter)) {
   2567      break;
   2568    }
   2569  }
   2570 
   2571  if (firstTextNode) {
   2572    // Always leave the iterator pointing at the first
   2573    // text node of the current block!
   2574    aFilteredIter.PositionAt(firstTextNode);
   2575    return aIteratorStatus;
   2576  }
   2577 
   2578  // If we never ran across a text node, the iterator
   2579  // might have been pointing to something invalid to
   2580  // begin with.
   2581  return IteratorStatus::eDone;
   2582 }
   2583 
   2584 void TextServicesDocument::OffsetEntryArray::RemoveInvalidElements() {
   2585  for (size_t i = 0; i < Length();) {
   2586    if (ElementAt(i)->mIsValid) {
   2587      i++;
   2588      continue;
   2589    }
   2590 
   2591    RemoveElementAt(i);
   2592    if (!mSelection.IsSet()) {
   2593      continue;
   2594    }
   2595    if (mSelection.StartIndex() == i) {
   2596      NS_ASSERTION(false, "What should we do in this case?");
   2597      mSelection.Reset();
   2598    } else if (mSelection.StartIndex() > i) {
   2599      MOZ_DIAGNOSTIC_ASSERT(mSelection.StartIndex() - 1 < Length());
   2600      MOZ_DIAGNOSTIC_ASSERT(mSelection.EndIndex() - 1 < Length());
   2601      mSelection.SetIndexes(mSelection.StartIndex() - 1,
   2602                            mSelection.EndIndex() - 1);
   2603    } else if (mSelection.EndIndex() >= i) {
   2604      MOZ_DIAGNOSTIC_ASSERT(mSelection.EndIndex() - 1 < Length());
   2605      mSelection.SetIndexes(mSelection.StartIndex(), mSelection.EndIndex() - 1);
   2606    }
   2607  }
   2608 }
   2609 
   2610 nsresult TextServicesDocument::OffsetEntryArray::SplitElementAt(
   2611    size_t aIndex, uint32_t aOffsetInTextNode) {
   2612  OffsetEntry* leftEntry = ElementAt(aIndex).get();
   2613  MOZ_ASSERT(leftEntry);
   2614  NS_ASSERTION((aOffsetInTextNode > 0), "aOffsetInTextNode == 0");
   2615  NS_ASSERTION((aOffsetInTextNode < leftEntry->mLength),
   2616               "aOffsetInTextNode >= mLength");
   2617 
   2618  if (aOffsetInTextNode < 1 || aOffsetInTextNode >= leftEntry->mLength) {
   2619    return NS_ERROR_FAILURE;
   2620  }
   2621 
   2622  const uint32_t oldLength = leftEntry->mLength - aOffsetInTextNode;
   2623 
   2624  // XXX(Bug 1631371) Check if this should use a fallible operation as it
   2625  // pretended earlier.
   2626  UniquePtr<OffsetEntry>& rightEntry = *InsertElementAt(
   2627      aIndex + 1,
   2628      MakeUnique<OffsetEntry>(leftEntry->mTextNode,
   2629                              leftEntry->mOffsetInTextInBlock + oldLength,
   2630                              aOffsetInTextNode));
   2631  LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
   2632  leftEntry->mLength = oldLength;
   2633  rightEntry->mOffsetInTextNode = leftEntry->mOffsetInTextNode + oldLength;
   2634 
   2635  return NS_OK;
   2636 }
   2637 
   2638 Maybe<size_t> TextServicesDocument::OffsetEntryArray::FirstIndexOf(
   2639    const Text& aTextNode) const {
   2640  for (size_t i = 0; i < Length(); i++) {
   2641    if (ElementAt(i)->mTextNode == &aTextNode) {
   2642      return Some(i);
   2643    }
   2644  }
   2645  return Nothing();
   2646 }
   2647 
   2648 // Spellchecker code has this. See bug 211343
   2649 #define IS_NBSP_CHAR(c) (((unsigned char)0xa0) == (c))
   2650 
   2651 Result<EditorDOMRangeInTexts, nsresult>
   2652 TextServicesDocument::OffsetEntryArray::FindWordRange(
   2653    nsAString& aAllTextInBlock, const EditorRawDOMPoint& aStartPointToScan) {
   2654  MOZ_ASSERT(aStartPointToScan.IsInTextNode());
   2655  // It's assumed that aNode is a text node. The first thing
   2656  // we do is get its index in the offset table so we can
   2657  // calculate the dom point's string offset.
   2658  Maybe<size_t> maybeEntryIndex =
   2659      FirstIndexOf(*aStartPointToScan.ContainerAs<Text>());
   2660  if (NS_WARN_IF(maybeEntryIndex.isNothing())) {
   2661    NS_WARNING(
   2662        "TextServicesDocument::OffsetEntryArray::FirstIndexOf() didn't find "
   2663        "entries");
   2664    return Err(NS_ERROR_FAILURE);
   2665  }
   2666 
   2667  // Next we map offset into a string offset.
   2668 
   2669  const UniquePtr<OffsetEntry>& entry = ElementAt(*maybeEntryIndex);
   2670  LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
   2671  uint32_t strOffset = entry->mOffsetInTextInBlock +
   2672                       aStartPointToScan.Offset() - entry->mOffsetInTextNode;
   2673 
   2674  // Now we use the word breaker to find the beginning and end
   2675  // of the word from our calculated string offset.
   2676 
   2677  const char16_t* str = aAllTextInBlock.BeginReading();
   2678  MOZ_ASSERT(strOffset <= aAllTextInBlock.Length(),
   2679             "The string offset shouldn't be greater than the string length!");
   2680 
   2681  intl::WordRange res = intl::WordBreaker::FindWord(aAllTextInBlock, strOffset);
   2682 
   2683  // Strip out the NBSPs at the ends
   2684  while (res.mBegin <= res.mEnd && IS_NBSP_CHAR(str[res.mBegin])) {
   2685    res.mBegin++;
   2686  }
   2687  if (str[res.mEnd] == static_cast<char16_t>(0x20)) {
   2688    uint32_t realEndWord = res.mEnd - 1;
   2689    while (realEndWord > res.mBegin && IS_NBSP_CHAR(str[realEndWord])) {
   2690      realEndWord--;
   2691    }
   2692    if (realEndWord < res.mEnd - 1) {
   2693      res.mEnd = realEndWord + 1;
   2694    }
   2695  }
   2696 
   2697  // Now that we have the string offsets for the beginning
   2698  // and end of the word, run through the offset table and
   2699  // convert them back into dom points.
   2700 
   2701  EditorDOMPointInText wordStart, wordEnd;
   2702  size_t lastIndex = Length() - 1;
   2703  for (size_t i = 0; i <= lastIndex; i++) {
   2704    // Check to see if res.mBegin is within the range covered
   2705    // by this entry. Note that if res.mBegin is after the last
   2706    // character covered by this entry, we will use the next
   2707    // entry if there is one.
   2708    const UniquePtr<OffsetEntry>& entry = ElementAt(i);
   2709    LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
   2710    if (entry->mOffsetInTextInBlock <= res.mBegin &&
   2711        (res.mBegin < entry->EndOffsetInTextInBlock() ||
   2712         (res.mBegin == entry->EndOffsetInTextInBlock() && i == lastIndex))) {
   2713      wordStart.Set(entry->mTextNode, entry->mOffsetInTextNode + res.mBegin -
   2714                                          entry->mOffsetInTextInBlock);
   2715    }
   2716 
   2717    // Check to see if res.mEnd is within the range covered
   2718    // by this entry.
   2719    if (entry->mOffsetInTextInBlock <= res.mEnd &&
   2720        res.mEnd <= entry->EndOffsetInTextInBlock()) {
   2721      if (res.mBegin == res.mEnd &&
   2722          res.mEnd == entry->EndOffsetInTextInBlock() && i != lastIndex) {
   2723        // Wait for the next round so that we use the same entry
   2724        // we did for aWordStartNode.
   2725        continue;
   2726      }
   2727 
   2728      wordEnd.Set(entry->mTextNode, entry->mOffsetInTextNode + res.mEnd -
   2729                                        entry->mOffsetInTextInBlock);
   2730      break;
   2731    }
   2732  }
   2733 
   2734  return EditorDOMRangeInTexts(wordStart, wordEnd);
   2735 }
   2736 
   2737 /**
   2738 * nsIEditActionListener implementation:
   2739 *   Don't implement the behavior directly here.  The methods won't be called
   2740 *   if the instance is created for inline spell checker created for editor.
   2741 *   If you need to listen a new edit action, you need to add similar
   2742 *   non-virtual method and you need to call it from EditorBase directly.
   2743 */
   2744 
   2745 NS_IMETHODIMP
   2746 TextServicesDocument::DidDeleteNode(nsINode* aChild, nsresult aResult) {
   2747  if (NS_WARN_IF(NS_FAILED(aResult)) || NS_WARN_IF(!aChild) ||
   2748      !aChild->IsContent()) {
   2749    return NS_OK;
   2750  }
   2751  DidDeleteContent(*aChild->AsContent());
   2752  return NS_OK;
   2753 }
   2754 
   2755 NS_IMETHODIMP TextServicesDocument::DidJoinContents(
   2756    const EditorRawDOMPoint& aJoinedPoint, const nsINode* aRemovedNode) {
   2757  if (MOZ_UNLIKELY(NS_WARN_IF(!aJoinedPoint.IsSetAndValid()) ||
   2758                   NS_WARN_IF(!aRemovedNode->IsContent()))) {
   2759    return NS_OK;
   2760  }
   2761  DidJoinContents(aJoinedPoint, *aRemovedNode->AsContent());
   2762  return NS_OK;
   2763 }
   2764 
   2765 NS_IMETHODIMP
   2766 TextServicesDocument::DidInsertText(CharacterData* aTextNode, int32_t aOffset,
   2767                                    const nsAString& aString,
   2768                                    nsresult aResult) {
   2769  return NS_OK;
   2770 }
   2771 
   2772 NS_IMETHODIMP
   2773 TextServicesDocument::WillDeleteText(CharacterData* aTextNode, int32_t aOffset,
   2774                                     int32_t aLength) {
   2775  return NS_OK;
   2776 }
   2777 
   2778 NS_IMETHODIMP
   2779 TextServicesDocument::WillDeleteRanges(
   2780    const nsTArray<RefPtr<nsRange>>& aRangesToDelete) {
   2781  return NS_OK;
   2782 }
   2783 
   2784 #undef LockOffsetEntryArrayLengthInDebugBuild
   2785 
   2786 }  // namespace mozilla