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
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
55 class OffsetEntry final
{
57 OffsetEntry() = delete;
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
64 * @param aLength Length in the text node which will be managed by the
67 OffsetEntry(Text
& aTextNode
, uint32_t aOffsetInTextInBlock
, uint32_t aLength
)
68 : mTextNode(aTextNode
),
70 mOffsetInTextInBlock(aOffsetInTextInBlock
),
72 mIsInsertedText(false),
76 * EndOffsetInTextNode() returns end offset in the text node, which is
77 * managed by the instance.
79 uint32_t EndOffsetInTextNode() const { return mOffsetInTextNode
+ mLength
; }
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();
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
;
99 * OffsetInTextNodeIsInRangeOrEndOffset() checks whether the offset in
100 * the all text in ancestor block of the text node is managed by the instance
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
;
114 bool mIsInsertedText
;
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");
130 const nsTArray
<ElementType
>& mArray
;
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
)
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
));
167 if (!selCon
|| (mSelCon
&& selCon
!= mSelCon
)) {
168 return NS_ERROR_FAILURE
;
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
;
186 rv
= CreateDocumentContentIterator(getter_AddRefs(mFilteredIter
));
192 mIteratorStatus
= IteratorStatus::eDone
;
201 mEditorBase
= aEditor
->AsEditorBase();
203 rv
= aEditor
->AddEditActionListener(this);
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
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.
224 CreateFilteredContentIterator(mExtent
, getter_AddRefs(mFilteredIter
));
225 if (NS_WARN_IF(NS_FAILED(rv
))) {
229 // Now position the iterator at the start of the first block
231 mIteratorStatus
= IteratorStatus::eDone
;
234 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "FirstBlock() failed");
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
),
250 if (NS_WARN_IF(NS_FAILED(rv
))) {
254 // Create a content iterator based on the range.
255 RefPtr
<FilteredContentIterator
> filteredIter
;
257 CreateFilteredContentIterator(aStaticRange
, getter_AddRefs(filteredIter
));
258 if (NS_WARN_IF(NS_FAILED(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
))) {
269 if (iterStatus
== IteratorStatus::eDone
) {
270 // No text was found so there's no adjustment necessary!
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
))) {
286 if (iterStatus
== IteratorStatus::eDone
) {
287 // We should never get here because a first text block
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
;
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
))) {
321 // Grab all the text in the block containing our
323 rv
= docFilteredIter
->PositionAt(firstText
);
324 if (NS_WARN_IF(NS_FAILED(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
));
342 if (maybeWordRange
.isErr()) {
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
353 rv
= docFilteredIter
->PositionAt(lastText
);
354 if (NS_WARN_IF(NS_FAILED(rv
))) {
358 result
= offsetTable
.Init(*docFilteredIter
, IteratorStatus::eValid
, nullptr,
360 if (result
.isErr()) {
361 return result
.unwrapErr();
364 maybeWordRange
= offsetTable
.FindWordRange(
365 blockStr
, EditorRawDOMPoint(rngEndNode
, rngEndOffset
));
367 if (maybeWordRange
.isErr()) {
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
,
387 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "Failed to update the given range");
391 nsresult
TextServicesDocument::SetFilterType(uint32_t aFilterType
) {
392 mTxtSvcFilterType
= aFilterType
;
397 nsresult
TextServicesDocument::GetCurrentTextBlock(nsAString
& aStr
) {
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();
412 nsresult
TextServicesDocument::FirstBlock() {
413 NS_ENSURE_TRUE(mFilteredIter
, NS_ERROR_FAILURE
);
415 nsresult rv
= FirstTextNode(mFilteredIter
, &mIteratorStatus
);
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
));
428 // There's no text block in the document!
430 mPrevTextBlock
= nullptr;
431 mNextTextBlock
= nullptr;
434 // XXX Result of FirstTextNode() or GetFirstTextNodeInNextBlock().
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);
469 return NS_ERROR_FAILURE
;
472 parent
= range
->GetStartContainer();
474 return NS_ERROR_FAILURE
;
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
483 rv
= mFilteredIter
->PositionAt(parent
->AsText());
488 rv
= FirstTextNodeInCurrentBlock(mFilteredIter
);
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
);
507 if (*aSelStatus
== BlockSelectionStatus::eBlockContains
) {
508 rv
= SetSelectionInternal(*aSelOffset
, *aSelLength
, false);
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;
528 RefPtr
<FilteredContentIterator
> filteredIter
;
529 rv
= CreateFilteredContentIterator(range
, getter_AddRefs(filteredIter
));
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();
549 rv
= mFilteredIter
->PositionAt(textNode
);
554 rv
= FirstTextNodeInCurrentBlock(mFilteredIter
);
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
);
574 // Result of SetSelectionInternal() in the |if| block or NS_OK.
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
584 const uint32_t rangeCount
= selection
->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
;
603 CreateFilteredContentIterator(range
, getter_AddRefs(filteredIter
));
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());
623 rv
= FirstTextNodeInCurrentBlock(mFilteredIter
);
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;
650 return NS_ERROR_FAILURE
;
653 parent
= range
->GetEndContainer();
655 return NS_ERROR_FAILURE
;
658 range
= CreateDocumentContentRootToNodeOffsetRange(parent
, range
->EndOffset(),
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;
670 RefPtr
<FilteredContentIterator
> filteredIter
;
672 CreateFilteredContentIterator(range
, getter_AddRefs(filteredIter
));
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());
688 rv
= FirstTextNodeInCurrentBlock(mFilteredIter
);
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");
709 // If we get here, we didn't find any block before or inside
710 // the selection! Just return OK.
714 nsresult
TextServicesDocument::PrevBlock() {
715 NS_ENSURE_TRUE(mFilteredIter
, NS_ERROR_FAILURE
);
717 if (mIteratorStatus
== IteratorStatus::eDone
) {
721 switch (mIteratorStatus
) {
722 case IteratorStatus::eValid
:
723 case IteratorStatus::eNext
: {
724 nsresult rv
= FirstTextNodeInPrevBlock(mFilteredIter
);
727 mIteratorStatus
= IteratorStatus::eDone
;
731 if (mFilteredIter
->IsDone()) {
732 mIteratorStatus
= IteratorStatus::eDone
;
736 mIteratorStatus
= IteratorStatus::eValid
;
739 case IteratorStatus::ePrev
:
741 // The iterator already points to the previous
742 // block, so don't do anything.
744 mIteratorStatus
= IteratorStatus::eValid
;
749 mIteratorStatus
= IteratorStatus::eDone
;
753 // Keep track of prev and next blocks, just in case
754 // the text service blows away the current block.
756 if (mIteratorStatus
== IteratorStatus::eValid
) {
757 GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock
));
758 rv
= GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock
));
761 mPrevTextBlock
= nullptr;
762 mNextTextBlock
= nullptr;
765 // XXX The result of GetFirstTextNodeInNextBlock() or NS_OK.
769 nsresult
TextServicesDocument::NextBlock() {
770 NS_ENSURE_TRUE(mFilteredIter
, NS_ERROR_FAILURE
);
772 if (mIteratorStatus
== IteratorStatus::eDone
) {
776 switch (mIteratorStatus
) {
777 case IteratorStatus::eValid
: {
778 // Advance the iterator to the next text block.
780 nsresult rv
= FirstTextNodeInNextBlock(mFilteredIter
);
783 mIteratorStatus
= IteratorStatus::eDone
;
787 if (mFilteredIter
->IsDone()) {
788 mIteratorStatus
= IteratorStatus::eDone
;
792 mIteratorStatus
= IteratorStatus::eValid
;
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
;
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!
811 mIteratorStatus
= IteratorStatus::eDone
;
815 // Keep track of prev and next blocks, just in case
816 // the text service blows away the current block.
818 if (mIteratorStatus
== IteratorStatus::eValid
) {
819 GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock
));
820 rv
= GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock
));
823 mPrevTextBlock
= nullptr;
824 mNextTextBlock
= nullptr;
827 // The result of GetFirstTextNodeInNextBlock() or NS_OK.
831 nsresult
TextServicesDocument::IsDone(bool* aIsDone
) {
832 NS_ENSURE_TRUE(aIsDone
, NS_ERROR_NULL_POINTER
);
836 NS_ENSURE_TRUE(mFilteredIter
, NS_ERROR_FAILURE
);
838 *aIsDone
= mIteratorStatus
== IteratorStatus::eDone
;
843 nsresult
TextServicesDocument::SetSelection(uint32_t aOffset
,
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
);
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.
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!
881 selLength
= entry
->EndOffsetInTextInBlock() -
882 mSelection
.StartOffsetInTextInBlock();
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
);
894 NS_WARNING("selLength was invalid for the OffsetEntry");
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
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;
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
;
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
);
936 "entry->mLength - selLength was invalid for the OffsetEntry");
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
948 entry
->mIsValid
= false;
954 if (i
!= mSelection
.StartIndex() && i
!= mSelection
.EndIndex()) {
955 // The entire entry is contained in the selection. Mark the
957 entry
->mIsValid
= false;
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()) {
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;
982 nsresult rv
= GetRangeEndPoints(
983 mExtent
, getter_AddRefs(origStartNode
), &origStartOffset
,
984 getter_AddRefs(origEndNode
), &origEndOffset
);
991 if (NS_FAILED(mOffsetTable
.WillDeleteSelection())) {
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
,
1004 if (NS_FAILED(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
),
1020 if (NS_FAILED(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()
1037 // Create the new iterator.
1039 CreateFilteredContentIterator(mExtent
, getter_AddRefs(mFilteredIter
));
1040 if (NS_FAILED(rv
)) {
1044 // Now make the new iterator point to the content node
1045 // the old one was pointing at.
1047 rv
= mFilteredIter
->PositionAt(curContent
);
1048 if (NS_FAILED(rv
)) {
1049 mIteratorStatus
= IteratorStatus::eDone
;
1051 mIteratorStatus
= IteratorStatus::eValid
;
1057 OffsetEntry
* entry
= mOffsetTable
.DidDeleteSelection();
1059 SetSelection(mOffsetTable
.mSelection
.StartOffsetInTextInBlock(), 0);
1062 // Now remove any invalid entries from the offset table.
1063 mOffsetTable
.RemoveInvalidElements();
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
) {
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
) {
1090 MOZ_DIAGNOSTIC_ASSERT(i
< Length());
1091 mSelection
.Set(i
, entry
->mOffsetInTextInBlock
);
1096 // Uuughh we have no valid offset entry to place our
1097 // caret ... just mark the selection invalid.
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
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
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");
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");
1149 if (!wasSelectionCollapsed
) {
1150 nsresult rv
= SetSelection(savedSelOffset
, savedSelLength
);
1151 if (NS_FAILED(rv
)) {
1155 rv
= DeleteSelection();
1156 if (NS_FAILED(rv
)) {
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
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();
1182 // Insert an inserted text offset entry before the current
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
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
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.
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
);
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");
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
)) {
1259 "entry->EndOffsetInTextInBlock() - "
1260 "mSelection.StartOffsetInTextInBlock() was invalid for the "
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
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
) {
1289 if (entry
->mIsValid
) {
1290 entry
->mOffsetInTextNode
+= aInsertedString
.Length();
1297 void TextServicesDocument::DidDeleteContent(const nsIContent
& aChildContent
) {
1298 if (NS_WARN_IF(!mFilteredIter
) || !aChildContent
.IsText()) {
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.
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();
1324 const UniquePtr
<OffsetEntry
>& entry
= mOffsetTable
[nodeIndex
];
1325 LockOffsetEntryArrayLengthInDebugBuild(observer
, mOffsetTable
);
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()) {
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.
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.
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.");
1371 NS_ASSERTION(mOffsetTable
[joinedIndex
]->mOffsetInTextNode
== 0,
1372 "Unexpected offset value for joinedIndex.");
1374 if (MOZ_UNLIKELY(joinedIndex
> removedIndex
)) {
1375 NS_ASSERTION(joinedIndex
< removedIndex
, "Indexes out of order.");
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()) {
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
1401 entry
->mOffsetInTextNode
+= movedTextDataLength
;
1406 if (aJoinNodesDirection
== JoinNodesDirection::LeftNodeIntoRightNode
) {
1407 // The text was moved from aRemovedContent to start of the container of
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()) {
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();
1442 case nsIEditorSpellCheck::FILTERTYPE_MAIL
:
1443 composeFilter
= nsComposeTxtSrvFilter::CreateMailFilter();
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
)) {
1457 filter
.forget(aFilteredIter
);
1461 Element
* TextServicesDocument::GetDocumentContentRootNode() const {
1462 if (NS_WARN_IF(!mDocument
)) {
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
)) {
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
)) {
1502 nsCOMPtr
<nsINode
> bodyNode
= GetDocumentContentRootNode();
1503 if (NS_WARN_IF(!bodyNode
)) {
1507 nsCOMPtr
<nsINode
> startNode
;
1508 nsCOMPtr
<nsINode
> endNode
;
1509 uint32_t startOffset
, endOffset
;
1512 // The range should begin at the start of the document
1513 // and extend up until (aParent, aOffset).
1514 startNode
= bodyNode
;
1517 endOffset
= aOffset
;
1519 // The range should begin at (aParent, aOffset) and
1520 // extend to the end of the document.
1521 startNode
= aParent
;
1522 startOffset
= aOffset
;
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!
1567 // We found an invalid entry that points to
1568 // the current iterator node. Stop looking for
1569 // a previous valid node!
1573 if (entry
->mIsValid
) {
1575 prevValidTextNode
= entry
->mTextNode
;
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
;
1594 mIteratorStatus
= IteratorStatus::eValid
;
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
;
1611 mIteratorStatus
= IteratorStatus::eNext
;
1612 } else if (mPrevTextBlock
) {
1613 nsresult rv
= mFilteredIter
->PositionAt(mPrevTextBlock
);
1614 if (NS_FAILED(rv
)) {
1615 mIteratorStatus
= IteratorStatus::eDone
;
1619 mIteratorStatus
= IteratorStatus::ePrev
;
1621 mIteratorStatus
= IteratorStatus::eDone
;
1627 bool TextServicesDocument::DidSkip(FilteredContentIterator
* aFilteredIter
) {
1628 return aFilteredIter
&& aFilteredIter
->DidSkip();
1632 void TextServicesDocument::ClearDidSkip(
1633 FilteredContentIterator
* aFilteredIter
) {
1634 // Clear filter's skip flag
1635 if (aFilteredIter
) {
1636 aFilteredIter
->ClearDidSkip();
1641 bool TextServicesDocument::HasSameBlockNodeParent(Text
& aTextNode1
,
1643 // XXX How about the case that both text nodes are orphan nodes?
1644 if (aTextNode1
.GetParent() == aTextNode2
.GetParent()) {
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(
1653 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost
);
1654 const Element
* editableBlockElementOrInlineEditingHost2
=
1655 HTMLEditUtils::GetAncestorElement(
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
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()) {
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
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.
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
);
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()) {
1765 "TextServicesDocument::OffsetEntryArray::WillSetSelection() failed");
1766 return newSelectionRange
.unwrapErr();
1773 // XXX: If we ever get a SetSelection() method in nsIEditor, we should
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()) {
1783 selection
->CollapseInLimiter(newSelectionRange
.inspect().StartRef());
1784 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1785 "Selection::CollapseInLimiter() failed");
1790 selection
->SetStartAndEndInLimiter(newSelectionRange
.inspect().StartRef(),
1791 newSelectionRange
.inspect().EndRef(),
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
) {
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();
1841 // Get pointers to the first and last offset entries
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();
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.
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
;
1886 entry
->mOffsetInTextInBlock
+ (offset
- entry
->mOffsetInTextNode
);
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
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
;
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
);
1934 // The parent has no children, so position the iterator
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
);
1945 // Now iterate to the left, towards the beginning of
1946 // the text block, to find the first text node you
1949 Text
* textNode
= nullptr;
1950 for (; !filteredIter
->IsDone(); filteredIter
->Prev()) {
1951 nsINode
* current
= filteredIter
->GetCurrentNode();
1952 if (current
->IsText()) {
1953 textNode
= current
->AsText();
1959 // We found a node, now set the offset to the end
1960 // of the text node.
1961 offset
= textNode
->TextLength();
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
1969 nsresult rv
= filteredIter
->PositionAt(saveNode
);
1970 NS_ENSURE_SUCCESS(rv
, rv
);
1973 for (; !filteredIter
->IsDone(); filteredIter
->Next()) {
1974 nsINode
* current
= filteredIter
->GetCurrentNode();
1975 if (current
->IsText()) {
1976 textNode
= current
->AsText();
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.
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
;
1994 entry
->mOffsetInTextInBlock
+ (offset
- entry
->mOffsetInTextNode
);
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
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
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
;
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) {
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
;
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
2109 *aSelStatus
= BlockSelectionStatus::eBlockContains
;
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
;
2121 // The start of the range will be the rightmost
2125 p1
= eStart
->mTextNode
;
2128 p1
= startContainer
;
2132 // The end of the range will be the leftmost
2136 p2
= eEnd
->mTextNode
;
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
;
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()) {
2161 for (; !filteredIter
->IsDone(); filteredIter
->Next()) {
2162 nsINode
* node
= filteredIter
->GetCurrentNode();
2163 if (node
->IsText()) {
2164 p1
= node
->AsText();
2170 NS_ENSURE_TRUE(found
, NS_ERROR_FAILURE
);
2173 // Find the last text node in the range.
2174 filteredIter
->Last();
2175 if (!p2
->IsText()) {
2177 for (; !filteredIter
->IsDone(); filteredIter
->Prev()) {
2178 nsINode
* node
= filteredIter
->GetCurrentNode();
2179 if (node
->IsText()) {
2180 p2
= node
->AsText();
2181 o2
= p2
->AsText()->Length();
2187 NS_ENSURE_TRUE(found
, NS_ERROR_FAILURE
);
2193 for (size_t i
= 0; i
< tableCount
; i
++) {
2194 const UniquePtr
<OffsetEntry
>& entry
= mOffsetTable
[i
];
2195 LockOffsetEntryArrayLengthInDebugBuild(observer
, mOffsetTable
);
2197 if (entry
->mTextNode
== p1
.get() &&
2198 entry
->OffsetInTextNodeIsInRangeOrEndOffset(o1
)) {
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
;
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
;
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
;
2220 // The entire entry must be in the range.
2221 *aSelLength
+= entry
->mLength
;
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();
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
;
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
;
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()
2307 if (lastTextNode
&& content
&&
2308 (HTMLEditUtils::IsBlockElement(*content
) ||
2309 content
->IsHTMLElement(nsGkAtoms::br
))) {
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
2319 lastTextNode
= content
->AsText();
2322 aFilteredIter
->Prev();
2324 if (DidSkip(aFilteredIter
)) {
2330 aFilteredIter
->PositionAt(lastTextNode
);
2333 // XXX: What should we return if last is null?
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
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
);
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()
2380 if (content
->IsText()) {
2381 if (crossedBlockBoundary
||
2382 (previousTextNode
&& !TextServicesDocument::HasSameBlockNodeParent(
2383 *previousTextNode
, *content
->AsText()))) {
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;
2404 nsresult
TextServicesDocument::GetFirstTextNodeInPrevBlock(
2405 nsIContent
** aContent
) {
2406 NS_ENSURE_TRUE(aContent
, NS_ERROR_NULL_POINTER
);
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
);
2423 if (!mFilteredIter
->IsDone()) {
2424 nsCOMPtr
<nsIContent
> current
=
2425 mFilteredIter
->GetCurrentNode()->IsContent()
2426 ? mFilteredIter
->GetCurrentNode()->AsContent()
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
);
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
);
2455 if (!mFilteredIter
->IsDone()) {
2456 nsCOMPtr
<nsIContent
> current
=
2457 mFilteredIter
->GetCurrentNode()->IsContent()
2458 ? mFilteredIter
->GetCurrentNode()->AsContent()
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 */) {
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;
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");
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:
2502 TextServicesDocument::FirstTextNodeInCurrentBlock(&aFilteredIter
);
2503 if (NS_FAILED(rv
)) {
2504 NS_WARNING("TextServicesDocument::FirstTextNodeInCurrentBlock() failed");
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()
2517 if (HTMLEditUtils::IsBlockElement(*content
) ||
2518 content
->IsHTMLElement(nsGkAtoms::br
)) {
2521 if (content
->IsText()) {
2522 if (previousTextNode
&& !TextServicesDocument::HasSameBlockNodeParent(
2523 *previousTextNode
, *content
->AsText())) {
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
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
;
2550 if (entry
->mTextNode
== rngEndNode
) {
2551 endOffset
= rngEndOffset
;
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
;
2567 *aAllTextInBlock
+= str
;
2571 previousTextNode
= content
->AsText();
2573 if (!firstTextNode
) {
2574 firstTextNode
= content
->AsText();
2579 aFilteredIter
.Next();
2581 if (TextServicesDocument::DidSkip(&aFilteredIter
)) {
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
2596 return IteratorStatus::eDone
;
2599 void TextServicesDocument::OffsetEntryArray::RemoveInvalidElements() {
2600 for (size_t i
= 0; i
< Length();) {
2601 if (ElementAt(i
)->mIsValid
) {
2607 if (!mSelection
.IsSet()) {
2610 if (mSelection
.StartIndex() == i
) {
2611 NS_ASSERTION(false, "What should we do in this case?");
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(
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
;
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
) {
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())) {
2677 "TextServicesDocument::OffsetEntryArray::FirstIndexOf() didn't find "
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
])) {
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
])) {
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
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.
2745 wordEnd
.Set(entry
->mTextNode
, entry
->mOffsetInTextNode
+ res
.mEnd
-
2746 entry
->mOffsetInTextInBlock
);
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.
2763 TextServicesDocument::DidDeleteNode(nsINode
* aChild
, nsresult aResult
) {
2764 if (NS_WARN_IF(NS_FAILED(aResult
)) || NS_WARN_IF(!aChild
) ||
2765 !aChild
->IsContent()) {
2768 DidDeleteContent(*aChild
->AsContent());
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()))) {
2779 DidJoinContents(aJoinedPoint
, *aRemovedNode
->AsContent(),
2781 ? JoinNodesDirection::LeftNodeIntoRightNode
2782 : JoinNodesDirection::RightNodeIntoLeftNode
);
2787 TextServicesDocument::DidInsertText(CharacterData
* aTextNode
, int32_t aOffset
,
2788 const nsAString
& aString
,
2794 TextServicesDocument::WillDeleteText(CharacterData
* aTextNode
, int32_t aOffset
,
2800 TextServicesDocument::WillDeleteRanges(
2801 const nsTArray
<RefPtr
<nsRange
>>& aRangesToDelete
) {
2805 #undef LockOffsetEntryArrayLengthInDebugBuild
2807 } // namespace mozilla