1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * Implementation of mozilla::dom::Selection
11 #include "mozilla/dom/Selection.h"
12 #include "mozilla/intl/BidiEmbeddingLevel.h"
14 #include "mozilla/AccessibleCaretEventHub.h"
15 #include "mozilla/AsyncEventDispatcher.h"
16 #include "mozilla/Attributes.h"
17 #include "mozilla/AutoCopyListener.h"
18 #include "mozilla/AutoRestore.h"
19 #include "mozilla/BasePrincipal.h"
20 #include "mozilla/ContentIterator.h"
21 #include "mozilla/dom/Element.h"
22 #include "mozilla/dom/SelectionBinding.h"
23 #include "mozilla/dom/ShadowRoot.h"
24 #include "mozilla/ErrorResult.h"
25 #include "mozilla/EventStates.h"
26 #include "mozilla/HTMLEditor.h"
27 #include "mozilla/IntegerRange.h"
28 #include "mozilla/Logging.h"
29 #include "mozilla/PresShell.h"
30 #include "mozilla/RangeBoundary.h"
31 #include "mozilla/RangeUtils.h"
32 #include "mozilla/StaticPrefs_dom.h"
33 #include "mozilla/Telemetry.h"
38 #include "nsFrameSelection.h"
39 #include "nsISelectionListener.h"
40 #include "nsContentCID.h"
41 #include "nsDeviceContext.h"
42 #include "nsIContent.h"
43 #include "nsIContentInlines.h"
45 #include "nsITableCellLayout.h"
47 #include "nsTableWrapperFrame.h"
48 #include "nsTableCellFrame.h"
49 #include "nsIScrollableFrame.h"
50 #include "nsCCUncollectableMarker.h"
51 #include "nsIDocumentEncoder.h"
52 #include "nsTextFragment.h"
54 #include "nsContentUtils.h"
56 #include "nsGkAtoms.h"
57 #include "nsLayoutUtils.h"
58 #include "nsBidiPresUtils.h"
59 #include "nsTextFrame.h"
61 #include "nsContentUtils.h"
62 #include "nsThreadUtils.h"
64 #include "nsPresContext.h"
68 #include "mozilla/dom/Document.h"
71 #include "nsISelectionController.h" //for the enums
72 #include "nsCopySupport.h"
73 #include "nsIFrameInlines.h"
74 #include "nsRefreshDriver.h"
77 #include "nsViewManager.h"
79 #include "nsFocusManager.h"
80 #include "nsPIDOMWindow.h"
82 using namespace mozilla
;
83 using namespace mozilla::dom
;
85 static LazyLogModule
sSelectionLog("Selection");
87 //#define DEBUG_TABLE 1
90 static void printRange(nsRange
* aDomRange
);
91 # define DEBUG_OUT_RANGE(x) printRange(x)
93 # define DEBUG_OUT_RANGE(x)
96 static constexpr nsLiteralCString kNoDocumentTypeNodeError
=
97 "DocumentType nodes are not supported"_ns
;
98 static constexpr nsLiteralCString kNoRangeExistsError
=
99 "No selection range exists"_ns
;
101 /******************************************************************************
102 * Utility methods defined in nsISelectionController.idl
103 ******************************************************************************/
107 const char* ToChar(SelectionType aSelectionType
) {
108 switch (aSelectionType
) {
109 case SelectionType::eInvalid
:
110 return "SelectionType::eInvalid";
111 case SelectionType::eNone
:
112 return "SelectionType::eNone";
113 case SelectionType::eNormal
:
114 return "SelectionType::eNormal";
115 case SelectionType::eSpellCheck
:
116 return "SelectionType::eSpellCheck";
117 case SelectionType::eIMERawClause
:
118 return "SelectionType::eIMERawClause";
119 case SelectionType::eIMESelectedRawClause
:
120 return "SelectionType::eIMESelectedRawClause";
121 case SelectionType::eIMEConvertedClause
:
122 return "SelectionType::eIMEConvertedClause";
123 case SelectionType::eIMESelectedClause
:
124 return "SelectionType::eIMESelectedClause";
125 case SelectionType::eAccessibility
:
126 return "SelectionType::eAccessibility";
127 case SelectionType::eFind
:
128 return "SelectionType::eFind";
129 case SelectionType::eURLSecondary
:
130 return "SelectionType::eURLSecondary";
131 case SelectionType::eURLStrikeout
:
132 return "SelectionType::eURLStrikeout";
134 return "Invalid SelectionType";
138 } // namespace mozilla
140 //#define DEBUG_SELECTION // uncomment for printf describing every collapse and
141 // extend. #define DEBUG_NAVIGATION
143 //#define DEBUG_TABLE_SELECTION 1
145 struct CachedOffsetForFrame
{
146 CachedOffsetForFrame()
147 : mCachedFrameOffset(0, 0) // nsPoint ctor
149 mLastCaretFrame(nullptr),
150 mLastContentOffset(0),
151 mCanCacheFrameOffset(false) {}
153 nsPoint mCachedFrameOffset
; // cached frame offset
154 nsIFrame
* mLastCaretFrame
; // store the frame the caret was last drawn in.
155 int32_t mLastContentOffset
; // store last content offset
156 bool mCanCacheFrameOffset
; // cached frame offset is valid?
159 class AutoScroller final
: public nsITimerCallback
, public nsINamed
{
163 explicit AutoScroller(nsFrameSelection
* aFrameSelection
)
164 : mFrameSelection(aFrameSelection
),
168 mFurtherScrollingAllowed(FurtherScrollingAllowed::kYes
) {
169 MOZ_ASSERT(mFrameSelection
);
172 MOZ_CAN_RUN_SCRIPT nsresult
DoAutoScroll(nsIFrame
* aFrame
, nsPoint aPoint
);
175 // aPoint is relative to aPresContext's root frame
176 nsresult
ScheduleNextDoAutoScroll(nsPresContext
* aPresContext
,
178 if (NS_WARN_IF(mFurtherScrollingAllowed
== FurtherScrollingAllowed::kNo
)) {
179 return NS_ERROR_FAILURE
;
184 // Store the presentation context. The timer will be
185 // stopped by the selection if the prescontext is destroyed.
186 mPresContext
= aPresContext
;
188 mContent
= PresShell::GetCapturingContent();
191 mTimer
= NS_NewTimer(
192 mPresContext
->Document()->EventTargetFor(TaskCategory::Other
));
195 return NS_ERROR_OUT_OF_MEMORY
;
199 return mTimer
->InitWithCallback(this, mDelayInMs
, nsITimer::TYPE_ONE_SHOT
);
203 enum class FurtherScrollingAllowed
{ kYes
, kNo
};
205 void Stop(const FurtherScrollingAllowed aFurtherScrollingAllowed
) {
206 MOZ_ASSERT((aFurtherScrollingAllowed
== FurtherScrollingAllowed::kNo
) ||
207 (mFurtherScrollingAllowed
== FurtherScrollingAllowed::kYes
));
215 mFurtherScrollingAllowed
= aFurtherScrollingAllowed
;
218 void SetDelay(uint32_t aDelayInMs
) { mDelayInMs
= aDelayInMs
; }
220 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Notify(nsITimer
* timer
) override
{
222 AutoWeakFrame frame
=
223 mContent
? mPresContext
->GetPrimaryFrameFor(mContent
) : nullptr;
229 nsPoint pt
= mPoint
- frame
->GetOffsetTo(
230 mPresContext
->PresShell()->GetRootFrame());
231 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
232 frameSelection
->HandleDrag(frame
, pt
);
233 if (!frame
.IsAlive()) {
237 NS_ASSERTION(frame
->PresContext() == mPresContext
, "document mismatch?");
238 DoAutoScroll(frame
, pt
);
243 NS_IMETHOD
GetName(nsACString
& aName
) override
{
244 aName
.AssignLiteral("AutoScroller");
249 virtual ~AutoScroller() {
256 nsFrameSelection
* const mFrameSelection
;
257 nsPresContext
* mPresContext
;
258 // relative to mPresContext's root frame
260 nsCOMPtr
<nsITimer
> mTimer
;
261 nsCOMPtr
<nsIContent
> mContent
;
263 FurtherScrollingAllowed mFurtherScrollingAllowed
;
266 NS_IMPL_ISUPPORTS(AutoScroller
, nsITimerCallback
, nsINamed
)
269 void printRange(nsRange
* aDomRange
) {
271 printf("NULL Range\n");
273 nsINode
* startNode
= aDomRange
->GetStartContainer();
274 nsINode
* endNode
= aDomRange
->GetEndContainer();
275 int32_t startOffset
= aDomRange
->StartOffset();
276 int32_t endOffset
= aDomRange
->EndOffset();
278 printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n",
279 (unsigned long)aDomRange
, (unsigned long)startNode
, (long)startOffset
,
280 (unsigned long)endNode
, (long)endOffset
);
282 #endif /* PRINT_RANGE */
284 void Selection::Stringify(nsAString
& aResult
, FlushFrames aFlushFrames
) {
285 if (aFlushFrames
== FlushFrames::Yes
) {
286 // We need FlushType::Frames here to make sure frames have been created for
287 // the selected content. Use mFrameSelection->GetPresShell() which returns
288 // null if the Selection has been disconnected (the shell is Destroyed).
289 RefPtr
<PresShell
> presShell
=
290 mFrameSelection
? mFrameSelection
->GetPresShell() : nullptr;
295 presShell
->FlushPendingNotifications(FlushType::Frames
);
298 IgnoredErrorResult rv
;
299 ToStringWithFormat(u
"text/plain"_ns
, nsIDocumentEncoder::SkipInvisibleContent
,
306 void Selection::ToStringWithFormat(const nsAString
& aFormatType
,
307 uint32_t aFlags
, int32_t aWrapCol
,
308 nsAString
& aReturn
, ErrorResult
& aRv
) {
309 nsCOMPtr
<nsIDocumentEncoder
> encoder
=
310 do_createDocumentEncoder(NS_ConvertUTF16toUTF8(aFormatType
).get());
312 aRv
.Throw(NS_ERROR_FAILURE
);
316 PresShell
* presShell
= GetPresShell();
318 aRv
.Throw(NS_ERROR_FAILURE
);
322 Document
* doc
= presShell
->GetDocument();
324 // Flags should always include OutputSelectionOnly if we're coming from here:
325 aFlags
|= nsIDocumentEncoder::OutputSelectionOnly
;
326 nsAutoString readstring
;
327 readstring
.Assign(aFormatType
);
328 nsresult rv
= encoder
->Init(doc
, readstring
, aFlags
);
334 encoder
->SetSelection(this);
335 if (aWrapCol
!= 0) encoder
->SetWrapColumn(aWrapCol
);
337 rv
= encoder
->EncodeToString(aReturn
);
343 void Selection::SetInterlinePosition(bool aHintRight
, ErrorResult
& aRv
) {
344 MOZ_ASSERT(mSelectionType
== SelectionType::eNormal
);
346 if (!mFrameSelection
) {
347 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
); // Can't do selection
351 mFrameSelection
->SetHint(aHintRight
? CARET_ASSOCIATE_AFTER
352 : CARET_ASSOCIATE_BEFORE
);
355 bool Selection::GetInterlinePosition(ErrorResult
& aRv
) {
356 MOZ_ASSERT(mSelectionType
== SelectionType::eNormal
);
358 if (!mFrameSelection
) {
359 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
); // Can't do selection
362 return mFrameSelection
->GetHint() == CARET_ASSOCIATE_AFTER
;
365 static bool IsEditorNode(const nsINode
* aNode
) {
370 if (aNode
->IsEditable()) {
374 auto* element
= Element::FromNode(aNode
);
375 return element
&& element
->State().HasState(NS_EVENT_STATE_READWRITE
);
378 bool Selection::IsEditorSelection() const {
379 return IsEditorNode(GetFocusNode());
382 Nullable
<int16_t> Selection::GetCaretBidiLevel(
383 mozilla::ErrorResult
& aRv
) const {
384 MOZ_ASSERT(mSelectionType
== SelectionType::eNormal
);
386 if (!mFrameSelection
) {
387 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
);
388 return Nullable
<int16_t>();
390 mozilla::intl::BidiEmbeddingLevel caretBidiLevel
=
391 static_cast<mozilla::intl::BidiEmbeddingLevel
>(
392 mFrameSelection
->GetCaretBidiLevel());
393 return (caretBidiLevel
& BIDI_LEVEL_UNDEFINED
)
394 ? Nullable
<int16_t>()
395 : Nullable
<int16_t>(caretBidiLevel
);
398 void Selection::SetCaretBidiLevel(const Nullable
<int16_t>& aCaretBidiLevel
,
399 mozilla::ErrorResult
& aRv
) {
400 MOZ_ASSERT(mSelectionType
== SelectionType::eNormal
);
402 if (!mFrameSelection
) {
403 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
);
406 if (aCaretBidiLevel
.IsNull()) {
407 mFrameSelection
->UndefineCaretBidiLevel();
409 mFrameSelection
->SetCaretBidiLevelAndMaybeSchedulePaint(
410 mozilla::intl::BidiEmbeddingLevel(aCaretBidiLevel
.Value()));
415 * Test whether the supplied range points to a single table element.
416 * Result is one of the TableSelectionMode constants. "None" means
417 * a table element isn't selected.
419 // TODO: Figure out TableSelectionMode::Column and TableSelectionMode::AllCells
420 static nsresult
GetTableSelectionMode(const nsRange
& aRange
,
421 TableSelectionMode
* aTableSelectionType
) {
422 if (!aTableSelectionType
) {
423 return NS_ERROR_NULL_POINTER
;
426 *aTableSelectionType
= TableSelectionMode::None
;
428 nsINode
* startNode
= aRange
.GetStartContainer();
430 return NS_ERROR_FAILURE
;
433 nsINode
* endNode
= aRange
.GetEndContainer();
435 return NS_ERROR_FAILURE
;
438 // Not a single selected node
439 if (startNode
!= endNode
) {
443 nsIContent
* child
= aRange
.GetChildAtStartOffset();
445 // Not a single selected node
446 if (!child
|| child
->GetNextSibling() != aRange
.GetChildAtEndOffset()) {
450 nsIContent
* startContent
= static_cast<nsIContent
*>(startNode
);
451 if (!(startNode
->IsElement() && startContent
->IsHTMLElement())) {
452 // Implies a check for being an element; if we ever make this work
453 // for non-HTML, need to keep checking for elements.
457 if (startContent
->IsHTMLElement(nsGkAtoms::tr
)) {
458 *aTableSelectionType
= TableSelectionMode::Cell
;
459 } else // check to see if we are selecting a table or row (column and all
460 // cells not done yet)
462 if (child
->IsHTMLElement(nsGkAtoms::table
)) {
463 *aTableSelectionType
= TableSelectionMode::Table
;
464 } else if (child
->IsHTMLElement(nsGkAtoms::tr
)) {
465 *aTableSelectionType
= TableSelectionMode::Row
;
472 nsresult
Selection::MaybeAddTableCellRange(nsRange
& aRange
,
473 Maybe
<size_t>* aOutIndex
) {
475 return NS_ERROR_NULL_POINTER
;
478 MOZ_ASSERT(aOutIndex
->isNothing());
480 if (!mFrameSelection
) {
484 // Get if we are adding a cell selection and the row, col of cell if we are
485 TableSelectionMode tableMode
;
486 nsresult result
= GetTableSelectionMode(aRange
, &tableMode
);
487 if (NS_FAILED(result
)) return result
;
489 // If not adding a cell range, we are done here
490 if (tableMode
!= TableSelectionMode::Cell
) {
491 mFrameSelection
->mTableSelection
.mMode
= tableMode
;
492 // Don't fail if range isn't a selected cell, aDidAddRange tells caller if
497 // Set frame selection mode only if not already set to a table mode
498 // so we don't lose the select row and column flags (not detected by
499 // getTableCellLocation)
500 if (mFrameSelection
->mTableSelection
.mMode
== TableSelectionMode::None
) {
501 mFrameSelection
->mTableSelection
.mMode
= tableMode
;
504 return AddRangesForSelectableNodes(&aRange
, aOutIndex
,
505 DispatchSelectstartEvent::Maybe
);
508 Selection::Selection(SelectionType aSelectionType
,
509 nsFrameSelection
* aFrameSelection
)
510 : mFrameSelection(aFrameSelection
),
511 mCachedOffsetForFrame(nullptr),
512 mDirection(eDirNext
),
513 mSelectionType(aSelectionType
),
514 mCustomColors(nullptr),
515 mSelectionChangeBlockerCount(0),
516 mUserInitiated(false),
518 mNotifyAutoCopy(false) {}
520 Selection::~Selection() { Disconnect(); }
522 void Selection::Disconnect() {
523 RemoveAnchorFocusRange();
525 mStyledRanges
.UnregisterSelection();
528 mAutoScroller
->Stop(AutoScroller::FurtherScrollingAllowed::kNo
);
529 mAutoScroller
= nullptr;
532 mScrollEvent
.Revoke();
534 if (mCachedOffsetForFrame
) {
535 delete mCachedOffsetForFrame
;
536 mCachedOffsetForFrame
= nullptr;
540 Document
* Selection::GetParentObject() const {
541 PresShell
* presShell
= GetPresShell();
542 return presShell
? presShell
->GetDocument() : nullptr;
545 DocGroup
* Selection::GetDocGroup() const {
546 PresShell
* presShell
= GetPresShell();
550 Document
* doc
= presShell
->GetDocument();
551 return doc
? doc
->GetDocGroup() : nullptr;
554 NS_IMPL_CYCLE_COLLECTION_CLASS(Selection
)
556 MOZ_CAN_RUN_SCRIPT_BOUNDARY
557 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Selection
)
558 // Unlink the selection listeners *before* we do RemoveAllRanges since
559 // we don't want to notify the listeners during JS GC (they could be
561 tmp
->mNotifyAutoCopy
= false;
562 if (tmp
->mAccessibleCaretEventHub
) {
563 tmp
->StopNotifyingAccessibleCaretEventHub();
565 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionChangeEventDispatcher
)
566 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionListeners
)
567 MOZ_KnownLive(tmp
)->RemoveAllRanges(IgnoreErrors());
568 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameSelection
)
569 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
570 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
571 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
572 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
573 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Selection
)
575 uint32_t i
, count
= tmp
->mStyledRanges
.Length();
576 for (i
= 0; i
< count
; ++i
) {
577 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyledRanges
.mRanges
[i
].mRange
)
580 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorFocusRange
)
581 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameSelection
)
582 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionChangeEventDispatcher
)
583 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionListeners
)
584 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
585 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Selection
)
587 // QueryInterface implementation for Selection
588 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Selection
)
589 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
590 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
591 NS_INTERFACE_MAP_ENTRY(nsISupports
)
594 NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_ADDREF(Selection
)
595 NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(
596 Selection
, Disconnect())
598 const RangeBoundary
& Selection::AnchorRef() const {
599 if (!mAnchorFocusRange
) {
600 static RangeBoundary sEmpty
;
604 if (GetDirection() == eDirNext
) {
605 return mAnchorFocusRange
->StartRef();
608 return mAnchorFocusRange
->EndRef();
611 const RangeBoundary
& Selection::FocusRef() const {
612 if (!mAnchorFocusRange
) {
613 static RangeBoundary sEmpty
;
617 if (GetDirection() == eDirNext
) {
618 return mAnchorFocusRange
->EndRef();
621 return mAnchorFocusRange
->StartRef();
624 void Selection::SetAnchorFocusRange(size_t aIndex
) {
625 if (aIndex
>= mStyledRanges
.Length()) {
628 mAnchorFocusRange
= mStyledRanges
.mRanges
[aIndex
].mRange
;
631 static int32_t CompareToRangeStart(const nsINode
& aCompareNode
,
632 uint32_t aCompareOffset
,
633 const nsRange
& aRange
) {
634 MOZ_ASSERT(aRange
.GetStartContainer());
635 nsINode
* start
= aRange
.GetStartContainer();
636 // If the nodes that we're comparing are not in the same document or in the
637 // same subtree, assume that aCompareNode will fall at the end of the ranges.
638 // NOTE(emilio): This is broken (bug 1590379). When fixed, shadow-including
639 // tree order[1] seems the most reasonable order, but if we choose other order
640 // than that code in nsPrintJob.cpp to deal with selection printing might need
643 // [1]: https://dom.spec.whatwg.org/#concept-shadow-including-tree-order
644 if (aCompareNode
.GetComposedDoc() != start
->GetComposedDoc() ||
645 !start
->GetComposedDoc() ||
646 aCompareNode
.SubtreeRoot() != start
->SubtreeRoot()) {
648 "`CompareToRangeStart` couldn't compare nodes, pretending some order.");
652 // The points are in the same subtree, hence there has to be an order.
653 return *nsContentUtils::ComparePoints(&aCompareNode
, aCompareOffset
, start
,
654 aRange
.StartOffset());
657 static int32_t CompareToRangeEnd(const nsINode
& aCompareNode
,
658 uint32_t aCompareOffset
,
659 const nsRange
& aRange
) {
660 MOZ_ASSERT(aRange
.IsPositioned());
661 nsINode
* end
= aRange
.GetEndContainer();
662 // If the nodes that we're comparing are not in the same document or in the
663 // same subtree, assume that aCompareNode will fall at the end of the ranges.
664 if (aCompareNode
.GetComposedDoc() != end
->GetComposedDoc() ||
665 !end
->GetComposedDoc() ||
666 aCompareNode
.SubtreeRoot() != end
->SubtreeRoot()) {
668 "`CompareToRangeEnd` couldn't compare nodes, pretending some order.");
672 // The points are in the same subtree, hence there has to be an order.
673 return *nsContentUtils::ComparePoints(&aCompareNode
, aCompareOffset
, end
,
678 size_t Selection::StyledRanges::FindInsertionPoint(
679 const nsTArray
<StyledRange
>* aElementArray
, const nsINode
& aPointNode
,
680 uint32_t aPointOffset
,
681 int32_t (*aComparator
)(const nsINode
&, uint32_t, const nsRange
&)) {
682 int32_t beginSearch
= 0;
683 int32_t endSearch
= aElementArray
->Length(); // one beyond what to check
686 int32_t center
= endSearch
- 1; // Check last index, then binary search
688 const nsRange
* range
= (*aElementArray
)[center
].mRange
;
690 int32_t cmp
{aComparator(aPointNode
, aPointOffset
, *range
)};
692 if (cmp
< 0) { // point < cur
694 } else if (cmp
> 0) { // point > cur
695 beginSearch
= center
+ 1;
696 } else { // found match, done
697 beginSearch
= center
;
700 center
= (endSearch
- beginSearch
) / 2 + beginSearch
;
701 } while (endSearch
- beginSearch
> 0);
704 return AssertedCast
<size_t>(beginSearch
);
707 // Selection::SubtractRange
709 // A helper function that subtracts aSubtract from aRange, and adds
710 // 1 or 2 StyledRange objects representing the remaining non-overlapping
711 // difference to aOutput. It is assumed that the caller has checked that
712 // aRange and aSubtract do indeed overlap
715 nsresult
Selection::StyledRanges::SubtractRange(
716 StyledRange
& aRange
, nsRange
& aSubtract
, nsTArray
<StyledRange
>* aOutput
) {
717 nsRange
* range
= aRange
.mRange
;
719 if (NS_WARN_IF(!range
->IsPositioned())) {
720 return NS_ERROR_UNEXPECTED
;
723 // First we want to compare to the range start
724 int32_t cmp
{CompareToRangeStart(*range
->GetStartContainer(),
725 range
->StartOffset(), aSubtract
)};
727 // Also, make a comparison to the range end
728 int32_t cmp2
{CompareToRangeEnd(*range
->GetEndContainer(), range
->EndOffset(),
731 // If the existing range left overlaps the new range (aSubtract) then
732 // cmp < 0, and cmp2 < 0
733 // If it right overlaps the new range then cmp > 0 and cmp2 > 0
734 // If it fully contains the new range, then cmp < 0 and cmp2 > 0
737 // We need to add a new StyledRange to the output, running from
738 // the end of aSubtract to the end of range
740 RefPtr
<nsRange
> postOverlap
=
741 nsRange::Create(aSubtract
.EndRef(), range
->EndRef(), error
);
742 if (NS_WARN_IF(error
.Failed())) {
743 return error
.StealNSResult();
745 MOZ_ASSERT(postOverlap
);
746 if (!postOverlap
->Collapsed()) {
747 // XXX(Bug 1631371) Check if this should use a fallible operation as it
748 // pretended earlier.
749 aOutput
->InsertElementAt(0, StyledRange(postOverlap
));
750 (*aOutput
)[0].mTextRangeStyle
= aRange
.mTextRangeStyle
;
755 // We need to add a new StyledRange to the output, running from
756 // the start of the range to the start of aSubtract
758 RefPtr
<nsRange
> preOverlap
=
759 nsRange::Create(range
->StartRef(), aSubtract
.StartRef(), error
);
760 if (NS_WARN_IF(error
.Failed())) {
761 return error
.StealNSResult();
763 MOZ_ASSERT(preOverlap
);
764 if (!preOverlap
->Collapsed()) {
765 // XXX(Bug 1631371) Check if this should use a fallible operation as it
766 // pretended earlier.
767 aOutput
->InsertElementAt(0, StyledRange(preOverlap
));
768 (*aOutput
)[0].mTextRangeStyle
= aRange
.mTextRangeStyle
;
775 static void UserSelectRangesToAdd(nsRange
* aItem
,
776 nsTArray
<RefPtr
<nsRange
>>& aRangesToAdd
) {
777 // We cannot directly call IsEditorSelection() because we may be in an
778 // inconsistent state during Collapse() (we're cleared already but we haven't
779 // got a new focus node yet).
780 if (IsEditorNode(aItem
->GetStartContainer()) &&
781 IsEditorNode(aItem
->GetEndContainer())) {
782 // Don't mess with the selection ranges for editing, editor doesn't really
783 // deal well with multi-range selections.
784 aRangesToAdd
.AppendElement(aItem
);
786 aItem
->ExcludeNonSelectableNodes(&aRangesToAdd
);
790 static nsINode
* DetermineSelectstartEventTarget(
791 const bool aSelectionEventsOnTextControlsEnabled
, const nsRange
& aRange
) {
792 nsINode
* target
= aRange
.GetStartContainer();
793 if (aSelectionEventsOnTextControlsEnabled
) {
794 // Get the first element which isn't in a native anonymous subtree
795 while (target
&& target
->IsInNativeAnonymousSubtree()) {
796 target
= target
->GetParent();
799 if (target
->IsInNativeAnonymousSubtree()) {
800 // This is a selection under a text control, so don't dispatch the
809 * @return true, iff the default action should be executed.
811 static bool MaybeDispatchSelectstartEvent(
812 const nsRange
& aRange
, const bool aSelectionEventsOnTextControlsEnabled
,
813 Document
* aDocument
) {
814 nsCOMPtr
<nsINode
> selectstartEventTarget
= DetermineSelectstartEventTarget(
815 aSelectionEventsOnTextControlsEnabled
, aRange
);
817 bool executeDefaultAction
= true;
819 if (selectstartEventTarget
) {
820 nsContentUtils::DispatchTrustedEvent(
821 aDocument
, selectstartEventTarget
, u
"selectstart"_ns
, CanBubble::eYes
,
822 Cancelable::eYes
, &executeDefaultAction
);
825 return executeDefaultAction
;
829 bool Selection::IsUserSelectionCollapsed(
830 const nsRange
& aRange
, nsTArray
<RefPtr
<nsRange
>>& aTempRangesToAdd
) {
831 MOZ_ASSERT(aTempRangesToAdd
.IsEmpty());
833 RefPtr
<nsRange
> scratchRange
= aRange
.CloneRange();
834 UserSelectRangesToAdd(scratchRange
, aTempRangesToAdd
);
835 const bool userSelectionCollapsed
=
836 (aTempRangesToAdd
.Length() == 0) ||
837 ((aTempRangesToAdd
.Length() == 1) && aTempRangesToAdd
[0]->Collapsed());
839 aTempRangesToAdd
.ClearAndRetainStorage();
841 return userSelectionCollapsed
;
844 nsresult
Selection::AddRangesForUserSelectableNodes(
845 nsRange
* aRange
, Maybe
<size_t>* aOutIndex
,
846 const DispatchSelectstartEvent aDispatchSelectstartEvent
) {
847 MOZ_ASSERT(mUserInitiated
);
848 MOZ_ASSERT(aOutIndex
);
849 MOZ_ASSERT(aOutIndex
->isNothing());
852 return NS_ERROR_NULL_POINTER
;
855 if (!aRange
->IsPositioned()) {
856 return NS_ERROR_UNEXPECTED
;
859 AutoTArray
<RefPtr
<nsRange
>, 4> rangesToAdd
;
860 if (mStyledRanges
.Length()) {
861 aOutIndex
->emplace(mStyledRanges
.Length() - 1);
864 Document
* doc
= GetDocument();
866 if (aDispatchSelectstartEvent
== DispatchSelectstartEvent::Maybe
&&
867 mSelectionType
== SelectionType::eNormal
&& IsCollapsed() &&
868 !IsBlockingSelectionChangeEvents()) {
869 // We consider a selection to be starting if we are currently collapsed,
870 // and the selection is becoming uncollapsed, and this is caused by a
871 // user initiated event.
873 // First, we generate the ranges to add with a scratch range, which is a
874 // clone of the original range passed in. We do this seperately, because
875 // the selectstart event could have caused the world to change, and
876 // required ranges to be re-generated
878 const bool userSelectionCollapsed
=
879 IsUserSelectionCollapsed(*aRange
, rangesToAdd
);
880 MOZ_ASSERT(userSelectionCollapsed
|| nsContentUtils::IsSafeToRunScript());
881 if (!userSelectionCollapsed
&& nsContentUtils::IsSafeToRunScript()) {
882 // The spec currently doesn't say that we should dispatch this event
883 // on text controls, so for now we only support doing that under a
884 // pref, disabled by default.
885 // See https://github.com/w3c/selection-api/issues/53.
886 const bool executeDefaultAction
= MaybeDispatchSelectstartEvent(
888 StaticPrefs::dom_select_events_textcontrols_selectstart_enabled(),
891 if (!executeDefaultAction
) {
895 // As we potentially dispatched an event to the DOM, something could have
896 // changed under our feet. Re-generate the rangesToAdd array, and
897 // ensure that the range we are about to add is still valid.
898 if (!aRange
->IsPositioned()) {
899 return NS_ERROR_UNEXPECTED
;
904 // Generate the ranges to add
905 UserSelectRangesToAdd(aRange
, rangesToAdd
);
906 size_t newAnchorFocusIndex
=
907 GetDirection() == eDirPrevious
? 0 : rangesToAdd
.Length() - 1;
908 for (size_t i
= 0; i
< rangesToAdd
.Length(); ++i
) {
910 const RefPtr
<Selection
> selection
{this};
911 // `MOZ_KnownLive` needed because of broken static analysis
912 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1622253#c1).
913 nsresult rv
= mStyledRanges
.MaybeAddRangeAndTruncateOverlaps(
914 MOZ_KnownLive(rangesToAdd
[i
]), &index
, *selection
);
915 NS_ENSURE_SUCCESS(rv
, rv
);
916 if (i
== newAnchorFocusIndex
) {
918 rangesToAdd
[i
]->SetIsGenerated(false);
920 rangesToAdd
[i
]->SetIsGenerated(true);
926 nsresult
Selection::AddRangesForSelectableNodes(
927 nsRange
* aRange
, Maybe
<size_t>* aOutIndex
,
928 const DispatchSelectstartEvent aDispatchSelectstartEvent
) {
929 MOZ_ASSERT(aOutIndex
);
930 MOZ_ASSERT(aOutIndex
->isNothing());
933 return NS_ERROR_NULL_POINTER
;
936 if (!aRange
->IsPositioned()) {
937 return NS_ERROR_UNEXPECTED
;
941 sSelectionLog
, LogLevel::Debug
,
942 ("%s: selection=%p, type=%i, range=(%p, StartOffset=%u, EndOffset=%u)",
943 __FUNCTION__
, this, static_cast<int>(GetType()), aRange
,
944 aRange
->StartOffset(), aRange
->EndOffset()));
946 if (mUserInitiated
) {
947 return AddRangesForUserSelectableNodes(aRange
, aOutIndex
,
948 aDispatchSelectstartEvent
);
951 const RefPtr
<Selection
> selection
{this};
952 return mStyledRanges
.MaybeAddRangeAndTruncateOverlaps(aRange
, aOutIndex
,
956 nsresult
Selection::StyledRanges::MaybeAddRangeAndTruncateOverlaps(
957 nsRange
* aRange
, Maybe
<size_t>* aOutIndex
, Selection
& aSelection
) {
959 MOZ_ASSERT(aRange
->IsPositioned());
960 MOZ_ASSERT(aOutIndex
);
961 MOZ_ASSERT(aOutIndex
->isNothing());
963 // a common case is that we have no ranges yet
964 if (mRanges
.Length() == 0) {
965 // XXX(Bug 1631371) Check if this should use a fallible operation as it
966 // pretended earlier.
967 mRanges
.AppendElement(StyledRange(aRange
));
968 aRange
->RegisterSelection(aSelection
);
970 aOutIndex
->emplace(0u);
974 Maybe
<size_t> maybeStartIndex
, maybeEndIndex
;
976 GetIndicesForInterval(aRange
->GetStartContainer(), aRange
->StartOffset(),
977 aRange
->GetEndContainer(), aRange
->EndOffset(),
978 false, maybeStartIndex
, maybeEndIndex
);
979 NS_ENSURE_SUCCESS(rv
, rv
);
981 size_t startIndex
, endIndex
;
982 if (maybeEndIndex
.isNothing()) {
983 // All ranges start after the given range. We can insert our range at
984 // position 0, knowing there are no overlaps (handled below)
987 } else if (maybeStartIndex
.isNothing()) {
988 // All ranges end before the given range. We can insert our range at
989 // the end of the array, knowing there are no overlaps (handled below)
990 startIndex
= mRanges
.Length();
991 endIndex
= startIndex
;
993 startIndex
= *maybeStartIndex
;
994 endIndex
= *maybeEndIndex
;
997 // If the range is already contained in mRanges, silently
999 const bool sameRange
= HasEqualRangeBoundariesAt(*aRange
, startIndex
);
1001 aOutIndex
->emplace(startIndex
);
1005 if (startIndex
== endIndex
) {
1006 // The new range doesn't overlap any existing ranges
1007 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1008 // pretended earlier.
1009 mRanges
.InsertElementAt(startIndex
, StyledRange(aRange
));
1010 aRange
->RegisterSelection(aSelection
);
1011 aOutIndex
->emplace(startIndex
);
1015 // We now know that at least 1 existing range overlaps with the range that
1016 // we are trying to add. In fact, the only ranges of interest are those at
1017 // the two end points, startIndex and endIndex - 1 (which may point to the
1018 // same range) as these may partially overlap the new range. Any ranges
1019 // between these indices are fully overlapped by the new range, and so can be
1021 nsTArray
<StyledRange
> overlaps
;
1022 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1023 // pretended earlier.
1024 overlaps
.InsertElementAt(0, mRanges
[startIndex
]);
1026 if (endIndex
- 1 != startIndex
) {
1027 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1028 // pretended earlier.
1029 overlaps
.InsertElementAt(1, mRanges
[endIndex
- 1]);
1032 // Remove all the overlapping ranges
1033 for (size_t i
= startIndex
; i
< endIndex
; ++i
) {
1034 mRanges
[i
].mRange
->UnregisterSelection();
1036 mRanges
.RemoveElementsAt(startIndex
, endIndex
- startIndex
);
1038 nsTArray
<StyledRange
> temp
;
1039 for (const size_t i
: Reversed(IntegerRange(overlaps
.Length()))) {
1040 nsresult rv
= SubtractRange(overlaps
[i
], *aRange
, &temp
);
1041 NS_ENSURE_SUCCESS(rv
, rv
);
1044 // Insert the new element into our "leftovers" array
1045 // `aRange` is positioned, so it has to have a start container.
1046 size_t insertionPoint
{FindInsertionPoint(&temp
, *aRange
->GetStartContainer(),
1047 aRange
->StartOffset(),
1048 CompareToRangeStart
)};
1050 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1051 // pretended earlier.
1052 temp
.InsertElementAt(insertionPoint
, StyledRange(aRange
));
1054 // Merge the leftovers back in to mRanges
1055 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1056 // pretended earlier.
1057 mRanges
.InsertElementsAt(startIndex
, temp
);
1059 for (uint32_t i
= 0; i
< temp
.Length(); ++i
) {
1060 MOZ_KnownLive(temp
[i
].mRange
)->RegisterSelection(aSelection
);
1061 // `MOZ_KnownLive` is required because of
1062 // https://bugzilla.mozilla.org/show_bug.cgi?id=1622253.
1065 aOutIndex
->emplace(startIndex
+ insertionPoint
);
1069 nsresult
Selection::StyledRanges::RemoveRangeAndUnregisterSelection(
1071 // Find the range's index & remove it. We could use FindInsertionPoint to
1072 // get O(log n) time, but that requires many expensive DOM comparisons.
1073 // For even several thousand items, this is probably faster because the
1074 // comparisons are so fast.
1077 for (i
= 0; i
< mRanges
.Length(); i
++) {
1078 if (mRanges
[i
].mRange
== &aRange
) {
1083 if (idx
< 0) return NS_ERROR_DOM_NOT_FOUND_ERR
;
1085 mRanges
.RemoveElementAt(idx
);
1086 aRange
.UnregisterSelection();
1089 nsresult
Selection::RemoveCollapsedRanges() {
1090 return mStyledRanges
.RemoveCollapsedRanges();
1093 nsresult
Selection::StyledRanges::RemoveCollapsedRanges() {
1095 while (i
< mRanges
.Length()) {
1096 if (mRanges
[i
].mRange
->Collapsed()) {
1097 nsresult rv
= RemoveRangeAndUnregisterSelection(*mRanges
[i
].mRange
);
1098 NS_ENSURE_SUCCESS(rv
, rv
);
1106 void Selection::Clear(nsPresContext
* aPresContext
) {
1107 RemoveAnchorFocusRange();
1109 mStyledRanges
.UnregisterSelection();
1110 for (uint32_t i
= 0; i
< mStyledRanges
.Length(); ++i
) {
1111 SelectFrames(aPresContext
, mStyledRanges
.mRanges
[i
].mRange
, false);
1113 mStyledRanges
.Clear();
1115 // Reset direction so for more dependable table selection range handling
1116 SetDirection(eDirNext
);
1118 // If this was an ATTENTION selection, change it back to normal now
1119 if (mFrameSelection
&& mFrameSelection
->GetDisplaySelection() ==
1120 nsISelectionController::SELECTION_ATTENTION
) {
1121 mFrameSelection
->SetDisplaySelection(nsISelectionController::SELECTION_ON
);
1125 bool Selection::StyledRanges::HasEqualRangeBoundariesAt(
1126 const nsRange
& aRange
, size_t aRangeIndex
) const {
1127 if (aRangeIndex
< mRanges
.Length()) {
1128 const nsRange
* range
= mRanges
[aRangeIndex
].mRange
;
1129 return range
->HasEqualBoundaries(aRange
);
1134 void Selection::GetRangesForInterval(nsINode
& aBeginNode
, uint32_t aBeginOffset
,
1135 nsINode
& aEndNode
, uint32_t aEndOffset
,
1136 bool aAllowAdjacent
,
1137 nsTArray
<RefPtr
<nsRange
>>& aReturn
,
1138 mozilla::ErrorResult
& aRv
) {
1139 nsTArray
<nsRange
*> results
;
1140 nsresult rv
= GetRangesForIntervalArray(&aBeginNode
, aBeginOffset
, &aEndNode
,
1141 aEndOffset
, aAllowAdjacent
, &results
);
1142 if (NS_FAILED(rv
)) {
1147 aReturn
.SetLength(results
.Length());
1148 for (size_t i
= 0; i
< results
.Length(); ++i
) {
1149 aReturn
[i
] = results
[i
]; // AddRefs
1153 nsresult
Selection::GetRangesForIntervalArray(
1154 nsINode
* aBeginNode
, uint32_t aBeginOffset
, nsINode
* aEndNode
,
1155 uint32_t aEndOffset
, bool aAllowAdjacent
, nsTArray
<nsRange
*>* aRanges
) {
1156 if (NS_WARN_IF(!aBeginNode
)) {
1157 return NS_ERROR_UNEXPECTED
;
1160 if (NS_WARN_IF(!aEndNode
)) {
1161 return NS_ERROR_UNEXPECTED
;
1165 Maybe
<size_t> maybeStartIndex
, maybeEndIndex
;
1166 nsresult res
= mStyledRanges
.GetIndicesForInterval(
1167 aBeginNode
, aBeginOffset
, aEndNode
, aEndOffset
, aAllowAdjacent
,
1168 maybeStartIndex
, maybeEndIndex
);
1169 NS_ENSURE_SUCCESS(res
, res
);
1171 if (maybeStartIndex
.isNothing() || maybeEndIndex
.isNothing()) {
1175 for (const size_t i
: IntegerRange(*maybeStartIndex
, *maybeEndIndex
)) {
1176 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1177 // pretended earlier.
1178 aRanges
->AppendElement(mStyledRanges
.mRanges
[i
].mRange
);
1184 nsresult
Selection::StyledRanges::GetIndicesForInterval(
1185 const nsINode
* aBeginNode
, uint32_t aBeginOffset
, const nsINode
* aEndNode
,
1186 uint32_t aEndOffset
, bool aAllowAdjacent
, Maybe
<size_t>& aStartIndex
,
1187 Maybe
<size_t>& aEndIndex
) const {
1188 MOZ_ASSERT(aStartIndex
.isNothing());
1189 MOZ_ASSERT(aEndIndex
.isNothing());
1191 if (NS_WARN_IF(!aBeginNode
)) {
1192 return NS_ERROR_INVALID_POINTER
;
1195 if (NS_WARN_IF(!aEndNode
)) {
1196 return NS_ERROR_INVALID_POINTER
;
1199 if (mRanges
.Length() == 0) {
1203 const bool intervalIsCollapsed
=
1204 aBeginNode
== aEndNode
&& aBeginOffset
== aEndOffset
;
1206 // Ranges that end before the given interval and begin after the given
1207 // interval can be discarded
1208 size_t endsBeforeIndex
{FindInsertionPoint(&mRanges
, *aEndNode
, aEndOffset
,
1209 &CompareToRangeStart
)};
1211 if (endsBeforeIndex
== 0) {
1212 const nsRange
* endRange
= mRanges
[endsBeforeIndex
].mRange
;
1214 // If the interval is strictly before the range at index 0, we can optimize
1215 // by returning now - all ranges start after the given interval
1216 if (!endRange
->StartRef().Equals(aEndNode
, aEndOffset
)) {
1220 // We now know that the start point of mRanges[0].mRange
1221 // equals the end of the interval. Thus, when aAllowadjacent is true, the
1222 // caller is always interested in this range. However, when excluding
1223 // adjacencies, we must remember to include the range when both it and the
1224 // given interval are collapsed to the same point
1225 if (!aAllowAdjacent
&& !(endRange
->Collapsed() && intervalIsCollapsed
))
1228 aEndIndex
.emplace(endsBeforeIndex
);
1230 size_t beginsAfterIndex
{FindInsertionPoint(&mRanges
, *aBeginNode
,
1231 aBeginOffset
, &CompareToRangeEnd
)};
1233 if (beginsAfterIndex
== mRanges
.Length()) {
1234 return NS_OK
; // optimization: all ranges are strictly before us
1237 if (aAllowAdjacent
) {
1238 // At this point, one of the following holds:
1239 // endsBeforeIndex == mRanges.Length(),
1240 // endsBeforeIndex points to a range whose start point does not equal the
1241 // given interval's start point
1242 // endsBeforeIndex points to a range whose start point equals the given
1243 // interval's start point
1244 // In the final case, there can be two such ranges, a collapsed range, and
1245 // an adjacent range (they will appear in mRanges in that
1246 // order). For this final case, we need to increment endsBeforeIndex, until
1247 // one of the first two possibilites hold
1248 while (endsBeforeIndex
< mRanges
.Length()) {
1249 const nsRange
* endRange
= mRanges
[endsBeforeIndex
].mRange
;
1250 if (!endRange
->StartRef().Equals(aEndNode
, aEndOffset
)) {
1256 // Likewise, one of the following holds:
1257 // beginsAfterIndex == 0,
1258 // beginsAfterIndex points to a range whose end point does not equal
1259 // the given interval's end point
1260 // beginsOnOrAfter points to a range whose end point equals the given
1261 // interval's end point
1262 // In the final case, there can be two such ranges, an adjacent range, and
1263 // a collapsed range (they will appear in mRanges in that
1264 // order). For this final case, we only need to take action if both those
1265 // ranges exist, and we are pointing to the collapsed range - we need to
1266 // point to the adjacent range
1267 const nsRange
* beginRange
= mRanges
[beginsAfterIndex
].mRange
;
1268 if (beginsAfterIndex
> 0 && beginRange
->Collapsed() &&
1269 beginRange
->EndRef().Equals(aBeginNode
, aBeginOffset
)) {
1270 beginRange
= mRanges
[beginsAfterIndex
- 1].mRange
;
1271 if (beginRange
->EndRef().Equals(aBeginNode
, aBeginOffset
)) {
1276 // See above for the possibilities at this point. The only case where we
1277 // need to take action is when the range at beginsAfterIndex ends on
1278 // the given interval's start point, but that range isn't collapsed (a
1279 // collapsed range should be included in the returned results).
1280 const nsRange
* beginRange
= mRanges
[beginsAfterIndex
].mRange
;
1281 if (beginRange
->EndRef().Equals(aBeginNode
, aBeginOffset
) &&
1282 !beginRange
->Collapsed()) {
1286 // Again, see above for the meaning of endsBeforeIndex at this point.
1287 // In particular, endsBeforeIndex may point to a collaped range which
1288 // represents the point at the end of the interval - this range should be
1290 if (endsBeforeIndex
< mRanges
.Length()) {
1291 const nsRange
* endRange
= mRanges
[endsBeforeIndex
].mRange
;
1292 if (endRange
->StartRef().Equals(aEndNode
, aEndOffset
) &&
1293 endRange
->Collapsed()) {
1299 NS_ASSERTION(beginsAfterIndex
<= endsBeforeIndex
, "Is mRanges not ordered?");
1300 NS_ENSURE_STATE(beginsAfterIndex
<= endsBeforeIndex
);
1302 aStartIndex
.emplace(beginsAfterIndex
);
1303 aEndIndex
= Some(endsBeforeIndex
);
1307 nsIFrame
* Selection::GetPrimaryFrameForAnchorNode() const {
1308 MOZ_ASSERT(mSelectionType
== SelectionType::eNormal
);
1310 int32_t frameOffset
= 0;
1311 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(GetAnchorNode());
1312 if (content
&& mFrameSelection
) {
1313 return nsFrameSelection::GetFrameForNodeOffset(
1314 content
, AnchorOffset(), mFrameSelection
->GetHint(), &frameOffset
);
1319 nsIFrame
* Selection::GetPrimaryFrameForFocusNode(bool aVisual
,
1320 int32_t* aOffsetUsed
) const {
1321 nsINode
* focusNode
= GetFocusNode();
1322 if (!focusNode
|| !focusNode
->IsContent() || !mFrameSelection
) {
1326 nsCOMPtr
<nsIContent
> content
= focusNode
->AsContent();
1327 int32_t frameOffset
= 0;
1329 aOffsetUsed
= &frameOffset
;
1332 nsIFrame
* frame
= GetPrimaryOrCaretFrameForNodeOffset(content
, FocusOffset(),
1333 aOffsetUsed
, aVisual
);
1338 // If content is whitespace only, we promote focus node to parent because
1339 // whitespace only node might have no frame.
1341 if (!content
->TextIsOnlyWhitespace()) {
1345 nsCOMPtr
<nsIContent
> parent
= content
->GetParent();
1346 if (NS_WARN_IF(!parent
)) {
1349 const Maybe
<uint32_t> offset
= parent
->ComputeIndexOf(content
);
1350 if (MOZ_UNLIKELY(NS_WARN_IF(offset
.isNothing()))) {
1353 return GetPrimaryOrCaretFrameForNodeOffset(parent
, *offset
, aOffsetUsed
,
1357 nsIFrame
* Selection::GetPrimaryOrCaretFrameForNodeOffset(nsIContent
* aContent
,
1359 int32_t* aOffsetUsed
,
1360 bool aVisual
) const {
1361 MOZ_ASSERT(aOffsetUsed
);
1363 if (!mFrameSelection
) {
1367 CaretAssociationHint hint
= mFrameSelection
->GetHint();
1370 mozilla::intl::BidiEmbeddingLevel caretBidiLevel
=
1371 mFrameSelection
->GetCaretBidiLevel();
1373 return nsCaret::GetCaretFrameForNodeOffset(
1374 mFrameSelection
, aContent
, aOffset
, hint
, caretBidiLevel
,
1375 /* aReturnUnadjustedFrame = */ nullptr, aOffsetUsed
);
1378 return nsFrameSelection::GetFrameForNodeOffset(aContent
, aOffset
, hint
,
1382 void Selection::SelectFramesOf(nsIContent
* aContent
, bool aSelected
) const {
1383 nsIFrame
* frame
= aContent
->GetPrimaryFrame();
1387 // The frame could be an SVG text frame, in which case we don't treat it
1389 if (frame
->IsTextFrame()) {
1390 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(frame
);
1391 textFrame
->SelectionStateChanged(0, textFrame
->TextFragment()->GetLength(),
1392 aSelected
, mSelectionType
);
1394 frame
->SelectionStateChanged();
1398 nsresult
Selection::SelectFramesOfInclusiveDescendantsOfContent(
1399 PostContentIterator
& aPostOrderIter
, nsIContent
* aContent
,
1400 bool aSelected
) const {
1401 // If aContent doesn't have children, we should avoid to use the content
1402 // iterator for performance reason.
1403 if (!aContent
->HasChildren()) {
1404 SelectFramesOf(aContent
, aSelected
);
1408 if (NS_WARN_IF(NS_FAILED(aPostOrderIter
.Init(aContent
)))) {
1409 return NS_ERROR_FAILURE
;
1412 for (; !aPostOrderIter
.IsDone(); aPostOrderIter
.Next()) {
1413 nsINode
* node
= aPostOrderIter
.GetCurrentNode();
1415 nsIContent
* innercontent
= node
->IsContent() ? node
->AsContent() : nullptr;
1416 SelectFramesOf(innercontent
, aSelected
);
1422 void Selection::SelectFramesInAllRanges(nsPresContext
* aPresContext
) {
1423 for (size_t i
= 0; i
< mStyledRanges
.Length(); ++i
) {
1424 nsRange
* range
= mStyledRanges
.mRanges
[i
].mRange
;
1425 MOZ_ASSERT(range
->IsInSelection());
1426 SelectFrames(aPresContext
, range
, range
->IsInSelection());
1431 * The idea of this helper method is to select or deselect "top to bottom",
1432 * traversing through the frames
1434 nsresult
Selection::SelectFrames(nsPresContext
* aPresContext
, nsRange
* aRange
,
1435 bool aSelect
) const {
1436 if (!mFrameSelection
|| !aPresContext
|| !aPresContext
->GetPresShell()) {
1440 MOZ_ASSERT(aRange
&& aRange
->IsPositioned());
1442 if (mFrameSelection
->IsInTableSelectionMode()) {
1443 nsINode
* node
= aRange
->GetClosestCommonInclusiveAncestor();
1444 nsIFrame
* frame
= node
->IsContent()
1445 ? node
->AsContent()->GetPrimaryFrame()
1446 : aPresContext
->PresShell()->GetRootFrame();
1448 if (frame
->IsTextFrame()) {
1449 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(frame
);
1451 MOZ_ASSERT(node
== aRange
->GetStartContainer());
1452 MOZ_ASSERT(node
== aRange
->GetEndContainer());
1453 textFrame
->SelectionStateChanged(aRange
->StartOffset(),
1454 aRange
->EndOffset(), aSelect
,
1457 frame
->SelectionStateChanged();
1464 // Loop through the content iterator for each content node; for each text
1465 // node, call SetSelected on it:
1466 nsINode
* startNode
= aRange
->GetStartContainer();
1467 nsIContent
* startContent
=
1468 startNode
->IsContent() ? startNode
->AsContent() : nullptr;
1469 if (!startContent
) {
1470 // Don't warn, bug 1055722
1471 // XXX The range can start from a document node and such range can be
1472 // added to Selection with JS. Therefore, even in such cases,
1473 // shouldn't we handle selection in the range?
1474 return NS_ERROR_UNEXPECTED
;
1477 // We must call first one explicitly
1478 bool isFirstContentTextNode
= startContent
->IsText();
1479 nsINode
* endNode
= aRange
->GetEndContainer();
1480 if (isFirstContentTextNode
) {
1481 nsIFrame
* frame
= startContent
->GetPrimaryFrame();
1482 // The frame could be an SVG text frame, in which case we don't treat it
1485 if (frame
->IsTextFrame()) {
1486 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(frame
);
1487 uint32_t startOffset
= aRange
->StartOffset();
1489 if (endNode
== startContent
) {
1490 endOffset
= aRange
->EndOffset();
1492 endOffset
= startContent
->Length();
1494 textFrame
->SelectionStateChanged(startOffset
, endOffset
, aSelect
,
1497 frame
->SelectionStateChanged();
1502 // If the range is in a node and the node is a leaf node, we don't need to
1503 // walk the subtree.
1504 if (aRange
->Collapsed() ||
1505 (startNode
== endNode
&& !startNode
->HasChildren())) {
1506 if (!isFirstContentTextNode
) {
1507 SelectFramesOf(startContent
, aSelect
);
1512 ContentSubtreeIterator subtreeIter
;
1513 subtreeIter
.Init(aRange
);
1514 if (isFirstContentTextNode
&& !subtreeIter
.IsDone() &&
1515 subtreeIter
.GetCurrentNode() == startNode
) {
1516 subtreeIter
.Next(); // first content has already been handled.
1518 PostContentIterator postOrderIter
;
1519 for (; !subtreeIter
.IsDone(); subtreeIter
.Next()) {
1520 nsINode
* node
= subtreeIter
.GetCurrentNode();
1522 nsIContent
* content
= node
->IsContent() ? node
->AsContent() : nullptr;
1523 SelectFramesOfInclusiveDescendantsOfContent(postOrderIter
, content
,
1527 // We must now do the last one if it is not the same as the first
1528 if (endNode
!= startNode
) {
1529 nsIContent
* endContent
=
1530 endNode
->IsContent() ? endNode
->AsContent() : nullptr;
1531 // XXX The range can end at a document node and such range can be
1532 // added to Selection with JS. Therefore, even in such cases,
1533 // shouldn't we handle selection in the range?
1534 if (NS_WARN_IF(!endContent
)) {
1535 return NS_ERROR_UNEXPECTED
;
1537 if (endContent
->IsText()) {
1538 nsIFrame
* frame
= endContent
->GetPrimaryFrame();
1539 // The frame could be an SVG text frame, in which case we'll ignore it.
1540 if (frame
&& frame
->IsTextFrame()) {
1541 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(frame
);
1542 textFrame
->SelectionStateChanged(0, aRange
->EndOffset(), aSelect
,
1550 // Selection::LookUpSelection
1552 // This function is called when a node wants to know where the selection is
1555 // Usually, this is called when we already know there is a selection over
1556 // the node in question, and we only need to find the boundaries of it on
1557 // that node. This is when slowCheck is false--a strict test is not needed.
1558 // Other times, the caller has no idea, and wants us to test everything,
1559 // so we are supposed to determine whether there is a selection over the
1562 // A previous version of this code used this flag to do less work when
1563 // inclusion was already known (slowCheck=false). However, our tree
1564 // structure allows us to quickly determine ranges overlapping the node,
1565 // so we just ignore the slowCheck flag and do the full test every time.
1567 // PERFORMANCE: a common case is that we are doing a fast check with exactly
1568 // one range in the selection. In this case, this function is slower than
1569 // brute force because of the overhead of checking the tree. We can optimize
1570 // this case to make it faster by doing the same thing the previous version
1571 // of this function did in the case of 1 range. This would also mean that
1572 // the aSlowCheck flag would have meaning again.
1574 UniquePtr
<SelectionDetails
> Selection::LookUpSelection(
1575 nsIContent
* aContent
, uint32_t aContentOffset
, uint32_t aContentLength
,
1576 UniquePtr
<SelectionDetails
> aDetailsHead
, SelectionType aSelectionType
,
1579 return aDetailsHead
;
1582 // it is common to have no ranges, to optimize that
1583 if (mStyledRanges
.Length() == 0) {
1584 return aDetailsHead
;
1587 nsTArray
<nsRange
*> overlappingRanges
;
1588 nsresult rv
= GetRangesForIntervalArray(aContent
, aContentOffset
, aContent
,
1589 aContentOffset
+ aContentLength
,
1590 false, &overlappingRanges
);
1591 if (NS_FAILED(rv
)) {
1592 return aDetailsHead
;
1595 if (overlappingRanges
.Length() == 0) {
1596 return aDetailsHead
;
1599 UniquePtr
<SelectionDetails
> detailsHead
= std::move(aDetailsHead
);
1601 for (size_t i
= 0; i
< overlappingRanges
.Length(); i
++) {
1602 nsRange
* range
= overlappingRanges
[i
];
1603 nsINode
* startNode
= range
->GetStartContainer();
1604 nsINode
* endNode
= range
->GetEndContainer();
1605 uint32_t startOffset
= range
->StartOffset();
1606 uint32_t endOffset
= range
->EndOffset();
1608 Maybe
<uint32_t> start
, end
;
1609 if (startNode
== aContent
&& endNode
== aContent
) {
1610 if (startOffset
< (aContentOffset
+ aContentLength
) &&
1611 endOffset
> aContentOffset
) {
1612 // this range is totally inside the requested content range
1614 startOffset
>= aContentOffset
? startOffset
- aContentOffset
: 0u);
1615 end
.emplace(std::min(aContentLength
, endOffset
- aContentOffset
));
1617 // otherwise, range is inside the requested node, but does not intersect
1618 // the requested content range, so ignore it
1619 } else if (startNode
== aContent
) {
1620 if (startOffset
< (aContentOffset
+ aContentLength
)) {
1621 // the beginning of the range is inside the requested node, but the
1622 // end is outside, select everything from there to the end
1624 startOffset
>= aContentOffset
? startOffset
- aContentOffset
: 0u);
1625 end
.emplace(aContentLength
);
1627 } else if (endNode
== aContent
) {
1628 if (endOffset
> aContentOffset
) {
1629 // the end of the range is inside the requested node, but the beginning
1630 // is outside, select everything from the beginning to there
1632 end
.emplace(std::min(aContentLength
, endOffset
- aContentOffset
));
1635 // this range does not begin or end in the requested node, but since
1636 // GetRangesForInterval returned this range, we know it overlaps.
1637 // Therefore, this node is enclosed in the range, and we select all
1640 end
.emplace(aContentLength
);
1642 if (start
.isNothing()) {
1643 continue; // the ranges do not overlap the input range
1646 auto newHead
= MakeUnique
<SelectionDetails
>();
1648 newHead
->mNext
= std::move(detailsHead
);
1649 newHead
->mStart
= AssertedCast
<int32_t>(*start
);
1650 newHead
->mEnd
= AssertedCast
<int32_t>(*end
);
1651 newHead
->mSelectionType
= aSelectionType
;
1652 StyledRange
* rd
= mStyledRanges
.FindRangeData(range
);
1654 newHead
->mTextRangeStyle
= rd
->mTextRangeStyle
;
1656 detailsHead
= std::move(newHead
);
1662 Selection::Repaint(nsPresContext
* aPresContext
) {
1663 int32_t arrCount
= (int32_t)mStyledRanges
.Length();
1665 if (arrCount
< 1) return NS_OK
;
1669 for (i
= 0; i
< arrCount
; i
++) {
1671 SelectFrames(aPresContext
, mStyledRanges
.mRanges
[i
].mRange
, true);
1673 if (NS_FAILED(rv
)) {
1681 void Selection::SetCanCacheFrameOffset(bool aCanCacheFrameOffset
) {
1682 if (!mCachedOffsetForFrame
) {
1683 mCachedOffsetForFrame
= new CachedOffsetForFrame
;
1686 mCachedOffsetForFrame
->mCanCacheFrameOffset
= aCanCacheFrameOffset
;
1688 // clean up cached frame when turn off cache
1690 if (!aCanCacheFrameOffset
) {
1691 mCachedOffsetForFrame
->mLastCaretFrame
= nullptr;
1695 nsresult
Selection::GetCachedFrameOffset(nsIFrame
* aFrame
, int32_t inOffset
,
1697 if (!mCachedOffsetForFrame
) {
1698 mCachedOffsetForFrame
= new CachedOffsetForFrame
;
1701 nsresult rv
= NS_OK
;
1702 if (mCachedOffsetForFrame
->mCanCacheFrameOffset
&&
1703 mCachedOffsetForFrame
->mLastCaretFrame
&&
1704 (aFrame
== mCachedOffsetForFrame
->mLastCaretFrame
) &&
1705 (inOffset
== mCachedOffsetForFrame
->mLastContentOffset
)) {
1706 // get cached frame offset
1707 aPoint
= mCachedOffsetForFrame
->mCachedFrameOffset
;
1709 // Recalculate frame offset and cache it. Don't cache a frame offset if
1710 // GetPointFromOffset fails, though.
1711 rv
= aFrame
->GetPointFromOffset(inOffset
, &aPoint
);
1712 if (NS_SUCCEEDED(rv
) && mCachedOffsetForFrame
->mCanCacheFrameOffset
) {
1713 mCachedOffsetForFrame
->mCachedFrameOffset
= aPoint
;
1714 mCachedOffsetForFrame
->mLastCaretFrame
= aFrame
;
1715 mCachedOffsetForFrame
->mLastContentOffset
= inOffset
;
1722 nsIContent
* Selection::GetAncestorLimiter() const {
1723 MOZ_ASSERT(mSelectionType
== SelectionType::eNormal
);
1725 if (mFrameSelection
) {
1726 return mFrameSelection
->GetAncestorLimiter();
1731 void Selection::SetAncestorLimiter(nsIContent
* aLimiter
) {
1732 MOZ_ASSERT(mSelectionType
== SelectionType::eNormal
);
1734 if (mFrameSelection
) {
1735 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
1736 frameSelection
->SetAncestorLimiter(aLimiter
);
1740 void Selection::StyledRanges::UnregisterSelection() {
1741 uint32_t count
= mRanges
.Length();
1742 for (uint32_t i
= 0; i
< count
; ++i
) {
1743 mRanges
[i
].mRange
->UnregisterSelection();
1747 void Selection::StyledRanges::Clear() { mRanges
.Clear(); }
1749 StyledRange
* Selection::StyledRanges::FindRangeData(nsRange
* aRange
) {
1750 NS_ENSURE_TRUE(aRange
, nullptr);
1751 for (uint32_t i
= 0; i
< mRanges
.Length(); i
++) {
1752 if (mRanges
[i
].mRange
== aRange
) {
1759 Selection::StyledRanges::Elements::size_type
Selection::StyledRanges::Length()
1761 return mRanges
.Length();
1764 nsresult
Selection::SetTextRangeStyle(nsRange
* aRange
,
1765 const TextRangeStyle
& aTextRangeStyle
) {
1766 NS_ENSURE_ARG_POINTER(aRange
);
1767 StyledRange
* rd
= mStyledRanges
.FindRangeData(aRange
);
1769 rd
->mTextRangeStyle
= aTextRangeStyle
;
1774 nsresult
Selection::StartAutoScrollTimer(nsIFrame
* aFrame
,
1775 const nsPoint
& aPoint
,
1776 uint32_t aDelayInMs
) {
1777 MOZ_ASSERT(aFrame
, "Need a frame");
1778 MOZ_ASSERT(mSelectionType
== SelectionType::eNormal
);
1780 if (!mFrameSelection
) {
1781 return NS_OK
; // nothing to do
1784 if (!mAutoScroller
) {
1785 mAutoScroller
= new AutoScroller(mFrameSelection
);
1788 mAutoScroller
->SetDelay(aDelayInMs
);
1790 RefPtr
<AutoScroller
> autoScroller
{mAutoScroller
};
1791 return autoScroller
->DoAutoScroll(aFrame
, aPoint
);
1794 nsresult
Selection::StopAutoScrollTimer() {
1795 MOZ_ASSERT(mSelectionType
== SelectionType::eNormal
);
1797 if (mAutoScroller
) {
1798 mAutoScroller
->Stop(AutoScroller::FurtherScrollingAllowed::kYes
);
1804 nsresult
AutoScroller::DoAutoScroll(nsIFrame
* aFrame
, nsPoint aPoint
) {
1805 MOZ_ASSERT(aFrame
, "Need a frame");
1807 Stop(FurtherScrollingAllowed::kYes
);
1809 nsPresContext
* presContext
= aFrame
->PresContext();
1810 RefPtr
<PresShell
> presShell
= presContext
->PresShell();
1811 nsRootPresContext
* rootPC
= presContext
->GetRootPresContext();
1815 nsIFrame
* rootmostFrame
= rootPC
->PresShell()->GetRootFrame();
1816 AutoWeakFrame
weakRootFrame(rootmostFrame
);
1817 AutoWeakFrame
weakFrame(aFrame
);
1818 // Get the point relative to the root most frame because the scroll we are
1819 // about to do will change the coordinates of aFrame.
1820 nsPoint globalPoint
= aPoint
+ aFrame
->GetOffsetToCrossDoc(rootmostFrame
);
1825 didScroll
= presShell
->ScrollFrameRectIntoView(
1826 aFrame
, nsRect(aPoint
, nsSize(0, 0)), nsMargin(), ScrollAxis(),
1827 ScrollAxis(), ScrollFlags::None
);
1828 if (!weakFrame
|| !weakRootFrame
) {
1831 if (!didScroll
&& !done
) {
1832 // If aPoint is at the very edge of the root, then try to scroll anyway,
1834 nsRect rootRect
= rootmostFrame
->GetRect();
1835 nscoord onePx
= AppUnitsPerCSSPixel();
1836 nscoord scrollAmount
= 10 * onePx
;
1837 if (std::abs(rootRect
.x
- globalPoint
.x
) <= onePx
) {
1838 aPoint
.x
-= scrollAmount
;
1839 } else if (std::abs(rootRect
.XMost() - globalPoint
.x
) <= onePx
) {
1840 aPoint
.x
+= scrollAmount
;
1841 } else if (std::abs(rootRect
.y
- globalPoint
.y
) <= onePx
) {
1842 aPoint
.y
-= scrollAmount
;
1843 } else if (std::abs(rootRect
.YMost() - globalPoint
.y
) <= onePx
) {
1844 aPoint
.y
+= scrollAmount
;
1854 // Start the AutoScroll timer if necessary.
1855 // `ScrollFrameRectIntoView` above may have run script and this may have
1856 // forbidden to continue scrolling.
1857 if (didScroll
&& mFurtherScrollingAllowed
== FurtherScrollingAllowed::kYes
) {
1858 nsPoint presContextPoint
=
1860 presShell
->GetRootFrame()->GetOffsetToCrossDoc(rootmostFrame
);
1861 ScheduleNextDoAutoScroll(presContext
, presContextPoint
);
1867 void Selection::RemoveAllRanges(ErrorResult
& aRv
) {
1868 if (!mFrameSelection
) {
1869 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
);
1873 RefPtr
<nsPresContext
> presContext
= GetPresContext();
1876 // Turn off signal for table selection
1877 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
1878 frameSelection
->ClearTableCellSelection();
1880 RefPtr
<Selection
> kungFuDeathGrip
{this};
1881 // Be aware, this instance may be destroyed after this call.
1882 NotifySelectionListeners();
1885 void Selection::AddRangeJS(nsRange
& aRange
, ErrorResult
& aRv
) {
1886 AutoRestore
<bool> calledFromJSRestorer(mCalledByJS
);
1888 AddRangeAndSelectFramesAndNotifyListeners(aRange
, aRv
);
1891 void Selection::AddRangeAndSelectFramesAndNotifyListeners(nsRange
& aRange
,
1893 RefPtr
<Document
> document(GetDocument());
1894 return AddRangeAndSelectFramesAndNotifyListeners(aRange
, document
, aRv
);
1897 void Selection::AddRangeAndSelectFramesAndNotifyListeners(nsRange
& aRange
,
1898 Document
* aDocument
,
1900 // If the given range is part of another Selection, we need to clone the
1902 RefPtr
<nsRange
> range
;
1903 if (aRange
.IsInSelection() && aRange
.GetSelection() != this) {
1904 // Because of performance reason, when there is a cached range, let's use
1905 // it. Otherwise, clone the range.
1906 range
= aRange
.CloneRange();
1911 nsINode
* rangeRoot
= range
->GetRoot();
1912 if (aDocument
!= rangeRoot
&&
1913 (!rangeRoot
|| aDocument
!= rangeRoot
->GetComposedDoc())) {
1914 // http://w3c.github.io/selection-api/#dom-selection-addrange
1915 // "... if the root of the range's boundary points are the document
1916 // associated with context object. Otherwise, this method must do nothing."
1920 // MaybeAddTableCellRange might flush frame and `NotifySelectionListeners`
1921 // below might destruct `this`.
1922 RefPtr
<Selection
> kungFuDeathGrip(this);
1924 // This inserts a table cell range in proper document order
1925 // and returns NS_OK if range doesn't contain just one table cell
1926 Maybe
<size_t> maybeRangeIndex
;
1927 nsresult result
= MaybeAddTableCellRange(*range
, &maybeRangeIndex
);
1928 if (NS_FAILED(result
)) {
1933 if (maybeRangeIndex
.isNothing()) {
1934 result
= AddRangesForSelectableNodes(range
, &maybeRangeIndex
,
1935 DispatchSelectstartEvent::Maybe
);
1936 if (NS_FAILED(result
)) {
1940 if (maybeRangeIndex
.isNothing()) {
1945 MOZ_ASSERT(*maybeRangeIndex
< mStyledRanges
.Length());
1947 SetAnchorFocusRange(*maybeRangeIndex
);
1949 // Make sure the caret appears on the next line, if at a newline
1950 if (mSelectionType
== SelectionType::eNormal
) {
1951 SetInterlinePosition(true, IgnoreErrors());
1954 if (!mFrameSelection
) {
1955 return; // nothing to do
1958 RefPtr
<nsPresContext
> presContext
= GetPresContext();
1959 SelectFrames(presContext
, range
, true);
1961 // Be aware, this instance may be destroyed after this call.
1962 NotifySelectionListeners();
1965 // Selection::RemoveRangeAndUnselectFramesAndNotifyListeners
1967 // Removes the given range from the selection. The tricky part is updating
1968 // the flags on the frames that indicate whether they have a selection or
1969 // not. There could be several selection ranges on the frame, and clearing
1970 // the bit would cause the selection to not be drawn, even when there is
1971 // another range on the frame (bug 346185).
1973 // We therefore find any ranges that intersect the same nodes as the range
1974 // being removed, and cause them to set the selected bits back on their
1975 // selected frames after we've cleared the bit from ours.
1977 void Selection::RemoveRangeAndUnselectFramesAndNotifyListeners(
1978 nsRange
& aRange
, ErrorResult
& aRv
) {
1979 nsresult rv
= mStyledRanges
.RemoveRangeAndUnregisterSelection(aRange
);
1980 if (NS_FAILED(rv
)) {
1985 nsINode
* beginNode
= aRange
.GetStartContainer();
1986 nsINode
* endNode
= aRange
.GetEndContainer();
1988 if (!beginNode
|| !endNode
) {
1989 // Detached range; nothing else to do here.
1993 // find out the length of the end node, so we can select all of it
1994 uint32_t beginOffset
, endOffset
;
1995 if (endNode
->IsText()) {
1996 // Get the length of the text. We can't just use the offset because
1997 // another range could be touching this text node but not intersect our
2000 endOffset
= endNode
->AsText()->TextLength();
2002 // For non-text nodes, the given offsets should be sufficient.
2003 beginOffset
= aRange
.StartOffset();
2004 endOffset
= aRange
.EndOffset();
2007 // clear the selected bit from the removed range's frames
2008 RefPtr
<nsPresContext
> presContext
= GetPresContext();
2009 SelectFrames(presContext
, &aRange
, false);
2011 // add back the selected bit for each range touching our nodes
2012 nsTArray
<nsRange
*> affectedRanges
;
2013 rv
= GetRangesForIntervalArray(beginNode
, beginOffset
, endNode
, endOffset
,
2014 true, &affectedRanges
);
2015 if (NS_FAILED(rv
)) {
2019 for (uint32_t i
= 0; i
< affectedRanges
.Length(); i
++) {
2020 SelectFrames(presContext
, affectedRanges
[i
], true);
2023 if (&aRange
== mAnchorFocusRange
) {
2024 const size_t rangeCount
= mStyledRanges
.Length();
2026 SetAnchorFocusRange(rangeCount
- 1);
2028 RemoveAnchorFocusRange();
2031 // When the selection is user-created it makes sense to scroll the range
2032 // into view. The spell-check selection, however, is created and destroyed
2033 // in the background. We don't want to scroll in this case or the view
2034 // might appear to be moving randomly (bug 337871).
2035 if (mSelectionType
!= SelectionType::eSpellCheck
&& rangeCount
) {
2036 ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION
);
2040 if (!mFrameSelection
) return; // nothing to do
2042 RefPtr
<Selection
> kungFuDeathGrip
{this};
2043 // Be aware, this instance may be destroyed after this call.
2044 NotifySelectionListeners();
2048 * Collapse sets the whole selection to be one point.
2050 void Selection::CollapseJS(nsINode
* aContainer
, uint32_t aOffset
,
2052 AutoRestore
<bool> calledFromJSRestorer(mCalledByJS
);
2055 RemoveAllRanges(aRv
);
2058 CollapseInternal(InLimiter::eNo
, RawRangeBoundary(aContainer
, aOffset
), aRv
);
2061 void Selection::CollapseInternal(InLimiter aInLimiter
,
2062 const RawRangeBoundary
& aPoint
,
2064 if (!mFrameSelection
) {
2065 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
); // Can't do selection
2069 if (!aPoint
.IsSet()) {
2070 aRv
.Throw(NS_ERROR_INVALID_ARG
);
2074 if (aPoint
.Container()->NodeType() == nsINode::DOCUMENT_TYPE_NODE
) {
2075 aRv
.ThrowInvalidNodeTypeError(kNoDocumentTypeNodeError
);
2079 // RawRangeBoundary::IsSetAndValid() checks if the point actually refers
2080 // a child of the container when IsSet() is true. If its offset hasn't been
2081 // computed yet, this just checks it with its mRef. So, we can avoid
2082 // computing offset here.
2083 if (!aPoint
.IsSetAndValid()) {
2084 aRv
.ThrowIndexSizeError("The offset is out of range.");
2088 if (!HasSameRootOrSameComposedDoc(*aPoint
.Container())) {
2089 // Return with no error
2093 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
2094 frameSelection
->InvalidateDesiredCaretPos();
2095 if (aInLimiter
== InLimiter::eYes
&&
2096 !frameSelection
->IsValidSelectionPoint(aPoint
.Container())) {
2097 aRv
.Throw(NS_ERROR_FAILURE
);
2102 RefPtr
<nsPresContext
> presContext
= GetPresContext();
2104 presContext
->Document() != aPoint
.Container()->OwnerDoc()) {
2105 aRv
.Throw(NS_ERROR_FAILURE
);
2109 // Delete all of the current ranges
2112 // Turn off signal for table selection
2113 frameSelection
->ClearTableCellSelection();
2115 // Hack to display the caret on the right line (bug 1237236).
2116 if (frameSelection
->GetHint() != CARET_ASSOCIATE_AFTER
&&
2117 aPoint
.Container()->IsContent()) {
2118 int32_t frameOffset
;
2119 nsTextFrame
* f
= do_QueryFrame(nsCaret::GetFrameAndOffset(
2120 this, aPoint
.Container(),
2121 *aPoint
.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets
),
2123 if (f
&& f
->IsAtEndOfLine() && f
->HasSignificantTerminalNewline()) {
2124 // RawRangeBounary::Offset() causes computing offset if it's not been
2125 // done yet. However, it's called only when the container is a text
2126 // node. In such case, offset has always been set since it cannot have
2127 // any children. So, this doesn't cause computing offset with expensive
2128 // method, nsINode::ComputeIndexOf().
2129 if ((aPoint
.Container()->AsContent() == f
->GetContent() &&
2130 f
->GetContentEnd() ==
2131 static_cast<int32_t>(*aPoint
.Offset(
2132 RawRangeBoundary::OffsetFilter::kValidOffsets
))) ||
2133 (aPoint
.Container() == f
->GetContent()->GetParentNode() &&
2134 f
->GetContent() == aPoint
.GetPreviousSiblingOfChildAtOffset())) {
2135 frameSelection
->SetHint(CARET_ASSOCIATE_AFTER
);
2140 RefPtr
<nsRange
> range
= nsRange::Create(aPoint
.Container());
2141 result
= range
->CollapseTo(aPoint
);
2142 if (NS_FAILED(result
)) {
2147 #ifdef DEBUG_SELECTION
2148 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aPoint
.Container());
2149 nsCOMPtr
<Document
> doc
= do_QueryInterface(aPoint
.Container());
2150 printf("Sel. Collapse to %p %s %d\n", container
.get(),
2151 content
? nsAtomCString(content
->NodeInfo()->NameAtom()).get()
2152 : (doc
? "DOCUMENT" : "???"),
2156 Maybe
<size_t> maybeRangeIndex
;
2157 result
= AddRangesForSelectableNodes(range
, &maybeRangeIndex
,
2158 DispatchSelectstartEvent::Maybe
);
2159 if (NS_FAILED(result
)) {
2163 SetAnchorFocusRange(0);
2164 SelectFrames(presContext
, range
, true);
2166 RefPtr
<Selection
> kungFuDeathGrip
{this};
2167 // Be aware, this instance may be destroyed after this call.
2168 NotifySelectionListeners();
2172 * Sets the whole selection to be one point
2173 * at the start of the current selection
2175 void Selection::CollapseToStartJS(ErrorResult
& aRv
) {
2176 AutoRestore
<bool> calledFromJSRestorer(mCalledByJS
);
2178 CollapseToStart(aRv
);
2181 void Selection::CollapseToStart(ErrorResult
& aRv
) {
2182 if (RangeCount() == 0) {
2183 aRv
.ThrowInvalidStateError(kNoRangeExistsError
);
2187 // Get the first range
2188 const nsRange
* firstRange
= mStyledRanges
.mRanges
[0].mRange
;
2190 aRv
.Throw(NS_ERROR_FAILURE
);
2194 if (mFrameSelection
) {
2195 mFrameSelection
->AddChangeReasons(
2196 nsISelectionListener::COLLAPSETOSTART_REASON
);
2198 nsINode
* container
= firstRange
->GetStartContainer();
2200 aRv
.Throw(NS_ERROR_FAILURE
);
2203 CollapseInternal(InLimiter::eNo
,
2204 RawRangeBoundary(container
, firstRange
->StartOffset()), aRv
);
2208 * Sets the whole selection to be one point
2209 * at the end of the current selection
2211 void Selection::CollapseToEndJS(ErrorResult
& aRv
) {
2212 AutoRestore
<bool> calledFromJSRestorer(mCalledByJS
);
2217 void Selection::CollapseToEnd(ErrorResult
& aRv
) {
2218 uint32_t cnt
= RangeCount();
2220 aRv
.ThrowInvalidStateError(kNoRangeExistsError
);
2224 // Get the last range
2225 const nsRange
* lastRange
= mStyledRanges
.mRanges
[cnt
- 1].mRange
;
2227 aRv
.Throw(NS_ERROR_FAILURE
);
2231 if (mFrameSelection
) {
2232 mFrameSelection
->AddChangeReasons(
2233 nsISelectionListener::COLLAPSETOEND_REASON
);
2235 nsINode
* container
= lastRange
->GetEndContainer();
2237 aRv
.Throw(NS_ERROR_FAILURE
);
2240 CollapseInternal(InLimiter::eNo
,
2241 RawRangeBoundary(container
, lastRange
->EndOffset()), aRv
);
2244 void Selection::GetType(nsAString
& aOutType
) const {
2245 if (!RangeCount()) {
2246 aOutType
.AssignLiteral("None");
2247 } else if (IsCollapsed()) {
2248 aOutType
.AssignLiteral("Caret");
2250 aOutType
.AssignLiteral("Range");
2254 nsRange
* Selection::GetRangeAt(uint32_t aIndex
, ErrorResult
& aRv
) {
2255 nsRange
* range
= GetRangeAt(aIndex
);
2257 aRv
.ThrowIndexSizeError(nsPrintfCString("%u is out of range", aIndex
));
2264 nsRange
* Selection::GetRangeAt(uint32_t aIndex
) const {
2265 StyledRange
empty(nullptr);
2266 return mStyledRanges
.mRanges
.SafeElementAt(aIndex
, empty
).mRange
;
2269 nsresult
Selection::SetAnchorFocusToRange(nsRange
* aRange
) {
2270 NS_ENSURE_STATE(mAnchorFocusRange
);
2272 const DispatchSelectstartEvent dispatchSelectstartEvent
=
2273 IsCollapsed() ? DispatchSelectstartEvent::Maybe
2274 : DispatchSelectstartEvent::No
;
2277 mStyledRanges
.RemoveRangeAndUnregisterSelection(*mAnchorFocusRange
);
2278 if (NS_FAILED(rv
)) {
2282 Maybe
<size_t> maybeOutIndex
;
2283 rv
= AddRangesForSelectableNodes(aRange
, &maybeOutIndex
,
2284 dispatchSelectstartEvent
);
2285 if (NS_FAILED(rv
)) {
2288 if (maybeOutIndex
.isSome()) {
2289 SetAnchorFocusRange(*maybeOutIndex
);
2291 RemoveAnchorFocusRange();
2297 void Selection::ReplaceAnchorFocusRange(nsRange
* aRange
) {
2298 NS_ENSURE_TRUE_VOID(mAnchorFocusRange
);
2299 RefPtr
<nsPresContext
> presContext
= GetPresContext();
2301 SelectFrames(presContext
, mAnchorFocusRange
, false);
2302 SetAnchorFocusToRange(aRange
);
2303 SelectFrames(presContext
, mAnchorFocusRange
, true);
2307 void Selection::AdjustAnchorFocusForMultiRange(nsDirection aDirection
) {
2308 if (aDirection
== mDirection
) {
2311 SetDirection(aDirection
);
2313 if (RangeCount() <= 1) {
2317 nsRange
* firstRange
= GetRangeAt(0);
2318 nsRange
* lastRange
= GetRangeAt(RangeCount() - 1);
2320 if (mDirection
== eDirPrevious
) {
2321 firstRange
->SetIsGenerated(false);
2322 lastRange
->SetIsGenerated(true);
2323 SetAnchorFocusRange(0);
2324 } else { // aDir == eDirNext
2325 firstRange
->SetIsGenerated(true);
2326 lastRange
->SetIsGenerated(false);
2327 SetAnchorFocusRange(RangeCount() - 1);
2332 * Extend extends the selection away from the anchor.
2333 * We don't need to know the direction, because we always change the focus.
2335 void Selection::ExtendJS(nsINode
& aContainer
, uint32_t aOffset
,
2337 AutoRestore
<bool> calledFromJSRestorer(mCalledByJS
);
2339 Extend(aContainer
, aOffset
, aRv
);
2342 nsresult
Selection::Extend(nsINode
* aContainer
, uint32_t aOffset
) {
2344 return NS_ERROR_INVALID_ARG
;
2348 Extend(*aContainer
, aOffset
, result
);
2349 return result
.StealNSResult();
2352 void Selection::Extend(nsINode
& aContainer
, uint32_t aOffset
,
2355 Notes which might come in handy for extend:
2357 We can tell the direction of the selection by asking for the anchors
2358 selection if the begin is less than the end then we know the selection is to
2359 the "right", else it is a backwards selection. Notation: a = anchor, 1 = old
2360 cursor, 2 = new cursor.
2362 if (a <= 1 && 1 <=2) a,1,2 or (a1,2)
2363 if (a < 2 && 1 > 2) a,2,1
2364 if (1 < a && a <2) 1,a,2
2365 if (a > 2 && 2 >1) 1,2,a
2366 if (2 < a && a <1) 2,a,1
2367 if (a > 1 && 1 >2) 2,1,a
2369 a 1 2 select from 1 to 2
2370 a 2 1 deselect from 2 to 1
2371 1 a 2 deselect from 1 to a select from a to 2
2372 1 2 a deselect from 1 to 2
2373 2 1 a = continue selection from 2 to 1
2376 // First, find the range containing the old focus point:
2377 if (!mAnchorFocusRange
) {
2378 aRv
.ThrowInvalidStateError(kNoRangeExistsError
);
2382 if (!mFrameSelection
) {
2383 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
); // Can't do selection
2387 if (!HasSameRootOrSameComposedDoc(aContainer
)) {
2388 // Return with no error
2393 if (!mFrameSelection
->IsValidSelectionPoint(&aContainer
)) {
2394 aRv
.Throw(NS_ERROR_FAILURE
);
2398 RefPtr
<nsPresContext
> presContext
= GetPresContext();
2399 if (!presContext
|| presContext
->Document() != aContainer
.OwnerDoc()) {
2400 aRv
.Throw(NS_ERROR_FAILURE
);
2404 #ifdef DEBUG_SELECTION
2405 nsDirection oldDirection
= GetDirection();
2407 nsINode
* anchorNode
= GetAnchorNode();
2408 nsINode
* focusNode
= GetFocusNode();
2409 const uint32_t anchorOffset
= AnchorOffset();
2410 const uint32_t focusOffset
= FocusOffset();
2412 RefPtr
<nsRange
> range
= mAnchorFocusRange
->CloneRange();
2414 nsINode
* startNode
= range
->GetStartContainer();
2415 nsINode
* endNode
= range
->GetEndContainer();
2416 const uint32_t startOffset
= range
->StartOffset();
2417 const uint32_t endOffset
= range
->EndOffset();
2419 bool shouldClearRange
= false;
2420 const Maybe
<int32_t> anchorOldFocusOrder
= nsContentUtils::ComparePoints(
2421 anchorNode
, anchorOffset
, focusNode
, focusOffset
);
2422 shouldClearRange
|= !anchorOldFocusOrder
;
2423 const Maybe
<int32_t> oldFocusNewFocusOrder
= nsContentUtils::ComparePoints(
2424 focusNode
, focusOffset
, &aContainer
, aOffset
);
2425 shouldClearRange
|= !oldFocusNewFocusOrder
;
2426 const Maybe
<int32_t> anchorNewFocusOrder
= nsContentUtils::ComparePoints(
2427 anchorNode
, anchorOffset
, &aContainer
, aOffset
);
2428 shouldClearRange
|= !anchorNewFocusOrder
;
2430 // If the points are disconnected, the range will be collapsed below,
2431 // resulting in a range that selects nothing.
2432 if (shouldClearRange
) {
2433 // Repaint the current range with the selection removed.
2434 SelectFrames(presContext
, range
, false);
2436 res
= range
->CollapseTo(&aContainer
, aOffset
);
2437 if (NS_FAILED(res
)) {
2442 res
= SetAnchorFocusToRange(range
);
2443 if (NS_FAILED(res
)) {
2448 RefPtr
<nsRange
> difRange
= nsRange::Create(&aContainer
);
2449 if ((*anchorOldFocusOrder
== 0 && *anchorNewFocusOrder
< 0) ||
2450 (*anchorOldFocusOrder
<= 0 &&
2451 *oldFocusNewFocusOrder
< 0)) { // a1,2 a,1,2
2452 // select from 1 to 2 unless they are collapsed
2453 range
->SetEnd(aContainer
, aOffset
, aRv
);
2457 SetDirection(eDirNext
);
2458 res
= difRange
->SetStartAndEnd(
2459 focusNode
, focusOffset
, range
->GetEndContainer(), range
->EndOffset());
2460 if (NS_FAILED(res
)) {
2464 SelectFrames(presContext
, difRange
, true);
2465 res
= SetAnchorFocusToRange(range
);
2466 if (NS_FAILED(res
)) {
2470 } else if (*anchorOldFocusOrder
== 0 &&
2471 *anchorNewFocusOrder
> 0) { // 2, a1
2472 // select from 2 to 1a
2473 SetDirection(eDirPrevious
);
2474 range
->SetStart(aContainer
, aOffset
, aRv
);
2478 SelectFrames(presContext
, range
, true);
2479 res
= SetAnchorFocusToRange(range
);
2480 if (NS_FAILED(res
)) {
2484 } else if (*anchorNewFocusOrder
<= 0 &&
2485 *oldFocusNewFocusOrder
>= 0) { // a,2,1 or a2,1 or a,21 or a21
2486 // deselect from 2 to 1
2487 res
= difRange
->SetStartAndEnd(&aContainer
, aOffset
, focusNode
,
2489 if (NS_FAILED(res
)) {
2494 range
->SetEnd(aContainer
, aOffset
, aRv
);
2498 res
= SetAnchorFocusToRange(range
);
2499 if (NS_FAILED(res
)) {
2503 SelectFrames(presContext
, difRange
, false); // deselect now
2504 difRange
->SetEnd(range
->GetEndContainer(), range
->EndOffset());
2505 SelectFrames(presContext
, difRange
, true); // must reselect last node
2507 } else if (*anchorOldFocusOrder
>= 0 &&
2508 *anchorNewFocusOrder
<= 0) { // 1,a,2 or 1a,2 or 1,a2 or 1a2
2509 if (GetDirection() == eDirPrevious
) {
2510 res
= range
->SetStart(endNode
, endOffset
);
2511 if (NS_FAILED(res
)) {
2516 SetDirection(eDirNext
);
2517 range
->SetEnd(aContainer
, aOffset
, aRv
);
2521 if (focusNode
!= anchorNode
||
2522 focusOffset
!= anchorOffset
) { // if collapsed diff dont do anything
2523 res
= difRange
->SetStart(focusNode
, focusOffset
);
2524 nsresult tmp
= difRange
->SetEnd(anchorNode
, anchorOffset
);
2525 if (NS_FAILED(tmp
)) {
2528 if (NS_FAILED(res
)) {
2532 res
= SetAnchorFocusToRange(range
);
2533 if (NS_FAILED(res
)) {
2537 // deselect from 1 to a
2538 SelectFrames(presContext
, difRange
, false);
2540 res
= SetAnchorFocusToRange(range
);
2541 if (NS_FAILED(res
)) {
2546 // select from a to 2
2547 SelectFrames(presContext
, range
, true);
2548 } else if (*oldFocusNewFocusOrder
<= 0 &&
2549 *anchorNewFocusOrder
>= 0) { // 1,2,a or 12,a or 1,2a or 12a
2550 // deselect from 1 to 2
2551 res
= difRange
->SetStartAndEnd(focusNode
, focusOffset
, &aContainer
,
2553 if (NS_FAILED(res
)) {
2557 SetDirection(eDirPrevious
);
2558 range
->SetStart(aContainer
, aOffset
, aRv
);
2563 res
= SetAnchorFocusToRange(range
);
2564 if (NS_FAILED(res
)) {
2568 SelectFrames(presContext
, difRange
, false);
2569 difRange
->SetStart(range
->GetStartContainer(), range
->StartOffset());
2570 SelectFrames(presContext
, difRange
, true); // must reselect last node
2571 } else if (*anchorNewFocusOrder
>= 0 &&
2572 *anchorOldFocusOrder
<= 0) { // 2,a,1 or 2a,1 or 2,a1 or 2a1
2573 if (GetDirection() == eDirNext
) {
2574 range
->SetEnd(startNode
, startOffset
);
2576 SetDirection(eDirPrevious
);
2577 range
->SetStart(aContainer
, aOffset
, aRv
);
2581 // deselect from a to 1
2582 if (focusNode
!= anchorNode
||
2583 focusOffset
!= anchorOffset
) { // if collapsed diff dont do anything
2584 res
= difRange
->SetStartAndEnd(anchorNode
, anchorOffset
, focusNode
,
2586 nsresult tmp
= SetAnchorFocusToRange(range
);
2587 if (NS_FAILED(tmp
)) {
2590 if (NS_FAILED(res
)) {
2594 SelectFrames(presContext
, difRange
, false);
2596 res
= SetAnchorFocusToRange(range
);
2597 if (NS_FAILED(res
)) {
2602 // select from 2 to a
2603 SelectFrames(presContext
, range
, true);
2604 } else if (*oldFocusNewFocusOrder
>= 0 &&
2605 *anchorOldFocusOrder
>= 0) { // 2,1,a or 21,a or 2,1a or 21a
2606 // select from 2 to 1
2607 range
->SetStart(aContainer
, aOffset
, aRv
);
2611 SetDirection(eDirPrevious
);
2612 res
= difRange
->SetStartAndEnd(range
->GetStartContainer(),
2613 range
->StartOffset(), focusNode
,
2615 if (NS_FAILED(res
)) {
2620 SelectFrames(presContext
, difRange
, true);
2621 res
= SetAnchorFocusToRange(range
);
2622 if (NS_FAILED(res
)) {
2629 if (mStyledRanges
.Length() > 1) {
2630 SelectFramesInAllRanges(presContext
);
2633 DEBUG_OUT_RANGE(range
);
2634 #ifdef DEBUG_SELECTION
2635 if (GetDirection() != oldDirection
) {
2636 printf(" direction changed to %s\n",
2637 GetDirection() == eDirNext
? "eDirNext" : "eDirPrevious");
2639 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(&aContainer
);
2640 printf("Sel. Extend to %p %s %d\n", content
.get(),
2641 nsAtomCString(content
->NodeInfo()->NameAtom()).get(), aOffset
);
2644 RefPtr
<Selection
> kungFuDeathGrip
{this};
2645 // Be aware, this instance may be destroyed after this call.
2646 NotifySelectionListeners();
2649 void Selection::SelectAllChildrenJS(nsINode
& aNode
, ErrorResult
& aRv
) {
2650 AutoRestore
<bool> calledFromJSRestorer(mCalledByJS
);
2652 SelectAllChildren(aNode
, aRv
);
2655 void Selection::SelectAllChildren(nsINode
& aNode
, ErrorResult
& aRv
) {
2656 if (aNode
.NodeType() == nsINode::DOCUMENT_TYPE_NODE
) {
2657 aRv
.ThrowInvalidNodeTypeError(kNoDocumentTypeNodeError
);
2661 if (!HasSameRootOrSameComposedDoc(aNode
)) {
2662 // Return with no error
2666 if (mFrameSelection
) {
2667 mFrameSelection
->AddChangeReasons(nsISelectionListener::SELECTALL_REASON
);
2670 // Chrome moves focus when aNode is outside of active editing host.
2671 // So, we don't need to respect the limiter with this method.
2672 SetStartAndEndInternal(InLimiter::eNo
, RawRangeBoundary(&aNode
, 0u),
2673 RawRangeBoundary(&aNode
, aNode
.GetChildCount()),
2677 bool Selection::ContainsNode(nsINode
& aNode
, bool aAllowPartial
,
2680 if (mStyledRanges
.Length() == 0) {
2684 // XXXbz this duplicates the GetNodeLength code in nsRange.cpp
2685 uint32_t nodeLength
;
2686 auto* nodeAsCharData
= CharacterData::FromNode(aNode
);
2687 if (nodeAsCharData
) {
2688 nodeLength
= nodeAsCharData
->TextLength();
2690 nodeLength
= aNode
.GetChildCount();
2693 nsTArray
<nsRange
*> overlappingRanges
;
2694 rv
= GetRangesForIntervalArray(&aNode
, 0, &aNode
, nodeLength
, false,
2695 &overlappingRanges
);
2696 if (NS_FAILED(rv
)) {
2700 if (overlappingRanges
.Length() == 0) return false; // no ranges overlap
2702 // if the caller said partial intersections are OK, we're done
2703 if (aAllowPartial
) {
2707 // text nodes always count as inside
2708 if (nodeAsCharData
) {
2712 // The caller wants to know if the node is entirely within the given range,
2713 // so we have to check all intersecting ranges.
2714 for (uint32_t i
= 0; i
< overlappingRanges
.Length(); i
++) {
2715 bool nodeStartsBeforeRange
, nodeEndsAfterRange
;
2716 if (NS_SUCCEEDED(RangeUtils::CompareNodeToRange(
2717 &aNode
, overlappingRanges
[i
], &nodeStartsBeforeRange
,
2718 &nodeEndsAfterRange
))) {
2719 if (!nodeStartsBeforeRange
&& !nodeEndsAfterRange
) {
2727 class PointInRectChecker
: public mozilla::RectCallback
{
2729 explicit PointInRectChecker(const nsPoint
& aPoint
)
2730 : mPoint(aPoint
), mMatchFound(false) {}
2732 void AddRect(const nsRect
& aRect
) override
{
2733 mMatchFound
= mMatchFound
|| aRect
.Contains(mPoint
);
2736 bool MatchFound() { return mMatchFound
; }
2743 bool Selection::ContainsPoint(const nsPoint
& aPoint
) {
2744 if (IsCollapsed()) {
2747 PointInRectChecker
checker(aPoint
);
2748 const uint32_t rangeCount
= RangeCount();
2749 for (const uint32_t i
: IntegerRange(rangeCount
)) {
2750 MOZ_ASSERT(RangeCount() == rangeCount
);
2751 nsRange
* range
= GetRangeAt(i
);
2753 nsRange::CollectClientRectsAndText(
2754 &checker
, nullptr, range
, range
->GetStartContainer(),
2755 range
->StartOffset(), range
->GetEndContainer(), range
->EndOffset(),
2757 if (checker
.MatchFound()) {
2764 void Selection::MaybeNotifyAccessibleCaretEventHub(PresShell
* aPresShell
) {
2765 MOZ_ASSERT(mSelectionType
== SelectionType::eNormal
);
2767 if (!mAccessibleCaretEventHub
&& aPresShell
) {
2768 mAccessibleCaretEventHub
= aPresShell
->GetAccessibleCaretEventHub();
2772 void Selection::StopNotifyingAccessibleCaretEventHub() {
2773 MOZ_ASSERT(mSelectionType
== SelectionType::eNormal
);
2775 mAccessibleCaretEventHub
= nullptr;
2778 nsPresContext
* Selection::GetPresContext() const {
2779 PresShell
* presShell
= GetPresShell();
2780 return presShell
? presShell
->GetPresContext() : nullptr;
2783 PresShell
* Selection::GetPresShell() const {
2784 if (!mFrameSelection
) {
2785 return nullptr; // nothing to do
2787 return mFrameSelection
->GetPresShell();
2790 Document
* Selection::GetDocument() const {
2791 PresShell
* presShell
= GetPresShell();
2792 return presShell
? presShell
->GetDocument() : nullptr;
2795 nsIFrame
* Selection::GetSelectionAnchorGeometry(SelectionRegion aRegion
,
2797 if (!mFrameSelection
) return nullptr; // nothing to do
2799 NS_ENSURE_TRUE(aRect
, nullptr);
2801 aRect
->SetRect(0, 0, 0, 0);
2804 case nsISelectionController::SELECTION_ANCHOR_REGION
:
2805 case nsISelectionController::SELECTION_FOCUS_REGION
:
2806 return GetSelectionEndPointGeometry(aRegion
, aRect
);
2807 case nsISelectionController::SELECTION_WHOLE_SELECTION
:
2813 NS_ASSERTION(aRegion
== nsISelectionController::SELECTION_WHOLE_SELECTION
,
2814 "should only be SELECTION_WHOLE_SELECTION here");
2817 nsIFrame
* anchorFrame
= GetSelectionEndPointGeometry(
2818 nsISelectionController::SELECTION_ANCHOR_REGION
, &anchorRect
);
2819 if (!anchorFrame
) return nullptr;
2822 nsIFrame
* focusFrame
= GetSelectionEndPointGeometry(
2823 nsISelectionController::SELECTION_FOCUS_REGION
, &focusRect
);
2824 if (!focusFrame
) return nullptr;
2826 NS_ASSERTION(anchorFrame
->PresContext() == focusFrame
->PresContext(),
2827 "points of selection in different documents?");
2828 // make focusRect relative to anchorFrame
2829 focusRect
+= focusFrame
->GetOffsetTo(anchorFrame
);
2831 *aRect
= anchorRect
.UnionEdges(focusRect
);
2835 nsIFrame
* Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion
,
2837 if (!mFrameSelection
) return nullptr; // nothing to do
2839 NS_ENSURE_TRUE(aRect
, nullptr);
2841 aRect
->SetRect(0, 0, 0, 0);
2843 nsINode
* node
= nullptr;
2844 uint32_t nodeOffset
= 0;
2845 nsIFrame
* frame
= nullptr;
2848 case nsISelectionController::SELECTION_ANCHOR_REGION
:
2849 node
= GetAnchorNode();
2850 nodeOffset
= AnchorOffset();
2852 case nsISelectionController::SELECTION_FOCUS_REGION
:
2853 node
= GetFocusNode();
2854 nodeOffset
= FocusOffset();
2860 if (!node
) return nullptr;
2862 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(node
);
2863 NS_ENSURE_TRUE(content
.get(), nullptr);
2864 int32_t frameOffset
= 0;
2865 frame
= nsFrameSelection::GetFrameForNodeOffset(
2866 content
, nodeOffset
, mFrameSelection
->GetHint(), &frameOffset
);
2867 if (!frame
) return nullptr;
2869 nsFrameSelection::AdjustFrameForLineStart(frame
, frameOffset
);
2871 // Figure out what node type we have, then get the
2872 // appropriate rect for it's nodeOffset.
2873 bool isText
= node
->IsText();
2877 nsIFrame
* childFrame
= nullptr;
2879 nsresult rv
= frame
->GetChildFrameContainingOffset(
2880 nodeOffset
, mFrameSelection
->GetHint(), &frameOffset
, &childFrame
);
2881 if (NS_FAILED(rv
)) return nullptr;
2882 if (!childFrame
) return nullptr;
2886 // Get the x coordinate of the offset into the text frame.
2887 rv
= GetCachedFrameOffset(frame
, nodeOffset
, pt
);
2888 if (NS_FAILED(rv
)) return nullptr;
2891 // Return the rect relative to the frame, with zero width.
2894 } else if (mFrameSelection
->GetHint() == CARET_ASSOCIATE_BEFORE
) {
2895 // It's the frame's right edge we're interested in.
2896 aRect
->x
= frame
->GetRect().Width();
2898 aRect
->SetHeight(frame
->GetRect().Height());
2904 Selection::ScrollSelectionIntoViewEvent::Run() {
2905 if (!mSelection
) return NS_OK
; // event revoked
2907 int32_t flags
= Selection::SCROLL_DO_FLUSH
| Selection::SCROLL_SYNCHRONOUS
;
2909 const RefPtr
<Selection
> selection
{mSelection
};
2910 selection
->mScrollEvent
.Forget();
2911 selection
->ScrollIntoView(mRegion
, mVerticalScroll
, mHorizontalScroll
,
2916 nsresult
Selection::PostScrollSelectionIntoViewEvent(SelectionRegion aRegion
,
2918 ScrollAxis aVertical
,
2919 ScrollAxis aHorizontal
) {
2920 // If we've already posted an event, revoke it and place a new one at the
2921 // end of the queue to make sure that any new pending reflow events are
2922 // processed before we scroll. This will insure that we scroll to the
2923 // correct place on screen.
2924 mScrollEvent
.Revoke();
2925 nsPresContext
* presContext
= GetPresContext();
2926 NS_ENSURE_STATE(presContext
);
2927 nsRefreshDriver
* refreshDriver
= presContext
->RefreshDriver();
2928 NS_ENSURE_STATE(refreshDriver
);
2930 mScrollEvent
= new ScrollSelectionIntoViewEvent(this, aRegion
, aVertical
,
2931 aHorizontal
, aFlags
);
2932 refreshDriver
->AddEarlyRunner(mScrollEvent
.get());
2936 void Selection::ScrollIntoView(int16_t aRegion
, bool aIsSynchronous
,
2937 WhereToScroll aVPercent
, WhereToScroll aHPercent
,
2939 int32_t flags
= aIsSynchronous
? Selection::SCROLL_SYNCHRONOUS
: 0;
2940 nsresult rv
= ScrollIntoView(aRegion
, ScrollAxis(aVPercent
),
2941 ScrollAxis(aHPercent
), flags
);
2942 if (NS_FAILED(rv
)) {
2947 nsresult
Selection::ScrollIntoView(SelectionRegion aRegion
,
2948 ScrollAxis aVertical
, ScrollAxis aHorizontal
,
2950 if (!mFrameSelection
) {
2951 return NS_ERROR_NOT_INITIALIZED
;
2954 RefPtr
<PresShell
> presShell
= mFrameSelection
->GetPresShell();
2955 if (!presShell
|| !presShell
->GetDocument()) {
2959 if (mFrameSelection
->IsBatching()) {
2963 if (!(aFlags
& Selection::SCROLL_SYNCHRONOUS
))
2964 return PostScrollSelectionIntoViewEvent(aRegion
, aFlags
, aVertical
,
2967 // From this point on, the presShell may get destroyed by the calls below, so
2968 // hold on to it using a strong reference to ensure the safety of the
2969 // accesses to frame pointers in the callees.
2970 RefPtr
<PresShell
> kungFuDeathGrip(presShell
);
2972 // Now that text frame character offsets are always valid (though not
2973 // necessarily correct), the worst that will happen if we don't flush here
2974 // is that some callers might scroll to the wrong place. Those should
2975 // either manually flush if they're in a safe position for it or use the
2976 // async version of this method.
2977 if (aFlags
& Selection::SCROLL_DO_FLUSH
) {
2978 presShell
->GetDocument()->FlushPendingNotifications(FlushType::Layout
);
2980 // Reget the presshell, since it might have been Destroy'ed.
2981 presShell
= mFrameSelection
? mFrameSelection
->GetPresShell() : nullptr;
2988 // Scroll the selection region into view.
2992 nsIFrame
* frame
= GetSelectionAnchorGeometry(aRegion
, &rect
);
2993 if (!frame
) return NS_ERROR_FAILURE
;
2995 // Scroll vertically to get the caret into view, but only if the container
2996 // is perceived to be scrollable in that direction (i.e. there is a visible
2997 // vertical scrollbar or the scroll range is at least one device pixel)
2998 aVertical
.mOnlyIfPerceivedScrollableDirection
= true;
3000 auto scrollFlags
= ScrollFlags::None
;
3001 if (aFlags
& Selection::SCROLL_FIRST_ANCESTOR_ONLY
) {
3002 scrollFlags
|= ScrollFlags::ScrollFirstAncestorOnly
;
3004 if (aFlags
& Selection::SCROLL_OVERFLOW_HIDDEN
) {
3005 scrollFlags
|= ScrollFlags::ScrollOverflowHidden
;
3008 presShell
->ScrollFrameRectIntoView(frame
, rect
, nsMargin(), aVertical
,
3009 aHorizontal
, scrollFlags
);
3013 void Selection::AddSelectionListener(nsISelectionListener
* aNewListener
) {
3014 MOZ_ASSERT(aNewListener
);
3015 mSelectionListeners
.AppendElement(aNewListener
); // AddRefs
3018 void Selection::RemoveSelectionListener(
3019 nsISelectionListener
* aListenerToRemove
) {
3020 mSelectionListeners
.RemoveElement(aListenerToRemove
); // Releases
3023 Element
* Selection::StyledRanges::GetCommonEditingHost() const {
3024 Element
* editingHost
= nullptr;
3025 for (const StyledRange
& rangeData
: mRanges
) {
3026 const nsRange
* range
= rangeData
.mRange
;
3028 nsINode
* commonAncestorNode
= range
->GetClosestCommonInclusiveAncestor();
3029 if (!commonAncestorNode
|| !commonAncestorNode
->IsContent()) {
3032 nsIContent
* commonAncestor
= commonAncestorNode
->AsContent();
3033 Element
* foundEditingHost
= commonAncestor
->GetEditingHost();
3034 // Even when common ancestor is a non-editable element in a contenteditable
3035 // element, we don't need to move focus to the contenteditable element
3036 // because Chromium doesn't set focus to it.
3037 if (!foundEditingHost
) {
3041 editingHost
= foundEditingHost
;
3044 if (editingHost
== foundEditingHost
) {
3047 if (foundEditingHost
->IsInclusiveDescendantOf(editingHost
)) {
3050 if (editingHost
->IsInclusiveDescendantOf(foundEditingHost
)) {
3051 editingHost
= foundEditingHost
;
3054 // editingHost and foundEditingHost are not a descendant of the other.
3055 // So, there is no common editing host.
3061 void Selection::StyledRanges::MaybeFocusCommonEditingHost(
3062 PresShell
* aPresShell
) const {
3067 nsPresContext
* presContext
= aPresShell
->GetPresContext();
3072 Document
* document
= aPresShell
->GetDocument();
3077 nsPIDOMWindowOuter
* window
= document
->GetWindow();
3078 // If the document is in design mode or doesn't have contenteditable
3079 // element, we don't need to move focus.
3080 if (window
&& !document
->IsInDesignMode() &&
3081 nsContentUtils::GetHTMLEditor(presContext
)) {
3082 RefPtr
<Element
> newEditingHost
= GetCommonEditingHost();
3083 RefPtr
<nsFocusManager
> fm
= nsFocusManager::GetFocusManager();
3084 nsCOMPtr
<nsPIDOMWindowOuter
> focusedWindow
;
3085 nsIContent
* focusedContent
= nsFocusManager::GetFocusedDescendant(
3086 window
, nsFocusManager::eOnlyCurrentWindow
,
3087 getter_AddRefs(focusedWindow
));
3088 nsCOMPtr
<Element
> focusedElement
= do_QueryInterface(focusedContent
);
3089 // When all selected ranges are in an editing host, it should take focus.
3090 // But otherwise, we shouldn't move focus since Chromium doesn't move
3091 // focus but only selection range is updated.
3092 if (newEditingHost
&& newEditingHost
!= focusedElement
) {
3093 MOZ_ASSERT(!newEditingHost
->IsInNativeAnonymousSubtree());
3094 // Note that don't steal focus from focused window if the window doesn't
3095 // have focus. Additionally, although when an element gets focus, we
3096 // usually scroll to the element, but in this case, we shouldn't do it
3097 // because Chrome does not do so.
3098 fm
->SetFocus(newEditingHost
, nsIFocusManager::FLAG_NOSWITCHFRAME
|
3099 nsIFocusManager::FLAG_NOSCROLL
);
3104 void Selection::NotifySelectionListeners(bool aCalledByJS
) {
3105 AutoRestore
<bool> calledFromJSRestorer(mCalledByJS
);
3106 mCalledByJS
= aCalledByJS
;
3107 NotifySelectionListeners();
3110 void Selection::NotifySelectionListeners() {
3111 if (!mFrameSelection
) {
3112 return; // nothing to do
3115 MOZ_LOG(sSelectionLog
, LogLevel::Debug
,
3116 ("%s: selection=%p", __FUNCTION__
, this));
3118 // Our internal code should not move focus with using this class while
3119 // this moves focus nor from selection listeners.
3120 AutoRestore
<bool> calledByJSRestorer(mCalledByJS
);
3121 mCalledByJS
= false;
3123 // When normal selection is changed by Selection API, we need to move focus
3124 // if common ancestor of all ranges are in an editing host. Note that we
3125 // don't need to move focus *to* the other focusable node because other
3126 // browsers don't do it either.
3127 if (mSelectionType
== SelectionType::eNormal
&&
3128 calledByJSRestorer
.SavedValue()) {
3129 RefPtr
<PresShell
> presShell
= GetPresShell();
3130 mStyledRanges
.MaybeFocusCommonEditingHost(presShell
);
3133 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
3134 if (frameSelection
->IsBatching()) {
3135 frameSelection
->SetChangesDuringBatchingFlag();
3138 if (mSelectionListeners
.IsEmpty()) {
3139 // If there are no selection listeners, we're done!
3143 nsCOMPtr
<Document
> doc
;
3144 PresShell
* presShell
= GetPresShell();
3146 doc
= presShell
->GetDocument();
3149 // We've notified all selection listeners even when some of them are removed
3150 // (and may be destroyed) during notifying one of them. Therefore, we should
3151 // copy all listeners to the local variable first.
3152 const CopyableAutoTArray
<nsCOMPtr
<nsISelectionListener
>, 5>
3153 selectionListeners
= mSelectionListeners
;
3155 int16_t reason
= frameSelection
->PopChangeReasons();
3156 if (calledByJSRestorer
.SavedValue()) {
3157 reason
|= nsISelectionListener::JS_REASON
;
3160 if (mNotifyAutoCopy
) {
3161 AutoCopyListener::OnSelectionChange(doc
, *this, reason
);
3164 if (mAccessibleCaretEventHub
) {
3165 RefPtr
<AccessibleCaretEventHub
> hub(mAccessibleCaretEventHub
);
3166 hub
->OnSelectionChange(doc
, this, reason
);
3169 if (mSelectionChangeEventDispatcher
) {
3170 RefPtr
<SelectionChangeEventDispatcher
> dispatcher(
3171 mSelectionChangeEventDispatcher
);
3172 dispatcher
->OnSelectionChange(doc
, this, reason
);
3174 for (const auto& listener
: selectionListeners
) {
3175 // MOZ_KnownLive because 'selectionListeners' is guaranteed to
3178 // This can go away once
3179 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
3180 MOZ_KnownLive(listener
)->NotifySelectionChanged(doc
, this, reason
);
3184 void Selection::StartBatchChanges() {
3185 if (mFrameSelection
) {
3186 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
3187 frameSelection
->StartBatchChanges();
3191 void Selection::EndBatchChanges(int16_t aReason
) {
3192 if (mFrameSelection
) {
3193 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
3194 frameSelection
->EndBatchChanges(aReason
);
3198 void Selection::AddSelectionChangeBlocker() { mSelectionChangeBlockerCount
++; }
3200 void Selection::RemoveSelectionChangeBlocker() {
3201 MOZ_ASSERT(mSelectionChangeBlockerCount
> 0,
3202 "mSelectionChangeBlockerCount has an invalid value - "
3203 "maybe you have a mismatched RemoveSelectionChangeBlocker?");
3204 mSelectionChangeBlockerCount
--;
3207 bool Selection::IsBlockingSelectionChangeEvents() const {
3208 return mSelectionChangeBlockerCount
> 0;
3211 void Selection::DeleteFromDocument(ErrorResult
& aRv
) {
3212 if (mSelectionType
!= SelectionType::eNormal
) {
3213 return; // Nothing to do.
3216 // If we're already collapsed, then we do nothing (bug 719503).
3217 if (IsCollapsed()) {
3221 for (uint32_t rangeIdx
= 0; rangeIdx
< RangeCount(); ++rangeIdx
) {
3222 RefPtr
<nsRange
> range
= GetRangeAt(rangeIdx
);
3223 range
->DeleteContents(aRv
);
3229 // Collapse to the new location.
3230 // If we deleted one character, then we move back one element.
3231 // FIXME We don't know how to do this past frame boundaries yet.
3232 if (AnchorOffset() > 0) {
3233 RefPtr
<nsINode
> anchor
= GetAnchorNode();
3234 CollapseInLimiter(anchor
, AnchorOffset());
3238 printf("Don't know how to set selection back past frame boundary\n");
3243 void Selection::Modify(const nsAString
& aAlter
, const nsAString
& aDirection
,
3244 const nsAString
& aGranularity
, ErrorResult
& aRv
) {
3245 if (!mFrameSelection
) {
3246 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
);
3250 if (!GetAnchorFocusRange() || !GetFocusNode()) {
3254 if (!aAlter
.LowerCaseEqualsLiteral("move") &&
3255 !aAlter
.LowerCaseEqualsLiteral("extend")) {
3256 aRv
.ThrowSyntaxError(
3257 R
"(The first argument must be one of: "move
" or "extend
")");
3261 if (!aDirection
.LowerCaseEqualsLiteral("forward") &&
3262 !aDirection
.LowerCaseEqualsLiteral("backward") &&
3263 !aDirection
.LowerCaseEqualsLiteral("left") &&
3264 !aDirection
.LowerCaseEqualsLiteral("right")) {
3265 aRv
.ThrowSyntaxError(
3266 R
"(The direction argument must be one of: "forward
", "backward
", "left
", or "right
")");
3270 // Make sure the layout is up to date as we access bidi information below.
3271 if (RefPtr
<Document
> doc
= GetDocument()) {
3272 doc
->FlushPendingNotifications(FlushType::Layout
);
3275 // Line moves are always visual.
3276 bool visual
= aDirection
.LowerCaseEqualsLiteral("left") ||
3277 aDirection
.LowerCaseEqualsLiteral("right") ||
3278 aGranularity
.LowerCaseEqualsLiteral("line");
3280 bool forward
= aDirection
.LowerCaseEqualsLiteral("forward") ||
3281 aDirection
.LowerCaseEqualsLiteral("right");
3283 bool extend
= aAlter
.LowerCaseEqualsLiteral("extend");
3285 nsSelectionAmount amount
;
3286 if (aGranularity
.LowerCaseEqualsLiteral("character")) {
3287 amount
= eSelectCluster
;
3288 } else if (aGranularity
.LowerCaseEqualsLiteral("word")) {
3289 amount
= eSelectWordNoSpace
;
3290 } else if (aGranularity
.LowerCaseEqualsLiteral("line")) {
3291 amount
= eSelectLine
;
3292 } else if (aGranularity
.LowerCaseEqualsLiteral("lineboundary")) {
3293 amount
= forward
? eSelectEndLine
: eSelectBeginLine
;
3294 } else if (aGranularity
.LowerCaseEqualsLiteral("sentence") ||
3295 aGranularity
.LowerCaseEqualsLiteral("sentenceboundary") ||
3296 aGranularity
.LowerCaseEqualsLiteral("paragraph") ||
3297 aGranularity
.LowerCaseEqualsLiteral("paragraphboundary") ||
3298 aGranularity
.LowerCaseEqualsLiteral("documentboundary")) {
3299 aRv
.Throw(NS_ERROR_NOT_IMPLEMENTED
);
3302 aRv
.ThrowSyntaxError(
3303 R
"(The granularity argument must be one of: "character
", "word
", "line
", or "lineboundary
")");
3307 // If the anchor doesn't equal the focus and we try to move without first
3308 // collapsing the selection, MoveCaret will collapse the selection and quit.
3309 // To avoid this, we need to collapse the selection first.
3310 nsresult rv
= NS_OK
;
3312 RefPtr
<nsINode
> focusNode
= GetFocusNode();
3313 // We should have checked earlier that there was a focus node.
3315 aRv
.Throw(NS_ERROR_UNEXPECTED
);
3318 uint32_t focusOffset
= FocusOffset();
3319 CollapseInLimiter(focusNode
, focusOffset
);
3322 // If the paragraph direction of the focused frame is right-to-left,
3323 // we may have to swap the direction of movement.
3324 if (nsIFrame
* frame
= GetPrimaryFrameForFocusNode(visual
)) {
3325 mozilla::intl::BidiDirection paraDir
=
3326 nsBidiPresUtils::ParagraphDirection(frame
);
3328 if (paraDir
== mozilla::intl::BidiDirection::RTL
&& visual
) {
3329 if (amount
== eSelectBeginLine
) {
3330 amount
= eSelectEndLine
;
3332 } else if (amount
== eSelectEndLine
) {
3333 amount
= eSelectBeginLine
;
3339 // MoveCaret will return an error if it can't move in the specified
3340 // direction, but we just ignore this error unless it's a line move, in which
3341 // case we call nsISelectionController::CompleteMove to move the cursor to
3342 // the beginning/end of the line.
3343 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
3344 rv
= frameSelection
->MoveCaret(
3345 forward
? eDirNext
: eDirPrevious
, extend
, amount
,
3346 visual
? nsFrameSelection::eVisual
: nsFrameSelection::eLogical
);
3348 if (aGranularity
.LowerCaseEqualsLiteral("line") && NS_FAILED(rv
)) {
3349 RefPtr
<PresShell
> presShell
= frameSelection
->GetPresShell();
3353 presShell
->CompleteMove(forward
, extend
);
3357 void Selection::SetBaseAndExtentJS(nsINode
& aAnchorNode
, uint32_t aAnchorOffset
,
3358 nsINode
& aFocusNode
, uint32_t aFocusOffset
,
3360 AutoRestore
<bool> calledFromJSRestorer(mCalledByJS
);
3362 SetBaseAndExtent(aAnchorNode
, aAnchorOffset
, aFocusNode
, aFocusOffset
, aRv
);
3365 void Selection::SetBaseAndExtent(nsINode
& aAnchorNode
, uint32_t aAnchorOffset
,
3366 nsINode
& aFocusNode
, uint32_t aFocusOffset
,
3368 if (aAnchorOffset
> aAnchorNode
.Length()) {
3369 aRv
.ThrowIndexSizeError(nsPrintfCString(
3370 "The anchor offset value %u is out of range", aAnchorOffset
));
3373 if (aFocusOffset
> aFocusNode
.Length()) {
3374 aRv
.ThrowIndexSizeError(nsPrintfCString(
3375 "The focus offset value %u is out of range", aFocusOffset
));
3379 SetBaseAndExtent(RawRangeBoundary
{&aAnchorNode
, aAnchorOffset
},
3380 RawRangeBoundary
{&aFocusNode
, aFocusOffset
}, aRv
);
3383 void Selection::SetBaseAndExtentInternal(InLimiter aInLimiter
,
3384 const RawRangeBoundary
& aAnchorRef
,
3385 const RawRangeBoundary
& aFocusRef
,
3387 if (!mFrameSelection
) {
3388 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
);
3392 if (NS_WARN_IF(!aAnchorRef
.IsSet()) || NS_WARN_IF(!aFocusRef
.IsSet())) {
3393 aRv
.Throw(NS_ERROR_INVALID_ARG
);
3397 if (!HasSameRootOrSameComposedDoc(*aAnchorRef
.Container()) ||
3398 !HasSameRootOrSameComposedDoc(*aFocusRef
.Container())) {
3399 // Return with no error
3403 // Prevent "selectionchange" event temporarily because it should be fired
3404 // after we set the direction.
3405 // XXX If they are disconnected, shouldn't we return error before allocating
3406 // new nsRange instance?
3407 SelectionBatcher
batch(this);
3408 const Maybe
<int32_t> order
=
3409 nsContentUtils::ComparePoints(aAnchorRef
, aFocusRef
);
3410 if (order
&& (*order
<= 0)) {
3411 SetStartAndEndInternal(aInLimiter
, aAnchorRef
, aFocusRef
, eDirNext
, aRv
);
3415 // If there's no `order`, the range will be collapsed, unless another error is
3417 SetStartAndEndInternal(aInLimiter
, aFocusRef
, aAnchorRef
, eDirPrevious
, aRv
);
3420 Result
<Ok
, nsresult
> Selection::SetStartAndEndInLimiter(
3421 nsINode
& aStartContainer
, uint32_t aStartOffset
, nsINode
& aEndContainer
,
3422 uint32_t aEndOffset
, nsDirection aDirection
, int16_t aReason
) {
3423 if (mFrameSelection
) {
3424 mFrameSelection
->AddChangeReasons(aReason
);
3428 SetStartAndEndInternal(
3429 InLimiter::eYes
, RawRangeBoundary(&aStartContainer
, aStartOffset
),
3430 RawRangeBoundary(&aEndContainer
, aEndOffset
), aDirection
, error
);
3431 MOZ_TRY(error
.StealNSResult());
3435 void Selection::SetStartAndEndInternal(InLimiter aInLimiter
,
3436 const RawRangeBoundary
& aStartRef
,
3437 const RawRangeBoundary
& aEndRef
,
3438 nsDirection aDirection
,
3440 if (NS_WARN_IF(!aStartRef
.IsSet()) || NS_WARN_IF(!aEndRef
.IsSet())) {
3441 aRv
.Throw(NS_ERROR_INVALID_ARG
);
3445 // Don't fire "selectionchange" event until everything done.
3446 SelectionBatcher
batch(this);
3448 if (aInLimiter
== InLimiter::eYes
) {
3449 if (!mFrameSelection
||
3450 !mFrameSelection
->IsValidSelectionPoint(aStartRef
.Container())) {
3451 aRv
.Throw(NS_ERROR_FAILURE
);
3454 if (aStartRef
.Container() != aEndRef
.Container() &&
3455 !mFrameSelection
->IsValidSelectionPoint(aEndRef
.Container())) {
3456 aRv
.Throw(NS_ERROR_FAILURE
);
3461 RefPtr
<nsRange
> newRange
= nsRange::Create(aStartRef
, aEndRef
, aRv
);
3466 RemoveAllRanges(aRv
);
3471 AddRangeAndSelectFramesAndNotifyListeners(*newRange
, aRv
);
3476 // Adding a range may set 2 or more ranges if there are non-selectable
3477 // contents only when this change is caused by a user operation. Therefore,
3478 // we need to select frames with the result in such case.
3479 if (mUserInitiated
) {
3480 RefPtr
<nsPresContext
> presContext
= GetPresContext();
3481 if (mStyledRanges
.Length() > 1 && presContext
) {
3482 SelectFramesInAllRanges(presContext
);
3486 SetDirection(aDirection
);
3489 /** SelectionLanguageChange modifies the cursor Bidi level after a change in
3490 * keyboard direction
3491 * @param aLangRTL is true if the new language is right-to-left or false if the
3492 * new language is left-to-right
3494 nsresult
Selection::SelectionLanguageChange(bool aLangRTL
) {
3495 if (!mFrameSelection
) {
3496 return NS_ERROR_NOT_INITIALIZED
;
3499 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
3501 // if the direction of the language hasn't changed, nothing to do
3502 mozilla::intl::BidiEmbeddingLevel kbdBidiLevel
=
3503 aLangRTL
? mozilla::intl::BidiEmbeddingLevel::RTL()
3504 : mozilla::intl::BidiEmbeddingLevel::LTR();
3505 if (kbdBidiLevel
== frameSelection
->mKbdBidiLevel
) {
3509 frameSelection
->mKbdBidiLevel
= kbdBidiLevel
;
3511 nsIFrame
* focusFrame
= GetPrimaryFrameForFocusNode(false);
3513 return NS_ERROR_FAILURE
;
3516 auto [frameStart
, frameEnd
] = focusFrame
->GetOffsets();
3517 RefPtr
<nsPresContext
> context
= GetPresContext();
3518 mozilla::intl::BidiEmbeddingLevel levelBefore
, levelAfter
;
3520 return NS_ERROR_FAILURE
;
3523 mozilla::intl::BidiEmbeddingLevel level
= focusFrame
->GetEmbeddingLevel();
3524 int32_t focusOffset
= static_cast<int32_t>(FocusOffset());
3525 if ((focusOffset
!= frameStart
) && (focusOffset
!= frameEnd
))
3526 // the cursor is not at a frame boundary, so the level of both the
3527 // characters (logically) before and after the cursor is equal to the frame
3529 levelBefore
= levelAfter
= level
;
3531 // the cursor is at a frame boundary, so use GetPrevNextBidiLevels to find
3532 // the level of the characters before and after the cursor
3533 nsCOMPtr
<nsIContent
> focusContent
= do_QueryInterface(GetFocusNode());
3534 nsPrevNextBidiLevels levels
=
3535 frameSelection
->GetPrevNextBidiLevels(focusContent
, focusOffset
, false);
3537 levelBefore
= levels
.mLevelBefore
;
3538 levelAfter
= levels
.mLevelAfter
;
3541 if (levelBefore
.IsSameDirection(levelAfter
)) {
3542 // if cursor is between two characters with the same orientation, changing
3543 // the keyboard language must toggle the cursor level between the level of
3544 // the character with the lowest level (if the new language corresponds to
3545 // the orientation of that character) and this level plus 1 (if the new
3546 // language corresponds to the opposite orientation)
3547 if ((level
!= levelBefore
) && (level
!= levelAfter
)) {
3548 level
= std::min(levelBefore
, levelAfter
);
3550 if (level
.IsSameDirection(kbdBidiLevel
)) {
3551 frameSelection
->SetCaretBidiLevelAndMaybeSchedulePaint(level
);
3553 frameSelection
->SetCaretBidiLevelAndMaybeSchedulePaint(
3554 mozilla::intl::BidiEmbeddingLevel(level
+ 1));
3557 // if cursor is between characters with opposite orientations, changing the
3558 // keyboard language must change the cursor level to that of the adjacent
3559 // character with the orientation corresponding to the new language.
3560 if (levelBefore
.IsSameDirection(kbdBidiLevel
)) {
3561 frameSelection
->SetCaretBidiLevelAndMaybeSchedulePaint(levelBefore
);
3563 frameSelection
->SetCaretBidiLevelAndMaybeSchedulePaint(levelAfter
);
3567 // The caret might have moved, so invalidate the desired position
3568 // for future usages of up-arrow or down-arrow
3569 frameSelection
->InvalidateDesiredCaretPos();
3574 void Selection::SetColors(const nsAString
& aForegroundColor
,
3575 const nsAString
& aBackgroundColor
,
3576 const nsAString
& aAltForegroundColor
,
3577 const nsAString
& aAltBackgroundColor
,
3579 if (mSelectionType
!= SelectionType::eFind
) {
3580 aRv
.Throw(NS_ERROR_FAILURE
);
3584 mCustomColors
.reset(new SelectionCustomColors
);
3586 constexpr auto currentColorStr
= u
"currentColor"_ns
;
3587 constexpr auto transparentStr
= u
"transparent"_ns
;
3589 if (!aForegroundColor
.Equals(currentColorStr
)) {
3590 nscolor foregroundColor
;
3591 nsAttrValue aForegroundColorValue
;
3592 aForegroundColorValue
.ParseColor(aForegroundColor
);
3593 if (!aForegroundColorValue
.GetColorValue(foregroundColor
)) {
3594 aRv
.Throw(NS_ERROR_INVALID_ARG
);
3597 mCustomColors
->mForegroundColor
= Some(foregroundColor
);
3599 mCustomColors
->mForegroundColor
= Nothing();
3602 if (!aBackgroundColor
.Equals(transparentStr
)) {
3603 nscolor backgroundColor
;
3604 nsAttrValue aBackgroundColorValue
;
3605 aBackgroundColorValue
.ParseColor(aBackgroundColor
);
3606 if (!aBackgroundColorValue
.GetColorValue(backgroundColor
)) {
3607 aRv
.Throw(NS_ERROR_INVALID_ARG
);
3610 mCustomColors
->mBackgroundColor
= Some(backgroundColor
);
3612 mCustomColors
->mBackgroundColor
= Nothing();
3615 if (!aAltForegroundColor
.Equals(currentColorStr
)) {
3616 nscolor altForegroundColor
;
3617 nsAttrValue aAltForegroundColorValue
;
3618 aAltForegroundColorValue
.ParseColor(aAltForegroundColor
);
3619 if (!aAltForegroundColorValue
.GetColorValue(altForegroundColor
)) {
3620 aRv
.Throw(NS_ERROR_INVALID_ARG
);
3623 mCustomColors
->mAltForegroundColor
= Some(altForegroundColor
);
3625 mCustomColors
->mAltForegroundColor
= Nothing();
3628 if (!aAltBackgroundColor
.Equals(transparentStr
)) {
3629 nscolor altBackgroundColor
;
3630 nsAttrValue aAltBackgroundColorValue
;
3631 aAltBackgroundColorValue
.ParseColor(aAltBackgroundColor
);
3632 if (!aAltBackgroundColorValue
.GetColorValue(altBackgroundColor
)) {
3633 aRv
.Throw(NS_ERROR_INVALID_ARG
);
3636 mCustomColors
->mAltBackgroundColor
= Some(altBackgroundColor
);
3638 mCustomColors
->mAltBackgroundColor
= Nothing();
3642 void Selection::ResetColors(ErrorResult
& aRv
) { mCustomColors
= nullptr; }
3644 JSObject
* Selection::WrapObject(JSContext
* aCx
,
3645 JS::Handle
<JSObject
*> aGivenProto
) {
3646 return mozilla::dom::Selection_Binding::Wrap(aCx
, this, aGivenProto
);
3649 // AutoHideSelectionChanges
3650 AutoHideSelectionChanges::AutoHideSelectionChanges(
3651 const nsFrameSelection
* aFrame
)
3652 : AutoHideSelectionChanges(
3653 aFrame
? aFrame
->GetSelection(SelectionType::eNormal
) : nullptr) {}
3655 bool Selection::HasSameRootOrSameComposedDoc(const nsINode
& aNode
) {
3656 nsINode
* root
= aNode
.SubtreeRoot();
3657 Document
* doc
= GetDocument();
3658 return doc
== root
|| (root
&& doc
== root
->GetComposedDoc());