Bug 1854550 - pt 10. Allow LOG() with zero extra arguments r=glandium
[gecko.git] / dom / base / Selection.cpp
blob4a6d2c3cf26bccbd96a6cf810c4398b1e6e38a36
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/ContentIterator.h"
23 #include "mozilla/dom/Element.h"
24 #include "mozilla/dom/SelectionBinding.h"
25 #include "mozilla/dom/ShadowRoot.h"
26 #include "mozilla/dom/StaticRange.h"
27 #include "mozilla/ErrorResult.h"
28 #include "mozilla/HTMLEditor.h"
29 #include "mozilla/IntegerRange.h"
30 #include "mozilla/intl/BidiEmbeddingLevel.h"
31 #include "mozilla/Logging.h"
32 #include "mozilla/PresShell.h"
33 #include "mozilla/RangeBoundary.h"
34 #include "mozilla/RangeUtils.h"
35 #include "mozilla/StackWalk.h"
36 #include "mozilla/StaticPrefs_dom.h"
37 #include "mozilla/Telemetry.h"
38 #include "mozilla/Try.h"
40 #include "nsCOMPtr.h"
41 #include "nsDebug.h"
42 #include "nsDirection.h"
43 #include "nsString.h"
44 #include "nsFrameSelection.h"
45 #include "nsISelectionListener.h"
46 #include "nsContentCID.h"
47 #include "nsDeviceContext.h"
48 #include "nsIContent.h"
49 #include "nsIContentInlines.h"
50 #include "nsRange.h"
51 #include "nsITableCellLayout.h"
52 #include "nsTArray.h"
53 #include "nsTableWrapperFrame.h"
54 #include "nsTableCellFrame.h"
55 #include "nsIScrollableFrame.h"
56 #include "nsCCUncollectableMarker.h"
57 #include "nsIDocumentEncoder.h"
58 #include "nsTextFragment.h"
59 #include <algorithm>
60 #include "nsContentUtils.h"
62 #include "nsGkAtoms.h"
63 #include "nsLayoutUtils.h"
64 #include "nsBidiPresUtils.h"
65 #include "nsTextFrame.h"
67 #include "nsThreadUtils.h"
69 #include "nsPresContext.h"
70 #include "nsCaret.h"
72 #include "nsITimer.h"
73 #include "mozilla/dom/Document.h"
74 #include "nsINamed.h"
76 #include "nsISelectionController.h" //for the enums
77 #include "nsCopySupport.h"
78 #include "nsIFrameInlines.h"
79 #include "nsRefreshDriver.h"
81 #include "nsError.h"
82 #include "nsViewManager.h"
84 #include "nsFocusManager.h"
85 #include "nsPIDOMWindow.h"
87 namespace mozilla {
88 // "Selection" logs only the calls of AddRangesForSelectableNodes and
89 // NotifySelectionListeners in debug level.
90 static LazyLogModule sSelectionLog("Selection");
91 // "SelectionAPI" logs all API calls (both internal ones and exposed to script
92 // ones) of normal selection which may change selection ranges.
93 // 3. Info: Calls of APIs
94 // 4. Debug: Call stacks with 7 ancestor callers of APIs
95 // 5. Verbose: Complete call stacks of APIs.
96 LazyLogModule sSelectionAPILog("SelectionAPI");
98 MOZ_ALWAYS_INLINE bool NeedsToLogSelectionAPI(dom::Selection& aSelection) {
99 return aSelection.Type() == SelectionType::eNormal &&
100 MOZ_LOG_TEST(sSelectionAPILog, LogLevel::Info);
103 void LogStackForSelectionAPI() {
104 if (!MOZ_LOG_TEST(sSelectionAPILog, LogLevel::Debug)) {
105 return;
107 static nsAutoCString* sBufPtr = nullptr;
108 MOZ_ASSERT(!sBufPtr);
109 nsAutoCString buf;
110 sBufPtr = &buf;
111 auto writer = [](const char* aBuf) { sBufPtr->Append(aBuf); };
112 const LogLevel logLevel = MOZ_LOG_TEST(sSelectionAPILog, LogLevel::Verbose)
113 ? LogLevel::Verbose
114 : LogLevel::Debug;
115 MozWalkTheStackWithWriter(writer, CallerPC(),
116 logLevel == LogLevel::Verbose
117 ? 0u /* all */
118 : 8u /* 8 inclusive ancestors */);
119 MOZ_LOG(sSelectionAPILog, logLevel, ("\n%s", buf.get()));
120 sBufPtr = nullptr;
123 static void LogSelectionAPI(const dom::Selection* aSelection,
124 const char* aFuncName) {
125 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
126 ("%p Selection::%s()", aSelection, aFuncName));
129 static void LogSelectionAPI(const dom::Selection* aSelection,
130 const char* aFuncName, const char* aArgName,
131 const nsINode* aNode) {
132 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
133 ("%p Selection::%s(%s=%s)", aSelection, aFuncName, aArgName,
134 aNode ? ToString(*aNode).c_str() : "nullptr"));
137 static void LogSelectionAPI(const dom::Selection* aSelection,
138 const char* aFuncName, const char* aArgName,
139 const dom::AbstractRange& aRange) {
140 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
141 ("%p Selection::%s(%s=%s)", aSelection, aFuncName, aArgName,
142 ToString(aRange).c_str()));
145 static void LogSelectionAPI(const dom::Selection* aSelection,
146 const char* aFuncName, const char* aArgName1,
147 const nsINode* aNode, const char* aArgName2,
148 uint32_t aOffset) {
149 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
150 ("%p Selection::%s(%s=%s, %s=%u)", aSelection, aFuncName, aArgName1,
151 aNode ? ToString(*aNode).c_str() : "nullptr", aArgName2, aOffset));
154 static void LogSelectionAPI(const dom::Selection* aSelection,
155 const char* aFuncName, const char* aArgName,
156 const RawRangeBoundary& aBoundary) {
157 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
158 ("%p Selection::%s(%s=%s)", aSelection, aFuncName, aArgName,
159 ToString(aBoundary).c_str()));
162 static void LogSelectionAPI(const dom::Selection* aSelection,
163 const char* aFuncName, const char* aArgName1,
164 const nsAString& aStr1, const char* aArgName2,
165 const nsAString& aStr2, const char* aArgName3,
166 const nsAString& aStr3) {
167 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
168 ("%p Selection::%s(%s=%s, %s=%s, %s=%s)", aSelection, aFuncName,
169 aArgName1, NS_ConvertUTF16toUTF8(aStr1).get(), aArgName2,
170 NS_ConvertUTF16toUTF8(aStr2).get(), aArgName3,
171 NS_ConvertUTF16toUTF8(aStr3).get()));
174 static void LogSelectionAPI(const dom::Selection* aSelection,
175 const char* aFuncName, const char* aNodeArgName1,
176 const nsINode& aNode1, const char* aOffsetArgName1,
177 uint32_t aOffset1, const char* aNodeArgName2,
178 const nsINode& aNode2, const char* aOffsetArgName2,
179 uint32_t aOffset2) {
180 if (&aNode1 == &aNode2 && aOffset1 == aOffset2) {
181 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
182 ("%p Selection::%s(%s=%s=%s, %s=%s=%u)", aSelection, aFuncName,
183 aNodeArgName1, aNodeArgName2, ToString(aNode1).c_str(),
184 aOffsetArgName1, aOffsetArgName2, aOffset1));
185 } else {
186 MOZ_LOG(
187 sSelectionAPILog, LogLevel::Info,
188 ("%p Selection::%s(%s=%s, %s=%u, %s=%s, %s=%u)", aSelection, aFuncName,
189 aNodeArgName1, ToString(aNode1).c_str(), aOffsetArgName1, aOffset1,
190 aNodeArgName2, ToString(aNode2).c_str(), aOffsetArgName2, aOffset2));
194 static void LogSelectionAPI(const dom::Selection* aSelection,
195 const char* aFuncName, const char* aNodeArgName1,
196 const nsINode& aNode1, const char* aOffsetArgName1,
197 uint32_t aOffset1, const char* aNodeArgName2,
198 const nsINode& aNode2, const char* aOffsetArgName2,
199 uint32_t aOffset2, const char* aDirArgName,
200 nsDirection aDirection, const char* aReasonArgName,
201 int16_t aReason) {
202 if (&aNode1 == &aNode2 && aOffset1 == aOffset2) {
203 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
204 ("%p Selection::%s(%s=%s=%s, %s=%s=%u, %s=%s, %s=%d)", aSelection,
205 aFuncName, aNodeArgName1, aNodeArgName2, ToString(aNode1).c_str(),
206 aOffsetArgName1, aOffsetArgName2, aOffset1, aDirArgName,
207 ToString(aDirection).c_str(), aReasonArgName, aReason));
208 } else {
209 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
210 ("%p Selection::%s(%s=%s, %s=%u, %s=%s, %s=%u, %s=%s, %s=%d)",
211 aSelection, aFuncName, aNodeArgName1, ToString(aNode1).c_str(),
212 aOffsetArgName1, aOffset1, aNodeArgName2, ToString(aNode2).c_str(),
213 aOffsetArgName2, aOffset2, aDirArgName,
214 ToString(aDirection).c_str(), aReasonArgName, aReason));
218 static void LogSelectionAPI(const dom::Selection* aSelection,
219 const char* aFuncName, const char* aArgName1,
220 const RawRangeBoundary& aBoundary1,
221 const char* aArgName2,
222 const RawRangeBoundary& aBoundary2) {
223 if (aBoundary1 == aBoundary2) {
224 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
225 ("%p Selection::%s(%s=%s=%s)", aSelection, aFuncName, aArgName1,
226 aArgName2, ToString(aBoundary1).c_str()));
227 } else {
228 MOZ_LOG(sSelectionAPILog, LogLevel::Info,
229 ("%p Selection::%s(%s=%s, %s=%s)", aSelection, aFuncName, aArgName1,
230 ToString(aBoundary1).c_str(), aArgName2,
231 ToString(aBoundary2).c_str()));
234 } // namespace mozilla
236 using namespace mozilla;
237 using namespace mozilla::dom;
239 // #define DEBUG_TABLE 1
241 #ifdef PRINT_RANGE
242 static void printRange(nsRange* aDomRange);
243 # define DEBUG_OUT_RANGE(x) printRange(x)
244 #else
245 # define DEBUG_OUT_RANGE(x)
246 #endif // PRINT_RANGE
248 static constexpr nsLiteralCString kNoDocumentTypeNodeError =
249 "DocumentType nodes are not supported"_ns;
250 static constexpr nsLiteralCString kNoRangeExistsError =
251 "No selection range exists"_ns;
253 namespace mozilla {
255 /******************************************************************************
256 * Utility methods defined in nsISelectionController.idl
257 ******************************************************************************/
259 const char* ToChar(SelectionType aSelectionType) {
260 switch (aSelectionType) {
261 case SelectionType::eInvalid:
262 return "SelectionType::eInvalid";
263 case SelectionType::eNone:
264 return "SelectionType::eNone";
265 case SelectionType::eNormal:
266 return "SelectionType::eNormal";
267 case SelectionType::eSpellCheck:
268 return "SelectionType::eSpellCheck";
269 case SelectionType::eIMERawClause:
270 return "SelectionType::eIMERawClause";
271 case SelectionType::eIMESelectedRawClause:
272 return "SelectionType::eIMESelectedRawClause";
273 case SelectionType::eIMEConvertedClause:
274 return "SelectionType::eIMEConvertedClause";
275 case SelectionType::eIMESelectedClause:
276 return "SelectionType::eIMESelectedClause";
277 case SelectionType::eAccessibility:
278 return "SelectionType::eAccessibility";
279 case SelectionType::eFind:
280 return "SelectionType::eFind";
281 case SelectionType::eURLSecondary:
282 return "SelectionType::eURLSecondary";
283 case SelectionType::eURLStrikeout:
284 return "SelectionType::eURLStrikeout";
285 default:
286 return "Invalid SelectionType";
290 /******************************************************************************
291 * Utility methods defined in nsISelectionListener.idl
292 ******************************************************************************/
294 nsCString SelectionChangeReasonsToCString(int16_t aReasons) {
295 nsCString reasons;
296 if (!aReasons) {
297 reasons.AssignLiteral("NO_REASON");
298 return reasons;
300 auto EnsureSeparator = [](nsCString& aString) -> void {
301 if (!aString.IsEmpty()) {
302 aString.AppendLiteral(" | ");
305 struct ReasonData {
306 int16_t mReason;
307 const char* mReasonStr;
309 ReasonData(int16_t aReason, const char* aReasonStr)
310 : mReason(aReason), mReasonStr(aReasonStr) {}
312 for (const ReasonData& reason :
313 {ReasonData(nsISelectionListener::DRAG_REASON, "DRAG_REASON"),
314 ReasonData(nsISelectionListener::MOUSEDOWN_REASON, "MOUSEDOWN_REASON"),
315 ReasonData(nsISelectionListener::MOUSEUP_REASON, "MOUSEUP_REASON"),
316 ReasonData(nsISelectionListener::KEYPRESS_REASON, "KEYPRESS_REASON"),
317 ReasonData(nsISelectionListener::SELECTALL_REASON, "SELECTALL_REASON"),
318 ReasonData(nsISelectionListener::COLLAPSETOSTART_REASON,
319 "COLLAPSETOSTART_REASON"),
320 ReasonData(nsISelectionListener::COLLAPSETOEND_REASON,
321 "COLLAPSETOEND_REASON"),
322 ReasonData(nsISelectionListener::IME_REASON, "IME_REASON"),
323 ReasonData(nsISelectionListener::JS_REASON, "JS_REASON")}) {
324 if (aReasons & reason.mReason) {
325 EnsureSeparator(reasons);
326 reasons.Append(reason.mReasonStr);
329 return reasons;
332 } // namespace mozilla
334 // #define DEBUG_SELECTION // uncomment for printf describing every collapse and
335 // extend. #define DEBUG_NAVIGATION
337 // #define DEBUG_TABLE_SELECTION 1
339 struct CachedOffsetForFrame {
340 CachedOffsetForFrame()
341 : mCachedFrameOffset(0, 0) // nsPoint ctor
343 mLastCaretFrame(nullptr),
344 mLastContentOffset(0),
345 mCanCacheFrameOffset(false) {}
347 nsPoint mCachedFrameOffset; // cached frame offset
348 nsIFrame* mLastCaretFrame; // store the frame the caret was last drawn in.
349 int32_t mLastContentOffset; // store last content offset
350 bool mCanCacheFrameOffset; // cached frame offset is valid?
353 class AutoScroller final : public nsITimerCallback, public nsINamed {
354 public:
355 NS_DECL_ISUPPORTS
357 explicit AutoScroller(nsFrameSelection* aFrameSelection)
358 : mFrameSelection(aFrameSelection),
359 mPresContext(0),
360 mPoint(0, 0),
361 mDelayInMs(30),
362 mFurtherScrollingAllowed(FurtherScrollingAllowed::kYes) {
363 MOZ_ASSERT(mFrameSelection);
366 MOZ_CAN_RUN_SCRIPT nsresult DoAutoScroll(nsIFrame* aFrame, nsPoint aPoint);
368 private:
369 // aPoint is relative to aPresContext's root frame
370 nsresult ScheduleNextDoAutoScroll(nsPresContext* aPresContext,
371 nsPoint& aPoint) {
372 if (NS_WARN_IF(mFurtherScrollingAllowed == FurtherScrollingAllowed::kNo)) {
373 return NS_ERROR_FAILURE;
376 mPoint = aPoint;
378 // Store the presentation context. The timer will be
379 // stopped by the selection if the prescontext is destroyed.
380 mPresContext = aPresContext;
382 mContent = PresShell::GetCapturingContent();
384 if (!mTimer) {
385 mTimer = NS_NewTimer(GetMainThreadSerialEventTarget());
386 if (!mTimer) {
387 return NS_ERROR_OUT_OF_MEMORY;
391 return mTimer->InitWithCallback(this, mDelayInMs, nsITimer::TYPE_ONE_SHOT);
394 public:
395 enum class FurtherScrollingAllowed { kYes, kNo };
397 void Stop(const FurtherScrollingAllowed aFurtherScrollingAllowed) {
398 MOZ_ASSERT((aFurtherScrollingAllowed == FurtherScrollingAllowed::kNo) ||
399 (mFurtherScrollingAllowed == FurtherScrollingAllowed::kYes));
401 if (mTimer) {
402 mTimer->Cancel();
403 mTimer = nullptr;
406 mContent = nullptr;
407 mFurtherScrollingAllowed = aFurtherScrollingAllowed;
410 void SetDelay(uint32_t aDelayInMs) { mDelayInMs = aDelayInMs; }
412 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Notify(nsITimer* timer) override {
413 if (mPresContext) {
414 AutoWeakFrame frame =
415 mContent ? mPresContext->GetPrimaryFrameFor(mContent) : nullptr;
416 if (!frame) {
417 return NS_OK;
419 mContent = nullptr;
421 nsPoint pt = mPoint - frame->GetOffsetTo(
422 mPresContext->PresShell()->GetRootFrame());
423 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
424 frameSelection->HandleDrag(frame, pt);
425 if (!frame.IsAlive()) {
426 return NS_OK;
429 NS_ASSERTION(frame->PresContext() == mPresContext, "document mismatch?");
430 DoAutoScroll(frame, pt);
432 return NS_OK;
435 NS_IMETHOD GetName(nsACString& aName) override {
436 aName.AssignLiteral("AutoScroller");
437 return NS_OK;
440 protected:
441 virtual ~AutoScroller() {
442 if (mTimer) {
443 mTimer->Cancel();
447 private:
448 nsFrameSelection* const mFrameSelection;
449 nsPresContext* mPresContext;
450 // relative to mPresContext's root frame
451 nsPoint mPoint;
452 nsCOMPtr<nsITimer> mTimer;
453 nsCOMPtr<nsIContent> mContent;
454 uint32_t mDelayInMs;
455 FurtherScrollingAllowed mFurtherScrollingAllowed;
458 NS_IMPL_ISUPPORTS(AutoScroller, nsITimerCallback, nsINamed)
460 #ifdef PRINT_RANGE
461 void printRange(nsRange* aDomRange) {
462 if (!aDomRange) {
463 printf("NULL Range\n");
465 nsINode* startNode = aDomRange->GetStartContainer();
466 nsINode* endNode = aDomRange->GetEndContainer();
467 int32_t startOffset = aDomRange->StartOffset();
468 int32_t endOffset = aDomRange->EndOffset();
470 printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n",
471 (unsigned long)aDomRange, (unsigned long)startNode, (long)startOffset,
472 (unsigned long)endNode, (long)endOffset);
474 #endif /* PRINT_RANGE */
476 void Selection::Stringify(nsAString& aResult, FlushFrames aFlushFrames) {
477 if (aFlushFrames == FlushFrames::Yes) {
478 // We need FlushType::Frames here to make sure frames have been created for
479 // the selected content. Use mFrameSelection->GetPresShell() which returns
480 // null if the Selection has been disconnected (the shell is Destroyed).
481 RefPtr<PresShell> presShell =
482 mFrameSelection ? mFrameSelection->GetPresShell() : nullptr;
483 if (!presShell) {
484 aResult.Truncate();
485 return;
487 presShell->FlushPendingNotifications(FlushType::Frames);
490 IgnoredErrorResult rv;
491 ToStringWithFormat(u"text/plain"_ns, nsIDocumentEncoder::SkipInvisibleContent,
492 0, aResult, rv);
493 if (rv.Failed()) {
494 aResult.Truncate();
498 void Selection::ToStringWithFormat(const nsAString& aFormatType,
499 uint32_t aFlags, int32_t aWrapCol,
500 nsAString& aReturn, ErrorResult& aRv) {
501 nsCOMPtr<nsIDocumentEncoder> encoder =
502 do_createDocumentEncoder(NS_ConvertUTF16toUTF8(aFormatType).get());
503 if (!encoder) {
504 aRv.Throw(NS_ERROR_FAILURE);
505 return;
508 PresShell* presShell = GetPresShell();
509 if (!presShell) {
510 aRv.Throw(NS_ERROR_FAILURE);
511 return;
514 Document* doc = presShell->GetDocument();
516 // Flags should always include OutputSelectionOnly if we're coming from here:
517 aFlags |= nsIDocumentEncoder::OutputSelectionOnly;
518 nsAutoString readstring;
519 readstring.Assign(aFormatType);
520 nsresult rv = encoder->Init(doc, readstring, aFlags);
521 if (NS_FAILED(rv)) {
522 aRv.Throw(rv);
523 return;
526 encoder->SetSelection(this);
527 if (aWrapCol != 0) encoder->SetWrapColumn(aWrapCol);
529 rv = encoder->EncodeToString(aReturn);
530 if (NS_FAILED(rv)) {
531 aRv.Throw(rv);
535 nsresult Selection::SetInterlinePosition(InterlinePosition aInterlinePosition) {
536 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
537 MOZ_ASSERT(aInterlinePosition != InterlinePosition::Undefined);
539 if (!mFrameSelection) {
540 return NS_ERROR_NOT_INITIALIZED; // Can't do selection
543 mFrameSelection->SetHint(aInterlinePosition ==
544 InterlinePosition::StartOfNextLine
545 ? CARET_ASSOCIATE_AFTER
546 : CARET_ASSOCIATE_BEFORE);
547 return NS_OK;
550 Selection::InterlinePosition Selection::GetInterlinePosition() const {
551 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
553 if (!mFrameSelection) {
554 return InterlinePosition::Undefined;
556 return mFrameSelection->GetHint() == CARET_ASSOCIATE_AFTER
557 ? InterlinePosition::StartOfNextLine
558 : InterlinePosition::EndOfLine;
561 void Selection::SetInterlinePositionJS(bool aHintRight, ErrorResult& aRv) {
562 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
564 aRv = SetInterlinePosition(aHintRight ? InterlinePosition::StartOfNextLine
565 : InterlinePosition::EndOfLine);
568 bool Selection::GetInterlinePositionJS(ErrorResult& aRv) const {
569 const InterlinePosition interlinePosition = GetInterlinePosition();
570 if (interlinePosition == InterlinePosition::Undefined) {
571 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
572 return false;
574 return interlinePosition == InterlinePosition::StartOfNextLine;
577 static bool IsEditorNode(const nsINode* aNode) {
578 if (!aNode) {
579 return false;
582 if (aNode->IsEditable()) {
583 return true;
586 auto* element = Element::FromNode(aNode);
587 return element && element->State().HasState(ElementState::READWRITE);
590 bool Selection::IsEditorSelection() const {
591 return IsEditorNode(GetFocusNode());
594 Nullable<int16_t> Selection::GetCaretBidiLevel(
595 mozilla::ErrorResult& aRv) const {
596 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
598 if (!mFrameSelection) {
599 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
600 return Nullable<int16_t>();
602 mozilla::intl::BidiEmbeddingLevel caretBidiLevel =
603 static_cast<mozilla::intl::BidiEmbeddingLevel>(
604 mFrameSelection->GetCaretBidiLevel());
605 return (caretBidiLevel & BIDI_LEVEL_UNDEFINED)
606 ? Nullable<int16_t>()
607 : Nullable<int16_t>(caretBidiLevel);
610 void Selection::SetCaretBidiLevel(const Nullable<int16_t>& aCaretBidiLevel,
611 mozilla::ErrorResult& aRv) {
612 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
614 if (!mFrameSelection) {
615 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
616 return;
618 if (aCaretBidiLevel.IsNull()) {
619 mFrameSelection->UndefineCaretBidiLevel();
620 } else {
621 mFrameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(
622 mozilla::intl::BidiEmbeddingLevel(aCaretBidiLevel.Value()));
627 * Test whether the supplied range points to a single table element.
628 * Result is one of the TableSelectionMode constants. "None" means
629 * a table element isn't selected.
631 // TODO: Figure out TableSelectionMode::Column and TableSelectionMode::AllCells
632 static nsresult GetTableSelectionMode(const nsRange& aRange,
633 TableSelectionMode* aTableSelectionType) {
634 if (!aTableSelectionType) {
635 return NS_ERROR_NULL_POINTER;
638 *aTableSelectionType = TableSelectionMode::None;
640 nsINode* startNode = aRange.GetStartContainer();
641 if (!startNode) {
642 return NS_ERROR_FAILURE;
645 nsINode* endNode = aRange.GetEndContainer();
646 if (!endNode) {
647 return NS_ERROR_FAILURE;
650 // Not a single selected node
651 if (startNode != endNode) {
652 return NS_OK;
655 nsIContent* child = aRange.GetChildAtStartOffset();
657 // Not a single selected node
658 if (!child || child->GetNextSibling() != aRange.GetChildAtEndOffset()) {
659 return NS_OK;
662 if (!startNode->IsHTMLElement()) {
663 // Implies a check for being an element; if we ever make this work
664 // for non-HTML, need to keep checking for elements.
665 return NS_OK;
668 if (startNode->IsHTMLElement(nsGkAtoms::tr)) {
669 *aTableSelectionType = TableSelectionMode::Cell;
670 } else // check to see if we are selecting a table or row (column and all
671 // cells not done yet)
673 if (child->IsHTMLElement(nsGkAtoms::table)) {
674 *aTableSelectionType = TableSelectionMode::Table;
675 } else if (child->IsHTMLElement(nsGkAtoms::tr)) {
676 *aTableSelectionType = TableSelectionMode::Row;
680 return NS_OK;
683 nsresult Selection::MaybeAddTableCellRange(nsRange& aRange,
684 Maybe<size_t>* aOutIndex) {
685 if (!aOutIndex) {
686 return NS_ERROR_NULL_POINTER;
689 MOZ_ASSERT(aOutIndex->isNothing());
691 if (!mFrameSelection) {
692 return NS_OK;
695 // Get if we are adding a cell selection and the row, col of cell if we are
696 TableSelectionMode tableMode;
697 nsresult result = GetTableSelectionMode(aRange, &tableMode);
698 if (NS_FAILED(result)) return result;
700 // If not adding a cell range, we are done here
701 if (tableMode != TableSelectionMode::Cell) {
702 mFrameSelection->mTableSelection.mMode = tableMode;
703 // Don't fail if range isn't a selected cell, aDidAddRange tells caller if
704 // we didn't proceed
705 return NS_OK;
708 // Set frame selection mode only if not already set to a table mode
709 // so we don't lose the select row and column flags (not detected by
710 // getTableCellLocation)
711 if (mFrameSelection->mTableSelection.mMode == TableSelectionMode::None) {
712 mFrameSelection->mTableSelection.mMode = tableMode;
715 return AddRangesForSelectableNodes(&aRange, aOutIndex,
716 DispatchSelectstartEvent::Maybe);
719 Selection::Selection(SelectionType aSelectionType,
720 nsFrameSelection* aFrameSelection)
721 : mFrameSelection(aFrameSelection),
722 mCachedOffsetForFrame(nullptr),
723 mDirection(eDirNext),
724 mSelectionType(aSelectionType),
725 mCustomColors(nullptr),
726 mSelectionChangeBlockerCount(0),
727 mUserInitiated(false),
728 mCalledByJS(false),
729 mNotifyAutoCopy(false) {}
731 Selection::~Selection() { Disconnect(); }
733 void Selection::Disconnect() {
734 RemoveAnchorFocusRange();
736 mStyledRanges.UnregisterSelection();
738 if (mAutoScroller) {
739 mAutoScroller->Stop(AutoScroller::FurtherScrollingAllowed::kNo);
740 mAutoScroller = nullptr;
743 mScrollEvent.Revoke();
745 if (mCachedOffsetForFrame) {
746 delete mCachedOffsetForFrame;
747 mCachedOffsetForFrame = nullptr;
751 Document* Selection::GetParentObject() const {
752 PresShell* presShell = GetPresShell();
753 return presShell ? presShell->GetDocument() : nullptr;
756 DocGroup* Selection::GetDocGroup() const {
757 PresShell* presShell = GetPresShell();
758 if (!presShell) {
759 return nullptr;
761 Document* doc = presShell->GetDocument();
762 return doc ? doc->GetDocGroup() : nullptr;
765 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Selection)
767 MOZ_CAN_RUN_SCRIPT_BOUNDARY
768 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Selection)
769 // Unlink the selection listeners *before* we do RemoveAllRangesInternal since
770 // we don't want to notify the listeners during JS GC (they could be
771 // in JS!).
772 tmp->mNotifyAutoCopy = false;
773 if (tmp->mAccessibleCaretEventHub) {
774 tmp->StopNotifyingAccessibleCaretEventHub();
776 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionChangeEventDispatcher)
777 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionListeners)
778 MOZ_KnownLive(tmp)->RemoveAllRangesInternal(IgnoreErrors());
779 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameSelection)
780 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightData.mHighlight)
781 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
782 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
783 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
784 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
785 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Selection)
787 uint32_t i, count = tmp->mStyledRanges.Length();
788 for (i = 0; i < count; ++i) {
789 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyledRanges.mRanges[i].mRange)
792 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorFocusRange)
793 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameSelection)
794 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightData.mHighlight)
795 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionChangeEventDispatcher)
796 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionListeners)
797 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
799 // QueryInterface implementation for Selection
800 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Selection)
801 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
802 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
803 NS_INTERFACE_MAP_ENTRY(nsISupports)
804 NS_INTERFACE_MAP_END
806 NS_IMPL_CYCLE_COLLECTING_ADDREF(Selection)
807 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Selection, Disconnect())
809 const RangeBoundary& Selection::AnchorRef() const {
810 if (!mAnchorFocusRange) {
811 static RangeBoundary sEmpty;
812 return sEmpty;
815 if (GetDirection() == eDirNext) {
816 return mAnchorFocusRange->StartRef();
819 return mAnchorFocusRange->EndRef();
822 const RangeBoundary& Selection::FocusRef() const {
823 if (!mAnchorFocusRange) {
824 static RangeBoundary sEmpty;
825 return sEmpty;
828 if (GetDirection() == eDirNext) {
829 return mAnchorFocusRange->EndRef();
832 return mAnchorFocusRange->StartRef();
835 void Selection::SetAnchorFocusRange(size_t aIndex) {
836 if (aIndex >= mStyledRanges.Length()) {
837 return;
839 // Highlight selections may contain static ranges.
840 MOZ_ASSERT(mSelectionType != SelectionType::eHighlight);
841 AbstractRange* anchorFocusRange = mStyledRanges.mRanges[aIndex].mRange;
842 mAnchorFocusRange = anchorFocusRange->AsDynamicRange();
845 static int32_t CompareToRangeStart(const nsINode& aCompareNode,
846 uint32_t aCompareOffset,
847 const AbstractRange& aRange) {
848 MOZ_ASSERT(aRange.GetStartContainer());
849 nsINode* start = aRange.GetStartContainer();
850 // If the nodes that we're comparing are not in the same document, assume that
851 // aCompareNode will fall at the end of the ranges.
852 if (aCompareNode.GetComposedDoc() != start->GetComposedDoc() ||
853 !start->GetComposedDoc()) {
854 NS_WARNING(
855 "`CompareToRangeStart` couldn't compare nodes, pretending some order.");
856 return 1;
859 // The points are in the same subtree, hence there has to be an order.
860 return *nsContentUtils::ComparePoints(&aCompareNode, aCompareOffset, start,
861 aRange.StartOffset());
864 static int32_t CompareToRangeEnd(const nsINode& aCompareNode,
865 uint32_t aCompareOffset,
866 const AbstractRange& aRange) {
867 MOZ_ASSERT(aRange.IsPositioned());
868 nsINode* end = aRange.GetEndContainer();
869 // If the nodes that we're comparing are not in the same document or in the
870 // same subtree, assume that aCompareNode will fall at the end of the ranges.
871 if (aCompareNode.GetComposedDoc() != end->GetComposedDoc() ||
872 !end->GetComposedDoc()) {
873 NS_WARNING(
874 "`CompareToRangeEnd` couldn't compare nodes, pretending some order.");
875 return 1;
878 // The points are in the same subtree, hence there has to be an order.
879 return *nsContentUtils::ComparePoints(&aCompareNode, aCompareOffset, end,
880 aRange.EndOffset());
883 // static
884 size_t Selection::StyledRanges::FindInsertionPoint(
885 const nsTArray<StyledRange>* aElementArray, const nsINode& aPointNode,
886 uint32_t aPointOffset,
887 int32_t (*aComparator)(const nsINode&, uint32_t, const AbstractRange&)) {
888 int32_t beginSearch = 0;
889 int32_t endSearch = aElementArray->Length(); // one beyond what to check
891 if (endSearch) {
892 int32_t center = endSearch - 1; // Check last index, then binary search
893 do {
894 const AbstractRange* range = (*aElementArray)[center].mRange;
896 int32_t cmp{aComparator(aPointNode, aPointOffset, *range)};
898 if (cmp < 0) { // point < cur
899 endSearch = center;
900 } else if (cmp > 0) { // point > cur
901 beginSearch = center + 1;
902 } else { // found match, done
903 beginSearch = center;
904 break;
906 center = (endSearch - beginSearch) / 2 + beginSearch;
907 } while (endSearch - beginSearch > 0);
910 return AssertedCast<size_t>(beginSearch);
913 // Selection::SubtractRange
915 // A helper function that subtracts aSubtract from aRange, and adds
916 // 1 or 2 StyledRange objects representing the remaining non-overlapping
917 // difference to aOutput. It is assumed that the caller has checked that
918 // aRange and aSubtract do indeed overlap
920 // static
921 nsresult Selection::StyledRanges::SubtractRange(
922 StyledRange& aRange, nsRange& aSubtract, nsTArray<StyledRange>* aOutput) {
923 AbstractRange* range = aRange.mRange;
924 if (NS_WARN_IF(!range->IsPositioned())) {
925 return NS_ERROR_UNEXPECTED;
928 if (range->GetStartContainer()->SubtreeRoot() !=
929 aSubtract.GetStartContainer()->SubtreeRoot()) {
930 // These are ranges for different shadow trees, we can't subtract them in
931 // any sensible way.
932 aOutput->InsertElementAt(0, aRange);
933 return NS_OK;
936 // First we want to compare to the range start
937 int32_t cmp{CompareToRangeStart(*range->GetStartContainer(),
938 range->StartOffset(), aSubtract)};
940 // Also, make a comparison to the range end
941 int32_t cmp2{CompareToRangeEnd(*range->GetEndContainer(), range->EndOffset(),
942 aSubtract)};
944 // If the existing range left overlaps the new range (aSubtract) then
945 // cmp < 0, and cmp2 < 0
946 // If it right overlaps the new range then cmp > 0 and cmp2 > 0
947 // If it fully contains the new range, then cmp < 0 and cmp2 > 0
949 if (cmp2 > 0) {
950 // We need to add a new StyledRange to the output, running from
951 // the end of aSubtract to the end of range
952 ErrorResult error;
953 RefPtr<nsRange> postOverlap =
954 nsRange::Create(aSubtract.EndRef(), range->EndRef(), error);
955 if (NS_WARN_IF(error.Failed())) {
956 return error.StealNSResult();
958 MOZ_ASSERT(postOverlap);
959 if (!postOverlap->Collapsed()) {
960 // XXX(Bug 1631371) Check if this should use a fallible operation as it
961 // pretended earlier.
962 aOutput->InsertElementAt(0, StyledRange(postOverlap));
963 (*aOutput)[0].mTextRangeStyle = aRange.mTextRangeStyle;
967 if (cmp < 0) {
968 // We need to add a new StyledRange to the output, running from
969 // the start of the range to the start of aSubtract
970 ErrorResult error;
971 RefPtr<nsRange> preOverlap =
972 nsRange::Create(range->StartRef(), aSubtract.StartRef(), error);
973 if (NS_WARN_IF(error.Failed())) {
974 return error.StealNSResult();
976 MOZ_ASSERT(preOverlap);
977 if (!preOverlap->Collapsed()) {
978 // XXX(Bug 1631371) Check if this should use a fallible operation as it
979 // pretended earlier.
980 aOutput->InsertElementAt(0, StyledRange(preOverlap));
981 (*aOutput)[0].mTextRangeStyle = aRange.mTextRangeStyle;
985 return NS_OK;
988 static void UserSelectRangesToAdd(nsRange* aItem,
989 nsTArray<RefPtr<nsRange>>& aRangesToAdd) {
990 // We cannot directly call IsEditorSelection() because we may be in an
991 // inconsistent state during Collapse() (we're cleared already but we haven't
992 // got a new focus node yet).
993 if (IsEditorNode(aItem->GetStartContainer()) &&
994 IsEditorNode(aItem->GetEndContainer())) {
995 // Don't mess with the selection ranges for editing, editor doesn't really
996 // deal well with multi-range selections.
997 aRangesToAdd.AppendElement(aItem);
998 } else {
999 aItem->ExcludeNonSelectableNodes(&aRangesToAdd);
1003 static nsINode* DetermineSelectstartEventTarget(
1004 const bool aSelectionEventsOnTextControlsEnabled, const nsRange& aRange) {
1005 nsINode* target = aRange.GetStartContainer();
1006 if (aSelectionEventsOnTextControlsEnabled) {
1007 // Get the first element which isn't in a native anonymous subtree
1008 while (target && target->IsInNativeAnonymousSubtree()) {
1009 target = target->GetParent();
1011 } else {
1012 if (target->IsInNativeAnonymousSubtree()) {
1013 // This is a selection under a text control, so don't dispatch the
1014 // event.
1015 target = nullptr;
1018 return target;
1022 * @return true, iff the default action should be executed.
1024 static bool MaybeDispatchSelectstartEvent(
1025 const nsRange& aRange, const bool aSelectionEventsOnTextControlsEnabled,
1026 Document* aDocument) {
1027 nsCOMPtr<nsINode> selectstartEventTarget = DetermineSelectstartEventTarget(
1028 aSelectionEventsOnTextControlsEnabled, aRange);
1030 bool executeDefaultAction = true;
1032 if (selectstartEventTarget) {
1033 nsContentUtils::DispatchTrustedEvent(
1034 aDocument, selectstartEventTarget, u"selectstart"_ns, CanBubble::eYes,
1035 Cancelable::eYes, &executeDefaultAction);
1038 return executeDefaultAction;
1041 // static
1042 bool Selection::IsUserSelectionCollapsed(
1043 const nsRange& aRange, nsTArray<RefPtr<nsRange>>& aTempRangesToAdd) {
1044 MOZ_ASSERT(aTempRangesToAdd.IsEmpty());
1046 RefPtr<nsRange> scratchRange = aRange.CloneRange();
1047 UserSelectRangesToAdd(scratchRange, aTempRangesToAdd);
1048 const bool userSelectionCollapsed =
1049 (aTempRangesToAdd.Length() == 0) ||
1050 ((aTempRangesToAdd.Length() == 1) && aTempRangesToAdd[0]->Collapsed());
1052 aTempRangesToAdd.ClearAndRetainStorage();
1054 return userSelectionCollapsed;
1057 nsresult Selection::AddRangesForUserSelectableNodes(
1058 nsRange* aRange, Maybe<size_t>* aOutIndex,
1059 const DispatchSelectstartEvent aDispatchSelectstartEvent) {
1060 MOZ_ASSERT(mUserInitiated);
1061 MOZ_ASSERT(aOutIndex);
1062 MOZ_ASSERT(aOutIndex->isNothing());
1064 if (!aRange) {
1065 return NS_ERROR_NULL_POINTER;
1068 if (!aRange->IsPositioned()) {
1069 return NS_ERROR_UNEXPECTED;
1072 AutoTArray<RefPtr<nsRange>, 4> rangesToAdd;
1073 if (mStyledRanges.Length()) {
1074 aOutIndex->emplace(mStyledRanges.Length() - 1);
1077 Document* doc = GetDocument();
1079 if (aDispatchSelectstartEvent == DispatchSelectstartEvent::Maybe &&
1080 mSelectionType == SelectionType::eNormal && IsCollapsed() &&
1081 !IsBlockingSelectionChangeEvents()) {
1082 // We consider a selection to be starting if we are currently collapsed,
1083 // and the selection is becoming uncollapsed, and this is caused by a
1084 // user initiated event.
1086 // First, we generate the ranges to add with a scratch range, which is a
1087 // clone of the original range passed in. We do this seperately, because
1088 // the selectstart event could have caused the world to change, and
1089 // required ranges to be re-generated
1091 const bool userSelectionCollapsed =
1092 IsUserSelectionCollapsed(*aRange, rangesToAdd);
1093 MOZ_ASSERT(userSelectionCollapsed || nsContentUtils::IsSafeToRunScript());
1094 if (!userSelectionCollapsed && nsContentUtils::IsSafeToRunScript()) {
1095 // The spec currently doesn't say that we should dispatch this event
1096 // on text controls, so for now we only support doing that under a
1097 // pref, disabled by default.
1098 // See https://github.com/w3c/selection-api/issues/53.
1099 const bool executeDefaultAction = MaybeDispatchSelectstartEvent(
1100 *aRange,
1101 StaticPrefs::dom_select_events_textcontrols_selectstart_enabled(),
1102 doc);
1104 if (!executeDefaultAction) {
1105 return NS_OK;
1108 // As we potentially dispatched an event to the DOM, something could have
1109 // changed under our feet. Re-generate the rangesToAdd array, and
1110 // ensure that the range we are about to add is still valid.
1111 if (!aRange->IsPositioned()) {
1112 return NS_ERROR_UNEXPECTED;
1117 // Generate the ranges to add
1118 UserSelectRangesToAdd(aRange, rangesToAdd);
1119 size_t newAnchorFocusIndex =
1120 GetDirection() == eDirPrevious ? 0 : rangesToAdd.Length() - 1;
1121 for (size_t i = 0; i < rangesToAdd.Length(); ++i) {
1122 Maybe<size_t> index;
1123 // `MOZ_KnownLive` needed because of broken static analysis
1124 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1622253#c1).
1125 nsresult rv = mStyledRanges.MaybeAddRangeAndTruncateOverlaps(
1126 MOZ_KnownLive(rangesToAdd[i]), &index);
1127 NS_ENSURE_SUCCESS(rv, rv);
1128 if (i == newAnchorFocusIndex) {
1129 *aOutIndex = index;
1130 rangesToAdd[i]->SetIsGenerated(false);
1131 } else {
1132 rangesToAdd[i]->SetIsGenerated(true);
1135 return NS_OK;
1138 nsresult Selection::AddRangesForSelectableNodes(
1139 nsRange* aRange, Maybe<size_t>* aOutIndex,
1140 const DispatchSelectstartEvent aDispatchSelectstartEvent) {
1141 MOZ_ASSERT(aOutIndex);
1142 MOZ_ASSERT(aOutIndex->isNothing());
1144 if (!aRange) {
1145 return NS_ERROR_NULL_POINTER;
1148 if (!aRange->IsPositioned()) {
1149 return NS_ERROR_UNEXPECTED;
1152 MOZ_LOG(
1153 sSelectionLog, LogLevel::Debug,
1154 ("%s: selection=%p, type=%i, range=(%p, StartOffset=%u, EndOffset=%u)",
1155 __FUNCTION__, this, static_cast<int>(GetType()), aRange,
1156 aRange->StartOffset(), aRange->EndOffset()));
1158 if (mUserInitiated) {
1159 return AddRangesForUserSelectableNodes(aRange, aOutIndex,
1160 aDispatchSelectstartEvent);
1163 return mStyledRanges.MaybeAddRangeAndTruncateOverlaps(aRange, aOutIndex);
1166 nsresult Selection::StyledRanges::MaybeAddRangeAndTruncateOverlaps(
1167 nsRange* aRange, Maybe<size_t>* aOutIndex) {
1168 MOZ_ASSERT(aRange);
1169 MOZ_ASSERT(aRange->IsPositioned());
1170 MOZ_ASSERT(aOutIndex);
1171 MOZ_ASSERT(aOutIndex->isNothing());
1173 // a common case is that we have no ranges yet
1174 if (mRanges.Length() == 0) {
1175 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1176 // pretended earlier.
1177 mRanges.AppendElement(StyledRange(aRange));
1178 aRange->RegisterSelection(MOZ_KnownLive(mSelection));
1180 aOutIndex->emplace(0u);
1181 return NS_OK;
1184 Maybe<size_t> maybeStartIndex, maybeEndIndex;
1185 nsresult rv =
1186 GetIndicesForInterval(aRange->GetStartContainer(), aRange->StartOffset(),
1187 aRange->GetEndContainer(), aRange->EndOffset(),
1188 false, maybeStartIndex, maybeEndIndex);
1189 NS_ENSURE_SUCCESS(rv, rv);
1191 size_t startIndex, endIndex;
1192 if (maybeEndIndex.isNothing()) {
1193 // All ranges start after the given range. We can insert our range at
1194 // position 0, knowing there are no overlaps (handled below)
1195 startIndex = endIndex = 0;
1196 } else if (maybeStartIndex.isNothing()) {
1197 // All ranges end before the given range. We can insert our range at
1198 // the end of the array, knowing there are no overlaps (handled below)
1199 startIndex = endIndex = mRanges.Length();
1200 } else {
1201 startIndex = *maybeStartIndex;
1202 endIndex = *maybeEndIndex;
1205 // If the range is already contained in mRanges, silently
1206 // succeed
1207 const bool sameRange = HasEqualRangeBoundariesAt(*aRange, startIndex);
1208 if (sameRange) {
1209 aOutIndex->emplace(startIndex);
1210 return NS_OK;
1213 if (startIndex == endIndex) {
1214 // The new range doesn't overlap any existing ranges
1215 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1216 // pretended earlier.
1217 mRanges.InsertElementAt(startIndex, StyledRange(aRange));
1218 aRange->RegisterSelection(MOZ_KnownLive(mSelection));
1219 aOutIndex->emplace(startIndex);
1220 return NS_OK;
1223 // We now know that at least 1 existing range overlaps with the range that
1224 // we are trying to add. In fact, the only ranges of interest are those at
1225 // the two end points, startIndex and endIndex - 1 (which may point to the
1226 // same range) as these may partially overlap the new range. Any ranges
1227 // between these indices are fully overlapped by the new range, and so can be
1228 // removed.
1229 AutoTArray<StyledRange, 2> overlaps;
1230 overlaps.AppendElement(mRanges[startIndex]);
1231 if (endIndex - 1 != startIndex) {
1232 overlaps.AppendElement(mRanges[endIndex - 1]);
1235 // Remove all the overlapping ranges
1236 for (size_t i = startIndex; i < endIndex; ++i) {
1237 mRanges[i].mRange->UnregisterSelection(mSelection);
1239 mRanges.RemoveElementsAt(startIndex, endIndex - startIndex);
1241 AutoTArray<StyledRange, 3> temp;
1242 for (const size_t i : Reversed(IntegerRange(overlaps.Length()))) {
1243 nsresult rv = SubtractRange(overlaps[i], *aRange, &temp);
1244 NS_ENSURE_SUCCESS(rv, rv);
1247 // Insert the new element into our "leftovers" array
1248 // `aRange` is positioned, so it has to have a start container.
1249 size_t insertionPoint{FindInsertionPoint(&temp, *aRange->GetStartContainer(),
1250 aRange->StartOffset(),
1251 CompareToRangeStart)};
1253 temp.InsertElementAt(insertionPoint, StyledRange(aRange));
1255 // Merge the leftovers back in to mRanges
1256 mRanges.InsertElementsAt(startIndex, temp);
1258 for (uint32_t i = 0; i < temp.Length(); ++i) {
1259 if (temp[i].mRange->IsDynamicRange()) {
1260 MOZ_KnownLive(temp[i].mRange->AsDynamicRange())
1261 ->RegisterSelection(MOZ_KnownLive(mSelection));
1262 // `MOZ_KnownLive` is required because of
1263 // https://bugzilla.mozilla.org/show_bug.cgi?id=1622253.
1267 aOutIndex->emplace(startIndex + insertionPoint);
1268 return NS_OK;
1271 nsresult Selection::StyledRanges::RemoveRangeAndUnregisterSelection(
1272 AbstractRange& aRange) {
1273 // Find the range's index & remove it. We could use FindInsertionPoint to
1274 // get O(log n) time, but that requires many expensive DOM comparisons.
1275 // For even several thousand items, this is probably faster because the
1276 // comparisons are so fast.
1277 int32_t idx = -1;
1278 uint32_t i;
1279 for (i = 0; i < mRanges.Length(); i++) {
1280 if (mRanges[i].mRange == &aRange) {
1281 idx = (int32_t)i;
1282 break;
1285 if (idx < 0) return NS_ERROR_DOM_NOT_FOUND_ERR;
1287 mRanges.RemoveElementAt(idx);
1288 aRange.UnregisterSelection(mSelection);
1290 return NS_OK;
1292 nsresult Selection::RemoveCollapsedRanges() {
1293 if (NeedsToLogSelectionAPI(*this)) {
1294 LogSelectionAPI(this, __FUNCTION__);
1295 LogStackForSelectionAPI();
1298 return mStyledRanges.RemoveCollapsedRanges();
1301 nsresult Selection::StyledRanges::RemoveCollapsedRanges() {
1302 uint32_t i = 0;
1303 while (i < mRanges.Length()) {
1304 if (mRanges[i].mRange->Collapsed()) {
1305 nsresult rv = RemoveRangeAndUnregisterSelection(*mRanges[i].mRange);
1306 NS_ENSURE_SUCCESS(rv, rv);
1307 } else {
1308 ++i;
1311 return NS_OK;
1314 void Selection::Clear(nsPresContext* aPresContext) {
1315 RemoveAnchorFocusRange();
1317 mStyledRanges.UnregisterSelection();
1318 for (uint32_t i = 0; i < mStyledRanges.Length(); ++i) {
1319 SelectFrames(aPresContext, *mStyledRanges.mRanges[i].mRange, false);
1321 mStyledRanges.Clear();
1323 // Reset direction so for more dependable table selection range handling
1324 SetDirection(eDirNext);
1326 // If this was an ATTENTION selection, change it back to normal now
1327 if (mFrameSelection && mFrameSelection->GetDisplaySelection() ==
1328 nsISelectionController::SELECTION_ATTENTION) {
1329 mFrameSelection->SetDisplaySelection(nsISelectionController::SELECTION_ON);
1333 bool Selection::StyledRanges::HasEqualRangeBoundariesAt(
1334 const nsRange& aRange, size_t aRangeIndex) const {
1335 if (aRangeIndex < mRanges.Length()) {
1336 const AbstractRange* range = mRanges[aRangeIndex].mRange;
1337 return range->HasEqualBoundaries(aRange);
1339 return false;
1342 void Selection::GetRangesForInterval(nsINode& aBeginNode, uint32_t aBeginOffset,
1343 nsINode& aEndNode, uint32_t aEndOffset,
1344 bool aAllowAdjacent,
1345 nsTArray<RefPtr<nsRange>>& aReturn,
1346 mozilla::ErrorResult& aRv) {
1347 AutoTArray<nsRange*, 2> results;
1348 nsresult rv =
1349 GetDynamicRangesForIntervalArray(&aBeginNode, aBeginOffset, &aEndNode,
1350 aEndOffset, aAllowAdjacent, &results);
1351 if (NS_FAILED(rv)) {
1352 aRv.Throw(rv);
1353 return;
1356 aReturn.SetLength(results.Length());
1357 for (size_t i = 0; i < results.Length(); ++i) {
1358 aReturn[i] = results[i]; // AddRefs
1362 nsresult Selection::GetAbstractRangesForIntervalArray(
1363 nsINode* aBeginNode, uint32_t aBeginOffset, nsINode* aEndNode,
1364 uint32_t aEndOffset, bool aAllowAdjacent,
1365 nsTArray<AbstractRange*>* aRanges) {
1366 if (NS_WARN_IF(!aBeginNode)) {
1367 return NS_ERROR_UNEXPECTED;
1370 if (NS_WARN_IF(!aEndNode)) {
1371 return NS_ERROR_UNEXPECTED;
1374 aRanges->Clear();
1375 Maybe<size_t> maybeStartIndex, maybeEndIndex;
1376 nsresult res = mStyledRanges.GetIndicesForInterval(
1377 aBeginNode, aBeginOffset, aEndNode, aEndOffset, aAllowAdjacent,
1378 maybeStartIndex, maybeEndIndex);
1379 NS_ENSURE_SUCCESS(res, res);
1381 if (maybeStartIndex.isNothing() || maybeEndIndex.isNothing()) {
1382 return NS_OK;
1385 for (const size_t i : IntegerRange(*maybeStartIndex, *maybeEndIndex)) {
1386 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1387 // pretended earlier.
1388 aRanges->AppendElement(mStyledRanges.mRanges[i].mRange);
1391 return NS_OK;
1394 nsresult Selection::GetDynamicRangesForIntervalArray(
1395 nsINode* aBeginNode, uint32_t aBeginOffset, nsINode* aEndNode,
1396 uint32_t aEndOffset, bool aAllowAdjacent, nsTArray<nsRange*>* aRanges) {
1397 MOZ_ASSERT(mSelectionType != SelectionType::eHighlight);
1398 AutoTArray<AbstractRange*, 2> abstractRanges;
1399 nsresult rv = GetAbstractRangesForIntervalArray(
1400 aBeginNode, aBeginOffset, aEndNode, aEndOffset, aAllowAdjacent,
1401 &abstractRanges);
1402 NS_ENSURE_SUCCESS(rv, rv);
1403 aRanges->Clear();
1404 aRanges->SetCapacity(abstractRanges.Length());
1405 for (auto* abstractRange : abstractRanges) {
1406 aRanges->AppendElement(abstractRange->AsDynamicRange());
1408 return NS_OK;
1411 nsresult Selection::StyledRanges::GetIndicesForInterval(
1412 const nsINode* aBeginNode, uint32_t aBeginOffset, const nsINode* aEndNode,
1413 uint32_t aEndOffset, bool aAllowAdjacent, Maybe<size_t>& aStartIndex,
1414 Maybe<size_t>& aEndIndex) const {
1415 MOZ_ASSERT(aStartIndex.isNothing());
1416 MOZ_ASSERT(aEndIndex.isNothing());
1418 if (NS_WARN_IF(!aBeginNode)) {
1419 return NS_ERROR_INVALID_POINTER;
1422 if (NS_WARN_IF(!aEndNode)) {
1423 return NS_ERROR_INVALID_POINTER;
1426 if (mRanges.Length() == 0) {
1427 return NS_OK;
1430 const bool intervalIsCollapsed =
1431 aBeginNode == aEndNode && aBeginOffset == aEndOffset;
1433 // Ranges that end before the given interval and begin after the given
1434 // interval can be discarded
1435 size_t endsBeforeIndex{FindInsertionPoint(&mRanges, *aEndNode, aEndOffset,
1436 &CompareToRangeStart)};
1438 if (endsBeforeIndex == 0) {
1439 const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange;
1441 // If the interval is strictly before the range at index 0, we can optimize
1442 // by returning now - all ranges start after the given interval
1443 if (!endRange->StartRef().Equals(aEndNode, aEndOffset)) {
1444 return NS_OK;
1447 // We now know that the start point of mRanges[0].mRange
1448 // equals the end of the interval. Thus, when aAllowadjacent is true, the
1449 // caller is always interested in this range. However, when excluding
1450 // adjacencies, we must remember to include the range when both it and the
1451 // given interval are collapsed to the same point
1452 if (!aAllowAdjacent && !(endRange->Collapsed() && intervalIsCollapsed))
1453 return NS_OK;
1455 aEndIndex.emplace(endsBeforeIndex);
1457 size_t beginsAfterIndex{FindInsertionPoint(&mRanges, *aBeginNode,
1458 aBeginOffset, &CompareToRangeEnd)};
1460 if (beginsAfterIndex == mRanges.Length()) {
1461 return NS_OK; // optimization: all ranges are strictly before us
1464 if (aAllowAdjacent) {
1465 // At this point, one of the following holds:
1466 // endsBeforeIndex == mRanges.Length(),
1467 // endsBeforeIndex points to a range whose start point does not equal the
1468 // given interval's start point
1469 // endsBeforeIndex points to a range whose start point equals the given
1470 // interval's start point
1471 // In the final case, there can be two such ranges, a collapsed range, and
1472 // an adjacent range (they will appear in mRanges in that
1473 // order). For this final case, we need to increment endsBeforeIndex, until
1474 // one of the first two possibilities hold
1475 while (endsBeforeIndex < mRanges.Length()) {
1476 const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange;
1477 if (!endRange->StartRef().Equals(aEndNode, aEndOffset)) {
1478 break;
1480 endsBeforeIndex++;
1483 // Likewise, one of the following holds:
1484 // beginsAfterIndex == 0,
1485 // beginsAfterIndex points to a range whose end point does not equal
1486 // the given interval's end point
1487 // beginsOnOrAfter points to a range whose end point equals the given
1488 // interval's end point
1489 // In the final case, there can be two such ranges, an adjacent range, and
1490 // a collapsed range (they will appear in mRanges in that
1491 // order). For this final case, we only need to take action if both those
1492 // ranges exist, and we are pointing to the collapsed range - we need to
1493 // point to the adjacent range
1494 const AbstractRange* beginRange = mRanges[beginsAfterIndex].mRange;
1495 if (beginsAfterIndex > 0 && beginRange->Collapsed() &&
1496 beginRange->EndRef().Equals(aBeginNode, aBeginOffset)) {
1497 beginRange = mRanges[beginsAfterIndex - 1].mRange;
1498 if (beginRange->EndRef().Equals(aBeginNode, aBeginOffset)) {
1499 beginsAfterIndex--;
1502 } else {
1503 // See above for the possibilities at this point. The only case where we
1504 // need to take action is when the range at beginsAfterIndex ends on
1505 // the given interval's start point, but that range isn't collapsed (a
1506 // collapsed range should be included in the returned results).
1507 const AbstractRange* beginRange = mRanges[beginsAfterIndex].mRange;
1508 if (beginRange->EndRef().Equals(aBeginNode, aBeginOffset) &&
1509 !beginRange->Collapsed()) {
1510 beginsAfterIndex++;
1513 // Again, see above for the meaning of endsBeforeIndex at this point.
1514 // In particular, endsBeforeIndex may point to a collaped range which
1515 // represents the point at the end of the interval - this range should be
1516 // included
1517 if (endsBeforeIndex < mRanges.Length()) {
1518 const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange;
1519 if (endRange->StartRef().Equals(aEndNode, aEndOffset) &&
1520 endRange->Collapsed()) {
1521 endsBeforeIndex++;
1526 NS_ASSERTION(beginsAfterIndex <= endsBeforeIndex, "Is mRanges not ordered?");
1527 NS_ENSURE_STATE(beginsAfterIndex <= endsBeforeIndex);
1529 aStartIndex.emplace(beginsAfterIndex);
1530 aEndIndex = Some(endsBeforeIndex);
1531 return NS_OK;
1534 nsIFrame* Selection::GetPrimaryFrameForAnchorNode() const {
1535 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
1537 int32_t frameOffset = 0;
1538 nsCOMPtr<nsIContent> content = do_QueryInterface(GetAnchorNode());
1539 if (content && mFrameSelection) {
1540 return nsFrameSelection::GetFrameForNodeOffset(
1541 content, AnchorOffset(), mFrameSelection->GetHint(), &frameOffset);
1543 return nullptr;
1546 nsIFrame* Selection::GetPrimaryFrameForFocusNode(bool aVisual,
1547 int32_t* aOffsetUsed) const {
1548 nsINode* focusNode = GetFocusNode();
1549 if (!focusNode || !focusNode->IsContent() || !mFrameSelection) {
1550 return nullptr;
1553 nsCOMPtr<nsIContent> content = focusNode->AsContent();
1554 int32_t frameOffset = 0;
1555 if (!aOffsetUsed) {
1556 aOffsetUsed = &frameOffset;
1559 nsIFrame* frame = GetPrimaryOrCaretFrameForNodeOffset(content, FocusOffset(),
1560 aOffsetUsed, aVisual);
1561 if (frame) {
1562 return frame;
1565 // If content is whitespace only, we promote focus node to parent because
1566 // whitespace only node might have no frame.
1568 if (!content->TextIsOnlyWhitespace()) {
1569 return nullptr;
1572 nsCOMPtr<nsIContent> parent = content->GetParent();
1573 if (NS_WARN_IF(!parent)) {
1574 return nullptr;
1576 const Maybe<uint32_t> offset = parent->ComputeIndexOf(content);
1577 if (MOZ_UNLIKELY(NS_WARN_IF(offset.isNothing()))) {
1578 return nullptr;
1580 return GetPrimaryOrCaretFrameForNodeOffset(parent, *offset, aOffsetUsed,
1581 aVisual);
1584 nsIFrame* Selection::GetPrimaryOrCaretFrameForNodeOffset(nsIContent* aContent,
1585 uint32_t aOffset,
1586 int32_t* aOffsetUsed,
1587 bool aVisual) const {
1588 MOZ_ASSERT(aOffsetUsed);
1590 if (!mFrameSelection) {
1591 return nullptr;
1594 CaretAssociationHint hint = mFrameSelection->GetHint();
1596 if (aVisual) {
1597 mozilla::intl::BidiEmbeddingLevel caretBidiLevel =
1598 mFrameSelection->GetCaretBidiLevel();
1600 return nsCaret::GetCaretFrameForNodeOffset(
1601 mFrameSelection, aContent, aOffset, hint, caretBidiLevel,
1602 /* aReturnUnadjustedFrame = */ nullptr, aOffsetUsed);
1605 return nsFrameSelection::GetFrameForNodeOffset(aContent, aOffset, hint,
1606 aOffsetUsed);
1609 void Selection::SelectFramesOf(nsIContent* aContent, bool aSelected) const {
1610 nsIFrame* frame = aContent->GetPrimaryFrame();
1611 if (!frame) {
1612 return;
1614 // The frame could be an SVG text frame, in which case we don't treat it
1615 // as a text frame.
1616 if (frame->IsTextFrame()) {
1617 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
1618 textFrame->SelectionStateChanged(0, textFrame->TextFragment()->GetLength(),
1619 aSelected, mSelectionType);
1620 } else {
1621 frame->SelectionStateChanged();
1625 nsresult Selection::SelectFramesOfInclusiveDescendantsOfContent(
1626 PostContentIterator& aPostOrderIter, nsIContent* aContent,
1627 bool aSelected) const {
1628 // If aContent doesn't have children, we should avoid to use the content
1629 // iterator for performance reason.
1630 if (!aContent->HasChildren()) {
1631 SelectFramesOf(aContent, aSelected);
1632 return NS_OK;
1635 if (NS_WARN_IF(NS_FAILED(aPostOrderIter.Init(aContent)))) {
1636 return NS_ERROR_FAILURE;
1639 for (; !aPostOrderIter.IsDone(); aPostOrderIter.Next()) {
1640 nsINode* node = aPostOrderIter.GetCurrentNode();
1641 MOZ_ASSERT(node);
1642 nsIContent* innercontent = node->IsContent() ? node->AsContent() : nullptr;
1643 SelectFramesOf(innercontent, aSelected);
1646 return NS_OK;
1649 void Selection::SelectFramesInAllRanges(nsPresContext* aPresContext) {
1650 // this method is currently only called in a user-initiated context.
1651 // therefore it is safe to assume that we are not in a Highlight selection
1652 // and we only have to deal with nsRanges (no StaticRanges).
1653 MOZ_ASSERT(mSelectionType != SelectionType::eHighlight);
1654 for (size_t i = 0; i < mStyledRanges.Length(); ++i) {
1655 nsRange* range = mStyledRanges.mRanges[i].mRange->AsDynamicRange();
1656 MOZ_ASSERT(range->IsInAnySelection());
1657 SelectFrames(aPresContext, *range, range->IsInAnySelection());
1662 * The idea of this helper method is to select or deselect "top to bottom",
1663 * traversing through the frames
1665 nsresult Selection::SelectFrames(nsPresContext* aPresContext,
1666 AbstractRange& aRange, bool aSelect) const {
1667 if (!mFrameSelection || !aPresContext || !aPresContext->GetPresShell()) {
1668 // nothing to do
1669 return NS_OK;
1672 MOZ_DIAGNOSTIC_ASSERT(aRange.IsPositioned());
1674 const Document* const document = GetDocument();
1675 if (MOZ_UNLIKELY(!document ||
1676 aRange.GetComposedDocOfContainers() != document)) {
1677 return NS_OK; // Do nothing if the range is now in different document.
1680 if (aRange.IsStaticRange() && !aRange.AsStaticRange()->IsValid()) {
1681 // TODO jjaschke: Actions necessary to unselect invalid static ranges?
1682 return NS_OK;
1685 if (mFrameSelection->IsInTableSelectionMode()) {
1686 const nsIContent* const commonAncestorContent =
1687 nsIContent::FromNodeOrNull(aRange.GetClosestCommonInclusiveAncestor());
1688 nsIFrame* const frame = commonAncestorContent
1689 ? commonAncestorContent->GetPrimaryFrame()
1690 : aPresContext->PresShell()->GetRootFrame();
1691 if (frame) {
1692 if (frame->IsTextFrame()) {
1693 MOZ_ASSERT(commonAncestorContent == aRange.GetStartContainer());
1694 MOZ_ASSERT(commonAncestorContent == aRange.GetEndContainer());
1695 static_cast<nsTextFrame*>(frame)->SelectionStateChanged(
1696 aRange.StartOffset(), aRange.EndOffset(), aSelect, mSelectionType);
1697 } else {
1698 frame->SelectionStateChanged();
1702 return NS_OK;
1705 // Loop through the content iterator for each content node; for each text
1706 // node, call SetSelected on it:
1707 nsIContent* const startContent =
1708 nsIContent::FromNodeOrNull(aRange.GetStartContainer());
1709 if (MOZ_UNLIKELY(!startContent)) {
1710 // Don't warn, bug 1055722
1711 // XXX The range can start from a document node and such range can be
1712 // added to Selection with JS. Therefore, even in such cases,
1713 // shouldn't we handle selection in the range?
1714 return NS_ERROR_UNEXPECTED;
1716 MOZ_DIAGNOSTIC_ASSERT(startContent->IsInComposedDoc());
1718 // We must call first one explicitly
1719 nsINode* const endNode = aRange.GetEndContainer();
1720 if (NS_WARN_IF(!endNode)) {
1721 // We null-checked start node above, therefore, end node should also be
1722 // non-null here.
1723 return NS_ERROR_UNEXPECTED;
1725 const bool isFirstContentTextNode = startContent->IsText();
1726 if (isFirstContentTextNode) {
1727 if (nsIFrame* const frame = startContent->GetPrimaryFrame()) {
1728 // The frame could be an SVG text frame, in which case we don't treat it
1729 // as a text frame.
1730 if (frame->IsTextFrame()) {
1731 const uint32_t startOffset = aRange.StartOffset();
1732 const uint32_t endOffset = endNode == startContent
1733 ? aRange.EndOffset()
1734 : startContent->Length();
1735 static_cast<nsTextFrame*>(frame)->SelectionStateChanged(
1736 startOffset, endOffset, aSelect, mSelectionType);
1737 } else {
1738 frame->SelectionStateChanged();
1743 // If the range is in a node and the node is a leaf node, we don't need to
1744 // walk the subtree.
1745 if (aRange.Collapsed() ||
1746 (startContent == endNode && !startContent->HasChildren())) {
1747 if (!isFirstContentTextNode) {
1748 SelectFramesOf(startContent, aSelect);
1750 return NS_OK;
1753 ContentSubtreeIterator subtreeIter;
1754 subtreeIter.Init(&aRange);
1755 if (isFirstContentTextNode && !subtreeIter.IsDone() &&
1756 subtreeIter.GetCurrentNode() == startContent) {
1757 subtreeIter.Next(); // first content has already been handled.
1759 PostContentIterator postOrderIter;
1760 for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
1761 MOZ_DIAGNOSTIC_ASSERT(subtreeIter.GetCurrentNode());
1762 if (nsIContent* const content =
1763 nsIContent::FromNodeOrNull(subtreeIter.GetCurrentNode())) {
1764 SelectFramesOfInclusiveDescendantsOfContent(postOrderIter, content,
1765 aSelect);
1769 // We must now do the last one if it is not the same as the first
1770 if (endNode == startContent || !endNode->IsText()) {
1771 return NS_OK;
1774 if (nsIFrame* const frame = endNode->AsText()->GetPrimaryFrame()) {
1775 // The frame could be an SVG text frame, in which case we'll ignore it.
1776 if (frame->IsTextFrame()) {
1777 static_cast<nsTextFrame*>(frame)->SelectionStateChanged(
1778 0, aRange.EndOffset(), aSelect, mSelectionType);
1781 return NS_OK;
1784 // Selection::LookUpSelection
1786 // This function is called when a node wants to know where the selection is
1787 // over itself.
1789 // Usually, this is called when we already know there is a selection over
1790 // the node in question, and we only need to find the boundaries of it on
1791 // that node. This is when slowCheck is false--a strict test is not needed.
1792 // Other times, the caller has no idea, and wants us to test everything,
1793 // so we are supposed to determine whether there is a selection over the
1794 // node at all.
1796 // A previous version of this code used this flag to do less work when
1797 // inclusion was already known (slowCheck=false). However, our tree
1798 // structure allows us to quickly determine ranges overlapping the node,
1799 // so we just ignore the slowCheck flag and do the full test every time.
1801 // PERFORMANCE: a common case is that we are doing a fast check with exactly
1802 // one range in the selection. In this case, this function is slower than
1803 // brute force because of the overhead of checking the tree. We can optimize
1804 // this case to make it faster by doing the same thing the previous version
1805 // of this function did in the case of 1 range. This would also mean that
1806 // the aSlowCheck flag would have meaning again.
1808 UniquePtr<SelectionDetails> Selection::LookUpSelection(
1809 nsIContent* aContent, uint32_t aContentOffset, uint32_t aContentLength,
1810 UniquePtr<SelectionDetails> aDetailsHead, SelectionType aSelectionType,
1811 bool aSlowCheck) {
1812 if (!aContent) {
1813 return aDetailsHead;
1816 // it is common to have no ranges, to optimize that
1817 if (mStyledRanges.Length() == 0) {
1818 return aDetailsHead;
1821 nsTArray<AbstractRange*> overlappingRanges;
1822 nsresult rv = GetAbstractRangesForIntervalArray(
1823 aContent, aContentOffset, aContent, aContentOffset + aContentLength,
1824 false, &overlappingRanges);
1825 if (NS_FAILED(rv)) {
1826 return aDetailsHead;
1829 if (overlappingRanges.Length() == 0) {
1830 return aDetailsHead;
1833 UniquePtr<SelectionDetails> detailsHead = std::move(aDetailsHead);
1835 for (size_t i = 0; i < overlappingRanges.Length(); i++) {
1836 AbstractRange* range = overlappingRanges[i];
1837 if (range->IsStaticRange() && !range->AsStaticRange()->IsValid()) {
1838 continue;
1840 nsINode* startNode = range->GetStartContainer();
1841 nsINode* endNode = range->GetEndContainer();
1842 uint32_t startOffset = range->StartOffset();
1843 uint32_t endOffset = range->EndOffset();
1845 Maybe<uint32_t> start, end;
1846 if (startNode == aContent && endNode == aContent) {
1847 if (startOffset < (aContentOffset + aContentLength) &&
1848 endOffset > aContentOffset) {
1849 // this range is totally inside the requested content range
1850 start.emplace(
1851 startOffset >= aContentOffset ? startOffset - aContentOffset : 0u);
1852 end.emplace(std::min(aContentLength, endOffset - aContentOffset));
1854 // otherwise, range is inside the requested node, but does not intersect
1855 // the requested content range, so ignore it
1856 } else if (startNode == aContent) {
1857 if (startOffset < (aContentOffset + aContentLength)) {
1858 // the beginning of the range is inside the requested node, but the
1859 // end is outside, select everything from there to the end
1860 start.emplace(
1861 startOffset >= aContentOffset ? startOffset - aContentOffset : 0u);
1862 end.emplace(aContentLength);
1864 } else if (endNode == aContent) {
1865 if (endOffset > aContentOffset) {
1866 // the end of the range is inside the requested node, but the beginning
1867 // is outside, select everything from the beginning to there
1868 start.emplace(0u);
1869 end.emplace(std::min(aContentLength, endOffset - aContentOffset));
1871 } else {
1872 // this range does not begin or end in the requested node, but since
1873 // GetRangesForInterval returned this range, we know it overlaps.
1874 // Therefore, this node is enclosed in the range, and we select all
1875 // of it.
1876 start.emplace(0u);
1877 end.emplace(aContentLength);
1879 if (start.isNothing()) {
1880 continue; // the ranges do not overlap the input range
1883 auto newHead = MakeUnique<SelectionDetails>();
1885 newHead->mNext = std::move(detailsHead);
1886 newHead->mStart = AssertedCast<int32_t>(*start);
1887 newHead->mEnd = AssertedCast<int32_t>(*end);
1888 newHead->mSelectionType = aSelectionType;
1889 newHead->mHighlightData = mHighlightData;
1890 StyledRange* rd = mStyledRanges.FindRangeData(range);
1891 if (rd) {
1892 newHead->mTextRangeStyle = rd->mTextRangeStyle;
1894 detailsHead = std::move(newHead);
1896 return detailsHead;
1899 NS_IMETHODIMP
1900 Selection::Repaint(nsPresContext* aPresContext) {
1901 int32_t arrCount = (int32_t)mStyledRanges.Length();
1903 if (arrCount < 1) return NS_OK;
1905 int32_t i;
1907 for (i = 0; i < arrCount; i++) {
1908 MOZ_ASSERT(mStyledRanges.mRanges[i].mRange);
1909 nsresult rv =
1910 SelectFrames(aPresContext, *mStyledRanges.mRanges[i].mRange, true);
1912 if (NS_FAILED(rv)) {
1913 return rv;
1917 return NS_OK;
1920 void Selection::SetCanCacheFrameOffset(bool aCanCacheFrameOffset) {
1921 if (!mCachedOffsetForFrame) {
1922 mCachedOffsetForFrame = new CachedOffsetForFrame;
1925 mCachedOffsetForFrame->mCanCacheFrameOffset = aCanCacheFrameOffset;
1927 // clean up cached frame when turn off cache
1928 // fix bug 207936
1929 if (!aCanCacheFrameOffset) {
1930 mCachedOffsetForFrame->mLastCaretFrame = nullptr;
1934 nsresult Selection::GetCachedFrameOffset(nsIFrame* aFrame, int32_t inOffset,
1935 nsPoint& aPoint) {
1936 if (!mCachedOffsetForFrame) {
1937 mCachedOffsetForFrame = new CachedOffsetForFrame;
1940 nsresult rv = NS_OK;
1941 if (mCachedOffsetForFrame->mCanCacheFrameOffset &&
1942 mCachedOffsetForFrame->mLastCaretFrame &&
1943 (aFrame == mCachedOffsetForFrame->mLastCaretFrame) &&
1944 (inOffset == mCachedOffsetForFrame->mLastContentOffset)) {
1945 // get cached frame offset
1946 aPoint = mCachedOffsetForFrame->mCachedFrameOffset;
1947 } else {
1948 // Recalculate frame offset and cache it. Don't cache a frame offset if
1949 // GetPointFromOffset fails, though.
1950 rv = aFrame->GetPointFromOffset(inOffset, &aPoint);
1951 if (NS_SUCCEEDED(rv) && mCachedOffsetForFrame->mCanCacheFrameOffset) {
1952 mCachedOffsetForFrame->mCachedFrameOffset = aPoint;
1953 mCachedOffsetForFrame->mLastCaretFrame = aFrame;
1954 mCachedOffsetForFrame->mLastContentOffset = inOffset;
1958 return rv;
1961 nsIContent* Selection::GetAncestorLimiter() const {
1962 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
1964 if (mFrameSelection) {
1965 return mFrameSelection->GetAncestorLimiter();
1967 return nullptr;
1970 void Selection::SetAncestorLimiter(nsIContent* aLimiter) {
1971 if (NeedsToLogSelectionAPI(*this)) {
1972 LogSelectionAPI(this, __FUNCTION__, "aLimiter", aLimiter);
1973 LogStackForSelectionAPI();
1976 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
1978 if (mFrameSelection) {
1979 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
1980 frameSelection->SetAncestorLimiter(aLimiter);
1984 void Selection::StyledRanges::UnregisterSelection() {
1985 uint32_t count = mRanges.Length();
1986 for (uint32_t i = 0; i < count; ++i) {
1987 mRanges[i].mRange->UnregisterSelection(mSelection);
1991 void Selection::StyledRanges::Clear() { mRanges.Clear(); }
1993 StyledRange* Selection::StyledRanges::FindRangeData(AbstractRange* aRange) {
1994 NS_ENSURE_TRUE(aRange, nullptr);
1995 for (uint32_t i = 0; i < mRanges.Length(); i++) {
1996 if (mRanges[i].mRange == aRange) {
1997 return &mRanges[i];
2000 return nullptr;
2003 Selection::StyledRanges::Elements::size_type Selection::StyledRanges::Length()
2004 const {
2005 return mRanges.Length();
2008 nsresult Selection::SetTextRangeStyle(nsRange* aRange,
2009 const TextRangeStyle& aTextRangeStyle) {
2010 NS_ENSURE_ARG_POINTER(aRange);
2011 StyledRange* rd = mStyledRanges.FindRangeData(aRange);
2012 if (rd) {
2013 rd->mTextRangeStyle = aTextRangeStyle;
2015 return NS_OK;
2018 nsresult Selection::StartAutoScrollTimer(nsIFrame* aFrame,
2019 const nsPoint& aPoint,
2020 uint32_t aDelayInMs) {
2021 MOZ_ASSERT(aFrame, "Need a frame");
2022 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
2024 if (!mFrameSelection) {
2025 return NS_OK; // nothing to do
2028 if (!mAutoScroller) {
2029 mAutoScroller = new AutoScroller(mFrameSelection);
2032 mAutoScroller->SetDelay(aDelayInMs);
2034 RefPtr<AutoScroller> autoScroller{mAutoScroller};
2035 return autoScroller->DoAutoScroll(aFrame, aPoint);
2038 nsresult Selection::StopAutoScrollTimer() {
2039 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
2041 if (mAutoScroller) {
2042 mAutoScroller->Stop(AutoScroller::FurtherScrollingAllowed::kYes);
2045 return NS_OK;
2048 nsresult AutoScroller::DoAutoScroll(nsIFrame* aFrame, nsPoint aPoint) {
2049 MOZ_ASSERT(aFrame, "Need a frame");
2051 Stop(FurtherScrollingAllowed::kYes);
2053 nsPresContext* presContext = aFrame->PresContext();
2054 RefPtr<PresShell> presShell = presContext->PresShell();
2055 nsRootPresContext* rootPC = presContext->GetRootPresContext();
2056 if (!rootPC) {
2057 return NS_OK;
2059 nsIFrame* rootmostFrame = rootPC->PresShell()->GetRootFrame();
2060 AutoWeakFrame weakRootFrame(rootmostFrame);
2061 AutoWeakFrame weakFrame(aFrame);
2062 // Get the point relative to the root most frame because the scroll we are
2063 // about to do will change the coordinates of aFrame.
2064 nsPoint globalPoint = aPoint + aFrame->GetOffsetToCrossDoc(rootmostFrame);
2066 bool done = false;
2067 bool didScroll;
2068 while (true) {
2069 didScroll = presShell->ScrollFrameIntoView(
2070 aFrame, Some(nsRect(aPoint, nsSize())), ScrollAxis(), ScrollAxis(),
2071 ScrollFlags::None);
2072 if (!weakFrame || !weakRootFrame) {
2073 return NS_OK;
2075 if (!didScroll && !done) {
2076 // If aPoint is at the very edge of the root, then try to scroll anyway,
2077 // once.
2078 nsRect rootRect = rootmostFrame->GetRect();
2079 nscoord onePx = AppUnitsPerCSSPixel();
2080 nscoord scrollAmount = 10 * onePx;
2081 if (std::abs(rootRect.x - globalPoint.x) <= onePx) {
2082 aPoint.x -= scrollAmount;
2083 } else if (std::abs(rootRect.XMost() - globalPoint.x) <= onePx) {
2084 aPoint.x += scrollAmount;
2085 } else if (std::abs(rootRect.y - globalPoint.y) <= onePx) {
2086 aPoint.y -= scrollAmount;
2087 } else if (std::abs(rootRect.YMost() - globalPoint.y) <= onePx) {
2088 aPoint.y += scrollAmount;
2089 } else {
2090 break;
2092 done = true;
2093 continue;
2095 break;
2098 // Start the AutoScroll timer if necessary.
2099 // `ScrollFrameRectIntoView` above may have run script and this may have
2100 // forbidden to continue scrolling.
2101 if (didScroll && mFurtherScrollingAllowed == FurtherScrollingAllowed::kYes) {
2102 nsPoint presContextPoint =
2103 globalPoint -
2104 presShell->GetRootFrame()->GetOffsetToCrossDoc(rootmostFrame);
2105 ScheduleNextDoAutoScroll(presContext, presContextPoint);
2108 return NS_OK;
2111 void Selection::RemoveAllRanges(ErrorResult& aRv) {
2112 if (NeedsToLogSelectionAPI(*this)) {
2113 LogSelectionAPI(this, __FUNCTION__);
2114 LogStackForSelectionAPI();
2117 RemoveAllRangesInternal(aRv);
2120 void Selection::RemoveAllRangesInternal(ErrorResult& aRv) {
2121 if (!mFrameSelection) {
2122 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
2123 return;
2126 RefPtr<nsPresContext> presContext = GetPresContext();
2127 Clear(presContext);
2129 // Turn off signal for table selection
2130 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
2131 frameSelection->ClearTableCellSelection();
2133 RefPtr<Selection> kungFuDeathGrip{this};
2134 // Be aware, this instance may be destroyed after this call.
2135 NotifySelectionListeners();
2138 void Selection::AddRangeJS(nsRange& aRange, ErrorResult& aRv) {
2139 if (NeedsToLogSelectionAPI(*this)) {
2140 LogSelectionAPI(this, __FUNCTION__, "aRange", aRange);
2141 LogStackForSelectionAPI();
2144 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
2145 mCalledByJS = true;
2146 RefPtr<Document> document(GetDocument());
2147 AddRangeAndSelectFramesAndNotifyListenersInternal(aRange, document, aRv);
2150 void Selection::AddRangeAndSelectFramesAndNotifyListeners(nsRange& aRange,
2151 ErrorResult& aRv) {
2152 if (NeedsToLogSelectionAPI(*this)) {
2153 LogSelectionAPI(this, __FUNCTION__, "aRange", aRange);
2154 LogStackForSelectionAPI();
2157 RefPtr<Document> document(GetDocument());
2158 return AddRangeAndSelectFramesAndNotifyListenersInternal(aRange, document,
2159 aRv);
2162 void Selection::AddRangeAndSelectFramesAndNotifyListenersInternal(
2163 nsRange& aRange, Document* aDocument, ErrorResult& aRv) {
2164 RefPtr<nsRange> range = &aRange;
2165 if (aRange.IsInAnySelection()) {
2166 if (aRange.IsInSelection(*this)) {
2167 // If we already have the range, we don't need to handle this except
2168 // setting the interline position.
2169 if (mSelectionType == SelectionType::eNormal) {
2170 SetInterlinePosition(InterlinePosition::StartOfNextLine);
2172 return;
2174 if (mSelectionType != SelectionType::eNormal &&
2175 mSelectionType != SelectionType::eHighlight) {
2176 range = aRange.CloneRange();
2180 nsINode* rangeRoot = range->GetRoot();
2181 if (aDocument != rangeRoot &&
2182 (!rangeRoot || aDocument != rangeRoot->GetComposedDoc())) {
2183 // http://w3c.github.io/selection-api/#dom-selection-addrange
2184 // "... if the root of the range's boundary points are the document
2185 // associated with context object. Otherwise, this method must do nothing."
2186 return;
2189 // MaybeAddTableCellRange might flush frame and `NotifySelectionListeners`
2190 // below might destruct `this`.
2191 RefPtr<Selection> kungFuDeathGrip(this);
2193 // This inserts a table cell range in proper document order
2194 // and returns NS_OK if range doesn't contain just one table cell
2195 Maybe<size_t> maybeRangeIndex;
2196 nsresult result = MaybeAddTableCellRange(*range, &maybeRangeIndex);
2197 if (NS_FAILED(result)) {
2198 aRv.Throw(result);
2199 return;
2202 if (maybeRangeIndex.isNothing()) {
2203 result = AddRangesForSelectableNodes(range, &maybeRangeIndex,
2204 DispatchSelectstartEvent::Maybe);
2205 if (NS_FAILED(result)) {
2206 aRv.Throw(result);
2207 return;
2209 if (maybeRangeIndex.isNothing()) {
2210 return;
2214 MOZ_ASSERT(*maybeRangeIndex < mStyledRanges.Length());
2216 SetAnchorFocusRange(*maybeRangeIndex);
2218 // Make sure the caret appears on the next line, if at a newline
2219 if (mSelectionType == SelectionType::eNormal) {
2220 SetInterlinePosition(InterlinePosition::StartOfNextLine);
2223 if (!mFrameSelection) {
2224 return; // nothing to do
2227 RefPtr<nsPresContext> presContext = GetPresContext();
2228 SelectFrames(presContext, *range, true);
2230 // Be aware, this instance may be destroyed after this call.
2231 NotifySelectionListeners();
2234 void Selection::AddHighlightRangeAndSelectFramesAndNotifyListeners(
2235 AbstractRange& aRange) {
2236 MOZ_ASSERT(mSelectionType == SelectionType::eHighlight);
2238 mStyledRanges.mRanges.AppendElement(StyledRange{&aRange});
2239 aRange.RegisterSelection(*this);
2241 if (!mFrameSelection) {
2242 return; // nothing to do
2245 RefPtr<nsPresContext> presContext = GetPresContext();
2246 SelectFrames(presContext, aRange, true);
2248 // Be aware, this instance may be destroyed after this call.
2249 NotifySelectionListeners();
2252 // Selection::RemoveRangeAndUnselectFramesAndNotifyListeners
2254 // Removes the given range from the selection. The tricky part is updating
2255 // the flags on the frames that indicate whether they have a selection or
2256 // not. There could be several selection ranges on the frame, and clearing
2257 // the bit would cause the selection to not be drawn, even when there is
2258 // another range on the frame (bug 346185).
2260 // We therefore find any ranges that intersect the same nodes as the range
2261 // being removed, and cause them to set the selected bits back on their
2262 // selected frames after we've cleared the bit from ours.
2264 void Selection::RemoveRangeAndUnselectFramesAndNotifyListeners(
2265 AbstractRange& aRange, ErrorResult& aRv) {
2266 if (NeedsToLogSelectionAPI(*this)) {
2267 LogSelectionAPI(this, __FUNCTION__, "aRange", aRange);
2268 LogStackForSelectionAPI();
2271 nsresult rv = mStyledRanges.RemoveRangeAndUnregisterSelection(aRange);
2272 if (NS_FAILED(rv)) {
2273 aRv.Throw(rv);
2274 return;
2277 nsINode* beginNode = aRange.GetStartContainer();
2278 nsINode* endNode = aRange.GetEndContainer();
2280 if (!beginNode || !endNode) {
2281 // Detached range; nothing else to do here.
2282 return;
2285 // find out the length of the end node, so we can select all of it
2286 uint32_t beginOffset, endOffset;
2287 if (endNode->IsText()) {
2288 // Get the length of the text. We can't just use the offset because
2289 // another range could be touching this text node but not intersect our
2290 // range.
2291 beginOffset = 0;
2292 endOffset = endNode->AsText()->TextLength();
2293 } else {
2294 // For non-text nodes, the given offsets should be sufficient.
2295 beginOffset = aRange.StartOffset();
2296 endOffset = aRange.EndOffset();
2299 // clear the selected bit from the removed range's frames
2300 RefPtr<nsPresContext> presContext = GetPresContext();
2301 SelectFrames(presContext, aRange, false);
2303 // add back the selected bit for each range touching our nodes
2304 nsTArray<AbstractRange*> affectedRanges;
2305 rv = GetAbstractRangesForIntervalArray(beginNode, beginOffset, endNode,
2306 endOffset, true, &affectedRanges);
2307 if (NS_FAILED(rv)) {
2308 aRv.Throw(rv);
2309 return;
2311 for (uint32_t i = 0; i < affectedRanges.Length(); i++) {
2312 MOZ_ASSERT(affectedRanges[i]);
2313 SelectFrames(presContext, *affectedRanges[i], true);
2316 if (&aRange == mAnchorFocusRange) {
2317 const size_t rangeCount = mStyledRanges.Length();
2318 if (rangeCount) {
2319 SetAnchorFocusRange(rangeCount - 1);
2320 } else {
2321 RemoveAnchorFocusRange();
2324 // When the selection is user-created it makes sense to scroll the range
2325 // into view. The spell-check selection, however, is created and destroyed
2326 // in the background. We don't want to scroll in this case or the view
2327 // might appear to be moving randomly (bug 337871).
2328 if (mSelectionType != SelectionType::eSpellCheck && rangeCount) {
2329 ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION);
2333 if (!mFrameSelection) return; // nothing to do
2335 RefPtr<Selection> kungFuDeathGrip{this};
2336 // Be aware, this instance may be destroyed after this call.
2337 NotifySelectionListeners();
2341 * Collapse sets the whole selection to be one point.
2343 void Selection::CollapseJS(nsINode* aContainer, uint32_t aOffset,
2344 ErrorResult& aRv) {
2345 if (NeedsToLogSelectionAPI(*this)) {
2346 LogSelectionAPI(this, __FUNCTION__, "aContainer", aContainer, "aOffset",
2347 aOffset);
2348 LogStackForSelectionAPI();
2351 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
2352 mCalledByJS = true;
2353 if (!aContainer) {
2354 RemoveAllRangesInternal(aRv);
2355 return;
2357 CollapseInternal(InLimiter::eNo, RawRangeBoundary(aContainer, aOffset), aRv);
2360 void Selection::CollapseInLimiter(const RawRangeBoundary& aPoint,
2361 ErrorResult& aRv) {
2362 if (NeedsToLogSelectionAPI(*this)) {
2363 LogSelectionAPI(this, __FUNCTION__, "aPoint", aPoint);
2364 LogStackForSelectionAPI();
2367 CollapseInternal(InLimiter::eYes, aPoint, aRv);
2370 void Selection::CollapseInternal(InLimiter aInLimiter,
2371 const RawRangeBoundary& aPoint,
2372 ErrorResult& aRv) {
2373 if (!mFrameSelection) {
2374 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
2375 return;
2378 if (!aPoint.IsSet()) {
2379 aRv.Throw(NS_ERROR_INVALID_ARG);
2380 return;
2383 if (aPoint.Container()->NodeType() == nsINode::DOCUMENT_TYPE_NODE) {
2384 aRv.ThrowInvalidNodeTypeError(kNoDocumentTypeNodeError);
2385 return;
2388 // RawRangeBoundary::IsSetAndValid() checks if the point actually refers
2389 // a child of the container when IsSet() is true. If its offset hasn't been
2390 // computed yet, this just checks it with its mRef. So, we can avoid
2391 // computing offset here.
2392 if (!aPoint.IsSetAndValid()) {
2393 aRv.ThrowIndexSizeError("The offset is out of range.");
2394 return;
2397 if (!HasSameRootOrSameComposedDoc(*aPoint.Container())) {
2398 // Return with no error
2399 return;
2402 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
2403 frameSelection->InvalidateDesiredCaretPos();
2404 if (aInLimiter == InLimiter::eYes &&
2405 !frameSelection->IsValidSelectionPoint(aPoint.Container())) {
2406 aRv.Throw(NS_ERROR_FAILURE);
2407 return;
2409 nsresult result;
2411 RefPtr<nsPresContext> presContext = GetPresContext();
2412 if (!presContext ||
2413 presContext->Document() != aPoint.Container()->OwnerDoc()) {
2414 aRv.Throw(NS_ERROR_FAILURE);
2415 return;
2418 // Delete all of the current ranges
2419 Clear(presContext);
2421 // Turn off signal for table selection
2422 frameSelection->ClearTableCellSelection();
2424 // Hack to display the caret on the right line (bug 1237236).
2425 if (frameSelection->GetHint() != CARET_ASSOCIATE_AFTER &&
2426 aPoint.Container()->IsContent()) {
2427 int32_t frameOffset;
2428 nsTextFrame* f = do_QueryFrame(nsCaret::GetFrameAndOffset(
2429 this, aPoint.Container(),
2430 *aPoint.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets),
2431 &frameOffset));
2432 if (f && f->IsAtEndOfLine() && f->HasSignificantTerminalNewline()) {
2433 // RawRangeBounary::Offset() causes computing offset if it's not been
2434 // done yet. However, it's called only when the container is a text
2435 // node. In such case, offset has always been set since it cannot have
2436 // any children. So, this doesn't cause computing offset with expensive
2437 // method, nsINode::ComputeIndexOf().
2438 if ((aPoint.Container()->AsContent() == f->GetContent() &&
2439 f->GetContentEnd() ==
2440 static_cast<int32_t>(*aPoint.Offset(
2441 RawRangeBoundary::OffsetFilter::kValidOffsets))) ||
2442 (aPoint.Container() == f->GetContent()->GetParentNode() &&
2443 f->GetContent() == aPoint.GetPreviousSiblingOfChildAtOffset())) {
2444 frameSelection->SetHint(CARET_ASSOCIATE_AFTER);
2449 RefPtr<nsRange> range = nsRange::Create(aPoint.Container());
2450 result = range->CollapseTo(aPoint);
2451 if (NS_FAILED(result)) {
2452 aRv.Throw(result);
2453 return;
2456 #ifdef DEBUG_SELECTION
2457 nsCOMPtr<nsIContent> content = do_QueryInterface(aPoint.Container());
2458 nsCOMPtr<Document> doc = do_QueryInterface(aPoint.Container());
2459 printf("Sel. Collapse to %p %s %d\n", container.get(),
2460 content ? nsAtomCString(content->NodeInfo()->NameAtom()).get()
2461 : (doc ? "DOCUMENT" : "???"),
2462 aPoint.Offset());
2463 #endif
2465 Maybe<size_t> maybeRangeIndex;
2466 result = AddRangesForSelectableNodes(range, &maybeRangeIndex,
2467 DispatchSelectstartEvent::Maybe);
2468 if (NS_FAILED(result)) {
2469 aRv.Throw(result);
2470 return;
2472 SetAnchorFocusRange(0);
2473 SelectFrames(presContext, *range, true);
2475 RefPtr<Selection> kungFuDeathGrip{this};
2476 // Be aware, this instance may be destroyed after this call.
2477 NotifySelectionListeners();
2481 * Sets the whole selection to be one point
2482 * at the start of the current selection
2484 void Selection::CollapseToStartJS(ErrorResult& aRv) {
2485 if (NeedsToLogSelectionAPI(*this)) {
2486 LogSelectionAPI(this, __FUNCTION__);
2487 LogStackForSelectionAPI();
2490 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
2491 mCalledByJS = true;
2492 CollapseToStart(aRv);
2495 void Selection::CollapseToStart(ErrorResult& aRv) {
2496 if (!mCalledByJS && NeedsToLogSelectionAPI(*this)) {
2497 LogSelectionAPI(this, __FUNCTION__);
2498 LogStackForSelectionAPI();
2501 if (RangeCount() == 0) {
2502 aRv.ThrowInvalidStateError(kNoRangeExistsError);
2503 return;
2506 // Get the first range
2507 const AbstractRange* firstRange = mStyledRanges.mRanges[0].mRange;
2508 if (!firstRange) {
2509 aRv.Throw(NS_ERROR_FAILURE);
2510 return;
2513 if (mFrameSelection) {
2514 mFrameSelection->AddChangeReasons(
2515 nsISelectionListener::COLLAPSETOSTART_REASON);
2517 nsINode* container = firstRange->GetStartContainer();
2518 if (!container) {
2519 aRv.Throw(NS_ERROR_FAILURE);
2520 return;
2522 CollapseInternal(InLimiter::eNo,
2523 RawRangeBoundary(container, firstRange->StartOffset()), aRv);
2527 * Sets the whole selection to be one point
2528 * at the end of the current selection
2530 void Selection::CollapseToEndJS(ErrorResult& aRv) {
2531 if (NeedsToLogSelectionAPI(*this)) {
2532 LogSelectionAPI(this, __FUNCTION__);
2533 LogStackForSelectionAPI();
2536 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
2537 mCalledByJS = true;
2538 CollapseToEnd(aRv);
2541 void Selection::CollapseToEnd(ErrorResult& aRv) {
2542 if (!mCalledByJS && NeedsToLogSelectionAPI(*this)) {
2543 LogSelectionAPI(this, __FUNCTION__);
2544 LogStackForSelectionAPI();
2547 uint32_t cnt = RangeCount();
2548 if (cnt == 0) {
2549 aRv.ThrowInvalidStateError(kNoRangeExistsError);
2550 return;
2553 // Get the last range
2554 const AbstractRange* lastRange = mStyledRanges.mRanges[cnt - 1].mRange;
2555 if (!lastRange) {
2556 aRv.Throw(NS_ERROR_FAILURE);
2557 return;
2560 if (mFrameSelection) {
2561 mFrameSelection->AddChangeReasons(
2562 nsISelectionListener::COLLAPSETOEND_REASON);
2564 nsINode* container = lastRange->GetEndContainer();
2565 if (!container) {
2566 aRv.Throw(NS_ERROR_FAILURE);
2567 return;
2569 CollapseInternal(InLimiter::eNo,
2570 RawRangeBoundary(container, lastRange->EndOffset()), aRv);
2573 void Selection::GetType(nsAString& aOutType) const {
2574 if (!RangeCount()) {
2575 aOutType.AssignLiteral("None");
2576 } else if (IsCollapsed()) {
2577 aOutType.AssignLiteral("Caret");
2578 } else {
2579 aOutType.AssignLiteral("Range");
2583 nsRange* Selection::GetRangeAt(uint32_t aIndex, ErrorResult& aRv) {
2584 nsRange* range = GetRangeAt(aIndex);
2585 if (!range) {
2586 aRv.ThrowIndexSizeError(nsPrintfCString("%u is out of range", aIndex));
2587 return nullptr;
2590 return range;
2593 AbstractRange* Selection::GetAbstractRangeAt(uint32_t aIndex) const {
2594 StyledRange empty(nullptr);
2595 return mStyledRanges.mRanges.SafeElementAt(aIndex, empty).mRange;
2598 nsRange* Selection::GetRangeAt(uint32_t aIndex) const {
2599 // This method per IDL spec returns a dynamic range.
2600 // Therefore, it must be ensured that it is only called
2601 // for a selection which contains dynamic ranges exclusively.
2602 // Highlight Selections are allowed to contain StaticRanges,
2603 // therefore this method must not be called.
2604 MOZ_ASSERT(mSelectionType != SelectionType::eHighlight);
2605 AbstractRange* abstractRange = GetAbstractRangeAt(aIndex);
2606 if (!abstractRange) {
2607 return nullptr;
2609 return abstractRange->AsDynamicRange();
2612 nsresult Selection::SetAnchorFocusToRange(nsRange* aRange) {
2613 NS_ENSURE_STATE(mAnchorFocusRange);
2615 const DispatchSelectstartEvent dispatchSelectstartEvent =
2616 IsCollapsed() ? DispatchSelectstartEvent::Maybe
2617 : DispatchSelectstartEvent::No;
2619 nsresult rv =
2620 mStyledRanges.RemoveRangeAndUnregisterSelection(*mAnchorFocusRange);
2621 if (NS_FAILED(rv)) {
2622 return rv;
2625 Maybe<size_t> maybeOutIndex;
2626 rv = AddRangesForSelectableNodes(aRange, &maybeOutIndex,
2627 dispatchSelectstartEvent);
2628 if (NS_FAILED(rv)) {
2629 return rv;
2631 if (maybeOutIndex.isSome()) {
2632 SetAnchorFocusRange(*maybeOutIndex);
2633 } else {
2634 RemoveAnchorFocusRange();
2637 return NS_OK;
2640 void Selection::ReplaceAnchorFocusRange(nsRange* aRange) {
2641 NS_ENSURE_TRUE_VOID(mAnchorFocusRange);
2642 RefPtr<nsPresContext> presContext = GetPresContext();
2643 if (presContext) {
2644 SelectFrames(presContext, *mAnchorFocusRange, false);
2645 SetAnchorFocusToRange(aRange);
2646 SelectFrames(presContext, *mAnchorFocusRange, true);
2650 void Selection::AdjustAnchorFocusForMultiRange(nsDirection aDirection) {
2651 if (aDirection == mDirection) {
2652 return;
2654 SetDirection(aDirection);
2656 if (RangeCount() <= 1) {
2657 return;
2660 nsRange* firstRange = GetRangeAt(0);
2661 nsRange* lastRange = GetRangeAt(RangeCount() - 1);
2663 if (mDirection == eDirPrevious) {
2664 firstRange->SetIsGenerated(false);
2665 lastRange->SetIsGenerated(true);
2666 SetAnchorFocusRange(0);
2667 } else { // aDir == eDirNext
2668 firstRange->SetIsGenerated(true);
2669 lastRange->SetIsGenerated(false);
2670 SetAnchorFocusRange(RangeCount() - 1);
2675 * Extend extends the selection away from the anchor.
2676 * We don't need to know the direction, because we always change the focus.
2678 void Selection::ExtendJS(nsINode& aContainer, uint32_t aOffset,
2679 ErrorResult& aRv) {
2680 if (NeedsToLogSelectionAPI(*this)) {
2681 LogSelectionAPI(this, __FUNCTION__, "aContainer", &aContainer, "aOffset",
2682 aOffset);
2683 LogStackForSelectionAPI();
2686 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
2687 mCalledByJS = true;
2688 Extend(aContainer, aOffset, aRv);
2691 nsresult Selection::Extend(nsINode* aContainer, uint32_t aOffset) {
2692 if (NeedsToLogSelectionAPI(*this)) {
2693 LogSelectionAPI(this, __FUNCTION__, "aContainer", aContainer, "aOffset",
2694 aOffset);
2695 LogStackForSelectionAPI();
2698 if (!aContainer) {
2699 return NS_ERROR_INVALID_ARG;
2702 ErrorResult result;
2703 Extend(*aContainer, aOffset, result);
2704 return result.StealNSResult();
2707 void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
2708 ErrorResult& aRv) {
2710 Notes which might come in handy for extend:
2712 We can tell the direction of the selection by asking for the anchors
2713 selection if the begin is less than the end then we know the selection is to
2714 the "right", else it is a backwards selection. Notation: a = anchor, 1 = old
2715 cursor, 2 = new cursor.
2717 if (a <= 1 && 1 <=2) a,1,2 or (a1,2)
2718 if (a < 2 && 1 > 2) a,2,1
2719 if (1 < a && a <2) 1,a,2
2720 if (a > 2 && 2 >1) 1,2,a
2721 if (2 < a && a <1) 2,a,1
2722 if (a > 1 && 1 >2) 2,1,a
2723 then execute
2724 a 1 2 select from 1 to 2
2725 a 2 1 deselect from 2 to 1
2726 1 a 2 deselect from 1 to a select from a to 2
2727 1 2 a deselect from 1 to 2
2728 2 1 a = continue selection from 2 to 1
2731 // First, find the range containing the old focus point:
2732 if (!mAnchorFocusRange) {
2733 aRv.ThrowInvalidStateError(kNoRangeExistsError);
2734 return;
2737 if (!mFrameSelection) {
2738 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
2739 return;
2742 if (!HasSameRootOrSameComposedDoc(aContainer)) {
2743 // Return with no error
2744 return;
2747 nsresult res;
2748 if (!mFrameSelection->IsValidSelectionPoint(&aContainer)) {
2749 aRv.Throw(NS_ERROR_FAILURE);
2750 return;
2753 RefPtr<nsPresContext> presContext = GetPresContext();
2754 if (!presContext || presContext->Document() != aContainer.OwnerDoc()) {
2755 aRv.Throw(NS_ERROR_FAILURE);
2756 return;
2759 #ifdef DEBUG_SELECTION
2760 nsDirection oldDirection = GetDirection();
2761 #endif
2762 nsINode* anchorNode = GetAnchorNode();
2763 nsINode* focusNode = GetFocusNode();
2764 const uint32_t anchorOffset = AnchorOffset();
2765 const uint32_t focusOffset = FocusOffset();
2767 RefPtr<nsRange> range = mAnchorFocusRange->CloneRange();
2769 nsINode* startNode = range->GetStartContainer();
2770 nsINode* endNode = range->GetEndContainer();
2771 const uint32_t startOffset = range->StartOffset();
2772 const uint32_t endOffset = range->EndOffset();
2774 bool shouldClearRange = false;
2775 const Maybe<int32_t> anchorOldFocusOrder = nsContentUtils::ComparePoints(
2776 anchorNode, anchorOffset, focusNode, focusOffset);
2777 shouldClearRange |= !anchorOldFocusOrder;
2778 const Maybe<int32_t> oldFocusNewFocusOrder = nsContentUtils::ComparePoints(
2779 focusNode, focusOffset, &aContainer, aOffset);
2780 shouldClearRange |= !oldFocusNewFocusOrder;
2781 const Maybe<int32_t> anchorNewFocusOrder = nsContentUtils::ComparePoints(
2782 anchorNode, anchorOffset, &aContainer, aOffset);
2783 shouldClearRange |= !anchorNewFocusOrder;
2785 // If the points are disconnected, the range will be collapsed below,
2786 // resulting in a range that selects nothing.
2787 if (shouldClearRange) {
2788 // Repaint the current range with the selection removed.
2789 SelectFrames(presContext, *range, false);
2791 res = range->CollapseTo(&aContainer, aOffset);
2792 if (NS_FAILED(res)) {
2793 aRv.Throw(res);
2794 return;
2797 res = SetAnchorFocusToRange(range);
2798 if (NS_FAILED(res)) {
2799 aRv.Throw(res);
2800 return;
2802 } else {
2803 RefPtr<nsRange> difRange = nsRange::Create(&aContainer);
2804 if ((*anchorOldFocusOrder == 0 && *anchorNewFocusOrder < 0) ||
2805 (*anchorOldFocusOrder <= 0 &&
2806 *oldFocusNewFocusOrder < 0)) { // a1,2 a,1,2
2807 // select from 1 to 2 unless they are collapsed
2808 range->SetEnd(aContainer, aOffset, aRv);
2809 if (aRv.Failed()) {
2810 return;
2812 SetDirection(eDirNext);
2813 res = difRange->SetStartAndEnd(
2814 focusNode, focusOffset, range->GetEndContainer(), range->EndOffset());
2815 if (NS_FAILED(res)) {
2816 aRv.Throw(res);
2817 return;
2819 SelectFrames(presContext, *difRange, true);
2820 res = SetAnchorFocusToRange(range);
2821 if (NS_FAILED(res)) {
2822 aRv.Throw(res);
2823 return;
2825 } else if (*anchorOldFocusOrder == 0 &&
2826 *anchorNewFocusOrder > 0) { // 2, a1
2827 // select from 2 to 1a
2828 SetDirection(eDirPrevious);
2829 range->SetStart(aContainer, aOffset, aRv);
2830 if (aRv.Failed()) {
2831 return;
2833 SelectFrames(presContext, *range, true);
2834 res = SetAnchorFocusToRange(range);
2835 if (NS_FAILED(res)) {
2836 aRv.Throw(res);
2837 return;
2839 } else if (*anchorNewFocusOrder <= 0 &&
2840 *oldFocusNewFocusOrder >= 0) { // a,2,1 or a2,1 or a,21 or a21
2841 // deselect from 2 to 1
2842 res = difRange->SetStartAndEnd(&aContainer, aOffset, focusNode,
2843 focusOffset);
2844 if (NS_FAILED(res)) {
2845 aRv.Throw(res);
2846 return;
2849 range->SetEnd(aContainer, aOffset, aRv);
2850 if (aRv.Failed()) {
2851 return;
2853 res = SetAnchorFocusToRange(range);
2854 if (NS_FAILED(res)) {
2855 aRv.Throw(res);
2856 return;
2858 SelectFrames(presContext, *difRange, false); // deselect now
2859 difRange->SetEnd(range->GetEndContainer(), range->EndOffset());
2860 SelectFrames(presContext, *difRange, true); // must reselect last node
2861 // maybe more
2862 } else if (*anchorOldFocusOrder >= 0 &&
2863 *anchorNewFocusOrder <= 0) { // 1,a,2 or 1a,2 or 1,a2 or 1a2
2864 if (GetDirection() == eDirPrevious) {
2865 res = range->SetStart(endNode, endOffset);
2866 if (NS_FAILED(res)) {
2867 aRv.Throw(res);
2868 return;
2871 SetDirection(eDirNext);
2872 range->SetEnd(aContainer, aOffset, aRv);
2873 if (aRv.Failed()) {
2874 return;
2876 if (focusNode != anchorNode ||
2877 focusOffset != anchorOffset) { // if collapsed diff dont do anything
2878 res = difRange->SetStart(focusNode, focusOffset);
2879 nsresult tmp = difRange->SetEnd(anchorNode, anchorOffset);
2880 if (NS_FAILED(tmp)) {
2881 res = tmp;
2883 if (NS_FAILED(res)) {
2884 aRv.Throw(res);
2885 return;
2887 res = SetAnchorFocusToRange(range);
2888 if (NS_FAILED(res)) {
2889 aRv.Throw(res);
2890 return;
2892 // deselect from 1 to a
2893 SelectFrames(presContext, *difRange, false);
2894 } else {
2895 res = SetAnchorFocusToRange(range);
2896 if (NS_FAILED(res)) {
2897 aRv.Throw(res);
2898 return;
2901 // select from a to 2
2902 SelectFrames(presContext, *range, true);
2903 } else if (*oldFocusNewFocusOrder <= 0 &&
2904 *anchorNewFocusOrder >= 0) { // 1,2,a or 12,a or 1,2a or 12a
2905 // deselect from 1 to 2
2906 res = difRange->SetStartAndEnd(focusNode, focusOffset, &aContainer,
2907 aOffset);
2908 if (NS_FAILED(res)) {
2909 aRv.Throw(res);
2910 return;
2912 SetDirection(eDirPrevious);
2913 range->SetStart(aContainer, aOffset, aRv);
2914 if (aRv.Failed()) {
2915 return;
2918 res = SetAnchorFocusToRange(range);
2919 if (NS_FAILED(res)) {
2920 aRv.Throw(res);
2921 return;
2923 SelectFrames(presContext, *difRange, false);
2924 difRange->SetStart(range->GetStartContainer(), range->StartOffset());
2925 SelectFrames(presContext, *difRange, true); // must reselect last node
2926 } else if (*anchorNewFocusOrder >= 0 &&
2927 *anchorOldFocusOrder <= 0) { // 2,a,1 or 2a,1 or 2,a1 or 2a1
2928 if (GetDirection() == eDirNext) {
2929 range->SetEnd(startNode, startOffset);
2931 SetDirection(eDirPrevious);
2932 range->SetStart(aContainer, aOffset, aRv);
2933 if (aRv.Failed()) {
2934 return;
2936 // deselect from a to 1
2937 if (focusNode != anchorNode ||
2938 focusOffset != anchorOffset) { // if collapsed diff dont do anything
2939 res = difRange->SetStartAndEnd(anchorNode, anchorOffset, focusNode,
2940 focusOffset);
2941 nsresult tmp = SetAnchorFocusToRange(range);
2942 if (NS_FAILED(tmp)) {
2943 res = tmp;
2945 if (NS_FAILED(res)) {
2946 aRv.Throw(res);
2947 return;
2949 SelectFrames(presContext, *difRange, false);
2950 } else {
2951 res = SetAnchorFocusToRange(range);
2952 if (NS_FAILED(res)) {
2953 aRv.Throw(res);
2954 return;
2957 // select from 2 to a
2958 SelectFrames(presContext, *range, true);
2959 } else if (*oldFocusNewFocusOrder >= 0 &&
2960 *anchorOldFocusOrder >= 0) { // 2,1,a or 21,a or 2,1a or 21a
2961 // select from 2 to 1
2962 range->SetStart(aContainer, aOffset, aRv);
2963 if (aRv.Failed()) {
2964 return;
2966 SetDirection(eDirPrevious);
2967 res = difRange->SetStartAndEnd(range->GetStartContainer(),
2968 range->StartOffset(), focusNode,
2969 focusOffset);
2970 if (NS_FAILED(res)) {
2971 aRv.Throw(res);
2972 return;
2975 SelectFrames(presContext, *difRange, true);
2976 res = SetAnchorFocusToRange(range);
2977 if (NS_FAILED(res)) {
2978 aRv.Throw(res);
2979 return;
2984 if (mStyledRanges.Length() > 1) {
2985 SelectFramesInAllRanges(presContext);
2988 DEBUG_OUT_RANGE(range);
2989 #ifdef DEBUG_SELECTION
2990 if (GetDirection() != oldDirection) {
2991 printf(" direction changed to %s\n",
2992 GetDirection() == eDirNext ? "eDirNext" : "eDirPrevious");
2994 nsCOMPtr<nsIContent> content = do_QueryInterface(&aContainer);
2995 printf("Sel. Extend to %p %s %d\n", content.get(),
2996 nsAtomCString(content->NodeInfo()->NameAtom()).get(), aOffset);
2997 #endif
2999 RefPtr<Selection> kungFuDeathGrip{this};
3000 // Be aware, this instance may be destroyed after this call.
3001 NotifySelectionListeners();
3004 void Selection::SelectAllChildrenJS(nsINode& aNode, ErrorResult& aRv) {
3005 if (NeedsToLogSelectionAPI(*this)) {
3006 LogSelectionAPI(this, __FUNCTION__, "aNode", &aNode);
3007 LogStackForSelectionAPI();
3010 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
3011 mCalledByJS = true;
3012 SelectAllChildren(aNode, aRv);
3015 void Selection::SelectAllChildren(nsINode& aNode, ErrorResult& aRv) {
3016 if (!mCalledByJS && NeedsToLogSelectionAPI(*this)) {
3017 LogSelectionAPI(this, __FUNCTION__, "aNode", &aNode);
3018 LogStackForSelectionAPI();
3021 if (aNode.NodeType() == nsINode::DOCUMENT_TYPE_NODE) {
3022 aRv.ThrowInvalidNodeTypeError(kNoDocumentTypeNodeError);
3023 return;
3026 if (!HasSameRootOrSameComposedDoc(aNode)) {
3027 // Return with no error
3028 return;
3031 if (mFrameSelection) {
3032 mFrameSelection->AddChangeReasons(nsISelectionListener::SELECTALL_REASON);
3035 // Chrome moves focus when aNode is outside of active editing host.
3036 // So, we don't need to respect the limiter with this method.
3037 SetStartAndEndInternal(InLimiter::eNo, RawRangeBoundary(&aNode, 0u),
3038 RawRangeBoundary(&aNode, aNode.GetChildCount()),
3039 eDirNext, aRv);
3042 bool Selection::ContainsNode(nsINode& aNode, bool aAllowPartial,
3043 ErrorResult& aRv) {
3044 nsresult rv;
3045 if (mStyledRanges.Length() == 0) {
3046 return false;
3049 // XXXbz this duplicates the GetNodeLength code in nsRange.cpp
3050 uint32_t nodeLength;
3051 auto* nodeAsCharData = CharacterData::FromNode(aNode);
3052 if (nodeAsCharData) {
3053 nodeLength = nodeAsCharData->TextLength();
3054 } else {
3055 nodeLength = aNode.GetChildCount();
3058 nsTArray<AbstractRange*> overlappingRanges;
3059 rv = GetAbstractRangesForIntervalArray(&aNode, 0, &aNode, nodeLength, false,
3060 &overlappingRanges);
3061 if (NS_FAILED(rv)) {
3062 aRv.Throw(rv);
3063 return false;
3065 if (overlappingRanges.Length() == 0) return false; // no ranges overlap
3067 // if the caller said partial intersections are OK, we're done
3068 if (aAllowPartial) {
3069 return true;
3072 // text nodes always count as inside
3073 if (nodeAsCharData) {
3074 return true;
3077 // The caller wants to know if the node is entirely within the given range,
3078 // so we have to check all intersecting ranges.
3079 for (uint32_t i = 0; i < overlappingRanges.Length(); i++) {
3080 bool nodeStartsBeforeRange, nodeEndsAfterRange;
3081 if (NS_SUCCEEDED(RangeUtils::CompareNodeToRange(
3082 &aNode, overlappingRanges[i], &nodeStartsBeforeRange,
3083 &nodeEndsAfterRange))) {
3084 if (!nodeStartsBeforeRange && !nodeEndsAfterRange) {
3085 return true;
3089 return false;
3092 class PointInRectChecker : public mozilla::RectCallback {
3093 public:
3094 explicit PointInRectChecker(const nsPoint& aPoint)
3095 : mPoint(aPoint), mMatchFound(false) {}
3097 void AddRect(const nsRect& aRect) override {
3098 mMatchFound = mMatchFound || aRect.Contains(mPoint);
3101 bool MatchFound() { return mMatchFound; }
3103 private:
3104 nsPoint mPoint;
3105 bool mMatchFound;
3108 bool Selection::ContainsPoint(const nsPoint& aPoint) {
3109 if (IsCollapsed()) {
3110 return false;
3112 PointInRectChecker checker(aPoint);
3113 const uint32_t rangeCount = RangeCount();
3114 for (const uint32_t i : IntegerRange(rangeCount)) {
3115 MOZ_ASSERT(RangeCount() == rangeCount);
3116 nsRange* range = GetRangeAt(i);
3117 MOZ_ASSERT(range);
3118 nsRange::CollectClientRectsAndText(
3119 &checker, nullptr, range, range->GetStartContainer(),
3120 range->StartOffset(), range->GetEndContainer(), range->EndOffset(),
3121 true, false);
3122 if (checker.MatchFound()) {
3123 return true;
3126 return false;
3129 void Selection::MaybeNotifyAccessibleCaretEventHub(PresShell* aPresShell) {
3130 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
3132 if (!mAccessibleCaretEventHub && aPresShell) {
3133 mAccessibleCaretEventHub = aPresShell->GetAccessibleCaretEventHub();
3137 void Selection::StopNotifyingAccessibleCaretEventHub() {
3138 MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
3140 mAccessibleCaretEventHub = nullptr;
3143 nsPresContext* Selection::GetPresContext() const {
3144 PresShell* presShell = GetPresShell();
3145 return presShell ? presShell->GetPresContext() : nullptr;
3148 PresShell* Selection::GetPresShell() const {
3149 if (!mFrameSelection) {
3150 return nullptr; // nothing to do
3152 return mFrameSelection->GetPresShell();
3155 Document* Selection::GetDocument() const {
3156 PresShell* presShell = GetPresShell();
3157 return presShell ? presShell->GetDocument() : nullptr;
3160 nsIFrame* Selection::GetSelectionAnchorGeometry(SelectionRegion aRegion,
3161 nsRect* aRect) {
3162 if (!mFrameSelection) return nullptr; // nothing to do
3164 NS_ENSURE_TRUE(aRect, nullptr);
3166 aRect->SetRect(0, 0, 0, 0);
3168 switch (aRegion) {
3169 case nsISelectionController::SELECTION_ANCHOR_REGION:
3170 case nsISelectionController::SELECTION_FOCUS_REGION:
3171 return GetSelectionEndPointGeometry(aRegion, aRect);
3172 case nsISelectionController::SELECTION_WHOLE_SELECTION:
3173 break;
3174 default:
3175 return nullptr;
3178 NS_ASSERTION(aRegion == nsISelectionController::SELECTION_WHOLE_SELECTION,
3179 "should only be SELECTION_WHOLE_SELECTION here");
3181 nsRect anchorRect;
3182 nsIFrame* anchorFrame = GetSelectionEndPointGeometry(
3183 nsISelectionController::SELECTION_ANCHOR_REGION, &anchorRect);
3184 if (!anchorFrame) return nullptr;
3186 nsRect focusRect;
3187 nsIFrame* focusFrame = GetSelectionEndPointGeometry(
3188 nsISelectionController::SELECTION_FOCUS_REGION, &focusRect);
3189 if (!focusFrame) return nullptr;
3191 NS_ASSERTION(anchorFrame->PresContext() == focusFrame->PresContext(),
3192 "points of selection in different documents?");
3193 // make focusRect relative to anchorFrame
3194 focusRect += focusFrame->GetOffsetTo(anchorFrame);
3196 *aRect = anchorRect.UnionEdges(focusRect);
3197 return anchorFrame;
3200 nsIFrame* Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion,
3201 nsRect* aRect) {
3202 if (!mFrameSelection) return nullptr; // nothing to do
3204 NS_ENSURE_TRUE(aRect, nullptr);
3206 aRect->SetRect(0, 0, 0, 0);
3208 nsINode* node = nullptr;
3209 uint32_t nodeOffset = 0;
3210 nsIFrame* frame = nullptr;
3212 switch (aRegion) {
3213 case nsISelectionController::SELECTION_ANCHOR_REGION:
3214 node = GetAnchorNode();
3215 nodeOffset = AnchorOffset();
3216 break;
3217 case nsISelectionController::SELECTION_FOCUS_REGION:
3218 node = GetFocusNode();
3219 nodeOffset = FocusOffset();
3220 break;
3221 default:
3222 return nullptr;
3225 if (!node) return nullptr;
3227 nsCOMPtr<nsIContent> content = do_QueryInterface(node);
3228 NS_ENSURE_TRUE(content.get(), nullptr);
3229 int32_t frameOffset = 0;
3230 frame = nsFrameSelection::GetFrameForNodeOffset(
3231 content, nodeOffset, mFrameSelection->GetHint(), &frameOffset);
3232 if (!frame) return nullptr;
3234 nsFrameSelection::AdjustFrameForLineStart(frame, frameOffset);
3236 // Figure out what node type we have, then get the
3237 // appropriate rect for its nodeOffset.
3238 bool isText = node->IsText();
3240 nsPoint pt(0, 0);
3241 if (isText) {
3242 nsIFrame* childFrame = nullptr;
3243 frameOffset = 0;
3244 nsresult rv = frame->GetChildFrameContainingOffset(
3245 nodeOffset, mFrameSelection->GetHint(), &frameOffset, &childFrame);
3246 if (NS_FAILED(rv)) return nullptr;
3247 if (!childFrame) return nullptr;
3249 frame = childFrame;
3251 // Get the coordinates of the offset into the text frame.
3252 rv = GetCachedFrameOffset(frame, nodeOffset, pt);
3253 if (NS_FAILED(rv)) return nullptr;
3256 // Return the rect relative to the frame, with zero inline-size. The
3257 // inline-position is either 'pt' (if we're a text node) or otherwise just
3258 // the physical "end" edge of the frame (which we express as the frame's own
3259 // width or height, since the returned position is relative to the frame).
3260 // The block position and size are set so as to fill the frame in that axis.
3261 // (i.e. block-position of 0, and block-size matching the frame's own block
3262 // size).
3263 const WritingMode wm = frame->GetWritingMode();
3264 // Helper to determine the inline-axis position for the aRect outparam.
3265 auto GetInlinePosition = [&]() {
3266 if (isText) {
3267 return wm.IsVertical() ? pt.y : pt.x;
3269 // Return the frame's physical end edge of its inline axis, relative to the
3270 // frame. That's just its height or width.
3271 // TODO(dholbert): This seems to work, but perhaps we really want the
3272 // inline-end edge (rather than physical end of inline axis)? (i.e. if we
3273 // have direction:rtl, maybe this code would want to return 0 instead of
3274 // height/width?)
3275 return frame->ISize(wm);
3278 // Set the inline position and block-size. Leave inline size and block
3279 // position set to 0, as discussed above.
3280 if (wm.IsVertical()) {
3281 aRect->y = GetInlinePosition();
3282 aRect->SetWidth(frame->BSize(wm));
3283 } else {
3284 aRect->x = GetInlinePosition();
3285 aRect->SetHeight(frame->BSize(wm));
3288 return frame;
3291 NS_IMETHODIMP
3292 Selection::ScrollSelectionIntoViewEvent::Run() {
3293 if (!mSelection) return NS_OK; // event revoked
3295 int32_t flags = Selection::SCROLL_DO_FLUSH | Selection::SCROLL_SYNCHRONOUS;
3297 const RefPtr<Selection> selection{mSelection};
3298 selection->mScrollEvent.Forget();
3299 selection->ScrollIntoView(mRegion, mVerticalScroll, mHorizontalScroll,
3300 mFlags | flags);
3301 return NS_OK;
3304 nsresult Selection::PostScrollSelectionIntoViewEvent(SelectionRegion aRegion,
3305 int32_t aFlags,
3306 ScrollAxis aVertical,
3307 ScrollAxis aHorizontal) {
3308 // If we've already posted an event, revoke it and place a new one at the
3309 // end of the queue to make sure that any new pending reflow events are
3310 // processed before we scroll. This will insure that we scroll to the
3311 // correct place on screen.
3312 mScrollEvent.Revoke();
3313 nsPresContext* presContext = GetPresContext();
3314 NS_ENSURE_STATE(presContext);
3315 nsRefreshDriver* refreshDriver = presContext->RefreshDriver();
3316 NS_ENSURE_STATE(refreshDriver);
3318 mScrollEvent = new ScrollSelectionIntoViewEvent(this, aRegion, aVertical,
3319 aHorizontal, aFlags);
3320 refreshDriver->AddEarlyRunner(mScrollEvent.get());
3321 return NS_OK;
3324 void Selection::ScrollIntoView(int16_t aRegion, bool aIsSynchronous,
3325 int16_t aVPercent, int16_t aHPercent,
3326 ErrorResult& aRv) {
3327 int32_t flags = aIsSynchronous ? Selection::SCROLL_SYNCHRONOUS : 0;
3328 // -1 means nearest in this API.
3329 const auto v =
3330 aVPercent == -1 ? WhereToScroll::Nearest : WhereToScroll(aVPercent);
3331 const auto h =
3332 aHPercent == -1 ? WhereToScroll::Nearest : WhereToScroll(aHPercent);
3333 nsresult rv = ScrollIntoView(aRegion, ScrollAxis(v), ScrollAxis(h), flags);
3334 if (NS_FAILED(rv)) {
3335 aRv.Throw(rv);
3339 nsresult Selection::ScrollIntoView(SelectionRegion aRegion,
3340 ScrollAxis aVertical, ScrollAxis aHorizontal,
3341 int32_t aFlags) {
3342 if (!mFrameSelection) {
3343 return NS_ERROR_NOT_INITIALIZED;
3346 RefPtr<PresShell> presShell = mFrameSelection->GetPresShell();
3347 if (!presShell || !presShell->GetDocument()) {
3348 return NS_OK;
3351 if (mFrameSelection->IsBatching()) {
3352 return NS_OK;
3355 if (!(aFlags & Selection::SCROLL_SYNCHRONOUS))
3356 return PostScrollSelectionIntoViewEvent(aRegion, aFlags, aVertical,
3357 aHorizontal);
3359 // From this point on, the presShell may get destroyed by the calls below, so
3360 // hold on to it using a strong reference to ensure the safety of the
3361 // accesses to frame pointers in the callees.
3362 RefPtr<PresShell> kungFuDeathGrip(presShell);
3364 // Now that text frame character offsets are always valid (though not
3365 // necessarily correct), the worst that will happen if we don't flush here
3366 // is that some callers might scroll to the wrong place. Those should
3367 // either manually flush if they're in a safe position for it or use the
3368 // async version of this method.
3369 if (aFlags & Selection::SCROLL_DO_FLUSH) {
3370 presShell->GetDocument()->FlushPendingNotifications(FlushType::Layout);
3372 // Reget the presshell, since it might have been Destroy'ed.
3373 presShell = mFrameSelection ? mFrameSelection->GetPresShell() : nullptr;
3374 if (!presShell) {
3375 return NS_OK;
3380 // Scroll the selection region into view.
3383 nsRect rect;
3384 nsIFrame* frame = GetSelectionAnchorGeometry(aRegion, &rect);
3385 if (!frame) return NS_ERROR_FAILURE;
3387 // Scroll vertically to get the caret into view, but only if the container
3388 // is perceived to be scrollable in that direction (i.e. there is a visible
3389 // vertical scrollbar or the scroll range is at least one device pixel)
3390 aVertical.mOnlyIfPerceivedScrollableDirection = true;
3392 auto scrollFlags = ScrollFlags::None;
3393 if (aFlags & Selection::SCROLL_FIRST_ANCESTOR_ONLY) {
3394 scrollFlags |= ScrollFlags::ScrollFirstAncestorOnly;
3396 if (aFlags & Selection::SCROLL_OVERFLOW_HIDDEN) {
3397 scrollFlags |= ScrollFlags::ScrollOverflowHidden;
3400 presShell->ScrollFrameIntoView(frame, Some(rect), aVertical, aHorizontal,
3401 scrollFlags);
3402 return NS_OK;
3405 void Selection::AddSelectionListener(nsISelectionListener* aNewListener) {
3406 MOZ_ASSERT(aNewListener);
3407 mSelectionListeners.AppendElement(aNewListener); // AddRefs
3410 void Selection::RemoveSelectionListener(
3411 nsISelectionListener* aListenerToRemove) {
3412 mSelectionListeners.RemoveElement(aListenerToRemove); // Releases
3415 Element* Selection::StyledRanges::GetCommonEditingHost() const {
3416 Element* editingHost = nullptr;
3417 for (const StyledRange& rangeData : mRanges) {
3418 const AbstractRange* range = rangeData.mRange;
3419 MOZ_ASSERT(range);
3420 nsINode* commonAncestorNode = range->GetClosestCommonInclusiveAncestor();
3421 if (!commonAncestorNode || !commonAncestorNode->IsContent()) {
3422 return nullptr;
3424 nsIContent* commonAncestor = commonAncestorNode->AsContent();
3425 Element* foundEditingHost = commonAncestor->GetEditingHost();
3426 // Even when common ancestor is a non-editable element in a contenteditable
3427 // element, we don't need to move focus to the contenteditable element
3428 // because Chromium doesn't set focus to it.
3429 if (!foundEditingHost) {
3430 return nullptr;
3432 if (!editingHost) {
3433 editingHost = foundEditingHost;
3434 continue;
3436 if (editingHost == foundEditingHost) {
3437 continue;
3439 if (foundEditingHost->IsInclusiveDescendantOf(editingHost)) {
3440 continue;
3442 if (editingHost->IsInclusiveDescendantOf(foundEditingHost)) {
3443 editingHost = foundEditingHost;
3444 continue;
3446 // editingHost and foundEditingHost are not a descendant of the other.
3447 // So, there is no common editing host.
3448 return nullptr;
3450 return editingHost;
3453 void Selection::StyledRanges::MaybeFocusCommonEditingHost(
3454 PresShell* aPresShell) const {
3455 if (!aPresShell) {
3456 return;
3459 nsPresContext* presContext = aPresShell->GetPresContext();
3460 if (!presContext) {
3461 return;
3464 Document* document = aPresShell->GetDocument();
3465 if (!document) {
3466 return;
3469 nsPIDOMWindowOuter* window = document->GetWindow();
3470 // If the document is in design mode or doesn't have contenteditable
3471 // element, we don't need to move focus.
3472 if (window && !document->IsInDesignMode() &&
3473 nsContentUtils::GetHTMLEditor(presContext)) {
3474 RefPtr<Element> newEditingHost = GetCommonEditingHost();
3475 RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
3476 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
3477 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
3478 window, nsFocusManager::eOnlyCurrentWindow,
3479 getter_AddRefs(focusedWindow));
3480 nsCOMPtr<Element> focusedElement = do_QueryInterface(focusedContent);
3481 // When all selected ranges are in an editing host, it should take focus.
3482 // But otherwise, we shouldn't move focus since Chromium doesn't move
3483 // focus but only selection range is updated.
3484 if (newEditingHost && newEditingHost != focusedElement) {
3485 MOZ_ASSERT(!newEditingHost->IsInNativeAnonymousSubtree());
3486 // Note that don't steal focus from focused window if the window doesn't
3487 // have focus. Additionally, although when an element gets focus, we
3488 // usually scroll to the element, but in this case, we shouldn't do it
3489 // because Chrome does not do so.
3490 fm->SetFocus(newEditingHost, nsIFocusManager::FLAG_NOSWITCHFRAME |
3491 nsIFocusManager::FLAG_NOSCROLL);
3496 void Selection::NotifySelectionListeners(bool aCalledByJS) {
3497 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
3498 mCalledByJS = aCalledByJS;
3499 NotifySelectionListeners();
3502 void Selection::NotifySelectionListeners() {
3503 if (!mFrameSelection) {
3504 return; // nothing to do
3507 MOZ_LOG(sSelectionLog, LogLevel::Debug,
3508 ("%s: selection=%p", __FUNCTION__, this));
3510 // Our internal code should not move focus with using this class while
3511 // this moves focus nor from selection listeners.
3512 AutoRestore<bool> calledByJSRestorer(mCalledByJS);
3513 mCalledByJS = false;
3515 // When normal selection is changed by Selection API, we need to move focus
3516 // if common ancestor of all ranges are in an editing host. Note that we
3517 // don't need to move focus *to* the other focusable node because other
3518 // browsers don't do it either.
3519 if (mSelectionType == SelectionType::eNormal &&
3520 calledByJSRestorer.SavedValue()) {
3521 RefPtr<PresShell> presShell = GetPresShell();
3522 mStyledRanges.MaybeFocusCommonEditingHost(presShell);
3525 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
3527 // This flag will be set to true if a selection by double click is detected.
3528 // As soon as the selection is modified, it needs to be set to false.
3529 frameSelection->SetIsDoubleClickSelection(false);
3531 if (frameSelection->IsBatching()) {
3532 frameSelection->SetChangesDuringBatchingFlag();
3533 return;
3535 if (mSelectionListeners.IsEmpty() && !mNotifyAutoCopy &&
3536 !mAccessibleCaretEventHub && !mSelectionChangeEventDispatcher) {
3537 // If there are no selection listeners, we're done!
3538 return;
3541 nsCOMPtr<Document> doc;
3542 if (PresShell* presShell = GetPresShell()) {
3543 doc = presShell->GetDocument();
3544 presShell->ScheduleContentRelevancyUpdate(ContentRelevancyReason::Selected);
3547 // We've notified all selection listeners even when some of them are removed
3548 // (and may be destroyed) during notifying one of them. Therefore, we should
3549 // copy all listeners to the local variable first.
3550 const CopyableAutoTArray<nsCOMPtr<nsISelectionListener>, 5>
3551 selectionListeners = mSelectionListeners;
3553 int16_t reason = frameSelection->PopChangeReasons();
3554 if (calledByJSRestorer.SavedValue()) {
3555 reason |= nsISelectionListener::JS_REASON;
3558 int32_t amount = static_cast<int32_t>(frameSelection->GetCaretMoveAmount());
3560 if (mNotifyAutoCopy) {
3561 AutoCopyListener::OnSelectionChange(doc, *this, reason);
3564 if (mAccessibleCaretEventHub) {
3565 RefPtr<AccessibleCaretEventHub> hub(mAccessibleCaretEventHub);
3566 hub->OnSelectionChange(doc, this, reason);
3569 if (mSelectionChangeEventDispatcher) {
3570 RefPtr<SelectionChangeEventDispatcher> dispatcher(
3571 mSelectionChangeEventDispatcher);
3572 dispatcher->OnSelectionChange(doc, this, reason);
3575 for (const auto& listener : selectionListeners) {
3576 // MOZ_KnownLive because 'selectionListeners' is guaranteed to
3577 // keep it alive.
3579 // This can go away once
3580 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
3581 MOZ_KnownLive(listener)->NotifySelectionChanged(doc, this, reason, amount);
3585 void Selection::StartBatchChanges(const char* aDetails) {
3586 if (mFrameSelection) {
3587 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
3588 frameSelection->StartBatchChanges(aDetails);
3592 void Selection::EndBatchChanges(const char* aDetails, int16_t aReasons) {
3593 if (mFrameSelection) {
3594 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
3595 frameSelection->EndBatchChanges(aDetails, aReasons);
3599 void Selection::AddSelectionChangeBlocker() { mSelectionChangeBlockerCount++; }
3601 void Selection::RemoveSelectionChangeBlocker() {
3602 MOZ_ASSERT(mSelectionChangeBlockerCount > 0,
3603 "mSelectionChangeBlockerCount has an invalid value - "
3604 "maybe you have a mismatched RemoveSelectionChangeBlocker?");
3605 mSelectionChangeBlockerCount--;
3608 bool Selection::IsBlockingSelectionChangeEvents() const {
3609 return mSelectionChangeBlockerCount > 0;
3612 void Selection::DeleteFromDocument(ErrorResult& aRv) {
3613 if (NeedsToLogSelectionAPI(*this)) {
3614 LogSelectionAPI(this, __FUNCTION__);
3615 LogStackForSelectionAPI();
3618 if (mSelectionType != SelectionType::eNormal) {
3619 return; // Nothing to do.
3622 // If we're already collapsed, then we do nothing (bug 719503).
3623 if (IsCollapsed()) {
3624 return;
3627 for (uint32_t rangeIdx = 0; rangeIdx < RangeCount(); ++rangeIdx) {
3628 RefPtr<nsRange> range = GetRangeAt(rangeIdx);
3629 range->DeleteContents(aRv);
3630 if (aRv.Failed()) {
3631 return;
3635 // Collapse to the new location.
3636 // If we deleted one character, then we move back one element.
3637 // FIXME We don't know how to do this past frame boundaries yet.
3638 if (AnchorOffset() > 0) {
3639 RefPtr<nsINode> anchor = GetAnchorNode();
3640 CollapseInLimiter(anchor, AnchorOffset());
3642 #ifdef DEBUG
3643 else {
3644 printf("Don't know how to set selection back past frame boundary\n");
3646 #endif
3649 void Selection::Modify(const nsAString& aAlter, const nsAString& aDirection,
3650 const nsAString& aGranularity, ErrorResult& aRv) {
3651 if (NeedsToLogSelectionAPI(*this)) {
3652 LogSelectionAPI(this, __FUNCTION__, "aAlter", aAlter, "aDirection",
3653 aDirection, "aGranularity", aGranularity);
3654 LogStackForSelectionAPI();
3657 if (!mFrameSelection) {
3658 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
3659 return;
3662 if (!GetAnchorFocusRange() || !GetFocusNode()) {
3663 return;
3666 if (!aAlter.LowerCaseEqualsLiteral("move") &&
3667 !aAlter.LowerCaseEqualsLiteral("extend")) {
3668 aRv.ThrowSyntaxError(
3669 R"(The first argument must be one of: "move" or "extend")");
3670 return;
3673 if (!aDirection.LowerCaseEqualsLiteral("forward") &&
3674 !aDirection.LowerCaseEqualsLiteral("backward") &&
3675 !aDirection.LowerCaseEqualsLiteral("left") &&
3676 !aDirection.LowerCaseEqualsLiteral("right")) {
3677 aRv.ThrowSyntaxError(
3678 R"(The direction argument must be one of: "forward", "backward", "left", or "right")");
3679 return;
3682 // Make sure the layout is up to date as we access bidi information below.
3683 if (RefPtr<Document> doc = GetDocument()) {
3684 doc->FlushPendingNotifications(FlushType::Layout);
3687 // Line moves are always visual.
3688 bool visual = aDirection.LowerCaseEqualsLiteral("left") ||
3689 aDirection.LowerCaseEqualsLiteral("right") ||
3690 aGranularity.LowerCaseEqualsLiteral("line");
3692 bool forward = aDirection.LowerCaseEqualsLiteral("forward") ||
3693 aDirection.LowerCaseEqualsLiteral("right");
3695 bool extend = aAlter.LowerCaseEqualsLiteral("extend");
3697 nsSelectionAmount amount;
3698 if (aGranularity.LowerCaseEqualsLiteral("character")) {
3699 amount = eSelectCluster;
3700 } else if (aGranularity.LowerCaseEqualsLiteral("word")) {
3701 amount = eSelectWordNoSpace;
3702 } else if (aGranularity.LowerCaseEqualsLiteral("line")) {
3703 amount = eSelectLine;
3704 } else if (aGranularity.LowerCaseEqualsLiteral("lineboundary")) {
3705 amount = forward ? eSelectEndLine : eSelectBeginLine;
3706 } else if (aGranularity.LowerCaseEqualsLiteral("sentence") ||
3707 aGranularity.LowerCaseEqualsLiteral("sentenceboundary") ||
3708 aGranularity.LowerCaseEqualsLiteral("paragraph") ||
3709 aGranularity.LowerCaseEqualsLiteral("paragraphboundary") ||
3710 aGranularity.LowerCaseEqualsLiteral("documentboundary")) {
3711 aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
3712 return;
3713 } else {
3714 aRv.ThrowSyntaxError(
3715 R"(The granularity argument must be one of: "character", "word", "line", or "lineboundary")");
3716 return;
3719 // If the anchor doesn't equal the focus and we try to move without first
3720 // collapsing the selection, MoveCaret will collapse the selection and quit.
3721 // To avoid this, we need to collapse the selection first.
3722 nsresult rv = NS_OK;
3723 if (!extend) {
3724 RefPtr<nsINode> focusNode = GetFocusNode();
3725 // We should have checked earlier that there was a focus node.
3726 if (!focusNode) {
3727 aRv.Throw(NS_ERROR_UNEXPECTED);
3728 return;
3730 uint32_t focusOffset = FocusOffset();
3731 CollapseInLimiter(focusNode, focusOffset);
3734 // If the paragraph direction of the focused frame is right-to-left,
3735 // we may have to swap the direction of movement.
3736 if (nsIFrame* frame = GetPrimaryFrameForFocusNode(visual)) {
3737 mozilla::intl::BidiDirection paraDir =
3738 nsBidiPresUtils::ParagraphDirection(frame);
3740 if (paraDir == mozilla::intl::BidiDirection::RTL && visual) {
3741 if (amount == eSelectBeginLine) {
3742 amount = eSelectEndLine;
3743 forward = !forward;
3744 } else if (amount == eSelectEndLine) {
3745 amount = eSelectBeginLine;
3746 forward = !forward;
3751 // MoveCaret will return an error if it can't move in the specified
3752 // direction, but we just ignore this error unless it's a line move, in which
3753 // case we call nsISelectionController::CompleteMove to move the cursor to
3754 // the beginning/end of the line.
3755 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
3756 rv = frameSelection->MoveCaret(
3757 forward ? eDirNext : eDirPrevious, extend, amount,
3758 visual ? nsFrameSelection::eVisual : nsFrameSelection::eLogical);
3760 if (aGranularity.LowerCaseEqualsLiteral("line") && NS_FAILED(rv)) {
3761 RefPtr<PresShell> presShell = frameSelection->GetPresShell();
3762 if (!presShell) {
3763 return;
3765 presShell->CompleteMove(forward, extend);
3769 void Selection::SetBaseAndExtentJS(nsINode& aAnchorNode, uint32_t aAnchorOffset,
3770 nsINode& aFocusNode, uint32_t aFocusOffset,
3771 ErrorResult& aRv) {
3772 if (NeedsToLogSelectionAPI(*this)) {
3773 LogSelectionAPI(this, __FUNCTION__, "aAnchorNode", aAnchorNode,
3774 "aAnchorOffset", aAnchorOffset, "aFocusNode", aFocusNode,
3775 "aFocusOffset", aFocusOffset);
3776 LogStackForSelectionAPI();
3779 AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
3780 mCalledByJS = true;
3781 SetBaseAndExtent(aAnchorNode, aAnchorOffset, aFocusNode, aFocusOffset, aRv);
3784 void Selection::SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset,
3785 nsINode& aFocusNode, uint32_t aFocusOffset,
3786 ErrorResult& aRv) {
3787 if (aAnchorOffset > aAnchorNode.Length()) {
3788 aRv.ThrowIndexSizeError(nsPrintfCString(
3789 "The anchor offset value %u is out of range", aAnchorOffset));
3790 return;
3792 if (aFocusOffset > aFocusNode.Length()) {
3793 aRv.ThrowIndexSizeError(nsPrintfCString(
3794 "The focus offset value %u is out of range", aFocusOffset));
3795 return;
3798 SetBaseAndExtent(RawRangeBoundary{&aAnchorNode, aAnchorOffset},
3799 RawRangeBoundary{&aFocusNode, aFocusOffset}, aRv);
3802 void Selection::SetBaseAndExtent(const RawRangeBoundary& aAnchorRef,
3803 const RawRangeBoundary& aFocusRef,
3804 ErrorResult& aRv) {
3805 if (!mCalledByJS && NeedsToLogSelectionAPI(*this)) {
3806 LogSelectionAPI(this, __FUNCTION__, "aAnchorRef", aAnchorRef, "aFocusRef",
3807 aFocusRef);
3808 LogStackForSelectionAPI();
3811 SetBaseAndExtentInternal(InLimiter::eNo, aAnchorRef, aFocusRef, aRv);
3814 void Selection::SetBaseAndExtentInLimiter(const RawRangeBoundary& aAnchorRef,
3815 const RawRangeBoundary& aFocusRef,
3816 ErrorResult& aRv) {
3817 if (NeedsToLogSelectionAPI(*this)) {
3818 LogSelectionAPI(this, __FUNCTION__, "aAnchorRef", aAnchorRef, "aFocusRef",
3819 aFocusRef);
3820 LogStackForSelectionAPI();
3823 SetBaseAndExtentInternal(InLimiter::eYes, aAnchorRef, aFocusRef, aRv);
3826 void Selection::SetBaseAndExtentInternal(InLimiter aInLimiter,
3827 const RawRangeBoundary& aAnchorRef,
3828 const RawRangeBoundary& aFocusRef,
3829 ErrorResult& aRv) {
3830 if (!mFrameSelection) {
3831 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
3832 return;
3835 if (NS_WARN_IF(!aAnchorRef.IsSet()) || NS_WARN_IF(!aFocusRef.IsSet())) {
3836 aRv.Throw(NS_ERROR_INVALID_ARG);
3837 return;
3840 if (!HasSameRootOrSameComposedDoc(*aAnchorRef.Container()) ||
3841 !HasSameRootOrSameComposedDoc(*aFocusRef.Container())) {
3842 // Return with no error
3843 return;
3846 // Prevent "selectionchange" event temporarily because it should be fired
3847 // after we set the direction.
3848 // XXX If they are disconnected, shouldn't we return error before allocating
3849 // new nsRange instance?
3850 SelectionBatcher batch(this, __FUNCTION__);
3851 const Maybe<int32_t> order =
3852 nsContentUtils::ComparePoints(aAnchorRef, aFocusRef);
3853 if (order && (*order <= 0)) {
3854 SetStartAndEndInternal(aInLimiter, aAnchorRef, aFocusRef, eDirNext, aRv);
3855 return;
3858 // If there's no `order`, the range will be collapsed, unless another error is
3859 // detected before.
3860 SetStartAndEndInternal(aInLimiter, aFocusRef, aAnchorRef, eDirPrevious, aRv);
3863 void Selection::SetStartAndEndInLimiter(const RawRangeBoundary& aStartRef,
3864 const RawRangeBoundary& aEndRef,
3865 ErrorResult& aRv) {
3866 if (NeedsToLogSelectionAPI(*this)) {
3867 LogSelectionAPI(this, __FUNCTION__, "aStartRef", aStartRef, "aEndRef",
3868 aEndRef);
3869 LogStackForSelectionAPI();
3872 SetStartAndEndInternal(InLimiter::eYes, aStartRef, aEndRef, eDirNext, aRv);
3875 Result<Ok, nsresult> Selection::SetStartAndEndInLimiter(
3876 nsINode& aStartContainer, uint32_t aStartOffset, nsINode& aEndContainer,
3877 uint32_t aEndOffset, nsDirection aDirection, int16_t aReason) {
3878 MOZ_ASSERT(aDirection == eDirPrevious || aDirection == eDirNext);
3879 if (NeedsToLogSelectionAPI(*this)) {
3880 LogSelectionAPI(this, __FUNCTION__, "aStartContainer", aStartContainer,
3881 "aStartOffset", aStartOffset, "aEndContainer",
3882 aEndContainer, "aEndOffset", aEndOffset, "nsDirection",
3883 aDirection, "aReason", aReason);
3884 LogStackForSelectionAPI();
3887 if (mFrameSelection) {
3888 mFrameSelection->AddChangeReasons(aReason);
3891 ErrorResult error;
3892 SetStartAndEndInternal(
3893 InLimiter::eYes, RawRangeBoundary(&aStartContainer, aStartOffset),
3894 RawRangeBoundary(&aEndContainer, aEndOffset), aDirection, error);
3895 MOZ_TRY(error.StealNSResult());
3896 return Ok();
3899 void Selection::SetStartAndEnd(const RawRangeBoundary& aStartRef,
3900 const RawRangeBoundary& aEndRef,
3901 ErrorResult& aRv) {
3902 if (NeedsToLogSelectionAPI(*this)) {
3903 LogSelectionAPI(this, __FUNCTION__, "aStartRef", aStartRef, "aEndRef",
3904 aEndRef);
3905 LogStackForSelectionAPI();
3908 SetStartAndEndInternal(InLimiter::eNo, aStartRef, aEndRef, eDirNext, aRv);
3911 void Selection::SetStartAndEndInternal(InLimiter aInLimiter,
3912 const RawRangeBoundary& aStartRef,
3913 const RawRangeBoundary& aEndRef,
3914 nsDirection aDirection,
3915 ErrorResult& aRv) {
3916 if (NS_WARN_IF(!aStartRef.IsSet()) || NS_WARN_IF(!aEndRef.IsSet())) {
3917 aRv.Throw(NS_ERROR_INVALID_ARG);
3918 return;
3921 // Don't fire "selectionchange" event until everything done.
3922 SelectionBatcher batch(this, __FUNCTION__);
3924 if (aInLimiter == InLimiter::eYes) {
3925 if (!mFrameSelection ||
3926 !mFrameSelection->IsValidSelectionPoint(aStartRef.Container())) {
3927 aRv.Throw(NS_ERROR_FAILURE);
3928 return;
3930 if (aStartRef.Container() != aEndRef.Container() &&
3931 !mFrameSelection->IsValidSelectionPoint(aEndRef.Container())) {
3932 aRv.Throw(NS_ERROR_FAILURE);
3933 return;
3937 RefPtr<nsRange> newRange = nsRange::Create(aStartRef, aEndRef, aRv);
3938 if (aRv.Failed()) {
3939 return;
3942 RemoveAllRangesInternal(aRv);
3943 if (aRv.Failed()) {
3944 return;
3947 RefPtr<Document> document(GetDocument());
3948 AddRangeAndSelectFramesAndNotifyListenersInternal(*newRange, document, aRv);
3949 if (aRv.Failed()) {
3950 return;
3953 // Adding a range may set 2 or more ranges if there are non-selectable
3954 // contents only when this change is caused by a user operation. Therefore,
3955 // we need to select frames with the result in such case.
3956 if (mUserInitiated) {
3957 RefPtr<nsPresContext> presContext = GetPresContext();
3958 if (mStyledRanges.Length() > 1 && presContext) {
3959 SelectFramesInAllRanges(presContext);
3963 SetDirection(aDirection);
3966 /** SelectionLanguageChange modifies the cursor Bidi level after a change in
3967 * keyboard direction
3968 * @param aLangRTL is true if the new language is right-to-left or false if the
3969 * new language is left-to-right
3971 nsresult Selection::SelectionLanguageChange(bool aLangRTL) {
3972 if (!mFrameSelection) {
3973 return NS_ERROR_NOT_INITIALIZED;
3976 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
3978 // if the direction of the language hasn't changed, nothing to do
3979 mozilla::intl::BidiEmbeddingLevel kbdBidiLevel =
3980 aLangRTL ? mozilla::intl::BidiEmbeddingLevel::RTL()
3981 : mozilla::intl::BidiEmbeddingLevel::LTR();
3982 if (kbdBidiLevel == frameSelection->mKbdBidiLevel) {
3983 return NS_OK;
3986 frameSelection->mKbdBidiLevel = kbdBidiLevel;
3988 nsIFrame* focusFrame = GetPrimaryFrameForFocusNode(false);
3989 if (!focusFrame) {
3990 return NS_ERROR_FAILURE;
3993 auto [frameStart, frameEnd] = focusFrame->GetOffsets();
3994 RefPtr<nsPresContext> context = GetPresContext();
3995 mozilla::intl::BidiEmbeddingLevel levelBefore, levelAfter;
3996 if (!context) {
3997 return NS_ERROR_FAILURE;
4000 mozilla::intl::BidiEmbeddingLevel level = focusFrame->GetEmbeddingLevel();
4001 int32_t focusOffset = static_cast<int32_t>(FocusOffset());
4002 if ((focusOffset != frameStart) && (focusOffset != frameEnd))
4003 // the cursor is not at a frame boundary, so the level of both the
4004 // characters (logically) before and after the cursor is equal to the frame
4005 // level
4006 levelBefore = levelAfter = level;
4007 else {
4008 // the cursor is at a frame boundary, so use GetPrevNextBidiLevels to find
4009 // the level of the characters before and after the cursor
4010 nsCOMPtr<nsIContent> focusContent = do_QueryInterface(GetFocusNode());
4011 nsPrevNextBidiLevels levels =
4012 frameSelection->GetPrevNextBidiLevels(focusContent, focusOffset, false);
4014 levelBefore = levels.mLevelBefore;
4015 levelAfter = levels.mLevelAfter;
4018 if (levelBefore.IsSameDirection(levelAfter)) {
4019 // if cursor is between two characters with the same orientation, changing
4020 // the keyboard language must toggle the cursor level between the level of
4021 // the character with the lowest level (if the new language corresponds to
4022 // the orientation of that character) and this level plus 1 (if the new
4023 // language corresponds to the opposite orientation)
4024 if ((level != levelBefore) && (level != levelAfter)) {
4025 level = std::min(levelBefore, levelAfter);
4027 if (level.IsSameDirection(kbdBidiLevel)) {
4028 frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(level);
4029 } else {
4030 frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(
4031 mozilla::intl::BidiEmbeddingLevel(level + 1));
4033 } else {
4034 // if cursor is between characters with opposite orientations, changing the
4035 // keyboard language must change the cursor level to that of the adjacent
4036 // character with the orientation corresponding to the new language.
4037 if (levelBefore.IsSameDirection(kbdBidiLevel)) {
4038 frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(levelBefore);
4039 } else {
4040 frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(levelAfter);
4044 // The caret might have moved, so invalidate the desired position
4045 // for future usages of up-arrow or down-arrow
4046 frameSelection->InvalidateDesiredCaretPos();
4048 return NS_OK;
4051 void Selection::SetColors(const nsAString& aForegroundColor,
4052 const nsAString& aBackgroundColor,
4053 const nsAString& aAltForegroundColor,
4054 const nsAString& aAltBackgroundColor,
4055 ErrorResult& aRv) {
4056 if (mSelectionType != SelectionType::eFind) {
4057 aRv.Throw(NS_ERROR_FAILURE);
4058 return;
4061 mCustomColors.reset(new SelectionCustomColors);
4063 constexpr auto currentColorStr = u"currentColor"_ns;
4064 constexpr auto transparentStr = u"transparent"_ns;
4066 if (!aForegroundColor.Equals(currentColorStr)) {
4067 nscolor foregroundColor;
4068 nsAttrValue aForegroundColorValue;
4069 aForegroundColorValue.ParseColor(aForegroundColor);
4070 if (!aForegroundColorValue.GetColorValue(foregroundColor)) {
4071 aRv.Throw(NS_ERROR_INVALID_ARG);
4072 return;
4074 mCustomColors->mForegroundColor = Some(foregroundColor);
4075 } else {
4076 mCustomColors->mForegroundColor = Nothing();
4079 if (!aBackgroundColor.Equals(transparentStr)) {
4080 nscolor backgroundColor;
4081 nsAttrValue aBackgroundColorValue;
4082 aBackgroundColorValue.ParseColor(aBackgroundColor);
4083 if (!aBackgroundColorValue.GetColorValue(backgroundColor)) {
4084 aRv.Throw(NS_ERROR_INVALID_ARG);
4085 return;
4087 mCustomColors->mBackgroundColor = Some(backgroundColor);
4088 } else {
4089 mCustomColors->mBackgroundColor = Nothing();
4092 if (!aAltForegroundColor.Equals(currentColorStr)) {
4093 nscolor altForegroundColor;
4094 nsAttrValue aAltForegroundColorValue;
4095 aAltForegroundColorValue.ParseColor(aAltForegroundColor);
4096 if (!aAltForegroundColorValue.GetColorValue(altForegroundColor)) {
4097 aRv.Throw(NS_ERROR_INVALID_ARG);
4098 return;
4100 mCustomColors->mAltForegroundColor = Some(altForegroundColor);
4101 } else {
4102 mCustomColors->mAltForegroundColor = Nothing();
4105 if (!aAltBackgroundColor.Equals(transparentStr)) {
4106 nscolor altBackgroundColor;
4107 nsAttrValue aAltBackgroundColorValue;
4108 aAltBackgroundColorValue.ParseColor(aAltBackgroundColor);
4109 if (!aAltBackgroundColorValue.GetColorValue(altBackgroundColor)) {
4110 aRv.Throw(NS_ERROR_INVALID_ARG);
4111 return;
4113 mCustomColors->mAltBackgroundColor = Some(altBackgroundColor);
4114 } else {
4115 mCustomColors->mAltBackgroundColor = Nothing();
4119 void Selection::ResetColors() { mCustomColors = nullptr; }
4121 void Selection::SetHighlightSelectionData(
4122 HighlightSelectionData aHighlightSelectionData) {
4123 MOZ_ASSERT(mSelectionType == SelectionType::eHighlight);
4124 mHighlightData = std::move(aHighlightSelectionData);
4127 JSObject* Selection::WrapObject(JSContext* aCx,
4128 JS::Handle<JSObject*> aGivenProto) {
4129 return mozilla::dom::Selection_Binding::Wrap(aCx, this, aGivenProto);
4132 // AutoHideSelectionChanges
4133 AutoHideSelectionChanges::AutoHideSelectionChanges(
4134 const nsFrameSelection* aFrame)
4135 : AutoHideSelectionChanges(
4136 aFrame ? aFrame->GetSelection(SelectionType::eNormal) : nullptr) {}
4138 bool Selection::HasSameRootOrSameComposedDoc(const nsINode& aNode) {
4139 nsINode* root = aNode.SubtreeRoot();
4140 Document* doc = GetDocument();
4141 return doc == root || (root && doc == root->GetComposedDoc());