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