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
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
57 class OffsetEntry final
{
59 OffsetEntry() = delete;
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
66 * @param aLength Length in the text node which will be managed by the
69 OffsetEntry(Text
& aTextNode
, uint32_t aOffsetInTextInBlock
, uint32_t aLength
)
70 : mTextNode(aTextNode
),
72 mOffsetInTextInBlock(aOffsetInTextInBlock
),
74 mIsInsertedText(false),
78 * EndOffsetInTextNode() returns end offset in the text node, which is
79 * managed by the instance.
81 uint32_t EndOffsetInTextNode() const { return mOffsetInTextNode
+ mLength
; }
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();
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
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
;
116 bool mIsInsertedText
;
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");
132 const nsTArray
<ElementType
>& mArray
;
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
)
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
));
169 if (!selCon
|| (mSelCon
&& selCon
!= mSelCon
)) {
170 return NS_ERROR_FAILURE
;
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
;
188 rv
= CreateDocumentContentIterator(getter_AddRefs(mFilteredIter
));
194 mIteratorStatus
= IteratorStatus::eDone
;
203 mEditorBase
= aEditor
->AsEditorBase();
205 rv
= aEditor
->AddEditActionListener(this);
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
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.
226 CreateFilteredContentIterator(mExtent
, getter_AddRefs(mFilteredIter
));
227 if (NS_WARN_IF(NS_FAILED(rv
))) {
231 // Now position the iterator at the start of the first block
233 mIteratorStatus
= IteratorStatus::eDone
;
236 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "FirstBlock() failed");
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
),
252 if (NS_WARN_IF(NS_FAILED(rv
))) {
256 // Create a content iterator based on the range.
257 RefPtr
<FilteredContentIterator
> filteredIter
;
259 CreateFilteredContentIterator(aStaticRange
, getter_AddRefs(filteredIter
));
260 if (NS_WARN_IF(NS_FAILED(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
))) {
271 if (iterStatus
== IteratorStatus::eDone
) {
272 // No text was found so there's no adjustment necessary!
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
))) {
288 if (iterStatus
== IteratorStatus::eDone
) {
289 // We should never get here because a first text block
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
;
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
))) {
323 // Grab all the text in the block containing our
325 rv
= docFilteredIter
->PositionAt(firstText
);
326 if (NS_WARN_IF(NS_FAILED(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
));
344 if (maybeWordRange
.isErr()) {
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
355 rv
= docFilteredIter
->PositionAt(lastText
);
356 if (NS_WARN_IF(NS_FAILED(rv
))) {
360 result
= offsetTable
.Init(*docFilteredIter
, IteratorStatus::eValid
, nullptr,
362 if (result
.isErr()) {
363 return result
.unwrapErr();
366 maybeWordRange
= offsetTable
.FindWordRange(
367 blockStr
, EditorRawDOMPoint(rngEndNode
, rngEndOffset
));
369 if (maybeWordRange
.isErr()) {
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.
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
,
390 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "Failed to update the given range");
394 nsresult
TextServicesDocument::SetFilterType(uint32_t aFilterType
) {
395 mTxtSvcFilterType
= aFilterType
;
400 nsresult
TextServicesDocument::GetCurrentTextBlock(nsAString
& aStr
) {
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();
415 nsresult
TextServicesDocument::FirstBlock() {
416 NS_ENSURE_TRUE(mFilteredIter
, NS_ERROR_FAILURE
);
418 nsresult rv
= FirstTextNode(mFilteredIter
, &mIteratorStatus
);
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
));
431 // There's no text block in the document!
433 mPrevTextBlock
= nullptr;
434 mNextTextBlock
= nullptr;
437 // XXX Result of FirstTextNode() or GetFirstTextNodeInNextBlock().
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);
472 return NS_ERROR_FAILURE
;
475 parent
= range
->GetStartContainer();
477 return NS_ERROR_FAILURE
;
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
486 rv
= mFilteredIter
->PositionAt(parent
->AsText());
491 rv
= FirstTextNodeInCurrentBlock(mFilteredIter
);
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
);
510 if (*aSelStatus
== BlockSelectionStatus::eBlockContains
) {
511 rv
= SetSelectionInternal(*aSelOffset
, *aSelLength
, false);
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;
531 RefPtr
<FilteredContentIterator
> filteredIter
;
532 rv
= CreateFilteredContentIterator(range
, getter_AddRefs(filteredIter
));
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();
552 rv
= mFilteredIter
->PositionAt(textNode
);
557 rv
= FirstTextNodeInCurrentBlock(mFilteredIter
);
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
);
577 // Result of SetSelectionInternal() in the |if| block or NS_OK.
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
587 const uint32_t rangeCount
= selection
->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
;
606 CreateFilteredContentIterator(range
, getter_AddRefs(filteredIter
));
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());
626 rv
= FirstTextNodeInCurrentBlock(mFilteredIter
);
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;
653 return NS_ERROR_FAILURE
;
656 parent
= range
->GetEndContainer();
658 return NS_ERROR_FAILURE
;
661 range
= CreateDocumentContentRootToNodeOffsetRange(parent
, range
->EndOffset(),
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;
673 RefPtr
<FilteredContentIterator
> filteredIter
;
675 CreateFilteredContentIterator(range
, getter_AddRefs(filteredIter
));
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());
691 rv
= FirstTextNodeInCurrentBlock(mFilteredIter
);
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");
712 // If we get here, we didn't find any block before or inside
713 // the selection! Just return OK.
717 nsresult
TextServicesDocument::PrevBlock() {
718 NS_ENSURE_TRUE(mFilteredIter
, NS_ERROR_FAILURE
);
720 if (mIteratorStatus
== IteratorStatus::eDone
) {
724 switch (mIteratorStatus
) {
725 case IteratorStatus::eValid
:
726 case IteratorStatus::eNext
: {
727 nsresult rv
= FirstTextNodeInPrevBlock(mFilteredIter
);
730 mIteratorStatus
= IteratorStatus::eDone
;
734 if (mFilteredIter
->IsDone()) {
735 mIteratorStatus
= IteratorStatus::eDone
;
739 mIteratorStatus
= IteratorStatus::eValid
;
742 case IteratorStatus::ePrev
:
744 // The iterator already points to the previous
745 // block, so don't do anything.
747 mIteratorStatus
= IteratorStatus::eValid
;
752 mIteratorStatus
= IteratorStatus::eDone
;
756 // Keep track of prev and next blocks, just in case
757 // the text service blows away the current block.
759 if (mIteratorStatus
== IteratorStatus::eValid
) {
760 GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock
));
761 rv
= GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock
));
764 mPrevTextBlock
= nullptr;
765 mNextTextBlock
= nullptr;
768 // XXX The result of GetFirstTextNodeInNextBlock() or NS_OK.
772 nsresult
TextServicesDocument::NextBlock() {
773 NS_ENSURE_TRUE(mFilteredIter
, NS_ERROR_FAILURE
);
775 if (mIteratorStatus
== IteratorStatus::eDone
) {
779 switch (mIteratorStatus
) {
780 case IteratorStatus::eValid
: {
781 // Advance the iterator to the next text block.
783 nsresult rv
= FirstTextNodeInNextBlock(mFilteredIter
);
786 mIteratorStatus
= IteratorStatus::eDone
;
790 if (mFilteredIter
->IsDone()) {
791 mIteratorStatus
= IteratorStatus::eDone
;
795 mIteratorStatus
= IteratorStatus::eValid
;
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
;
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!
814 mIteratorStatus
= IteratorStatus::eDone
;
818 // Keep track of prev and next blocks, just in case
819 // the text service blows away the current block.
821 if (mIteratorStatus
== IteratorStatus::eValid
) {
822 GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock
));
823 rv
= GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock
));
826 mPrevTextBlock
= nullptr;
827 mNextTextBlock
= nullptr;
830 // The result of GetFirstTextNodeInNextBlock() or NS_OK.
834 nsresult
TextServicesDocument::IsDone(bool* aIsDone
) {
835 NS_ENSURE_TRUE(aIsDone
, NS_ERROR_NULL_POINTER
);
839 NS_ENSURE_TRUE(mFilteredIter
, NS_ERROR_FAILURE
);
841 *aIsDone
= mIteratorStatus
== IteratorStatus::eDone
;
846 nsresult
TextServicesDocument::SetSelection(uint32_t aOffset
,
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.
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!
883 selLength
= entry
->EndOffsetInTextInBlock() -
884 mSelection
.StartOffsetInTextInBlock();
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
);
896 NS_WARNING("selLength was invalid for the OffsetEntry");
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
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;
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
;
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
);
938 "entry->mLength - selLength was invalid for the OffsetEntry");
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
950 entry
->mIsValid
= false;
956 if (i
!= mSelection
.StartIndex() && i
!= mSelection
.EndIndex()) {
957 // The entire entry is contained in the selection. Mark the
959 entry
->mIsValid
= false;
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()) {
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;
984 nsresult rv
= GetRangeEndPoints(
985 mExtent
, getter_AddRefs(origStartNode
), &origStartOffset
,
986 getter_AddRefs(origEndNode
), &origEndOffset
);
993 if (NS_FAILED(mOffsetTable
.WillDeleteSelection())) {
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
,
1006 if (NS_FAILED(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
),
1022 if (NS_FAILED(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()
1039 // Create the new iterator.
1041 CreateFilteredContentIterator(mExtent
, getter_AddRefs(mFilteredIter
));
1042 if (NS_FAILED(rv
)) {
1046 // Now make the new iterator point to the content node
1047 // the old one was pointing at.
1049 rv
= mFilteredIter
->PositionAt(curContent
);
1050 if (NS_FAILED(rv
)) {
1051 mIteratorStatus
= IteratorStatus::eDone
;
1053 mIteratorStatus
= IteratorStatus::eValid
;
1059 OffsetEntry
* entry
= mOffsetTable
.DidDeleteSelection();
1061 SetSelection(mOffsetTable
.mSelection
.StartOffsetInTextInBlock(), 0);
1064 // Now remove any invalid entries from the offset table.
1065 mOffsetTable
.RemoveInvalidElements();
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
) {
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
) {
1092 MOZ_DIAGNOSTIC_ASSERT(i
< Length());
1093 mSelection
.Set(i
, entry
->mOffsetInTextInBlock
);
1098 // Uuughh we have no valid offset entry to place our
1099 // caret ... just mark the selection invalid.
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
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
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");
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");
1151 if (!wasSelectionCollapsed
) {
1152 nsresult rv
= SetSelection(savedSelOffset
, savedSelLength
);
1153 if (NS_FAILED(rv
)) {
1157 rv
= DeleteSelection();
1158 if (NS_FAILED(rv
)) {
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
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();
1184 // Insert an inserted text offset entry before the current
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
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
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.
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
);
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");
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
)) {
1261 "entry->EndOffsetInTextInBlock() - "
1262 "mSelection.StartOffsetInTextInBlock() was invalid for the "
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
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
) {
1291 if (entry
->mIsValid
) {
1292 entry
->mOffsetInTextNode
+= aInsertedString
.Length();
1299 void TextServicesDocument::DidDeleteContent(const nsIContent
& aChildContent
) {
1300 if (NS_WARN_IF(!mFilteredIter
) || !aChildContent
.IsText()) {
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.
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();
1326 const UniquePtr
<OffsetEntry
>& entry
= mOffsetTable
[nodeIndex
];
1327 LockOffsetEntryArrayLengthInDebugBuild(observer
, mOffsetTable
);
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()) {
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.
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.
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.");
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()) {
1386 if (entry
->mIsValid
) {
1387 entry
->mTextNode
= aJoinedPoint
.ContainerAs
<Text
>();
1388 // The text was moved from aRemovedContent to end of the container of
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();
1415 case nsIEditorSpellCheck::FILTERTYPE_MAIL
:
1416 composeFilter
= nsComposeTxtSrvFilter::CreateMailFilter();
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
)) {
1430 filter
.forget(aFilteredIter
);
1434 Element
* TextServicesDocument::GetDocumentContentRootNode() const {
1435 if (NS_WARN_IF(!mDocument
)) {
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
)) {
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
)) {
1475 nsCOMPtr
<nsINode
> bodyNode
= GetDocumentContentRootNode();
1476 if (NS_WARN_IF(!bodyNode
)) {
1480 nsCOMPtr
<nsINode
> startNode
;
1481 nsCOMPtr
<nsINode
> endNode
;
1482 uint32_t startOffset
, endOffset
;
1485 // The range should begin at the start of the document
1486 // and extend up until (aParent, aOffset).
1487 startNode
= bodyNode
;
1490 endOffset
= aOffset
;
1492 // The range should begin at (aParent, aOffset) and
1493 // extend to the end of the document.
1494 startNode
= aParent
;
1495 startOffset
= aOffset
;
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!
1540 // We found an invalid entry that points to
1541 // the current iterator node. Stop looking for
1542 // a previous valid node!
1546 if (entry
->mIsValid
) {
1548 prevValidTextNode
= entry
->mTextNode
;
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
;
1567 mIteratorStatus
= IteratorStatus::eValid
;
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
;
1584 mIteratorStatus
= IteratorStatus::eNext
;
1585 } else if (mPrevTextBlock
) {
1586 nsresult rv
= mFilteredIter
->PositionAt(mPrevTextBlock
);
1587 if (NS_FAILED(rv
)) {
1588 mIteratorStatus
= IteratorStatus::eDone
;
1592 mIteratorStatus
= IteratorStatus::ePrev
;
1594 mIteratorStatus
= IteratorStatus::eDone
;
1600 bool TextServicesDocument::DidSkip(FilteredContentIterator
* aFilteredIter
) {
1601 return aFilteredIter
&& aFilteredIter
->DidSkip();
1605 void TextServicesDocument::ClearDidSkip(
1606 FilteredContentIterator
* aFilteredIter
) {
1607 // Clear filter's skip flag
1608 if (aFilteredIter
) {
1609 aFilteredIter
->ClearDidSkip();
1614 bool TextServicesDocument::HasSameBlockNodeParent(Text
& aTextNode1
,
1616 // XXX How about the case that both text nodes are orphan nodes?
1617 if (aTextNode1
.GetParent() == aTextNode2
.GetParent()) {
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(
1626 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost
,
1627 BlockInlineCheck::UseHTMLDefaultStyle
);
1628 const Element
* editableBlockElementOrInlineEditingHost2
=
1629 HTMLEditUtils::GetAncestorElement(
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
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()) {
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
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.
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
);
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()) {
1740 "TextServicesDocument::OffsetEntryArray::WillSetSelection() failed");
1741 return newSelectionRange
.unwrapErr();
1748 // XXX: If we ever get a SetSelection() method in nsIEditor, we should
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()) {
1758 selection
->CollapseInLimiter(newSelectionRange
.inspect().StartRef());
1759 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1760 "Selection::CollapseInLimiter() failed");
1765 selection
->SetStartAndEndInLimiter(newSelectionRange
.inspect().StartRef(),
1766 newSelectionRange
.inspect().EndRef(),
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
) {
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();
1816 // Get pointers to the first and last offset entries
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();
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.
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
;
1861 entry
->mOffsetInTextInBlock
+ (offset
- entry
->mOffsetInTextNode
);
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
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
;
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
);
1909 // The parent has no children, so position the iterator
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
);
1920 // Now iterate to the left, towards the beginning of
1921 // the text block, to find the first text node you
1924 Text
* textNode
= nullptr;
1925 for (; !filteredIter
->IsDone(); filteredIter
->Prev()) {
1926 nsINode
* current
= filteredIter
->GetCurrentNode();
1927 if (current
->IsText()) {
1928 textNode
= current
->AsText();
1934 // We found a node, now set the offset to the end
1935 // of the text node.
1936 offset
= textNode
->TextLength();
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
1944 nsresult rv
= filteredIter
->PositionAt(saveNode
);
1945 NS_ENSURE_SUCCESS(rv
, rv
);
1948 for (; !filteredIter
->IsDone(); filteredIter
->Next()) {
1949 nsINode
* current
= filteredIter
->GetCurrentNode();
1950 if (current
->IsText()) {
1951 textNode
= current
->AsText();
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.
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
;
1969 entry
->mOffsetInTextInBlock
+ (offset
- entry
->mOffsetInTextNode
);
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
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
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
;
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) {
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
;
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
2084 *aSelStatus
= BlockSelectionStatus::eBlockContains
;
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
;
2096 // The start of the range will be the rightmost
2100 p1
= eStart
->mTextNode
;
2103 p1
= startContainer
;
2107 // The end of the range will be the leftmost
2111 p2
= eEnd
->mTextNode
;
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
;
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()) {
2136 for (; !filteredIter
->IsDone(); filteredIter
->Next()) {
2137 nsINode
* node
= filteredIter
->GetCurrentNode();
2138 if (node
->IsText()) {
2139 p1
= node
->AsText();
2145 NS_ENSURE_TRUE(found
, NS_ERROR_FAILURE
);
2148 // Find the last text node in the range.
2149 filteredIter
->Last();
2150 if (!p2
->IsText()) {
2152 for (; !filteredIter
->IsDone(); filteredIter
->Prev()) {
2153 nsINode
* node
= filteredIter
->GetCurrentNode();
2154 if (node
->IsText()) {
2155 p2
= node
->AsText();
2156 o2
= p2
->AsText()->Length();
2162 NS_ENSURE_TRUE(found
, NS_ERROR_FAILURE
);
2168 for (size_t i
= 0; i
< tableCount
; i
++) {
2169 const UniquePtr
<OffsetEntry
>& entry
= mOffsetTable
[i
];
2170 LockOffsetEntryArrayLengthInDebugBuild(observer
, mOffsetTable
);
2172 if (entry
->mTextNode
== p1
.get() &&
2173 entry
->OffsetInTextNodeIsInRangeOrEndOffset(o1
)) {
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
;
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
;
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
;
2195 // The entire entry must be in the range.
2196 *aSelLength
+= entry
->mLength
;
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();
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
;
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
;
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()
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
))) {
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
2297 lastTextNode
= content
->AsText();
2300 aFilteredIter
->Prev();
2302 if (DidSkip(aFilteredIter
)) {
2308 aFilteredIter
->PositionAt(lastTextNode
);
2311 // XXX: What should we return if last is null?
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
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
);
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()
2358 if (content
->IsText()) {
2359 if (crossedBlockBoundary
||
2360 (previousTextNode
&& !TextServicesDocument::HasSameBlockNodeParent(
2361 *previousTextNode
, *content
->AsText()))) {
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;
2386 nsresult
TextServicesDocument::GetFirstTextNodeInPrevBlock(
2387 nsIContent
** aContent
) {
2388 NS_ENSURE_TRUE(aContent
, NS_ERROR_NULL_POINTER
);
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
);
2405 if (!mFilteredIter
->IsDone()) {
2406 nsCOMPtr
<nsIContent
> current
=
2407 mFilteredIter
->GetCurrentNode()->IsContent()
2408 ? mFilteredIter
->GetCurrentNode()->AsContent()
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
);
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
);
2437 if (!mFilteredIter
->IsDone()) {
2438 nsCOMPtr
<nsIContent
> current
=
2439 mFilteredIter
->GetCurrentNode()->IsContent()
2440 ? mFilteredIter
->GetCurrentNode()->AsContent()
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 */) {
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;
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");
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:
2484 TextServicesDocument::FirstTextNodeInCurrentBlock(&aFilteredIter
);
2485 if (NS_FAILED(rv
)) {
2486 NS_WARNING("TextServicesDocument::FirstTextNodeInCurrentBlock() failed");
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()
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
)) {
2506 if (content
->IsText()) {
2507 if (previousTextNode
&& !TextServicesDocument::HasSameBlockNodeParent(
2508 *previousTextNode
, *content
->AsText())) {
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
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
;
2535 if (entry
->mTextNode
== rngEndNode
) {
2536 endOffset
= rngEndOffset
;
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
;
2552 *aAllTextInBlock
+= str
;
2556 previousTextNode
= content
->AsText();
2558 if (!firstTextNode
) {
2559 firstTextNode
= content
->AsText();
2564 aFilteredIter
.Next();
2566 if (TextServicesDocument::DidSkip(&aFilteredIter
)) {
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
2581 return IteratorStatus::eDone
;
2584 void TextServicesDocument::OffsetEntryArray::RemoveInvalidElements() {
2585 for (size_t i
= 0; i
< Length();) {
2586 if (ElementAt(i
)->mIsValid
) {
2592 if (!mSelection
.IsSet()) {
2595 if (mSelection
.StartIndex() == i
) {
2596 NS_ASSERTION(false, "What should we do in this case?");
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(
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
;
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
) {
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())) {
2662 "TextServicesDocument::OffsetEntryArray::FirstIndexOf() didn't find "
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
])) {
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
])) {
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
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.
2728 wordEnd
.Set(entry
->mTextNode
, entry
->mOffsetInTextNode
+ res
.mEnd
-
2729 entry
->mOffsetInTextInBlock
);
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.
2746 TextServicesDocument::DidDeleteNode(nsINode
* aChild
, nsresult aResult
) {
2747 if (NS_WARN_IF(NS_FAILED(aResult
)) || NS_WARN_IF(!aChild
) ||
2748 !aChild
->IsContent()) {
2751 DidDeleteContent(*aChild
->AsContent());
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()))) {
2761 DidJoinContents(aJoinedPoint
, *aRemovedNode
->AsContent());
2766 TextServicesDocument::DidInsertText(CharacterData
* aTextNode
, int32_t aOffset
,
2767 const nsAString
& aString
,
2773 TextServicesDocument::WillDeleteText(CharacterData
* aTextNode
, int32_t aOffset
,
2779 TextServicesDocument::WillDeleteRanges(
2780 const nsTArray
<RefPtr
<nsRange
>>& aRangesToDelete
) {
2784 #undef LockOffsetEntryArrayLengthInDebugBuild
2786 } // namespace mozilla