Bug 1892041 - Part 1: Update test262 features. r=spidermonkey-reviewers,dminor
[gecko.git] / dom / base / Selection.cpp
blob7983ef98f90b3259975e1cda7eae31abb6830655
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 "Selection.h"
13 #include "ErrorList.h"
14 #include "LayoutConstants.h"
15 #include "mozilla/AccessibleCaretEventHub.h"
16 #include "mozilla/Assertions.h"
17 #include "mozilla/AsyncEventDispatcher.h"
18 #include "mozilla/Attributes.h"
19 #include "mozilla/AutoCopyListener.h"
20 #include "mozilla/AutoRestore.h"
21 #include "mozilla/BasePrincipal.h"
22 #include "mozilla/CaretAssociationHint.h"
23 #include "mozilla/ContentIterator.h"
24 #include "mozilla/dom/Element.h"
25 #include "mozilla/dom/ChildIterator.h"
26 #include "mozilla/dom/SelectionBinding.h"
27 #include "mozilla/dom/ShadowRoot.h"
28 #include "mozilla/dom/StaticRange.h"
29 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
30 #include "mozilla/ErrorResult.h"
31 #include "mozilla/HTMLEditor.h"
32 #include "mozilla/IntegerRange.h"
33 #include "mozilla/intl/Bidi.h"
34 #include "mozilla/intl/BidiEmbeddingLevel.h"
35 #include "mozilla/Logging.h"
36 #include "mozilla/PresShell.h"
37 #include "mozilla/RangeBoundary.h"
38 #include "mozilla/RangeUtils.h"
39 #include "mozilla/SelectionMovementUtils.h"
40 #include "mozilla/StackWalk.h"
41 #include "mozilla/StaticPrefs_dom.h"
42 #include "mozilla/Telemetry.h"
43 #include "mozilla/Try.h"
45 #include "nsCOMPtr.h"
46 #include "nsDebug.h"
47 #include "nsDirection.h"
48 #include "nsString.h"
49 #include "nsFrameSelection.h"
50 #include "nsISelectionListener.h"
51 #include "nsDeviceContext.h"
52 #include "nsIContent.h"
53 #include "nsIContentInlines.h"
54 #include "nsRange.h"
55 #include "nsITableCellLayout.h"
56 #include "nsTArray.h"
57 #include "nsTableWrapperFrame.h"
58 #include "nsTableCellFrame.h"
59 #include "nsIScrollableFrame.h"
60 #include "nsCCUncollectableMarker.h"
61 #include "nsIDocumentEncoder.h"
62 #include "nsTextFragment.h"
63 #include <algorithm>
64 #include "nsContentUtils.h"
66 #include "nsGkAtoms.h"
67 #include "nsLayoutUtils.h"
68 #include "nsBidiPresUtils.h"
69 #include "nsTextFrame.h"
71 #include "nsThreadUtils.h"
73 #include "nsPresContext.h"
74 #include "nsCaret.h"
76 #include "nsITimer.h"
77 #include "mozilla/dom/Document.h"
78 #include "nsINamed.h"
80 #include "nsISelectionController.h" //for the enums
81 #include "nsCopySupport.h"
82 #include "nsIFrameInlines.h"
83 #include "nsRefreshDriver.h"
85 #include "nsError.h"
86 #include "nsViewManager.h"
88 #include "nsFocusManager.h"
89 #include "nsPIDOMWindow.h"
91 namespace mozilla {
92 // "Selection" logs only the calls of AddRangesForSelectableNodes and
93 // NotifySelectionListeners in debug level.
94 static LazyLogModule sSelectionLog("Selection");
95 // "SelectionAPI" logs all API calls (both internal ones and exposed to script
96 // ones) of normal selection which may change selection ranges.
97 // 3. Info: Calls of APIs
98 // 4. Debug: Call stacks with 7 ancestor callers of APIs
99 // 5. Verbose: Complete call stacks of APIs.
100 LazyLogModule sSelectionAPILog("SelectionAPI");
102 MOZ_ALWAYS_INLINE bool NeedsToLogSelectionAPI(dom::Selection& aSelection) {
103 return aSelection.Type() == SelectionType::eNormal &&
104 MOZ_LOG_TEST(sSelectionAPILog, LogLevel::Info);
107 void LogStackForSelectionAPI() {
108 if (!MOZ_LOG_TEST(sSelectionAPILog, LogLevel::Debug)) {
109 return;
111 static nsAutoCString* sBufPtr = nullptr;
112 MOZ_ASSERT(!sBufPtr);
113 nsAutoCString buf;
114 sBufPtr = &buf;
115 auto writer = [](const char* aBuf) { sBufPtr->Append(aBuf); };
116 const LogLevel logLevel = MOZ_LOG_TEST(sSelectionAPILog, LogLevel::Verbose)
117 ? LogLevel::Verbose
118 : LogLevel::Debug;
119 MozWalkTheStackWithWriter(writer, CallerPC(),
120 logLevel == LogLevel::Verbose
121 ? 0u /* all */
122 : 8u /* 8 inclusive ancestors */);
123 MOZ_LOG(sSelectionAPILog, logLevel, ("\n%s", buf.get()));
124 sBufPtr = nullptr;
127 static void LogSelectionAPI(const dom::Selection* aSelection,
128 const char* aFuncName) {
129 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
130 ("%p Selection::%s()", aSelection, aFuncName));
133 static void LogSelectionAPI(const dom::Selection* aSelection,
134 const char* aFuncName, const char* aArgName,
135 const nsINode* aNode) {
136 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
137 ("%p Selection::%s(%s=%s)", aSelection, aFuncName, aArgName,
138 aNode ? ToString(*aNode).c_str() : "nullptr"));
141 static void LogSelectionAPI(const dom::Selection* aSelection,
142 const char* aFuncName, const char* aArgName,
143 const dom::AbstractRange& aRange) {
144 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
145 ("%p Selection::%s(%s=%s)", aSelection, aFuncName, aArgName,
146 ToString(aRange).c_str()));
149 static void LogSelectionAPI(const dom::Selection* aSelection,
150 const char* aFuncName, const char* aArgName1,
151 const nsINode* aNode, const char* aArgName2,
152 uint32_t aOffset) {
153 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
154 ("%p Selection::%s(%s=%s, %s=%u)", aSelection, aFuncName, aArgName1,
155 aNode ? ToString(*aNode).c_str() : "nullptr", aArgName2, aOffset));
158 static void LogSelectionAPI(const dom::Selection* aSelection,
159 const char* aFuncName, const char* aArgName,
160 const RawRangeBoundary& aBoundary) {
161 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
162 ("%p Selection::%s(%s=%s)", aSelection, aFuncName, aArgName,
163 ToString(aBoundary).c_str()));
166 static void LogSelectionAPI(const dom::Selection* aSelection,
167 const char* aFuncName, const char* aArgName1,
168 const nsAString& aStr1, const char* aArgName2,
169 const nsAString& aStr2, const char* aArgName3,
170 const nsAString& aStr3) {
171 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
172 ("%p Selection::%s(%s=%s, %s=%s, %s=%s)", aSelection, aFuncName,
173 aArgName1, NS_ConvertUTF16toUTF8(aStr1).get(), aArgName2,
174 NS_ConvertUTF16toUTF8(aStr2).get(), aArgName3,
175 NS_ConvertUTF16toUTF8(aStr3).get()));
178 static void LogSelectionAPI(const dom::Selection* aSelection,
179 const char* aFuncName, const char* aNodeArgName1,
180 const nsINode& aNode1, const char* aOffsetArgName1,
181 uint32_t aOffset1, const char* aNodeArgName2,
182 const nsINode& aNode2, const char* aOffsetArgName2,
183 uint32_t aOffset2) {
184 if (&aNode1 == &aNode2 && aOffset1 == aOffset2) {
185 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
186 ("%p Selection::%s(%s=%s=%s, %s=%s=%u)", aSelection, aFuncName,
187 aNodeArgName1, aNodeArgName2, ToString(aNode1).c_str(),
188 aOffsetArgName1, aOffsetArgName2, aOffset1));
189 } else {
190 MOZ_LOG(
191 sSelectionAPILog, LogLevel::Info,
192 ("%p Selection::%s(%s=%s, %s=%u, %s=%s, %s=%u)", aSelection, aFuncName,
193 aNodeArgName1, ToString(aNode1).c_str(), aOffsetArgName1, aOffset1,
194 aNodeArgName2, ToString(aNode2).c_str(), aOffsetArgName2, aOffset2));
198 static void LogSelectionAPI(const dom::Selection* aSelection,
199 const char* aFuncName, const char* aNodeArgName1,
200 const nsINode& aNode1, const char* aOffsetArgName1,
201 uint32_t aOffset1, const char* aNodeArgName2,
202 const nsINode& aNode2, const char* aOffsetArgName2,
203 uint32_t aOffset2, const char* aDirArgName,
204 nsDirection aDirection, const char* aReasonArgName,
205 int16_t aReason) {
206 if (&aNode1 == &aNode2 && aOffset1 == aOffset2) {
207 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
208 ("%p Selection::%s(%s=%s=%s, %s=%s=%u, %s=%s, %s=%d)", aSelection,
209 aFuncName, aNodeArgName1, aNodeArgName2, ToString(aNode1).c_str(),
210 aOffsetArgName1, aOffsetArgName2, aOffset1, aDirArgName,
211 ToString(aDirection).c_str(), aReasonArgName, aReason));
212 } else {
213 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
214 ("%p Selection::%s(%s=%s, %s=%u, %s=%s, %s=%u, %s=%s, %s=%d)",
215 aSelection, aFuncName, aNodeArgName1, ToString(aNode1).c_str(),
216 aOffsetArgName1, aOffset1, aNodeArgName2, ToString(aNode2).c_str(),
217 aOffsetArgName2, aOffset2, aDirArgName,
218 ToString(aDirection).c_str(), aReasonArgName, aReason));
222 static void LogSelectionAPI(const dom::Selection* aSelection,
223 const char* aFuncName, const char* aArgName1,
224 const RawRangeBoundary& aBoundary1,
225 const char* aArgName2,
226 const RawRangeBoundary& aBoundary2) {
227 if (aBoundary1 == aBoundary2) {
228 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
229 ("%p Selection::%s(%s=%s=%s)", aSelection, aFuncName, aArgName1,
230 aArgName2, ToString(aBoundary1).c_str()));
231 } else {
232 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
233 ("%p Selection::%s(%s=%s, %s=%s)", aSelection, aFuncName, aArgName1,
234 ToString(aBoundary1).c_str(), aArgName2,
235 ToString(aBoundary2).c_str()));
238 } // namespace mozilla
240 using namespace mozilla;
241 using namespace mozilla::dom;
243 // #define DEBUG_TABLE 1
245 #ifdef PRINT_RANGE
246 static void printRange(nsRange* aDomRange);
247 # define DEBUG_OUT_RANGE(x) printRange(x)
248 #else
249 # define DEBUG_OUT_RANGE(x)
250 #endif // PRINT_RANGE
252 static constexpr nsLiteralCString kNoDocumentTypeNodeError =
253 "DocumentType nodes are not supported"_ns;
254 static constexpr nsLiteralCString kNoRangeExistsError =
255 "No selection range exists"_ns;
257 namespace mozilla {
259 /******************************************************************************
260 * Utility methods defined in nsISelectionListener.idl
261 ******************************************************************************/
263 nsCString SelectionChangeReasonsToCString(int16_t aReasons) {
264 nsCString reasons;
265 if (!aReasons) {
266 reasons.AssignLiteral("NO_REASON");
267 return reasons;
269 auto EnsureSeparator = [](nsCString& aString) -> void {
270 if (!aString.IsEmpty()) {
271 aString.AppendLiteral(" | ");
274 struct ReasonData {
275 int16_t mReason;
276 const char* mReasonStr;
278 ReasonData(int16_t aReason, const char* aReasonStr)
279 : mReason(aReason), mReasonStr(aReasonStr) {}
281 for (const ReasonData& reason :
282 {ReasonData(nsISelectionListener::DRAG_REASON, "DRAG_REASON"),
283 ReasonData(nsISelectionListener::MOUSEDOWN_REASON, "MOUSEDOWN_REASON"),
284 ReasonData(nsISelectionListener::MOUSEUP_REASON, "MOUSEUP_REASON"),
285 ReasonData(nsISelectionListener::KEYPRESS_REASON, "KEYPRESS_REASON"),
286 ReasonData(nsISelectionListener::SELECTALL_REASON, "SELECTALL_REASON"),
287 ReasonData(nsISelectionListener::COLLAPSETOSTART_REASON,
288 "COLLAPSETOSTART_REASON"),
289 ReasonData(nsISelectionListener::COLLAPSETOEND_REASON,
290 "COLLAPSETOEND_REASON"),
291 ReasonData(nsISelectionListener::IME_REASON, "IME_REASON"),
292 ReasonData(nsISelectionListener::JS_REASON, "JS_REASON")}) {
293 if (aReasons & reason.mReason) {
294 EnsureSeparator(reasons);
295 reasons.Append(reason.mReasonStr);
298 return reasons;
301 } // namespace mozilla
303 // #define DEBUG_SELECTION // uncomment for printf describing every collapse and
304 // extend. #define DEBUG_NAVIGATION
306 // #define DEBUG_TABLE_SELECTION 1
308 struct CachedOffsetForFrame {
309 CachedOffsetForFrame()
310 : mCachedFrameOffset(0, 0) // nsPoint ctor
312 mLastCaretFrame(nullptr),
313 mLastContentOffset(0),
314 mCanCacheFrameOffset(false) {}
316 nsPoint mCachedFrameOffset; // cached frame offset
317 nsIFrame* mLastCaretFrame; // store the frame the caret was last drawn in.
318 int32_t mLastContentOffset; // store last content offset
319 bool mCanCacheFrameOffset; // cached frame offset is valid?
322 class AutoScroller final : public nsITimerCallback, public nsINamed {
323 public:
324 NS_DECL_ISUPPORTS
326 explicit AutoScroller(nsFrameSelection* aFrameSelection)
327 : mFrameSelection(aFrameSelection),
328 mPresContext(0),
329 mPoint(0, 0),
330 mDelayInMs(30),
331 mFurtherScrollingAllowed(FurtherScrollingAllowed::kYes) {
332 MOZ_ASSERT(mFrameSelection);
335 MOZ_CAN_RUN_SCRIPT nsresult DoAutoScroll(nsIFrame* aFrame, nsPoint aPoint);
337 private:
338 // aPoint is relative to aPresContext's root frame
339 nsresult ScheduleNextDoAutoScroll(nsPresContext* aPresContext,
340 nsPoint& aPoint) {
341 if (NS_WARN_IF(mFurtherScrollingAllowed == FurtherScrollingAllowed::kNo)) {
342 return NS_ERROR_FAILURE;
345 mPoint = aPoint;
347 // Store the presentation context. The timer will be
348 // stopped by the selection if the prescontext is destroyed.
349 mPresContext = aPresContext;
351 mContent = PresShell::GetCapturingContent();
353 if (!mTimer) {
354 mTimer = NS_NewTimer(GetMainThreadSerialEventTarget());
355 if (!mTimer) {
356 return NS_ERROR_OUT_OF_MEMORY;
360 return mTimer->InitWithCallback(this, mDelayInMs, nsITimer::TYPE_ONE_SHOT);
363 public:
364 enum class FurtherScrollingAllowed { kYes, kNo };
366 void Stop(const FurtherScrollingAllowed aFurtherScrollingAllowed) {
367 MOZ_ASSERT((aFurtherScrollingAllowed == FurtherScrollingAllowed::kNo) ||
368 (mFurtherScrollingAllowed == FurtherScrollingAllowed::kYes));
370 if (mTimer) {
371 mTimer->Cancel();
372 mTimer = nullptr;
375 mContent = nullptr;
376 mFurtherScrollingAllowed = aFurtherScrollingAllowed;
379 void SetDelay(uint32_t aDelayInMs) { mDelayInMs = aDelayInMs; }
381 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Notify(nsITimer* timer) override {
382 if (mPresContext) {
383 AutoWeakFrame frame =
384 mContent ? mPresContext->GetPrimaryFrameFor(mContent) : nullptr;
385 if (!frame) {
386 return NS_OK;
388 mContent = nullptr;
390 nsPoint pt = mPoint - frame->GetOffsetTo(
391 mPresContext->PresShell()->GetRootFrame());
392 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
393 frameSelection->HandleDrag(frame, pt);
394 if (!frame.IsAlive()) {
395 return NS_OK;
398 NS_ASSERTION(frame->PresContext() == mPresContext, "document mismatch?");
399 DoAutoScroll(frame, pt);
401 return NS_OK;
404 NS_IMETHOD GetName(nsACString& aName) override {
405 aName.AssignLiteral("AutoScroller");
406 return NS_OK;
409 protected:
410 virtual ~AutoScroller() {
411 if (mTimer) {
412 mTimer->Cancel();
416 private:
417 nsFrameSelection* const mFrameSelection;
418 nsPresContext* mPresContext;
419 // relative to mPresContext's root frame
420 nsPoint mPoint;
421 nsCOMPtr<nsITimer> mTimer;
422 nsCOMPtr<nsIContent> mContent;
423 uint32_t mDelayInMs;
424 FurtherScrollingAllowed mFurtherScrollingAllowed;
427 NS_IMPL_ISUPPORTS(AutoScroller, nsITimerCallback, nsINamed)
429 #ifdef PRINT_RANGE
430 void printRange(nsRange* aDomRange) {
431 if (!aDomRange) {
432 printf("NULL Range\n");
434 nsINode* startNode = aDomRange->GetStartContainer();
435 nsINode* endNode = aDomRange->GetEndContainer();
436 int32_t startOffset = aDomRange->StartOffset();
437 int32_t endOffset = aDomRange->EndOffset();
439 printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n",
440 (unsigned long)aDomRange, (unsigned long)startNode, (long)startOffset,
441 (unsigned long)endNode, (long)endOffset);
443 #endif /* PRINT_RANGE */
445 void Selection::Stringify(nsAString& aResult, FlushFrames aFlushFrames) {
446 if (aFlushFrames == FlushFrames::Yes) {
447 // We need FlushType::Frames here to make sure frames have been created for
448 // the selected content. Use mFrameSelection->GetPresShell() which returns
449 // null if the Selection has been disconnected (the shell is Destroyed).
450 RefPtr<PresShell> presShell =
451 mFrameSelection ? mFrameSelection->GetPresShell() : nullptr;
452 if (!presShell) {
453 aResult.Truncate();
454 return;
456 presShell->FlushPendingNotifications(FlushType::Frames);
459 IgnoredErrorResult rv;
460 ToStringWithFormat(u"text/plain"_ns, nsIDocumentEncoder::SkipInvisibleContent,
461 0, aResult, rv);
462 if (rv.Failed()) {
463 aResult.Truncate();
467 void Selection::ToStringWithFormat(const nsAString& aFormatType,
468 uint32_t aFlags, int32_t aWrapCol,
469 nsAString& aReturn, ErrorResult& aRv) {
470 nsCOMPtr<nsIDocumentEncoder> encoder =
471 do_createDocumentEncoder(NS_ConvertUTF16toUTF8(aFormatType).get());
472 if (!encoder) {
473 aRv.Throw(NS_ERROR_FAILURE);
474 return;
477 PresShell* presShell = GetPresShell();
478 if (!presShell) {
479 aRv.Throw(NS_ERROR_FAILURE);
480 return;
483 Document* doc = presShell->GetDocument();
485 // Flags should always include OutputSelectionOnly if we're coming from here:
486 aFlags |= nsIDocumentEncoder::OutputSelectionOnly;
487 nsAutoString readstring;
488 readstring.Assign(aFormatType);
489 nsresult rv = encoder->Init(doc, readstring, aFlags);
490 if (NS_FAILED(rv)) {
491 aRv.Throw(rv);
492 return;
495 encoder->SetSelection(this);
496 if (aWrapCol != 0) encoder->SetWrapColumn(aWrapCol);
498 rv = encoder->EncodeToString(aReturn);
499 if (NS_FAILED(rv)) {
500 aRv.Throw(rv);
504 nsresult Selection::SetInterlinePosition(InterlinePosition aInterlinePosition) {
505 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
506 MOZ_ASSERT(aInterlinePosition != InterlinePosition::Undefined);
508 if (!mFrameSelection) {
509 return NS_ERROR_NOT_INITIALIZED; // Can't do selection
512 mFrameSelection->SetHint(aInterlinePosition ==
513 InterlinePosition::StartOfNextLine
514 ? CaretAssociationHint::After
515 : CaretAssociationHint::Before);
516 return NS_OK;
519 Selection::InterlinePosition Selection::GetInterlinePosition() const {
520 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
522 if (!mFrameSelection) {
523 return InterlinePosition::Undefined;
525 return mFrameSelection->GetHint() == CaretAssociationHint::After
526 ? InterlinePosition::StartOfNextLine
527 : InterlinePosition::EndOfLine;
530 void Selection::SetInterlinePositionJS(bool aHintRight, ErrorResult& aRv) {
531 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
533 aRv = SetInterlinePosition(aHintRight ? InterlinePosition::StartOfNextLine
534 : InterlinePosition::EndOfLine);
537 bool Selection::GetInterlinePositionJS(ErrorResult& aRv) const {
538 const InterlinePosition interlinePosition = GetInterlinePosition();
539 if (interlinePosition == InterlinePosition::Undefined) {
540 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
541 return false;
543 return interlinePosition == InterlinePosition::StartOfNextLine;
546 static bool IsEditorNode(const nsINode* aNode) {
547 if (!aNode) {
548 return false;
551 if (aNode->IsEditable()) {
552 return true;
555 auto* element = Element::FromNode(aNode);
556 return element && element->State().HasState(ElementState::READWRITE);
559 bool Selection::IsEditorSelection() const {
560 return IsEditorNode(GetFocusNode());
563 Nullable<int16_t> Selection::GetCaretBidiLevel(
564 mozilla::ErrorResult& aRv) const {
565 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
567 if (!mFrameSelection) {
568 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
569 return Nullable<int16_t>();
571 mozilla::intl::BidiEmbeddingLevel caretBidiLevel =
572 static_cast<mozilla::intl::BidiEmbeddingLevel>(
573 mFrameSelection->GetCaretBidiLevel());
574 return (caretBidiLevel & BIDI_LEVEL_UNDEFINED)
575 ? Nullable<int16_t>()
576 : Nullable<int16_t>(caretBidiLevel);
579 void Selection::SetCaretBidiLevel(const Nullable<int16_t>& aCaretBidiLevel,
580 mozilla::ErrorResult& aRv) {
581 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
583 if (!mFrameSelection) {
584 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
585 return;
587 if (aCaretBidiLevel.IsNull()) {
588 mFrameSelection->UndefineCaretBidiLevel();
589 } else {
590 mFrameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(
591 mozilla::intl::BidiEmbeddingLevel(aCaretBidiLevel.Value()));
596 * Test whether the supplied range points to a single table element.
597 * Result is one of the TableSelectionMode constants. "None" means
598 * a table element isn't selected.
600 // TODO: Figure out TableSelectionMode::Column and TableSelectionMode::AllCells
601 static nsresult GetTableSelectionMode(const nsRange& aRange,
602 TableSelectionMode* aTableSelectionType) {
603 if (!aTableSelectionType) {
604 return NS_ERROR_NULL_POINTER;
607 *aTableSelectionType = TableSelectionMode::None;
609 nsINode* startNode = aRange.GetStartContainer();
610 if (!startNode) {
611 return NS_ERROR_FAILURE;
614 nsINode* endNode = aRange.GetEndContainer();
615 if (!endNode) {
616 return NS_ERROR_FAILURE;
619 // Not a single selected node
620 if (startNode != endNode) {
621 return NS_OK;
624 nsIContent* child = aRange.GetChildAtStartOffset();
626 // Not a single selected node
627 if (!child || child->GetNextSibling() != aRange.GetChildAtEndOffset()) {
628 return NS_OK;
631 if (!startNode->IsHTMLElement()) {
632 // Implies a check for being an element; if we ever make this work
633 // for non-HTML, need to keep checking for elements.
634 return NS_OK;
637 if (startNode->IsHTMLElement(nsGkAtoms::tr)) {
638 *aTableSelectionType = TableSelectionMode::Cell;
639 } else // check to see if we are selecting a table or row (column and all
640 // cells not done yet)
642 if (child->IsHTMLElement(nsGkAtoms::table)) {
643 *aTableSelectionType = TableSelectionMode::Table;
644 } else if (child->IsHTMLElement(nsGkAtoms::tr)) {
645 *aTableSelectionType = TableSelectionMode::Row;
649 return NS_OK;
652 nsresult Selection::MaybeAddTableCellRange(nsRange& aRange,
653 Maybe<size_t>* aOutIndex) {
654 if (!aOutIndex) {
655 return NS_ERROR_NULL_POINTER;
658 MOZ_ASSERT(aOutIndex->isNothing());
660 if (!mFrameSelection) {
661 return NS_OK;
664 // Get if we are adding a cell selection and the row, col of cell if we are
665 TableSelectionMode tableMode;
666 nsresult result = GetTableSelectionMode(aRange, &tableMode);
667 if (NS_FAILED(result)) return result;
669 // If not adding a cell range, we are done here
670 if (tableMode != TableSelectionMode::Cell) {
671 mFrameSelection->mTableSelection.mMode = tableMode;
672 // Don't fail if range isn't a selected cell, aDidAddRange tells caller if
673 // we didn't proceed
674 return NS_OK;
677 // Set frame selection mode only if not already set to a table mode
678 // so we don't lose the select row and column flags (not detected by
679 // getTableCellLocation)
680 if (mFrameSelection->mTableSelection.mMode == TableSelectionMode::None) {
681 mFrameSelection->mTableSelection.mMode = tableMode;
684 return AddRangesForSelectableNodes(&aRange, aOutIndex,
685 DispatchSelectstartEvent::Maybe);
688 Selection::Selection(SelectionType aSelectionType,
689 nsFrameSelection* aFrameSelection)
690 : mFrameSelection(aFrameSelection),
691 mCachedOffsetForFrame(nullptr),
692 mDirection(eDirNext),
693 mSelectionType(aSelectionType),
694 mCustomColors(nullptr),
695 mSelectionChangeBlockerCount(0),
696 mUserInitiated(false),
697 mCalledByJS(false),
698 mNotifyAutoCopy(false) {}
700 Selection::~Selection() { Disconnect(); }
702 void Selection::Disconnect() {
703 RemoveAnchorFocusRange();
705 mStyledRanges.UnregisterSelection();
707 if (mAutoScroller) {
708 mAutoScroller->Stop(AutoScroller::FurtherScrollingAllowed::kNo);
709 mAutoScroller = nullptr;
712 mScrollEvent.Revoke();
714 if (mCachedOffsetForFrame) {
715 delete mCachedOffsetForFrame;
716 mCachedOffsetForFrame = nullptr;
720 Document* Selection::GetParentObject() const {
721 PresShell* presShell = GetPresShell();
722 return presShell ? presShell->GetDocument() : nullptr;
725 DocGroup* Selection::GetDocGroup() const {
726 PresShell* presShell = GetPresShell();
727 if (!presShell) {
728 return nullptr;
730 Document* doc = presShell->GetDocument();
731 return doc ? doc->GetDocGroup() : nullptr;
734 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Selection)
736 MOZ_CAN_RUN_SCRIPT_BOUNDARY
737 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Selection)
738 // Unlink the selection listeners *before* we do RemoveAllRangesInternal since
739 // we don't want to notify the listeners during JS GC (they could be
740 // in JS!).
741 tmp->mNotifyAutoCopy = false;
742 if (tmp->mAccessibleCaretEventHub) {
743 tmp->StopNotifyingAccessibleCaretEventHub();
745 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionChangeEventDispatcher)
746 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionListeners)
747 MOZ_KnownLive(tmp)->RemoveAllRangesInternal(IgnoreErrors());
748 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameSelection)
749 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightData.mHighlight)
750 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
751 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
752 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
753 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
754 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Selection)
756 uint32_t i, count = tmp->mStyledRanges.Length();
757 for (i = 0; i < count; ++i) {
758 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyledRanges.mRanges[i].mRange)
760 count = tmp->mStyledRanges.mInvalidStaticRanges.Length();
761 for (i = 0; i < count; ++i) {
762 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
763 mStyledRanges.mInvalidStaticRanges[i].mRange);
766 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorFocusRange)
767 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameSelection)
768 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightData.mHighlight)
769 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionChangeEventDispatcher)
770 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionListeners)
771 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
773 // QueryInterface implementation for Selection
774 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Selection)
775 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
776 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
777 NS_INTERFACE_MAP_ENTRY(nsISupports)
778 NS_INTERFACE_MAP_END
780 NS_IMPL_CYCLE_COLLECTING_ADDREF(Selection)
781 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Selection, Disconnect())
783 const RangeBoundary& Selection::AnchorRef(
784 AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) const {
785 if (!mAnchorFocusRange) {
786 static RangeBoundary sEmpty;
787 return sEmpty;
790 if (GetDirection() == eDirNext) {
791 return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
792 ? mAnchorFocusRange->MayCrossShadowBoundaryStartRef()
793 : mAnchorFocusRange->StartRef();
796 return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
797 ? mAnchorFocusRange->MayCrossShadowBoundaryEndRef()
798 : mAnchorFocusRange->EndRef();
801 const RangeBoundary& Selection::FocusRef(
802 AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) const {
803 if (!mAnchorFocusRange) {
804 static RangeBoundary sEmpty;
805 return sEmpty;
808 if (GetDirection() == eDirNext) {
809 return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
810 ? mAnchorFocusRange->MayCrossShadowBoundaryEndRef()
811 : mAnchorFocusRange->EndRef();
813 return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
814 ? mAnchorFocusRange->MayCrossShadowBoundaryStartRef()
815 : mAnchorFocusRange->StartRef();
818 void Selection::SetAnchorFocusRange(size_t aIndex) {
819 if (aIndex >= mStyledRanges.Length()) {
820 return;
822 // Highlight selections may contain static ranges.
823 MOZ_ASSERT(mSelectionType != SelectionType::eHighlight);
824 AbstractRange* anchorFocusRange = mStyledRanges.mRanges[aIndex].mRange;
825 mAnchorFocusRange = anchorFocusRange->AsDynamicRange();
828 static int32_t CompareToRangeStart(const nsINode& aCompareNode,
829 uint32_t aCompareOffset,
830 const AbstractRange& aRange,
831 nsContentUtils::NodeIndexCache* aCache) {
832 MOZ_ASSERT(aRange.GetMayCrossShadowBoundaryStartContainer());
833 nsINode* start = aRange.GetMayCrossShadowBoundaryStartContainer();
834 // If the nodes that we're comparing are not in the same document, assume that
835 // aCompareNode will fall at the end of the ranges.
836 if (aCompareNode.GetComposedDoc() != start->GetComposedDoc() ||
837 !start->GetComposedDoc()) {
838 NS_WARNING(
839 "`CompareToRangeStart` couldn't compare nodes, pretending some order.");
840 return 1;
843 // The points are in the same subtree, hence there has to be an order.
844 return *nsContentUtils::ComparePoints(
845 &aCompareNode, aCompareOffset, start,
846 aRange.MayCrossShadowBoundaryStartOffset(), aCache);
849 static int32_t CompareToRangeStart(const nsINode& aCompareNode,
850 uint32_t aCompareOffset,
851 const AbstractRange& aRange) {
852 return CompareToRangeStart(aCompareNode, aCompareOffset, aRange, nullptr);
855 static int32_t CompareToRangeEnd(const nsINode& aCompareNode,
856 uint32_t aCompareOffset,
857 const AbstractRange& aRange) {
858 MOZ_ASSERT(aRange.IsPositioned());
859 nsINode* end = aRange.GetMayCrossShadowBoundaryEndContainer();
860 // If the nodes that we're comparing are not in the same document or in the
861 // same subtree, assume that aCompareNode will fall at the end of the ranges.
862 if (aCompareNode.GetComposedDoc() != end->GetComposedDoc() ||
863 !end->GetComposedDoc()) {
864 NS_WARNING(
865 "`CompareToRangeEnd` couldn't compare nodes, pretending some order.");
866 return 1;
869 // The points are in the same subtree, hence there has to be an order.
870 return *nsContentUtils::ComparePoints(
871 &aCompareNode, aCompareOffset, end,
872 aRange.MayCrossShadowBoundaryEndOffset());
875 // static
876 size_t Selection::StyledRanges::FindInsertionPoint(
877 const nsTArray<StyledRange>* aElementArray, const nsINode& aPointNode,
878 uint32_t aPointOffset,
879 int32_t (*aComparator)(const nsINode&, uint32_t, const AbstractRange&)) {
880 int32_t beginSearch = 0;
881 int32_t endSearch = aElementArray->Length(); // one beyond what to check
883 if (endSearch) {
884 int32_t center = endSearch - 1; // Check last index, then binary search
885 do {
886 const AbstractRange* range = (*aElementArray)[center].mRange;
888 int32_t cmp{aComparator(aPointNode, aPointOffset, *range)};
890 if (cmp < 0) { // point < cur
891 endSearch = center;
892 } else if (cmp > 0) { // point > cur
893 beginSearch = center + 1;
894 } else { // found match, done
895 beginSearch = center;
896 break;
898 center = (endSearch - beginSearch) / 2 + beginSearch;
899 } while (endSearch - beginSearch > 0);
902 return AssertedCast<size_t>(beginSearch);
905 // Selection::SubtractRange
907 // A helper function that subtracts aSubtract from aRange, and adds
908 // 1 or 2 StyledRange objects representing the remaining non-overlapping
909 // difference to aOutput. It is assumed that the caller has checked that
910 // aRange and aSubtract do indeed overlap
912 // static
913 nsresult Selection::StyledRanges::SubtractRange(
914 StyledRange& aRange, nsRange& aSubtract, nsTArray<StyledRange>* aOutput) {
915 AbstractRange* range = aRange.mRange;
916 if (NS_WARN_IF(!range->IsPositioned())) {
917 return NS_ERROR_UNEXPECTED;
920 if (range->GetStartContainer()->SubtreeRoot() !=
921 aSubtract.GetStartContainer()->SubtreeRoot()) {
922 // These are ranges for different shadow trees, we can't subtract them in
923 // any sensible way.
924 aOutput->InsertElementAt(0, aRange);
925 return NS_OK;
928 // First we want to compare to the range start
929 int32_t cmp{CompareToRangeStart(*range->GetStartContainer(),
930 range->StartOffset(), aSubtract)};
932 // Also, make a comparison to the range end
933 int32_t cmp2{CompareToRangeEnd(*range->GetEndContainer(), range->EndOffset(),
934 aSubtract)};
936 // If the existing range left overlaps the new range (aSubtract) then
937 // cmp < 0, and cmp2 < 0
938 // If it right overlaps the new range then cmp > 0 and cmp2 > 0
939 // If it fully contains the new range, then cmp < 0 and cmp2 > 0
941 if (cmp2 > 0) {
942 // We need to add a new StyledRange to the output, running from
943 // the end of aSubtract to the end of range
944 ErrorResult error;
945 RefPtr<nsRange> postOverlap =
946 nsRange::Create(aSubtract.EndRef(), range->EndRef(), error);
947 if (NS_WARN_IF(error.Failed())) {
948 return error.StealNSResult();
950 MOZ_ASSERT(postOverlap);
951 if (!postOverlap->Collapsed()) {
952 // XXX(Bug 1631371) Check if this should use a fallible operation as it
953 // pretended earlier.
954 aOutput->InsertElementAt(0, StyledRange(postOverlap));
955 (*aOutput)[0].mTextRangeStyle = aRange.mTextRangeStyle;
959 if (cmp < 0) {
960 // We need to add a new StyledRange to the output, running from
961 // the start of the range to the start of aSubtract
962 ErrorResult error;
963 RefPtr<nsRange> preOverlap =
964 nsRange::Create(range->StartRef(), aSubtract.StartRef(), error);
965 if (NS_WARN_IF(error.Failed())) {
966 return error.StealNSResult();
968 MOZ_ASSERT(preOverlap);
969 if (!preOverlap->Collapsed()) {
970 // XXX(Bug 1631371) Check if this should use a fallible operation as it
971 // pretended earlier.
972 aOutput->InsertElementAt(0, StyledRange(preOverlap));
973 (*aOutput)[0].mTextRangeStyle = aRange.mTextRangeStyle;
977 return NS_OK;
980 static void UserSelectRangesToAdd(nsRange* aItem,
981 nsTArray<RefPtr<nsRange>>& aRangesToAdd) {
982 // We cannot directly call IsEditorSelection() because we may be in an
983 // inconsistent state during Collapse() (we're cleared already but we haven't
984 // got a new focus node yet).
985 if (IsEditorNode(aItem->GetStartContainer()) &&
986 IsEditorNode(aItem->GetEndContainer())) {
987 // Don't mess with the selection ranges for editing, editor doesn't really
988 // deal well with multi-range selections.
989 aRangesToAdd.AppendElement(aItem);
990 } else {
991 aItem->ExcludeNonSelectableNodes(&aRangesToAdd);
995 static nsINode* DetermineSelectstartEventTarget(
996 const bool aSelectionEventsOnTextControlsEnabled, const nsRange& aRange) {
997 nsINode* target = aRange.GetStartContainer();
998 if (aSelectionEventsOnTextControlsEnabled) {
999 // Get the first element which isn't in a native anonymous subtree
1000 while (target && target->IsInNativeAnonymousSubtree()) {
1001 target = target->GetParent();
1003 } else {
1004 if (target->IsInNativeAnonymousSubtree()) {
1005 // This is a selection under a text control, so don't dispatch the
1006 // event.
1007 target = nullptr;
1010 return target;
1014 * @return true, iff the default action should be executed.
1016 static bool MaybeDispatchSelectstartEvent(
1017 const nsRange& aRange, const bool aSelectionEventsOnTextControlsEnabled,
1018 Document* aDocument) {
1019 nsCOMPtr<nsINode> selectstartEventTarget = DetermineSelectstartEventTarget(
1020 aSelectionEventsOnTextControlsEnabled, aRange);
1022 bool executeDefaultAction = true;
1024 if (selectstartEventTarget) {
1025 nsContentUtils::DispatchTrustedEvent(
1026 aDocument, selectstartEventTarget, u"selectstart"_ns, CanBubble::eYes,
1027 Cancelable::eYes, &executeDefaultAction);
1030 return executeDefaultAction;
1033 // static
1034 bool Selection::IsUserSelectionCollapsed(
1035 const nsRange& aRange, nsTArray<RefPtr<nsRange>>& aTempRangesToAdd) {
1036 MOZ_ASSERT(aTempRangesToAdd.IsEmpty());
1038 RefPtr<nsRange> scratchRange = aRange.CloneRange();
1039 UserSelectRangesToAdd(scratchRange, aTempRangesToAdd);
1040 const bool userSelectionCollapsed =
1041 (aTempRangesToAdd.Length() == 0) ||
1042 ((aTempRangesToAdd.Length() == 1) && aTempRangesToAdd[0]->Collapsed());
1044 aTempRangesToAdd.ClearAndRetainStorage();
1046 return userSelectionCollapsed;
1049 nsresult Selection::AddRangesForUserSelectableNodes(
1050 nsRange* aRange, Maybe<size_t>* aOutIndex,
1051 const DispatchSelectstartEvent aDispatchSelectstartEvent) {
1052 MOZ_ASSERT(mUserInitiated);
1053 MOZ_ASSERT(aOutIndex);
1054 MOZ_ASSERT(aOutIndex->isNothing());
1056 if (!aRange) {
1057 return NS_ERROR_NULL_POINTER;
1060 if (!aRange->IsPositioned()) {
1061 return NS_ERROR_UNEXPECTED;
1064 AutoTArray<RefPtr<nsRange>, 4> rangesToAdd;
1065 if (mStyledRanges.Length()) {
1066 aOutIndex->emplace(mStyledRanges.Length() - 1);
1069 Document* doc = GetDocument();
1071 if (aDispatchSelectstartEvent == DispatchSelectstartEvent::Maybe &&
1072 mSelectionType == SelectionType::eNormal && IsCollapsed() &&
1073 !IsBlockingSelectionChangeEvents()) {
1074 // We consider a selection to be starting if we are currently collapsed,
1075 // and the selection is becoming uncollapsed, and this is caused by a
1076 // user initiated event.
1078 // First, we generate the ranges to add with a scratch range, which is a
1079 // clone of the original range passed in. We do this seperately, because
1080 // the selectstart event could have caused the world to change, and
1081 // required ranges to be re-generated
1083 const bool userSelectionCollapsed =
1084 IsUserSelectionCollapsed(*aRange, rangesToAdd);
1085 MOZ_ASSERT(userSelectionCollapsed || nsContentUtils::IsSafeToRunScript());
1086 if (!userSelectionCollapsed && nsContentUtils::IsSafeToRunScript()) {
1087 // The spec currently doesn't say that we should dispatch this event
1088 // on text controls, so for now we only support doing that under a
1089 // pref, disabled by default.
1090 // See https://github.com/w3c/selection-api/issues/53.
1091 const bool executeDefaultAction = MaybeDispatchSelectstartEvent(
1092 *aRange,
1093 StaticPrefs::dom_select_events_textcontrols_selectstart_enabled(),
1094 doc);
1096 if (!executeDefaultAction) {
1097 return NS_OK;
1100 // As we potentially dispatched an event to the DOM, something could have
1101 // changed under our feet. Re-generate the rangesToAdd array, and
1102 // ensure that the range we are about to add is still valid.
1103 if (!aRange->IsPositioned()) {
1104 return NS_ERROR_UNEXPECTED;
1109 // Generate the ranges to add
1110 UserSelectRangesToAdd(aRange, rangesToAdd);
1111 size_t newAnchorFocusIndex =
1112 GetDirection() == eDirPrevious ? 0 : rangesToAdd.Length() - 1;
1113 for (size_t i = 0; i < rangesToAdd.Length(); ++i) {
1114 Maybe<size_t> index;
1115 // `MOZ_KnownLive` needed because of broken static analysis
1116 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1622253#c1).
1117 nsresult rv = mStyledRanges.MaybeAddRangeAndTruncateOverlaps(
1118 MOZ_KnownLive(rangesToAdd[i]), &index);
1119 NS_ENSURE_SUCCESS(rv, rv);
1120 if (i == newAnchorFocusIndex) {
1121 *aOutIndex = index;
1122 rangesToAdd[i]->SetIsGenerated(false);
1123 } else {
1124 rangesToAdd[i]->SetIsGenerated(true);
1127 return NS_OK;
1130 nsresult Selection::AddRangesForSelectableNodes(
1131 nsRange* aRange, Maybe<size_t>* aOutIndex,
1132 const DispatchSelectstartEvent aDispatchSelectstartEvent) {
1133 MOZ_ASSERT(aOutIndex);
1134 MOZ_ASSERT(aOutIndex->isNothing());
1136 if (!aRange) {
1137 return NS_ERROR_NULL_POINTER;
1140 if (!aRange->IsPositioned()) {
1141 return NS_ERROR_UNEXPECTED;
1144 MOZ_LOG(
1145 sSelectionLog, LogLevel::Debug,
1146 ("%s: selection=%p, type=%i, range=(%p, StartOffset=%u, EndOffset=%u)",
1147 __FUNCTION__, this, static_cast<int>(GetType()), aRange,
1148 aRange->StartOffset(), aRange->EndOffset()));
1150 if (mUserInitiated) {
1151 return AddRangesForUserSelectableNodes(aRange, aOutIndex,
1152 aDispatchSelectstartEvent);
1155 return mStyledRanges.MaybeAddRangeAndTruncateOverlaps(aRange, aOutIndex);
1158 nsresult Selection::StyledRanges::AddRangeAndIgnoreOverlaps(
1159 AbstractRange* aRange) {
1160 MOZ_ASSERT(aRange);
1161 MOZ_ASSERT(aRange->IsPositioned());
1162 MOZ_ASSERT(mSelection.mSelectionType == SelectionType::eHighlight);
1163 if (aRange->IsStaticRange() && !aRange->AsStaticRange()->IsValid()) {
1164 mInvalidStaticRanges.AppendElement(StyledRange(aRange));
1165 aRange->RegisterSelection(MOZ_KnownLive(mSelection));
1166 return NS_OK;
1169 // a common case is that we have no ranges yet
1170 if (mRanges.Length() == 0) {
1171 mRanges.AppendElement(StyledRange(aRange));
1172 aRange->RegisterSelection(MOZ_KnownLive(mSelection));
1173 return NS_OK;
1176 Maybe<size_t> maybeStartIndex, maybeEndIndex;
1177 nsresult rv =
1178 GetIndicesForInterval(aRange->GetStartContainer(), aRange->StartOffset(),
1179 aRange->GetEndContainer(), aRange->EndOffset(),
1180 false, maybeStartIndex, maybeEndIndex);
1181 NS_ENSURE_SUCCESS(rv, rv);
1183 size_t startIndex(0);
1184 if (maybeEndIndex.isNothing()) {
1185 // All ranges start after the given range. We can insert our range at
1186 // position 0.
1187 startIndex = 0;
1188 } else if (maybeStartIndex.isNothing()) {
1189 // All ranges end before the given range. We can insert our range at
1190 // the end of the array.
1191 startIndex = mRanges.Length();
1192 } else {
1193 startIndex = *maybeStartIndex;
1196 mRanges.InsertElementAt(startIndex, StyledRange(aRange));
1197 aRange->RegisterSelection(MOZ_KnownLive(mSelection));
1198 return NS_OK;
1201 nsresult Selection::StyledRanges::MaybeAddRangeAndTruncateOverlaps(
1202 nsRange* aRange, Maybe<size_t>* aOutIndex) {
1203 MOZ_ASSERT(aRange);
1204 MOZ_ASSERT(aRange->IsPositioned());
1205 MOZ_ASSERT(aOutIndex);
1206 MOZ_ASSERT(aOutIndex->isNothing());
1208 // a common case is that we have no ranges yet
1209 if (mRanges.Length() == 0) {
1210 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1211 // pretended earlier.
1212 mRanges.AppendElement(StyledRange(aRange));
1213 aRange->RegisterSelection(MOZ_KnownLive(mSelection));
1215 aOutIndex->emplace(0u);
1216 return NS_OK;
1219 Maybe<size_t> maybeStartIndex, maybeEndIndex;
1220 nsresult rv =
1221 GetIndicesForInterval(aRange->GetStartContainer(), aRange->StartOffset(),
1222 aRange->GetEndContainer(), aRange->EndOffset(),
1223 false, maybeStartIndex, maybeEndIndex);
1224 NS_ENSURE_SUCCESS(rv, rv);
1226 size_t startIndex, endIndex;
1227 if (maybeEndIndex.isNothing()) {
1228 // All ranges start after the given range. We can insert our range at
1229 // position 0, knowing there are no overlaps (handled below)
1230 startIndex = endIndex = 0;
1231 } else if (maybeStartIndex.isNothing()) {
1232 // All ranges end before the given range. We can insert our range at
1233 // the end of the array, knowing there are no overlaps (handled below)
1234 startIndex = endIndex = mRanges.Length();
1235 } else {
1236 startIndex = *maybeStartIndex;
1237 endIndex = *maybeEndIndex;
1240 // If the range is already contained in mRanges, silently
1241 // succeed
1242 const bool sameRange = HasEqualRangeBoundariesAt(*aRange, startIndex);
1243 if (sameRange) {
1244 aOutIndex->emplace(startIndex);
1245 return NS_OK;
1248 if (startIndex == endIndex) {
1249 // The new range doesn't overlap any existing ranges
1250 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1251 // pretended earlier.
1252 mRanges.InsertElementAt(startIndex, StyledRange(aRange));
1253 aRange->RegisterSelection(MOZ_KnownLive(mSelection));
1254 aOutIndex->emplace(startIndex);
1255 return NS_OK;
1258 // We now know that at least 1 existing range overlaps with the range that
1259 // we are trying to add. In fact, the only ranges of interest are those at
1260 // the two end points, startIndex and endIndex - 1 (which may point to the
1261 // same range) as these may partially overlap the new range. Any ranges
1262 // between these indices are fully overlapped by the new range, and so can be
1263 // removed.
1264 AutoTArray<StyledRange, 2> overlaps;
1265 overlaps.AppendElement(mRanges[startIndex]);
1266 if (endIndex - 1 != startIndex) {
1267 overlaps.AppendElement(mRanges[endIndex - 1]);
1270 // Remove all the overlapping ranges
1271 for (size_t i = startIndex; i < endIndex; ++i) {
1272 mRanges[i].mRange->UnregisterSelection(mSelection);
1274 mRanges.RemoveElementsAt(startIndex, endIndex - startIndex);
1276 AutoTArray<StyledRange, 3> temp;
1277 for (const size_t i : Reversed(IntegerRange(overlaps.Length()))) {
1278 nsresult rv = SubtractRange(overlaps[i], *aRange, &temp);
1279 NS_ENSURE_SUCCESS(rv, rv);
1282 // Insert the new element into our "leftovers" array
1283 // `aRange` is positioned, so it has to have a start container.
1284 size_t insertionPoint{FindInsertionPoint(&temp, *aRange->GetStartContainer(),
1285 aRange->StartOffset(),
1286 CompareToRangeStart)};
1288 temp.InsertElementAt(insertionPoint, StyledRange(aRange));
1290 // Merge the leftovers back in to mRanges
1291 mRanges.InsertElementsAt(startIndex, temp);
1293 for (uint32_t i = 0; i < temp.Length(); ++i) {
1294 if (temp[i].mRange->IsDynamicRange()) {
1295 MOZ_KnownLive(temp[i].mRange->AsDynamicRange())
1296 ->RegisterSelection(MOZ_KnownLive(mSelection));
1297 // `MOZ_KnownLive` is required because of
1298 // https://bugzilla.mozilla.org/show_bug.cgi?id=1622253.
1302 aOutIndex->emplace(startIndex + insertionPoint);
1303 return NS_OK;
1306 nsresult Selection::StyledRanges::RemoveRangeAndUnregisterSelection(
1307 AbstractRange& aRange) {
1308 // Find the range's index & remove it. We could use FindInsertionPoint to
1309 // get O(log n) time, but that requires many expensive DOM comparisons.
1310 // For even several thousand items, this is probably faster because the
1311 // comparisons are so fast.
1312 int32_t idx = -1;
1313 uint32_t i;
1314 for (i = 0; i < mRanges.Length(); i++) {
1315 if (mRanges[i].mRange == &aRange) {
1316 idx = (int32_t)i;
1317 break;
1320 if (idx < 0) return NS_ERROR_DOM_NOT_FOUND_ERR;
1322 mRanges.RemoveElementAt(idx);
1323 aRange.UnregisterSelection(mSelection);
1325 return NS_OK;
1327 nsresult Selection::RemoveCollapsedRanges() {
1328 if (NeedsToLogSelectionAPI(*this)) {
1329 LogSelectionAPI(this, __FUNCTION__);
1330 LogStackForSelectionAPI();
1333 return mStyledRanges.RemoveCollapsedRanges();
1336 nsresult Selection::StyledRanges::RemoveCollapsedRanges() {
1337 uint32_t i = 0;
1338 while (i < mRanges.Length()) {
1339 const AbstractRange* range = mRanges[i].mRange;
1340 // If nsRange::mCrossShadowBoundaryRange exists, it means
1341 // there's a cross boundary selection, so obviously
1342 // we shouldn't remove this range.
1343 const bool collapsed =
1344 range->Collapsed() && !range->MayCrossShadowBoundary();
1345 // Cross boundary range should always be uncollapsed.
1346 MOZ_ASSERT_IF(
1347 range->MayCrossShadowBoundary(),
1348 !range->AsDynamicRange()->CrossShadowBoundaryRangeCollapsed());
1350 if (collapsed) {
1351 nsresult rv = RemoveRangeAndUnregisterSelection(*mRanges[i].mRange);
1352 NS_ENSURE_SUCCESS(rv, rv);
1353 } else {
1354 ++i;
1357 return NS_OK;
1360 void Selection::Clear(nsPresContext* aPresContext) {
1361 RemoveAnchorFocusRange();
1363 mStyledRanges.UnregisterSelection();
1364 for (uint32_t i = 0; i < mStyledRanges.Length(); ++i) {
1365 SelectFrames(aPresContext, *mStyledRanges.mRanges[i].mRange, false);
1367 mStyledRanges.Clear();
1369 // Reset direction so for more dependable table selection range handling
1370 SetDirection(eDirNext);
1372 // If this was an ATTENTION selection, change it back to normal now
1373 if (mFrameSelection && mFrameSelection->GetDisplaySelection() ==
1374 nsISelectionController::SELECTION_ATTENTION) {
1375 mFrameSelection->SetDisplaySelection(nsISelectionController::SELECTION_ON);
1379 bool Selection::StyledRanges::HasEqualRangeBoundariesAt(
1380 const AbstractRange& aRange, size_t aRangeIndex) const {
1381 if (aRangeIndex < mRanges.Length()) {
1382 const AbstractRange* range = mRanges[aRangeIndex].mRange;
1383 return range->HasEqualBoundaries(aRange);
1385 return false;
1388 void Selection::GetRangesForInterval(nsINode& aBeginNode, uint32_t aBeginOffset,
1389 nsINode& aEndNode, uint32_t aEndOffset,
1390 bool aAllowAdjacent,
1391 nsTArray<RefPtr<nsRange>>& aReturn,
1392 mozilla::ErrorResult& aRv) {
1393 AutoTArray<nsRange*, 2> results;
1394 nsresult rv =
1395 GetDynamicRangesForIntervalArray(&aBeginNode, aBeginOffset, &aEndNode,
1396 aEndOffset, aAllowAdjacent, &results);
1397 if (NS_FAILED(rv)) {
1398 aRv.Throw(rv);
1399 return;
1402 aReturn.SetLength(results.Length());
1403 for (size_t i = 0; i < results.Length(); ++i) {
1404 aReturn[i] = results[i]; // AddRefs
1408 nsresult Selection::GetAbstractRangesForIntervalArray(
1409 nsINode* aBeginNode, uint32_t aBeginOffset, nsINode* aEndNode,
1410 uint32_t aEndOffset, bool aAllowAdjacent,
1411 nsTArray<AbstractRange*>* aRanges) {
1412 if (NS_WARN_IF(!aBeginNode)) {
1413 return NS_ERROR_UNEXPECTED;
1416 if (NS_WARN_IF(!aEndNode)) {
1417 return NS_ERROR_UNEXPECTED;
1420 aRanges->Clear();
1421 Maybe<size_t> maybeStartIndex, maybeEndIndex;
1422 nsresult res = mStyledRanges.GetIndicesForInterval(
1423 aBeginNode, aBeginOffset, aEndNode, aEndOffset, aAllowAdjacent,
1424 maybeStartIndex, maybeEndIndex);
1425 NS_ENSURE_SUCCESS(res, res);
1427 if (maybeStartIndex.isNothing() || maybeEndIndex.isNothing()) {
1428 return NS_OK;
1431 for (const size_t i : IntegerRange(*maybeStartIndex, *maybeEndIndex)) {
1432 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1433 // pretended earlier.
1434 aRanges->AppendElement(mStyledRanges.mRanges[i].mRange);
1437 return NS_OK;
1440 nsresult Selection::GetDynamicRangesForIntervalArray(
1441 nsINode* aBeginNode, uint32_t aBeginOffset, nsINode* aEndNode,
1442 uint32_t aEndOffset, bool aAllowAdjacent, nsTArray<nsRange*>* aRanges) {
1443 MOZ_ASSERT(mSelectionType != SelectionType::eHighlight);
1444 AutoTArray<AbstractRange*, 2> abstractRanges;
1445 nsresult rv = GetAbstractRangesForIntervalArray(
1446 aBeginNode, aBeginOffset, aEndNode, aEndOffset, aAllowAdjacent,
1447 &abstractRanges);
1448 NS_ENSURE_SUCCESS(rv, rv);
1449 aRanges->Clear();
1450 aRanges->SetCapacity(abstractRanges.Length());
1451 for (auto* abstractRange : abstractRanges) {
1452 aRanges->AppendElement(abstractRange->AsDynamicRange());
1454 return NS_OK;
1457 void Selection::StyledRanges::ReorderRangesIfNecessary() {
1458 const Document* doc = mSelection.GetDocument();
1459 if (!doc) {
1460 return;
1462 if (mRanges.Length() < 2 && mInvalidStaticRanges.IsEmpty()) {
1463 // There is nothing to be reordered.
1464 return;
1466 const int32_t currentDocumentGeneration = doc->GetGeneration();
1467 const bool domMutationHasHappened =
1468 currentDocumentGeneration != mDocumentGeneration;
1469 if (domMutationHasHappened) {
1470 // After a DOM mutation, invalid static ranges might have become valid and
1471 // valid static ranges might have become invalid.
1472 StyledRangeArray invalidStaticRanges;
1473 for (StyledRangeArray::const_iterator iter = mRanges.begin();
1474 iter != mRanges.end();) {
1475 const AbstractRange* range = iter->mRange;
1476 if (range->IsStaticRange() && !range->AsStaticRange()->IsValid()) {
1477 invalidStaticRanges.AppendElement(*iter);
1478 iter = mRanges.RemoveElementAt(iter);
1479 } else {
1480 ++iter;
1483 for (StyledRangeArray::const_iterator iter = mInvalidStaticRanges.begin();
1484 iter != mInvalidStaticRanges.end();) {
1485 MOZ_ASSERT(iter->mRange->IsStaticRange());
1486 if (iter->mRange->AsStaticRange()->IsValid()) {
1487 mRanges.AppendElement(*iter);
1488 iter = mInvalidStaticRanges.RemoveElementAt(iter);
1489 } else {
1490 ++iter;
1493 mInvalidStaticRanges.AppendElements(std::move(invalidStaticRanges));
1495 if (domMutationHasHappened || mRangesMightHaveChanged) {
1496 // This is hot code. Proceed with caution.
1497 // This path uses a cache that keep the last 100 node/index combinations
1498 // in a stack-allocated array to save up on expensive calls to
1499 // nsINode::ComputeIndexOf() (which happen in
1500 // nsContentUtils::ComparePoints()).
1501 // The second expensive call here is the sort() below, which should be
1502 // avoided if possible. Sorting can be avoided if the ranges are still in
1503 // order. Checking the order is cheap compared to sorting (also, it fills up
1504 // the cache, which is reused by the sort call).
1505 nsContentUtils::NodeIndexCache cache;
1506 bool rangeOrderHasChanged = false;
1507 const nsINode* prevStartContainer = nullptr;
1508 uint32_t prevStartOffset = 0;
1509 for (const StyledRange& range : mRanges) {
1510 const nsINode* startContainer = range.mRange->GetStartContainer();
1511 uint32_t startOffset = range.mRange->StartOffset();
1512 if (!prevStartContainer) {
1513 prevStartContainer = startContainer;
1514 prevStartOffset = startOffset;
1515 continue;
1517 // Calling ComparePoints here saves one call of
1518 // AbstractRange::StartOffset() per iteration (which is surprisingly
1519 // expensive).
1520 const Maybe<int32_t> compareResult = nsContentUtils::ComparePoints(
1521 startContainer, startOffset, prevStartContainer, prevStartOffset,
1522 &cache);
1523 // If the nodes are in different subtrees, the Maybe is empty.
1524 // Since CompareToRangeStart pretends ranges to be ordered, this aligns
1525 // to that behavior.
1526 if (compareResult.valueOr(1) != 1) {
1527 rangeOrderHasChanged = true;
1528 break;
1530 prevStartContainer = startContainer;
1531 prevStartOffset = startOffset;
1533 if (rangeOrderHasChanged) {
1534 mRanges.Sort([&cache](const StyledRange& a, const StyledRange& b) -> int {
1535 return CompareToRangeStart(*a.mRange->GetStartContainer(),
1536 a.mRange->StartOffset(), *b.mRange, &cache);
1539 mDocumentGeneration = currentDocumentGeneration;
1540 mRangesMightHaveChanged = false;
1544 nsresult Selection::StyledRanges::GetIndicesForInterval(
1545 const nsINode* aBeginNode, uint32_t aBeginOffset, const nsINode* aEndNode,
1546 uint32_t aEndOffset, bool aAllowAdjacent, Maybe<size_t>& aStartIndex,
1547 Maybe<size_t>& aEndIndex) {
1548 MOZ_ASSERT(aStartIndex.isNothing());
1549 MOZ_ASSERT(aEndIndex.isNothing());
1551 if (NS_WARN_IF(!aBeginNode)) {
1552 return NS_ERROR_INVALID_POINTER;
1555 if (NS_WARN_IF(!aEndNode)) {
1556 return NS_ERROR_INVALID_POINTER;
1559 ReorderRangesIfNecessary();
1561 if (mRanges.Length() == 0) {
1562 return NS_OK;
1565 const bool intervalIsCollapsed =
1566 aBeginNode == aEndNode && aBeginOffset == aEndOffset;
1568 // Ranges that end before the given interval and begin after the given
1569 // interval can be discarded
1570 size_t endsBeforeIndex{FindInsertionPoint(&mRanges, *aEndNode, aEndOffset,
1571 &CompareToRangeStart)};
1573 if (endsBeforeIndex == 0) {
1574 const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange;
1576 // If the interval is strictly before the range at index 0, we can optimize
1577 // by returning now - all ranges start after the given interval
1578 if (!endRange->StartRef().Equals(aEndNode, aEndOffset)) {
1579 return NS_OK;
1582 // We now know that the start point of mRanges[0].mRange
1583 // equals the end of the interval. Thus, when aAllowadjacent is true, the
1584 // caller is always interested in this range. However, when excluding
1585 // adjacencies, we must remember to include the range when both it and the
1586 // given interval are collapsed to the same point
1587 if (!aAllowAdjacent && !(endRange->Collapsed() && intervalIsCollapsed))
1588 return NS_OK;
1590 aEndIndex.emplace(endsBeforeIndex);
1592 size_t beginsAfterIndex{FindInsertionPoint(&mRanges, *aBeginNode,
1593 aBeginOffset, &CompareToRangeEnd)};
1595 if (beginsAfterIndex == mRanges.Length()) {
1596 return NS_OK; // optimization: all ranges are strictly before us
1599 if (aAllowAdjacent) {
1600 // At this point, one of the following holds:
1601 // endsBeforeIndex == mRanges.Length(),
1602 // endsBeforeIndex points to a range whose start point does not equal the
1603 // given interval's start point
1604 // endsBeforeIndex points to a range whose start point equals the given
1605 // interval's start point
1606 // In the final case, there can be two such ranges, a collapsed range, and
1607 // an adjacent range (they will appear in mRanges in that
1608 // order). For this final case, we need to increment endsBeforeIndex, until
1609 // one of the first two possibilities hold
1610 while (endsBeforeIndex < mRanges.Length()) {
1611 const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange;
1612 if (!endRange->StartRef().Equals(aEndNode, aEndOffset)) {
1613 break;
1615 endsBeforeIndex++;
1618 // Likewise, one of the following holds:
1619 // beginsAfterIndex == 0,
1620 // beginsAfterIndex points to a range whose end point does not equal
1621 // the given interval's end point
1622 // beginsOnOrAfter points to a range whose end point equals the given
1623 // interval's end point
1624 // In the final case, there can be two such ranges, an adjacent range, and
1625 // a collapsed range (they will appear in mRanges in that
1626 // order). For this final case, we only need to take action if both those
1627 // ranges exist, and we are pointing to the collapsed range - we need to
1628 // point to the adjacent range
1629 const AbstractRange* beginRange = mRanges[beginsAfterIndex].mRange;
1630 if (beginsAfterIndex > 0 && beginRange->Collapsed() &&
1631 beginRange->EndRef().Equals(aBeginNode, aBeginOffset)) {
1632 beginRange = mRanges[beginsAfterIndex - 1].mRange;
1633 if (beginRange->EndRef().Equals(aBeginNode, aBeginOffset)) {
1634 beginsAfterIndex--;
1637 } else {
1638 // See above for the possibilities at this point. The only case where we
1639 // need to take action is when the range at beginsAfterIndex ends on
1640 // the given interval's start point, but that range isn't collapsed (a
1641 // collapsed range should be included in the returned results).
1642 const AbstractRange* beginRange = mRanges[beginsAfterIndex].mRange;
1643 if (beginRange->MayCrossShadowBoundaryEndRef().Equals(aBeginNode,
1644 aBeginOffset) &&
1645 !beginRange->Collapsed()) {
1646 beginsAfterIndex++;
1649 // Again, see above for the meaning of endsBeforeIndex at this point.
1650 // In particular, endsBeforeIndex may point to a collaped range which
1651 // represents the point at the end of the interval - this range should be
1652 // included
1653 if (endsBeforeIndex < mRanges.Length()) {
1654 const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange;
1655 if (endRange->MayCrossShadowBoundaryStartRef().Equals(aEndNode,
1656 aEndOffset) &&
1657 endRange->Collapsed()) {
1658 endsBeforeIndex++;
1663 NS_ASSERTION(beginsAfterIndex <= endsBeforeIndex, "Is mRanges not ordered?");
1664 NS_ENSURE_STATE(beginsAfterIndex <= endsBeforeIndex);
1666 aStartIndex.emplace(beginsAfterIndex);
1667 aEndIndex = Some(endsBeforeIndex);
1668 return NS_OK;
1671 nsIFrame* Selection::GetPrimaryFrameForAnchorNode() const {
1672 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
1674 nsCOMPtr<nsIContent> content = do_QueryInterface(GetAnchorNode());
1675 if (content && mFrameSelection) {
1676 return SelectionMovementUtils::GetFrameForNodeOffset(
1677 content, AnchorOffset(), mFrameSelection->GetHint());
1679 return nullptr;
1682 PrimaryFrameData Selection::GetPrimaryFrameForCaretAtFocusNode(
1683 bool aVisual) const {
1684 nsIContent* content = nsIContent::FromNodeOrNull(GetFocusNode());
1685 if (!content || !mFrameSelection || !mFrameSelection->GetPresShell()) {
1686 return {};
1689 MOZ_ASSERT(mFrameSelection->GetPresShell()->GetDocument() ==
1690 content->GetComposedDoc());
1692 CaretAssociationHint hint = mFrameSelection->GetHint();
1693 intl::BidiEmbeddingLevel caretBidiLevel =
1694 mFrameSelection->GetCaretBidiLevel();
1695 return SelectionMovementUtils::GetPrimaryFrameForCaret(
1696 content, FocusOffset(), aVisual, hint, caretBidiLevel);
1699 void Selection::SelectFramesOf(nsIContent* aContent, bool aSelected) const {
1700 nsIFrame* frame = aContent->GetPrimaryFrame();
1701 if (!frame) {
1702 return;
1704 // The frame could be an SVG text frame, in which case we don't treat it
1705 // as a text frame.
1706 if (frame->IsTextFrame()) {
1707 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
1708 textFrame->SelectionStateChanged(0, textFrame->TextFragment()->GetLength(),
1709 aSelected, mSelectionType);
1710 } else {
1711 frame->SelectionStateChanged();
1715 nsresult Selection::SelectFramesOfInclusiveDescendantsOfContent(
1716 PostContentIterator& aPostOrderIter, nsIContent* aContent,
1717 bool aSelected) const {
1718 // If aContent doesn't have children, we should avoid to use the content
1719 // iterator for performance reason.
1720 if (!aContent->HasChildren()) {
1721 SelectFramesOf(aContent, aSelected);
1722 return NS_OK;
1725 if (NS_WARN_IF(NS_FAILED(aPostOrderIter.Init(aContent)))) {
1726 return NS_ERROR_FAILURE;
1729 for (; !aPostOrderIter.IsDone(); aPostOrderIter.Next()) {
1730 nsINode* node = aPostOrderIter.GetCurrentNode();
1731 MOZ_ASSERT(node);
1732 nsIContent* innercontent = node->IsContent() ? node->AsContent() : nullptr;
1733 SelectFramesOf(innercontent, aSelected);
1736 return NS_OK;
1739 void Selection::SelectFramesOfShadowIncludingDescendantsOfContent(
1740 nsIContent* aContent, bool aSelected) const {
1741 MOZ_ASSERT(aContent);
1742 MOZ_ASSERT(StaticPrefs::dom_shadowdom_selection_across_boundary_enabled());
1743 for (nsINode* node : ShadowIncludingTreeIterator(*aContent)) {
1744 nsIContent* innercontent = node->IsContent() ? node->AsContent() : nullptr;
1745 SelectFramesOf(innercontent, aSelected);
1749 void Selection::SelectFramesInAllRanges(nsPresContext* aPresContext) {
1750 // this method is currently only called in a user-initiated context.
1751 // therefore it is safe to assume that we are not in a Highlight selection
1752 // and we only have to deal with nsRanges (no StaticRanges).
1753 MOZ_ASSERT(mSelectionType != SelectionType::eHighlight);
1754 for (size_t i = 0; i < mStyledRanges.Length(); ++i) {
1755 nsRange* range = mStyledRanges.mRanges[i].mRange->AsDynamicRange();
1756 MOZ_ASSERT(range->IsInAnySelection());
1757 SelectFrames(aPresContext, *range, range->IsInAnySelection());
1762 * The idea of this helper method is to select or deselect "top to bottom",
1763 * traversing through the frames
1765 nsresult Selection::SelectFrames(nsPresContext* aPresContext,
1766 AbstractRange& aRange, bool aSelect) const {
1767 if (!mFrameSelection || !aPresContext || !aPresContext->GetPresShell()) {
1768 // nothing to do
1769 return NS_OK;
1772 MOZ_DIAGNOSTIC_ASSERT(aRange.IsPositioned());
1774 const Document* const document = GetDocument();
1775 if (MOZ_UNLIKELY(!document ||
1776 aRange.GetComposedDocOfContainers() != document)) {
1777 return NS_OK; // Do nothing if the range is now in different document.
1780 if (aRange.IsStaticRange() && !aRange.AsStaticRange()->IsValid()) {
1781 // TODO jjaschke: Actions necessary to unselect invalid static ranges?
1782 return NS_OK;
1785 if (mFrameSelection->IsInTableSelectionMode()) {
1786 const nsIContent* const commonAncestorContent =
1787 nsIContent::FromNodeOrNull(aRange.GetClosestCommonInclusiveAncestor(
1788 StaticPrefs::dom_select_events_textcontrols_selectstart_enabled()
1789 ? AllowRangeCrossShadowBoundary::Yes
1790 : AllowRangeCrossShadowBoundary::No));
1791 nsIFrame* const frame = commonAncestorContent
1792 ? commonAncestorContent->GetPrimaryFrame()
1793 : aPresContext->PresShell()->GetRootFrame();
1794 if (frame) {
1795 if (frame->IsTextFrame()) {
1796 MOZ_ASSERT(commonAncestorContent ==
1797 aRange.GetMayCrossShadowBoundaryStartContainer());
1798 MOZ_ASSERT(commonAncestorContent ==
1799 aRange.GetMayCrossShadowBoundaryEndContainer());
1800 static_cast<nsTextFrame*>(frame)->SelectionStateChanged(
1801 aRange.MayCrossShadowBoundaryStartOffset(),
1802 aRange.MayCrossShadowBoundaryEndOffset(), aSelect, mSelectionType);
1803 } else {
1804 frame->SelectionStateChanged();
1808 return NS_OK;
1811 // Loop through the content iterator for each content node; for each text
1812 // node, call SetSelected on it:
1813 nsIContent* const startContent = nsIContent::FromNodeOrNull(
1814 aRange.GetMayCrossShadowBoundaryStartContainer());
1815 if (MOZ_UNLIKELY(!startContent)) {
1816 // Don't warn, bug 1055722
1817 // XXX The range can start from a document node and such range can be
1818 // added to Selection with JS. Therefore, even in such cases,
1819 // shouldn't we handle selection in the range?
1820 return NS_ERROR_UNEXPECTED;
1822 MOZ_DIAGNOSTIC_ASSERT(startContent->IsInComposedDoc());
1824 // We must call first one explicitly
1825 nsINode* const endNode = aRange.GetMayCrossShadowBoundaryEndContainer();
1826 if (NS_WARN_IF(!endNode)) {
1827 // We null-checked start node above, therefore, end node should also be
1828 // non-null here.
1829 return NS_ERROR_UNEXPECTED;
1831 const bool isFirstContentTextNode = startContent->IsText();
1832 if (isFirstContentTextNode) {
1833 if (nsIFrame* const frame = startContent->GetPrimaryFrame()) {
1834 // The frame could be an SVG text frame, in which case we don't treat it
1835 // as a text frame.
1836 if (frame->IsTextFrame()) {
1837 const uint32_t startOffset = aRange.MayCrossShadowBoundaryStartOffset();
1838 const uint32_t endOffset =
1839 endNode == startContent ? aRange.MayCrossShadowBoundaryEndOffset()
1840 : startContent->Length();
1841 static_cast<nsTextFrame*>(frame)->SelectionStateChanged(
1842 startOffset, endOffset, aSelect, mSelectionType);
1843 } else {
1844 frame->SelectionStateChanged();
1849 // If the range is in a node and the node is a leaf node, we don't need to
1850 // walk the subtree.
1851 if ((aRange.Collapsed() && !aRange.MayCrossShadowBoundary()) ||
1852 (startContent == endNode && !startContent->HasChildren())) {
1853 if (!isFirstContentTextNode) {
1854 SelectFramesOf(startContent, aSelect);
1856 return NS_OK;
1859 ContentSubtreeIterator subtreeIter;
1860 subtreeIter.InitWithAllowCrossShadowBoundary(&aRange);
1861 if (isFirstContentTextNode && !subtreeIter.IsDone() &&
1862 subtreeIter.GetCurrentNode() == startContent) {
1863 subtreeIter.Next(); // first content has already been handled.
1865 PostContentIterator postOrderIter;
1866 for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
1867 MOZ_DIAGNOSTIC_ASSERT(subtreeIter.GetCurrentNode());
1868 if (nsIContent* const content =
1869 nsIContent::FromNodeOrNull(subtreeIter.GetCurrentNode())) {
1870 if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) {
1871 SelectFramesOfShadowIncludingDescendantsOfContent(content, aSelect);
1872 } else {
1873 SelectFramesOfInclusiveDescendantsOfContent(postOrderIter, content,
1874 aSelect);
1879 // We must now do the last one if it is not the same as the first
1880 if (endNode == startContent || !endNode->IsText()) {
1881 return NS_OK;
1884 if (nsIFrame* const frame = endNode->AsText()->GetPrimaryFrame()) {
1885 // The frame could be an SVG text frame, in which case we'll ignore it.
1886 if (frame->IsTextFrame()) {
1887 static_cast<nsTextFrame*>(frame)->SelectionStateChanged(
1888 0, aRange.MayCrossShadowBoundaryEndOffset(), aSelect, mSelectionType);
1891 return NS_OK;
1894 // Selection::LookUpSelection
1896 // This function is called when a node wants to know where the selection is
1897 // over itself.
1899 // Usually, this is called when we already know there is a selection over
1900 // the node in question, and we only need to find the boundaries of it on
1901 // that node. This is when slowCheck is false--a strict test is not needed.
1902 // Other times, the caller has no idea, and wants us to test everything,
1903 // so we are supposed to determine whether there is a selection over the
1904 // node at all.
1906 // A previous version of this code used this flag to do less work when
1907 // inclusion was already known (slowCheck=false). However, our tree
1908 // structure allows us to quickly determine ranges overlapping the node,
1909 // so we just ignore the slowCheck flag and do the full test every time.
1911 // PERFORMANCE: a common case is that we are doing a fast check with exactly
1912 // one range in the selection. In this case, this function is slower than
1913 // brute force because of the overhead of checking the tree. We can optimize
1914 // this case to make it faster by doing the same thing the previous version
1915 // of this function did in the case of 1 range. This would also mean that
1916 // the aSlowCheck flag would have meaning again.
1918 UniquePtr<SelectionDetails> Selection::LookUpSelection(
1919 nsIContent* aContent, uint32_t aContentOffset, uint32_t aContentLength,
1920 UniquePtr<SelectionDetails> aDetailsHead, SelectionType aSelectionType,
1921 bool aSlowCheck) {
1922 if (!aContent) {
1923 return aDetailsHead;
1926 // it is common to have no ranges, to optimize that
1927 if (mStyledRanges.Length() == 0) {
1928 return aDetailsHead;
1931 nsTArray<AbstractRange*> overlappingRanges;
1932 nsresult rv = GetAbstractRangesForIntervalArray(
1933 aContent, aContentOffset, aContent, aContentOffset + aContentLength,
1934 false, &overlappingRanges);
1935 if (NS_FAILED(rv)) {
1936 return aDetailsHead;
1939 if (overlappingRanges.Length() == 0) {
1940 return aDetailsHead;
1943 UniquePtr<SelectionDetails> detailsHead = std::move(aDetailsHead);
1945 for (size_t i = 0; i < overlappingRanges.Length(); i++) {
1946 AbstractRange* range = overlappingRanges[i];
1947 if (range->IsStaticRange() && !range->AsStaticRange()->IsValid()) {
1948 continue;
1951 nsINode* startNode = range->GetMayCrossShadowBoundaryStartContainer();
1952 nsINode* endNode = range->GetMayCrossShadowBoundaryEndContainer();
1953 uint32_t startOffset = range->MayCrossShadowBoundaryStartOffset();
1954 uint32_t endOffset = range->MayCrossShadowBoundaryEndOffset();
1956 Maybe<uint32_t> start, end;
1957 if (startNode == aContent && endNode == aContent) {
1958 if (startOffset < (aContentOffset + aContentLength) &&
1959 endOffset > aContentOffset) {
1960 // this range is totally inside the requested content range
1961 start.emplace(
1962 startOffset >= aContentOffset ? startOffset - aContentOffset : 0u);
1963 end.emplace(std::min(aContentLength, endOffset - aContentOffset));
1965 // otherwise, range is inside the requested node, but does not intersect
1966 // the requested content range, so ignore it
1967 } else if (startNode == aContent) {
1968 if (startOffset < (aContentOffset + aContentLength)) {
1969 // the beginning of the range is inside the requested node, but the
1970 // end is outside, select everything from there to the end
1971 start.emplace(
1972 startOffset >= aContentOffset ? startOffset - aContentOffset : 0u);
1973 end.emplace(aContentLength);
1975 } else if (endNode == aContent) {
1976 if (endOffset > aContentOffset) {
1977 // the end of the range is inside the requested node, but the beginning
1978 // is outside, select everything from the beginning to there
1979 start.emplace(0u);
1980 end.emplace(std::min(aContentLength, endOffset - aContentOffset));
1982 } else {
1983 // this range does not begin or end in the requested node, but since
1984 // GetRangesForInterval returned this range, we know it overlaps.
1985 // Therefore, this node is enclosed in the range, and we select all
1986 // of it.
1987 start.emplace(0u);
1988 end.emplace(aContentLength);
1990 if (start.isNothing()) {
1991 continue; // the ranges do not overlap the input range
1994 auto newHead = MakeUnique<SelectionDetails>();
1996 newHead->mNext = std::move(detailsHead);
1997 newHead->mStart = AssertedCast<int32_t>(*start);
1998 newHead->mEnd = AssertedCast<int32_t>(*end);
1999 newHead->mSelectionType = aSelectionType;
2000 newHead->mHighlightData = mHighlightData;
2001 StyledRange* rd = mStyledRanges.FindRangeData(range);
2002 if (rd) {
2003 newHead->mTextRangeStyle = rd->mTextRangeStyle;
2005 detailsHead = std::move(newHead);
2007 return detailsHead;
2010 NS_IMETHODIMP
2011 Selection::Repaint(nsPresContext* aPresContext) {
2012 int32_t arrCount = (int32_t)mStyledRanges.Length();
2014 if (arrCount < 1) return NS_OK;
2016 int32_t i;
2018 for (i = 0; i < arrCount; i++) {
2019 MOZ_ASSERT(mStyledRanges.mRanges[i].mRange);
2020 nsresult rv =
2021 SelectFrames(aPresContext, *mStyledRanges.mRanges[i].mRange, true);
2023 if (NS_FAILED(rv)) {
2024 return rv;
2028 return NS_OK;
2031 void Selection::SetCanCacheFrameOffset(bool aCanCacheFrameOffset) {
2032 if (!mCachedOffsetForFrame) {
2033 mCachedOffsetForFrame = new CachedOffsetForFrame;
2036 mCachedOffsetForFrame->mCanCacheFrameOffset = aCanCacheFrameOffset;
2038 // clean up cached frame when turn off cache
2039 // fix bug 207936
2040 if (!aCanCacheFrameOffset) {
2041 mCachedOffsetForFrame->mLastCaretFrame = nullptr;
2045 nsresult Selection::GetCachedFrameOffset(nsIFrame* aFrame, int32_t inOffset,
2046 nsPoint& aPoint) {
2047 if (!mCachedOffsetForFrame) {
2048 mCachedOffsetForFrame = new CachedOffsetForFrame;
2051 nsresult rv = NS_OK;
2052 if (mCachedOffsetForFrame->mCanCacheFrameOffset &&
2053 mCachedOffsetForFrame->mLastCaretFrame &&
2054 (aFrame == mCachedOffsetForFrame->mLastCaretFrame) &&
2055 (inOffset == mCachedOffsetForFrame->mLastContentOffset)) {
2056 // get cached frame offset
2057 aPoint = mCachedOffsetForFrame->mCachedFrameOffset;
2058 } else {
2059 // Recalculate frame offset and cache it. Don't cache a frame offset if
2060 // GetPointFromOffset fails, though.
2061 rv = aFrame->GetPointFromOffset(inOffset, &aPoint);
2062 if (NS_SUCCEEDED(rv) && mCachedOffsetForFrame->mCanCacheFrameOffset) {
2063 mCachedOffsetForFrame->mCachedFrameOffset = aPoint;
2064 mCachedOffsetForFrame->mLastCaretFrame = aFrame;
2065 mCachedOffsetForFrame->mLastContentOffset = inOffset;
2069 return rv;
2072 nsIContent* Selection::GetAncestorLimiter() const {
2073 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
2075 if (mFrameSelection) {
2076 return mFrameSelection->GetAncestorLimiter();
2078 return nullptr;
2081 void Selection::SetAncestorLimiter(nsIContent* aLimiter) {
2082 if (NeedsToLogSelectionAPI(*this)) {
2083 LogSelectionAPI(this, __FUNCTION__, "aLimiter", aLimiter);
2084 LogStackForSelectionAPI();
2087 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
2089 if (mFrameSelection) {
2090 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
2091 frameSelection->SetAncestorLimiter(aLimiter);
2095 void Selection::StyledRanges::UnregisterSelection() {
2096 uint32_t count = mRanges.Length();
2097 for (uint32_t i = 0; i < count; ++i) {
2098 mRanges[i].mRange->UnregisterSelection(mSelection);
2102 void Selection::StyledRanges::Clear() {
2103 mRanges.Clear();
2104 mInvalidStaticRanges.Clear();
2107 StyledRange* Selection::StyledRanges::FindRangeData(AbstractRange* aRange) {
2108 NS_ENSURE_TRUE(aRange, nullptr);
2109 for (uint32_t i = 0; i < mRanges.Length(); i++) {
2110 if (mRanges[i].mRange == aRange) {
2111 return &mRanges[i];
2114 return nullptr;
2117 Selection::StyledRanges::StyledRangeArray::size_type
2118 Selection::StyledRanges::Length() const {
2119 return mRanges.Length();
2122 nsresult Selection::SetTextRangeStyle(nsRange* aRange,
2123 const TextRangeStyle& aTextRangeStyle) {
2124 NS_ENSURE_ARG_POINTER(aRange);
2125 StyledRange* rd = mStyledRanges.FindRangeData(aRange);
2126 if (rd) {
2127 rd->mTextRangeStyle = aTextRangeStyle;
2129 return NS_OK;
2132 nsresult Selection::StartAutoScrollTimer(nsIFrame* aFrame,
2133 const nsPoint& aPoint,
2134 uint32_t aDelayInMs) {
2135 MOZ_ASSERT(aFrame, "Need a frame");
2136 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
2138 if (!mFrameSelection) {
2139 return NS_OK; // nothing to do
2142 if (!mAutoScroller) {
2143 mAutoScroller = new AutoScroller(mFrameSelection);
2146 mAutoScroller->SetDelay(aDelayInMs);
2148 RefPtr<AutoScroller> autoScroller{mAutoScroller};
2149 return autoScroller->DoAutoScroll(aFrame, aPoint);
2152 nsresult Selection::StopAutoScrollTimer() {
2153 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
2155 if (mAutoScroller) {
2156 mAutoScroller->Stop(AutoScroller::FurtherScrollingAllowed::kYes);
2159 return NS_OK;
2162 nsresult AutoScroller::DoAutoScroll(nsIFrame* aFrame, nsPoint aPoint) {
2163 MOZ_ASSERT(aFrame, "Need a frame");
2165 Stop(FurtherScrollingAllowed::kYes);
2167 nsPresContext* presContext = aFrame->PresContext();
2168 RefPtr<PresShell> presShell = presContext->PresShell();
2169 nsRootPresContext* rootPC = presContext->GetRootPresContext();
2170 if (!rootPC) {
2171 return NS_OK;
2173 nsIFrame* rootmostFrame = rootPC->PresShell()->GetRootFrame();
2174 AutoWeakFrame weakRootFrame(rootmostFrame);
2175 AutoWeakFrame weakFrame(aFrame);
2176 // Get the point relative to the root most frame because the scroll we are
2177 // about to do will change the coordinates of aFrame.
2178 nsPoint globalPoint = aPoint + aFrame->GetOffsetToCrossDoc(rootmostFrame);
2180 bool done = false;
2181 bool didScroll;
2182 while (true) {
2183 didScroll = presShell->ScrollFrameIntoView(
2184 aFrame, Some(nsRect(aPoint, nsSize())), ScrollAxis(), ScrollAxis(),
2185 ScrollFlags::None);
2186 if (!weakFrame || !weakRootFrame) {
2187 return NS_OK;
2189 if (!didScroll && !done) {
2190 // If aPoint is at the very edge of the root, then try to scroll anyway,
2191 // once.
2192 nsRect rootRect = rootmostFrame->GetRect();
2193 nscoord onePx = AppUnitsPerCSSPixel();
2194 nscoord scrollAmount = 10 * onePx;
2195 if (std::abs(rootRect.x - globalPoint.x) <= onePx) {
2196 aPoint.x -= scrollAmount;
2197 } else if (std::abs(rootRect.XMost() - globalPoint.x) <= onePx) {
2198 aPoint.x += scrollAmount;
2199 } else if (std::abs(rootRect.y - globalPoint.y) <= onePx) {
2200 aPoint.y -= scrollAmount;
2201 } else if (std::abs(rootRect.YMost() - globalPoint.y) <= onePx) {
2202 aPoint.y += scrollAmount;
2203 } else {
2204 break;
2206 done = true;
2207 continue;
2209 break;
2212 // Start the AutoScroll timer if necessary.
2213 // `ScrollFrameRectIntoView` above may have run script and this may have
2214 // forbidden to continue scrolling.
2215 if (didScroll && mFurtherScrollingAllowed == FurtherScrollingAllowed::kYes) {
2216 nsPoint presContextPoint =
2217 globalPoint -
2218 presShell->GetRootFrame()->GetOffsetToCrossDoc(rootmostFrame);
2219 ScheduleNextDoAutoScroll(presContext, presContextPoint);
2222 return NS_OK;
2225 void Selection::RemoveAllRanges(ErrorResult& aRv) {
2226 if (NeedsToLogSelectionAPI(*this)) {
2227 LogSelectionAPI(this, __FUNCTION__);
2228 LogStackForSelectionAPI();
2231 RemoveAllRangesInternal(aRv);
2234 already_AddRefed<StaticRange> Selection::GetComposedRange(
2235 const AbstractRange* aRange,
2236 const Sequence<OwningNonNull<ShadowRoot>>& aShadowRoots) const {
2237 // If aIsEndNode is true, this method does the Step 5.1 and 5.2
2238 // in https://www.w3.org/TR/selection-api/#dom-selection-getcomposedranges,
2239 // otherwise it does the Step 3.1 and 3.2.
2240 auto reScope = [&aShadowRoots](nsINode*& aNode, uint32_t& aOffset,
2241 bool aIsEndNode) {
2242 MOZ_ASSERT(aNode);
2243 while (aNode) {
2244 const ShadowRoot* shadowRootOfNode = aNode->GetContainingShadow();
2245 if (!shadowRootOfNode) {
2246 return;
2249 for (const OwningNonNull<ShadowRoot>& shadowRoot : aShadowRoots) {
2250 if (shadowRoot->IsShadowIncludingInclusiveDescendantOf(
2251 shadowRootOfNode)) {
2252 return;
2256 const nsIContent* host = aNode->GetContainingShadowHost();
2257 const Maybe<uint32_t> maybeIndex = host->ComputeIndexInParentContent();
2258 MOZ_ASSERT(maybeIndex.isSome(), "not parent or anonymous child?");
2259 if (MOZ_UNLIKELY(maybeIndex.isNothing())) {
2260 // Unlikely to happen, but still set aNode to nullptr to avoid
2261 // leaking information about the shadow tree.
2262 aNode = nullptr;
2263 return;
2265 aOffset = maybeIndex.value();
2266 if (aIsEndNode) {
2267 aOffset += 1;
2269 aNode = host->GetParentNode();
2273 nsINode* startNode = aRange->GetMayCrossShadowBoundaryStartContainer();
2274 uint32_t startOffset = aRange->MayCrossShadowBoundaryStartOffset();
2275 nsINode* endNode = aRange->GetMayCrossShadowBoundaryEndContainer();
2276 uint32_t endOffset = aRange->MayCrossShadowBoundaryEndOffset();
2278 reScope(startNode, startOffset, false /* aIsEndNode */);
2279 reScope(endNode, endOffset, true /* aIsEndNode */);
2281 RefPtr<StaticRange> composedRange = StaticRange::Create(
2282 startNode, startOffset, endNode, endOffset, IgnoreErrors());
2283 return composedRange.forget();
2286 void Selection::GetComposedRanges(
2287 const Sequence<OwningNonNull<ShadowRoot>>& aShadowRoots,
2288 nsTArray<RefPtr<StaticRange>>& aComposedRanges) {
2289 aComposedRanges.SetCapacity(mStyledRanges.mRanges.Length());
2290 for (const auto& range : mStyledRanges.mRanges) {
2291 aComposedRanges.AppendElement(GetComposedRange(range.mRange, aShadowRoots));
2295 void Selection::RemoveAllRangesInternal(ErrorResult& aRv) {
2296 if (!mFrameSelection) {
2297 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
2298 return;
2301 RefPtr<nsPresContext> presContext = GetPresContext();
2302 Clear(presContext);
2304 // Turn off signal for table selection
2305 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
2306 frameSelection->ClearTableCellSelection();
2308 RefPtr<Selection> kungFuDeathGrip{this};
2309 // Be aware, this instance may be destroyed after this call.
2310 NotifySelectionListeners();
2313 void Selection::AddRangeJS(nsRange& aRange, ErrorResult& aRv) {
2314 if (NeedsToLogSelectionAPI(*this)) {
2315 LogSelectionAPI(this, __FUNCTION__, "aRange", aRange);
2316 LogStackForSelectionAPI();
2319 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
2320 mCalledByJS = true;
2321 RefPtr<Document> document(GetDocument());
2322 AddRangeAndSelectFramesAndNotifyListenersInternal(aRange, document, aRv);
2325 void Selection::AddRangeAndSelectFramesAndNotifyListeners(nsRange& aRange,
2326 ErrorResult& aRv) {
2327 if (NeedsToLogSelectionAPI(*this)) {
2328 LogSelectionAPI(this, __FUNCTION__, "aRange", aRange);
2329 LogStackForSelectionAPI();
2332 RefPtr<Document> document(GetDocument());
2333 return AddRangeAndSelectFramesAndNotifyListenersInternal(aRange, document,
2334 aRv);
2337 void Selection::AddRangeAndSelectFramesAndNotifyListenersInternal(
2338 nsRange& aRange, Document* aDocument, ErrorResult& aRv) {
2339 RefPtr<nsRange> range = &aRange;
2340 if (aRange.IsInAnySelection()) {
2341 if (aRange.IsInSelection(*this)) {
2342 // If we already have the range, we don't need to handle this except
2343 // setting the interline position.
2344 if (mSelectionType == SelectionType::eNormal) {
2345 SetInterlinePosition(InterlinePosition::StartOfNextLine);
2347 return;
2349 if (mSelectionType != SelectionType::eNormal &&
2350 mSelectionType != SelectionType::eHighlight) {
2351 range = aRange.CloneRange();
2355 nsINode* rangeRoot = range->GetRoot();
2356 if (aDocument != rangeRoot &&
2357 (!rangeRoot || aDocument != rangeRoot->GetComposedDoc())) {
2358 // http://w3c.github.io/selection-api/#dom-selection-addrange
2359 // "... if the root of the range's boundary points are the document
2360 // associated with context object. Otherwise, this method must do nothing."
2361 return;
2364 // MaybeAddTableCellRange might flush frame and `NotifySelectionListeners`
2365 // below might destruct `this`.
2366 RefPtr<Selection> kungFuDeathGrip(this);
2368 // This inserts a table cell range in proper document order
2369 // and returns NS_OK if range doesn't contain just one table cell
2370 Maybe<size_t> maybeRangeIndex;
2371 nsresult result = MaybeAddTableCellRange(*range, &maybeRangeIndex);
2372 if (NS_FAILED(result)) {
2373 aRv.Throw(result);
2374 return;
2377 if (maybeRangeIndex.isNothing()) {
2378 result = AddRangesForSelectableNodes(range, &maybeRangeIndex,
2379 DispatchSelectstartEvent::Maybe);
2380 if (NS_FAILED(result)) {
2381 aRv.Throw(result);
2382 return;
2384 if (maybeRangeIndex.isNothing()) {
2385 return;
2389 MOZ_ASSERT(*maybeRangeIndex < mStyledRanges.Length());
2391 SetAnchorFocusRange(*maybeRangeIndex);
2393 // Make sure the caret appears on the next line, if at a newline
2394 if (mSelectionType == SelectionType::eNormal) {
2395 SetInterlinePosition(InterlinePosition::StartOfNextLine);
2398 if (!mFrameSelection) {
2399 return; // nothing to do
2402 RefPtr<nsPresContext> presContext = GetPresContext();
2403 SelectFrames(presContext, *range, true);
2405 // Be aware, this instance may be destroyed after this call.
2406 NotifySelectionListeners();
2407 // Range order is guaranteed after adding a range.
2408 // Therefore, this flag can be reset to avoid
2409 // another unnecessary and costly reordering.
2410 mStyledRanges.mRangesMightHaveChanged = false;
2413 void Selection::AddHighlightRangeAndSelectFramesAndNotifyListeners(
2414 AbstractRange& aRange) {
2415 MOZ_ASSERT(mSelectionType == SelectionType::eHighlight);
2416 nsresult rv = mStyledRanges.AddRangeAndIgnoreOverlaps(&aRange);
2417 if (NS_FAILED(rv)) {
2418 return;
2421 if (!mFrameSelection) {
2422 return; // nothing to do
2425 RefPtr<nsPresContext> presContext = GetPresContext();
2426 SelectFrames(presContext, aRange, true);
2428 // Be aware, this instance may be destroyed after this call.
2429 RefPtr<Selection> kungFuDeathGrip(this);
2430 NotifySelectionListeners();
2431 // Range order is guaranteed after adding a range.
2432 // Therefore, this flag can be reset to avoid
2433 // another unnecessary and costly reordering.
2434 mStyledRanges.mRangesMightHaveChanged = false;
2437 // Selection::RemoveRangeAndUnselectFramesAndNotifyListeners
2439 // Removes the given range from the selection. The tricky part is updating
2440 // the flags on the frames that indicate whether they have a selection or
2441 // not. There could be several selection ranges on the frame, and clearing
2442 // the bit would cause the selection to not be drawn, even when there is
2443 // another range on the frame (bug 346185).
2445 // We therefore find any ranges that intersect the same nodes as the range
2446 // being removed, and cause them to set the selected bits back on their
2447 // selected frames after we've cleared the bit from ours.
2449 void Selection::RemoveRangeAndUnselectFramesAndNotifyListeners(
2450 AbstractRange& aRange, ErrorResult& aRv) {
2451 if (NeedsToLogSelectionAPI(*this)) {
2452 LogSelectionAPI(this, __FUNCTION__, "aRange", aRange);
2453 LogStackForSelectionAPI();
2456 nsresult rv = mStyledRanges.RemoveRangeAndUnregisterSelection(aRange);
2457 if (NS_FAILED(rv)) {
2458 aRv.Throw(rv);
2459 return;
2462 nsINode* beginNode = aRange.GetStartContainer();
2463 nsINode* endNode = aRange.GetEndContainer();
2465 if (!beginNode || !endNode) {
2466 // Detached range; nothing else to do here.
2467 return;
2470 // find out the length of the end node, so we can select all of it
2471 uint32_t beginOffset, endOffset;
2472 if (endNode->IsText()) {
2473 // Get the length of the text. We can't just use the offset because
2474 // another range could be touching this text node but not intersect our
2475 // range.
2476 beginOffset = 0;
2477 endOffset = endNode->AsText()->TextLength();
2478 } else {
2479 // For non-text nodes, the given offsets should be sufficient.
2480 beginOffset = aRange.StartOffset();
2481 endOffset = aRange.EndOffset();
2484 // clear the selected bit from the removed range's frames
2485 RefPtr<nsPresContext> presContext = GetPresContext();
2486 SelectFrames(presContext, aRange, false);
2488 // add back the selected bit for each range touching our nodes
2489 nsTArray<AbstractRange*> affectedRanges;
2490 rv = GetAbstractRangesForIntervalArray(beginNode, beginOffset, endNode,
2491 endOffset, true, &affectedRanges);
2492 if (NS_FAILED(rv)) {
2493 aRv.Throw(rv);
2494 return;
2496 for (uint32_t i = 0; i < affectedRanges.Length(); i++) {
2497 MOZ_ASSERT(affectedRanges[i]);
2498 SelectFrames(presContext, *affectedRanges[i], true);
2501 if (&aRange == mAnchorFocusRange) {
2502 const size_t rangeCount = mStyledRanges.Length();
2503 if (rangeCount) {
2504 SetAnchorFocusRange(rangeCount - 1);
2505 } else {
2506 RemoveAnchorFocusRange();
2509 // When the selection is user-created it makes sense to scroll the range
2510 // into view. The spell-check selection, however, is created and destroyed
2511 // in the background. We don't want to scroll in this case or the view
2512 // might appear to be moving randomly (bug 337871).
2513 if (mSelectionType != SelectionType::eSpellCheck && rangeCount) {
2514 ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION);
2518 if (!mFrameSelection) return; // nothing to do
2520 RefPtr<Selection> kungFuDeathGrip{this};
2521 // Be aware, this instance may be destroyed after this call.
2522 NotifySelectionListeners();
2526 * Collapse sets the whole selection to be one point.
2528 void Selection::CollapseJS(nsINode* aContainer, uint32_t aOffset,
2529 ErrorResult& aRv) {
2530 if (NeedsToLogSelectionAPI(*this)) {
2531 LogSelectionAPI(this, __FUNCTION__, "aContainer", aContainer, "aOffset",
2532 aOffset);
2533 LogStackForSelectionAPI();
2536 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
2537 mCalledByJS = true;
2538 if (!aContainer) {
2539 RemoveAllRangesInternal(aRv);
2540 return;
2542 CollapseInternal(InLimiter::eNo, RawRangeBoundary(aContainer, aOffset), aRv);
2545 void Selection::CollapseInLimiter(const RawRangeBoundary& aPoint,
2546 ErrorResult& aRv) {
2547 if (NeedsToLogSelectionAPI(*this)) {
2548 LogSelectionAPI(this, __FUNCTION__, "aPoint", aPoint);
2549 LogStackForSelectionAPI();
2552 CollapseInternal(InLimiter::eYes, aPoint, aRv);
2555 void Selection::CollapseInternal(InLimiter aInLimiter,
2556 const RawRangeBoundary& aPoint,
2557 ErrorResult& aRv) {
2558 if (!mFrameSelection) {
2559 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
2560 return;
2563 if (!aPoint.IsSet()) {
2564 aRv.Throw(NS_ERROR_INVALID_ARG);
2565 return;
2568 if (aPoint.Container()->NodeType() == nsINode::DOCUMENT_TYPE_NODE) {
2569 aRv.ThrowInvalidNodeTypeError(kNoDocumentTypeNodeError);
2570 return;
2573 // RawRangeBoundary::IsSetAndValid() checks if the point actually refers
2574 // a child of the container when IsSet() is true. If its offset hasn't been
2575 // computed yet, this just checks it with its mRef. So, we can avoid
2576 // computing offset here.
2577 if (!aPoint.IsSetAndValid()) {
2578 aRv.ThrowIndexSizeError("The offset is out of range.");
2579 return;
2582 if (!HasSameRootOrSameComposedDoc(*aPoint.Container())) {
2583 // Return with no error
2584 return;
2587 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
2588 frameSelection->InvalidateDesiredCaretPos();
2589 if (aInLimiter == InLimiter::eYes &&
2590 !frameSelection->IsValidSelectionPoint(aPoint.Container())) {
2591 aRv.Throw(NS_ERROR_FAILURE);
2592 return;
2594 nsresult result;
2596 RefPtr<nsPresContext> presContext = GetPresContext();
2597 if (!presContext ||
2598 presContext->Document() != aPoint.Container()->OwnerDoc()) {
2599 aRv.Throw(NS_ERROR_FAILURE);
2600 return;
2603 // Delete all of the current ranges
2604 Clear(presContext);
2606 // Turn off signal for table selection
2607 frameSelection->ClearTableCellSelection();
2609 // Hack to display the caret on the right line (bug 1237236).
2610 if (frameSelection->GetHint() == CaretAssociationHint::Before &&
2611 aPoint.Container()->IsContent()) {
2612 const nsCaret::CaretPosition pos{
2613 aPoint.Container(),
2614 int32_t(*aPoint.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets)),
2615 frameSelection->GetHint(), frameSelection->GetCaretBidiLevel()};
2616 CaretFrameData frameData = nsCaret::GetFrameAndOffset(pos);
2617 if (frameData.mFrame) {
2618 frameSelection->SetHint(frameData.mHint);
2620 nsTextFrame* f = do_QueryFrame(frameData.mFrame);
2621 if (f && f->IsAtEndOfLine() && f->HasSignificantTerminalNewline()) {
2622 // RawRangeBounary::Offset() causes computing offset if it's not been
2623 // done yet. However, it's called only when the container is a text
2624 // node. In such case, offset has always been set since it cannot have
2625 // any children. So, this doesn't cause computing offset with expensive
2626 // method, nsINode::ComputeIndexOf().
2627 if ((aPoint.Container()->AsContent() == f->GetContent() &&
2628 f->GetContentEnd() ==
2629 static_cast<int32_t>(*aPoint.Offset(
2630 RawRangeBoundary::OffsetFilter::kValidOffsets))) ||
2631 (aPoint.Container() == f->GetContent()->GetParentNode() &&
2632 f->GetContent() == aPoint.GetPreviousSiblingOfChildAtOffset())) {
2633 frameSelection->SetHint(CaretAssociationHint::After);
2638 RefPtr<nsRange> range = nsRange::Create(aPoint.Container());
2639 result = range->CollapseTo(aPoint);
2640 if (NS_FAILED(result)) {
2641 aRv.Throw(result);
2642 return;
2645 #ifdef DEBUG_SELECTION
2646 nsCOMPtr<nsIContent> content = do_QueryInterface(aPoint.Container());
2647 nsCOMPtr<Document> doc = do_QueryInterface(aPoint.Container());
2648 printf("Sel. Collapse to %p %s %d\n", container.get(),
2649 content ? nsAtomCString(content->NodeInfo()->NameAtom()).get()
2650 : (doc ? "DOCUMENT" : "???"),
2651 aPoint.Offset());
2652 #endif
2654 Maybe<size_t> maybeRangeIndex;
2655 result = AddRangesForSelectableNodes(range, &maybeRangeIndex,
2656 DispatchSelectstartEvent::Maybe);
2657 if (NS_FAILED(result)) {
2658 aRv.Throw(result);
2659 return;
2661 SetAnchorFocusRange(0);
2662 SelectFrames(presContext, *range, true);
2664 RefPtr<Selection> kungFuDeathGrip{this};
2665 // Be aware, this instance may be destroyed after this call.
2666 NotifySelectionListeners();
2670 * Sets the whole selection to be one point
2671 * at the start of the current selection
2673 void Selection::CollapseToStartJS(ErrorResult& aRv) {
2674 if (NeedsToLogSelectionAPI(*this)) {
2675 LogSelectionAPI(this, __FUNCTION__);
2676 LogStackForSelectionAPI();
2679 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
2680 mCalledByJS = true;
2681 CollapseToStart(aRv);
2684 void Selection::CollapseToStart(ErrorResult& aRv) {
2685 if (!mCalledByJS && NeedsToLogSelectionAPI(*this)) {
2686 LogSelectionAPI(this, __FUNCTION__);
2687 LogStackForSelectionAPI();
2690 if (RangeCount() == 0) {
2691 aRv.ThrowInvalidStateError(kNoRangeExistsError);
2692 return;
2695 // Get the first range
2696 const AbstractRange* firstRange = mStyledRanges.mRanges[0].mRange;
2697 if (!firstRange) {
2698 aRv.Throw(NS_ERROR_FAILURE);
2699 return;
2702 if (mFrameSelection) {
2703 mFrameSelection->AddChangeReasons(
2704 nsISelectionListener::COLLAPSETOSTART_REASON);
2706 nsINode* container = firstRange->GetStartContainer();
2707 if (!container) {
2708 aRv.Throw(NS_ERROR_FAILURE);
2709 return;
2711 CollapseInternal(InLimiter::eNo,
2712 RawRangeBoundary(container, firstRange->StartOffset()), aRv);
2716 * Sets the whole selection to be one point
2717 * at the end of the current selection
2719 void Selection::CollapseToEndJS(ErrorResult& aRv) {
2720 if (NeedsToLogSelectionAPI(*this)) {
2721 LogSelectionAPI(this, __FUNCTION__);
2722 LogStackForSelectionAPI();
2725 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
2726 mCalledByJS = true;
2727 CollapseToEnd(aRv);
2730 void Selection::CollapseToEnd(ErrorResult& aRv) {
2731 if (!mCalledByJS && NeedsToLogSelectionAPI(*this)) {
2732 LogSelectionAPI(this, __FUNCTION__);
2733 LogStackForSelectionAPI();
2736 uint32_t cnt = RangeCount();
2737 if (cnt == 0) {
2738 aRv.ThrowInvalidStateError(kNoRangeExistsError);
2739 return;
2742 // Get the last range
2743 const AbstractRange* lastRange = mStyledRanges.mRanges[cnt - 1].mRange;
2744 if (!lastRange) {
2745 aRv.Throw(NS_ERROR_FAILURE);
2746 return;
2749 if (mFrameSelection) {
2750 mFrameSelection->AddChangeReasons(
2751 nsISelectionListener::COLLAPSETOEND_REASON);
2753 nsINode* container = lastRange->GetEndContainer();
2754 if (!container) {
2755 aRv.Throw(NS_ERROR_FAILURE);
2756 return;
2758 CollapseInternal(InLimiter::eNo,
2759 RawRangeBoundary(container, lastRange->EndOffset()), aRv);
2762 void Selection::GetType(nsAString& aOutType) const {
2763 if (!RangeCount()) {
2764 aOutType.AssignLiteral("None");
2765 } else if (IsCollapsed()) {
2766 aOutType.AssignLiteral("Caret");
2767 } else {
2768 aOutType.AssignLiteral("Range");
2772 nsRange* Selection::GetRangeAt(uint32_t aIndex, ErrorResult& aRv) {
2773 nsRange* range = GetRangeAt(aIndex);
2774 if (!range) {
2775 aRv.ThrowIndexSizeError(nsPrintfCString("%u is out of range", aIndex));
2776 return nullptr;
2779 return range;
2782 AbstractRange* Selection::GetAbstractRangeAt(uint32_t aIndex) const {
2783 StyledRange empty(nullptr);
2784 return mStyledRanges.mRanges.SafeElementAt(aIndex, empty).mRange;
2787 void Selection::GetDirection(nsAString& aDirection) const {
2788 if (mStyledRanges.mRanges.IsEmpty() ||
2789 (mFrameSelection && (mFrameSelection->IsDoubleClickSelection() ||
2790 mFrameSelection->IsTripleClickSelection()))) {
2791 // Empty range and double/triple clicks result a directionless selection.
2792 aDirection.AssignLiteral("none");
2793 } else if (mDirection == nsDirection::eDirPrevious) {
2794 aDirection.AssignLiteral("backward");
2795 } else {
2796 aDirection.AssignLiteral("forward");
2800 nsRange* Selection::GetRangeAt(uint32_t aIndex) const {
2801 // This method per IDL spec returns a dynamic range.
2802 // Therefore, it must be ensured that it is only called
2803 // for a selection which contains dynamic ranges exclusively.
2804 // Highlight Selections are allowed to contain StaticRanges,
2805 // therefore this method must not be called.
2806 MOZ_ASSERT(mSelectionType != SelectionType::eHighlight);
2807 AbstractRange* abstractRange = GetAbstractRangeAt(aIndex);
2808 if (!abstractRange) {
2809 return nullptr;
2811 return abstractRange->AsDynamicRange();
2814 nsresult Selection::SetAnchorFocusToRange(nsRange* aRange) {
2815 NS_ENSURE_STATE(mAnchorFocusRange);
2817 const DispatchSelectstartEvent dispatchSelectstartEvent =
2818 IsCollapsed() ? DispatchSelectstartEvent::Maybe
2819 : DispatchSelectstartEvent::No;
2821 nsresult rv =
2822 mStyledRanges.RemoveRangeAndUnregisterSelection(*mAnchorFocusRange);
2823 if (NS_FAILED(rv)) {
2824 return rv;
2827 Maybe<size_t> maybeOutIndex;
2828 rv = AddRangesForSelectableNodes(aRange, &maybeOutIndex,
2829 dispatchSelectstartEvent);
2830 if (NS_FAILED(rv)) {
2831 return rv;
2833 if (maybeOutIndex.isSome()) {
2834 SetAnchorFocusRange(*maybeOutIndex);
2835 } else {
2836 RemoveAnchorFocusRange();
2839 return NS_OK;
2842 void Selection::ReplaceAnchorFocusRange(nsRange* aRange) {
2843 NS_ENSURE_TRUE_VOID(mAnchorFocusRange);
2844 RefPtr<nsPresContext> presContext = GetPresContext();
2845 if (presContext) {
2846 SelectFrames(presContext, *mAnchorFocusRange, false);
2847 SetAnchorFocusToRange(aRange);
2848 SelectFrames(presContext, *mAnchorFocusRange, true);
2852 void Selection::AdjustAnchorFocusForMultiRange(nsDirection aDirection) {
2853 if (aDirection == mDirection) {
2854 return;
2856 SetDirection(aDirection);
2858 if (RangeCount() <= 1) {
2859 return;
2862 nsRange* firstRange = GetRangeAt(0);
2863 nsRange* lastRange = GetRangeAt(RangeCount() - 1);
2865 if (mDirection == eDirPrevious) {
2866 firstRange->SetIsGenerated(false);
2867 lastRange->SetIsGenerated(true);
2868 SetAnchorFocusRange(0);
2869 } else { // aDir == eDirNext
2870 firstRange->SetIsGenerated(true);
2871 lastRange->SetIsGenerated(false);
2872 SetAnchorFocusRange(RangeCount() - 1);
2877 * Extend extends the selection away from the anchor.
2878 * We don't need to know the direction, because we always change the focus.
2880 void Selection::ExtendJS(nsINode& aContainer, uint32_t aOffset,
2881 ErrorResult& aRv) {
2882 if (NeedsToLogSelectionAPI(*this)) {
2883 LogSelectionAPI(this, __FUNCTION__, "aContainer", &aContainer, "aOffset",
2884 aOffset);
2885 LogStackForSelectionAPI();
2888 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
2889 mCalledByJS = true;
2890 Extend(aContainer, aOffset, aRv);
2893 nsresult Selection::Extend(nsINode* aContainer, uint32_t aOffset) {
2894 if (NeedsToLogSelectionAPI(*this)) {
2895 LogSelectionAPI(this, __FUNCTION__, "aContainer", aContainer, "aOffset",
2896 aOffset);
2897 LogStackForSelectionAPI();
2900 if (!aContainer) {
2901 return NS_ERROR_INVALID_ARG;
2904 ErrorResult result;
2905 Extend(*aContainer, aOffset, result);
2906 return result.StealNSResult();
2909 void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
2910 ErrorResult& aRv) {
2912 Notes which might come in handy for extend:
2914 We can tell the direction of the selection by asking for the anchors
2915 selection if the begin is less than the end then we know the selection is to
2916 the "right", else it is a backwards selection. Notation: a = anchor, 1 = old
2917 cursor, 2 = new cursor.
2919 if (a <= 1 && 1 <=2) a,1,2 or (a1,2)
2920 if (a < 2 && 1 > 2) a,2,1
2921 if (1 < a && a <2) 1,a,2
2922 if (a > 2 && 2 >1) 1,2,a
2923 if (2 < a && a <1) 2,a,1
2924 if (a > 1 && 1 >2) 2,1,a
2925 then execute
2926 a 1 2 select from 1 to 2
2927 a 2 1 deselect from 2 to 1
2928 1 a 2 deselect from 1 to a select from a to 2
2929 1 2 a deselect from 1 to 2
2930 2 1 a = continue selection from 2 to 1
2933 // First, find the range containing the old focus point:
2934 if (!mAnchorFocusRange) {
2935 aRv.ThrowInvalidStateError(kNoRangeExistsError);
2936 return;
2939 if (!mFrameSelection) {
2940 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
2941 return;
2944 if (!HasSameRootOrSameComposedDoc(aContainer)) {
2945 // Return with no error
2946 return;
2949 nsresult res;
2950 if (!mFrameSelection->IsValidSelectionPoint(&aContainer)) {
2951 aRv.Throw(NS_ERROR_FAILURE);
2952 return;
2955 RefPtr<nsPresContext> presContext = GetPresContext();
2956 if (!presContext || presContext->Document() != aContainer.OwnerDoc()) {
2957 aRv.Throw(NS_ERROR_FAILURE);
2958 return;
2961 #ifdef DEBUG_SELECTION
2962 nsDirection oldDirection = GetDirection();
2963 #endif
2964 nsINode* anchorNode = GetMayCrossShadowBoundaryAnchorNode();
2965 nsINode* focusNode = GetMayCrossShadowBoundaryFocusNode();
2966 const uint32_t anchorOffset = MayCrossShadowBoundaryAnchorOffset();
2967 const uint32_t focusOffset = MayCrossShadowBoundaryFocusOffset();
2969 RefPtr<nsRange> range = mAnchorFocusRange->CloneRange();
2971 nsINode* startNode = range->GetMayCrossShadowBoundaryStartContainer();
2972 nsINode* endNode = range->GetMayCrossShadowBoundaryEndContainer();
2973 const uint32_t startOffset = range->MayCrossShadowBoundaryStartOffset();
2974 const uint32_t endOffset = range->MayCrossShadowBoundaryEndOffset();
2976 bool shouldClearRange = false;
2977 const Maybe<int32_t> anchorOldFocusOrder = nsContentUtils::ComparePoints(
2978 anchorNode, anchorOffset, focusNode, focusOffset);
2979 shouldClearRange |= !anchorOldFocusOrder;
2980 const Maybe<int32_t> oldFocusNewFocusOrder = nsContentUtils::ComparePoints(
2981 focusNode, focusOffset, &aContainer, aOffset);
2982 shouldClearRange |= !oldFocusNewFocusOrder;
2983 const Maybe<int32_t> anchorNewFocusOrder = nsContentUtils::ComparePoints(
2984 anchorNode, anchorOffset, &aContainer, aOffset);
2985 shouldClearRange |= !anchorNewFocusOrder;
2987 // If the points are disconnected, the range will be collapsed below,
2988 // resulting in a range that selects nothing.
2989 if (shouldClearRange) {
2990 // Repaint the current range with the selection removed.
2991 SelectFrames(presContext, *range, false);
2993 res = range->CollapseTo(&aContainer, aOffset);
2994 if (NS_FAILED(res)) {
2995 aRv.Throw(res);
2996 return;
2999 res = SetAnchorFocusToRange(range);
3000 if (NS_FAILED(res)) {
3001 aRv.Throw(res);
3002 return;
3004 } else {
3005 RefPtr<nsRange> difRange = nsRange::Create(&aContainer);
3006 if ((*anchorOldFocusOrder == 0 && *anchorNewFocusOrder < 0) ||
3007 (*anchorOldFocusOrder <= 0 &&
3008 *oldFocusNewFocusOrder < 0)) { // a1,2 a,1,2
3009 // select from 1 to 2 unless they are collapsed
3010 range->SetEnd(aContainer, aOffset, aRv,
3011 AllowRangeCrossShadowBoundary::Yes);
3012 if (aRv.Failed()) {
3013 return;
3015 SetDirection(eDirNext);
3016 res = difRange->SetStartAndEnd(
3017 focusNode, focusOffset, range->GetEndContainer(), range->EndOffset());
3018 if (NS_FAILED(res)) {
3019 aRv.Throw(res);
3020 return;
3022 SelectFrames(presContext, *difRange, true);
3023 res = SetAnchorFocusToRange(range);
3024 if (NS_FAILED(res)) {
3025 aRv.Throw(res);
3026 return;
3028 } else if (*anchorOldFocusOrder == 0 &&
3029 *anchorNewFocusOrder > 0) { // 2, a1
3030 // select from 2 to 1a
3031 SetDirection(eDirPrevious);
3032 range->SetStart(aContainer, aOffset, aRv,
3033 AllowRangeCrossShadowBoundary::Yes);
3034 if (aRv.Failed()) {
3035 return;
3037 SelectFrames(presContext, *range, true);
3038 res = SetAnchorFocusToRange(range);
3039 if (NS_FAILED(res)) {
3040 aRv.Throw(res);
3041 return;
3043 } else if (*anchorNewFocusOrder <= 0 &&
3044 *oldFocusNewFocusOrder >= 0) { // a,2,1 or a2,1 or a,21 or a21
3045 // deselect from 2 to 1
3046 res = difRange->SetStartAndEnd(&aContainer, aOffset, focusNode,
3047 focusOffset);
3048 if (NS_FAILED(res)) {
3049 aRv.Throw(res);
3050 return;
3053 range->SetEnd(aContainer, aOffset, aRv,
3054 AllowRangeCrossShadowBoundary::Yes);
3055 if (aRv.Failed()) {
3056 return;
3058 res = SetAnchorFocusToRange(range);
3059 if (NS_FAILED(res)) {
3060 aRv.Throw(res);
3061 return;
3063 SelectFrames(presContext, *difRange, false); // deselect now
3064 difRange->SetEnd(range->GetMayCrossShadowBoundaryEndContainer(),
3065 range->MayCrossShadowBoundaryEndOffset(),
3066 AllowRangeCrossShadowBoundary::Yes);
3067 SelectFrames(presContext, *difRange, true); // must reselect last node
3068 // maybe more
3069 } else if (*anchorOldFocusOrder >= 0 &&
3070 *anchorNewFocusOrder <= 0) { // 1,a,2 or 1a,2 or 1,a2 or 1a2
3071 if (GetDirection() == eDirPrevious) {
3072 res = range->SetStart(endNode, endOffset,
3073 AllowRangeCrossShadowBoundary::Yes);
3074 if (NS_FAILED(res)) {
3075 aRv.Throw(res);
3076 return;
3079 SetDirection(eDirNext);
3080 range->SetEnd(aContainer, aOffset, aRv,
3081 AllowRangeCrossShadowBoundary::Yes);
3082 if (aRv.Failed()) {
3083 return;
3085 if (focusNode != anchorNode ||
3086 focusOffset != anchorOffset) { // if collapsed diff dont do anything
3087 res = difRange->SetStart(focusNode, focusOffset,
3088 AllowRangeCrossShadowBoundary::Yes);
3089 nsresult tmp = difRange->SetEnd(anchorNode, anchorOffset,
3090 AllowRangeCrossShadowBoundary::Yes);
3091 if (NS_FAILED(tmp)) {
3092 res = tmp;
3094 if (NS_FAILED(res)) {
3095 aRv.Throw(res);
3096 return;
3098 res = SetAnchorFocusToRange(range);
3099 if (NS_FAILED(res)) {
3100 aRv.Throw(res);
3101 return;
3103 // deselect from 1 to a
3104 SelectFrames(presContext, *difRange, false);
3105 } else {
3106 res = SetAnchorFocusToRange(range);
3107 if (NS_FAILED(res)) {
3108 aRv.Throw(res);
3109 return;
3112 // select from a to 2
3113 SelectFrames(presContext, *range, true);
3114 } else if (*oldFocusNewFocusOrder <= 0 &&
3115 *anchorNewFocusOrder >= 0) { // 1,2,a or 12,a or 1,2a or 12a
3116 // deselect from 1 to 2
3117 res = difRange->SetStartAndEnd(focusNode, focusOffset, &aContainer,
3118 aOffset);
3119 if (NS_FAILED(res)) {
3120 aRv.Throw(res);
3121 return;
3123 SetDirection(eDirPrevious);
3124 range->SetStart(aContainer, aOffset, aRv,
3125 AllowRangeCrossShadowBoundary::Yes);
3126 if (aRv.Failed()) {
3127 return;
3130 res = SetAnchorFocusToRange(range);
3131 if (NS_FAILED(res)) {
3132 aRv.Throw(res);
3133 return;
3135 SelectFrames(presContext, *difRange, false);
3136 difRange->SetStart(range->GetMayCrossShadowBoundaryStartContainer(),
3137 range->MayCrossShadowBoundaryStartOffset(),
3138 AllowRangeCrossShadowBoundary::Yes);
3139 SelectFrames(presContext, *difRange, true); // must reselect last node
3140 } else if (*anchorNewFocusOrder >= 0 &&
3141 *anchorOldFocusOrder <= 0) { // 2,a,1 or 2a,1 or 2,a1 or 2a1
3142 if (GetDirection() == eDirNext) {
3143 range->SetEnd(startNode, startOffset,
3144 AllowRangeCrossShadowBoundary::Yes);
3146 SetDirection(eDirPrevious);
3147 range->SetStart(aContainer, aOffset, aRv,
3148 AllowRangeCrossShadowBoundary::Yes);
3149 if (aRv.Failed()) {
3150 return;
3152 // deselect from a to 1
3153 if (focusNode != anchorNode ||
3154 focusOffset != anchorOffset) { // if collapsed diff dont do anything
3155 res = difRange->SetStartAndEnd(anchorNode, anchorOffset, focusNode,
3156 focusOffset);
3157 nsresult tmp = SetAnchorFocusToRange(range);
3158 if (NS_FAILED(tmp)) {
3159 res = tmp;
3161 if (NS_FAILED(res)) {
3162 aRv.Throw(res);
3163 return;
3165 SelectFrames(presContext, *difRange, false);
3166 } else {
3167 res = SetAnchorFocusToRange(range);
3168 if (NS_FAILED(res)) {
3169 aRv.Throw(res);
3170 return;
3173 // select from 2 to a
3174 SelectFrames(presContext, *range, true);
3175 } else if (*oldFocusNewFocusOrder >= 0 &&
3176 *anchorOldFocusOrder >= 0) { // 2,1,a or 21,a or 2,1a or 21a
3177 // select from 2 to 1
3178 range->SetStart(aContainer, aOffset, aRv,
3179 AllowRangeCrossShadowBoundary::Yes);
3180 if (aRv.Failed()) {
3181 return;
3183 SetDirection(eDirPrevious);
3184 res = difRange->SetStartAndEnd(range->GetStartContainer(),
3185 range->StartOffset(), focusNode,
3186 focusOffset);
3187 if (NS_FAILED(res)) {
3188 aRv.Throw(res);
3189 return;
3192 SelectFrames(presContext, *difRange, true);
3193 res = SetAnchorFocusToRange(range);
3194 if (NS_FAILED(res)) {
3195 aRv.Throw(res);
3196 return;
3201 if (mStyledRanges.Length() > 1) {
3202 SelectFramesInAllRanges(presContext);
3205 DEBUG_OUT_RANGE(range);
3206 #ifdef DEBUG_SELECTION
3207 if (GetDirection() != oldDirection) {
3208 printf(" direction changed to %s\n",
3209 GetDirection() == eDirNext ? "eDirNext" : "eDirPrevious");
3211 nsCOMPtr<nsIContent> content = do_QueryInterface(&aContainer);
3212 printf("Sel. Extend to %p %s %d\n", content.get(),
3213 nsAtomCString(content->NodeInfo()->NameAtom()).get(), aOffset);
3214 #endif
3216 RefPtr<Selection> kungFuDeathGrip{this};
3217 // Be aware, this instance may be destroyed after this call.
3218 NotifySelectionListeners();
3221 void Selection::SelectAllChildrenJS(nsINode& aNode, ErrorResult& aRv) {
3222 if (NeedsToLogSelectionAPI(*this)) {
3223 LogSelectionAPI(this, __FUNCTION__, "aNode", &aNode);
3224 LogStackForSelectionAPI();
3227 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
3228 mCalledByJS = true;
3229 SelectAllChildren(aNode, aRv);
3232 void Selection::SelectAllChildren(nsINode& aNode, ErrorResult& aRv) {
3233 if (!mCalledByJS && NeedsToLogSelectionAPI(*this)) {
3234 LogSelectionAPI(this, __FUNCTION__, "aNode", &aNode);
3235 LogStackForSelectionAPI();
3238 if (aNode.NodeType() == nsINode::DOCUMENT_TYPE_NODE) {
3239 aRv.ThrowInvalidNodeTypeError(kNoDocumentTypeNodeError);
3240 return;
3243 if (!HasSameRootOrSameComposedDoc(aNode)) {
3244 // Return with no error
3245 return;
3248 if (mFrameSelection) {
3249 mFrameSelection->AddChangeReasons(nsISelectionListener::SELECTALL_REASON);
3252 // Chrome moves focus when aNode is outside of active editing host.
3253 // So, we don't need to respect the limiter with this method.
3254 SetStartAndEndInternal(InLimiter::eNo, RawRangeBoundary(&aNode, 0u),
3255 RawRangeBoundary(&aNode, aNode.GetChildCount()),
3256 eDirNext, aRv);
3259 bool Selection::ContainsNode(nsINode& aNode, bool aAllowPartial,
3260 ErrorResult& aRv) {
3261 nsresult rv;
3262 if (mStyledRanges.Length() == 0) {
3263 return false;
3266 // XXXbz this duplicates the GetNodeLength code in nsRange.cpp
3267 uint32_t nodeLength;
3268 auto* nodeAsCharData = CharacterData::FromNode(aNode);
3269 if (nodeAsCharData) {
3270 nodeLength = nodeAsCharData->TextLength();
3271 } else {
3272 nodeLength = aNode.GetChildCount();
3275 nsTArray<AbstractRange*> overlappingRanges;
3276 rv = GetAbstractRangesForIntervalArray(&aNode, 0, &aNode, nodeLength, false,
3277 &overlappingRanges);
3278 if (NS_FAILED(rv)) {
3279 aRv.Throw(rv);
3280 return false;
3282 if (overlappingRanges.Length() == 0) return false; // no ranges overlap
3284 // if the caller said partial intersections are OK, we're done
3285 if (aAllowPartial) {
3286 return true;
3289 // text nodes always count as inside
3290 if (nodeAsCharData) {
3291 return true;
3294 // The caller wants to know if the node is entirely within the given range,
3295 // so we have to check all intersecting ranges.
3296 for (uint32_t i = 0; i < overlappingRanges.Length(); i++) {
3297 bool nodeStartsBeforeRange, nodeEndsAfterRange;
3298 if (NS_SUCCEEDED(RangeUtils::CompareNodeToRange(
3299 &aNode, overlappingRanges[i], &nodeStartsBeforeRange,
3300 &nodeEndsAfterRange))) {
3301 if (!nodeStartsBeforeRange && !nodeEndsAfterRange) {
3302 return true;
3306 return false;
3309 class PointInRectChecker : public mozilla::RectCallback {
3310 public:
3311 explicit PointInRectChecker(const nsPoint& aPoint)
3312 : mPoint(aPoint), mMatchFound(false) {}
3314 void AddRect(const nsRect& aRect) override {
3315 mMatchFound = mMatchFound || aRect.Contains(mPoint);
3318 bool MatchFound() { return mMatchFound; }
3320 private:
3321 nsPoint mPoint;
3322 bool mMatchFound;
3325 bool Selection::ContainsPoint(const nsPoint& aPoint) {
3326 if (IsCollapsed()) {
3327 return false;
3329 PointInRectChecker checker(aPoint);
3330 const uint32_t rangeCount = RangeCount();
3331 for (const uint32_t i : IntegerRange(rangeCount)) {
3332 MOZ_ASSERT(RangeCount() == rangeCount);
3333 nsRange* range = GetRangeAt(i);
3334 MOZ_ASSERT(range);
3335 nsRange::CollectClientRectsAndText(
3336 &checker, nullptr, range, range->GetStartContainer(),
3337 range->StartOffset(), range->GetEndContainer(), range->EndOffset(),
3338 true, false);
3339 if (checker.MatchFound()) {
3340 return true;
3343 return false;
3346 void Selection::MaybeNotifyAccessibleCaretEventHub(PresShell* aPresShell) {
3347 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
3349 if (!mAccessibleCaretEventHub && aPresShell) {
3350 mAccessibleCaretEventHub = aPresShell->GetAccessibleCaretEventHub();
3354 void Selection::StopNotifyingAccessibleCaretEventHub() {
3355 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
3357 mAccessibleCaretEventHub = nullptr;
3360 nsPresContext* Selection::GetPresContext() const {
3361 PresShell* presShell = GetPresShell();
3362 return presShell ? presShell->GetPresContext() : nullptr;
3365 PresShell* Selection::GetPresShell() const {
3366 if (!mFrameSelection) {
3367 return nullptr; // nothing to do
3369 return mFrameSelection->GetPresShell();
3372 Document* Selection::GetDocument() const {
3373 PresShell* presShell = GetPresShell();
3374 return presShell ? presShell->GetDocument() : nullptr;
3377 nsIFrame* Selection::GetSelectionAnchorGeometry(SelectionRegion aRegion,
3378 nsRect* aRect) {
3379 if (!mFrameSelection) return nullptr; // nothing to do
3381 NS_ENSURE_TRUE(aRect, nullptr);
3383 aRect->SetRect(0, 0, 0, 0);
3385 switch (aRegion) {
3386 case nsISelectionController::SELECTION_ANCHOR_REGION:
3387 case nsISelectionController::SELECTION_FOCUS_REGION:
3388 return GetSelectionEndPointGeometry(aRegion, aRect);
3389 case nsISelectionController::SELECTION_WHOLE_SELECTION:
3390 break;
3391 default:
3392 return nullptr;
3395 NS_ASSERTION(aRegion == nsISelectionController::SELECTION_WHOLE_SELECTION,
3396 "should only be SELECTION_WHOLE_SELECTION here");
3398 nsRect anchorRect;
3399 nsIFrame* anchorFrame = GetSelectionEndPointGeometry(
3400 nsISelectionController::SELECTION_ANCHOR_REGION, &anchorRect);
3401 if (!anchorFrame) return nullptr;
3403 nsRect focusRect;
3404 nsIFrame* focusFrame = GetSelectionEndPointGeometry(
3405 nsISelectionController::SELECTION_FOCUS_REGION, &focusRect);
3406 if (!focusFrame) return nullptr;
3408 NS_ASSERTION(anchorFrame->PresContext() == focusFrame->PresContext(),
3409 "points of selection in different documents?");
3410 // make focusRect relative to anchorFrame
3411 focusRect += focusFrame->GetOffsetTo(anchorFrame);
3413 *aRect = anchorRect.UnionEdges(focusRect);
3414 return anchorFrame;
3417 nsIFrame* Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion,
3418 nsRect* aRect) {
3419 if (!mFrameSelection) return nullptr; // nothing to do
3421 NS_ENSURE_TRUE(aRect, nullptr);
3423 aRect->SetRect(0, 0, 0, 0);
3425 nsINode* node = nullptr;
3426 uint32_t nodeOffset = 0;
3427 nsIFrame* frame = nullptr;
3429 switch (aRegion) {
3430 case nsISelectionController::SELECTION_ANCHOR_REGION:
3431 node = GetAnchorNode();
3432 nodeOffset = AnchorOffset();
3433 break;
3434 case nsISelectionController::SELECTION_FOCUS_REGION:
3435 node = GetFocusNode();
3436 nodeOffset = FocusOffset();
3437 break;
3438 default:
3439 return nullptr;
3442 if (!node) return nullptr;
3444 nsCOMPtr<nsIContent> content = do_QueryInterface(node);
3445 NS_ENSURE_TRUE(content.get(), nullptr);
3446 uint32_t frameOffset = 0;
3447 frame = SelectionMovementUtils::GetFrameForNodeOffset(
3448 content, nodeOffset, mFrameSelection->GetHint(), &frameOffset);
3449 if (!frame) return nullptr;
3451 SelectionMovementUtils::AdjustFrameForLineStart(frame, frameOffset);
3453 // Figure out what node type we have, then get the
3454 // appropriate rect for its nodeOffset.
3455 bool isText = node->IsText();
3457 nsPoint pt(0, 0);
3458 if (isText) {
3459 nsIFrame* childFrame = nullptr;
3460 int32_t frameOffset = 0;
3461 nsresult rv = frame->GetChildFrameContainingOffset(
3462 nodeOffset, mFrameSelection->GetHint() == CaretAssociationHint::After,
3463 &frameOffset, &childFrame);
3464 if (NS_FAILED(rv)) return nullptr;
3465 if (!childFrame) return nullptr;
3467 frame = childFrame;
3469 // Get the coordinates of the offset into the text frame.
3470 rv = GetCachedFrameOffset(frame, nodeOffset, pt);
3471 if (NS_FAILED(rv)) return nullptr;
3474 // Return the rect relative to the frame, with zero inline-size. The
3475 // inline-position is either 'pt' (if we're a text node) or otherwise just
3476 // the physical "end" edge of the frame (which we express as the frame's own
3477 // width or height, since the returned position is relative to the frame).
3478 // The block position and size are set so as to fill the frame in that axis.
3479 // (i.e. block-position of 0, and block-size matching the frame's own block
3480 // size).
3481 const WritingMode wm = frame->GetWritingMode();
3482 // Helper to determine the inline-axis position for the aRect outparam.
3483 auto GetInlinePosition = [&]() {
3484 if (isText) {
3485 return wm.IsVertical() ? pt.y : pt.x;
3487 // Return the frame's physical end edge of its inline axis, relative to the
3488 // frame. That's just its height or width.
3489 // TODO(dholbert): This seems to work, but perhaps we really want the
3490 // inline-end edge (rather than physical end of inline axis)? (i.e. if we
3491 // have direction:rtl, maybe this code would want to return 0 instead of
3492 // height/width?)
3493 return frame->ISize(wm);
3496 // Set the inline position and block-size. Leave inline size and block
3497 // position set to 0, as discussed above.
3498 if (wm.IsVertical()) {
3499 aRect->y = GetInlinePosition();
3500 aRect->SetWidth(frame->BSize(wm));
3501 } else {
3502 aRect->x = GetInlinePosition();
3503 aRect->SetHeight(frame->BSize(wm));
3506 return frame;
3509 NS_IMETHODIMP
3510 Selection::ScrollSelectionIntoViewEvent::Run() {
3511 if (!mSelection) return NS_OK; // event revoked
3513 int32_t flags = Selection::SCROLL_DO_FLUSH | Selection::SCROLL_SYNCHRONOUS;
3515 const RefPtr<Selection> selection{mSelection};
3516 selection->mScrollEvent.Forget();
3517 selection->ScrollIntoView(mRegion, mVerticalScroll, mHorizontalScroll,
3518 mFlags | flags);
3519 return NS_OK;
3522 nsresult Selection::PostScrollSelectionIntoViewEvent(SelectionRegion aRegion,
3523 int32_t aFlags,
3524 ScrollAxis aVertical,
3525 ScrollAxis aHorizontal) {
3526 // If we've already posted an event, revoke it and place a new one at the
3527 // end of the queue to make sure that any new pending reflow events are
3528 // processed before we scroll. This will insure that we scroll to the
3529 // correct place on screen.
3530 mScrollEvent.Revoke();
3531 nsPresContext* presContext = GetPresContext();
3532 NS_ENSURE_STATE(presContext);
3533 nsRefreshDriver* refreshDriver = presContext->RefreshDriver();
3534 NS_ENSURE_STATE(refreshDriver);
3536 mScrollEvent = new ScrollSelectionIntoViewEvent(this, aRegion, aVertical,
3537 aHorizontal, aFlags);
3538 refreshDriver->AddEarlyRunner(mScrollEvent.get());
3539 return NS_OK;
3542 void Selection::ScrollIntoView(int16_t aRegion, bool aIsSynchronous,
3543 int16_t aVPercent, int16_t aHPercent,
3544 ErrorResult& aRv) {
3545 int32_t flags = aIsSynchronous ? Selection::SCROLL_SYNCHRONOUS : 0;
3546 // -1 means nearest in this API.
3547 const auto v =
3548 aVPercent == -1 ? WhereToScroll::Nearest : WhereToScroll(aVPercent);
3549 const auto h =
3550 aHPercent == -1 ? WhereToScroll::Nearest : WhereToScroll(aHPercent);
3551 nsresult rv = ScrollIntoView(aRegion, ScrollAxis(v), ScrollAxis(h), flags);
3552 if (NS_FAILED(rv)) {
3553 aRv.Throw(rv);
3557 nsresult Selection::ScrollIntoView(SelectionRegion aRegion,
3558 ScrollAxis aVertical, ScrollAxis aHorizontal,
3559 int32_t aFlags) {
3560 if (!mFrameSelection) {
3561 return NS_ERROR_NOT_INITIALIZED;
3564 RefPtr<PresShell> presShell = mFrameSelection->GetPresShell();
3565 if (!presShell || !presShell->GetDocument()) {
3566 return NS_OK;
3569 if (mFrameSelection->IsBatching()) {
3570 return NS_OK;
3573 if (!(aFlags & Selection::SCROLL_SYNCHRONOUS))
3574 return PostScrollSelectionIntoViewEvent(aRegion, aFlags, aVertical,
3575 aHorizontal);
3577 // From this point on, the presShell may get destroyed by the calls below, so
3578 // hold on to it using a strong reference to ensure the safety of the
3579 // accesses to frame pointers in the callees.
3580 RefPtr<PresShell> kungFuDeathGrip(presShell);
3582 // Now that text frame character offsets are always valid (though not
3583 // necessarily correct), the worst that will happen if we don't flush here
3584 // is that some callers might scroll to the wrong place. Those should
3585 // either manually flush if they're in a safe position for it or use the
3586 // async version of this method.
3587 if (aFlags & Selection::SCROLL_DO_FLUSH) {
3588 presShell->GetDocument()->FlushPendingNotifications(FlushType::Layout);
3590 // Reget the presshell, since it might have been Destroy'ed.
3591 presShell = mFrameSelection ? mFrameSelection->GetPresShell() : nullptr;
3592 if (!presShell) {
3593 return NS_OK;
3598 // Scroll the selection region into view.
3601 nsRect rect;
3602 nsIFrame* frame = GetSelectionAnchorGeometry(aRegion, &rect);
3603 if (!frame) return NS_ERROR_FAILURE;
3605 // Scroll vertically to get the caret into view, but only if the container
3606 // is perceived to be scrollable in that direction (i.e. there is a visible
3607 // vertical scrollbar or the scroll range is at least one device pixel)
3608 aVertical.mOnlyIfPerceivedScrollableDirection = true;
3610 auto scrollFlags = ScrollFlags::None;
3611 if (aFlags & Selection::SCROLL_FIRST_ANCESTOR_ONLY) {
3612 scrollFlags |= ScrollFlags::ScrollFirstAncestorOnly;
3614 if (aFlags & Selection::SCROLL_OVERFLOW_HIDDEN) {
3615 scrollFlags |= ScrollFlags::ScrollOverflowHidden;
3618 presShell->ScrollFrameIntoView(frame, Some(rect), aVertical, aHorizontal,
3619 scrollFlags);
3620 return NS_OK;
3623 void Selection::AddSelectionListener(nsISelectionListener* aNewListener) {
3624 MOZ_ASSERT(aNewListener);
3625 mSelectionListeners.AppendElement(aNewListener); // AddRefs
3628 void Selection::RemoveSelectionListener(
3629 nsISelectionListener* aListenerToRemove) {
3630 mSelectionListeners.RemoveElement(aListenerToRemove); // Releases
3633 Element* Selection::StyledRanges::GetCommonEditingHost() const {
3634 Element* editingHost = nullptr;
3635 for (const StyledRange& rangeData : mRanges) {
3636 const AbstractRange* range = rangeData.mRange;
3637 MOZ_ASSERT(range);
3638 nsINode* commonAncestorNode = range->GetClosestCommonInclusiveAncestor();
3639 if (!commonAncestorNode || !commonAncestorNode->IsContent()) {
3640 return nullptr;
3642 nsIContent* commonAncestor = commonAncestorNode->AsContent();
3643 Element* foundEditingHost = commonAncestor->GetEditingHost();
3644 // Even when common ancestor is a non-editable element in a contenteditable
3645 // element, we don't need to move focus to the contenteditable element
3646 // because Chromium doesn't set focus to it.
3647 if (!foundEditingHost) {
3648 return nullptr;
3650 if (!editingHost) {
3651 editingHost = foundEditingHost;
3652 continue;
3654 if (editingHost == foundEditingHost) {
3655 continue;
3657 if (foundEditingHost->IsInclusiveDescendantOf(editingHost)) {
3658 continue;
3660 if (editingHost->IsInclusiveDescendantOf(foundEditingHost)) {
3661 editingHost = foundEditingHost;
3662 continue;
3664 // editingHost and foundEditingHost are not a descendant of the other.
3665 // So, there is no common editing host.
3666 return nullptr;
3668 return editingHost;
3671 void Selection::StyledRanges::MaybeFocusCommonEditingHost(
3672 PresShell* aPresShell) const {
3673 if (!aPresShell) {
3674 return;
3677 nsPresContext* presContext = aPresShell->GetPresContext();
3678 if (!presContext) {
3679 return;
3682 Document* document = aPresShell->GetDocument();
3683 if (!document) {
3684 return;
3687 nsPIDOMWindowOuter* window = document->GetWindow();
3688 // If the document is in design mode or doesn't have contenteditable
3689 // element, we don't need to move focus.
3690 if (window && !document->IsInDesignMode() &&
3691 nsContentUtils::GetHTMLEditor(presContext)) {
3692 RefPtr<Element> newEditingHost = GetCommonEditingHost();
3693 RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
3694 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
3695 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
3696 window, nsFocusManager::eOnlyCurrentWindow,
3697 getter_AddRefs(focusedWindow));
3698 nsCOMPtr<Element> focusedElement = do_QueryInterface(focusedContent);
3699 // When all selected ranges are in an editing host, it should take focus.
3700 // But otherwise, we shouldn't move focus since Chromium doesn't move
3701 // focus but only selection range is updated.
3702 if (newEditingHost && newEditingHost != focusedElement) {
3703 MOZ_ASSERT(!newEditingHost->IsInNativeAnonymousSubtree());
3704 // Note that don't steal focus from focused window if the window doesn't
3705 // have focus. Additionally, although when an element gets focus, we
3706 // usually scroll to the element, but in this case, we shouldn't do it
3707 // because Chrome does not do so.
3708 fm->SetFocus(newEditingHost, nsIFocusManager::FLAG_NOSWITCHFRAME |
3709 nsIFocusManager::FLAG_NOSCROLL);
3714 void Selection::NotifySelectionListeners(bool aCalledByJS) {
3715 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
3716 mCalledByJS = aCalledByJS;
3717 NotifySelectionListeners();
3720 void Selection::NotifySelectionListeners() {
3721 if (!mFrameSelection) {
3722 return; // nothing to do
3725 MOZ_LOG(sSelectionLog, LogLevel::Debug,
3726 ("%s: selection=%p", __FUNCTION__, this));
3728 mStyledRanges.mRangesMightHaveChanged = true;
3730 // Our internal code should not move focus with using this class while
3731 // this moves focus nor from selection listeners.
3732 AutoRestore<bool> calledByJSRestorer(mCalledByJS);
3733 mCalledByJS = false;
3735 // When normal selection is changed by Selection API, we need to move focus
3736 // if common ancestor of all ranges are in an editing host. Note that we
3737 // don't need to move focus *to* the other focusable node because other
3738 // browsers don't do it either.
3739 if (mSelectionType == SelectionType::eNormal &&
3740 calledByJSRestorer.SavedValue()) {
3741 RefPtr<PresShell> presShell = GetPresShell();
3742 mStyledRanges.MaybeFocusCommonEditingHost(presShell);
3745 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
3747 // This flag will be set to Double or Triple if a selection by double click or
3748 // triple click is detected. As soon as the selection is modified, it needs to
3749 // be reset to NotApplicable.
3750 frameSelection->SetClickSelectionType(ClickSelectionType::NotApplicable);
3752 if (frameSelection->IsBatching()) {
3753 frameSelection->SetChangesDuringBatchingFlag();
3754 return;
3756 if (mSelectionListeners.IsEmpty() && !mNotifyAutoCopy &&
3757 !mAccessibleCaretEventHub && !mSelectionChangeEventDispatcher) {
3758 // If there are no selection listeners, we're done!
3759 return;
3762 nsCOMPtr<Document> doc;
3763 if (PresShell* presShell = GetPresShell()) {
3764 doc = presShell->GetDocument();
3765 presShell->ScheduleContentRelevancyUpdate(ContentRelevancyReason::Selected);
3768 // We've notified all selection listeners even when some of them are removed
3769 // (and may be destroyed) during notifying one of them. Therefore, we should
3770 // copy all listeners to the local variable first.
3771 const CopyableAutoTArray<nsCOMPtr<nsISelectionListener>, 5>
3772 selectionListeners = mSelectionListeners;
3774 int16_t reason = frameSelection->PopChangeReasons();
3775 if (calledByJSRestorer.SavedValue()) {
3776 reason |= nsISelectionListener::JS_REASON;
3779 int32_t amount = static_cast<int32_t>(frameSelection->GetCaretMoveAmount());
3781 if (mNotifyAutoCopy) {
3782 AutoCopyListener::OnSelectionChange(doc, *this, reason);
3785 if (mAccessibleCaretEventHub) {
3786 RefPtr<AccessibleCaretEventHub> hub(mAccessibleCaretEventHub);
3787 hub->OnSelectionChange(doc, this, reason);
3790 if (mSelectionChangeEventDispatcher) {
3791 RefPtr<SelectionChangeEventDispatcher> dispatcher(
3792 mSelectionChangeEventDispatcher);
3793 dispatcher->OnSelectionChange(doc, this, reason);
3796 for (const auto& listener : selectionListeners) {
3797 // MOZ_KnownLive because 'selectionListeners' is guaranteed to
3798 // keep it alive.
3800 // This can go away once
3801 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
3802 MOZ_KnownLive(listener)->NotifySelectionChanged(doc, this, reason, amount);
3806 void Selection::StartBatchChanges(const char* aDetails) {
3807 if (mFrameSelection) {
3808 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
3809 frameSelection->StartBatchChanges(aDetails);
3813 void Selection::EndBatchChanges(const char* aDetails, int16_t aReasons) {
3814 if (mFrameSelection) {
3815 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
3816 frameSelection->EndBatchChanges(aDetails, aReasons);
3820 void Selection::AddSelectionChangeBlocker() { mSelectionChangeBlockerCount++; }
3822 void Selection::RemoveSelectionChangeBlocker() {
3823 MOZ_ASSERT(mSelectionChangeBlockerCount > 0,
3824 "mSelectionChangeBlockerCount has an invalid value - "
3825 "maybe you have a mismatched RemoveSelectionChangeBlocker?");
3826 mSelectionChangeBlockerCount--;
3829 bool Selection::IsBlockingSelectionChangeEvents() const {
3830 return mSelectionChangeBlockerCount > 0;
3833 void Selection::DeleteFromDocument(ErrorResult& aRv) {
3834 if (NeedsToLogSelectionAPI(*this)) {
3835 LogSelectionAPI(this, __FUNCTION__);
3836 LogStackForSelectionAPI();
3839 if (mSelectionType != SelectionType::eNormal) {
3840 return; // Nothing to do.
3843 // If we're already collapsed, then we do nothing (bug 719503).
3844 if (IsCollapsed()) {
3845 return;
3848 // nsRange::DeleteContents() may run script, let's store all ranges first.
3849 AutoTArray<RefPtr<nsRange>, 1> ranges;
3850 MOZ_ASSERT(RangeCount() == mStyledRanges.mRanges.Length());
3851 ranges.SetCapacity(RangeCount());
3852 for (uint32_t index : IntegerRange(RangeCount())) {
3853 ranges.AppendElement(mStyledRanges.mRanges[index].mRange->AsDynamicRange());
3855 for (const auto& range : ranges) {
3856 MOZ_KnownLive(range)->DeleteContents(aRv);
3857 if (aRv.Failed()) {
3858 return;
3862 // Collapse to the new location.
3863 // If we deleted one character, then we move back one element.
3864 // FIXME We don't know how to do this past frame boundaries yet.
3865 if (AnchorOffset() > 0) {
3866 RefPtr<nsINode> anchor = GetAnchorNode();
3867 CollapseInLimiter(anchor, AnchorOffset());
3869 #ifdef DEBUG
3870 else {
3871 printf("Don't know how to set selection back past frame boundary\n");
3873 #endif
3876 void Selection::Modify(const nsAString& aAlter, const nsAString& aDirection,
3877 const nsAString& aGranularity, ErrorResult& aRv) {
3878 if (NeedsToLogSelectionAPI(*this)) {
3879 LogSelectionAPI(this, __FUNCTION__, "aAlter", aAlter, "aDirection",
3880 aDirection, "aGranularity", aGranularity);
3881 LogStackForSelectionAPI();
3884 if (!mFrameSelection) {
3885 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
3886 return;
3889 if (!GetAnchorFocusRange() || !GetFocusNode()) {
3890 return;
3893 if (!aAlter.LowerCaseEqualsLiteral("move") &&
3894 !aAlter.LowerCaseEqualsLiteral("extend")) {
3895 aRv.ThrowSyntaxError(
3896 R"(The first argument must be one of: "move" or "extend")");
3897 return;
3900 if (!aDirection.LowerCaseEqualsLiteral("forward") &&
3901 !aDirection.LowerCaseEqualsLiteral("backward") &&
3902 !aDirection.LowerCaseEqualsLiteral("left") &&
3903 !aDirection.LowerCaseEqualsLiteral("right")) {
3904 aRv.ThrowSyntaxError(
3905 R"(The direction argument must be one of: "forward", "backward", "left", or "right")");
3906 return;
3909 // Make sure the layout is up to date as we access bidi information below.
3910 if (RefPtr<Document> doc = GetDocument()) {
3911 doc->FlushPendingNotifications(FlushType::Layout);
3914 // Line moves are always visual.
3915 bool visual = aDirection.LowerCaseEqualsLiteral("left") ||
3916 aDirection.LowerCaseEqualsLiteral("right") ||
3917 aGranularity.LowerCaseEqualsLiteral("line");
3919 bool forward = aDirection.LowerCaseEqualsLiteral("forward") ||
3920 aDirection.LowerCaseEqualsLiteral("right");
3922 bool extend = aAlter.LowerCaseEqualsLiteral("extend");
3924 nsSelectionAmount amount;
3925 if (aGranularity.LowerCaseEqualsLiteral("character")) {
3926 amount = eSelectCluster;
3927 } else if (aGranularity.LowerCaseEqualsLiteral("word")) {
3928 amount = eSelectWordNoSpace;
3929 } else if (aGranularity.LowerCaseEqualsLiteral("line")) {
3930 amount = eSelectLine;
3931 } else if (aGranularity.LowerCaseEqualsLiteral("lineboundary")) {
3932 amount = forward ? eSelectEndLine : eSelectBeginLine;
3933 } else if (aGranularity.LowerCaseEqualsLiteral("sentence") ||
3934 aGranularity.LowerCaseEqualsLiteral("sentenceboundary") ||
3935 aGranularity.LowerCaseEqualsLiteral("paragraph") ||
3936 aGranularity.LowerCaseEqualsLiteral("paragraphboundary") ||
3937 aGranularity.LowerCaseEqualsLiteral("documentboundary")) {
3938 aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
3939 return;
3940 } else {
3941 aRv.ThrowSyntaxError(
3942 R"(The granularity argument must be one of: "character", "word", "line", or "lineboundary")");
3943 return;
3946 // If the anchor doesn't equal the focus and we try to move without first
3947 // collapsing the selection, MoveCaret will collapse the selection and quit.
3948 // To avoid this, we need to collapse the selection first.
3949 nsresult rv = NS_OK;
3950 if (!extend) {
3951 RefPtr<nsINode> focusNode = GetFocusNode();
3952 // We should have checked earlier that there was a focus node.
3953 if (!focusNode) {
3954 aRv.Throw(NS_ERROR_UNEXPECTED);
3955 return;
3957 uint32_t focusOffset = FocusOffset();
3958 CollapseInLimiter(focusNode, focusOffset);
3961 // If the paragraph direction of the focused frame is right-to-left,
3962 // we may have to swap the direction of movement.
3963 const PrimaryFrameData frameForFocus =
3964 GetPrimaryFrameForCaretAtFocusNode(visual);
3965 if (frameForFocus.mFrame) {
3966 if (visual) {
3967 // FYI: This was done during a call of GetPrimaryFrameForCaretAtFocusNode.
3968 // Therefore, this may not be intended by the original author.
3969 mFrameSelection->SetHint(frameForFocus.mHint);
3971 mozilla::intl::BidiDirection paraDir =
3972 nsBidiPresUtils::ParagraphDirection(frameForFocus.mFrame);
3974 if (paraDir == mozilla::intl::BidiDirection::RTL && visual) {
3975 if (amount == eSelectBeginLine) {
3976 amount = eSelectEndLine;
3977 forward = !forward;
3978 } else if (amount == eSelectEndLine) {
3979 amount = eSelectBeginLine;
3980 forward = !forward;
3985 // MoveCaret will return an error if it can't move in the specified
3986 // direction, but we just ignore this error unless it's a line move, in which
3987 // case we call nsISelectionController::CompleteMove to move the cursor to
3988 // the beginning/end of the line.
3989 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
3990 rv = frameSelection->MoveCaret(
3991 forward ? eDirNext : eDirPrevious, extend, amount,
3992 visual ? nsFrameSelection::eVisual : nsFrameSelection::eLogical);
3994 if (aGranularity.LowerCaseEqualsLiteral("line") && NS_FAILED(rv)) {
3995 RefPtr<PresShell> presShell = frameSelection->GetPresShell();
3996 if (!presShell) {
3997 return;
3999 presShell->CompleteMove(forward, extend);
4003 void Selection::SetBaseAndExtentJS(nsINode& aAnchorNode, uint32_t aAnchorOffset,
4004 nsINode& aFocusNode, uint32_t aFocusOffset,
4005 ErrorResult& aRv) {
4006 if (NeedsToLogSelectionAPI(*this)) {
4007 LogSelectionAPI(this, __FUNCTION__, "aAnchorNode", aAnchorNode,
4008 "aAnchorOffset", aAnchorOffset, "aFocusNode", aFocusNode,
4009 "aFocusOffset", aFocusOffset);
4010 LogStackForSelectionAPI();
4013 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
4014 mCalledByJS = true;
4015 SetBaseAndExtent(aAnchorNode, aAnchorOffset, aFocusNode, aFocusOffset, aRv);
4018 void Selection::SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset,
4019 nsINode& aFocusNode, uint32_t aFocusOffset,
4020 ErrorResult& aRv) {
4021 if (aAnchorOffset > aAnchorNode.Length()) {
4022 aRv.ThrowIndexSizeError(nsPrintfCString(
4023 "The anchor offset value %u is out of range", aAnchorOffset));
4024 return;
4026 if (aFocusOffset > aFocusNode.Length()) {
4027 aRv.ThrowIndexSizeError(nsPrintfCString(
4028 "The focus offset value %u is out of range", aFocusOffset));
4029 return;
4032 SetBaseAndExtent(RawRangeBoundary{&aAnchorNode, aAnchorOffset},
4033 RawRangeBoundary{&aFocusNode, aFocusOffset}, aRv);
4036 void Selection::SetBaseAndExtent(const RawRangeBoundary& aAnchorRef,
4037 const RawRangeBoundary& aFocusRef,
4038 ErrorResult& aRv) {
4039 if (!mCalledByJS && NeedsToLogSelectionAPI(*this)) {
4040 LogSelectionAPI(this, __FUNCTION__, "aAnchorRef", aAnchorRef, "aFocusRef",
4041 aFocusRef);
4042 LogStackForSelectionAPI();
4045 SetBaseAndExtentInternal(InLimiter::eNo, aAnchorRef, aFocusRef, aRv);
4048 void Selection::SetBaseAndExtentInLimiter(const RawRangeBoundary& aAnchorRef,
4049 const RawRangeBoundary& aFocusRef,
4050 ErrorResult& aRv) {
4051 if (NeedsToLogSelectionAPI(*this)) {
4052 LogSelectionAPI(this, __FUNCTION__, "aAnchorRef", aAnchorRef, "aFocusRef",
4053 aFocusRef);
4054 LogStackForSelectionAPI();
4057 SetBaseAndExtentInternal(InLimiter::eYes, aAnchorRef, aFocusRef, aRv);
4060 void Selection::SetBaseAndExtentInternal(InLimiter aInLimiter,
4061 const RawRangeBoundary& aAnchorRef,
4062 const RawRangeBoundary& aFocusRef,
4063 ErrorResult& aRv) {
4064 if (!mFrameSelection) {
4065 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
4066 return;
4069 if (NS_WARN_IF(!aAnchorRef.IsSet()) || NS_WARN_IF(!aFocusRef.IsSet())) {
4070 aRv.Throw(NS_ERROR_INVALID_ARG);
4071 return;
4074 if (!HasSameRootOrSameComposedDoc(*aAnchorRef.Container()) ||
4075 !HasSameRootOrSameComposedDoc(*aFocusRef.Container())) {
4076 // Return with no error
4077 return;
4080 // Prevent "selectionchange" event temporarily because it should be fired
4081 // after we set the direction.
4082 // XXX If they are disconnected, shouldn't we return error before allocating
4083 // new nsRange instance?
4084 SelectionBatcher batch(this, __FUNCTION__);
4085 const Maybe<int32_t> order =
4086 nsContentUtils::ComparePoints(aAnchorRef, aFocusRef);
4087 if (order && (*order <= 0)) {
4088 SetStartAndEndInternal(aInLimiter, aAnchorRef, aFocusRef, eDirNext, aRv);
4089 return;
4092 // If there's no `order`, the range will be collapsed, unless another error is
4093 // detected before.
4094 SetStartAndEndInternal(aInLimiter, aFocusRef, aAnchorRef, eDirPrevious, aRv);
4097 void Selection::SetStartAndEndInLimiter(const RawRangeBoundary& aStartRef,
4098 const RawRangeBoundary& aEndRef,
4099 ErrorResult& aRv) {
4100 if (NeedsToLogSelectionAPI(*this)) {
4101 LogSelectionAPI(this, __FUNCTION__, "aStartRef", aStartRef, "aEndRef",
4102 aEndRef);
4103 LogStackForSelectionAPI();
4106 SetStartAndEndInternal(InLimiter::eYes, aStartRef, aEndRef, eDirNext, aRv);
4109 Result<Ok, nsresult> Selection::SetStartAndEndInLimiter(
4110 nsINode& aStartContainer, uint32_t aStartOffset, nsINode& aEndContainer,
4111 uint32_t aEndOffset, nsDirection aDirection, int16_t aReason) {
4112 MOZ_ASSERT(aDirection == eDirPrevious || aDirection == eDirNext);
4113 if (NeedsToLogSelectionAPI(*this)) {
4114 LogSelectionAPI(this, __FUNCTION__, "aStartContainer", aStartContainer,
4115 "aStartOffset", aStartOffset, "aEndContainer",
4116 aEndContainer, "aEndOffset", aEndOffset, "nsDirection",
4117 aDirection, "aReason", aReason);
4118 LogStackForSelectionAPI();
4121 if (mFrameSelection) {
4122 mFrameSelection->AddChangeReasons(aReason);
4125 ErrorResult error;
4126 SetStartAndEndInternal(
4127 InLimiter::eYes, RawRangeBoundary(&aStartContainer, aStartOffset),
4128 RawRangeBoundary(&aEndContainer, aEndOffset), aDirection, error);
4129 MOZ_TRY(error.StealNSResult());
4130 return Ok();
4133 void Selection::SetStartAndEnd(const RawRangeBoundary& aStartRef,
4134 const RawRangeBoundary& aEndRef,
4135 ErrorResult& aRv) {
4136 if (NeedsToLogSelectionAPI(*this)) {
4137 LogSelectionAPI(this, __FUNCTION__, "aStartRef", aStartRef, "aEndRef",
4138 aEndRef);
4139 LogStackForSelectionAPI();
4142 SetStartAndEndInternal(InLimiter::eNo, aStartRef, aEndRef, eDirNext, aRv);
4145 void Selection::SetStartAndEndInternal(InLimiter aInLimiter,
4146 const RawRangeBoundary& aStartRef,
4147 const RawRangeBoundary& aEndRef,
4148 nsDirection aDirection,
4149 ErrorResult& aRv) {
4150 if (NS_WARN_IF(!aStartRef.IsSet()) || NS_WARN_IF(!aEndRef.IsSet())) {
4151 aRv.Throw(NS_ERROR_INVALID_ARG);
4152 return;
4155 // Don't fire "selectionchange" event until everything done.
4156 SelectionBatcher batch(this, __FUNCTION__);
4158 if (aInLimiter == InLimiter::eYes) {
4159 if (!mFrameSelection ||
4160 !mFrameSelection->IsValidSelectionPoint(aStartRef.Container())) {
4161 aRv.Throw(NS_ERROR_FAILURE);
4162 return;
4164 if (aStartRef.Container() != aEndRef.Container() &&
4165 !mFrameSelection->IsValidSelectionPoint(aEndRef.Container())) {
4166 aRv.Throw(NS_ERROR_FAILURE);
4167 return;
4171 RefPtr<nsRange> newRange = nsRange::Create(aStartRef, aEndRef, aRv);
4172 if (aRv.Failed()) {
4173 return;
4176 RemoveAllRangesInternal(aRv);
4177 if (aRv.Failed()) {
4178 return;
4181 RefPtr<Document> document(GetDocument());
4182 AddRangeAndSelectFramesAndNotifyListenersInternal(*newRange, document, aRv);
4183 if (aRv.Failed()) {
4184 return;
4187 // Adding a range may set 2 or more ranges if there are non-selectable
4188 // contents only when this change is caused by a user operation. Therefore,
4189 // we need to select frames with the result in such case.
4190 if (mUserInitiated) {
4191 RefPtr<nsPresContext> presContext = GetPresContext();
4192 if (mStyledRanges.Length() > 1 && presContext) {
4193 SelectFramesInAllRanges(presContext);
4197 SetDirection(aDirection);
4200 /** SelectionLanguageChange modifies the cursor Bidi level after a change in
4201 * keyboard direction
4202 * @param aLangRTL is true if the new language is right-to-left or false if the
4203 * new language is left-to-right
4205 nsresult Selection::SelectionLanguageChange(bool aLangRTL) {
4206 if (!mFrameSelection) {
4207 return NS_ERROR_NOT_INITIALIZED;
4210 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
4212 // if the direction of the language hasn't changed, nothing to do
4213 mozilla::intl::BidiEmbeddingLevel kbdBidiLevel =
4214 aLangRTL ? mozilla::intl::BidiEmbeddingLevel::RTL()
4215 : mozilla::intl::BidiEmbeddingLevel::LTR();
4216 if (kbdBidiLevel == frameSelection->mKbdBidiLevel) {
4217 return NS_OK;
4220 frameSelection->mKbdBidiLevel = kbdBidiLevel;
4222 PrimaryFrameData focusFrameData = GetPrimaryFrameForCaretAtFocusNode(false);
4223 if (!focusFrameData.mFrame) {
4224 return NS_ERROR_FAILURE;
4227 auto [frameStart, frameEnd] = focusFrameData.mFrame->GetOffsets();
4228 RefPtr<nsPresContext> context = GetPresContext();
4229 mozilla::intl::BidiEmbeddingLevel levelBefore, levelAfter;
4230 if (!context) {
4231 return NS_ERROR_FAILURE;
4234 mozilla::intl::BidiEmbeddingLevel level =
4235 focusFrameData.mFrame->GetEmbeddingLevel();
4236 int32_t focusOffset = static_cast<int32_t>(FocusOffset());
4237 if ((focusOffset != frameStart) && (focusOffset != frameEnd))
4238 // the cursor is not at a frame boundary, so the level of both the
4239 // characters (logically) before and after the cursor is equal to the frame
4240 // level
4241 levelBefore = levelAfter = level;
4242 else {
4243 // the cursor is at a frame boundary, so use GetPrevNextBidiLevels to find
4244 // the level of the characters before and after the cursor
4245 nsCOMPtr<nsIContent> focusContent = do_QueryInterface(GetFocusNode());
4246 nsPrevNextBidiLevels levels =
4247 frameSelection->GetPrevNextBidiLevels(focusContent, focusOffset, false);
4249 levelBefore = levels.mLevelBefore;
4250 levelAfter = levels.mLevelAfter;
4253 if (levelBefore.IsSameDirection(levelAfter)) {
4254 // if cursor is between two characters with the same orientation, changing
4255 // the keyboard language must toggle the cursor level between the level of
4256 // the character with the lowest level (if the new language corresponds to
4257 // the orientation of that character) and this level plus 1 (if the new
4258 // language corresponds to the opposite orientation)
4259 if ((level != levelBefore) && (level != levelAfter)) {
4260 level = std::min(levelBefore, levelAfter);
4262 if (level.IsSameDirection(kbdBidiLevel)) {
4263 frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(level);
4264 } else {
4265 frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(
4266 mozilla::intl::BidiEmbeddingLevel(level + 1));
4268 } else {
4269 // if cursor is between characters with opposite orientations, changing the
4270 // keyboard language must change the cursor level to that of the adjacent
4271 // character with the orientation corresponding to the new language.
4272 if (levelBefore.IsSameDirection(kbdBidiLevel)) {
4273 frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(levelBefore);
4274 } else {
4275 frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(levelAfter);
4279 // The caret might have moved, so invalidate the desired position
4280 // for future usages of up-arrow or down-arrow
4281 frameSelection->InvalidateDesiredCaretPos();
4283 return NS_OK;
4286 void Selection::SetColors(const nsAString& aForegroundColor,
4287 const nsAString& aBackgroundColor,
4288 const nsAString& aAltForegroundColor,
4289 const nsAString& aAltBackgroundColor,
4290 ErrorResult& aRv) {
4291 if (mSelectionType != SelectionType::eFind) {
4292 aRv.Throw(NS_ERROR_FAILURE);
4293 return;
4296 mCustomColors.reset(new SelectionCustomColors);
4298 constexpr auto currentColorStr = u"currentColor"_ns;
4299 constexpr auto transparentStr = u"transparent"_ns;
4301 if (!aForegroundColor.Equals(currentColorStr)) {
4302 nscolor foregroundColor;
4303 nsAttrValue aForegroundColorValue;
4304 aForegroundColorValue.ParseColor(aForegroundColor);
4305 if (!aForegroundColorValue.GetColorValue(foregroundColor)) {
4306 aRv.Throw(NS_ERROR_INVALID_ARG);
4307 return;
4309 mCustomColors->mForegroundColor = Some(foregroundColor);
4310 } else {
4311 mCustomColors->mForegroundColor = Nothing();
4314 if (!aBackgroundColor.Equals(transparentStr)) {
4315 nscolor backgroundColor;
4316 nsAttrValue aBackgroundColorValue;
4317 aBackgroundColorValue.ParseColor(aBackgroundColor);
4318 if (!aBackgroundColorValue.GetColorValue(backgroundColor)) {
4319 aRv.Throw(NS_ERROR_INVALID_ARG);
4320 return;
4322 mCustomColors->mBackgroundColor = Some(backgroundColor);
4323 } else {
4324 mCustomColors->mBackgroundColor = Nothing();
4327 if (!aAltForegroundColor.Equals(currentColorStr)) {
4328 nscolor altForegroundColor;
4329 nsAttrValue aAltForegroundColorValue;
4330 aAltForegroundColorValue.ParseColor(aAltForegroundColor);
4331 if (!aAltForegroundColorValue.GetColorValue(altForegroundColor)) {
4332 aRv.Throw(NS_ERROR_INVALID_ARG);
4333 return;
4335 mCustomColors->mAltForegroundColor = Some(altForegroundColor);
4336 } else {
4337 mCustomColors->mAltForegroundColor = Nothing();
4340 if (!aAltBackgroundColor.Equals(transparentStr)) {
4341 nscolor altBackgroundColor;
4342 nsAttrValue aAltBackgroundColorValue;
4343 aAltBackgroundColorValue.ParseColor(aAltBackgroundColor);
4344 if (!aAltBackgroundColorValue.GetColorValue(altBackgroundColor)) {
4345 aRv.Throw(NS_ERROR_INVALID_ARG);
4346 return;
4348 mCustomColors->mAltBackgroundColor = Some(altBackgroundColor);
4349 } else {
4350 mCustomColors->mAltBackgroundColor = Nothing();
4354 void Selection::ResetColors() { mCustomColors = nullptr; }
4356 void Selection::SetHighlightSelectionData(
4357 HighlightSelectionData aHighlightSelectionData) {
4358 MOZ_ASSERT(mSelectionType == SelectionType::eHighlight);
4359 mHighlightData = std::move(aHighlightSelectionData);
4362 JSObject* Selection::WrapObject(JSContext* aCx,
4363 JS::Handle<JSObject*> aGivenProto) {
4364 return mozilla::dom::Selection_Binding::Wrap(aCx, this, aGivenProto);
4367 // AutoHideSelectionChanges
4368 AutoHideSelectionChanges::AutoHideSelectionChanges(
4369 const nsFrameSelection* aFrame)
4370 : AutoHideSelectionChanges(
4371 aFrame ? aFrame->GetSelection(SelectionType::eNormal) : nullptr) {}
4373 bool Selection::HasSameRootOrSameComposedDoc(const nsINode& aNode) {
4374 nsINode* root = aNode.SubtreeRoot();
4375 Document* doc = GetDocument();
4376 return doc == root || (root && doc == root->GetComposedDoc());