Bug 1758813 [wpt PR 33142] - Implement RP sign out, a=testonly
[gecko.git] / editor / spellchecker / TextServicesDocument.cpp
blob4bb4be3e24e9cfbf2df457e88d61d18dbaa92791
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/. */
6 #include "TextServicesDocument.h"
8 #include "FilteredContentIterator.h" // for FilteredContentIterator
9 #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc
10 #include "mozilla/EditorBase.h" // for EditorBase
11 #include "mozilla/EditorUtils.h" // for AutoTransactionBatchExternal
12 #include "mozilla/HTMLEditHelpers.h" // for JoinNodesDirection
13 #include "mozilla/HTMLEditUtils.h" // for HTMLEditUtils
14 #include "mozilla/IntegerRange.h" // for IntegerRange
15 #include "mozilla/mozalloc.h" // for operator new, etc
16 #include "mozilla/OwningNonNull.h"
17 #include "mozilla/UniquePtr.h" // for UniquePtr
18 #include "mozilla/dom/AbstractRange.h" // for AbstractRange
19 #include "mozilla/dom/Element.h"
20 #include "mozilla/dom/Selection.h"
21 #include "mozilla/dom/StaticRange.h" // for StaticRange
22 #include "mozilla/dom/Text.h"
23 #include "mozilla/intl/WordBreaker.h" // for WordRange, WordBreaker
24 #include "nsAString.h" // for nsAString::Length, etc
25 #include "nsContentUtils.h" // for nsContentUtils
26 #include "nsComposeTxtSrvFilter.h"
27 #include "nsDebug.h" // for NS_ENSURE_TRUE, etc
28 #include "nsDependentSubstring.h" // for Substring
29 #include "nsError.h" // for NS_OK, NS_ERROR_FAILURE, etc
30 #include "nsGenericHTMLElement.h" // for nsGenericHTMLElement
31 #include "nsIContent.h" // for nsIContent, etc
32 #include "nsID.h" // for NS_GET_IID
33 #include "nsIEditor.h" // for nsIEditor, etc
34 #include "nsIEditorSpellCheck.h" // for nsIEditorSpellCheck, etc
35 #include "nsINode.h" // for nsINode
36 #include "nsISelectionController.h" // for nsISelectionController, etc
37 #include "nsISupportsBase.h" // for nsISupports
38 #include "nsISupportsUtils.h" // for NS_IF_ADDREF, NS_ADDREF, etc
39 #include "nsRange.h" // for nsRange
40 #include "nsString.h" // for nsString, nsAutoString
41 #include "nscore.h" // for nsresult, NS_IMETHODIMP, etc
43 namespace mozilla {
45 using namespace dom;
47 /**
48 * OffsetEntry manages a range in a text node. It stores 2 offset values,
49 * one is offset in the text node, the other is offset in all text in
50 * the ancestor block of the text node. And the length is managing length
51 * in the text node, starting from the offset in text node.
52 * In other words, a text node may be managed by multiple instances of this
53 * class.
55 class OffsetEntry final {
56 public:
57 OffsetEntry() = delete;
59 /**
60 * @param aTextNode The text node which will be manged by the instance.
61 * @param aOffsetInTextInBlock
62 * Start offset in the text node which will be managed by
63 * the instance.
64 * @param aLength Length in the text node which will be managed by the
65 * instance.
67 OffsetEntry(Text& aTextNode, uint32_t aOffsetInTextInBlock, uint32_t aLength)
68 : mTextNode(aTextNode),
69 mOffsetInTextNode(0),
70 mOffsetInTextInBlock(aOffsetInTextInBlock),
71 mLength(aLength),
72 mIsInsertedText(false),
73 mIsValid(true) {}
75 /**
76 * EndOffsetInTextNode() returns end offset in the text node, which is
77 * managed by the instance.
79 uint32_t EndOffsetInTextNode() const { return mOffsetInTextNode + mLength; }
81 /**
82 * OffsetInTextNodeIsInRangeOrEndOffset() checks whether the offset in
83 * the text node is managed by the instance or not.
85 bool OffsetInTextNodeIsInRangeOrEndOffset(uint32_t aOffsetInTextNode) const {
86 return aOffsetInTextNode >= mOffsetInTextNode &&
87 aOffsetInTextNode <= EndOffsetInTextNode();
90 /**
91 * EndOffsetInTextInBlock() returns end offset in the all text in ancestor
92 * block of the text node, which is managed by the instance.
94 uint32_t EndOffsetInTextInBlock() const {
95 return mOffsetInTextInBlock + mLength;
98 /**
99 * OffsetInTextNodeIsInRangeOrEndOffset() checks whether the offset in
100 * the all text in ancestor block of the text node is managed by the instance
101 * or not.
103 bool OffsetInTextInBlockIsInRangeOrEndOffset(
104 uint32_t aOffsetInTextInBlock) const {
105 return aOffsetInTextInBlock >= mOffsetInTextInBlock &&
106 aOffsetInTextInBlock <= EndOffsetInTextInBlock();
109 OwningNonNull<Text> mTextNode;
110 uint32_t mOffsetInTextNode;
111 // Offset in all text in the closest ancestor block of mTextNode.
112 uint32_t mOffsetInTextInBlock;
113 uint32_t mLength;
114 bool mIsInsertedText;
115 bool mIsValid;
118 template <typename ElementType>
119 struct MOZ_STACK_CLASS ArrayLengthMutationGuard final {
120 ArrayLengthMutationGuard() = delete;
121 explicit ArrayLengthMutationGuard(const nsTArray<ElementType>& aArray)
122 : mArray(aArray), mOldLength(aArray.Length()) {}
123 ~ArrayLengthMutationGuard() {
124 if (mArray.Length() != mOldLength) {
125 MOZ_CRASH("The array length was changed unexpectedly");
129 private:
130 const nsTArray<ElementType>& mArray;
131 size_t mOldLength;
134 #define LockOffsetEntryArrayLengthInDebugBuild(aName, aArray) \
135 DebugOnly<ArrayLengthMutationGuard<UniquePtr<OffsetEntry>>> const aName = \
136 ArrayLengthMutationGuard<UniquePtr<OffsetEntry>>(aArray);
138 TextServicesDocument::TextServicesDocument()
139 : mTxtSvcFilterType(0), mIteratorStatus(IteratorStatus::eDone) {}
141 NS_IMPL_CYCLE_COLLECTING_ADDREF(TextServicesDocument)
142 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextServicesDocument)
144 NS_INTERFACE_MAP_BEGIN(TextServicesDocument)
145 NS_INTERFACE_MAP_ENTRY(nsIEditActionListener)
146 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditActionListener)
147 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(TextServicesDocument)
148 NS_INTERFACE_MAP_END
150 NS_IMPL_CYCLE_COLLECTION(TextServicesDocument, mDocument, mSelCon, mEditorBase,
151 mFilteredIter, mPrevTextBlock, mNextTextBlock, mExtent)
153 nsresult TextServicesDocument::InitWithEditor(nsIEditor* aEditor) {
154 nsCOMPtr<nsISelectionController> selCon;
156 NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER);
158 // Check to see if we already have an mSelCon. If we do, it
159 // better be the same one the editor uses!
161 nsresult rv = aEditor->GetSelectionController(getter_AddRefs(selCon));
163 if (NS_FAILED(rv)) {
164 return rv;
167 if (!selCon || (mSelCon && selCon != mSelCon)) {
168 return NS_ERROR_FAILURE;
171 if (!mSelCon) {
172 mSelCon = selCon;
175 // Check to see if we already have an mDocument. If we do, it
176 // better be the same one the editor uses!
178 RefPtr<Document> doc = aEditor->AsEditorBase()->GetDocument();
179 if (!doc || (mDocument && doc != mDocument)) {
180 return NS_ERROR_FAILURE;
183 if (!mDocument) {
184 mDocument = doc;
186 rv = CreateDocumentContentIterator(getter_AddRefs(mFilteredIter));
188 if (NS_FAILED(rv)) {
189 return rv;
192 mIteratorStatus = IteratorStatus::eDone;
194 rv = FirstBlock();
196 if (NS_FAILED(rv)) {
197 return rv;
201 mEditorBase = aEditor->AsEditorBase();
203 rv = aEditor->AddEditActionListener(this);
205 return rv;
208 nsresult TextServicesDocument::SetExtent(const AbstractRange* aAbstractRange) {
209 MOZ_ASSERT(aAbstractRange);
211 if (NS_WARN_IF(!mDocument)) {
212 return NS_ERROR_FAILURE;
215 // We need to store a copy of aAbstractRange since we don't know where it
216 // came from.
217 mExtent = nsRange::Create(aAbstractRange, IgnoreErrors());
218 if (NS_WARN_IF(!mExtent)) {
219 return NS_ERROR_FAILURE;
222 // Create a new iterator based on our new extent range.
223 nsresult rv =
224 CreateFilteredContentIterator(mExtent, getter_AddRefs(mFilteredIter));
225 if (NS_WARN_IF(NS_FAILED(rv))) {
226 return rv;
229 // Now position the iterator at the start of the first block
230 // in the range.
231 mIteratorStatus = IteratorStatus::eDone;
233 rv = FirstBlock();
234 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "FirstBlock() failed");
235 return rv;
238 nsresult TextServicesDocument::ExpandRangeToWordBoundaries(
239 StaticRange* aStaticRange) {
240 MOZ_ASSERT(aStaticRange);
242 // Get the end points of the range.
244 nsCOMPtr<nsINode> rngStartNode, rngEndNode;
245 uint32_t rngStartOffset, rngEndOffset;
247 nsresult rv = GetRangeEndPoints(aStaticRange, getter_AddRefs(rngStartNode),
248 &rngStartOffset, getter_AddRefs(rngEndNode),
249 &rngEndOffset);
250 if (NS_WARN_IF(NS_FAILED(rv))) {
251 return rv;
254 // Create a content iterator based on the range.
255 RefPtr<FilteredContentIterator> filteredIter;
256 rv =
257 CreateFilteredContentIterator(aStaticRange, getter_AddRefs(filteredIter));
258 if (NS_WARN_IF(NS_FAILED(rv))) {
259 return rv;
262 // Find the first text node in the range.
263 IteratorStatus iterStatus = IteratorStatus::eDone;
264 rv = FirstTextNode(filteredIter, &iterStatus);
265 if (NS_WARN_IF(NS_FAILED(rv))) {
266 return rv;
269 if (iterStatus == IteratorStatus::eDone) {
270 // No text was found so there's no adjustment necessary!
271 return NS_OK;
274 nsINode* firstText = filteredIter->GetCurrentNode();
275 if (NS_WARN_IF(!firstText)) {
276 return NS_ERROR_FAILURE;
279 // Find the last text node in the range.
281 rv = LastTextNode(filteredIter, &iterStatus);
282 if (NS_WARN_IF(NS_FAILED(rv))) {
283 return rv;
286 if (iterStatus == IteratorStatus::eDone) {
287 // We should never get here because a first text block
288 // was found above.
289 NS_ASSERTION(false, "Found a first without a last!");
290 return NS_ERROR_FAILURE;
293 nsINode* lastText = filteredIter->GetCurrentNode();
294 if (NS_WARN_IF(!lastText)) {
295 return NS_ERROR_FAILURE;
298 // Now make sure our end points are in terms of text nodes in the range!
300 if (rngStartNode != firstText) {
301 // The range includes the start of the first text node!
302 rngStartNode = firstText;
303 rngStartOffset = 0;
306 if (rngEndNode != lastText) {
307 // The range includes the end of the last text node!
308 rngEndNode = lastText;
309 rngEndOffset = lastText->Length();
312 // Create a doc iterator so that we can scan beyond
313 // the bounds of the extent range.
315 RefPtr<FilteredContentIterator> docFilteredIter;
316 rv = CreateDocumentContentIterator(getter_AddRefs(docFilteredIter));
317 if (NS_WARN_IF(NS_FAILED(rv))) {
318 return rv;
321 // Grab all the text in the block containing our
322 // first text node.
323 rv = docFilteredIter->PositionAt(firstText);
324 if (NS_WARN_IF(NS_FAILED(rv))) {
325 return rv;
328 iterStatus = IteratorStatus::eValid;
330 OffsetEntryArray offsetTable;
331 nsAutoString blockStr;
332 Result<IteratorStatus, nsresult> result = offsetTable.Init(
333 *docFilteredIter, IteratorStatus::eValid, nullptr, &blockStr);
334 if (result.isErr()) {
335 return result.unwrapErr();
338 Result<EditorDOMRangeInTexts, nsresult> maybeWordRange =
339 offsetTable.FindWordRange(
340 blockStr, EditorRawDOMPoint(rngStartNode, rngStartOffset));
341 offsetTable.Clear();
342 if (maybeWordRange.isErr()) {
343 NS_WARNING(
344 "TextServicesDocument::OffsetEntryArray::FindWordRange() failed");
345 return maybeWordRange.unwrapErr();
347 rngStartNode = maybeWordRange.inspect().StartRef().GetContainerAsText();
348 rngStartOffset = maybeWordRange.inspect().StartRef().Offset();
350 // Grab all the text in the block containing our
351 // last text node.
353 rv = docFilteredIter->PositionAt(lastText);
354 if (NS_WARN_IF(NS_FAILED(rv))) {
355 return rv;
358 result = offsetTable.Init(*docFilteredIter, IteratorStatus::eValid, nullptr,
359 &blockStr);
360 if (result.isErr()) {
361 return result.unwrapErr();
364 maybeWordRange = offsetTable.FindWordRange(
365 blockStr, EditorRawDOMPoint(rngEndNode, rngEndOffset));
366 offsetTable.Clear();
367 if (maybeWordRange.isErr()) {
368 NS_WARNING(
369 "TextServicesDocument::OffsetEntryArray::FindWordRange() failed");
370 return maybeWordRange.unwrapErr();
373 // To prevent expanding the range too much, we only change
374 // rngEndNode and rngEndOffset if it isn't already at the start of the
375 // word and isn't equivalent to rngStartNode and rngStartOffset.
377 if (rngEndNode != maybeWordRange.inspect().StartRef().GetContainerAsText() ||
378 rngEndOffset != maybeWordRange.inspect().StartRef().Offset() ||
379 (rngEndNode == rngStartNode && rngEndOffset == rngStartOffset)) {
380 rngEndNode = maybeWordRange.inspect().EndRef().GetContainerAsText();
381 rngEndOffset = maybeWordRange.inspect().EndRef().Offset();
384 // Now adjust the range so that it uses our new end points.
385 rv = aStaticRange->SetStartAndEnd(rngStartNode, rngStartOffset, rngEndNode,
386 rngEndOffset);
387 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to update the given range");
388 return rv;
391 nsresult TextServicesDocument::SetFilterType(uint32_t aFilterType) {
392 mTxtSvcFilterType = aFilterType;
394 return NS_OK;
397 nsresult TextServicesDocument::GetCurrentTextBlock(nsAString& aStr) {
398 aStr.Truncate();
400 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
402 Result<IteratorStatus, nsresult> result =
403 mOffsetTable.Init(*mFilteredIter, mIteratorStatus, mExtent, &aStr);
404 if (result.isErr()) {
405 NS_WARNING("OffsetEntryArray::Init() failed");
406 return result.unwrapErr();
408 mIteratorStatus = result.unwrap();
409 return NS_OK;
412 nsresult TextServicesDocument::FirstBlock() {
413 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
415 nsresult rv = FirstTextNode(mFilteredIter, &mIteratorStatus);
417 if (NS_FAILED(rv)) {
418 return rv;
421 // Keep track of prev and next blocks, just in case
422 // the text service blows away the current block.
424 if (mIteratorStatus == IteratorStatus::eValid) {
425 mPrevTextBlock = nullptr;
426 rv = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock));
427 } else {
428 // There's no text block in the document!
430 mPrevTextBlock = nullptr;
431 mNextTextBlock = nullptr;
434 // XXX Result of FirstTextNode() or GetFirstTextNodeInNextBlock().
435 return rv;
438 nsresult TextServicesDocument::LastSelectedBlock(
439 BlockSelectionStatus* aSelStatus, uint32_t* aSelOffset,
440 uint32_t* aSelLength) {
441 NS_ENSURE_TRUE(aSelStatus && aSelOffset && aSelLength, NS_ERROR_NULL_POINTER);
443 mIteratorStatus = IteratorStatus::eDone;
445 *aSelStatus = BlockSelectionStatus::eBlockNotFound;
446 *aSelOffset = *aSelLength = UINT32_MAX;
448 if (!mSelCon || !mFilteredIter) {
449 return NS_ERROR_FAILURE;
452 RefPtr<Selection> selection =
453 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
454 if (NS_WARN_IF(!selection)) {
455 return NS_ERROR_FAILURE;
458 RefPtr<const nsRange> range;
459 nsCOMPtr<nsINode> parent;
461 if (selection->IsCollapsed()) {
462 // We have a caret. Check if the caret is in a text node.
463 // If it is, make the text node's block the current block.
464 // If the caret isn't in a text node, search forwards in
465 // the document, till we find a text node.
467 range = selection->GetRangeAt(0);
468 if (!range) {
469 return NS_ERROR_FAILURE;
472 parent = range->GetStartContainer();
473 if (!parent) {
474 return NS_ERROR_FAILURE;
477 nsresult rv;
478 if (parent->IsText()) {
479 // The caret is in a text node. Find the beginning
480 // of the text block containing this text node and
481 // return.
483 rv = mFilteredIter->PositionAt(parent->AsText());
484 if (NS_FAILED(rv)) {
485 return rv;
488 rv = FirstTextNodeInCurrentBlock(mFilteredIter);
489 if (NS_FAILED(rv)) {
490 return rv;
493 Result<IteratorStatus, nsresult> result =
494 mOffsetTable.Init(*mFilteredIter, IteratorStatus::eValid, mExtent);
495 if (result.isErr()) {
496 NS_WARNING("OffsetEntryArray::Init() failed");
497 mIteratorStatus = IteratorStatus::eValid; // XXX
498 return result.unwrapErr();
500 mIteratorStatus = result.unwrap();
502 rv = GetSelection(aSelStatus, aSelOffset, aSelLength);
503 if (NS_FAILED(rv)) {
504 return rv;
507 if (*aSelStatus == BlockSelectionStatus::eBlockContains) {
508 rv = SetSelectionInternal(*aSelOffset, *aSelLength, false);
510 } else {
511 // The caret isn't in a text node. Create an iterator
512 // based on a range that extends from the current caret
513 // position to the end of the document, then walk forwards
514 // till you find a text node, then find the beginning of it's block.
516 range = CreateDocumentContentRootToNodeOffsetRange(
517 parent, range->StartOffset(), false);
518 if (NS_WARN_IF(!range)) {
519 return NS_ERROR_FAILURE;
522 if (range->Collapsed()) {
523 // If we get here, the range is collapsed because there is nothing after
524 // the caret! Just return NS_OK;
525 return NS_OK;
528 RefPtr<FilteredContentIterator> filteredIter;
529 rv = CreateFilteredContentIterator(range, getter_AddRefs(filteredIter));
530 if (NS_FAILED(rv)) {
531 return rv;
534 filteredIter->First();
536 Text* textNode = nullptr;
537 for (; !filteredIter->IsDone(); filteredIter->Next()) {
538 nsINode* currentNode = filteredIter->GetCurrentNode();
539 if (currentNode->IsText()) {
540 textNode = currentNode->AsText();
541 break;
545 if (!textNode) {
546 return NS_OK;
549 rv = mFilteredIter->PositionAt(textNode);
550 if (NS_FAILED(rv)) {
551 return rv;
554 rv = FirstTextNodeInCurrentBlock(mFilteredIter);
555 if (NS_FAILED(rv)) {
556 return rv;
559 Result<IteratorStatus, nsresult> result = mOffsetTable.Init(
560 *mFilteredIter, IteratorStatus::eValid, mExtent, nullptr);
561 if (result.isErr()) {
562 NS_WARNING("OffsetEntryArray::Init() failed");
563 mIteratorStatus = IteratorStatus::eValid; // XXX
564 return result.unwrapErr();
566 mIteratorStatus = result.inspect();
568 rv = GetSelection(aSelStatus, aSelOffset, aSelLength);
569 if (NS_FAILED(rv)) {
570 return rv;
574 // Result of SetSelectionInternal() in the |if| block or NS_OK.
575 return rv;
578 // If we get here, we have an uncollapsed selection!
579 // Look backwards through each range in the selection till you
580 // find the first text node. If you find one, find the
581 // beginning of its text block, and make it the current
582 // block.
584 const uint32_t rangeCount = selection->RangeCount();
585 MOZ_ASSERT(
586 rangeCount,
587 "Selection is not collapsed, so, the range count should be 1 or larger");
589 // XXX: We may need to add some code here to make sure
590 // the ranges are sorted in document appearance order!
592 for (const uint32_t i : Reversed(IntegerRange(rangeCount))) {
593 MOZ_ASSERT(selection->RangeCount() == rangeCount);
594 range = selection->GetRangeAt(i);
595 if (MOZ_UNLIKELY(!range)) {
596 return NS_OK; // XXX Really?
599 // Create an iterator for the range.
601 RefPtr<FilteredContentIterator> filteredIter;
602 nsresult rv =
603 CreateFilteredContentIterator(range, getter_AddRefs(filteredIter));
604 if (NS_FAILED(rv)) {
605 return rv;
608 filteredIter->Last();
610 // Now walk through the range till we find a text node.
612 for (; !filteredIter->IsDone(); filteredIter->Prev()) {
613 if (filteredIter->GetCurrentNode()->NodeType() == nsINode::TEXT_NODE) {
614 // We found a text node, so position the document's
615 // iterator at the beginning of the block, then get
616 // the selection in terms of the string offset.
618 nsresult rv = mFilteredIter->PositionAt(filteredIter->GetCurrentNode());
619 if (NS_FAILED(rv)) {
620 return rv;
623 rv = FirstTextNodeInCurrentBlock(mFilteredIter);
624 if (NS_FAILED(rv)) {
625 return rv;
628 mIteratorStatus = IteratorStatus::eValid;
630 Result<IteratorStatus, nsresult> result =
631 mOffsetTable.Init(*mFilteredIter, IteratorStatus::eValid, mExtent);
632 if (result.isErr()) {
633 NS_WARNING("OffsetEntryArray::Init() failed");
634 mIteratorStatus = IteratorStatus::eValid; // XXX
635 return result.unwrapErr();
637 mIteratorStatus = result.unwrap();
639 return GetSelection(aSelStatus, aSelOffset, aSelLength);
644 // If we get here, we didn't find any text node in the selection!
645 // Create a range that extends from the end of the selection,
646 // to the end of the document, then iterate forwards through
647 // it till you find a text node!
648 range = rangeCount > 0 ? selection->GetRangeAt(rangeCount - 1) : nullptr;
649 if (!range) {
650 return NS_ERROR_FAILURE;
653 parent = range->GetEndContainer();
654 if (!parent) {
655 return NS_ERROR_FAILURE;
658 range = CreateDocumentContentRootToNodeOffsetRange(parent, range->EndOffset(),
659 false);
660 if (NS_WARN_IF(!range)) {
661 return NS_ERROR_FAILURE;
664 if (range->Collapsed()) {
665 // If we get here, the range is collapsed because there is nothing after
666 // the current selection! Just return NS_OK;
667 return NS_OK;
670 RefPtr<FilteredContentIterator> filteredIter;
671 nsresult rv =
672 CreateFilteredContentIterator(range, getter_AddRefs(filteredIter));
673 if (NS_FAILED(rv)) {
674 return rv;
677 filteredIter->First();
679 for (; !filteredIter->IsDone(); filteredIter->Next()) {
680 if (filteredIter->GetCurrentNode()->NodeType() == nsINode::TEXT_NODE) {
681 // We found a text node! Adjust the document's iterator to point
682 // to the beginning of its text block, then get the current selection.
683 nsresult rv = mFilteredIter->PositionAt(filteredIter->GetCurrentNode());
684 if (NS_FAILED(rv)) {
685 return rv;
688 rv = FirstTextNodeInCurrentBlock(mFilteredIter);
689 if (NS_FAILED(rv)) {
690 return rv;
693 Result<IteratorStatus, nsresult> result =
694 mOffsetTable.Init(*mFilteredIter, IteratorStatus::eValid, mExtent);
695 if (result.isErr()) {
696 NS_WARNING("OffsetEntryArray::Init() failed");
697 mIteratorStatus = IteratorStatus::eValid; // XXX
698 return result.unwrapErr();
700 mIteratorStatus = result.unwrap();
702 rv = GetSelection(aSelStatus, aSelOffset, aSelLength);
703 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
704 "TextServicesDocument::GetSelection() failed");
705 return rv;
709 // If we get here, we didn't find any block before or inside
710 // the selection! Just return OK.
711 return NS_OK;
714 nsresult TextServicesDocument::PrevBlock() {
715 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
717 if (mIteratorStatus == IteratorStatus::eDone) {
718 return NS_OK;
721 switch (mIteratorStatus) {
722 case IteratorStatus::eValid:
723 case IteratorStatus::eNext: {
724 nsresult rv = FirstTextNodeInPrevBlock(mFilteredIter);
726 if (NS_FAILED(rv)) {
727 mIteratorStatus = IteratorStatus::eDone;
728 return rv;
731 if (mFilteredIter->IsDone()) {
732 mIteratorStatus = IteratorStatus::eDone;
733 return NS_OK;
736 mIteratorStatus = IteratorStatus::eValid;
737 break;
739 case IteratorStatus::ePrev:
741 // The iterator already points to the previous
742 // block, so don't do anything.
744 mIteratorStatus = IteratorStatus::eValid;
745 break;
747 default:
749 mIteratorStatus = IteratorStatus::eDone;
750 break;
753 // Keep track of prev and next blocks, just in case
754 // the text service blows away the current block.
755 nsresult rv = NS_OK;
756 if (mIteratorStatus == IteratorStatus::eValid) {
757 GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock));
758 rv = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock));
759 } else {
760 // We must be done!
761 mPrevTextBlock = nullptr;
762 mNextTextBlock = nullptr;
765 // XXX The result of GetFirstTextNodeInNextBlock() or NS_OK.
766 return rv;
769 nsresult TextServicesDocument::NextBlock() {
770 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
772 if (mIteratorStatus == IteratorStatus::eDone) {
773 return NS_OK;
776 switch (mIteratorStatus) {
777 case IteratorStatus::eValid: {
778 // Advance the iterator to the next text block.
780 nsresult rv = FirstTextNodeInNextBlock(mFilteredIter);
782 if (NS_FAILED(rv)) {
783 mIteratorStatus = IteratorStatus::eDone;
784 return rv;
787 if (mFilteredIter->IsDone()) {
788 mIteratorStatus = IteratorStatus::eDone;
789 return NS_OK;
792 mIteratorStatus = IteratorStatus::eValid;
793 break;
795 case IteratorStatus::eNext:
797 // The iterator already points to the next block,
798 // so don't do anything to it!
800 mIteratorStatus = IteratorStatus::eValid;
801 break;
803 case IteratorStatus::ePrev:
805 // If the iterator is pointing to the previous block,
806 // we know that there is no next text block! Just
807 // fall through to the default case!
809 default:
811 mIteratorStatus = IteratorStatus::eDone;
812 break;
815 // Keep track of prev and next blocks, just in case
816 // the text service blows away the current block.
817 nsresult rv = NS_OK;
818 if (mIteratorStatus == IteratorStatus::eValid) {
819 GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock));
820 rv = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock));
821 } else {
822 // We must be done.
823 mPrevTextBlock = nullptr;
824 mNextTextBlock = nullptr;
827 // The result of GetFirstTextNodeInNextBlock() or NS_OK.
828 return rv;
831 nsresult TextServicesDocument::IsDone(bool* aIsDone) {
832 NS_ENSURE_TRUE(aIsDone, NS_ERROR_NULL_POINTER);
834 *aIsDone = false;
836 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
838 *aIsDone = mIteratorStatus == IteratorStatus::eDone;
840 return NS_OK;
843 nsresult TextServicesDocument::SetSelection(uint32_t aOffset,
844 uint32_t aLength) {
845 NS_ENSURE_TRUE(mSelCon, NS_ERROR_FAILURE);
847 return SetSelectionInternal(aOffset, aLength, true);
850 nsresult TextServicesDocument::ScrollSelectionIntoView() {
851 NS_ENSURE_TRUE(mSelCon, NS_ERROR_FAILURE);
853 // After ScrollSelectionIntoView(), the pending notifications might be flushed
854 // and PresShell/PresContext/Frames may be dead. See bug 418470.
855 nsresult rv = mSelCon->ScrollSelectionIntoView(
856 nsISelectionController::SELECTION_NORMAL,
857 nsISelectionController::SELECTION_FOCUS_REGION,
858 nsISelectionController::SCROLL_SYNCHRONOUS);
860 return rv;
863 nsresult TextServicesDocument::OffsetEntryArray::WillDeleteSelection() {
864 MOZ_ASSERT(mSelection.IsSet());
865 MOZ_ASSERT(!mSelection.IsCollapsed());
867 for (size_t i = mSelection.StartIndex(); i <= mSelection.EndIndex(); i++) {
868 OffsetEntry* entry = ElementAt(i).get();
869 if (i == mSelection.StartIndex()) {
870 // Calculate the length of the selection. Note that the
871 // selection length can be zero if the start of the selection
872 // is at the very end of a text node entry.
873 uint32_t selLength;
874 if (entry->mIsInsertedText) {
875 // Inserted text offset entries have no width when
876 // talking in terms of string offsets! If the beginning
877 // of the selection is in an inserted text offset entry,
878 // the caret is always at the end of the entry!
879 selLength = 0;
880 } else {
881 selLength = entry->EndOffsetInTextInBlock() -
882 mSelection.StartOffsetInTextInBlock();
885 if (selLength > 0) {
886 if (mSelection.StartOffsetInTextInBlock() >
887 entry->mOffsetInTextInBlock) {
888 // Selection doesn't start at the beginning of the
889 // text node entry. We need to split this entry into
890 // two pieces, the piece before the selection, and
891 // the piece inside the selection.
892 nsresult rv = SplitElementAt(i, selLength);
893 if (NS_FAILED(rv)) {
894 NS_WARNING("selLength was invalid for the OffsetEntry");
895 return rv;
898 // Adjust selection indexes to account for new entry:
899 MOZ_DIAGNOSTIC_ASSERT(mSelection.StartIndex() + 1 < Length());
900 MOZ_DIAGNOSTIC_ASSERT(mSelection.EndIndex() + 1 < Length());
901 mSelection.SetIndexes(mSelection.StartIndex() + 1,
902 mSelection.EndIndex() + 1);
903 entry = ElementAt(++i).get();
906 if (mSelection.StartIndex() < mSelection.EndIndex()) {
907 // The entire entry is contained in the selection. Mark the
908 // entry invalid.
909 entry->mIsValid = false;
914 if (i == mSelection.EndIndex()) {
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 entry->mIsValid = false;
921 } else {
922 // Calculate the length of the selection. Note that the
923 // selection length can be zero if the end of the selection
924 // is at the very beginning of a text node entry.
926 const uint32_t selLength =
927 mSelection.EndOffsetInTextInBlock() - entry->mOffsetInTextInBlock;
928 if (selLength) {
929 if (mSelection.EndOffsetInTextInBlock() <
930 entry->EndOffsetInTextInBlock()) {
931 // mOffsetInTextInBlock is guaranteed to be inside the selection,
932 // even when mSelection.IsInSameElement() is true.
933 nsresult rv = SplitElementAt(i, entry->mLength - selLength);
934 if (NS_FAILED(rv)) {
935 NS_WARNING(
936 "entry->mLength - selLength was invalid for the OffsetEntry");
937 return rv;
940 // Update the entry fields:
941 ElementAt(i + 1)->mOffsetInTextNode = entry->mOffsetInTextNode;
944 if (mSelection.EndOffsetInTextInBlock() ==
945 entry->EndOffsetInTextInBlock()) {
946 // The entire entry is contained in the selection. Mark the
947 // entry invalid.
948 entry->mIsValid = false;
954 if (i != mSelection.StartIndex() && i != mSelection.EndIndex()) {
955 // The entire entry is contained in the selection. Mark the
956 // entry invalid.
957 entry->mIsValid = false;
961 return NS_OK;
964 nsresult TextServicesDocument::DeleteSelection() {
965 if (NS_WARN_IF(!mEditorBase) ||
966 NS_WARN_IF(!mOffsetTable.mSelection.IsSet())) {
967 return NS_ERROR_FAILURE;
970 if (mOffsetTable.mSelection.IsCollapsed()) {
971 return NS_OK;
974 // If we have an mExtent, save off its current set of
975 // end points so we can compare them against mExtent's
976 // set after the deletion of the content.
978 nsCOMPtr<nsINode> origStartNode, origEndNode;
979 uint32_t origStartOffset = 0, origEndOffset = 0;
981 if (mExtent) {
982 nsresult rv = GetRangeEndPoints(
983 mExtent, getter_AddRefs(origStartNode), &origStartOffset,
984 getter_AddRefs(origEndNode), &origEndOffset);
986 if (NS_FAILED(rv)) {
987 return rv;
991 if (NS_FAILED(mOffsetTable.WillDeleteSelection())) {
992 NS_WARNING(
993 "TextServicesDocument::OffsetEntryTable::WillDeleteSelection() failed");
994 return NS_ERROR_FAILURE;
997 // Make sure mFilteredIter always points to something valid!
998 AdjustContentIterator();
1000 // Now delete the actual content!
1001 OwningNonNull<EditorBase> editorBase = *mEditorBase;
1002 nsresult rv = editorBase->DeleteSelectionAsAction(nsIEditor::ePrevious,
1003 nsIEditor::eStrip);
1004 if (NS_FAILED(rv)) {
1005 return rv;
1008 // Now that we've actually deleted the selected content,
1009 // check to see if our mExtent has changed, if so, then
1010 // we have to create a new content iterator!
1012 if (origStartNode && origEndNode) {
1013 nsCOMPtr<nsINode> curStartNode, curEndNode;
1014 uint32_t curStartOffset = 0, curEndOffset = 0;
1016 rv = GetRangeEndPoints(mExtent, getter_AddRefs(curStartNode),
1017 &curStartOffset, getter_AddRefs(curEndNode),
1018 &curEndOffset);
1020 if (NS_FAILED(rv)) {
1021 return rv;
1024 if (origStartNode != curStartNode || origEndNode != curEndNode) {
1025 // The range has changed, so we need to create a new content
1026 // iterator based on the new range.
1027 nsCOMPtr<nsIContent> curContent;
1028 if (mIteratorStatus != IteratorStatus::eDone) {
1029 // The old iterator is still pointing to something valid,
1030 // so get its current node so we can restore it after we
1031 // create the new iterator!
1032 curContent = mFilteredIter->GetCurrentNode()
1033 ? mFilteredIter->GetCurrentNode()->AsContent()
1034 : nullptr;
1037 // Create the new iterator.
1038 rv =
1039 CreateFilteredContentIterator(mExtent, getter_AddRefs(mFilteredIter));
1040 if (NS_FAILED(rv)) {
1041 return rv;
1044 // Now make the new iterator point to the content node
1045 // the old one was pointing at.
1046 if (curContent) {
1047 rv = mFilteredIter->PositionAt(curContent);
1048 if (NS_FAILED(rv)) {
1049 mIteratorStatus = IteratorStatus::eDone;
1050 } else {
1051 mIteratorStatus = IteratorStatus::eValid;
1057 OffsetEntry* entry = mOffsetTable.DidDeleteSelection();
1058 if (entry) {
1059 SetSelection(mOffsetTable.mSelection.StartOffsetInTextInBlock(), 0);
1062 // Now remove any invalid entries from the offset table.
1063 mOffsetTable.RemoveInvalidElements();
1064 return NS_OK;
1067 OffsetEntry* TextServicesDocument::OffsetEntryArray::DidDeleteSelection() {
1068 MOZ_ASSERT(mSelection.IsSet());
1070 // Move the caret to the end of the first valid entry.
1071 // Start with SelectionStartIndex() since it may still be valid.
1072 OffsetEntry* entry = nullptr;
1073 for (size_t i = mSelection.StartIndex() + 1; !entry && i > 0; i--) {
1074 entry = ElementAt(i - 1).get();
1075 if (!entry->mIsValid) {
1076 entry = nullptr;
1077 } else {
1078 MOZ_DIAGNOSTIC_ASSERT(i - 1 < Length());
1079 mSelection.Set(i - 1, entry->EndOffsetInTextInBlock());
1083 // If we still don't have a valid entry, move the caret
1084 // to the next valid entry after the selection:
1085 for (size_t i = mSelection.EndIndex(); !entry && i < Length(); i++) {
1086 entry = ElementAt(i).get();
1087 if (!entry->mIsValid) {
1088 entry = nullptr;
1089 } else {
1090 MOZ_DIAGNOSTIC_ASSERT(i < Length());
1091 mSelection.Set(i, entry->mOffsetInTextInBlock);
1095 if (!entry) {
1096 // Uuughh we have no valid offset entry to place our
1097 // caret ... just mark the selection invalid.
1098 mSelection.Reset();
1101 return entry;
1104 nsresult TextServicesDocument::InsertText(const nsAString& aText) {
1105 if (NS_WARN_IF(!mEditorBase) ||
1106 NS_WARN_IF(!mOffsetTable.mSelection.IsSet())) {
1107 return NS_ERROR_FAILURE;
1110 // If the selection is not collapsed, we need to save
1111 // off the selection offsets so we can restore the
1112 // selection and delete the selected content after we've
1113 // inserted the new text. This is necessary to try and
1114 // retain as much of the original style of the content
1115 // being deleted.
1117 const bool wasSelectionCollapsed = mOffsetTable.mSelection.IsCollapsed();
1118 const uint32_t savedSelOffset =
1119 mOffsetTable.mSelection.StartOffsetInTextInBlock();
1120 const uint32_t savedSelLength = mOffsetTable.mSelection.LengthInTextInBlock();
1122 if (!wasSelectionCollapsed) {
1123 // Collapse to the start of the current selection
1124 // for the insert!
1125 nsresult rv =
1126 SetSelection(mOffsetTable.mSelection.StartOffsetInTextInBlock(), 0);
1127 NS_ENSURE_SUCCESS(rv, rv);
1130 // AutoTransactionBatchExternal grabs mEditorBase, so, we don't need to grab
1131 // the instance with local variable here.
1132 OwningNonNull<EditorBase> editorBase = *mEditorBase;
1133 AutoTransactionBatchExternal treatAsOneTransaction(editorBase);
1135 nsresult rv = editorBase->InsertTextAsAction(aText);
1136 if (NS_FAILED(rv)) {
1137 NS_WARNING("InsertTextAsAction() failed");
1138 return rv;
1141 RefPtr<Selection> selection =
1142 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
1143 rv = mOffsetTable.DidInsertText(selection, aText);
1144 if (NS_FAILED(rv)) {
1145 NS_WARNING("TextServicesDocument::OffsetEntry::DidInsertText() failed");
1146 return rv;
1149 if (!wasSelectionCollapsed) {
1150 nsresult rv = SetSelection(savedSelOffset, savedSelLength);
1151 if (NS_FAILED(rv)) {
1152 return rv;
1155 rv = DeleteSelection();
1156 if (NS_FAILED(rv)) {
1157 return rv;
1161 return NS_OK;
1164 nsresult TextServicesDocument::OffsetEntryArray::DidInsertText(
1165 dom::Selection* aSelection, const nsAString& aInsertedString) {
1166 MOZ_ASSERT(mSelection.IsSet());
1168 // When you touch this method, please make sure that the entry instance
1169 // won't be deleted. If you know it'll be deleted, you should set it to
1170 // `nullptr`.
1171 OffsetEntry* entry = ElementAt(mSelection.StartIndex()).get();
1172 OwningNonNull<Text> const textNodeAtStartEntry = entry->mTextNode;
1174 NS_ASSERTION((entry->mIsValid), "Invalid insertion point!");
1176 if (entry->mOffsetInTextInBlock == mSelection.StartOffsetInTextInBlock()) {
1177 if (entry->mIsInsertedText) {
1178 // If the caret is in an inserted text offset entry,
1179 // we simply insert the text at the end of the entry.
1180 entry->mLength += aInsertedString.Length();
1181 } else {
1182 // Insert an inserted text offset entry before the current
1183 // entry!
1184 UniquePtr<OffsetEntry> newInsertedTextEntry =
1185 MakeUnique<OffsetEntry>(entry->mTextNode, entry->mOffsetInTextInBlock,
1186 aInsertedString.Length());
1187 newInsertedTextEntry->mIsInsertedText = true;
1188 newInsertedTextEntry->mOffsetInTextNode = entry->mOffsetInTextNode;
1189 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1190 // pretended earlier.
1191 InsertElementAt(mSelection.StartIndex(), std::move(newInsertedTextEntry));
1193 } else if (entry->EndOffsetInTextInBlock() ==
1194 mSelection.EndOffsetInTextInBlock()) {
1195 // We are inserting text at the end of the current offset entry.
1196 // Look at the next valid entry in the table. If it's an inserted
1197 // text entry, add to its length and adjust its node offset. If
1198 // it isn't, add a new inserted text entry.
1199 uint32_t nextIndex = mSelection.StartIndex() + 1;
1200 OffsetEntry* insertedTextEntry = nullptr;
1201 if (Length() > nextIndex) {
1202 insertedTextEntry = ElementAt(nextIndex).get();
1203 if (!insertedTextEntry) {
1204 return NS_ERROR_FAILURE;
1207 // Check if the entry is a match. If it isn't, set
1208 // iEntry to zero.
1209 if (!insertedTextEntry->mIsInsertedText ||
1210 insertedTextEntry->mOffsetInTextInBlock !=
1211 mSelection.StartOffsetInTextInBlock()) {
1212 insertedTextEntry = nullptr;
1216 if (!insertedTextEntry) {
1217 // We didn't find an inserted text offset entry, so
1218 // create one.
1219 UniquePtr<OffsetEntry> newInsertedTextEntry = MakeUnique<OffsetEntry>(
1220 entry->mTextNode, mSelection.StartOffsetInTextInBlock(), 0);
1221 newInsertedTextEntry->mOffsetInTextNode = entry->EndOffsetInTextNode();
1222 newInsertedTextEntry->mIsInsertedText = true;
1223 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1224 // pretended earlier.
1225 insertedTextEntry =
1226 InsertElementAt(nextIndex, std::move(newInsertedTextEntry))->get();
1229 // We have a valid inserted text offset entry. Update its
1230 // length, adjust the selection indexes, and make sure the
1231 // caret is properly placed!
1233 insertedTextEntry->mLength += aInsertedString.Length();
1235 MOZ_DIAGNOSTIC_ASSERT(nextIndex < Length());
1236 mSelection.SetIndex(nextIndex);
1238 if (!aSelection) {
1239 return NS_OK;
1242 OwningNonNull<Text> textNode = insertedTextEntry->mTextNode;
1243 nsresult rv = aSelection->CollapseInLimiter(
1244 textNode, insertedTextEntry->EndOffsetInTextNode());
1245 if (NS_FAILED(rv)) {
1246 NS_WARNING("Selection::CollapseInLimiter() failed");
1247 return rv;
1249 } else if (entry->EndOffsetInTextInBlock() >
1250 mSelection.StartOffsetInTextInBlock()) {
1251 // We are inserting text into the middle of the current offset entry.
1252 // split the current entry into two parts, then insert an inserted text
1253 // entry between them!
1254 nsresult rv = SplitElementAt(mSelection.StartIndex(),
1255 entry->EndOffsetInTextInBlock() -
1256 mSelection.StartOffsetInTextInBlock());
1257 if (NS_FAILED(rv)) {
1258 NS_WARNING(
1259 "entry->EndOffsetInTextInBlock() - "
1260 "mSelection.StartOffsetInTextInBlock() was invalid for the "
1261 "OffsetEntry");
1262 return rv;
1265 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1266 // pretended earlier.
1267 UniquePtr<OffsetEntry>& insertedTextEntry = *InsertElementAt(
1268 mSelection.StartIndex() + 1,
1269 MakeUnique<OffsetEntry>(entry->mTextNode,
1270 mSelection.StartOffsetInTextInBlock(),
1271 aInsertedString.Length()));
1272 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
1273 insertedTextEntry->mIsInsertedText = true;
1274 insertedTextEntry->mOffsetInTextNode = entry->EndOffsetInTextNode();
1275 MOZ_DIAGNOSTIC_ASSERT(mSelection.StartIndex() + 1 < Length());
1276 mSelection.SetIndex(mSelection.StartIndex() + 1);
1279 // We've just finished inserting an inserted text offset entry.
1280 // update all entries with the same mTextNode pointer that follow
1281 // it in the table!
1283 for (size_t i = mSelection.StartIndex() + 1; i < Length(); i++) {
1284 const UniquePtr<OffsetEntry>& entry = ElementAt(i);
1285 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
1286 if (entry->mTextNode != textNodeAtStartEntry) {
1287 break;
1289 if (entry->mIsValid) {
1290 entry->mOffsetInTextNode += aInsertedString.Length();
1294 return NS_OK;
1297 void TextServicesDocument::DidDeleteContent(const nsIContent& aChildContent) {
1298 if (NS_WARN_IF(!mFilteredIter) || !aChildContent.IsText()) {
1299 return;
1302 Maybe<size_t> maybeNodeIndex =
1303 mOffsetTable.FirstIndexOf(*aChildContent.AsText());
1304 if (maybeNodeIndex.isNothing()) {
1305 // It's okay if the node isn't in the offset table, the
1306 // editor could be cleaning house.
1307 return;
1310 nsINode* node = mFilteredIter->GetCurrentNode();
1311 if (node && node == &aChildContent &&
1312 mIteratorStatus != IteratorStatus::eDone) {
1313 // XXX: This should never really happen because
1314 // AdjustContentIterator() should have been called prior
1315 // to the delete to try and position the iterator on the
1316 // next valid text node in the offset table, and if there
1317 // wasn't a next, it would've set mIteratorStatus to eIsDone.
1319 NS_ERROR("DeleteNode called for current iterator node.");
1322 for (size_t nodeIndex = *maybeNodeIndex; nodeIndex < mOffsetTable.Length();
1323 nodeIndex++) {
1324 const UniquePtr<OffsetEntry>& entry = mOffsetTable[nodeIndex];
1325 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
1326 if (!entry) {
1327 return;
1330 if (entry->mTextNode == &aChildContent) {
1331 entry->mIsValid = false;
1336 void TextServicesDocument::DidJoinContents(
1337 const EditorRawDOMPoint& aJoinedPoint, const nsIContent& aRemovedContent,
1338 JoinNodesDirection aJoinNodesDirection) {
1339 // Make sure that both nodes are text nodes -- otherwise we don't care.
1340 if (!aJoinedPoint.IsInTextNode() || !aRemovedContent.IsText()) {
1341 return;
1344 // Note: The editor merges the contents of the left node into the
1345 // contents of the right.
1347 Maybe<size_t> maybeRemovedIndex =
1348 mOffsetTable.FirstIndexOf(*aRemovedContent.AsText());
1349 if (maybeRemovedIndex.isNothing()) {
1350 // It's okay if the node isn't in the offset table, the
1351 // editor could be cleaning house.
1352 return;
1355 Maybe<size_t> maybeJoinedIndex =
1356 mOffsetTable.FirstIndexOf(*aJoinedPoint.ContainerAsText());
1357 if (maybeJoinedIndex.isNothing()) {
1358 // It's okay if the node isn't in the offset table, the
1359 // editor could be cleaning house.
1360 return;
1363 const size_t removedIndex = *maybeRemovedIndex;
1364 const size_t joinedIndex = *maybeJoinedIndex;
1366 if (aJoinNodesDirection == JoinNodesDirection::LeftNodeIntoRightNode) {
1367 if (MOZ_UNLIKELY(removedIndex > joinedIndex)) {
1368 NS_ASSERTION(removedIndex < joinedIndex, "Indexes out of order.");
1369 return;
1371 NS_ASSERTION(mOffsetTable[joinedIndex]->mOffsetInTextNode == 0,
1372 "Unexpected offset value for joinedIndex.");
1373 } else {
1374 if (MOZ_UNLIKELY(joinedIndex > removedIndex)) {
1375 NS_ASSERTION(joinedIndex < removedIndex, "Indexes out of order.");
1376 return;
1378 NS_ASSERTION(mOffsetTable[removedIndex]->mOffsetInTextNode == 0,
1379 "Unexpected offset value for rightIndex.");
1382 // Run through the table and change all entries referring to
1383 // the removed node so that they now refer to the joined node,
1384 // and adjust offsets if necessary.
1385 const uint32_t movedTextDataLength =
1386 aJoinNodesDirection == JoinNodesDirection::LeftNodeIntoRightNode
1387 ? aJoinedPoint.Offset()
1388 : aJoinedPoint.ContainerAsText()->TextDataLength() -
1389 aJoinedPoint.Offset();
1390 for (uint32_t i = removedIndex; i < mOffsetTable.Length(); i++) {
1391 const UniquePtr<OffsetEntry>& entry = mOffsetTable[i];
1392 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
1393 if (entry->mTextNode != aRemovedContent.AsText()) {
1394 break;
1396 if (entry->mIsValid) {
1397 entry->mTextNode = aJoinedPoint.ContainerAsText();
1398 if (aJoinNodesDirection == JoinNodesDirection::RightNodeIntoLeftNode) {
1399 // The text was moved from aRemovedContent to end of the container of
1400 // aJoinedPoint.
1401 entry->mOffsetInTextNode += movedTextDataLength;
1406 if (aJoinNodesDirection == JoinNodesDirection::LeftNodeIntoRightNode) {
1407 // The text was moved from aRemovedContent to start of the container of
1408 // aJoinedPoint.
1409 for (uint32_t i = joinedIndex; i < mOffsetTable.Length(); i++) {
1410 const UniquePtr<OffsetEntry>& entry = mOffsetTable[i];
1411 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
1412 if (entry->mTextNode != aJoinedPoint.ContainerAsText()) {
1413 break;
1415 if (entry->mIsValid) {
1416 entry->mOffsetInTextNode += movedTextDataLength;
1421 // Now check to see if the iterator is pointing to the
1422 // left node. If it is, make it point to the joined node!
1423 if (mFilteredIter->GetCurrentNode() == aRemovedContent.AsText()) {
1424 mFilteredIter->PositionAt(aJoinedPoint.ContainerAsText());
1428 nsresult TextServicesDocument::CreateFilteredContentIterator(
1429 const AbstractRange* aAbstractRange,
1430 FilteredContentIterator** aFilteredIter) {
1431 if (NS_WARN_IF(!aAbstractRange) || NS_WARN_IF(!aFilteredIter)) {
1432 return NS_ERROR_INVALID_ARG;
1435 *aFilteredIter = nullptr;
1437 UniquePtr<nsComposeTxtSrvFilter> composeFilter;
1438 switch (mTxtSvcFilterType) {
1439 case nsIEditorSpellCheck::FILTERTYPE_NORMAL:
1440 composeFilter = nsComposeTxtSrvFilter::CreateNormalFilter();
1441 break;
1442 case nsIEditorSpellCheck::FILTERTYPE_MAIL:
1443 composeFilter = nsComposeTxtSrvFilter::CreateMailFilter();
1444 break;
1447 // Create a FilteredContentIterator
1448 // This class wraps the ContentIterator in order to give itself a chance
1449 // to filter out certain content nodes
1450 RefPtr<FilteredContentIterator> filter =
1451 new FilteredContentIterator(std::move(composeFilter));
1452 nsresult rv = filter->Init(aAbstractRange);
1453 if (NS_FAILED(rv)) {
1454 return rv;
1457 filter.forget(aFilteredIter);
1458 return NS_OK;
1461 Element* TextServicesDocument::GetDocumentContentRootNode() const {
1462 if (NS_WARN_IF(!mDocument)) {
1463 return nullptr;
1466 if (mDocument->IsHTMLOrXHTML()) {
1467 Element* rootElement = mDocument->GetRootElement();
1468 if (rootElement && rootElement->IsXULElement()) {
1469 // HTML documents with root XUL elements should eventually be transitioned
1470 // to a regular document structure, but for now the content root node will
1471 // be the document element.
1472 return mDocument->GetDocumentElement();
1474 // For HTML documents, the content root node is the body.
1475 return mDocument->GetBody();
1478 // For non-HTML documents, the content root node will be the document element.
1479 return mDocument->GetDocumentElement();
1482 already_AddRefed<nsRange> TextServicesDocument::CreateDocumentContentRange() {
1483 nsCOMPtr<nsINode> node = GetDocumentContentRootNode();
1484 if (NS_WARN_IF(!node)) {
1485 return nullptr;
1488 RefPtr<nsRange> range = nsRange::Create(node);
1489 IgnoredErrorResult ignoredError;
1490 range->SelectNodeContents(*node, ignoredError);
1491 NS_WARNING_ASSERTION(!ignoredError.Failed(), "SelectNodeContents() failed");
1492 return range.forget();
1495 already_AddRefed<nsRange>
1496 TextServicesDocument::CreateDocumentContentRootToNodeOffsetRange(
1497 nsINode* aParent, uint32_t aOffset, bool aToStart) {
1498 if (NS_WARN_IF(!aParent)) {
1499 return nullptr;
1502 nsCOMPtr<nsINode> bodyNode = GetDocumentContentRootNode();
1503 if (NS_WARN_IF(!bodyNode)) {
1504 return nullptr;
1507 nsCOMPtr<nsINode> startNode;
1508 nsCOMPtr<nsINode> endNode;
1509 uint32_t startOffset, endOffset;
1511 if (aToStart) {
1512 // The range should begin at the start of the document
1513 // and extend up until (aParent, aOffset).
1514 startNode = bodyNode;
1515 startOffset = 0;
1516 endNode = aParent;
1517 endOffset = aOffset;
1518 } else {
1519 // The range should begin at (aParent, aOffset) and
1520 // extend to the end of the document.
1521 startNode = aParent;
1522 startOffset = aOffset;
1523 endNode = bodyNode;
1524 endOffset = endNode ? endNode->GetChildCount() : 0;
1527 RefPtr<nsRange> range = nsRange::Create(startNode, startOffset, endNode,
1528 endOffset, IgnoreErrors());
1529 NS_WARNING_ASSERTION(range,
1530 "nsRange::Create() failed to create new valid range");
1531 return range.forget();
1534 nsresult TextServicesDocument::CreateDocumentContentIterator(
1535 FilteredContentIterator** aFilteredIter) {
1536 NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER);
1538 RefPtr<nsRange> range = CreateDocumentContentRange();
1539 if (NS_WARN_IF(!range)) {
1540 *aFilteredIter = nullptr;
1541 return NS_ERROR_FAILURE;
1544 return CreateFilteredContentIterator(range, aFilteredIter);
1547 nsresult TextServicesDocument::AdjustContentIterator() {
1548 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
1550 nsCOMPtr<nsINode> node = mFilteredIter->GetCurrentNode();
1551 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
1553 Text* prevValidTextNode = nullptr;
1554 Text* nextValidTextNode = nullptr;
1555 bool foundEntry = false;
1557 const size_t tableLength = mOffsetTable.Length();
1558 for (size_t i = 0; i < tableLength && !nextValidTextNode; i++) {
1559 UniquePtr<OffsetEntry>& entry = mOffsetTable[i];
1560 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
1561 if (entry->mTextNode == node) {
1562 if (entry->mIsValid) {
1563 // The iterator is still pointing to something valid!
1564 // Do nothing!
1565 return NS_OK;
1567 // We found an invalid entry that points to
1568 // the current iterator node. Stop looking for
1569 // a previous valid node!
1570 foundEntry = true;
1573 if (entry->mIsValid) {
1574 if (!foundEntry) {
1575 prevValidTextNode = entry->mTextNode;
1576 } else {
1577 nextValidTextNode = entry->mTextNode;
1582 Text* validTextNode = nullptr;
1583 if (prevValidTextNode) {
1584 validTextNode = prevValidTextNode;
1585 } else if (nextValidTextNode) {
1586 validTextNode = nextValidTextNode;
1589 if (validTextNode) {
1590 nsresult rv = mFilteredIter->PositionAt(validTextNode);
1591 if (NS_FAILED(rv)) {
1592 mIteratorStatus = IteratorStatus::eDone;
1593 } else {
1594 mIteratorStatus = IteratorStatus::eValid;
1596 return rv;
1599 // If we get here, there aren't any valid entries
1600 // in the offset table! Try to position the iterator
1601 // on the next text block first, then previous if
1602 // one doesn't exist!
1604 if (mNextTextBlock) {
1605 nsresult rv = mFilteredIter->PositionAt(mNextTextBlock);
1606 if (NS_FAILED(rv)) {
1607 mIteratorStatus = IteratorStatus::eDone;
1608 return rv;
1611 mIteratorStatus = IteratorStatus::eNext;
1612 } else if (mPrevTextBlock) {
1613 nsresult rv = mFilteredIter->PositionAt(mPrevTextBlock);
1614 if (NS_FAILED(rv)) {
1615 mIteratorStatus = IteratorStatus::eDone;
1616 return rv;
1619 mIteratorStatus = IteratorStatus::ePrev;
1620 } else {
1621 mIteratorStatus = IteratorStatus::eDone;
1623 return NS_OK;
1626 // static
1627 bool TextServicesDocument::DidSkip(FilteredContentIterator* aFilteredIter) {
1628 return aFilteredIter && aFilteredIter->DidSkip();
1631 // static
1632 void TextServicesDocument::ClearDidSkip(
1633 FilteredContentIterator* aFilteredIter) {
1634 // Clear filter's skip flag
1635 if (aFilteredIter) {
1636 aFilteredIter->ClearDidSkip();
1640 // static
1641 bool TextServicesDocument::HasSameBlockNodeParent(Text& aTextNode1,
1642 Text& aTextNode2) {
1643 // XXX How about the case that both text nodes are orphan nodes?
1644 if (aTextNode1.GetParent() == aTextNode2.GetParent()) {
1645 return true;
1648 // I think that spellcheck should be available only in editable nodes.
1649 // So, we also need to check whether they are in same editing host.
1650 const Element* editableBlockElementOrInlineEditingHost1 =
1651 HTMLEditUtils::GetAncestorElement(
1652 aTextNode1,
1653 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost);
1654 const Element* editableBlockElementOrInlineEditingHost2 =
1655 HTMLEditUtils::GetAncestorElement(
1656 aTextNode2,
1657 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost);
1658 return editableBlockElementOrInlineEditingHost1 &&
1659 editableBlockElementOrInlineEditingHost1 ==
1660 editableBlockElementOrInlineEditingHost2;
1663 Result<EditorRawDOMRangeInTexts, nsresult>
1664 TextServicesDocument::OffsetEntryArray::WillSetSelection(
1665 uint32_t aOffsetInTextInBlock, uint32_t aLength) {
1666 // Find start of selection in node offset terms:
1667 EditorRawDOMPointInText newStart;
1668 for (size_t i = 0; !newStart.IsSet() && i < Length(); i++) {
1669 const UniquePtr<OffsetEntry>& entry = ElementAt(i);
1670 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
1671 if (entry->mIsValid) {
1672 if (entry->mIsInsertedText) {
1673 // Caret can only be placed at the end of an
1674 // inserted text offset entry, if the offsets
1675 // match exactly!
1676 if (entry->mOffsetInTextInBlock == aOffsetInTextInBlock) {
1677 newStart.Set(entry->mTextNode, entry->EndOffsetInTextNode());
1679 } else if (aOffsetInTextInBlock >= entry->mOffsetInTextInBlock) {
1680 bool foundEntry = false;
1681 if (aOffsetInTextInBlock < entry->EndOffsetInTextInBlock()) {
1682 foundEntry = true;
1683 } else if (aOffsetInTextInBlock == entry->EndOffsetInTextInBlock()) {
1684 // Peek after this entry to see if we have any
1685 // inserted text entries belonging to the same
1686 // entry->mTextNode. If so, we have to place the selection
1687 // after it!
1688 if (i + 1 < Length()) {
1689 const UniquePtr<OffsetEntry>& nextEntry = ElementAt(i + 1);
1690 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
1691 if (!nextEntry->mIsValid ||
1692 nextEntry->mOffsetInTextInBlock != aOffsetInTextInBlock) {
1693 // Next offset entry isn't an exact match, so we'll
1694 // just use the current entry.
1695 foundEntry = true;
1700 if (foundEntry) {
1701 newStart.Set(entry->mTextNode, entry->mOffsetInTextNode +
1702 aOffsetInTextInBlock -
1703 entry->mOffsetInTextInBlock);
1707 if (newStart.IsSet()) {
1708 MOZ_DIAGNOSTIC_ASSERT(i < Length());
1709 mSelection.Set(i, aOffsetInTextInBlock);
1714 if (NS_WARN_IF(!newStart.IsSet())) {
1715 return Err(NS_ERROR_FAILURE);
1718 if (!aLength) {
1719 mSelection.CollapseToStart();
1720 return EditorRawDOMRangeInTexts(newStart);
1723 // Find the end of the selection in node offset terms:
1724 EditorRawDOMPointInText newEnd;
1725 const uint32_t endOffset = aOffsetInTextInBlock + aLength;
1726 for (uint32_t i = Length(); !newEnd.IsSet() && i > 0; i--) {
1727 const UniquePtr<OffsetEntry>& entry = ElementAt(i - 1);
1728 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
1729 if (entry->mIsValid) {
1730 if (entry->mIsInsertedText) {
1731 if (entry->mOffsetInTextInBlock ==
1732 (newEnd.IsSet() ? newEnd.Offset() : 0)) {
1733 // If the selection ends on an inserted text offset entry,
1734 // the selection includes the entire entry!
1735 newEnd.Set(entry->mTextNode, entry->EndOffsetInTextNode());
1737 } else if (entry->OffsetInTextInBlockIsInRangeOrEndOffset(endOffset)) {
1738 newEnd.Set(entry->mTextNode, entry->mOffsetInTextNode + endOffset -
1739 entry->mOffsetInTextInBlock);
1742 if (newEnd.IsSet()) {
1743 MOZ_DIAGNOSTIC_ASSERT(mSelection.StartIndex() < Length());
1744 MOZ_DIAGNOSTIC_ASSERT(i - 1 < Length());
1745 mSelection.Set(mSelection.StartIndex(), i - 1,
1746 mSelection.StartOffsetInTextInBlock(), endOffset);
1751 return newEnd.IsSet() ? EditorRawDOMRangeInTexts(newStart, newEnd)
1752 : EditorRawDOMRangeInTexts(newStart);
1755 nsresult TextServicesDocument::SetSelectionInternal(
1756 uint32_t aOffsetInTextInBlock, uint32_t aLength, bool aDoUpdate) {
1757 if (NS_WARN_IF(!mSelCon)) {
1758 return NS_ERROR_INVALID_ARG;
1761 Result<EditorRawDOMRangeInTexts, nsresult> newSelectionRange =
1762 mOffsetTable.WillSetSelection(aOffsetInTextInBlock, aLength);
1763 if (newSelectionRange.isErr()) {
1764 NS_WARNING(
1765 "TextServicesDocument::OffsetEntryArray::WillSetSelection() failed");
1766 return newSelectionRange.unwrapErr();
1769 if (!aDoUpdate) {
1770 return NS_OK;
1773 // XXX: If we ever get a SetSelection() method in nsIEditor, we should
1774 // use it.
1775 RefPtr<Selection> selection =
1776 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
1777 if (NS_WARN_IF(!selection)) {
1778 return NS_ERROR_FAILURE;
1781 if (newSelectionRange.inspect().Collapsed()) {
1782 nsresult rv =
1783 selection->CollapseInLimiter(newSelectionRange.inspect().StartRef());
1784 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1785 "Selection::CollapseInLimiter() failed");
1786 return rv;
1789 ErrorResult error;
1790 selection->SetStartAndEndInLimiter(newSelectionRange.inspect().StartRef(),
1791 newSelectionRange.inspect().EndRef(),
1792 error);
1793 NS_WARNING_ASSERTION(!error.Failed(),
1794 "Selection::SetStartAndEndInLimiter() failed");
1795 return error.StealNSResult();
1798 nsresult TextServicesDocument::GetSelection(BlockSelectionStatus* aSelStatus,
1799 uint32_t* aSelOffset,
1800 uint32_t* aSelLength) {
1801 NS_ENSURE_TRUE(aSelStatus && aSelOffset && aSelLength, NS_ERROR_NULL_POINTER);
1803 *aSelStatus = BlockSelectionStatus::eBlockNotFound;
1804 *aSelOffset = UINT32_MAX;
1805 *aSelLength = UINT32_MAX;
1807 NS_ENSURE_TRUE(mDocument && mSelCon, NS_ERROR_FAILURE);
1809 if (mIteratorStatus == IteratorStatus::eDone) {
1810 return NS_OK;
1813 RefPtr<Selection> selection =
1814 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
1815 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1817 if (selection->IsCollapsed()) {
1818 return GetCollapsedSelection(aSelStatus, aSelOffset, aSelLength);
1821 return GetUncollapsedSelection(aSelStatus, aSelOffset, aSelLength);
1824 nsresult TextServicesDocument::GetCollapsedSelection(
1825 BlockSelectionStatus* aSelStatus, uint32_t* aSelOffset,
1826 uint32_t* aSelLength) {
1827 RefPtr<Selection> selection =
1828 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
1829 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1831 // The calling function should have done the GetIsCollapsed()
1832 // check already. Just assume it's collapsed!
1833 *aSelStatus = BlockSelectionStatus::eBlockOutside;
1834 *aSelOffset = *aSelLength = UINT32_MAX;
1836 const uint32_t tableCount = mOffsetTable.Length();
1837 if (!tableCount) {
1838 return NS_OK;
1841 // Get pointers to the first and last offset entries
1842 // in the table.
1844 UniquePtr<OffsetEntry>& eStart = mOffsetTable[0];
1845 UniquePtr<OffsetEntry>& eEnd =
1846 tableCount > 1 ? mOffsetTable[tableCount - 1] : eStart;
1847 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
1849 const uint32_t eStartOffset = eStart->mOffsetInTextNode;
1850 const uint32_t eEndOffset = eEnd->EndOffsetInTextNode();
1852 RefPtr<const nsRange> range = selection->GetRangeAt(0);
1853 NS_ENSURE_STATE(range);
1855 nsCOMPtr<nsINode> parent = range->GetStartContainer();
1856 MOZ_ASSERT(parent);
1858 uint32_t offset = range->StartOffset();
1860 const Maybe<int32_t> e1s1 = nsContentUtils::ComparePoints(
1861 eStart->mTextNode, eStartOffset, parent, offset);
1862 const Maybe<int32_t> e2s1 = nsContentUtils::ComparePoints(
1863 eEnd->mTextNode, eEndOffset, parent, offset);
1865 if (MOZ_UNLIKELY(NS_WARN_IF(!e1s1) || NS_WARN_IF(!e2s1))) {
1866 return NS_ERROR_FAILURE;
1869 if (*e1s1 > 0 || *e2s1 < 0) {
1870 // We're done if the caret is outside the current text block.
1871 return NS_OK;
1874 if (parent->IsText()) {
1875 // Good news, the caret is in a text node. Look
1876 // through the offset table for the entry that
1877 // matches its parent and offset.
1879 for (uint32_t i = 0; i < tableCount; i++) {
1880 const UniquePtr<OffsetEntry>& entry = mOffsetTable[i];
1881 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
1882 if (entry->mTextNode == parent->AsText() &&
1883 entry->OffsetInTextNodeIsInRangeOrEndOffset(offset)) {
1884 *aSelStatus = BlockSelectionStatus::eBlockContains;
1885 *aSelOffset =
1886 entry->mOffsetInTextInBlock + (offset - entry->mOffsetInTextNode);
1887 *aSelLength = 0;
1888 return NS_OK;
1892 // If we get here, we didn't find a text node entry
1893 // in our offset table that matched.
1894 return NS_ERROR_FAILURE;
1897 // The caret is in our text block, but it's positioned in some
1898 // non-text node (ex. <b>). Create a range based on the start
1899 // and end of the text block, then create an iterator based on
1900 // this range, with its initial position set to the closest
1901 // child of this non-text node. Then look for the closest text
1902 // node.
1904 range = nsRange::Create(eStart->mTextNode, eStartOffset, eEnd->mTextNode,
1905 eEndOffset, IgnoreErrors());
1906 if (NS_WARN_IF(!range)) {
1907 return NS_ERROR_FAILURE;
1910 RefPtr<FilteredContentIterator> filteredIter;
1911 nsresult rv =
1912 CreateFilteredContentIterator(range, getter_AddRefs(filteredIter));
1913 NS_ENSURE_SUCCESS(rv, rv);
1915 nsIContent* saveNode;
1916 if (parent->HasChildren()) {
1917 // XXX: We need to make sure that all of parent's
1918 // children are in the text block.
1920 // If the parent has children, position the iterator
1921 // on the child that is to the left of the offset.
1923 nsIContent* content = range->GetChildAtStartOffset();
1924 if (content && parent->GetFirstChild() != content) {
1925 content = content->GetPreviousSibling();
1927 NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
1929 nsresult rv = filteredIter->PositionAt(content);
1930 NS_ENSURE_SUCCESS(rv, rv);
1932 saveNode = content;
1933 } else {
1934 // The parent has no children, so position the iterator
1935 // on the parent.
1936 NS_ENSURE_TRUE(parent->IsContent(), NS_ERROR_FAILURE);
1937 nsCOMPtr<nsIContent> content = parent->AsContent();
1939 nsresult rv = filteredIter->PositionAt(content);
1940 NS_ENSURE_SUCCESS(rv, rv);
1942 saveNode = content;
1945 // Now iterate to the left, towards the beginning of
1946 // the text block, to find the first text node you
1947 // come across.
1949 Text* textNode = nullptr;
1950 for (; !filteredIter->IsDone(); filteredIter->Prev()) {
1951 nsINode* current = filteredIter->GetCurrentNode();
1952 if (current->IsText()) {
1953 textNode = current->AsText();
1954 break;
1958 if (textNode) {
1959 // We found a node, now set the offset to the end
1960 // of the text node.
1961 offset = textNode->TextLength();
1962 } else {
1963 // We should never really get here, but I'm paranoid.
1965 // We didn't find a text node above, so iterate to
1966 // the right, towards the end of the text block, looking
1967 // for a text node.
1969 nsresult rv = filteredIter->PositionAt(saveNode);
1970 NS_ENSURE_SUCCESS(rv, rv);
1972 textNode = nullptr;
1973 for (; !filteredIter->IsDone(); filteredIter->Next()) {
1974 nsINode* current = filteredIter->GetCurrentNode();
1975 if (current->IsText()) {
1976 textNode = current->AsText();
1977 break;
1980 NS_ENSURE_TRUE(textNode, NS_ERROR_FAILURE);
1982 // We found a text node, so set the offset to
1983 // the beginning of the node.
1984 offset = 0;
1987 for (size_t i = 0; i < tableCount; i++) {
1988 const UniquePtr<OffsetEntry>& entry = mOffsetTable[i];
1989 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
1990 if (entry->mTextNode == textNode &&
1991 entry->OffsetInTextNodeIsInRangeOrEndOffset(offset)) {
1992 *aSelStatus = BlockSelectionStatus::eBlockContains;
1993 *aSelOffset =
1994 entry->mOffsetInTextInBlock + (offset - entry->mOffsetInTextNode);
1995 *aSelLength = 0;
1997 // Now move the caret so that it is actually in the text node.
1998 // We do this to keep things in sync.
2000 // In most cases, the user shouldn't see any movement in the caret
2001 // on screen.
2002 return SetSelectionInternal(*aSelOffset, *aSelLength, true);
2006 return NS_ERROR_FAILURE;
2009 nsresult TextServicesDocument::GetUncollapsedSelection(
2010 BlockSelectionStatus* aSelStatus, uint32_t* aSelOffset,
2011 uint32_t* aSelLength) {
2012 RefPtr<const nsRange> range;
2013 RefPtr<Selection> selection =
2014 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
2015 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
2017 // It is assumed that the calling function has made sure that the
2018 // selection is not collapsed, and that the input params to this
2019 // method are initialized to some defaults.
2021 nsCOMPtr<nsINode> startContainer, endContainer;
2023 const size_t tableCount = mOffsetTable.Length();
2025 // Get pointers to the first and last offset entries
2026 // in the table.
2028 UniquePtr<OffsetEntry>& eStart = mOffsetTable[0];
2029 UniquePtr<OffsetEntry>& eEnd =
2030 tableCount > 1 ? mOffsetTable[tableCount - 1] : eStart;
2031 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
2033 const uint32_t eStartOffset = eStart->mOffsetInTextNode;
2034 const uint32_t eEndOffset = eEnd->EndOffsetInTextNode();
2036 const uint32_t rangeCount = selection->RangeCount();
2037 MOZ_ASSERT(rangeCount);
2039 // Find the first range in the selection that intersects
2040 // the current text block.
2041 Maybe<int32_t> e1s2;
2042 Maybe<int32_t> e2s1;
2043 uint32_t startOffset, endOffset;
2044 for (const uint32_t i : IntegerRange(rangeCount)) {
2045 MOZ_ASSERT(selection->RangeCount() == rangeCount);
2046 range = selection->GetRangeAt(i);
2047 if (MOZ_UNLIKELY(NS_WARN_IF(!range))) {
2048 return NS_ERROR_FAILURE;
2051 nsresult rv =
2052 GetRangeEndPoints(range, getter_AddRefs(startContainer), &startOffset,
2053 getter_AddRefs(endContainer), &endOffset);
2055 NS_ENSURE_SUCCESS(rv, rv);
2057 e1s2 = nsContentUtils::ComparePoints(eStart->mTextNode, eStartOffset,
2058 endContainer, endOffset);
2059 if (NS_WARN_IF(!e1s2)) {
2060 return NS_ERROR_FAILURE;
2063 e2s1 = nsContentUtils::ComparePoints(eEnd->mTextNode, eEndOffset,
2064 startContainer, startOffset);
2065 if (NS_WARN_IF(!e2s1)) {
2066 return NS_ERROR_FAILURE;
2069 // Break out of the loop if the text block intersects the current range.
2071 if (*e1s2 <= 0 && *e2s1 >= 0) {
2072 break;
2076 // We're done if we didn't find an intersecting range.
2078 if (rangeCount < 1 || *e1s2 > 0 || *e2s1 < 0) {
2079 *aSelStatus = BlockSelectionStatus::eBlockOutside;
2080 *aSelOffset = *aSelLength = UINT32_MAX;
2081 return NS_OK;
2084 // Now that we have an intersecting range, find out more info:
2085 const Maybe<int32_t> e1s1 = nsContentUtils::ComparePoints(
2086 eStart->mTextNode, eStartOffset, startContainer, startOffset);
2087 if (NS_WARN_IF(!e1s1)) {
2088 return NS_ERROR_FAILURE;
2091 const Maybe<int32_t> e2s2 = nsContentUtils::ComparePoints(
2092 eEnd->mTextNode, eEndOffset, endContainer, endOffset);
2093 if (NS_WARN_IF(!e2s2)) {
2094 return NS_ERROR_FAILURE;
2097 if (rangeCount > 1) {
2098 // There are multiple selection ranges, we only deal
2099 // with the first one that intersects the current,
2100 // text block, so mark this a as a partial.
2101 *aSelStatus = BlockSelectionStatus::eBlockPartial;
2102 } else if (*e1s1 > 0 && *e2s2 < 0) {
2103 // The range extends beyond the start and
2104 // end of the current text block.
2105 *aSelStatus = BlockSelectionStatus::eBlockInside;
2106 } else if (*e1s1 <= 0 && *e2s2 >= 0) {
2107 // The current text block contains the entire
2108 // range.
2109 *aSelStatus = BlockSelectionStatus::eBlockContains;
2110 } else {
2111 // The range partially intersects the block.
2112 *aSelStatus = BlockSelectionStatus::eBlockPartial;
2115 // Now create a range based on the intersection of the
2116 // text block and range:
2118 nsCOMPtr<nsINode> p1, p2;
2119 uint32_t o1, o2;
2121 // The start of the range will be the rightmost
2122 // start node.
2124 if (*e1s1 >= 0) {
2125 p1 = eStart->mTextNode;
2126 o1 = eStartOffset;
2127 } else {
2128 p1 = startContainer;
2129 o1 = startOffset;
2132 // The end of the range will be the leftmost
2133 // end node.
2135 if (*e2s2 <= 0) {
2136 p2 = eEnd->mTextNode;
2137 o2 = eEndOffset;
2138 } else {
2139 p2 = endContainer;
2140 o2 = endOffset;
2143 range = nsRange::Create(p1, o1, p2, o2, IgnoreErrors());
2144 if (NS_WARN_IF(!range)) {
2145 return NS_ERROR_FAILURE;
2148 // Now iterate over this range to figure out the selection's
2149 // block offset and length.
2151 RefPtr<FilteredContentIterator> filteredIter;
2152 nsresult rv =
2153 CreateFilteredContentIterator(range, getter_AddRefs(filteredIter));
2154 NS_ENSURE_SUCCESS(rv, rv);
2156 // Find the first text node in the range.
2157 nsCOMPtr<nsIContent> content;
2158 filteredIter->First();
2159 if (!p1->IsText()) {
2160 bool found = false;
2161 for (; !filteredIter->IsDone(); filteredIter->Next()) {
2162 nsINode* node = filteredIter->GetCurrentNode();
2163 if (node->IsText()) {
2164 p1 = node->AsText();
2165 o1 = 0;
2166 found = true;
2167 break;
2170 NS_ENSURE_TRUE(found, NS_ERROR_FAILURE);
2173 // Find the last text node in the range.
2174 filteredIter->Last();
2175 if (!p2->IsText()) {
2176 bool found = false;
2177 for (; !filteredIter->IsDone(); filteredIter->Prev()) {
2178 nsINode* node = filteredIter->GetCurrentNode();
2179 if (node->IsText()) {
2180 p2 = node->AsText();
2181 o2 = p2->AsText()->Length();
2182 found = true;
2184 break;
2187 NS_ENSURE_TRUE(found, NS_ERROR_FAILURE);
2190 bool found = false;
2191 *aSelLength = 0;
2193 for (size_t i = 0; i < tableCount; i++) {
2194 const UniquePtr<OffsetEntry>& entry = mOffsetTable[i];
2195 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
2196 if (!found) {
2197 if (entry->mTextNode == p1.get() &&
2198 entry->OffsetInTextNodeIsInRangeOrEndOffset(o1)) {
2199 *aSelOffset =
2200 entry->mOffsetInTextInBlock + (o1 - entry->mOffsetInTextNode);
2201 if (p1 == p2 && entry->OffsetInTextNodeIsInRangeOrEndOffset(o2)) {
2202 // The start and end of the range are in the same offset
2203 // entry. Calculate the length of the range then we're done.
2204 *aSelLength = o2 - o1;
2205 break;
2207 // Add the length of the sub string in this offset entry
2208 // that follows the start of the range.
2209 *aSelLength = entry->EndOffsetInTextNode() - o1;
2210 found = true;
2212 } else { // Found.
2213 if (entry->mTextNode == p2.get() &&
2214 entry->OffsetInTextNodeIsInRangeOrEndOffset(o2)) {
2215 // We found the end of the range. Calculate the length of the
2216 // sub string that is before the end of the range, then we're done.
2217 *aSelLength += o2 - entry->mOffsetInTextNode;
2218 break;
2220 // The entire entry must be in the range.
2221 *aSelLength += entry->mLength;
2225 return NS_OK;
2228 // static
2229 nsresult TextServicesDocument::GetRangeEndPoints(
2230 const AbstractRange* aAbstractRange, nsINode** aStartContainer,
2231 uint32_t* aStartOffset, nsINode** aEndContainer, uint32_t* aEndOffset) {
2232 if (NS_WARN_IF(!aAbstractRange) || NS_WARN_IF(!aStartContainer) ||
2233 NS_WARN_IF(!aEndContainer) || NS_WARN_IF(!aEndOffset)) {
2234 return NS_ERROR_INVALID_ARG;
2237 nsCOMPtr<nsINode> startContainer = aAbstractRange->GetStartContainer();
2238 if (NS_WARN_IF(!startContainer)) {
2239 return NS_ERROR_FAILURE;
2241 nsCOMPtr<nsINode> endContainer = aAbstractRange->GetEndContainer();
2242 if (NS_WARN_IF(!endContainer)) {
2243 return NS_ERROR_FAILURE;
2246 startContainer.forget(aStartContainer);
2247 endContainer.forget(aEndContainer);
2248 *aStartOffset = aAbstractRange->StartOffset();
2249 *aEndOffset = aAbstractRange->EndOffset();
2250 return NS_OK;
2253 // static
2254 nsresult TextServicesDocument::FirstTextNode(
2255 FilteredContentIterator* aFilteredIter, IteratorStatus* aIteratorStatus) {
2256 if (aIteratorStatus) {
2257 *aIteratorStatus = IteratorStatus::eDone;
2260 for (aFilteredIter->First(); !aFilteredIter->IsDone();
2261 aFilteredIter->Next()) {
2262 if (aFilteredIter->GetCurrentNode()->NodeType() == nsINode::TEXT_NODE) {
2263 if (aIteratorStatus) {
2264 *aIteratorStatus = IteratorStatus::eValid;
2266 break;
2270 return NS_OK;
2273 // static
2274 nsresult TextServicesDocument::LastTextNode(
2275 FilteredContentIterator* aFilteredIter, IteratorStatus* aIteratorStatus) {
2276 if (aIteratorStatus) {
2277 *aIteratorStatus = IteratorStatus::eDone;
2280 for (aFilteredIter->Last(); !aFilteredIter->IsDone(); aFilteredIter->Prev()) {
2281 if (aFilteredIter->GetCurrentNode()->NodeType() == nsINode::TEXT_NODE) {
2282 if (aIteratorStatus) {
2283 *aIteratorStatus = IteratorStatus::eValid;
2285 break;
2289 return NS_OK;
2292 // static
2293 nsresult TextServicesDocument::FirstTextNodeInCurrentBlock(
2294 FilteredContentIterator* aFilteredIter) {
2295 NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER);
2297 ClearDidSkip(aFilteredIter);
2299 // Walk backwards over adjacent text nodes until
2300 // we hit a block boundary:
2301 RefPtr<Text> lastTextNode;
2302 while (!aFilteredIter->IsDone()) {
2303 nsCOMPtr<nsIContent> content =
2304 aFilteredIter->GetCurrentNode()->IsContent()
2305 ? aFilteredIter->GetCurrentNode()->AsContent()
2306 : nullptr;
2307 if (lastTextNode && content &&
2308 (HTMLEditUtils::IsBlockElement(*content) ||
2309 content->IsHTMLElement(nsGkAtoms::br))) {
2310 break;
2312 if (content && content->IsText()) {
2313 if (lastTextNode && !TextServicesDocument::HasSameBlockNodeParent(
2314 *content->AsText(), *lastTextNode)) {
2315 // We're done, the current text node is in a
2316 // different block.
2317 break;
2319 lastTextNode = content->AsText();
2322 aFilteredIter->Prev();
2324 if (DidSkip(aFilteredIter)) {
2325 break;
2329 if (lastTextNode) {
2330 aFilteredIter->PositionAt(lastTextNode);
2333 // XXX: What should we return if last is null?
2335 return NS_OK;
2338 // static
2339 nsresult TextServicesDocument::FirstTextNodeInPrevBlock(
2340 FilteredContentIterator* aFilteredIter) {
2341 NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER);
2343 // XXX: What if mFilteredIter is not currently on a text node?
2345 // Make sure mFilteredIter is pointing to the first text node in the
2346 // current block:
2348 nsresult rv = FirstTextNodeInCurrentBlock(aFilteredIter);
2350 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
2352 // Point mFilteredIter to the first node before the first text node:
2354 aFilteredIter->Prev();
2356 if (aFilteredIter->IsDone()) {
2357 return NS_ERROR_FAILURE;
2360 // Now find the first text node of the next block:
2362 return FirstTextNodeInCurrentBlock(aFilteredIter);
2365 // static
2366 nsresult TextServicesDocument::FirstTextNodeInNextBlock(
2367 FilteredContentIterator* aFilteredIter) {
2368 bool crossedBlockBoundary = false;
2370 NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER);
2372 ClearDidSkip(aFilteredIter);
2374 RefPtr<Text> previousTextNode;
2375 while (!aFilteredIter->IsDone()) {
2376 if (nsCOMPtr<nsIContent> content =
2377 aFilteredIter->GetCurrentNode()->IsContent()
2378 ? aFilteredIter->GetCurrentNode()->AsContent()
2379 : nullptr) {
2380 if (content->IsText()) {
2381 if (crossedBlockBoundary ||
2382 (previousTextNode && !TextServicesDocument::HasSameBlockNodeParent(
2383 *previousTextNode, *content->AsText()))) {
2384 break;
2386 previousTextNode = content->AsText();
2387 } else if (!crossedBlockBoundary &&
2388 (HTMLEditUtils::IsBlockElement(*content) ||
2389 content->IsHTMLElement(nsGkAtoms::br))) {
2390 crossedBlockBoundary = true;
2394 aFilteredIter->Next();
2396 if (!crossedBlockBoundary && DidSkip(aFilteredIter)) {
2397 crossedBlockBoundary = true;
2401 return NS_OK;
2404 nsresult TextServicesDocument::GetFirstTextNodeInPrevBlock(
2405 nsIContent** aContent) {
2406 NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER);
2408 *aContent = 0;
2410 // Save the iterator's current content node so we can restore
2411 // it when we are done:
2413 nsINode* node = mFilteredIter->GetCurrentNode();
2415 nsresult rv = FirstTextNodeInPrevBlock(mFilteredIter);
2417 if (NS_FAILED(rv)) {
2418 // Try to restore the iterator before returning.
2419 mFilteredIter->PositionAt(node);
2420 return rv;
2423 if (!mFilteredIter->IsDone()) {
2424 nsCOMPtr<nsIContent> current =
2425 mFilteredIter->GetCurrentNode()->IsContent()
2426 ? mFilteredIter->GetCurrentNode()->AsContent()
2427 : nullptr;
2428 current.forget(aContent);
2431 // Restore the iterator:
2433 return mFilteredIter->PositionAt(node);
2436 nsresult TextServicesDocument::GetFirstTextNodeInNextBlock(
2437 nsIContent** aContent) {
2438 NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER);
2440 *aContent = 0;
2442 // Save the iterator's current content node so we can restore
2443 // it when we are done:
2445 nsINode* node = mFilteredIter->GetCurrentNode();
2447 nsresult rv = FirstTextNodeInNextBlock(mFilteredIter);
2449 if (NS_FAILED(rv)) {
2450 // Try to restore the iterator before returning.
2451 mFilteredIter->PositionAt(node);
2452 return rv;
2455 if (!mFilteredIter->IsDone()) {
2456 nsCOMPtr<nsIContent> current =
2457 mFilteredIter->GetCurrentNode()->IsContent()
2458 ? mFilteredIter->GetCurrentNode()->AsContent()
2459 : nullptr;
2460 current.forget(aContent);
2463 // Restore the iterator:
2464 return mFilteredIter->PositionAt(node);
2467 Result<TextServicesDocument::IteratorStatus, nsresult>
2468 TextServicesDocument::OffsetEntryArray::Init(
2469 FilteredContentIterator& aFilteredIter, IteratorStatus aIteratorStatus,
2470 nsRange* aIterRange, nsAString* aAllTextInBlock /* = nullptr */) {
2471 Clear();
2473 if (aAllTextInBlock) {
2474 aAllTextInBlock->Truncate();
2477 if (aIteratorStatus == IteratorStatus::eDone) {
2478 return IteratorStatus::eDone;
2481 // If we have an aIterRange, retrieve the endpoints so
2482 // they can be used in the while loop below to trim entries
2483 // for text nodes that are partially selected by aIterRange.
2485 nsCOMPtr<nsINode> rngStartNode, rngEndNode;
2486 uint32_t rngStartOffset = 0, rngEndOffset = 0;
2487 if (aIterRange) {
2488 nsresult rv = TextServicesDocument::GetRangeEndPoints(
2489 aIterRange, getter_AddRefs(rngStartNode), &rngStartOffset,
2490 getter_AddRefs(rngEndNode), &rngEndOffset);
2491 if (NS_FAILED(rv)) {
2492 NS_WARNING("TextServicesDocument::GetRangeEndPoints() failed");
2493 return Err(rv);
2497 // The text service could have added text nodes to the beginning
2498 // of the current block and called this method again. Make sure
2499 // we really are at the beginning of the current block:
2501 nsresult rv =
2502 TextServicesDocument::FirstTextNodeInCurrentBlock(&aFilteredIter);
2503 if (NS_FAILED(rv)) {
2504 NS_WARNING("TextServicesDocument::FirstTextNodeInCurrentBlock() failed");
2505 return Err(rv);
2508 TextServicesDocument::ClearDidSkip(&aFilteredIter);
2510 uint32_t offset = 0;
2511 RefPtr<Text> firstTextNode, previousTextNode;
2512 while (!aFilteredIter.IsDone()) {
2513 if (nsCOMPtr<nsIContent> content =
2514 aFilteredIter.GetCurrentNode()->IsContent()
2515 ? aFilteredIter.GetCurrentNode()->AsContent()
2516 : nullptr) {
2517 if (HTMLEditUtils::IsBlockElement(*content) ||
2518 content->IsHTMLElement(nsGkAtoms::br)) {
2519 break;
2521 if (content->IsText()) {
2522 if (previousTextNode && !TextServicesDocument::HasSameBlockNodeParent(
2523 *previousTextNode, *content->AsText())) {
2524 break;
2527 nsString str;
2528 content->AsText()->GetNodeValue(str);
2530 // Add an entry for this text node into the offset table:
2532 UniquePtr<OffsetEntry>& entry = *AppendElement(
2533 MakeUnique<OffsetEntry>(*content->AsText(), offset, str.Length()));
2534 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
2536 // If one or both of the endpoints of the iteration range
2537 // are in the text node for this entry, make sure the entry
2538 // only accounts for the portion of the text node that is
2539 // in the range.
2541 uint32_t startOffset = 0;
2542 uint32_t endOffset = str.Length();
2543 bool adjustStr = false;
2545 if (entry->mTextNode == rngStartNode) {
2546 entry->mOffsetInTextNode = startOffset = rngStartOffset;
2547 adjustStr = true;
2550 if (entry->mTextNode == rngEndNode) {
2551 endOffset = rngEndOffset;
2552 adjustStr = true;
2555 if (adjustStr) {
2556 entry->mLength = endOffset - startOffset;
2557 str = Substring(str, startOffset, entry->mLength);
2560 offset += str.Length();
2562 if (aAllTextInBlock) {
2563 // Append the text node's string to the output string:
2564 if (!firstTextNode) {
2565 *aAllTextInBlock = str;
2566 } else {
2567 *aAllTextInBlock += str;
2571 previousTextNode = content->AsText();
2573 if (!firstTextNode) {
2574 firstTextNode = content->AsText();
2579 aFilteredIter.Next();
2581 if (TextServicesDocument::DidSkip(&aFilteredIter)) {
2582 break;
2586 if (firstTextNode) {
2587 // Always leave the iterator pointing at the first
2588 // text node of the current block!
2589 aFilteredIter.PositionAt(firstTextNode);
2590 return aIteratorStatus;
2593 // If we never ran across a text node, the iterator
2594 // might have been pointing to something invalid to
2595 // begin with.
2596 return IteratorStatus::eDone;
2599 void TextServicesDocument::OffsetEntryArray::RemoveInvalidElements() {
2600 for (size_t i = 0; i < Length();) {
2601 if (ElementAt(i)->mIsValid) {
2602 i++;
2603 continue;
2606 RemoveElementAt(i);
2607 if (!mSelection.IsSet()) {
2608 continue;
2610 if (mSelection.StartIndex() == i) {
2611 NS_ASSERTION(false, "What should we do in this case?");
2612 mSelection.Reset();
2613 } else if (mSelection.StartIndex() > i) {
2614 MOZ_DIAGNOSTIC_ASSERT(mSelection.StartIndex() - 1 < Length());
2615 MOZ_DIAGNOSTIC_ASSERT(mSelection.EndIndex() - 1 < Length());
2616 mSelection.SetIndexes(mSelection.StartIndex() - 1,
2617 mSelection.EndIndex() - 1);
2618 } else if (mSelection.EndIndex() >= i) {
2619 MOZ_DIAGNOSTIC_ASSERT(mSelection.EndIndex() - 1 < Length());
2620 mSelection.SetIndexes(mSelection.StartIndex(), mSelection.EndIndex() - 1);
2625 nsresult TextServicesDocument::OffsetEntryArray::SplitElementAt(
2626 size_t aIndex, uint32_t aOffsetInTextNode) {
2627 OffsetEntry* leftEntry = ElementAt(aIndex).get();
2628 MOZ_ASSERT(leftEntry);
2629 NS_ASSERTION((aOffsetInTextNode > 0), "aOffsetInTextNode == 0");
2630 NS_ASSERTION((aOffsetInTextNode < leftEntry->mLength),
2631 "aOffsetInTextNode >= mLength");
2633 if (aOffsetInTextNode < 1 || aOffsetInTextNode >= leftEntry->mLength) {
2634 return NS_ERROR_FAILURE;
2637 const uint32_t oldLength = leftEntry->mLength - aOffsetInTextNode;
2639 // XXX(Bug 1631371) Check if this should use a fallible operation as it
2640 // pretended earlier.
2641 UniquePtr<OffsetEntry>& rightEntry = *InsertElementAt(
2642 aIndex + 1,
2643 MakeUnique<OffsetEntry>(leftEntry->mTextNode,
2644 leftEntry->mOffsetInTextInBlock + oldLength,
2645 aOffsetInTextNode));
2646 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
2647 leftEntry->mLength = oldLength;
2648 rightEntry->mOffsetInTextNode = leftEntry->mOffsetInTextNode + oldLength;
2650 return NS_OK;
2653 Maybe<size_t> TextServicesDocument::OffsetEntryArray::FirstIndexOf(
2654 const Text& aTextNode) const {
2655 for (size_t i = 0; i < Length(); i++) {
2656 if (ElementAt(i)->mTextNode == &aTextNode) {
2657 return Some(i);
2660 return Nothing();
2663 // Spellchecker code has this. See bug 211343
2664 #define IS_NBSP_CHAR(c) (((unsigned char)0xa0) == (c))
2666 Result<EditorDOMRangeInTexts, nsresult>
2667 TextServicesDocument::OffsetEntryArray::FindWordRange(
2668 nsAString& aAllTextInBlock, const EditorRawDOMPoint& aStartPointToScan) {
2669 MOZ_ASSERT(aStartPointToScan.IsInTextNode());
2670 // It's assumed that aNode is a text node. The first thing
2671 // we do is get its index in the offset table so we can
2672 // calculate the dom point's string offset.
2673 Maybe<size_t> maybeEntryIndex =
2674 FirstIndexOf(*aStartPointToScan.ContainerAsText());
2675 if (NS_WARN_IF(maybeEntryIndex.isNothing())) {
2676 NS_WARNING(
2677 "TextServicesDocument::OffsetEntryArray::FirstIndexOf() didn't find "
2678 "entries");
2679 return Err(NS_ERROR_FAILURE);
2682 // Next we map offset into a string offset.
2684 const UniquePtr<OffsetEntry>& entry = ElementAt(*maybeEntryIndex);
2685 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
2686 uint32_t strOffset = entry->mOffsetInTextInBlock +
2687 aStartPointToScan.Offset() - entry->mOffsetInTextNode;
2689 // Now we use the word breaker to find the beginning and end
2690 // of the word from our calculated string offset.
2692 const char16_t* str = aAllTextInBlock.BeginReading();
2693 uint32_t strLen = aAllTextInBlock.Length();
2695 intl::WordRange res = intl::WordBreaker::FindWord(str, strLen, strOffset);
2696 if (res.mBegin == res.mEnd) {
2697 return Err(str ? NS_ERROR_ILLEGAL_VALUE : NS_ERROR_NULL_POINTER);
2700 // Strip out the NBSPs at the ends
2701 while (res.mBegin <= res.mEnd && IS_NBSP_CHAR(str[res.mBegin])) {
2702 res.mBegin++;
2704 if (str[res.mEnd] == static_cast<char16_t>(0x20)) {
2705 uint32_t realEndWord = res.mEnd - 1;
2706 while (realEndWord > res.mBegin && IS_NBSP_CHAR(str[realEndWord])) {
2707 realEndWord--;
2709 if (realEndWord < res.mEnd - 1) {
2710 res.mEnd = realEndWord + 1;
2714 // Now that we have the string offsets for the beginning
2715 // and end of the word, run through the offset table and
2716 // convert them back into dom points.
2718 EditorDOMPointInText wordStart, wordEnd;
2719 size_t lastIndex = Length() - 1;
2720 for (size_t i = 0; i <= lastIndex; i++) {
2721 // Check to see if res.mBegin is within the range covered
2722 // by this entry. Note that if res.mBegin is after the last
2723 // character covered by this entry, we will use the next
2724 // entry if there is one.
2725 const UniquePtr<OffsetEntry>& entry = ElementAt(i);
2726 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
2727 if (entry->mOffsetInTextInBlock <= res.mBegin &&
2728 (res.mBegin < entry->EndOffsetInTextInBlock() ||
2729 (res.mBegin == entry->EndOffsetInTextInBlock() && i == lastIndex))) {
2730 wordStart.Set(entry->mTextNode, entry->mOffsetInTextNode + res.mBegin -
2731 entry->mOffsetInTextInBlock);
2734 // Check to see if res.mEnd is within the range covered
2735 // by this entry.
2736 if (entry->mOffsetInTextInBlock <= res.mEnd &&
2737 res.mEnd <= entry->EndOffsetInTextInBlock()) {
2738 if (res.mBegin == res.mEnd &&
2739 res.mEnd == entry->EndOffsetInTextInBlock() && i != lastIndex) {
2740 // Wait for the next round so that we use the same entry
2741 // we did for aWordStartNode.
2742 continue;
2745 wordEnd.Set(entry->mTextNode, entry->mOffsetInTextNode + res.mEnd -
2746 entry->mOffsetInTextInBlock);
2747 break;
2751 return EditorDOMRangeInTexts(wordStart, wordEnd);
2755 * nsIEditActionListener implementation:
2756 * Don't implement the behavior directly here. The methods won't be called
2757 * if the instance is created for inline spell checker created for editor.
2758 * If you need to listen a new edit action, you need to add similar
2759 * non-virtual method and you need to call it from EditorBase directly.
2762 NS_IMETHODIMP
2763 TextServicesDocument::DidDeleteNode(nsINode* aChild, nsresult aResult) {
2764 if (NS_WARN_IF(NS_FAILED(aResult)) || NS_WARN_IF(!aChild) ||
2765 !aChild->IsContent()) {
2766 return NS_OK;
2768 DidDeleteContent(*aChild->AsContent());
2769 return NS_OK;
2772 NS_IMETHODIMP TextServicesDocument::DidJoinContents(
2773 const EditorRawDOMPoint& aJoinedPoint, const nsINode* aRemovedNode,
2774 bool aLeftNodeWasRemoved) {
2775 if (MOZ_UNLIKELY(NS_WARN_IF(!aJoinedPoint.IsSetAndValid()) ||
2776 NS_WARN_IF(!aRemovedNode->IsContent()))) {
2777 return NS_OK;
2779 DidJoinContents(aJoinedPoint, *aRemovedNode->AsContent(),
2780 aLeftNodeWasRemoved
2781 ? JoinNodesDirection::LeftNodeIntoRightNode
2782 : JoinNodesDirection::RightNodeIntoLeftNode);
2783 return NS_OK;
2786 NS_IMETHODIMP
2787 TextServicesDocument::DidInsertText(CharacterData* aTextNode, int32_t aOffset,
2788 const nsAString& aString,
2789 nsresult aResult) {
2790 return NS_OK;
2793 NS_IMETHODIMP
2794 TextServicesDocument::WillDeleteText(CharacterData* aTextNode, int32_t aOffset,
2795 int32_t aLength) {
2796 return NS_OK;
2799 NS_IMETHODIMP
2800 TextServicesDocument::WillDeleteRanges(
2801 const nsTArray<RefPtr<nsRange>>& aRangesToDelete) {
2802 return NS_OK;
2805 #undef LockOffsetEntryArrayLengthInDebugBuild
2807 } // namespace mozilla