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