Bug 1750871 - run mochitest-remote on fission everywhere. r=releng-reviewers,aki
[gecko.git] / dom / base / Selection.cpp
blob56069f308680881465b2a8ff56c4d866160f7517
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/. */
7 /*
8 * Implementation of mozilla::dom::Selection
9 */
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"
35 #include "nsCOMPtr.h"
36 #include "nsDebug.h"
37 #include "nsString.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"
44 #include "nsRange.h"
45 #include "nsITableCellLayout.h"
46 #include "nsTArray.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"
53 #include <algorithm>
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"
65 #include "nsCaret.h"
67 #include "nsITimer.h"
68 #include "mozilla/dom/Document.h"
69 #include "nsINamed.h"
71 #include "nsISelectionController.h" //for the enums
72 #include "nsCopySupport.h"
73 #include "nsIFrameInlines.h"
74 #include "nsRefreshDriver.h"
76 #include "nsError.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
89 #ifdef PRINT_RANGE
90 static void printRange(nsRange* aDomRange);
91 # define DEBUG_OUT_RANGE(x) printRange(x)
92 #else
93 # define DEBUG_OUT_RANGE(x)
94 #endif // PRINT_RANGE
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 ******************************************************************************/
105 namespace mozilla {
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";
133 default:
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 {
160 public:
161 NS_DECL_ISUPPORTS
163 explicit AutoScroller(nsFrameSelection* aFrameSelection)
164 : mFrameSelection(aFrameSelection),
165 mPresContext(0),
166 mPoint(0, 0),
167 mDelayInMs(30),
168 mFurtherScrollingAllowed(FurtherScrollingAllowed::kYes) {
169 MOZ_ASSERT(mFrameSelection);
172 MOZ_CAN_RUN_SCRIPT nsresult DoAutoScroll(nsIFrame* aFrame, nsPoint aPoint);
174 private:
175 // aPoint is relative to aPresContext's root frame
176 nsresult ScheduleNextDoAutoScroll(nsPresContext* aPresContext,
177 nsPoint& aPoint) {
178 if (NS_WARN_IF(mFurtherScrollingAllowed == FurtherScrollingAllowed::kNo)) {
179 return NS_ERROR_FAILURE;
182 mPoint = aPoint;
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();
190 if (!mTimer) {
191 mTimer = NS_NewTimer(
192 mPresContext->Document()->EventTargetFor(TaskCategory::Other));
194 if (!mTimer) {
195 return NS_ERROR_OUT_OF_MEMORY;
199 return mTimer->InitWithCallback(this, mDelayInMs, nsITimer::TYPE_ONE_SHOT);
202 public:
203 enum class FurtherScrollingAllowed { kYes, kNo };
205 void Stop(const FurtherScrollingAllowed aFurtherScrollingAllowed) {
206 MOZ_ASSERT((aFurtherScrollingAllowed == FurtherScrollingAllowed::kNo) ||
207 (mFurtherScrollingAllowed == FurtherScrollingAllowed::kYes));
209 if (mTimer) {
210 mTimer->Cancel();
211 mTimer = nullptr;
214 mContent = nullptr;
215 mFurtherScrollingAllowed = aFurtherScrollingAllowed;
218 void SetDelay(uint32_t aDelayInMs) { mDelayInMs = aDelayInMs; }
220 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Notify(nsITimer* timer) override {
221 if (mPresContext) {
222 AutoWeakFrame frame =
223 mContent ? mPresContext->GetPrimaryFrameFor(mContent) : nullptr;
224 if (!frame) {
225 return NS_OK;
227 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()) {
234 return NS_OK;
237 NS_ASSERTION(frame->PresContext() == mPresContext, "document mismatch?");
238 DoAutoScroll(frame, pt);
240 return NS_OK;
243 NS_IMETHOD GetName(nsACString& aName) override {
244 aName.AssignLiteral("AutoScroller");
245 return NS_OK;
248 protected:
249 virtual ~AutoScroller() {
250 if (mTimer) {
251 mTimer->Cancel();
255 private:
256 nsFrameSelection* const mFrameSelection;
257 nsPresContext* mPresContext;
258 // relative to mPresContext's root frame
259 nsPoint mPoint;
260 nsCOMPtr<nsITimer> mTimer;
261 nsCOMPtr<nsIContent> mContent;
262 uint32_t mDelayInMs;
263 FurtherScrollingAllowed mFurtherScrollingAllowed;
266 NS_IMPL_ISUPPORTS(AutoScroller, nsITimerCallback, nsINamed)
268 #ifdef PRINT_RANGE
269 void printRange(nsRange* aDomRange) {
270 if (!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;
291 if (!presShell) {
292 aResult.Truncate();
293 return;
295 presShell->FlushPendingNotifications(FlushType::Frames);
298 IgnoredErrorResult rv;
299 ToStringWithFormat(u"text/plain"_ns, nsIDocumentEncoder::SkipInvisibleContent,
300 0, aResult, rv);
301 if (rv.Failed()) {
302 aResult.Truncate();
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());
311 if (!encoder) {
312 aRv.Throw(NS_ERROR_FAILURE);
313 return;
316 PresShell* presShell = GetPresShell();
317 if (!presShell) {
318 aRv.Throw(NS_ERROR_FAILURE);
319 return;
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);
329 if (NS_FAILED(rv)) {
330 aRv.Throw(rv);
331 return;
334 encoder->SetSelection(this);
335 if (aWrapCol != 0) encoder->SetWrapColumn(aWrapCol);
337 rv = encoder->EncodeToString(aReturn);
338 if (NS_FAILED(rv)) {
339 aRv.Throw(rv);
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
348 return;
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
360 return false;
362 return mFrameSelection->GetHint() == CARET_ASSOCIATE_AFTER;
365 static bool IsEditorNode(const nsINode* aNode) {
366 if (!aNode) {
367 return false;
370 if (aNode->IsEditable()) {
371 return true;
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);
404 return;
406 if (aCaretBidiLevel.IsNull()) {
407 mFrameSelection->UndefineCaretBidiLevel();
408 } else {
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();
429 if (!startNode) {
430 return NS_ERROR_FAILURE;
433 nsINode* endNode = aRange.GetEndContainer();
434 if (!endNode) {
435 return NS_ERROR_FAILURE;
438 // Not a single selected node
439 if (startNode != endNode) {
440 return NS_OK;
443 nsIContent* child = aRange.GetChildAtStartOffset();
445 // Not a single selected node
446 if (!child || child->GetNextSibling() != aRange.GetChildAtEndOffset()) {
447 return NS_OK;
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.
454 return NS_OK;
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;
469 return NS_OK;
472 nsresult Selection::MaybeAddTableCellRange(nsRange& aRange,
473 Maybe<size_t>* aOutIndex) {
474 if (!aOutIndex) {
475 return NS_ERROR_NULL_POINTER;
478 MOZ_ASSERT(aOutIndex->isNothing());
480 if (!mFrameSelection) {
481 return NS_OK;
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
493 // we didn't proceed
494 return NS_OK;
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),
517 mCalledByJS(false),
518 mNotifyAutoCopy(false) {}
520 Selection::~Selection() { Disconnect(); }
522 void Selection::Disconnect() {
523 RemoveAnchorFocusRange();
525 mStyledRanges.UnregisterSelection();
527 if (mAutoScroller) {
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();
547 if (!presShell) {
548 return nullptr;
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
560 // in JS!).
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)
592 NS_INTERFACE_MAP_END
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;
601 return 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;
614 return 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()) {
626 return;
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
641 // to be fixed.
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()) {
647 NS_WARNING(
648 "`CompareToRangeStart` couldn't compare nodes, pretending some order.");
649 return 1;
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()) {
667 NS_WARNING(
668 "`CompareToRangeEnd` couldn't compare nodes, pretending some order.");
669 return 1;
672 // The points are in the same subtree, hence there has to be an order.
673 return *nsContentUtils::ComparePoints(&aCompareNode, aCompareOffset, end,
674 aRange.EndOffset());
677 // static
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
685 if (endSearch) {
686 int32_t center = endSearch - 1; // Check last index, then binary search
687 do {
688 const nsRange* range = (*aElementArray)[center].mRange;
690 int32_t cmp{aComparator(aPointNode, aPointOffset, *range)};
692 if (cmp < 0) { // point < cur
693 endSearch = center;
694 } else if (cmp > 0) { // point > cur
695 beginSearch = center + 1;
696 } else { // found match, done
697 beginSearch = center;
698 break;
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
714 // static
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(),
729 aSubtract)};
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
736 if (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
739 ErrorResult error;
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;
754 if (cmp < 0) {
755 // We need to add a new StyledRange to the output, running from
756 // the start of the range to the start of aSubtract
757 ErrorResult error;
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;
772 return NS_OK;
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);
785 } else {
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();
798 } else {
799 if (target->IsInNativeAnonymousSubtree()) {
800 // This is a selection under a text control, so don't dispatch the
801 // event.
802 target = nullptr;
805 return target;
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;
828 // static
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());
851 if (!aRange) {
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(
887 *aRange,
888 StaticPrefs::dom_select_events_textcontrols_selectstart_enabled(),
889 doc);
891 if (!executeDefaultAction) {
892 return NS_OK;
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) {
909 Maybe<size_t> index;
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) {
917 *aOutIndex = index;
918 rangesToAdd[i]->SetIsGenerated(false);
919 } else {
920 rangesToAdd[i]->SetIsGenerated(true);
923 return NS_OK;
926 nsresult Selection::AddRangesForSelectableNodes(
927 nsRange* aRange, Maybe<size_t>* aOutIndex,
928 const DispatchSelectstartEvent aDispatchSelectstartEvent) {
929 MOZ_ASSERT(aOutIndex);
930 MOZ_ASSERT(aOutIndex->isNothing());
932 if (!aRange) {
933 return NS_ERROR_NULL_POINTER;
936 if (!aRange->IsPositioned()) {
937 return NS_ERROR_UNEXPECTED;
940 MOZ_LOG(
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,
953 *selection);
956 nsresult Selection::StyledRanges::MaybeAddRangeAndTruncateOverlaps(
957 nsRange* aRange, Maybe<size_t>* aOutIndex, Selection& aSelection) {
958 MOZ_ASSERT(aRange);
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);
971 return NS_OK;
974 Maybe<size_t> maybeStartIndex, maybeEndIndex;
975 nsresult rv =
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)
985 startIndex = 0;
986 endIndex = 0;
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;
992 } else {
993 startIndex = *maybeStartIndex;
994 endIndex = *maybeEndIndex;
997 // If the range is already contained in mRanges, silently
998 // succeed
999 const bool sameRange = HasEqualRangeBoundariesAt(*aRange, startIndex);
1000 if (sameRange) {
1001 aOutIndex->emplace(startIndex);
1002 return NS_OK;
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);
1012 return NS_OK;
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
1020 // removed
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);
1066 return NS_OK;
1069 nsresult Selection::StyledRanges::RemoveRangeAndUnregisterSelection(
1070 nsRange& aRange) {
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.
1075 int32_t idx = -1;
1076 uint32_t i;
1077 for (i = 0; i < mRanges.Length(); i++) {
1078 if (mRanges[i].mRange == &aRange) {
1079 idx = (int32_t)i;
1080 break;
1083 if (idx < 0) return NS_ERROR_DOM_NOT_FOUND_ERR;
1085 mRanges.RemoveElementAt(idx);
1086 aRange.UnregisterSelection();
1087 return NS_OK;
1089 nsresult Selection::RemoveCollapsedRanges() {
1090 return mStyledRanges.RemoveCollapsedRanges();
1093 nsresult Selection::StyledRanges::RemoveCollapsedRanges() {
1094 uint32_t i = 0;
1095 while (i < mRanges.Length()) {
1096 if (mRanges[i].mRange->Collapsed()) {
1097 nsresult rv = RemoveRangeAndUnregisterSelection(*mRanges[i].mRange);
1098 NS_ENSURE_SUCCESS(rv, rv);
1099 } else {
1100 ++i;
1103 return NS_OK;
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);
1131 return false;
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)) {
1143 aRv.Throw(rv);
1144 return;
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;
1164 aRanges->Clear();
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()) {
1172 return NS_OK;
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);
1181 return NS_OK;
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) {
1200 return NS_OK;
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)) {
1217 return NS_OK;
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))
1226 return NS_OK;
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)) {
1251 break;
1253 endsBeforeIndex++;
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)) {
1272 beginsAfterIndex--;
1275 } else {
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()) {
1283 beginsAfterIndex++;
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
1289 // included
1290 if (endsBeforeIndex < mRanges.Length()) {
1291 const nsRange* endRange = mRanges[endsBeforeIndex].mRange;
1292 if (endRange->StartRef().Equals(aEndNode, aEndOffset) &&
1293 endRange->Collapsed()) {
1294 endsBeforeIndex++;
1299 NS_ASSERTION(beginsAfterIndex <= endsBeforeIndex, "Is mRanges not ordered?");
1300 NS_ENSURE_STATE(beginsAfterIndex <= endsBeforeIndex);
1302 aStartIndex.emplace(beginsAfterIndex);
1303 aEndIndex = Some(endsBeforeIndex);
1304 return NS_OK;
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);
1316 return nullptr;
1319 nsIFrame* Selection::GetPrimaryFrameForFocusNode(bool aVisual,
1320 int32_t* aOffsetUsed) const {
1321 nsINode* focusNode = GetFocusNode();
1322 if (!focusNode || !focusNode->IsContent() || !mFrameSelection) {
1323 return nullptr;
1326 nsCOMPtr<nsIContent> content = focusNode->AsContent();
1327 int32_t frameOffset = 0;
1328 if (!aOffsetUsed) {
1329 aOffsetUsed = &frameOffset;
1332 nsIFrame* frame = GetPrimaryOrCaretFrameForNodeOffset(content, FocusOffset(),
1333 aOffsetUsed, aVisual);
1334 if (frame) {
1335 return frame;
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()) {
1342 return nullptr;
1345 nsCOMPtr<nsIContent> parent = content->GetParent();
1346 if (NS_WARN_IF(!parent)) {
1347 return nullptr;
1349 const Maybe<uint32_t> offset = parent->ComputeIndexOf(content);
1350 if (MOZ_UNLIKELY(NS_WARN_IF(offset.isNothing()))) {
1351 return nullptr;
1353 return GetPrimaryOrCaretFrameForNodeOffset(parent, *offset, aOffsetUsed,
1354 aVisual);
1357 nsIFrame* Selection::GetPrimaryOrCaretFrameForNodeOffset(nsIContent* aContent,
1358 uint32_t aOffset,
1359 int32_t* aOffsetUsed,
1360 bool aVisual) const {
1361 MOZ_ASSERT(aOffsetUsed);
1363 if (!mFrameSelection) {
1364 return nullptr;
1367 CaretAssociationHint hint = mFrameSelection->GetHint();
1369 if (aVisual) {
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,
1379 aOffsetUsed);
1382 void Selection::SelectFramesOf(nsIContent* aContent, bool aSelected) const {
1383 nsIFrame* frame = aContent->GetPrimaryFrame();
1384 if (!frame) {
1385 return;
1387 // The frame could be an SVG text frame, in which case we don't treat it
1388 // as a text frame.
1389 if (frame->IsTextFrame()) {
1390 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
1391 textFrame->SelectionStateChanged(0, textFrame->TextFragment()->GetLength(),
1392 aSelected, mSelectionType);
1393 } else {
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);
1405 return NS_OK;
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();
1414 MOZ_ASSERT(node);
1415 nsIContent* innercontent = node->IsContent() ? node->AsContent() : nullptr;
1416 SelectFramesOf(innercontent, aSelected);
1419 return NS_OK;
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()) {
1437 // nothing to do
1438 return NS_OK;
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();
1447 if (frame) {
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,
1455 mSelectionType);
1456 } else {
1457 frame->SelectionStateChanged();
1461 return NS_OK;
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
1483 // as a text frame.
1484 if (frame) {
1485 if (frame->IsTextFrame()) {
1486 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
1487 uint32_t startOffset = aRange->StartOffset();
1488 uint32_t endOffset;
1489 if (endNode == startContent) {
1490 endOffset = aRange->EndOffset();
1491 } else {
1492 endOffset = startContent->Length();
1494 textFrame->SelectionStateChanged(startOffset, endOffset, aSelect,
1495 mSelectionType);
1496 } else {
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);
1509 return NS_OK;
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();
1521 MOZ_ASSERT(node);
1522 nsIContent* content = node->IsContent() ? node->AsContent() : nullptr;
1523 SelectFramesOfInclusiveDescendantsOfContent(postOrderIter, content,
1524 aSelect);
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,
1543 mSelectionType);
1547 return NS_OK;
1550 // Selection::LookUpSelection
1552 // This function is called when a node wants to know where the selection is
1553 // over itself.
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
1560 // node at all.
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,
1577 bool aSlowCheck) {
1578 if (!aContent) {
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
1613 start.emplace(
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
1623 start.emplace(
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
1631 start.emplace(0u);
1632 end.emplace(std::min(aContentLength, endOffset - aContentOffset));
1634 } else {
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
1638 // of it.
1639 start.emplace(0u);
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);
1653 if (rd) {
1654 newHead->mTextRangeStyle = rd->mTextRangeStyle;
1656 detailsHead = std::move(newHead);
1658 return detailsHead;
1661 NS_IMETHODIMP
1662 Selection::Repaint(nsPresContext* aPresContext) {
1663 int32_t arrCount = (int32_t)mStyledRanges.Length();
1665 if (arrCount < 1) return NS_OK;
1667 int32_t i;
1669 for (i = 0; i < arrCount; i++) {
1670 nsresult rv =
1671 SelectFrames(aPresContext, mStyledRanges.mRanges[i].mRange, true);
1673 if (NS_FAILED(rv)) {
1674 return rv;
1678 return NS_OK;
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
1689 // fix bug 207936
1690 if (!aCanCacheFrameOffset) {
1691 mCachedOffsetForFrame->mLastCaretFrame = nullptr;
1695 nsresult Selection::GetCachedFrameOffset(nsIFrame* aFrame, int32_t inOffset,
1696 nsPoint& aPoint) {
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;
1708 } else {
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;
1719 return rv;
1722 nsIContent* Selection::GetAncestorLimiter() const {
1723 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
1725 if (mFrameSelection) {
1726 return mFrameSelection->GetAncestorLimiter();
1728 return nullptr;
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) {
1753 return &mRanges[i];
1756 return nullptr;
1759 Selection::StyledRanges::Elements::size_type Selection::StyledRanges::Length()
1760 const {
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);
1768 if (rd) {
1769 rd->mTextRangeStyle = aTextRangeStyle;
1771 return NS_OK;
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);
1801 return NS_OK;
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();
1812 if (!rootPC) {
1813 return NS_OK;
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);
1822 bool done = false;
1823 bool didScroll;
1824 while (true) {
1825 didScroll = presShell->ScrollFrameRectIntoView(
1826 aFrame, nsRect(aPoint, nsSize(0, 0)), nsMargin(), ScrollAxis(),
1827 ScrollAxis(), ScrollFlags::None);
1828 if (!weakFrame || !weakRootFrame) {
1829 return NS_OK;
1831 if (!didScroll && !done) {
1832 // If aPoint is at the very edge of the root, then try to scroll anyway,
1833 // once.
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;
1845 } else {
1846 break;
1848 done = true;
1849 continue;
1851 break;
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 =
1859 globalPoint -
1860 presShell->GetRootFrame()->GetOffsetToCrossDoc(rootmostFrame);
1861 ScheduleNextDoAutoScroll(presContext, presContextPoint);
1864 return NS_OK;
1867 void Selection::RemoveAllRanges(ErrorResult& aRv) {
1868 if (!mFrameSelection) {
1869 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
1870 return;
1873 RefPtr<nsPresContext> presContext = GetPresContext();
1874 Clear(presContext);
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);
1887 mCalledByJS = true;
1888 AddRangeAndSelectFramesAndNotifyListeners(aRange, aRv);
1891 void Selection::AddRangeAndSelectFramesAndNotifyListeners(nsRange& aRange,
1892 ErrorResult& aRv) {
1893 RefPtr<Document> document(GetDocument());
1894 return AddRangeAndSelectFramesAndNotifyListeners(aRange, document, aRv);
1897 void Selection::AddRangeAndSelectFramesAndNotifyListeners(nsRange& aRange,
1898 Document* aDocument,
1899 ErrorResult& aRv) {
1900 // If the given range is part of another Selection, we need to clone the
1901 // range first.
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();
1907 } else {
1908 range = &aRange;
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."
1917 return;
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)) {
1929 aRv.Throw(result);
1930 return;
1933 if (maybeRangeIndex.isNothing()) {
1934 result = AddRangesForSelectableNodes(range, &maybeRangeIndex,
1935 DispatchSelectstartEvent::Maybe);
1936 if (NS_FAILED(result)) {
1937 aRv.Throw(result);
1938 return;
1940 if (maybeRangeIndex.isNothing()) {
1941 return;
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)) {
1981 aRv.Throw(rv);
1982 return;
1985 nsINode* beginNode = aRange.GetStartContainer();
1986 nsINode* endNode = aRange.GetEndContainer();
1988 if (!beginNode || !endNode) {
1989 // Detached range; nothing else to do here.
1990 return;
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
1998 // range.
1999 beginOffset = 0;
2000 endOffset = endNode->AsText()->TextLength();
2001 } else {
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)) {
2016 aRv.Throw(rv);
2017 return;
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();
2025 if (rangeCount) {
2026 SetAnchorFocusRange(rangeCount - 1);
2027 } else {
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,
2051 ErrorResult& aRv) {
2052 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
2053 mCalledByJS = true;
2054 if (!aContainer) {
2055 RemoveAllRanges(aRv);
2056 return;
2058 CollapseInternal(InLimiter::eNo, RawRangeBoundary(aContainer, aOffset), aRv);
2061 void Selection::CollapseInternal(InLimiter aInLimiter,
2062 const RawRangeBoundary& aPoint,
2063 ErrorResult& aRv) {
2064 if (!mFrameSelection) {
2065 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
2066 return;
2069 if (!aPoint.IsSet()) {
2070 aRv.Throw(NS_ERROR_INVALID_ARG);
2071 return;
2074 if (aPoint.Container()->NodeType() == nsINode::DOCUMENT_TYPE_NODE) {
2075 aRv.ThrowInvalidNodeTypeError(kNoDocumentTypeNodeError);
2076 return;
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.");
2085 return;
2088 if (!HasSameRootOrSameComposedDoc(*aPoint.Container())) {
2089 // Return with no error
2090 return;
2093 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
2094 frameSelection->InvalidateDesiredCaretPos();
2095 if (aInLimiter == InLimiter::eYes &&
2096 !frameSelection->IsValidSelectionPoint(aPoint.Container())) {
2097 aRv.Throw(NS_ERROR_FAILURE);
2098 return;
2100 nsresult result;
2102 RefPtr<nsPresContext> presContext = GetPresContext();
2103 if (!presContext ||
2104 presContext->Document() != aPoint.Container()->OwnerDoc()) {
2105 aRv.Throw(NS_ERROR_FAILURE);
2106 return;
2109 // Delete all of the current ranges
2110 Clear(presContext);
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),
2122 &frameOffset));
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)) {
2143 aRv.Throw(result);
2144 return;
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" : "???"),
2153 aPoint.Offset());
2154 #endif
2156 Maybe<size_t> maybeRangeIndex;
2157 result = AddRangesForSelectableNodes(range, &maybeRangeIndex,
2158 DispatchSelectstartEvent::Maybe);
2159 if (NS_FAILED(result)) {
2160 aRv.Throw(result);
2161 return;
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);
2177 mCalledByJS = true;
2178 CollapseToStart(aRv);
2181 void Selection::CollapseToStart(ErrorResult& aRv) {
2182 if (RangeCount() == 0) {
2183 aRv.ThrowInvalidStateError(kNoRangeExistsError);
2184 return;
2187 // Get the first range
2188 const nsRange* firstRange = mStyledRanges.mRanges[0].mRange;
2189 if (!firstRange) {
2190 aRv.Throw(NS_ERROR_FAILURE);
2191 return;
2194 if (mFrameSelection) {
2195 mFrameSelection->AddChangeReasons(
2196 nsISelectionListener::COLLAPSETOSTART_REASON);
2198 nsINode* container = firstRange->GetStartContainer();
2199 if (!container) {
2200 aRv.Throw(NS_ERROR_FAILURE);
2201 return;
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);
2213 mCalledByJS = true;
2214 CollapseToEnd(aRv);
2217 void Selection::CollapseToEnd(ErrorResult& aRv) {
2218 uint32_t cnt = RangeCount();
2219 if (cnt == 0) {
2220 aRv.ThrowInvalidStateError(kNoRangeExistsError);
2221 return;
2224 // Get the last range
2225 const nsRange* lastRange = mStyledRanges.mRanges[cnt - 1].mRange;
2226 if (!lastRange) {
2227 aRv.Throw(NS_ERROR_FAILURE);
2228 return;
2231 if (mFrameSelection) {
2232 mFrameSelection->AddChangeReasons(
2233 nsISelectionListener::COLLAPSETOEND_REASON);
2235 nsINode* container = lastRange->GetEndContainer();
2236 if (!container) {
2237 aRv.Throw(NS_ERROR_FAILURE);
2238 return;
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");
2249 } else {
2250 aOutType.AssignLiteral("Range");
2254 nsRange* Selection::GetRangeAt(uint32_t aIndex, ErrorResult& aRv) {
2255 nsRange* range = GetRangeAt(aIndex);
2256 if (!range) {
2257 aRv.ThrowIndexSizeError(nsPrintfCString("%u is out of range", aIndex));
2258 return nullptr;
2261 return range;
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;
2276 nsresult rv =
2277 mStyledRanges.RemoveRangeAndUnregisterSelection(*mAnchorFocusRange);
2278 if (NS_FAILED(rv)) {
2279 return rv;
2282 Maybe<size_t> maybeOutIndex;
2283 rv = AddRangesForSelectableNodes(aRange, &maybeOutIndex,
2284 dispatchSelectstartEvent);
2285 if (NS_FAILED(rv)) {
2286 return rv;
2288 if (maybeOutIndex.isSome()) {
2289 SetAnchorFocusRange(*maybeOutIndex);
2290 } else {
2291 RemoveAnchorFocusRange();
2294 return NS_OK;
2297 void Selection::ReplaceAnchorFocusRange(nsRange* aRange) {
2298 NS_ENSURE_TRUE_VOID(mAnchorFocusRange);
2299 RefPtr<nsPresContext> presContext = GetPresContext();
2300 if (presContext) {
2301 SelectFrames(presContext, mAnchorFocusRange, false);
2302 SetAnchorFocusToRange(aRange);
2303 SelectFrames(presContext, mAnchorFocusRange, true);
2307 void Selection::AdjustAnchorFocusForMultiRange(nsDirection aDirection) {
2308 if (aDirection == mDirection) {
2309 return;
2311 SetDirection(aDirection);
2313 if (RangeCount() <= 1) {
2314 return;
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,
2336 ErrorResult& aRv) {
2337 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
2338 mCalledByJS = true;
2339 Extend(aContainer, aOffset, aRv);
2342 nsresult Selection::Extend(nsINode* aContainer, uint32_t aOffset) {
2343 if (!aContainer) {
2344 return NS_ERROR_INVALID_ARG;
2347 ErrorResult result;
2348 Extend(*aContainer, aOffset, result);
2349 return result.StealNSResult();
2352 void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
2353 ErrorResult& aRv) {
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
2368 then execute
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);
2379 return;
2382 if (!mFrameSelection) {
2383 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
2384 return;
2387 if (!HasSameRootOrSameComposedDoc(aContainer)) {
2388 // Return with no error
2389 return;
2392 nsresult res;
2393 if (!mFrameSelection->IsValidSelectionPoint(&aContainer)) {
2394 aRv.Throw(NS_ERROR_FAILURE);
2395 return;
2398 RefPtr<nsPresContext> presContext = GetPresContext();
2399 if (!presContext || presContext->Document() != aContainer.OwnerDoc()) {
2400 aRv.Throw(NS_ERROR_FAILURE);
2401 return;
2404 #ifdef DEBUG_SELECTION
2405 nsDirection oldDirection = GetDirection();
2406 #endif
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)) {
2438 aRv.Throw(res);
2439 return;
2442 res = SetAnchorFocusToRange(range);
2443 if (NS_FAILED(res)) {
2444 aRv.Throw(res);
2445 return;
2447 } else {
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);
2454 if (aRv.Failed()) {
2455 return;
2457 SetDirection(eDirNext);
2458 res = difRange->SetStartAndEnd(
2459 focusNode, focusOffset, range->GetEndContainer(), range->EndOffset());
2460 if (NS_FAILED(res)) {
2461 aRv.Throw(res);
2462 return;
2464 SelectFrames(presContext, difRange, true);
2465 res = SetAnchorFocusToRange(range);
2466 if (NS_FAILED(res)) {
2467 aRv.Throw(res);
2468 return;
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);
2475 if (aRv.Failed()) {
2476 return;
2478 SelectFrames(presContext, range, true);
2479 res = SetAnchorFocusToRange(range);
2480 if (NS_FAILED(res)) {
2481 aRv.Throw(res);
2482 return;
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,
2488 focusOffset);
2489 if (NS_FAILED(res)) {
2490 aRv.Throw(res);
2491 return;
2494 range->SetEnd(aContainer, aOffset, aRv);
2495 if (aRv.Failed()) {
2496 return;
2498 res = SetAnchorFocusToRange(range);
2499 if (NS_FAILED(res)) {
2500 aRv.Throw(res);
2501 return;
2503 SelectFrames(presContext, difRange, false); // deselect now
2504 difRange->SetEnd(range->GetEndContainer(), range->EndOffset());
2505 SelectFrames(presContext, difRange, true); // must reselect last node
2506 // maybe more
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)) {
2512 aRv.Throw(res);
2513 return;
2516 SetDirection(eDirNext);
2517 range->SetEnd(aContainer, aOffset, aRv);
2518 if (aRv.Failed()) {
2519 return;
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)) {
2526 res = tmp;
2528 if (NS_FAILED(res)) {
2529 aRv.Throw(res);
2530 return;
2532 res = SetAnchorFocusToRange(range);
2533 if (NS_FAILED(res)) {
2534 aRv.Throw(res);
2535 return;
2537 // deselect from 1 to a
2538 SelectFrames(presContext, difRange, false);
2539 } else {
2540 res = SetAnchorFocusToRange(range);
2541 if (NS_FAILED(res)) {
2542 aRv.Throw(res);
2543 return;
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,
2552 aOffset);
2553 if (NS_FAILED(res)) {
2554 aRv.Throw(res);
2555 return;
2557 SetDirection(eDirPrevious);
2558 range->SetStart(aContainer, aOffset, aRv);
2559 if (aRv.Failed()) {
2560 return;
2563 res = SetAnchorFocusToRange(range);
2564 if (NS_FAILED(res)) {
2565 aRv.Throw(res);
2566 return;
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);
2578 if (aRv.Failed()) {
2579 return;
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,
2585 focusOffset);
2586 nsresult tmp = SetAnchorFocusToRange(range);
2587 if (NS_FAILED(tmp)) {
2588 res = tmp;
2590 if (NS_FAILED(res)) {
2591 aRv.Throw(res);
2592 return;
2594 SelectFrames(presContext, difRange, false);
2595 } else {
2596 res = SetAnchorFocusToRange(range);
2597 if (NS_FAILED(res)) {
2598 aRv.Throw(res);
2599 return;
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);
2608 if (aRv.Failed()) {
2609 return;
2611 SetDirection(eDirPrevious);
2612 res = difRange->SetStartAndEnd(range->GetStartContainer(),
2613 range->StartOffset(), focusNode,
2614 focusOffset);
2615 if (NS_FAILED(res)) {
2616 aRv.Throw(res);
2617 return;
2620 SelectFrames(presContext, difRange, true);
2621 res = SetAnchorFocusToRange(range);
2622 if (NS_FAILED(res)) {
2623 aRv.Throw(res);
2624 return;
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);
2642 #endif
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);
2651 mCalledByJS = true;
2652 SelectAllChildren(aNode, aRv);
2655 void Selection::SelectAllChildren(nsINode& aNode, ErrorResult& aRv) {
2656 if (aNode.NodeType() == nsINode::DOCUMENT_TYPE_NODE) {
2657 aRv.ThrowInvalidNodeTypeError(kNoDocumentTypeNodeError);
2658 return;
2661 if (!HasSameRootOrSameComposedDoc(aNode)) {
2662 // Return with no error
2663 return;
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()),
2674 eDirNext, aRv);
2677 bool Selection::ContainsNode(nsINode& aNode, bool aAllowPartial,
2678 ErrorResult& aRv) {
2679 nsresult rv;
2680 if (mStyledRanges.Length() == 0) {
2681 return false;
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();
2689 } else {
2690 nodeLength = aNode.GetChildCount();
2693 nsTArray<nsRange*> overlappingRanges;
2694 rv = GetRangesForIntervalArray(&aNode, 0, &aNode, nodeLength, false,
2695 &overlappingRanges);
2696 if (NS_FAILED(rv)) {
2697 aRv.Throw(rv);
2698 return false;
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) {
2704 return true;
2707 // text nodes always count as inside
2708 if (nodeAsCharData) {
2709 return true;
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) {
2720 return true;
2724 return false;
2727 class PointInRectChecker : public mozilla::RectCallback {
2728 public:
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; }
2738 private:
2739 nsPoint mPoint;
2740 bool mMatchFound;
2743 bool Selection::ContainsPoint(const nsPoint& aPoint) {
2744 if (IsCollapsed()) {
2745 return false;
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);
2752 MOZ_ASSERT(range);
2753 nsRange::CollectClientRectsAndText(
2754 &checker, nullptr, range, range->GetStartContainer(),
2755 range->StartOffset(), range->GetEndContainer(), range->EndOffset(),
2756 true, false);
2757 if (checker.MatchFound()) {
2758 return true;
2761 return false;
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,
2796 nsRect* aRect) {
2797 if (!mFrameSelection) return nullptr; // nothing to do
2799 NS_ENSURE_TRUE(aRect, nullptr);
2801 aRect->SetRect(0, 0, 0, 0);
2803 switch (aRegion) {
2804 case nsISelectionController::SELECTION_ANCHOR_REGION:
2805 case nsISelectionController::SELECTION_FOCUS_REGION:
2806 return GetSelectionEndPointGeometry(aRegion, aRect);
2807 case nsISelectionController::SELECTION_WHOLE_SELECTION:
2808 break;
2809 default:
2810 return nullptr;
2813 NS_ASSERTION(aRegion == nsISelectionController::SELECTION_WHOLE_SELECTION,
2814 "should only be SELECTION_WHOLE_SELECTION here");
2816 nsRect anchorRect;
2817 nsIFrame* anchorFrame = GetSelectionEndPointGeometry(
2818 nsISelectionController::SELECTION_ANCHOR_REGION, &anchorRect);
2819 if (!anchorFrame) return nullptr;
2821 nsRect focusRect;
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);
2832 return anchorFrame;
2835 nsIFrame* Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion,
2836 nsRect* aRect) {
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;
2847 switch (aRegion) {
2848 case nsISelectionController::SELECTION_ANCHOR_REGION:
2849 node = GetAnchorNode();
2850 nodeOffset = AnchorOffset();
2851 break;
2852 case nsISelectionController::SELECTION_FOCUS_REGION:
2853 node = GetFocusNode();
2854 nodeOffset = FocusOffset();
2855 break;
2856 default:
2857 return nullptr;
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();
2875 nsPoint pt(0, 0);
2876 if (isText) {
2877 nsIFrame* childFrame = nullptr;
2878 frameOffset = 0;
2879 nsresult rv = frame->GetChildFrameContainingOffset(
2880 nodeOffset, mFrameSelection->GetHint(), &frameOffset, &childFrame);
2881 if (NS_FAILED(rv)) return nullptr;
2882 if (!childFrame) return nullptr;
2884 frame = childFrame;
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.
2892 if (isText) {
2893 aRect->x = pt.x;
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());
2900 return frame;
2903 NS_IMETHODIMP
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,
2912 mFlags | flags);
2913 return NS_OK;
2916 nsresult Selection::PostScrollSelectionIntoViewEvent(SelectionRegion aRegion,
2917 int32_t aFlags,
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());
2933 return NS_OK;
2936 void Selection::ScrollIntoView(int16_t aRegion, bool aIsSynchronous,
2937 WhereToScroll aVPercent, WhereToScroll aHPercent,
2938 ErrorResult& aRv) {
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)) {
2943 aRv.Throw(rv);
2947 nsresult Selection::ScrollIntoView(SelectionRegion aRegion,
2948 ScrollAxis aVertical, ScrollAxis aHorizontal,
2949 int32_t aFlags) {
2950 if (!mFrameSelection) {
2951 return NS_ERROR_NOT_INITIALIZED;
2954 RefPtr<PresShell> presShell = mFrameSelection->GetPresShell();
2955 if (!presShell || !presShell->GetDocument()) {
2956 return NS_OK;
2959 if (mFrameSelection->IsBatching()) {
2960 return NS_OK;
2963 if (!(aFlags & Selection::SCROLL_SYNCHRONOUS))
2964 return PostScrollSelectionIntoViewEvent(aRegion, aFlags, aVertical,
2965 aHorizontal);
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;
2982 if (!presShell) {
2983 return NS_OK;
2988 // Scroll the selection region into view.
2991 nsRect rect;
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);
3010 return NS_OK;
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;
3027 MOZ_ASSERT(range);
3028 nsINode* commonAncestorNode = range->GetClosestCommonInclusiveAncestor();
3029 if (!commonAncestorNode || !commonAncestorNode->IsContent()) {
3030 return nullptr;
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) {
3038 return nullptr;
3040 if (!editingHost) {
3041 editingHost = foundEditingHost;
3042 continue;
3044 if (editingHost == foundEditingHost) {
3045 continue;
3047 if (foundEditingHost->IsInclusiveDescendantOf(editingHost)) {
3048 continue;
3050 if (editingHost->IsInclusiveDescendantOf(foundEditingHost)) {
3051 editingHost = foundEditingHost;
3052 continue;
3054 // editingHost and foundEditingHost are not a descendant of the other.
3055 // So, there is no common editing host.
3056 return nullptr;
3058 return editingHost;
3061 void Selection::StyledRanges::MaybeFocusCommonEditingHost(
3062 PresShell* aPresShell) const {
3063 if (!aPresShell) {
3064 return;
3067 nsPresContext* presContext = aPresShell->GetPresContext();
3068 if (!presContext) {
3069 return;
3072 Document* document = aPresShell->GetDocument();
3073 if (!document) {
3074 return;
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();
3136 return;
3138 if (mSelectionListeners.IsEmpty()) {
3139 // If there are no selection listeners, we're done!
3140 return;
3143 nsCOMPtr<Document> doc;
3144 PresShell* presShell = GetPresShell();
3145 if (presShell) {
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
3176 // keep it alive.
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()) {
3218 return;
3221 for (uint32_t rangeIdx = 0; rangeIdx < RangeCount(); ++rangeIdx) {
3222 RefPtr<nsRange> range = GetRangeAt(rangeIdx);
3223 range->DeleteContents(aRv);
3224 if (aRv.Failed()) {
3225 return;
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());
3236 #ifdef DEBUG
3237 else {
3238 printf("Don't know how to set selection back past frame boundary\n");
3240 #endif
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);
3247 return;
3250 if (!GetAnchorFocusRange() || !GetFocusNode()) {
3251 return;
3254 if (!aAlter.LowerCaseEqualsLiteral("move") &&
3255 !aAlter.LowerCaseEqualsLiteral("extend")) {
3256 aRv.ThrowSyntaxError(
3257 R"(The first argument must be one of: "move" or "extend")");
3258 return;
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")");
3267 return;
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);
3300 return;
3301 } else {
3302 aRv.ThrowSyntaxError(
3303 R"(The granularity argument must be one of: "character", "word", "line", or "lineboundary")");
3304 return;
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;
3311 if (!extend) {
3312 RefPtr<nsINode> focusNode = GetFocusNode();
3313 // We should have checked earlier that there was a focus node.
3314 if (!focusNode) {
3315 aRv.Throw(NS_ERROR_UNEXPECTED);
3316 return;
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;
3331 forward = !forward;
3332 } else if (amount == eSelectEndLine) {
3333 amount = eSelectBeginLine;
3334 forward = !forward;
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();
3350 if (!presShell) {
3351 return;
3353 presShell->CompleteMove(forward, extend);
3357 void Selection::SetBaseAndExtentJS(nsINode& aAnchorNode, uint32_t aAnchorOffset,
3358 nsINode& aFocusNode, uint32_t aFocusOffset,
3359 ErrorResult& aRv) {
3360 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
3361 mCalledByJS = true;
3362 SetBaseAndExtent(aAnchorNode, aAnchorOffset, aFocusNode, aFocusOffset, aRv);
3365 void Selection::SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset,
3366 nsINode& aFocusNode, uint32_t aFocusOffset,
3367 ErrorResult& aRv) {
3368 if (aAnchorOffset > aAnchorNode.Length()) {
3369 aRv.ThrowIndexSizeError(nsPrintfCString(
3370 "The anchor offset value %u is out of range", aAnchorOffset));
3371 return;
3373 if (aFocusOffset > aFocusNode.Length()) {
3374 aRv.ThrowIndexSizeError(nsPrintfCString(
3375 "The focus offset value %u is out of range", aFocusOffset));
3376 return;
3379 SetBaseAndExtent(RawRangeBoundary{&aAnchorNode, aAnchorOffset},
3380 RawRangeBoundary{&aFocusNode, aFocusOffset}, aRv);
3383 void Selection::SetBaseAndExtentInternal(InLimiter aInLimiter,
3384 const RawRangeBoundary& aAnchorRef,
3385 const RawRangeBoundary& aFocusRef,
3386 ErrorResult& aRv) {
3387 if (!mFrameSelection) {
3388 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
3389 return;
3392 if (NS_WARN_IF(!aAnchorRef.IsSet()) || NS_WARN_IF(!aFocusRef.IsSet())) {
3393 aRv.Throw(NS_ERROR_INVALID_ARG);
3394 return;
3397 if (!HasSameRootOrSameComposedDoc(*aAnchorRef.Container()) ||
3398 !HasSameRootOrSameComposedDoc(*aFocusRef.Container())) {
3399 // Return with no error
3400 return;
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);
3412 return;
3415 // If there's no `order`, the range will be collapsed, unless another error is
3416 // detected before.
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);
3427 ErrorResult error;
3428 SetStartAndEndInternal(
3429 InLimiter::eYes, RawRangeBoundary(&aStartContainer, aStartOffset),
3430 RawRangeBoundary(&aEndContainer, aEndOffset), aDirection, error);
3431 MOZ_TRY(error.StealNSResult());
3432 return Ok();
3435 void Selection::SetStartAndEndInternal(InLimiter aInLimiter,
3436 const RawRangeBoundary& aStartRef,
3437 const RawRangeBoundary& aEndRef,
3438 nsDirection aDirection,
3439 ErrorResult& aRv) {
3440 if (NS_WARN_IF(!aStartRef.IsSet()) || NS_WARN_IF(!aEndRef.IsSet())) {
3441 aRv.Throw(NS_ERROR_INVALID_ARG);
3442 return;
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);
3452 return;
3454 if (aStartRef.Container() != aEndRef.Container() &&
3455 !mFrameSelection->IsValidSelectionPoint(aEndRef.Container())) {
3456 aRv.Throw(NS_ERROR_FAILURE);
3457 return;
3461 RefPtr<nsRange> newRange = nsRange::Create(aStartRef, aEndRef, aRv);
3462 if (aRv.Failed()) {
3463 return;
3466 RemoveAllRanges(aRv);
3467 if (aRv.Failed()) {
3468 return;
3471 AddRangeAndSelectFramesAndNotifyListeners(*newRange, aRv);
3472 if (aRv.Failed()) {
3473 return;
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) {
3506 return NS_OK;
3509 frameSelection->mKbdBidiLevel = kbdBidiLevel;
3511 nsIFrame* focusFrame = GetPrimaryFrameForFocusNode(false);
3512 if (!focusFrame) {
3513 return NS_ERROR_FAILURE;
3516 auto [frameStart, frameEnd] = focusFrame->GetOffsets();
3517 RefPtr<nsPresContext> context = GetPresContext();
3518 mozilla::intl::BidiEmbeddingLevel levelBefore, levelAfter;
3519 if (!context) {
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
3528 // level
3529 levelBefore = levelAfter = level;
3530 else {
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);
3552 } else {
3553 frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(
3554 mozilla::intl::BidiEmbeddingLevel(level + 1));
3556 } else {
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);
3562 } else {
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();
3571 return NS_OK;
3574 void Selection::SetColors(const nsAString& aForegroundColor,
3575 const nsAString& aBackgroundColor,
3576 const nsAString& aAltForegroundColor,
3577 const nsAString& aAltBackgroundColor,
3578 ErrorResult& aRv) {
3579 if (mSelectionType != SelectionType::eFind) {
3580 aRv.Throw(NS_ERROR_FAILURE);
3581 return;
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);
3595 return;
3597 mCustomColors->mForegroundColor = Some(foregroundColor);
3598 } else {
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);
3608 return;
3610 mCustomColors->mBackgroundColor = Some(backgroundColor);
3611 } else {
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);
3621 return;
3623 mCustomColors->mAltForegroundColor = Some(altForegroundColor);
3624 } else {
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);
3634 return;
3636 mCustomColors->mAltBackgroundColor = Some(altBackgroundColor);
3637 } else {
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());