Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / editor / spellchecker / TextServicesDocument.cpp
blobad1e3ca037699d9c4a5d1afc7a957b053ce6742d
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 "EditorBase.h" // for EditorBase
9 #include "EditorUtils.h" // for AutoTransactionBatchExternal
10 #include "FilteredContentIterator.h" // for FilteredContentIterator
11 #include "HTMLEditHelpers.h" // for BlockInlineCheck
12 #include "HTMLEditUtils.h" // for HTMLEditUtils
14 #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc
15 #include "mozilla/IntegerRange.h" // for IntegerRange
16 #include "mozilla/mozalloc.h" // for operator new, etc
17 #include "mozilla/OwningNonNull.h"
18 #include "mozilla/UniquePtr.h" // for UniquePtr
19 #include "mozilla/dom/AbstractRange.h" // for AbstractRange
20 #include "mozilla/dom/Element.h"
21 #include "mozilla/dom/Selection.h"
22 #include "mozilla/dom/StaticRange.h" // for StaticRange
23 #include "mozilla/dom/Text.h"
24 #include "mozilla/intl/WordBreaker.h" // for WordRange, WordBreaker
26 #include "nsAString.h" // for nsAString::Length, etc
27 #include "nsContentUtils.h" // for nsContentUtils
28 #include "nsComposeTxtSrvFilter.h"
29 #include "nsDebug.h" // for NS_ENSURE_TRUE, etc
30 #include "nsDependentSubstring.h" // for Substring
31 #include "nsError.h" // for NS_OK, NS_ERROR_FAILURE, etc
32 #include "nsGenericHTMLElement.h" // for nsGenericHTMLElement
33 #include "nsIContent.h" // for nsIContent, etc
34 #include "nsID.h" // for NS_GET_IID
35 #include "nsIEditor.h" // for nsIEditor, etc
36 #include "nsIEditorSpellCheck.h" // for nsIEditorSpellCheck, etc
37 #include "nsINode.h" // for nsINode
38 #include "nsISelectionController.h" // for nsISelectionController, etc
39 #include "nsISupports.h" // for nsISupports
40 #include "nsISupportsUtils.h" // for NS_IF_ADDREF, NS_ADDREF, etc
41 #include "nsRange.h" // for nsRange
42 #include "nsString.h" // for nsString, nsAutoString
43 #include "nscore.h" // for nsresult, NS_IMETHODIMP, etc
45 namespace mozilla {
47 using namespace dom;
49 /**
50 * OffsetEntry manages a range in a text node. It stores 2 offset values,
51 * one is offset in the text node, the other is offset in all text in
52 * the ancestor block of the text node. And the length is managing length
53 * in the text node, starting from the offset in text node.
54 * In other words, a text node may be managed by multiple instances of this
55 * class.
57 class OffsetEntry final {
58 public:
59 OffsetEntry() = delete;
61 /**
62 * @param aTextNode The text node which will be manged by the instance.
63 * @param aOffsetInTextInBlock
64 * Start offset in the text node which will be managed by
65 * the instance.
66 * @param aLength Length in the text node which will be managed by the
67 * instance.
69 OffsetEntry(Text& aTextNode, uint32_t aOffsetInTextInBlock, uint32_t aLength)
70 : mTextNode(aTextNode),
71 mOffsetInTextNode(0),
72 mOffsetInTextInBlock(aOffsetInTextInBlock),
73 mLength(aLength),
74 mIsInsertedText(false),
75 mIsValid(true) {}
77 /**
78 * EndOffsetInTextNode() returns end offset in the text node, which is
79 * managed by the instance.
81 uint32_t EndOffsetInTextNode() const { return mOffsetInTextNode + mLength; }
83 /**
84 * OffsetInTextNodeIsInRangeOrEndOffset() checks whether the offset in
85 * the text node is managed by the instance or not.
87 bool OffsetInTextNodeIsInRangeOrEndOffset(uint32_t aOffsetInTextNode) const {
88 return aOffsetInTextNode >= mOffsetInTextNode &&
89 aOffsetInTextNode <= EndOffsetInTextNode();
92 /**
93 * EndOffsetInTextInBlock() returns end offset in the all text in ancestor
94 * block of the text node, which is managed by the instance.
96 uint32_t EndOffsetInTextInBlock() const {
97 return mOffsetInTextInBlock + mLength;
101 * OffsetInTextNodeIsInRangeOrEndOffset() checks whether the offset in
102 * the all text in ancestor block of the text node is managed by the instance
103 * or not.
105 bool OffsetInTextInBlockIsInRangeOrEndOffset(
106 uint32_t aOffsetInTextInBlock) const {
107 return aOffsetInTextInBlock >= mOffsetInTextInBlock &&
108 aOffsetInTextInBlock <= EndOffsetInTextInBlock();
111 OwningNonNull<Text> mTextNode;
112 uint32_t mOffsetInTextNode;
113 // Offset in all text in the closest ancestor block of mTextNode.
114 uint32_t mOffsetInTextInBlock;
115 uint32_t mLength;
116 bool mIsInsertedText;
117 bool mIsValid;
120 template <typename ElementType>
121 struct MOZ_STACK_CLASS ArrayLengthMutationGuard final {
122 ArrayLengthMutationGuard() = delete;
123 explicit ArrayLengthMutationGuard(const nsTArray<ElementType>& aArray)
124 : mArray(aArray), mOldLength(aArray.Length()) {}
125 ~ArrayLengthMutationGuard() {
126 if (mArray.Length() != mOldLength) {
127 MOZ_CRASH("The array length was changed unexpectedly");
131 private:
132 const nsTArray<ElementType>& mArray;
133 size_t mOldLength;
136 #define LockOffsetEntryArrayLengthInDebugBuild(aName, aArray) \
137 DebugOnly<ArrayLengthMutationGuard<UniquePtr<OffsetEntry>>> const aName = \
138 ArrayLengthMutationGuard<UniquePtr<OffsetEntry>>(aArray);
140 TextServicesDocument::TextServicesDocument()
141 : mTxtSvcFilterType(0), mIteratorStatus(IteratorStatus::eDone) {}
143 NS_IMPL_CYCLE_COLLECTING_ADDREF(TextServicesDocument)
144 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextServicesDocument)
146 NS_INTERFACE_MAP_BEGIN(TextServicesDocument)
147 NS_INTERFACE_MAP_ENTRY(nsIEditActionListener)
148 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditActionListener)
149 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(TextServicesDocument)
150 NS_INTERFACE_MAP_END
152 NS_IMPL_CYCLE_COLLECTION(TextServicesDocument, mDocument, mSelCon, mEditorBase,
153 mFilteredIter, mPrevTextBlock, mNextTextBlock, mExtent)
155 nsresult TextServicesDocument::InitWithEditor(nsIEditor* aEditor) {
156 nsCOMPtr<nsISelectionController> selCon;
158 NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER);
160 // Check to see if we already have an mSelCon. If we do, it
161 // better be the same one the editor uses!
163 nsresult rv = aEditor->GetSelectionController(getter_AddRefs(selCon));
165 if (NS_FAILED(rv)) {
166 return rv;
169 if (!selCon || (mSelCon && selCon != mSelCon)) {
170 return NS_ERROR_FAILURE;
173 if (!mSelCon) {
174 mSelCon = selCon;
177 // Check to see if we already have an mDocument. If we do, it
178 // better be the same one the editor uses!
180 RefPtr<Document> doc = aEditor->AsEditorBase()->GetDocument();
181 if (!doc || (mDocument && doc != mDocument)) {
182 return NS_ERROR_FAILURE;
185 if (!mDocument) {
186 mDocument = doc;
188 rv = CreateDocumentContentIterator(getter_AddRefs(mFilteredIter));
190 if (NS_FAILED(rv)) {
191 return rv;
194 mIteratorStatus = IteratorStatus::eDone;
196 rv = FirstBlock();
198 if (NS_FAILED(rv)) {
199 return rv;
203 mEditorBase = aEditor->AsEditorBase();
205 rv = aEditor->AddEditActionListener(this);
207 return rv;
210 nsresult TextServicesDocument::SetExtent(const AbstractRange* aAbstractRange) {
211 MOZ_ASSERT(aAbstractRange);
213 if (NS_WARN_IF(!mDocument)) {
214 return NS_ERROR_FAILURE;
217 // We need to store a copy of aAbstractRange since we don't know where it
218 // came from.
219 mExtent = nsRange::Create(aAbstractRange, IgnoreErrors());
220 if (NS_WARN_IF(!mExtent)) {
221 return NS_ERROR_FAILURE;
224 // Create a new iterator based on our new extent range.
225 nsresult rv =
226 CreateFilteredContentIterator(mExtent, getter_AddRefs(mFilteredIter));
227 if (NS_WARN_IF(NS_FAILED(rv))) {
228 return rv;
231 // Now position the iterator at the start of the first block
232 // in the range.
233 mIteratorStatus = IteratorStatus::eDone;
235 rv = FirstBlock();
236 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "FirstBlock() failed");
237 return rv;
240 nsresult TextServicesDocument::ExpandRangeToWordBoundaries(
241 StaticRange* aStaticRange) {
242 MOZ_ASSERT(aStaticRange);
244 // Get the end points of the range.
246 nsCOMPtr<nsINode> rngStartNode, rngEndNode;
247 uint32_t rngStartOffset, rngEndOffset;
249 nsresult rv = GetRangeEndPoints(aStaticRange, getter_AddRefs(rngStartNode),
250 &rngStartOffset, getter_AddRefs(rngEndNode),
251 &rngEndOffset);
252 if (NS_WARN_IF(NS_FAILED(rv))) {
253 return rv;
256 // Create a content iterator based on the range.
257 RefPtr<FilteredContentIterator> filteredIter;
258 rv =
259 CreateFilteredContentIterator(aStaticRange, getter_AddRefs(filteredIter));
260 if (NS_WARN_IF(NS_FAILED(rv))) {
261 return rv;
264 // Find the first text node in the range.
265 IteratorStatus iterStatus = IteratorStatus::eDone;
266 rv = FirstTextNode(filteredIter, &iterStatus);
267 if (NS_WARN_IF(NS_FAILED(rv))) {
268 return rv;
271 if (iterStatus == IteratorStatus::eDone) {
272 // No text was found so there's no adjustment necessary!
273 return NS_OK;
276 nsINode* firstText = filteredIter->GetCurrentNode();
277 if (NS_WARN_IF(!firstText)) {
278 return NS_ERROR_FAILURE;
281 // Find the last text node in the range.
283 rv = LastTextNode(filteredIter, &iterStatus);
284 if (NS_WARN_IF(NS_FAILED(rv))) {
285 return rv;
288 if (iterStatus == IteratorStatus::eDone) {
289 // We should never get here because a first text block
290 // was found above.
291 NS_ASSERTION(false, "Found a first without a last!");
292 return NS_ERROR_FAILURE;
295 nsINode* lastText = filteredIter->GetCurrentNode();
296 if (NS_WARN_IF(!lastText)) {
297 return NS_ERROR_FAILURE;
300 // Now make sure our end points are in terms of text nodes in the range!
302 if (rngStartNode != firstText) {
303 // The range includes the start of the first text node!
304 rngStartNode = firstText;
305 rngStartOffset = 0;
308 if (rngEndNode != lastText) {
309 // The range includes the end of the last text node!
310 rngEndNode = lastText;
311 rngEndOffset = lastText->Length();
314 // Create a doc iterator so that we can scan beyond
315 // the bounds of the extent range.
317 RefPtr<FilteredContentIterator> docFilteredIter;
318 rv = CreateDocumentContentIterator(getter_AddRefs(docFilteredIter));
319 if (NS_WARN_IF(NS_FAILED(rv))) {
320 return rv;
323 // Grab all the text in the block containing our
324 // first text node.
325 rv = docFilteredIter->PositionAt(firstText);
326 if (NS_WARN_IF(NS_FAILED(rv))) {
327 return rv;
330 iterStatus = IteratorStatus::eValid;
332 OffsetEntryArray offsetTable;
333 nsAutoString blockStr;
334 Result<IteratorStatus, nsresult> result = offsetTable.Init(
335 *docFilteredIter, IteratorStatus::eValid, nullptr, &blockStr);
336 if (result.isErr()) {
337 return result.unwrapErr();
340 Result<EditorDOMRangeInTexts, nsresult> maybeWordRange =
341 offsetTable.FindWordRange(
342 blockStr, EditorRawDOMPoint(rngStartNode, rngStartOffset));
343 offsetTable.Clear();
344 if (maybeWordRange.isErr()) {
345 NS_WARNING(
346 "TextServicesDocument::OffsetEntryArray::FindWordRange() failed");
347 return maybeWordRange.unwrapErr();
349 rngStartNode = maybeWordRange.inspect().StartRef().GetContainerAs<Text>();
350 rngStartOffset = maybeWordRange.inspect().StartRef().Offset();
352 // Grab all the text in the block containing our
353 // last text node.
355 rv = docFilteredIter->PositionAt(lastText);
356 if (NS_WARN_IF(NS_FAILED(rv))) {
357 return rv;
360 result = offsetTable.Init(*docFilteredIter, IteratorStatus::eValid, nullptr,
361 &blockStr);
362 if (result.isErr()) {
363 return result.unwrapErr();
366 maybeWordRange = offsetTable.FindWordRange(
367 blockStr, EditorRawDOMPoint(rngEndNode, rngEndOffset));
368 offsetTable.Clear();
369 if (maybeWordRange.isErr()) {
370 NS_WARNING(
371 "TextServicesDocument::OffsetEntryArray::FindWordRange() failed");
372 return maybeWordRange.unwrapErr();
375 // To prevent expanding the range too much, we only change
376 // rngEndNode and rngEndOffset if it isn't already at the start of the
377 // word and isn't equivalent to rngStartNode and rngStartOffset.
379 if (rngEndNode !=
380 maybeWordRange.inspect().StartRef().GetContainerAs<Text>() ||
381 rngEndOffset != maybeWordRange.inspect().StartRef().Offset() ||
382 (rngEndNode == rngStartNode && rngEndOffset == rngStartOffset)) {
383 rngEndNode = maybeWordRange.inspect().EndRef().GetContainerAs<Text>();
384 rngEndOffset = maybeWordRange.inspect().EndRef().Offset();
387 // Now adjust the range so that it uses our new end points.
388 rv = aStaticRange->SetStartAndEnd(rngStartNode, rngStartOffset, rngEndNode,
389 rngEndOffset);
390 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to update the given range");
391 return rv;
394 nsresult TextServicesDocument::SetFilterType(uint32_t aFilterType) {
395 mTxtSvcFilterType = aFilterType;
397 return NS_OK;
400 nsresult TextServicesDocument::GetCurrentTextBlock(nsAString& aStr) {
401 aStr.Truncate();
403 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
405 Result<IteratorStatus, nsresult> result =
406 mOffsetTable.Init(*mFilteredIter, mIteratorStatus, mExtent, &aStr);
407 if (result.isErr()) {
408 NS_WARNING("OffsetEntryArray::Init() failed");
409 return result.unwrapErr();
411 mIteratorStatus = result.unwrap();
412 return NS_OK;
415 nsresult TextServicesDocument::FirstBlock() {
416 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
418 nsresult rv = FirstTextNode(mFilteredIter, &mIteratorStatus);
420 if (NS_FAILED(rv)) {
421 return rv;
424 // Keep track of prev and next blocks, just in case
425 // the text service blows away the current block.
427 if (mIteratorStatus == IteratorStatus::eValid) {
428 mPrevTextBlock = nullptr;
429 rv = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock));
430 } else {
431 // There's no text block in the document!
433 mPrevTextBlock = nullptr;
434 mNextTextBlock = nullptr;
437 // XXX Result of FirstTextNode() or GetFirstTextNodeInNextBlock().
438 return rv;
441 nsresult TextServicesDocument::LastSelectedBlock(
442 BlockSelectionStatus* aSelStatus, uint32_t* aSelOffset,
443 uint32_t* aSelLength) {
444 NS_ENSURE_TRUE(aSelStatus && aSelOffset && aSelLength, NS_ERROR_NULL_POINTER);
446 mIteratorStatus = IteratorStatus::eDone;
448 *aSelStatus = BlockSelectionStatus::eBlockNotFound;
449 *aSelOffset = *aSelLength = UINT32_MAX;
451 if (!mSelCon || !mFilteredIter) {
452 return NS_ERROR_FAILURE;
455 RefPtr<Selection> selection =
456 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
457 if (NS_WARN_IF(!selection)) {
458 return NS_ERROR_FAILURE;
461 RefPtr<const nsRange> range;
462 nsCOMPtr<nsINode> parent;
464 if (selection->IsCollapsed()) {
465 // We have a caret. Check if the caret is in a text node.
466 // If it is, make the text node's block the current block.
467 // If the caret isn't in a text node, search forwards in
468 // the document, till we find a text node.
470 range = selection->GetRangeAt(0);
471 if (!range) {
472 return NS_ERROR_FAILURE;
475 parent = range->GetStartContainer();
476 if (!parent) {
477 return NS_ERROR_FAILURE;
480 nsresult rv;
481 if (parent->IsText()) {
482 // The caret is in a text node. Find the beginning
483 // of the text block containing this text node and
484 // return.
486 rv = mFilteredIter->PositionAt(parent->AsText());
487 if (NS_FAILED(rv)) {
488 return rv;
491 rv = FirstTextNodeInCurrentBlock(mFilteredIter);
492 if (NS_FAILED(rv)) {
493 return rv;
496 Result<IteratorStatus, nsresult> result =
497 mOffsetTable.Init(*mFilteredIter, IteratorStatus::eValid, mExtent);
498 if (result.isErr()) {
499 NS_WARNING("OffsetEntryArray::Init() failed");
500 mIteratorStatus = IteratorStatus::eValid; // XXX
501 return result.unwrapErr();
503 mIteratorStatus = result.unwrap();
505 rv = GetSelection(aSelStatus, aSelOffset, aSelLength);
506 if (NS_FAILED(rv)) {
507 return rv;
510 if (*aSelStatus == BlockSelectionStatus::eBlockContains) {
511 rv = SetSelectionInternal(*aSelOffset, *aSelLength, false);
513 } else {
514 // The caret isn't in a text node. Create an iterator
515 // based on a range that extends from the current caret
516 // position to the end of the document, then walk forwards
517 // till you find a text node, then find the beginning of it's block.
519 range = CreateDocumentContentRootToNodeOffsetRange(
520 parent, range->StartOffset(), false);
521 if (NS_WARN_IF(!range)) {
522 return NS_ERROR_FAILURE;
525 if (range->Collapsed()) {
526 // If we get here, the range is collapsed because there is nothing after
527 // the caret! Just return NS_OK;
528 return NS_OK;
531 RefPtr<FilteredContentIterator> filteredIter;
532 rv = CreateFilteredContentIterator(range, getter_AddRefs(filteredIter));
533 if (NS_FAILED(rv)) {
534 return rv;
537 filteredIter->First();
539 Text* textNode = nullptr;
540 for (; !filteredIter->IsDone(); filteredIter->Next()) {
541 nsINode* currentNode = filteredIter->GetCurrentNode();
542 if (currentNode->IsText()) {
543 textNode = currentNode->AsText();
544 break;
548 if (!textNode) {
549 return NS_OK;
552 rv = mFilteredIter->PositionAt(textNode);
553 if (NS_FAILED(rv)) {
554 return rv;
557 rv = FirstTextNodeInCurrentBlock(mFilteredIter);
558 if (NS_FAILED(rv)) {
559 return rv;
562 Result<IteratorStatus, nsresult> result = mOffsetTable.Init(
563 *mFilteredIter, IteratorStatus::eValid, mExtent, nullptr);
564 if (result.isErr()) {
565 NS_WARNING("OffsetEntryArray::Init() failed");
566 mIteratorStatus = IteratorStatus::eValid; // XXX
567 return result.unwrapErr();
569 mIteratorStatus = result.inspect();
571 rv = GetSelection(aSelStatus, aSelOffset, aSelLength);
572 if (NS_FAILED(rv)) {
573 return rv;
577 // Result of SetSelectionInternal() in the |if| block or NS_OK.
578 return rv;
581 // If we get here, we have an uncollapsed selection!
582 // Look backwards through each range in the selection till you
583 // find the first text node. If you find one, find the
584 // beginning of its text block, and make it the current
585 // block.
587 const uint32_t rangeCount = selection->RangeCount();
588 MOZ_ASSERT(
589 rangeCount,
590 "Selection is not collapsed, so, the range count should be 1 or larger");
592 // XXX: We may need to add some code here to make sure
593 // the ranges are sorted in document appearance order!
595 for (const uint32_t i : Reversed(IntegerRange(rangeCount))) {
596 MOZ_ASSERT(selection->RangeCount() == rangeCount);
597 range = selection->GetRangeAt(i);
598 if (MOZ_UNLIKELY(!range)) {
599 return NS_OK; // XXX Really?
602 // Create an iterator for the range.
604 RefPtr<FilteredContentIterator> filteredIter;
605 nsresult rv =
606 CreateFilteredContentIterator(range, getter_AddRefs(filteredIter));
607 if (NS_FAILED(rv)) {
608 return rv;
611 filteredIter->Last();
613 // Now walk through the range till we find a text node.
615 for (; !filteredIter->IsDone(); filteredIter->Prev()) {
616 if (filteredIter->GetCurrentNode()->NodeType() == nsINode::TEXT_NODE) {
617 // We found a text node, so position the document's
618 // iterator at the beginning of the block, then get
619 // the selection in terms of the string offset.
621 nsresult rv = mFilteredIter->PositionAt(filteredIter->GetCurrentNode());
622 if (NS_FAILED(rv)) {
623 return rv;
626 rv = FirstTextNodeInCurrentBlock(mFilteredIter);
627 if (NS_FAILED(rv)) {
628 return rv;
631 mIteratorStatus = IteratorStatus::eValid;
633 Result<IteratorStatus, nsresult> result =
634 mOffsetTable.Init(*mFilteredIter, IteratorStatus::eValid, mExtent);
635 if (result.isErr()) {
636 NS_WARNING("OffsetEntryArray::Init() failed");
637 mIteratorStatus = IteratorStatus::eValid; // XXX
638 return result.unwrapErr();
640 mIteratorStatus = result.unwrap();
642 return GetSelection(aSelStatus, aSelOffset, aSelLength);
647 // If we get here, we didn't find any text node in the selection!
648 // Create a range that extends from the end of the selection,
649 // to the end of the document, then iterate forwards through
650 // it till you find a text node!
651 range = rangeCount > 0 ? selection->GetRangeAt(rangeCount - 1) : nullptr;
652 if (!range) {
653 return NS_ERROR_FAILURE;
656 parent = range->GetEndContainer();
657 if (!parent) {
658 return NS_ERROR_FAILURE;
661 range = CreateDocumentContentRootToNodeOffsetRange(parent, range->EndOffset(),
662 false);
663 if (NS_WARN_IF(!range)) {
664 return NS_ERROR_FAILURE;
667 if (range->Collapsed()) {
668 // If we get here, the range is collapsed because there is nothing after
669 // the current selection! Just return NS_OK;
670 return NS_OK;
673 RefPtr<FilteredContentIterator> filteredIter;
674 nsresult rv =
675 CreateFilteredContentIterator(range, getter_AddRefs(filteredIter));
676 if (NS_FAILED(rv)) {
677 return rv;
680 filteredIter->First();
682 for (; !filteredIter->IsDone(); filteredIter->Next()) {
683 if (filteredIter->GetCurrentNode()->NodeType() == nsINode::TEXT_NODE) {
684 // We found a text node! Adjust the document's iterator to point
685 // to the beginning of its text block, then get the current selection.
686 nsresult rv = mFilteredIter->PositionAt(filteredIter->GetCurrentNode());
687 if (NS_FAILED(rv)) {
688 return rv;
691 rv = FirstTextNodeInCurrentBlock(mFilteredIter);
692 if (NS_FAILED(rv)) {
693 return rv;
696 Result<IteratorStatus, nsresult> result =
697 mOffsetTable.Init(*mFilteredIter, IteratorStatus::eValid, mExtent);
698 if (result.isErr()) {
699 NS_WARNING("OffsetEntryArray::Init() failed");
700 mIteratorStatus = IteratorStatus::eValid; // XXX
701 return result.unwrapErr();
703 mIteratorStatus = result.unwrap();
705 rv = GetSelection(aSelStatus, aSelOffset, aSelLength);
706 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
707 "TextServicesDocument::GetSelection() failed");
708 return rv;
712 // If we get here, we didn't find any block before or inside
713 // the selection! Just return OK.
714 return NS_OK;
717 nsresult TextServicesDocument::PrevBlock() {
718 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
720 if (mIteratorStatus == IteratorStatus::eDone) {
721 return NS_OK;
724 switch (mIteratorStatus) {
725 case IteratorStatus::eValid:
726 case IteratorStatus::eNext: {
727 nsresult rv = FirstTextNodeInPrevBlock(mFilteredIter);
729 if (NS_FAILED(rv)) {
730 mIteratorStatus = IteratorStatus::eDone;
731 return rv;
734 if (mFilteredIter->IsDone()) {
735 mIteratorStatus = IteratorStatus::eDone;
736 return NS_OK;
739 mIteratorStatus = IteratorStatus::eValid;
740 break;
742 case IteratorStatus::ePrev:
744 // The iterator already points to the previous
745 // block, so don't do anything.
747 mIteratorStatus = IteratorStatus::eValid;
748 break;
750 default:
752 mIteratorStatus = IteratorStatus::eDone;
753 break;
756 // Keep track of prev and next blocks, just in case
757 // the text service blows away the current block.
758 nsresult rv = NS_OK;
759 if (mIteratorStatus == IteratorStatus::eValid) {
760 GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock));
761 rv = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock));
762 } else {
763 // We must be done!
764 mPrevTextBlock = nullptr;
765 mNextTextBlock = nullptr;
768 // XXX The result of GetFirstTextNodeInNextBlock() or NS_OK.
769 return rv;
772 nsresult TextServicesDocument::NextBlock() {
773 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
775 if (mIteratorStatus == IteratorStatus::eDone) {
776 return NS_OK;
779 switch (mIteratorStatus) {
780 case IteratorStatus::eValid: {
781 // Advance the iterator to the next text block.
783 nsresult rv = FirstTextNodeInNextBlock(mFilteredIter);
785 if (NS_FAILED(rv)) {
786 mIteratorStatus = IteratorStatus::eDone;
787 return rv;
790 if (mFilteredIter->IsDone()) {
791 mIteratorStatus = IteratorStatus::eDone;
792 return NS_OK;
795 mIteratorStatus = IteratorStatus::eValid;
796 break;
798 case IteratorStatus::eNext:
800 // The iterator already points to the next block,
801 // so don't do anything to it!
803 mIteratorStatus = IteratorStatus::eValid;
804 break;
806 case IteratorStatus::ePrev:
808 // If the iterator is pointing to the previous block,
809 // we know that there is no next text block! Just
810 // fall through to the default case!
812 default:
814 mIteratorStatus = IteratorStatus::eDone;
815 break;
818 // Keep track of prev and next blocks, just in case
819 // the text service blows away the current block.
820 nsresult rv = NS_OK;
821 if (mIteratorStatus == IteratorStatus::eValid) {
822 GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock));
823 rv = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock));
824 } else {
825 // We must be done.
826 mPrevTextBlock = nullptr;
827 mNextTextBlock = nullptr;
830 // The result of GetFirstTextNodeInNextBlock() or NS_OK.
831 return rv;
834 nsresult TextServicesDocument::IsDone(bool* aIsDone) {
835 NS_ENSURE_TRUE(aIsDone, NS_ERROR_NULL_POINTER);
837 *aIsDone = false;
839 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
841 *aIsDone = mIteratorStatus == IteratorStatus::eDone;
843 return NS_OK;
846 nsresult TextServicesDocument::SetSelection(uint32_t aOffset,
847 uint32_t aLength) {
848 NS_ENSURE_TRUE(mSelCon, NS_ERROR_FAILURE);
850 return SetSelectionInternal(aOffset, aLength, true);
853 nsresult TextServicesDocument::ScrollSelectionIntoView() {
854 NS_ENSURE_TRUE(mSelCon, NS_ERROR_FAILURE);
856 // After ScrollSelectionIntoView(), the pending notifications might be flushed
857 // and PresShell/PresContext/Frames may be dead. See bug 418470.
858 const nsCOMPtr selCon = mSelCon;
859 return selCon->ScrollSelectionIntoView(
860 SelectionType::eNormal, nsISelectionController::SELECTION_FOCUS_REGION,
861 ScrollAxis(), ScrollAxis(), ScrollFlags::None,
862 SelectionScrollMode::SyncFlush);
865 nsresult TextServicesDocument::OffsetEntryArray::WillDeleteSelection() {
866 MOZ_ASSERT(mSelection.IsSet());
867 MOZ_ASSERT(!mSelection.IsCollapsed());
869 for (size_t i = mSelection.StartIndex(); i <= mSelection.EndIndex(); i++) {
870 OffsetEntry* entry = ElementAt(i).get();
871 if (i == mSelection.StartIndex()) {
872 // Calculate the length of the selection. Note that the
873 // selection length can be zero if the start of the selection
874 // is at the very end of a text node entry.
875 uint32_t selLength;
876 if (entry->mIsInsertedText) {
877 // Inserted text offset entries have no width when
878 // talking in terms of string offsets! If the beginning
879 // of the selection is in an inserted text offset entry,
880 // the caret is always at the end of the entry!
881 selLength = 0;
882 } else {
883 selLength = entry->EndOffsetInTextInBlock() -
884 mSelection.StartOffsetInTextInBlock();
887 if (selLength > 0) {
888 if (mSelection.StartOffsetInTextInBlock() >
889 entry->mOffsetInTextInBlock) {
890 // Selection doesn't start at the beginning of the
891 // text node entry. We need to split this entry into
892 // two pieces, the piece before the selection, and
893 // the piece inside the selection.
894 nsresult rv = SplitElementAt(i, selLength);
895 if (NS_FAILED(rv)) {
896 NS_WARNING("selLength was invalid for the OffsetEntry");
897 return rv;
900 // Adjust selection indexes to account for new entry:
901 MOZ_DIAGNOSTIC_ASSERT(mSelection.StartIndex() + 1 < Length());
902 MOZ_DIAGNOSTIC_ASSERT(mSelection.EndIndex() + 1 < Length());
903 mSelection.SetIndexes(mSelection.StartIndex() + 1,
904 mSelection.EndIndex() + 1);
905 entry = ElementAt(++i).get();
908 if (mSelection.StartIndex() < mSelection.EndIndex()) {
909 // The entire entry is contained in the selection. Mark the
910 // entry invalid.
911 entry->mIsValid = false;
916 if (i == mSelection.EndIndex()) {
917 if (entry->mIsInsertedText) {
918 // Inserted text offset entries have no width when
919 // talking in terms of string offsets! If the end
920 // of the selection is in an inserted text offset entry,
921 // the selection includes the entire entry!
922 entry->mIsValid = false;
923 } else {
924 // Calculate the length of the selection. Note that the
925 // selection length can be zero if the end of the selection
926 // is at the very beginning of a text node entry.
928 const uint32_t selLength =
929 mSelection.EndOffsetInTextInBlock() - entry->mOffsetInTextInBlock;
930 if (selLength) {
931 if (mSelection.EndOffsetInTextInBlock() <
932 entry->EndOffsetInTextInBlock()) {
933 // mOffsetInTextInBlock is guaranteed to be inside the selection,
934 // even when mSelection.IsInSameElement() is true.
935 nsresult rv = SplitElementAt(i, entry->mLength - selLength);
936 if (NS_FAILED(rv)) {
937 NS_WARNING(
938 "entry->mLength - selLength was invalid for the OffsetEntry");
939 return rv;
942 // Update the entry fields:
943 ElementAt(i + 1)->mOffsetInTextNode = entry->mOffsetInTextNode;
946 if (mSelection.EndOffsetInTextInBlock() ==
947 entry->EndOffsetInTextInBlock()) {
948 // The entire entry is contained in the selection. Mark the
949 // entry invalid.
950 entry->mIsValid = false;
956 if (i != mSelection.StartIndex() && i != mSelection.EndIndex()) {
957 // The entire entry is contained in the selection. Mark the
958 // entry invalid.
959 entry->mIsValid = false;
963 return NS_OK;
966 nsresult TextServicesDocument::DeleteSelection() {
967 if (NS_WARN_IF(!mEditorBase) ||
968 NS_WARN_IF(!mOffsetTable.mSelection.IsSet())) {
969 return NS_ERROR_FAILURE;
972 if (mOffsetTable.mSelection.IsCollapsed()) {
973 return NS_OK;
976 // If we have an mExtent, save off its current set of
977 // end points so we can compare them against mExtent's
978 // set after the deletion of the content.
980 nsCOMPtr<nsINode> origStartNode, origEndNode;
981 uint32_t origStartOffset = 0, origEndOffset = 0;
983 if (mExtent) {
984 nsresult rv = GetRangeEndPoints(
985 mExtent, getter_AddRefs(origStartNode), &origStartOffset,
986 getter_AddRefs(origEndNode), &origEndOffset);
988 if (NS_FAILED(rv)) {
989 return rv;
993 if (NS_FAILED(mOffsetTable.WillDeleteSelection())) {
994 NS_WARNING(
995 "TextServicesDocument::OffsetEntryTable::WillDeleteSelection() failed");
996 return NS_ERROR_FAILURE;
999 // Make sure mFilteredIter always points to something valid!
1000 AdjustContentIterator();
1002 // Now delete the actual content!
1003 OwningNonNull<EditorBase> editorBase = *mEditorBase;
1004 nsresult rv = editorBase->DeleteSelectionAsAction(nsIEditor::ePrevious,
1005 nsIEditor::eStrip);
1006 if (NS_FAILED(rv)) {
1007 return rv;
1010 // Now that we've actually deleted the selected content,
1011 // check to see if our mExtent has changed, if so, then
1012 // we have to create a new content iterator!
1014 if (origStartNode && origEndNode) {
1015 nsCOMPtr<nsINode> curStartNode, curEndNode;
1016 uint32_t curStartOffset = 0, curEndOffset = 0;
1018 rv = GetRangeEndPoints(mExtent, getter_AddRefs(curStartNode),
1019 &curStartOffset, getter_AddRefs(curEndNode),
1020 &curEndOffset);
1022 if (NS_FAILED(rv)) {
1023 return rv;
1026 if (origStartNode != curStartNode || origEndNode != curEndNode) {
1027 // The range has changed, so we need to create a new content
1028 // iterator based on the new range.
1029 nsCOMPtr<nsIContent> curContent;
1030 if (mIteratorStatus != IteratorStatus::eDone) {
1031 // The old iterator is still pointing to something valid,
1032 // so get its current node so we can restore it after we
1033 // create the new iterator!
1034 curContent = mFilteredIter->GetCurrentNode()
1035 ? mFilteredIter->GetCurrentNode()->AsContent()
1036 : nullptr;
1039 // Create the new iterator.
1040 rv =
1041 CreateFilteredContentIterator(mExtent, getter_AddRefs(mFilteredIter));
1042 if (NS_FAILED(rv)) {
1043 return rv;
1046 // Now make the new iterator point to the content node
1047 // the old one was pointing at.
1048 if (curContent) {
1049 rv = mFilteredIter->PositionAt(curContent);
1050 if (NS_FAILED(rv)) {
1051 mIteratorStatus = IteratorStatus::eDone;
1052 } else {
1053 mIteratorStatus = IteratorStatus::eValid;
1059 OffsetEntry* entry = mOffsetTable.DidDeleteSelection();
1060 if (entry) {
1061 SetSelection(mOffsetTable.mSelection.StartOffsetInTextInBlock(), 0);
1064 // Now remove any invalid entries from the offset table.
1065 mOffsetTable.RemoveInvalidElements();
1066 return NS_OK;
1069 OffsetEntry* TextServicesDocument::OffsetEntryArray::DidDeleteSelection() {
1070 MOZ_ASSERT(mSelection.IsSet());
1072 // Move the caret to the end of the first valid entry.
1073 // Start with SelectionStartIndex() since it may still be valid.
1074 OffsetEntry* entry = nullptr;
1075 for (size_t i = mSelection.StartIndex() + 1; !entry && i > 0; i--) {
1076 entry = ElementAt(i - 1).get();
1077 if (!entry->mIsValid) {
1078 entry = nullptr;
1079 } else {
1080 MOZ_DIAGNOSTIC_ASSERT(i - 1 < Length());
1081 mSelection.Set(i - 1, entry->EndOffsetInTextInBlock());
1085 // If we still don't have a valid entry, move the caret
1086 // to the next valid entry after the selection:
1087 for (size_t i = mSelection.EndIndex(); !entry && i < Length(); i++) {
1088 entry = ElementAt(i).get();
1089 if (!entry->mIsValid) {
1090 entry = nullptr;
1091 } else {
1092 MOZ_DIAGNOSTIC_ASSERT(i < Length());
1093 mSelection.Set(i, entry->mOffsetInTextInBlock);
1097 if (!entry) {
1098 // Uuughh we have no valid offset entry to place our
1099 // caret ... just mark the selection invalid.
1100 mSelection.Reset();
1103 return entry;
1106 nsresult TextServicesDocument::InsertText(const nsAString& aText) {
1107 if (NS_WARN_IF(!mEditorBase) ||
1108 NS_WARN_IF(!mOffsetTable.mSelection.IsSet())) {
1109 return NS_ERROR_FAILURE;
1112 // If the selection is not collapsed, we need to save
1113 // off the selection offsets so we can restore the
1114 // selection and delete the selected content after we've
1115 // inserted the new text. This is necessary to try and
1116 // retain as much of the original style of the content
1117 // being deleted.
1119 const bool wasSelectionCollapsed = mOffsetTable.mSelection.IsCollapsed();
1120 const uint32_t savedSelOffset =
1121 mOffsetTable.mSelection.StartOffsetInTextInBlock();
1122 const uint32_t savedSelLength = mOffsetTable.mSelection.LengthInTextInBlock();
1124 if (!wasSelectionCollapsed) {
1125 // Collapse to the start of the current selection
1126 // for the insert!
1127 nsresult rv =
1128 SetSelection(mOffsetTable.mSelection.StartOffsetInTextInBlock(), 0);
1129 NS_ENSURE_SUCCESS(rv, rv);
1132 // AutoTransactionBatchExternal grabs mEditorBase, so, we don't need to grab
1133 // the instance with local variable here.
1134 OwningNonNull<EditorBase> editorBase = *mEditorBase;
1135 AutoTransactionBatchExternal treatAsOneTransaction(editorBase);
1137 nsresult rv = editorBase->InsertTextAsAction(aText);
1138 if (NS_FAILED(rv)) {
1139 NS_WARNING("InsertTextAsAction() failed");
1140 return rv;
1143 RefPtr<Selection> selection =
1144 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
1145 rv = mOffsetTable.DidInsertText(selection, aText);
1146 if (NS_FAILED(rv)) {
1147 NS_WARNING("TextServicesDocument::OffsetEntry::DidInsertText() failed");
1148 return rv;
1151 if (!wasSelectionCollapsed) {
1152 nsresult rv = SetSelection(savedSelOffset, savedSelLength);
1153 if (NS_FAILED(rv)) {
1154 return rv;
1157 rv = DeleteSelection();
1158 if (NS_FAILED(rv)) {
1159 return rv;
1163 return NS_OK;
1166 nsresult TextServicesDocument::OffsetEntryArray::DidInsertText(
1167 dom::Selection* aSelection, const nsAString& aInsertedString) {
1168 MOZ_ASSERT(mSelection.IsSet());
1170 // When you touch this method, please make sure that the entry instance
1171 // won't be deleted. If you know it'll be deleted, you should set it to
1172 // `nullptr`.
1173 OffsetEntry* entry = ElementAt(mSelection.StartIndex()).get();
1174 OwningNonNull<Text> const textNodeAtStartEntry = entry->mTextNode;
1176 NS_ASSERTION((entry->mIsValid), "Invalid insertion point!");
1178 if (entry->mOffsetInTextInBlock == mSelection.StartOffsetInTextInBlock()) {
1179 if (entry->mIsInsertedText) {
1180 // If the caret is in an inserted text offset entry,
1181 // we simply insert the text at the end of the entry.
1182 entry->mLength += aInsertedString.Length();
1183 } else {
1184 // Insert an inserted text offset entry before the current
1185 // entry!
1186 UniquePtr<OffsetEntry> newInsertedTextEntry =
1187 MakeUnique<OffsetEntry>(entry->mTextNode, entry->mOffsetInTextInBlock,
1188 aInsertedString.Length());
1189 newInsertedTextEntry->mIsInsertedText = true;
1190 newInsertedTextEntry->mOffsetInTextNode = entry->mOffsetInTextNode;
1191 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1192 // pretended earlier.
1193 InsertElementAt(mSelection.StartIndex(), std::move(newInsertedTextEntry));
1195 } else if (entry->EndOffsetInTextInBlock() ==
1196 mSelection.EndOffsetInTextInBlock()) {
1197 // We are inserting text at the end of the current offset entry.
1198 // Look at the next valid entry in the table. If it's an inserted
1199 // text entry, add to its length and adjust its node offset. If
1200 // it isn't, add a new inserted text entry.
1201 uint32_t nextIndex = mSelection.StartIndex() + 1;
1202 OffsetEntry* insertedTextEntry = nullptr;
1203 if (Length() > nextIndex) {
1204 insertedTextEntry = ElementAt(nextIndex).get();
1205 if (!insertedTextEntry) {
1206 return NS_ERROR_FAILURE;
1209 // Check if the entry is a match. If it isn't, set
1210 // iEntry to zero.
1211 if (!insertedTextEntry->mIsInsertedText ||
1212 insertedTextEntry->mOffsetInTextInBlock !=
1213 mSelection.StartOffsetInTextInBlock()) {
1214 insertedTextEntry = nullptr;
1218 if (!insertedTextEntry) {
1219 // We didn't find an inserted text offset entry, so
1220 // create one.
1221 UniquePtr<OffsetEntry> newInsertedTextEntry = MakeUnique<OffsetEntry>(
1222 entry->mTextNode, mSelection.StartOffsetInTextInBlock(), 0);
1223 newInsertedTextEntry->mOffsetInTextNode = entry->EndOffsetInTextNode();
1224 newInsertedTextEntry->mIsInsertedText = true;
1225 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1226 // pretended earlier.
1227 insertedTextEntry =
1228 InsertElementAt(nextIndex, std::move(newInsertedTextEntry))->get();
1231 // We have a valid inserted text offset entry. Update its
1232 // length, adjust the selection indexes, and make sure the
1233 // caret is properly placed!
1235 insertedTextEntry->mLength += aInsertedString.Length();
1237 MOZ_DIAGNOSTIC_ASSERT(nextIndex < Length());
1238 mSelection.SetIndex(nextIndex);
1240 if (!aSelection) {
1241 return NS_OK;
1244 OwningNonNull<Text> textNode = insertedTextEntry->mTextNode;
1245 nsresult rv = aSelection->CollapseInLimiter(
1246 textNode, insertedTextEntry->EndOffsetInTextNode());
1247 if (NS_FAILED(rv)) {
1248 NS_WARNING("Selection::CollapseInLimiter() failed");
1249 return rv;
1251 } else if (entry->EndOffsetInTextInBlock() >
1252 mSelection.StartOffsetInTextInBlock()) {
1253 // We are inserting text into the middle of the current offset entry.
1254 // split the current entry into two parts, then insert an inserted text
1255 // entry between them!
1256 nsresult rv = SplitElementAt(mSelection.StartIndex(),
1257 entry->EndOffsetInTextInBlock() -
1258 mSelection.StartOffsetInTextInBlock());
1259 if (NS_FAILED(rv)) {
1260 NS_WARNING(
1261 "entry->EndOffsetInTextInBlock() - "
1262 "mSelection.StartOffsetInTextInBlock() was invalid for the "
1263 "OffsetEntry");
1264 return rv;
1267 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1268 // pretended earlier.
1269 UniquePtr<OffsetEntry>& insertedTextEntry = *InsertElementAt(
1270 mSelection.StartIndex() + 1,
1271 MakeUnique<OffsetEntry>(entry->mTextNode,
1272 mSelection.StartOffsetInTextInBlock(),
1273 aInsertedString.Length()));
1274 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
1275 insertedTextEntry->mIsInsertedText = true;
1276 insertedTextEntry->mOffsetInTextNode = entry->EndOffsetInTextNode();
1277 MOZ_DIAGNOSTIC_ASSERT(mSelection.StartIndex() + 1 < Length());
1278 mSelection.SetIndex(mSelection.StartIndex() + 1);
1281 // We've just finished inserting an inserted text offset entry.
1282 // update all entries with the same mTextNode pointer that follow
1283 // it in the table!
1285 for (size_t i = mSelection.StartIndex() + 1; i < Length(); i++) {
1286 const UniquePtr<OffsetEntry>& entry = ElementAt(i);
1287 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
1288 if (entry->mTextNode != textNodeAtStartEntry) {
1289 break;
1291 if (entry->mIsValid) {
1292 entry->mOffsetInTextNode += aInsertedString.Length();
1296 return NS_OK;
1299 void TextServicesDocument::DidDeleteContent(const nsIContent& aChildContent) {
1300 if (NS_WARN_IF(!mFilteredIter) || !aChildContent.IsText()) {
1301 return;
1304 Maybe<size_t> maybeNodeIndex =
1305 mOffsetTable.FirstIndexOf(*aChildContent.AsText());
1306 if (maybeNodeIndex.isNothing()) {
1307 // It's okay if the node isn't in the offset table, the
1308 // editor could be cleaning house.
1309 return;
1312 nsINode* node = mFilteredIter->GetCurrentNode();
1313 if (node && node == &aChildContent &&
1314 mIteratorStatus != IteratorStatus::eDone) {
1315 // XXX: This should never really happen because
1316 // AdjustContentIterator() should have been called prior
1317 // to the delete to try and position the iterator on the
1318 // next valid text node in the offset table, and if there
1319 // wasn't a next, it would've set mIteratorStatus to eIsDone.
1321 NS_ERROR("DeleteNode called for current iterator node.");
1324 for (size_t nodeIndex = *maybeNodeIndex; nodeIndex < mOffsetTable.Length();
1325 nodeIndex++) {
1326 const UniquePtr<OffsetEntry>& entry = mOffsetTable[nodeIndex];
1327 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
1328 if (!entry) {
1329 return;
1332 if (entry->mTextNode == &aChildContent) {
1333 entry->mIsValid = false;
1338 void TextServicesDocument::DidJoinContents(
1339 const EditorRawDOMPoint& aJoinedPoint, const nsIContent& aRemovedContent) {
1340 // Make sure that both nodes are text nodes -- otherwise we don't care.
1341 if (!aJoinedPoint.IsInTextNode() || !aRemovedContent.IsText()) {
1342 return;
1345 // Note: The editor merges the contents of the left node into the
1346 // contents of the right.
1348 Maybe<size_t> maybeRemovedIndex =
1349 mOffsetTable.FirstIndexOf(*aRemovedContent.AsText());
1350 if (maybeRemovedIndex.isNothing()) {
1351 // It's okay if the node isn't in the offset table, the
1352 // editor could be cleaning house.
1353 return;
1356 Maybe<size_t> maybeJoinedIndex =
1357 mOffsetTable.FirstIndexOf(*aJoinedPoint.ContainerAs<Text>());
1358 if (maybeJoinedIndex.isNothing()) {
1359 // It's okay if the node isn't in the offset table, the
1360 // editor could be cleaning house.
1361 return;
1364 const size_t removedIndex = *maybeRemovedIndex;
1365 const size_t joinedIndex = *maybeJoinedIndex;
1367 if (MOZ_UNLIKELY(joinedIndex > removedIndex)) {
1368 NS_ASSERTION(joinedIndex < removedIndex, "Indexes out of order.");
1369 return;
1371 NS_ASSERTION(mOffsetTable[removedIndex]->mOffsetInTextNode == 0,
1372 "Unexpected offset value for rightIndex.");
1374 // Run through the table and change all entries referring to
1375 // the removed node so that they now refer to the joined node,
1376 // and adjust offsets if necessary.
1377 const uint32_t movedTextDataLength =
1378 aJoinedPoint.ContainerAs<Text>()->TextDataLength() -
1379 aJoinedPoint.Offset();
1380 for (uint32_t i = removedIndex; i < mOffsetTable.Length(); i++) {
1381 const UniquePtr<OffsetEntry>& entry = mOffsetTable[i];
1382 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
1383 if (entry->mTextNode != aRemovedContent.AsText()) {
1384 break;
1386 if (entry->mIsValid) {
1387 entry->mTextNode = aJoinedPoint.ContainerAs<Text>();
1388 // The text was moved from aRemovedContent to end of the container of
1389 // aJoinedPoint.
1390 entry->mOffsetInTextNode += movedTextDataLength;
1394 // Now check to see if the iterator is pointing to the
1395 // left node. If it is, make it point to the joined node!
1396 if (mFilteredIter->GetCurrentNode() == aRemovedContent.AsText()) {
1397 mFilteredIter->PositionAt(aJoinedPoint.ContainerAs<Text>());
1401 nsresult TextServicesDocument::CreateFilteredContentIterator(
1402 const AbstractRange* aAbstractRange,
1403 FilteredContentIterator** aFilteredIter) {
1404 if (NS_WARN_IF(!aAbstractRange) || NS_WARN_IF(!aFilteredIter)) {
1405 return NS_ERROR_INVALID_ARG;
1408 *aFilteredIter = nullptr;
1410 UniquePtr<nsComposeTxtSrvFilter> composeFilter;
1411 switch (mTxtSvcFilterType) {
1412 case nsIEditorSpellCheck::FILTERTYPE_NORMAL:
1413 composeFilter = nsComposeTxtSrvFilter::CreateNormalFilter();
1414 break;
1415 case nsIEditorSpellCheck::FILTERTYPE_MAIL:
1416 composeFilter = nsComposeTxtSrvFilter::CreateMailFilter();
1417 break;
1420 // Create a FilteredContentIterator
1421 // This class wraps the ContentIterator in order to give itself a chance
1422 // to filter out certain content nodes
1423 RefPtr<FilteredContentIterator> filter =
1424 new FilteredContentIterator(std::move(composeFilter));
1425 nsresult rv = filter->Init(aAbstractRange);
1426 if (NS_FAILED(rv)) {
1427 return rv;
1430 filter.forget(aFilteredIter);
1431 return NS_OK;
1434 Element* TextServicesDocument::GetDocumentContentRootNode() const {
1435 if (NS_WARN_IF(!mDocument)) {
1436 return nullptr;
1439 if (mDocument->IsHTMLOrXHTML()) {
1440 Element* rootElement = mDocument->GetRootElement();
1441 if (rootElement && rootElement->IsXULElement()) {
1442 // HTML documents with root XUL elements should eventually be transitioned
1443 // to a regular document structure, but for now the content root node will
1444 // be the document element.
1445 return mDocument->GetDocumentElement();
1447 // For HTML documents, the content root node is the body.
1448 return mDocument->GetBody();
1451 // For non-HTML documents, the content root node will be the document element.
1452 return mDocument->GetDocumentElement();
1455 already_AddRefed<nsRange> TextServicesDocument::CreateDocumentContentRange() {
1456 nsCOMPtr<nsINode> node = GetDocumentContentRootNode();
1457 if (NS_WARN_IF(!node)) {
1458 return nullptr;
1461 RefPtr<nsRange> range = nsRange::Create(node);
1462 IgnoredErrorResult ignoredError;
1463 range->SelectNodeContents(*node, ignoredError);
1464 NS_WARNING_ASSERTION(!ignoredError.Failed(), "SelectNodeContents() failed");
1465 return range.forget();
1468 already_AddRefed<nsRange>
1469 TextServicesDocument::CreateDocumentContentRootToNodeOffsetRange(
1470 nsINode* aParent, uint32_t aOffset, bool aToStart) {
1471 if (NS_WARN_IF(!aParent)) {
1472 return nullptr;
1475 nsCOMPtr<nsINode> bodyNode = GetDocumentContentRootNode();
1476 if (NS_WARN_IF(!bodyNode)) {
1477 return nullptr;
1480 nsCOMPtr<nsINode> startNode;
1481 nsCOMPtr<nsINode> endNode;
1482 uint32_t startOffset, endOffset;
1484 if (aToStart) {
1485 // The range should begin at the start of the document
1486 // and extend up until (aParent, aOffset).
1487 startNode = bodyNode;
1488 startOffset = 0;
1489 endNode = aParent;
1490 endOffset = aOffset;
1491 } else {
1492 // The range should begin at (aParent, aOffset) and
1493 // extend to the end of the document.
1494 startNode = aParent;
1495 startOffset = aOffset;
1496 endNode = bodyNode;
1497 endOffset = endNode ? endNode->GetChildCount() : 0;
1500 RefPtr<nsRange> range = nsRange::Create(startNode, startOffset, endNode,
1501 endOffset, IgnoreErrors());
1502 NS_WARNING_ASSERTION(range,
1503 "nsRange::Create() failed to create new valid range");
1504 return range.forget();
1507 nsresult TextServicesDocument::CreateDocumentContentIterator(
1508 FilteredContentIterator** aFilteredIter) {
1509 NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER);
1511 RefPtr<nsRange> range = CreateDocumentContentRange();
1512 if (NS_WARN_IF(!range)) {
1513 *aFilteredIter = nullptr;
1514 return NS_ERROR_FAILURE;
1517 return CreateFilteredContentIterator(range, aFilteredIter);
1520 nsresult TextServicesDocument::AdjustContentIterator() {
1521 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
1523 nsCOMPtr<nsINode> node = mFilteredIter->GetCurrentNode();
1524 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
1526 Text* prevValidTextNode = nullptr;
1527 Text* nextValidTextNode = nullptr;
1528 bool foundEntry = false;
1530 const size_t tableLength = mOffsetTable.Length();
1531 for (size_t i = 0; i < tableLength && !nextValidTextNode; i++) {
1532 UniquePtr<OffsetEntry>& entry = mOffsetTable[i];
1533 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
1534 if (entry->mTextNode == node) {
1535 if (entry->mIsValid) {
1536 // The iterator is still pointing to something valid!
1537 // Do nothing!
1538 return NS_OK;
1540 // We found an invalid entry that points to
1541 // the current iterator node. Stop looking for
1542 // a previous valid node!
1543 foundEntry = true;
1546 if (entry->mIsValid) {
1547 if (!foundEntry) {
1548 prevValidTextNode = entry->mTextNode;
1549 } else {
1550 nextValidTextNode = entry->mTextNode;
1555 Text* validTextNode = nullptr;
1556 if (prevValidTextNode) {
1557 validTextNode = prevValidTextNode;
1558 } else if (nextValidTextNode) {
1559 validTextNode = nextValidTextNode;
1562 if (validTextNode) {
1563 nsresult rv = mFilteredIter->PositionAt(validTextNode);
1564 if (NS_FAILED(rv)) {
1565 mIteratorStatus = IteratorStatus::eDone;
1566 } else {
1567 mIteratorStatus = IteratorStatus::eValid;
1569 return rv;
1572 // If we get here, there aren't any valid entries
1573 // in the offset table! Try to position the iterator
1574 // on the next text block first, then previous if
1575 // one doesn't exist!
1577 if (mNextTextBlock) {
1578 nsresult rv = mFilteredIter->PositionAt(mNextTextBlock);
1579 if (NS_FAILED(rv)) {
1580 mIteratorStatus = IteratorStatus::eDone;
1581 return rv;
1584 mIteratorStatus = IteratorStatus::eNext;
1585 } else if (mPrevTextBlock) {
1586 nsresult rv = mFilteredIter->PositionAt(mPrevTextBlock);
1587 if (NS_FAILED(rv)) {
1588 mIteratorStatus = IteratorStatus::eDone;
1589 return rv;
1592 mIteratorStatus = IteratorStatus::ePrev;
1593 } else {
1594 mIteratorStatus = IteratorStatus::eDone;
1596 return NS_OK;
1599 // static
1600 bool TextServicesDocument::DidSkip(FilteredContentIterator* aFilteredIter) {
1601 return aFilteredIter && aFilteredIter->DidSkip();
1604 // static
1605 void TextServicesDocument::ClearDidSkip(
1606 FilteredContentIterator* aFilteredIter) {
1607 // Clear filter's skip flag
1608 if (aFilteredIter) {
1609 aFilteredIter->ClearDidSkip();
1613 // static
1614 bool TextServicesDocument::HasSameBlockNodeParent(Text& aTextNode1,
1615 Text& aTextNode2) {
1616 // XXX How about the case that both text nodes are orphan nodes?
1617 if (aTextNode1.GetParent() == aTextNode2.GetParent()) {
1618 return true;
1621 // I think that spellcheck should be available only in editable nodes.
1622 // So, we also need to check whether they are in same editing host.
1623 const Element* editableBlockElementOrInlineEditingHost1 =
1624 HTMLEditUtils::GetAncestorElement(
1625 aTextNode1,
1626 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost,
1627 BlockInlineCheck::UseHTMLDefaultStyle);
1628 const Element* editableBlockElementOrInlineEditingHost2 =
1629 HTMLEditUtils::GetAncestorElement(
1630 aTextNode2,
1631 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost,
1632 BlockInlineCheck::UseHTMLDefaultStyle);
1633 return editableBlockElementOrInlineEditingHost1 &&
1634 editableBlockElementOrInlineEditingHost1 ==
1635 editableBlockElementOrInlineEditingHost2;
1638 Result<EditorRawDOMRangeInTexts, nsresult>
1639 TextServicesDocument::OffsetEntryArray::WillSetSelection(
1640 uint32_t aOffsetInTextInBlock, uint32_t aLength) {
1641 // Find start of selection in node offset terms:
1642 EditorRawDOMPointInText newStart;
1643 for (size_t i = 0; !newStart.IsSet() && i < Length(); i++) {
1644 const UniquePtr<OffsetEntry>& entry = ElementAt(i);
1645 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
1646 if (entry->mIsValid) {
1647 if (entry->mIsInsertedText) {
1648 // Caret can only be placed at the end of an
1649 // inserted text offset entry, if the offsets
1650 // match exactly!
1651 if (entry->mOffsetInTextInBlock == aOffsetInTextInBlock) {
1652 newStart.Set(entry->mTextNode, entry->EndOffsetInTextNode());
1654 } else if (aOffsetInTextInBlock >= entry->mOffsetInTextInBlock) {
1655 bool foundEntry = false;
1656 if (aOffsetInTextInBlock < entry->EndOffsetInTextInBlock()) {
1657 foundEntry = true;
1658 } else if (aOffsetInTextInBlock == entry->EndOffsetInTextInBlock()) {
1659 // Peek after this entry to see if we have any
1660 // inserted text entries belonging to the same
1661 // entry->mTextNode. If so, we have to place the selection
1662 // after it!
1663 if (i + 1 < Length()) {
1664 const UniquePtr<OffsetEntry>& nextEntry = ElementAt(i + 1);
1665 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
1666 if (!nextEntry->mIsValid ||
1667 nextEntry->mOffsetInTextInBlock != aOffsetInTextInBlock) {
1668 // Next offset entry isn't an exact match, so we'll
1669 // just use the current entry.
1670 foundEntry = true;
1675 if (foundEntry) {
1676 newStart.Set(entry->mTextNode, entry->mOffsetInTextNode +
1677 aOffsetInTextInBlock -
1678 entry->mOffsetInTextInBlock);
1682 if (newStart.IsSet()) {
1683 MOZ_DIAGNOSTIC_ASSERT(i < Length());
1684 mSelection.Set(i, aOffsetInTextInBlock);
1689 if (NS_WARN_IF(!newStart.IsSet())) {
1690 return Err(NS_ERROR_FAILURE);
1693 if (!aLength) {
1694 mSelection.CollapseToStart();
1695 return EditorRawDOMRangeInTexts(newStart);
1698 // Find the end of the selection in node offset terms:
1699 EditorRawDOMPointInText newEnd;
1700 const uint32_t endOffset = aOffsetInTextInBlock + aLength;
1701 for (uint32_t i = Length(); !newEnd.IsSet() && i > 0; i--) {
1702 const UniquePtr<OffsetEntry>& entry = ElementAt(i - 1);
1703 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
1704 if (entry->mIsValid) {
1705 if (entry->mIsInsertedText) {
1706 if (entry->mOffsetInTextInBlock ==
1707 (newEnd.IsSet() ? newEnd.Offset() : 0)) {
1708 // If the selection ends on an inserted text offset entry,
1709 // the selection includes the entire entry!
1710 newEnd.Set(entry->mTextNode, entry->EndOffsetInTextNode());
1712 } else if (entry->OffsetInTextInBlockIsInRangeOrEndOffset(endOffset)) {
1713 newEnd.Set(entry->mTextNode, entry->mOffsetInTextNode + endOffset -
1714 entry->mOffsetInTextInBlock);
1717 if (newEnd.IsSet()) {
1718 MOZ_DIAGNOSTIC_ASSERT(mSelection.StartIndex() < Length());
1719 MOZ_DIAGNOSTIC_ASSERT(i - 1 < Length());
1720 mSelection.Set(mSelection.StartIndex(), i - 1,
1721 mSelection.StartOffsetInTextInBlock(), endOffset);
1726 return newEnd.IsSet() ? EditorRawDOMRangeInTexts(newStart, newEnd)
1727 : EditorRawDOMRangeInTexts(newStart);
1730 nsresult TextServicesDocument::SetSelectionInternal(
1731 uint32_t aOffsetInTextInBlock, uint32_t aLength, bool aDoUpdate) {
1732 if (NS_WARN_IF(!mSelCon)) {
1733 return NS_ERROR_INVALID_ARG;
1736 Result<EditorRawDOMRangeInTexts, nsresult> newSelectionRange =
1737 mOffsetTable.WillSetSelection(aOffsetInTextInBlock, aLength);
1738 if (newSelectionRange.isErr()) {
1739 NS_WARNING(
1740 "TextServicesDocument::OffsetEntryArray::WillSetSelection() failed");
1741 return newSelectionRange.unwrapErr();
1744 if (!aDoUpdate) {
1745 return NS_OK;
1748 // XXX: If we ever get a SetSelection() method in nsIEditor, we should
1749 // use it.
1750 RefPtr<Selection> selection =
1751 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
1752 if (NS_WARN_IF(!selection)) {
1753 return NS_ERROR_FAILURE;
1756 if (newSelectionRange.inspect().Collapsed()) {
1757 nsresult rv =
1758 selection->CollapseInLimiter(newSelectionRange.inspect().StartRef());
1759 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1760 "Selection::CollapseInLimiter() failed");
1761 return rv;
1764 ErrorResult error;
1765 selection->SetStartAndEndInLimiter(newSelectionRange.inspect().StartRef(),
1766 newSelectionRange.inspect().EndRef(),
1767 error);
1768 NS_WARNING_ASSERTION(!error.Failed(),
1769 "Selection::SetStartAndEndInLimiter() failed");
1770 return error.StealNSResult();
1773 nsresult TextServicesDocument::GetSelection(BlockSelectionStatus* aSelStatus,
1774 uint32_t* aSelOffset,
1775 uint32_t* aSelLength) {
1776 NS_ENSURE_TRUE(aSelStatus && aSelOffset && aSelLength, NS_ERROR_NULL_POINTER);
1778 *aSelStatus = BlockSelectionStatus::eBlockNotFound;
1779 *aSelOffset = UINT32_MAX;
1780 *aSelLength = UINT32_MAX;
1782 NS_ENSURE_TRUE(mDocument && mSelCon, NS_ERROR_FAILURE);
1784 if (mIteratorStatus == IteratorStatus::eDone) {
1785 return NS_OK;
1788 RefPtr<Selection> selection =
1789 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
1790 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1792 if (selection->IsCollapsed()) {
1793 return GetCollapsedSelection(aSelStatus, aSelOffset, aSelLength);
1796 return GetUncollapsedSelection(aSelStatus, aSelOffset, aSelLength);
1799 nsresult TextServicesDocument::GetCollapsedSelection(
1800 BlockSelectionStatus* aSelStatus, uint32_t* aSelOffset,
1801 uint32_t* aSelLength) {
1802 RefPtr<Selection> selection =
1803 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
1804 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1806 // The calling function should have done the GetIsCollapsed()
1807 // check already. Just assume it's collapsed!
1808 *aSelStatus = BlockSelectionStatus::eBlockOutside;
1809 *aSelOffset = *aSelLength = UINT32_MAX;
1811 const uint32_t tableCount = mOffsetTable.Length();
1812 if (!tableCount) {
1813 return NS_OK;
1816 // Get pointers to the first and last offset entries
1817 // in the table.
1819 UniquePtr<OffsetEntry>& eStart = mOffsetTable[0];
1820 UniquePtr<OffsetEntry>& eEnd =
1821 tableCount > 1 ? mOffsetTable[tableCount - 1] : eStart;
1822 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
1824 const uint32_t eStartOffset = eStart->mOffsetInTextNode;
1825 const uint32_t eEndOffset = eEnd->EndOffsetInTextNode();
1827 RefPtr<const nsRange> range = selection->GetRangeAt(0);
1828 NS_ENSURE_STATE(range);
1830 nsCOMPtr<nsINode> parent = range->GetStartContainer();
1831 MOZ_ASSERT(parent);
1833 uint32_t offset = range->StartOffset();
1835 const Maybe<int32_t> e1s1 = nsContentUtils::ComparePoints(
1836 eStart->mTextNode, eStartOffset, parent, offset);
1837 const Maybe<int32_t> e2s1 = nsContentUtils::ComparePoints(
1838 eEnd->mTextNode, eEndOffset, parent, offset);
1840 if (MOZ_UNLIKELY(NS_WARN_IF(!e1s1) || NS_WARN_IF(!e2s1))) {
1841 return NS_ERROR_FAILURE;
1844 if (*e1s1 > 0 || *e2s1 < 0) {
1845 // We're done if the caret is outside the current text block.
1846 return NS_OK;
1849 if (parent->IsText()) {
1850 // Good news, the caret is in a text node. Look
1851 // through the offset table for the entry that
1852 // matches its parent and offset.
1854 for (uint32_t i = 0; i < tableCount; i++) {
1855 const UniquePtr<OffsetEntry>& entry = mOffsetTable[i];
1856 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
1857 if (entry->mTextNode == parent->AsText() &&
1858 entry->OffsetInTextNodeIsInRangeOrEndOffset(offset)) {
1859 *aSelStatus = BlockSelectionStatus::eBlockContains;
1860 *aSelOffset =
1861 entry->mOffsetInTextInBlock + (offset - entry->mOffsetInTextNode);
1862 *aSelLength = 0;
1863 return NS_OK;
1867 // If we get here, we didn't find a text node entry
1868 // in our offset table that matched.
1869 return NS_ERROR_FAILURE;
1872 // The caret is in our text block, but it's positioned in some
1873 // non-text node (ex. <b>). Create a range based on the start
1874 // and end of the text block, then create an iterator based on
1875 // this range, with its initial position set to the closest
1876 // child of this non-text node. Then look for the closest text
1877 // node.
1879 range = nsRange::Create(eStart->mTextNode, eStartOffset, eEnd->mTextNode,
1880 eEndOffset, IgnoreErrors());
1881 if (NS_WARN_IF(!range)) {
1882 return NS_ERROR_FAILURE;
1885 RefPtr<FilteredContentIterator> filteredIter;
1886 nsresult rv =
1887 CreateFilteredContentIterator(range, getter_AddRefs(filteredIter));
1888 NS_ENSURE_SUCCESS(rv, rv);
1890 nsIContent* saveNode;
1891 if (parent->HasChildren()) {
1892 // XXX: We need to make sure that all of parent's
1893 // children are in the text block.
1895 // If the parent has children, position the iterator
1896 // on the child that is to the left of the offset.
1898 nsIContent* content = range->GetChildAtStartOffset();
1899 if (content && parent->GetFirstChild() != content) {
1900 content = content->GetPreviousSibling();
1902 NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
1904 nsresult rv = filteredIter->PositionAt(content);
1905 NS_ENSURE_SUCCESS(rv, rv);
1907 saveNode = content;
1908 } else {
1909 // The parent has no children, so position the iterator
1910 // on the parent.
1911 NS_ENSURE_TRUE(parent->IsContent(), NS_ERROR_FAILURE);
1912 nsCOMPtr<nsIContent> content = parent->AsContent();
1914 nsresult rv = filteredIter->PositionAt(content);
1915 NS_ENSURE_SUCCESS(rv, rv);
1917 saveNode = content;
1920 // Now iterate to the left, towards the beginning of
1921 // the text block, to find the first text node you
1922 // come across.
1924 Text* textNode = nullptr;
1925 for (; !filteredIter->IsDone(); filteredIter->Prev()) {
1926 nsINode* current = filteredIter->GetCurrentNode();
1927 if (current->IsText()) {
1928 textNode = current->AsText();
1929 break;
1933 if (textNode) {
1934 // We found a node, now set the offset to the end
1935 // of the text node.
1936 offset = textNode->TextLength();
1937 } else {
1938 // We should never really get here, but I'm paranoid.
1940 // We didn't find a text node above, so iterate to
1941 // the right, towards the end of the text block, looking
1942 // for a text node.
1944 nsresult rv = filteredIter->PositionAt(saveNode);
1945 NS_ENSURE_SUCCESS(rv, rv);
1947 textNode = nullptr;
1948 for (; !filteredIter->IsDone(); filteredIter->Next()) {
1949 nsINode* current = filteredIter->GetCurrentNode();
1950 if (current->IsText()) {
1951 textNode = current->AsText();
1952 break;
1955 NS_ENSURE_TRUE(textNode, NS_ERROR_FAILURE);
1957 // We found a text node, so set the offset to
1958 // the beginning of the node.
1959 offset = 0;
1962 for (size_t i = 0; i < tableCount; i++) {
1963 const UniquePtr<OffsetEntry>& entry = mOffsetTable[i];
1964 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
1965 if (entry->mTextNode == textNode &&
1966 entry->OffsetInTextNodeIsInRangeOrEndOffset(offset)) {
1967 *aSelStatus = BlockSelectionStatus::eBlockContains;
1968 *aSelOffset =
1969 entry->mOffsetInTextInBlock + (offset - entry->mOffsetInTextNode);
1970 *aSelLength = 0;
1972 // Now move the caret so that it is actually in the text node.
1973 // We do this to keep things in sync.
1975 // In most cases, the user shouldn't see any movement in the caret
1976 // on screen.
1977 return SetSelectionInternal(*aSelOffset, *aSelLength, true);
1981 return NS_ERROR_FAILURE;
1984 nsresult TextServicesDocument::GetUncollapsedSelection(
1985 BlockSelectionStatus* aSelStatus, uint32_t* aSelOffset,
1986 uint32_t* aSelLength) {
1987 RefPtr<const nsRange> range;
1988 RefPtr<Selection> selection =
1989 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
1990 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1992 // It is assumed that the calling function has made sure that the
1993 // selection is not collapsed, and that the input params to this
1994 // method are initialized to some defaults.
1996 nsCOMPtr<nsINode> startContainer, endContainer;
1998 const size_t tableCount = mOffsetTable.Length();
2000 // Get pointers to the first and last offset entries
2001 // in the table.
2003 UniquePtr<OffsetEntry>& eStart = mOffsetTable[0];
2004 UniquePtr<OffsetEntry>& eEnd =
2005 tableCount > 1 ? mOffsetTable[tableCount - 1] : eStart;
2006 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
2008 const uint32_t eStartOffset = eStart->mOffsetInTextNode;
2009 const uint32_t eEndOffset = eEnd->EndOffsetInTextNode();
2011 const uint32_t rangeCount = selection->RangeCount();
2012 MOZ_ASSERT(rangeCount);
2014 // Find the first range in the selection that intersects
2015 // the current text block.
2016 Maybe<int32_t> e1s2;
2017 Maybe<int32_t> e2s1;
2018 uint32_t startOffset, endOffset;
2019 for (const uint32_t i : IntegerRange(rangeCount)) {
2020 MOZ_ASSERT(selection->RangeCount() == rangeCount);
2021 range = selection->GetRangeAt(i);
2022 if (MOZ_UNLIKELY(NS_WARN_IF(!range))) {
2023 return NS_ERROR_FAILURE;
2026 nsresult rv =
2027 GetRangeEndPoints(range, getter_AddRefs(startContainer), &startOffset,
2028 getter_AddRefs(endContainer), &endOffset);
2030 NS_ENSURE_SUCCESS(rv, rv);
2032 e1s2 = nsContentUtils::ComparePoints(eStart->mTextNode, eStartOffset,
2033 endContainer, endOffset);
2034 if (NS_WARN_IF(!e1s2)) {
2035 return NS_ERROR_FAILURE;
2038 e2s1 = nsContentUtils::ComparePoints(eEnd->mTextNode, eEndOffset,
2039 startContainer, startOffset);
2040 if (NS_WARN_IF(!e2s1)) {
2041 return NS_ERROR_FAILURE;
2044 // Break out of the loop if the text block intersects the current range.
2046 if (*e1s2 <= 0 && *e2s1 >= 0) {
2047 break;
2051 // We're done if we didn't find an intersecting range.
2053 if (rangeCount < 1 || *e1s2 > 0 || *e2s1 < 0) {
2054 *aSelStatus = BlockSelectionStatus::eBlockOutside;
2055 *aSelOffset = *aSelLength = UINT32_MAX;
2056 return NS_OK;
2059 // Now that we have an intersecting range, find out more info:
2060 const Maybe<int32_t> e1s1 = nsContentUtils::ComparePoints(
2061 eStart->mTextNode, eStartOffset, startContainer, startOffset);
2062 if (NS_WARN_IF(!e1s1)) {
2063 return NS_ERROR_FAILURE;
2066 const Maybe<int32_t> e2s2 = nsContentUtils::ComparePoints(
2067 eEnd->mTextNode, eEndOffset, endContainer, endOffset);
2068 if (NS_WARN_IF(!e2s2)) {
2069 return NS_ERROR_FAILURE;
2072 if (rangeCount > 1) {
2073 // There are multiple selection ranges, we only deal
2074 // with the first one that intersects the current,
2075 // text block, so mark this a as a partial.
2076 *aSelStatus = BlockSelectionStatus::eBlockPartial;
2077 } else if (*e1s1 > 0 && *e2s2 < 0) {
2078 // The range extends beyond the start and
2079 // end of the current text block.
2080 *aSelStatus = BlockSelectionStatus::eBlockInside;
2081 } else if (*e1s1 <= 0 && *e2s2 >= 0) {
2082 // The current text block contains the entire
2083 // range.
2084 *aSelStatus = BlockSelectionStatus::eBlockContains;
2085 } else {
2086 // The range partially intersects the block.
2087 *aSelStatus = BlockSelectionStatus::eBlockPartial;
2090 // Now create a range based on the intersection of the
2091 // text block and range:
2093 nsCOMPtr<nsINode> p1, p2;
2094 uint32_t o1, o2;
2096 // The start of the range will be the rightmost
2097 // start node.
2099 if (*e1s1 >= 0) {
2100 p1 = eStart->mTextNode;
2101 o1 = eStartOffset;
2102 } else {
2103 p1 = startContainer;
2104 o1 = startOffset;
2107 // The end of the range will be the leftmost
2108 // end node.
2110 if (*e2s2 <= 0) {
2111 p2 = eEnd->mTextNode;
2112 o2 = eEndOffset;
2113 } else {
2114 p2 = endContainer;
2115 o2 = endOffset;
2118 range = nsRange::Create(p1, o1, p2, o2, IgnoreErrors());
2119 if (NS_WARN_IF(!range)) {
2120 return NS_ERROR_FAILURE;
2123 // Now iterate over this range to figure out the selection's
2124 // block offset and length.
2126 RefPtr<FilteredContentIterator> filteredIter;
2127 nsresult rv =
2128 CreateFilteredContentIterator(range, getter_AddRefs(filteredIter));
2129 NS_ENSURE_SUCCESS(rv, rv);
2131 // Find the first text node in the range.
2132 nsCOMPtr<nsIContent> content;
2133 filteredIter->First();
2134 if (!p1->IsText()) {
2135 bool found = false;
2136 for (; !filteredIter->IsDone(); filteredIter->Next()) {
2137 nsINode* node = filteredIter->GetCurrentNode();
2138 if (node->IsText()) {
2139 p1 = node->AsText();
2140 o1 = 0;
2141 found = true;
2142 break;
2145 NS_ENSURE_TRUE(found, NS_ERROR_FAILURE);
2148 // Find the last text node in the range.
2149 filteredIter->Last();
2150 if (!p2->IsText()) {
2151 bool found = false;
2152 for (; !filteredIter->IsDone(); filteredIter->Prev()) {
2153 nsINode* node = filteredIter->GetCurrentNode();
2154 if (node->IsText()) {
2155 p2 = node->AsText();
2156 o2 = p2->AsText()->Length();
2157 found = true;
2159 break;
2162 NS_ENSURE_TRUE(found, NS_ERROR_FAILURE);
2165 bool found = false;
2166 *aSelLength = 0;
2168 for (size_t i = 0; i < tableCount; i++) {
2169 const UniquePtr<OffsetEntry>& entry = mOffsetTable[i];
2170 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable);
2171 if (!found) {
2172 if (entry->mTextNode == p1.get() &&
2173 entry->OffsetInTextNodeIsInRangeOrEndOffset(o1)) {
2174 *aSelOffset =
2175 entry->mOffsetInTextInBlock + (o1 - entry->mOffsetInTextNode);
2176 if (p1 == p2 && entry->OffsetInTextNodeIsInRangeOrEndOffset(o2)) {
2177 // The start and end of the range are in the same offset
2178 // entry. Calculate the length of the range then we're done.
2179 *aSelLength = o2 - o1;
2180 break;
2182 // Add the length of the sub string in this offset entry
2183 // that follows the start of the range.
2184 *aSelLength = entry->EndOffsetInTextNode() - o1;
2185 found = true;
2187 } else { // Found.
2188 if (entry->mTextNode == p2.get() &&
2189 entry->OffsetInTextNodeIsInRangeOrEndOffset(o2)) {
2190 // We found the end of the range. Calculate the length of the
2191 // sub string that is before the end of the range, then we're done.
2192 *aSelLength += o2 - entry->mOffsetInTextNode;
2193 break;
2195 // The entire entry must be in the range.
2196 *aSelLength += entry->mLength;
2200 return NS_OK;
2203 // static
2204 nsresult TextServicesDocument::GetRangeEndPoints(
2205 const AbstractRange* aAbstractRange, nsINode** aStartContainer,
2206 uint32_t* aStartOffset, nsINode** aEndContainer, uint32_t* aEndOffset) {
2207 if (NS_WARN_IF(!aAbstractRange) || NS_WARN_IF(!aStartContainer) ||
2208 NS_WARN_IF(!aEndContainer) || NS_WARN_IF(!aEndOffset)) {
2209 return NS_ERROR_INVALID_ARG;
2212 nsCOMPtr<nsINode> startContainer = aAbstractRange->GetStartContainer();
2213 if (NS_WARN_IF(!startContainer)) {
2214 return NS_ERROR_FAILURE;
2216 nsCOMPtr<nsINode> endContainer = aAbstractRange->GetEndContainer();
2217 if (NS_WARN_IF(!endContainer)) {
2218 return NS_ERROR_FAILURE;
2221 startContainer.forget(aStartContainer);
2222 endContainer.forget(aEndContainer);
2223 *aStartOffset = aAbstractRange->StartOffset();
2224 *aEndOffset = aAbstractRange->EndOffset();
2225 return NS_OK;
2228 // static
2229 nsresult TextServicesDocument::FirstTextNode(
2230 FilteredContentIterator* aFilteredIter, IteratorStatus* aIteratorStatus) {
2231 if (aIteratorStatus) {
2232 *aIteratorStatus = IteratorStatus::eDone;
2235 for (aFilteredIter->First(); !aFilteredIter->IsDone();
2236 aFilteredIter->Next()) {
2237 if (aFilteredIter->GetCurrentNode()->NodeType() == nsINode::TEXT_NODE) {
2238 if (aIteratorStatus) {
2239 *aIteratorStatus = IteratorStatus::eValid;
2241 break;
2245 return NS_OK;
2248 // static
2249 nsresult TextServicesDocument::LastTextNode(
2250 FilteredContentIterator* aFilteredIter, IteratorStatus* aIteratorStatus) {
2251 if (aIteratorStatus) {
2252 *aIteratorStatus = IteratorStatus::eDone;
2255 for (aFilteredIter->Last(); !aFilteredIter->IsDone(); aFilteredIter->Prev()) {
2256 if (aFilteredIter->GetCurrentNode()->NodeType() == nsINode::TEXT_NODE) {
2257 if (aIteratorStatus) {
2258 *aIteratorStatus = IteratorStatus::eValid;
2260 break;
2264 return NS_OK;
2267 // static
2268 nsresult TextServicesDocument::FirstTextNodeInCurrentBlock(
2269 FilteredContentIterator* aFilteredIter) {
2270 NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER);
2272 ClearDidSkip(aFilteredIter);
2274 // Walk backwards over adjacent text nodes until
2275 // we hit a block boundary:
2276 RefPtr<Text> lastTextNode;
2277 while (!aFilteredIter->IsDone()) {
2278 nsCOMPtr<nsIContent> content =
2279 aFilteredIter->GetCurrentNode()->IsContent()
2280 ? aFilteredIter->GetCurrentNode()->AsContent()
2281 : nullptr;
2282 // We don't observe layout updates, therefore, we should consider whether
2283 // block or inline only with the default definition of the element.
2284 if (lastTextNode && content &&
2285 (HTMLEditUtils::IsBlockElement(*content,
2286 BlockInlineCheck::UseHTMLDefaultStyle) ||
2287 content->IsHTMLElement(nsGkAtoms::br))) {
2288 break;
2290 if (content && content->IsText()) {
2291 if (lastTextNode && !TextServicesDocument::HasSameBlockNodeParent(
2292 *content->AsText(), *lastTextNode)) {
2293 // We're done, the current text node is in a
2294 // different block.
2295 break;
2297 lastTextNode = content->AsText();
2300 aFilteredIter->Prev();
2302 if (DidSkip(aFilteredIter)) {
2303 break;
2307 if (lastTextNode) {
2308 aFilteredIter->PositionAt(lastTextNode);
2311 // XXX: What should we return if last is null?
2313 return NS_OK;
2316 // static
2317 nsresult TextServicesDocument::FirstTextNodeInPrevBlock(
2318 FilteredContentIterator* aFilteredIter) {
2319 NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER);
2321 // XXX: What if mFilteredIter is not currently on a text node?
2323 // Make sure mFilteredIter is pointing to the first text node in the
2324 // current block:
2326 nsresult rv = FirstTextNodeInCurrentBlock(aFilteredIter);
2328 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
2330 // Point mFilteredIter to the first node before the first text node:
2332 aFilteredIter->Prev();
2334 if (aFilteredIter->IsDone()) {
2335 return NS_ERROR_FAILURE;
2338 // Now find the first text node of the next block:
2340 return FirstTextNodeInCurrentBlock(aFilteredIter);
2343 // static
2344 nsresult TextServicesDocument::FirstTextNodeInNextBlock(
2345 FilteredContentIterator* aFilteredIter) {
2346 bool crossedBlockBoundary = false;
2348 NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER);
2350 ClearDidSkip(aFilteredIter);
2352 RefPtr<Text> previousTextNode;
2353 while (!aFilteredIter->IsDone()) {
2354 if (nsCOMPtr<nsIContent> content =
2355 aFilteredIter->GetCurrentNode()->IsContent()
2356 ? aFilteredIter->GetCurrentNode()->AsContent()
2357 : nullptr) {
2358 if (content->IsText()) {
2359 if (crossedBlockBoundary ||
2360 (previousTextNode && !TextServicesDocument::HasSameBlockNodeParent(
2361 *previousTextNode, *content->AsText()))) {
2362 break;
2364 previousTextNode = content->AsText();
2366 // We don't observe layout updates, therefore, we should consider whether
2367 // block or inline only with the default definition of the element.
2368 else if (!crossedBlockBoundary &&
2369 (HTMLEditUtils::IsBlockElement(
2370 *content, BlockInlineCheck::UseHTMLDefaultStyle) ||
2371 content->IsHTMLElement(nsGkAtoms::br))) {
2372 crossedBlockBoundary = true;
2376 aFilteredIter->Next();
2378 if (!crossedBlockBoundary && DidSkip(aFilteredIter)) {
2379 crossedBlockBoundary = true;
2383 return NS_OK;
2386 nsresult TextServicesDocument::GetFirstTextNodeInPrevBlock(
2387 nsIContent** aContent) {
2388 NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER);
2390 *aContent = 0;
2392 // Save the iterator's current content node so we can restore
2393 // it when we are done:
2395 nsINode* node = mFilteredIter->GetCurrentNode();
2397 nsresult rv = FirstTextNodeInPrevBlock(mFilteredIter);
2399 if (NS_FAILED(rv)) {
2400 // Try to restore the iterator before returning.
2401 mFilteredIter->PositionAt(node);
2402 return rv;
2405 if (!mFilteredIter->IsDone()) {
2406 nsCOMPtr<nsIContent> current =
2407 mFilteredIter->GetCurrentNode()->IsContent()
2408 ? mFilteredIter->GetCurrentNode()->AsContent()
2409 : nullptr;
2410 current.forget(aContent);
2413 // Restore the iterator:
2415 return mFilteredIter->PositionAt(node);
2418 nsresult TextServicesDocument::GetFirstTextNodeInNextBlock(
2419 nsIContent** aContent) {
2420 NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER);
2422 *aContent = 0;
2424 // Save the iterator's current content node so we can restore
2425 // it when we are done:
2427 nsINode* node = mFilteredIter->GetCurrentNode();
2429 nsresult rv = FirstTextNodeInNextBlock(mFilteredIter);
2431 if (NS_FAILED(rv)) {
2432 // Try to restore the iterator before returning.
2433 mFilteredIter->PositionAt(node);
2434 return rv;
2437 if (!mFilteredIter->IsDone()) {
2438 nsCOMPtr<nsIContent> current =
2439 mFilteredIter->GetCurrentNode()->IsContent()
2440 ? mFilteredIter->GetCurrentNode()->AsContent()
2441 : nullptr;
2442 current.forget(aContent);
2445 // Restore the iterator:
2446 return mFilteredIter->PositionAt(node);
2449 Result<TextServicesDocument::IteratorStatus, nsresult>
2450 TextServicesDocument::OffsetEntryArray::Init(
2451 FilteredContentIterator& aFilteredIter, IteratorStatus aIteratorStatus,
2452 nsRange* aIterRange, nsAString* aAllTextInBlock /* = nullptr */) {
2453 Clear();
2455 if (aAllTextInBlock) {
2456 aAllTextInBlock->Truncate();
2459 if (aIteratorStatus == IteratorStatus::eDone) {
2460 return IteratorStatus::eDone;
2463 // If we have an aIterRange, retrieve the endpoints so
2464 // they can be used in the while loop below to trim entries
2465 // for text nodes that are partially selected by aIterRange.
2467 nsCOMPtr<nsINode> rngStartNode, rngEndNode;
2468 uint32_t rngStartOffset = 0, rngEndOffset = 0;
2469 if (aIterRange) {
2470 nsresult rv = TextServicesDocument::GetRangeEndPoints(
2471 aIterRange, getter_AddRefs(rngStartNode), &rngStartOffset,
2472 getter_AddRefs(rngEndNode), &rngEndOffset);
2473 if (NS_FAILED(rv)) {
2474 NS_WARNING("TextServicesDocument::GetRangeEndPoints() failed");
2475 return Err(rv);
2479 // The text service could have added text nodes to the beginning
2480 // of the current block and called this method again. Make sure
2481 // we really are at the beginning of the current block:
2483 nsresult rv =
2484 TextServicesDocument::FirstTextNodeInCurrentBlock(&aFilteredIter);
2485 if (NS_FAILED(rv)) {
2486 NS_WARNING("TextServicesDocument::FirstTextNodeInCurrentBlock() failed");
2487 return Err(rv);
2490 TextServicesDocument::ClearDidSkip(&aFilteredIter);
2492 uint32_t offset = 0;
2493 RefPtr<Text> firstTextNode, previousTextNode;
2494 while (!aFilteredIter.IsDone()) {
2495 if (nsCOMPtr<nsIContent> content =
2496 aFilteredIter.GetCurrentNode()->IsContent()
2497 ? aFilteredIter.GetCurrentNode()->AsContent()
2498 : nullptr) {
2499 // We don't observe layout updates, therefore, we should consider whether
2500 // block or inline only with the default definition of the element.
2501 if (HTMLEditUtils::IsBlockElement(
2502 *content, BlockInlineCheck::UseHTMLDefaultStyle) ||
2503 content->IsHTMLElement(nsGkAtoms::br)) {
2504 break;
2506 if (content->IsText()) {
2507 if (previousTextNode && !TextServicesDocument::HasSameBlockNodeParent(
2508 *previousTextNode, *content->AsText())) {
2509 break;
2512 nsString str;
2513 content->AsText()->GetNodeValue(str);
2515 // Add an entry for this text node into the offset table:
2517 UniquePtr<OffsetEntry>& entry = *AppendElement(
2518 MakeUnique<OffsetEntry>(*content->AsText(), offset, str.Length()));
2519 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
2521 // If one or both of the endpoints of the iteration range
2522 // are in the text node for this entry, make sure the entry
2523 // only accounts for the portion of the text node that is
2524 // in the range.
2526 uint32_t startOffset = 0;
2527 uint32_t endOffset = str.Length();
2528 bool adjustStr = false;
2530 if (entry->mTextNode == rngStartNode) {
2531 entry->mOffsetInTextNode = startOffset = rngStartOffset;
2532 adjustStr = true;
2535 if (entry->mTextNode == rngEndNode) {
2536 endOffset = rngEndOffset;
2537 adjustStr = true;
2540 if (adjustStr) {
2541 entry->mLength = endOffset - startOffset;
2542 str = Substring(str, startOffset, entry->mLength);
2545 offset += str.Length();
2547 if (aAllTextInBlock) {
2548 // Append the text node's string to the output string:
2549 if (!firstTextNode) {
2550 *aAllTextInBlock = str;
2551 } else {
2552 *aAllTextInBlock += str;
2556 previousTextNode = content->AsText();
2558 if (!firstTextNode) {
2559 firstTextNode = content->AsText();
2564 aFilteredIter.Next();
2566 if (TextServicesDocument::DidSkip(&aFilteredIter)) {
2567 break;
2571 if (firstTextNode) {
2572 // Always leave the iterator pointing at the first
2573 // text node of the current block!
2574 aFilteredIter.PositionAt(firstTextNode);
2575 return aIteratorStatus;
2578 // If we never ran across a text node, the iterator
2579 // might have been pointing to something invalid to
2580 // begin with.
2581 return IteratorStatus::eDone;
2584 void TextServicesDocument::OffsetEntryArray::RemoveInvalidElements() {
2585 for (size_t i = 0; i < Length();) {
2586 if (ElementAt(i)->mIsValid) {
2587 i++;
2588 continue;
2591 RemoveElementAt(i);
2592 if (!mSelection.IsSet()) {
2593 continue;
2595 if (mSelection.StartIndex() == i) {
2596 NS_ASSERTION(false, "What should we do in this case?");
2597 mSelection.Reset();
2598 } else if (mSelection.StartIndex() > i) {
2599 MOZ_DIAGNOSTIC_ASSERT(mSelection.StartIndex() - 1 < Length());
2600 MOZ_DIAGNOSTIC_ASSERT(mSelection.EndIndex() - 1 < Length());
2601 mSelection.SetIndexes(mSelection.StartIndex() - 1,
2602 mSelection.EndIndex() - 1);
2603 } else if (mSelection.EndIndex() >= i) {
2604 MOZ_DIAGNOSTIC_ASSERT(mSelection.EndIndex() - 1 < Length());
2605 mSelection.SetIndexes(mSelection.StartIndex(), mSelection.EndIndex() - 1);
2610 nsresult TextServicesDocument::OffsetEntryArray::SplitElementAt(
2611 size_t aIndex, uint32_t aOffsetInTextNode) {
2612 OffsetEntry* leftEntry = ElementAt(aIndex).get();
2613 MOZ_ASSERT(leftEntry);
2614 NS_ASSERTION((aOffsetInTextNode > 0), "aOffsetInTextNode == 0");
2615 NS_ASSERTION((aOffsetInTextNode < leftEntry->mLength),
2616 "aOffsetInTextNode >= mLength");
2618 if (aOffsetInTextNode < 1 || aOffsetInTextNode >= leftEntry->mLength) {
2619 return NS_ERROR_FAILURE;
2622 const uint32_t oldLength = leftEntry->mLength - aOffsetInTextNode;
2624 // XXX(Bug 1631371) Check if this should use a fallible operation as it
2625 // pretended earlier.
2626 UniquePtr<OffsetEntry>& rightEntry = *InsertElementAt(
2627 aIndex + 1,
2628 MakeUnique<OffsetEntry>(leftEntry->mTextNode,
2629 leftEntry->mOffsetInTextInBlock + oldLength,
2630 aOffsetInTextNode));
2631 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
2632 leftEntry->mLength = oldLength;
2633 rightEntry->mOffsetInTextNode = leftEntry->mOffsetInTextNode + oldLength;
2635 return NS_OK;
2638 Maybe<size_t> TextServicesDocument::OffsetEntryArray::FirstIndexOf(
2639 const Text& aTextNode) const {
2640 for (size_t i = 0; i < Length(); i++) {
2641 if (ElementAt(i)->mTextNode == &aTextNode) {
2642 return Some(i);
2645 return Nothing();
2648 // Spellchecker code has this. See bug 211343
2649 #define IS_NBSP_CHAR(c) (((unsigned char)0xa0) == (c))
2651 Result<EditorDOMRangeInTexts, nsresult>
2652 TextServicesDocument::OffsetEntryArray::FindWordRange(
2653 nsAString& aAllTextInBlock, const EditorRawDOMPoint& aStartPointToScan) {
2654 MOZ_ASSERT(aStartPointToScan.IsInTextNode());
2655 // It's assumed that aNode is a text node. The first thing
2656 // we do is get its index in the offset table so we can
2657 // calculate the dom point's string offset.
2658 Maybe<size_t> maybeEntryIndex =
2659 FirstIndexOf(*aStartPointToScan.ContainerAs<Text>());
2660 if (NS_WARN_IF(maybeEntryIndex.isNothing())) {
2661 NS_WARNING(
2662 "TextServicesDocument::OffsetEntryArray::FirstIndexOf() didn't find "
2663 "entries");
2664 return Err(NS_ERROR_FAILURE);
2667 // Next we map offset into a string offset.
2669 const UniquePtr<OffsetEntry>& entry = ElementAt(*maybeEntryIndex);
2670 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
2671 uint32_t strOffset = entry->mOffsetInTextInBlock +
2672 aStartPointToScan.Offset() - entry->mOffsetInTextNode;
2674 // Now we use the word breaker to find the beginning and end
2675 // of the word from our calculated string offset.
2677 const char16_t* str = aAllTextInBlock.BeginReading();
2678 MOZ_ASSERT(strOffset <= aAllTextInBlock.Length(),
2679 "The string offset shouldn't be greater than the string length!");
2681 intl::WordRange res = intl::WordBreaker::FindWord(aAllTextInBlock, strOffset);
2683 // Strip out the NBSPs at the ends
2684 while (res.mBegin <= res.mEnd && IS_NBSP_CHAR(str[res.mBegin])) {
2685 res.mBegin++;
2687 if (str[res.mEnd] == static_cast<char16_t>(0x20)) {
2688 uint32_t realEndWord = res.mEnd - 1;
2689 while (realEndWord > res.mBegin && IS_NBSP_CHAR(str[realEndWord])) {
2690 realEndWord--;
2692 if (realEndWord < res.mEnd - 1) {
2693 res.mEnd = realEndWord + 1;
2697 // Now that we have the string offsets for the beginning
2698 // and end of the word, run through the offset table and
2699 // convert them back into dom points.
2701 EditorDOMPointInText wordStart, wordEnd;
2702 size_t lastIndex = Length() - 1;
2703 for (size_t i = 0; i <= lastIndex; i++) {
2704 // Check to see if res.mBegin is within the range covered
2705 // by this entry. Note that if res.mBegin is after the last
2706 // character covered by this entry, we will use the next
2707 // entry if there is one.
2708 const UniquePtr<OffsetEntry>& entry = ElementAt(i);
2709 LockOffsetEntryArrayLengthInDebugBuild(observer, *this);
2710 if (entry->mOffsetInTextInBlock <= res.mBegin &&
2711 (res.mBegin < entry->EndOffsetInTextInBlock() ||
2712 (res.mBegin == entry->EndOffsetInTextInBlock() && i == lastIndex))) {
2713 wordStart.Set(entry->mTextNode, entry->mOffsetInTextNode + res.mBegin -
2714 entry->mOffsetInTextInBlock);
2717 // Check to see if res.mEnd is within the range covered
2718 // by this entry.
2719 if (entry->mOffsetInTextInBlock <= res.mEnd &&
2720 res.mEnd <= entry->EndOffsetInTextInBlock()) {
2721 if (res.mBegin == res.mEnd &&
2722 res.mEnd == entry->EndOffsetInTextInBlock() && i != lastIndex) {
2723 // Wait for the next round so that we use the same entry
2724 // we did for aWordStartNode.
2725 continue;
2728 wordEnd.Set(entry->mTextNode, entry->mOffsetInTextNode + res.mEnd -
2729 entry->mOffsetInTextInBlock);
2730 break;
2734 return EditorDOMRangeInTexts(wordStart, wordEnd);
2738 * nsIEditActionListener implementation:
2739 * Don't implement the behavior directly here. The methods won't be called
2740 * if the instance is created for inline spell checker created for editor.
2741 * If you need to listen a new edit action, you need to add similar
2742 * non-virtual method and you need to call it from EditorBase directly.
2745 NS_IMETHODIMP
2746 TextServicesDocument::DidDeleteNode(nsINode* aChild, nsresult aResult) {
2747 if (NS_WARN_IF(NS_FAILED(aResult)) || NS_WARN_IF(!aChild) ||
2748 !aChild->IsContent()) {
2749 return NS_OK;
2751 DidDeleteContent(*aChild->AsContent());
2752 return NS_OK;
2755 NS_IMETHODIMP TextServicesDocument::DidJoinContents(
2756 const EditorRawDOMPoint& aJoinedPoint, const nsINode* aRemovedNode) {
2757 if (MOZ_UNLIKELY(NS_WARN_IF(!aJoinedPoint.IsSetAndValid()) ||
2758 NS_WARN_IF(!aRemovedNode->IsContent()))) {
2759 return NS_OK;
2761 DidJoinContents(aJoinedPoint, *aRemovedNode->AsContent());
2762 return NS_OK;
2765 NS_IMETHODIMP
2766 TextServicesDocument::DidInsertText(CharacterData* aTextNode, int32_t aOffset,
2767 const nsAString& aString,
2768 nsresult aResult) {
2769 return NS_OK;
2772 NS_IMETHODIMP
2773 TextServicesDocument::WillDeleteText(CharacterData* aTextNode, int32_t aOffset,
2774 int32_t aLength) {
2775 return NS_OK;
2778 NS_IMETHODIMP
2779 TextServicesDocument::WillDeleteRanges(
2780 const nsTArray<RefPtr<nsRange>>& aRangesToDelete) {
2781 return NS_OK;
2784 #undef LockOffsetEntryArrayLengthInDebugBuild
2786 } // namespace mozilla