Selection.cpp (164366B)
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/. */ 6 7 /* 8 * Implementation of mozilla::dom::Selection 9 */ 10 11 #include "Selection.h" 12 13 #include <algorithm> 14 15 #include "ErrorList.h" 16 #include "LayoutConstants.h" 17 #include "mozilla/AccessibleCaretEventHub.h" 18 #include "mozilla/Assertions.h" 19 #include "mozilla/AsyncEventDispatcher.h" 20 #include "mozilla/Attributes.h" 21 #include "mozilla/AutoCopyListener.h" 22 #include "mozilla/AutoRestore.h" 23 #include "mozilla/BasePrincipal.h" 24 #include "mozilla/CaretAssociationHint.h" 25 #include "mozilla/ContentIterator.h" 26 #include "mozilla/ErrorResult.h" 27 #include "mozilla/HTMLEditor.h" 28 #include "mozilla/IntegerRange.h" 29 #include "mozilla/Logging.h" 30 #include "mozilla/PresShell.h" 31 #include "mozilla/RangeBoundary.h" 32 #include "mozilla/RangeUtils.h" 33 #include "mozilla/SelectionMovementUtils.h" 34 #include "mozilla/StackWalk.h" 35 #include "mozilla/StaticPrefs_dom.h" 36 #include "mozilla/ToString.h" 37 #include "mozilla/Try.h" 38 #include "mozilla/dom/CharacterDataBuffer.h" 39 #include "mozilla/dom/ChildIterator.h" 40 #include "mozilla/dom/Document.h" 41 #include "mozilla/dom/Element.h" 42 #include "mozilla/dom/SelectionBinding.h" 43 #include "mozilla/dom/ShadowRoot.h" 44 #include "mozilla/dom/StaticRange.h" 45 #include "mozilla/dom/TreeIterator.h" 46 #include "mozilla/intl/Bidi.h" 47 #include "mozilla/intl/BidiEmbeddingLevel.h" 48 #include "nsBidiPresUtils.h" 49 #include "nsCCUncollectableMarker.h" 50 #include "nsCOMPtr.h" 51 #include "nsCaret.h" 52 #include "nsContentUtils.h" 53 #include "nsCopySupport.h" 54 #include "nsDebug.h" 55 #include "nsDeviceContext.h" 56 #include "nsDirection.h" 57 #include "nsError.h" 58 #include "nsFmtString.h" 59 #include "nsFocusManager.h" 60 #include "nsFrameSelection.h" 61 #include "nsGkAtoms.h" 62 #include "nsIContent.h" 63 #include "nsIContentInlines.h" 64 #include "nsIDocumentEncoder.h" 65 #include "nsIFrameInlines.h" 66 #include "nsINamed.h" 67 #include "nsISelectionController.h" //for the enums 68 #include "nsISelectionListener.h" 69 #include "nsITableCellLayout.h" 70 #include "nsITimer.h" 71 #include "nsLayoutUtils.h" 72 #include "nsPIDOMWindow.h" 73 #include "nsPresContext.h" 74 #include "nsRange.h" 75 #include "nsRefreshDriver.h" 76 #include "nsString.h" 77 #include "nsTArray.h" 78 #include "nsTableCellFrame.h" 79 #include "nsTableWrapperFrame.h" 80 #include "nsTextFrame.h" 81 #include "nsThreadUtils.h" 82 83 #ifdef ACCESSIBILITY 84 # include "nsAccessibilityService.h" 85 #endif 86 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"); 97 98 MOZ_ALWAYS_INLINE bool NeedsToLogSelectionAPI(dom::Selection& aSelection) { 99 return aSelection.Type() == SelectionType::eNormal && 100 MOZ_LOG_TEST(sSelectionAPILog, LogLevel::Info); 101 } 102 103 void LogStackForSelectionAPI() { 104 if (!MOZ_LOG_TEST(sSelectionAPILog, LogLevel::Debug)) { 105 return; 106 } 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; 121 } 122 123 static void LogSelectionAPI(const dom::Selection* aSelection, 124 const char* aFuncName) { 125 MOZ_LOG(sSelectionAPILog, LogLevel::Info, 126 ("%p Selection::%s()", aSelection, aFuncName)); 127 } 128 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")); 135 } 136 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())); 143 } 144 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)); 152 } 153 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())); 160 } 161 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())); 172 } 173 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)); 191 } 192 } 193 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)); 215 } 216 } 217 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())); 232 } 233 } 234 } // namespace mozilla 235 236 using namespace mozilla; 237 using namespace mozilla::dom; 238 239 // #define DEBUG_TABLE 1 240 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 247 248 static constexpr nsLiteralCString kNoDocumentTypeNodeError = 249 "DocumentType nodes are not supported"_ns; 250 static constexpr nsLiteralCString kNoRangeExistsError = 251 "No selection range exists"_ns; 252 253 namespace mozilla { 254 255 /****************************************************************************** 256 * Utility methods defined in nsISelectionListener.idl 257 ******************************************************************************/ 258 259 nsCString SelectionChangeReasonsToCString(int16_t aReasons) { 260 nsCString reasons; 261 if (!aReasons) { 262 reasons.AssignLiteral("NO_REASON"); 263 return reasons; 264 } 265 auto EnsureSeparator = [](nsCString& aString) -> void { 266 if (!aString.IsEmpty()) { 267 aString.AppendLiteral(" | "); 268 } 269 }; 270 struct ReasonData { 271 int16_t mReason; 272 const char* mReasonStr; 273 274 ReasonData(int16_t aReason, const char* aReasonStr) 275 : mReason(aReason), mReasonStr(aReasonStr) {} 276 }; 277 for (const ReasonData& reason : 278 {ReasonData(nsISelectionListener::DRAG_REASON, "DRAG_REASON"), 279 ReasonData(nsISelectionListener::MOUSEDOWN_REASON, "MOUSEDOWN_REASON"), 280 ReasonData(nsISelectionListener::MOUSEUP_REASON, "MOUSEUP_REASON"), 281 ReasonData(nsISelectionListener::KEYPRESS_REASON, "KEYPRESS_REASON"), 282 ReasonData(nsISelectionListener::SELECTALL_REASON, "SELECTALL_REASON"), 283 ReasonData(nsISelectionListener::COLLAPSETOSTART_REASON, 284 "COLLAPSETOSTART_REASON"), 285 ReasonData(nsISelectionListener::COLLAPSETOEND_REASON, 286 "COLLAPSETOEND_REASON"), 287 ReasonData(nsISelectionListener::IME_REASON, "IME_REASON"), 288 ReasonData(nsISelectionListener::JS_REASON, "JS_REASON")}) { 289 if (aReasons & reason.mReason) { 290 EnsureSeparator(reasons); 291 reasons.Append(reason.mReasonStr); 292 } 293 } 294 return reasons; 295 } 296 297 } // namespace mozilla 298 299 SelectionNodeCache::SelectionNodeCache(PresShell& aOwningPresShell) 300 : mOwningPresShell(aOwningPresShell) { 301 MOZ_ASSERT(!mOwningPresShell.mSelectionNodeCache); 302 mOwningPresShell.mSelectionNodeCache = this; 303 } 304 305 SelectionNodeCache::~SelectionNodeCache() { 306 mOwningPresShell.mSelectionNodeCache = nullptr; 307 } 308 309 bool SelectionNodeCache::MaybeCollectNodesAndCheckIfFullySelectedInAnyOf( 310 const nsINode* aNode, const nsTArray<Selection*>& aSelections) { 311 for (const auto* sel : aSelections) { 312 if (MaybeCollectNodesAndCheckIfFullySelected(aNode, sel)) { 313 return true; 314 } 315 } 316 return false; 317 } 318 319 const nsTHashSet<const nsINode*>& SelectionNodeCache::MaybeCollect( 320 const Selection* aSelection) { 321 MOZ_ASSERT(aSelection); 322 return mSelectedNodes.LookupOrInsertWith(aSelection, [sel = RefPtr( 323 aSelection)] { 324 nsTHashSet<const nsINode*> fullySelectedNodes; 325 for (size_t rangeIndex = 0; rangeIndex < sel->RangeCount(); ++rangeIndex) { 326 AbstractRange* range = sel->GetAbstractRangeAt(rangeIndex); 327 MOZ_ASSERT(range); 328 if (range->AreNormalRangeAndCrossShadowBoundaryRangeCollapsed()) { 329 continue; 330 } 331 if (range->IsStaticRange() && !range->AsStaticRange()->IsValid()) { 332 continue; 333 } 334 const RangeBoundary& startRef = range->MayCrossShadowBoundaryStartRef(); 335 const RangeBoundary& endRef = range->MayCrossShadowBoundaryEndRef(); 336 337 const nsINode* startContainer = 338 startRef.IsStartOfContainer() ? nullptr : startRef.GetContainer(); 339 const nsINode* endContainer = 340 endRef.IsEndOfContainer() ? nullptr : endRef.GetContainer(); 341 342 auto AddNodeIfFullySelected = [&](const nsINode* aNode) { 343 if (!aNode) { 344 return; 345 } 346 // Only collect start and end container if they are fully 347 // selected (they are null in that case). 348 if (aNode == startContainer || aNode == endContainer) { 349 return; 350 } 351 fullySelectedNodes.Insert(aNode); 352 }; 353 354 if (!StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { 355 UnsafePreContentIterator iter; 356 nsresult rv = iter.Init(range); 357 if (NS_FAILED(rv)) { 358 continue; 359 } 360 for (; !iter.IsDone(); iter.Next()) { 361 AddNodeIfFullySelected(iter.GetCurrentNode()); 362 } 363 } else { 364 ContentSubtreeIterator subtreeIter; 365 nsresult rv = subtreeIter.InitWithAllowCrossShadowBoundary(range); 366 if (NS_FAILED(rv)) { 367 continue; 368 } 369 370 for (; !subtreeIter.IsDone(); subtreeIter.Next()) { 371 MOZ_DIAGNOSTIC_ASSERT(subtreeIter.GetCurrentNode()); 372 if (subtreeIter.GetCurrentNode()->IsContent()) { 373 TreeIterator<FlattenedChildIterator> iter( 374 *(subtreeIter.GetCurrentNode()->AsContent())); 375 for (; iter.GetCurrent(); iter.GetNext()) { 376 AddNodeIfFullySelected(iter.GetCurrent()); 377 } 378 } 379 } 380 } 381 } 382 return fullySelectedNodes; 383 }); 384 } 385 386 // #define DEBUG_SELECTION // uncomment for printf describing every collapse and 387 // extend. #define DEBUG_NAVIGATION 388 389 // #define DEBUG_TABLE_SELECTION 1 390 391 struct CachedOffsetForFrame { 392 CachedOffsetForFrame() 393 : mCachedFrameOffset(0, 0) // nsPoint ctor 394 , 395 mLastCaretFrame(nullptr), 396 mLastContentOffset(0), 397 mCanCacheFrameOffset(false) {} 398 399 nsPoint mCachedFrameOffset; // cached frame offset 400 nsIFrame* mLastCaretFrame; // store the frame the caret was last drawn in. 401 int32_t mLastContentOffset; // store last content offset 402 bool mCanCacheFrameOffset; // cached frame offset is valid? 403 }; 404 405 class AutoScroller final : public nsITimerCallback, public nsINamed { 406 public: 407 NS_DECL_ISUPPORTS 408 409 explicit AutoScroller(nsFrameSelection* aFrameSelection) 410 : mFrameSelection(aFrameSelection), 411 mPresContext(0), 412 mPoint(0, 0), 413 mDelayInMs(30), 414 mFurtherScrollingAllowed(FurtherScrollingAllowed::kYes) { 415 MOZ_ASSERT(mFrameSelection); 416 } 417 418 MOZ_CAN_RUN_SCRIPT nsresult DoAutoScroll(nsIFrame* aFrame, nsPoint aPoint); 419 420 private: 421 // aPoint is relative to aPresContext's root frame 422 nsresult ScheduleNextDoAutoScroll(nsPresContext* aPresContext, 423 nsPoint& aPoint) { 424 if (NS_WARN_IF(mFurtherScrollingAllowed == FurtherScrollingAllowed::kNo)) { 425 return NS_ERROR_FAILURE; 426 } 427 428 mPoint = aPoint; 429 430 // Store the presentation context. The timer will be 431 // stopped by the selection if the prescontext is destroyed. 432 mPresContext = aPresContext; 433 434 mContent = PresShell::GetCapturingContent(); 435 436 if (!mTimer) { 437 mTimer = NS_NewTimer(GetMainThreadSerialEventTarget()); 438 if (!mTimer) { 439 return NS_ERROR_OUT_OF_MEMORY; 440 } 441 } 442 443 return mTimer->InitWithCallback(this, mDelayInMs, nsITimer::TYPE_ONE_SHOT); 444 } 445 446 public: 447 enum class FurtherScrollingAllowed { kYes, kNo }; 448 449 void Stop(const FurtherScrollingAllowed aFurtherScrollingAllowed) { 450 MOZ_ASSERT((aFurtherScrollingAllowed == FurtherScrollingAllowed::kNo) || 451 (mFurtherScrollingAllowed == FurtherScrollingAllowed::kYes)); 452 453 if (mTimer) { 454 mTimer->Cancel(); 455 mTimer = nullptr; 456 } 457 458 mContent = nullptr; 459 mFurtherScrollingAllowed = aFurtherScrollingAllowed; 460 } 461 462 void SetDelay(uint32_t aDelayInMs) { mDelayInMs = aDelayInMs; } 463 464 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Notify(nsITimer* timer) override { 465 if (mPresContext) { 466 AutoWeakFrame frame = 467 mContent ? mPresContext->GetPrimaryFrameFor(mContent) : nullptr; 468 if (!frame) { 469 return NS_OK; 470 } 471 mContent = nullptr; 472 473 nsPoint pt = mPoint - frame->GetOffsetTo( 474 mPresContext->PresShell()->GetRootFrame()); 475 RefPtr<nsFrameSelection> frameSelection = mFrameSelection; 476 frameSelection->HandleDrag(frame, pt); 477 if (!frame.IsAlive()) { 478 return NS_OK; 479 } 480 481 NS_ASSERTION(frame->PresContext() == mPresContext, "document mismatch?"); 482 DoAutoScroll(frame, pt); 483 } 484 return NS_OK; 485 } 486 487 NS_IMETHOD GetName(nsACString& aName) override { 488 aName.AssignLiteral("AutoScroller"); 489 return NS_OK; 490 } 491 492 protected: 493 virtual ~AutoScroller() { 494 if (mTimer) { 495 mTimer->Cancel(); 496 } 497 } 498 499 private: 500 nsFrameSelection* const mFrameSelection; 501 nsPresContext* mPresContext; 502 // relative to mPresContext's root frame 503 nsPoint mPoint; 504 nsCOMPtr<nsITimer> mTimer; 505 nsCOMPtr<nsIContent> mContent; 506 uint32_t mDelayInMs; 507 FurtherScrollingAllowed mFurtherScrollingAllowed; 508 }; 509 510 NS_IMPL_ISUPPORTS(AutoScroller, nsITimerCallback, nsINamed) 511 512 #ifdef PRINT_RANGE 513 void printRange(nsRange* aDomRange) { 514 if (!aDomRange) { 515 printf("NULL Range\n"); 516 } 517 nsINode* startNode = aDomRange->GetStartContainer(); 518 nsINode* endNode = aDomRange->GetEndContainer(); 519 int32_t startOffset = aDomRange->StartOffset(); 520 int32_t endOffset = aDomRange->EndOffset(); 521 522 printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n", 523 (unsigned long)aDomRange, (unsigned long)startNode, (long)startOffset, 524 (unsigned long)endNode, (long)endOffset); 525 } 526 #endif /* PRINT_RANGE */ 527 528 void Selection::Stringify(nsAString& aResult, CallerType aCallerType, 529 FlushFrames aFlushFrames) { 530 if (aFlushFrames == FlushFrames::Yes) { 531 // We need FlushType::Frames here to make sure frames have been created for 532 // the selected content. Use mFrameSelection->GetPresShell() which returns 533 // null if the Selection has been disconnected (the shell is Destroyed). 534 RefPtr<PresShell> presShell = 535 mFrameSelection ? mFrameSelection->GetPresShell() : nullptr; 536 if (!presShell) { 537 aResult.Truncate(); 538 return; 539 } 540 presShell->FlushPendingNotifications(FlushType::Frames); 541 } 542 543 IgnoredErrorResult rv; 544 uint32_t flags = nsIDocumentEncoder::SkipInvisibleContent; 545 if (StaticPrefs::dom_selection_mimic_chrome_tostring_enabled() && 546 Type() == SelectionType::eNormal && 547 aCallerType == CallerType::NonSystem) { 548 if (mFrameSelection && 549 !mFrameSelection->GetIndependentSelectionRootElement()) { 550 // NonSystem and non-independent selection 551 flags |= nsIDocumentEncoder::MimicChromeToStringBehaviour; 552 } 553 } 554 555 ToStringWithFormat(u"text/plain"_ns, flags, 0, aResult, rv); 556 if (rv.Failed()) { 557 aResult.Truncate(); 558 } 559 } 560 561 void Selection::ToStringWithFormat(const nsAString& aFormatType, 562 uint32_t aFlags, int32_t aWrapCol, 563 nsAString& aReturn, ErrorResult& aRv) { 564 nsCOMPtr<nsIDocumentEncoder> encoder = 565 do_createDocumentEncoder(NS_ConvertUTF16toUTF8(aFormatType).get()); 566 if (!encoder) { 567 aRv.Throw(NS_ERROR_FAILURE); 568 return; 569 } 570 571 PresShell* presShell = GetPresShell(); 572 if (!presShell) { 573 aRv.Throw(NS_ERROR_FAILURE); 574 return; 575 } 576 577 Document* doc = presShell->GetDocument(); 578 579 // Flags should always include OutputSelectionOnly if we're coming from here: 580 aFlags |= nsIDocumentEncoder::OutputSelectionOnly; 581 nsAutoString readstring; 582 readstring.Assign(aFormatType); 583 nsresult rv = encoder->Init(doc, readstring, aFlags); 584 if (NS_FAILED(rv)) { 585 aRv.Throw(rv); 586 return; 587 } 588 589 Selection* selectionToEncode = this; 590 591 if (aFlags & nsIDocumentEncoder::MimicChromeToStringBehaviour) { 592 if (const nsFrameSelection* sel = 593 presShell->GetLastSelectionForToString()) { 594 MOZ_ASSERT(StaticPrefs::dom_selection_mimic_chrome_tostring_enabled()); 595 selectionToEncode = &sel->NormalSelection(); 596 } 597 } 598 599 encoder->SetSelection(selectionToEncode); 600 if (aWrapCol != 0) encoder->SetWrapColumn(aWrapCol); 601 602 rv = encoder->EncodeToString(aReturn); 603 if (NS_FAILED(rv)) { 604 aRv.Throw(rv); 605 } 606 } 607 608 nsresult Selection::SetInterlinePosition(InterlinePosition aInterlinePosition) { 609 MOZ_ASSERT(mSelectionType == SelectionType::eNormal); 610 MOZ_ASSERT(aInterlinePosition != InterlinePosition::Undefined); 611 612 if (!mFrameSelection) { 613 return NS_ERROR_NOT_INITIALIZED; // Can't do selection 614 } 615 616 mFrameSelection->SetHint(aInterlinePosition == 617 InterlinePosition::StartOfNextLine 618 ? CaretAssociationHint::After 619 : CaretAssociationHint::Before); 620 return NS_OK; 621 } 622 623 Selection::InterlinePosition Selection::GetInterlinePosition() const { 624 MOZ_ASSERT(mSelectionType == SelectionType::eNormal); 625 626 if (!mFrameSelection) { 627 return InterlinePosition::Undefined; 628 } 629 return mFrameSelection->GetHint() == CaretAssociationHint::After 630 ? InterlinePosition::StartOfNextLine 631 : InterlinePosition::EndOfLine; 632 } 633 634 void Selection::SetInterlinePositionJS(bool aHintRight, ErrorResult& aRv) { 635 MOZ_ASSERT(mSelectionType == SelectionType::eNormal); 636 637 aRv = SetInterlinePosition(aHintRight ? InterlinePosition::StartOfNextLine 638 : InterlinePosition::EndOfLine); 639 } 640 641 bool Selection::GetInterlinePositionJS(ErrorResult& aRv) const { 642 const InterlinePosition interlinePosition = GetInterlinePosition(); 643 if (interlinePosition == InterlinePosition::Undefined) { 644 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection 645 return false; 646 } 647 return interlinePosition == InterlinePosition::StartOfNextLine; 648 } 649 650 static bool IsEditorNode(const nsINode* aNode) { 651 if (!aNode) { 652 return false; 653 } 654 655 if (aNode->IsEditable()) { 656 return true; 657 } 658 659 auto* element = Element::FromNode(aNode); 660 return element && element->State().HasState(ElementState::READWRITE); 661 } 662 663 bool Selection::IsEditorSelection() const { 664 return IsEditorNode(GetFocusNode()); 665 } 666 667 Nullable<int16_t> Selection::GetCaretBidiLevel( 668 mozilla::ErrorResult& aRv) const { 669 MOZ_ASSERT(mSelectionType == SelectionType::eNormal); 670 671 if (!mFrameSelection) { 672 aRv.Throw(NS_ERROR_NOT_INITIALIZED); 673 return Nullable<int16_t>(); 674 } 675 mozilla::intl::BidiEmbeddingLevel caretBidiLevel = 676 static_cast<mozilla::intl::BidiEmbeddingLevel>( 677 mFrameSelection->GetCaretBidiLevel()); 678 return (caretBidiLevel & BIDI_LEVEL_UNDEFINED) 679 ? Nullable<int16_t>() 680 : Nullable<int16_t>(caretBidiLevel); 681 } 682 683 void Selection::SetCaretBidiLevel(const Nullable<int16_t>& aCaretBidiLevel, 684 mozilla::ErrorResult& aRv) { 685 MOZ_ASSERT(mSelectionType == SelectionType::eNormal); 686 687 if (!mFrameSelection) { 688 aRv.Throw(NS_ERROR_NOT_INITIALIZED); 689 return; 690 } 691 if (aCaretBidiLevel.IsNull()) { 692 mFrameSelection->UndefineCaretBidiLevel(); 693 } else { 694 mFrameSelection->SetCaretBidiLevelAndMaybeSchedulePaint( 695 mozilla::intl::BidiEmbeddingLevel(aCaretBidiLevel.Value())); 696 } 697 } 698 699 /** 700 * Test whether the supplied range points to a single table element. 701 * Result is one of the TableSelectionMode constants. "None" means 702 * a table element isn't selected. 703 */ 704 // TODO: Figure out TableSelectionMode::Column and TableSelectionMode::AllCells 705 static nsresult GetTableSelectionMode(const nsRange& aRange, 706 TableSelectionMode* aTableSelectionType) { 707 if (!aTableSelectionType) { 708 return NS_ERROR_NULL_POINTER; 709 } 710 711 *aTableSelectionType = TableSelectionMode::None; 712 713 nsINode* startNode = aRange.GetStartContainer(); 714 if (!startNode) { 715 return NS_ERROR_FAILURE; 716 } 717 718 nsINode* endNode = aRange.GetEndContainer(); 719 if (!endNode) { 720 return NS_ERROR_FAILURE; 721 } 722 723 // Not a single selected node 724 if (startNode != endNode) { 725 return NS_OK; 726 } 727 728 nsIContent* child = aRange.GetChildAtStartOffset(); 729 730 // Not a single selected node 731 if (!child || child->GetNextSibling() != aRange.GetChildAtEndOffset()) { 732 return NS_OK; 733 } 734 735 if (!startNode->IsHTMLElement()) { 736 // Implies a check for being an element; if we ever make this work 737 // for non-HTML, need to keep checking for elements. 738 return NS_OK; 739 } 740 741 if (startNode->IsHTMLElement(nsGkAtoms::tr)) { 742 *aTableSelectionType = TableSelectionMode::Cell; 743 } else // check to see if we are selecting a table or row (column and all 744 // cells not done yet) 745 { 746 if (child->IsHTMLElement(nsGkAtoms::table)) { 747 *aTableSelectionType = TableSelectionMode::Table; 748 } else if (child->IsHTMLElement(nsGkAtoms::tr)) { 749 *aTableSelectionType = TableSelectionMode::Row; 750 } 751 } 752 753 return NS_OK; 754 } 755 756 nsresult Selection::MaybeAddTableCellRange(nsRange& aRange, 757 Maybe<size_t>* aOutIndex) { 758 if (!aOutIndex) { 759 return NS_ERROR_NULL_POINTER; 760 } 761 762 MOZ_ASSERT(aOutIndex->isNothing()); 763 764 if (!mFrameSelection) { 765 return NS_OK; 766 } 767 768 // Get if we are adding a cell selection and the row, col of cell if we are 769 TableSelectionMode tableMode; 770 nsresult result = GetTableSelectionMode(aRange, &tableMode); 771 if (NS_FAILED(result)) return result; 772 773 // If not adding a cell range, we are done here 774 if (tableMode != TableSelectionMode::Cell) { 775 mFrameSelection->mTableSelection.mMode = tableMode; 776 // Don't fail if range isn't a selected cell, aDidAddRange tells caller if 777 // we didn't proceed 778 return NS_OK; 779 } 780 781 // Set frame selection mode only if not already set to a table mode 782 // so we don't lose the select row and column flags (not detected by 783 // getTableCellLocation) 784 if (mFrameSelection->mTableSelection.mMode == TableSelectionMode::None) { 785 mFrameSelection->mTableSelection.mMode = tableMode; 786 } 787 788 return AddRangesForSelectableNodes(&aRange, aOutIndex, 789 DispatchSelectstartEvent::Maybe); 790 } 791 792 Selection::Selection(SelectionType aSelectionType, 793 nsFrameSelection* aFrameSelection) 794 : mFrameSelection(aFrameSelection), 795 mCachedOffsetForFrame(nullptr), 796 mDirection(eDirNext), 797 mSelectionType(aSelectionType), 798 mCustomColors(nullptr), 799 mSelectionChangeBlockerCount(0), 800 mUserInitiated(false), 801 mCalledByJS(false), 802 mNotifyAutoCopy(false) {} 803 804 Selection::~Selection() { Disconnect(); } 805 806 void Selection::Disconnect() { 807 RemoveAnchorFocusRange(); 808 809 mStyledRanges.UnregisterSelection(); 810 811 if (mAutoScroller) { 812 mAutoScroller->Stop(AutoScroller::FurtherScrollingAllowed::kNo); 813 mAutoScroller = nullptr; 814 } 815 816 mScrollEvent.Revoke(); 817 818 if (mCachedOffsetForFrame) { 819 delete mCachedOffsetForFrame; 820 mCachedOffsetForFrame = nullptr; 821 } 822 } 823 824 Document* Selection::GetParentObject() const { 825 PresShell* presShell = GetPresShell(); 826 return presShell ? presShell->GetDocument() : nullptr; 827 } 828 829 DocGroup* Selection::GetDocGroup() const { 830 PresShell* presShell = GetPresShell(); 831 if (!presShell) { 832 return nullptr; 833 } 834 Document* doc = presShell->GetDocument(); 835 return doc ? doc->GetDocGroup() : nullptr; 836 } 837 838 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Selection) 839 840 MOZ_CAN_RUN_SCRIPT_BOUNDARY 841 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Selection) 842 // Unlink the selection listeners *before* we do RemoveAllRangesInternal since 843 // we don't want to notify the listeners during JS GC (they could be 844 // in JS!). 845 tmp->mNotifyAutoCopy = false; 846 if (tmp->mAccessibleCaretEventHub) { 847 tmp->StopNotifyingAccessibleCaretEventHub(); 848 } 849 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionChangeEventDispatcher) 850 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionListeners) 851 MOZ_KnownLive(tmp)->RemoveAllRangesInternal(IgnoreErrors(), IsUnlinking::Yes); 852 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameSelection) 853 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightData.mHighlight) 854 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 855 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR 856 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE 857 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 858 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Selection) 859 { 860 uint32_t i, count = tmp->mStyledRanges.Length(); 861 for (i = 0; i < count; ++i) { 862 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyledRanges.mRanges[i].mRange) 863 } 864 count = tmp->mStyledRanges.mInvalidStaticRanges.Length(); 865 for (i = 0; i < count; ++i) { 866 NS_IMPL_CYCLE_COLLECTION_TRAVERSE( 867 mStyledRanges.mInvalidStaticRanges[i].mRange); 868 } 869 } 870 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorFocusRange) 871 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameSelection) 872 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightData.mHighlight) 873 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionChangeEventDispatcher) 874 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionListeners) 875 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 876 877 // QueryInterface implementation for Selection 878 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Selection) 879 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 880 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) 881 NS_INTERFACE_MAP_ENTRY(nsISupports) 882 NS_INTERFACE_MAP_END 883 884 NS_IMPL_CYCLE_COLLECTING_ADDREF(Selection) 885 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Selection, Disconnect()) 886 887 const RangeBoundary& Selection::AnchorRef( 888 AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) const { 889 if (!mAnchorFocusRange) { 890 static RangeBoundary sEmpty; 891 return sEmpty; 892 } 893 894 if (GetDirection() == eDirNext) { 895 return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes 896 ? mAnchorFocusRange->MayCrossShadowBoundaryStartRef() 897 : mAnchorFocusRange->StartRef(); 898 } 899 900 return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes 901 ? mAnchorFocusRange->MayCrossShadowBoundaryEndRef() 902 : mAnchorFocusRange->EndRef(); 903 } 904 905 const RangeBoundary& Selection::FocusRef( 906 AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) const { 907 if (!mAnchorFocusRange) { 908 static RangeBoundary sEmpty; 909 return sEmpty; 910 } 911 912 if (GetDirection() == eDirNext) { 913 return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes 914 ? mAnchorFocusRange->MayCrossShadowBoundaryEndRef() 915 : mAnchorFocusRange->EndRef(); 916 } 917 return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes 918 ? mAnchorFocusRange->MayCrossShadowBoundaryStartRef() 919 : mAnchorFocusRange->StartRef(); 920 } 921 922 void Selection::SetAnchorFocusRange(size_t aIndex) { 923 if (aIndex >= mStyledRanges.Length()) { 924 return; 925 } 926 // Highlight selections may contain static ranges. 927 MOZ_ASSERT(mSelectionType != SelectionType::eHighlight); 928 AbstractRange* anchorFocusRange = mStyledRanges.mRanges[aIndex].mRange; 929 mAnchorFocusRange = anchorFocusRange->AsDynamicRange(); 930 } 931 932 template <TreeKind aKind, typename PT, typename RT, 933 typename = std::enable_if_t<aKind == TreeKind::ShadowIncludingDOM || 934 aKind == TreeKind::Flat>> 935 static int32_t CompareToRangeStart( 936 const RangeBoundaryBase<PT, RT>& aCompareBoundary, 937 const AbstractRange& aRange, nsContentUtils::NodeIndexCache* aCache) { 938 MOZ_ASSERT(aCompareBoundary.IsSet()); 939 MOZ_ASSERT(aRange.GetMayCrossShadowBoundaryStartContainer()); 940 // If the nodes that we're comparing are not in the same document, assume 941 // that aCompareNode will fall at the end of the ranges. 942 if (aCompareBoundary.GetComposedDoc() != 943 aRange.MayCrossShadowBoundaryStartRef().GetComposedDoc() || 944 !aRange.MayCrossShadowBoundaryStartRef().IsSetAndInComposedDoc()) { 945 NS_WARNING( 946 "`CompareToRangeStart` couldn't compare nodes, pretending some order."); 947 return 1; 948 } 949 return *nsContentUtils::ComparePoints<aKind>( 950 aCompareBoundary, 951 ConstRawRangeBoundary{aRange.GetMayCrossShadowBoundaryStartContainer(), 952 aRange.MayCrossShadowBoundaryStartOffset()}, 953 aCache); 954 } 955 956 template <TreeKind aKind, typename PT, typename RT, 957 typename = std::enable_if_t<aKind == TreeKind::ShadowIncludingDOM || 958 aKind == TreeKind::Flat>> 959 static int32_t CompareToRangeStart( 960 const RangeBoundaryBase<PT, RT>& aCompareBoundary, 961 const AbstractRange& aRange) { 962 return CompareToRangeStart<aKind>(aCompareBoundary, aRange, nullptr); 963 } 964 965 template <TreeKind aKind, typename PT, typename RT, 966 typename = std::enable_if_t<aKind == TreeKind::ShadowIncludingDOM || 967 aKind == TreeKind::Flat>> 968 static int32_t CompareToRangeEnd( 969 const RangeBoundaryBase<PT, RT>& aCompareBoundary, 970 const AbstractRange& aRange) { 971 MOZ_ASSERT(aCompareBoundary.IsSet()); 972 MOZ_ASSERT(aRange.IsPositioned()); 973 // If the nodes that we're comparing are not in the same document or in the 974 // same subtree, assume that aCompareNode will fall at the end of the ranges. 975 if (aCompareBoundary.GetComposedDoc() != 976 aRange.MayCrossShadowBoundaryEndRef().GetComposedDoc() || 977 !aRange.MayCrossShadowBoundaryEndRef().IsSetAndInComposedDoc()) { 978 NS_WARNING( 979 "`CompareToRangeEnd` couldn't compare nodes, pretending some order."); 980 return 1; 981 } 982 983 nsINode* end = aRange.GetMayCrossShadowBoundaryEndContainer(); 984 uint32_t endOffset = aRange.MayCrossShadowBoundaryEndOffset(); 985 if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { 986 return *nsContentUtils::ComparePoints<TreeKind::Flat>( 987 aCompareBoundary, ConstRawRangeBoundary{end, endOffset}); 988 } 989 return *nsContentUtils::ComparePoints<TreeKind::ShadowIncludingDOM>( 990 aCompareBoundary, ConstRawRangeBoundary{end, endOffset}); 991 } 992 993 // static 994 template <typename PT, typename RT> 995 size_t Selection::StyledRanges::FindInsertionPoint( 996 const nsTArray<StyledRange>* aElementArray, 997 const RangeBoundaryBase<PT, RT>& aBoundary, 998 int32_t (*aComparator)(const RangeBoundaryBase<PT, RT>&, 999 const AbstractRange&)) { 1000 int32_t beginSearch = 0; 1001 int32_t endSearch = aElementArray->Length(); // one beyond what to check 1002 1003 if (endSearch) { 1004 int32_t center = endSearch - 1; // Check last index, then binary search 1005 do { 1006 const AbstractRange* range = (*aElementArray)[center].mRange; 1007 1008 int32_t cmp{aComparator(aBoundary, *range)}; 1009 1010 if (cmp < 0) { // point < cur 1011 endSearch = center; 1012 } else if (cmp > 0) { // point > cur 1013 beginSearch = center + 1; 1014 } else { // found match, done 1015 beginSearch = center; 1016 break; 1017 } 1018 center = (endSearch - beginSearch) / 2 + beginSearch; 1019 } while (endSearch - beginSearch > 0); 1020 } 1021 1022 return AssertedCast<size_t>(beginSearch); 1023 } 1024 1025 // Selection::SubtractRange 1026 // 1027 // A helper function that subtracts aSubtract from aRange, and adds 1028 // 1 or 2 StyledRange objects representing the remaining non-overlapping 1029 // difference to aOutput. It is assumed that the caller has checked that 1030 // aRange and aSubtract do indeed overlap 1031 1032 // static 1033 nsresult Selection::StyledRanges::SubtractRange( 1034 StyledRange& aRange, nsRange& aSubtract, nsTArray<StyledRange>* aOutput) { 1035 AbstractRange* range = aRange.mRange; 1036 if (NS_WARN_IF(!range->IsPositioned())) { 1037 return NS_ERROR_UNEXPECTED; 1038 } 1039 1040 if (range->GetStartContainer()->SubtreeRoot() != 1041 aSubtract.GetStartContainer()->SubtreeRoot()) { 1042 // These are ranges for different shadow trees, we can't subtract them in 1043 // any sensible way. 1044 aOutput->InsertElementAt(0, aRange); 1045 return NS_OK; 1046 } 1047 1048 // First we want to compare to the range start 1049 int32_t cmp = [&range, &aSubtract]() { 1050 if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { 1051 return CompareToRangeStart<TreeKind::Flat>(range->StartRef(), aSubtract); 1052 } 1053 return CompareToRangeStart<TreeKind::ShadowIncludingDOM>(range->StartRef(), 1054 aSubtract); 1055 }(); 1056 1057 // Also, make a comparison to the range end 1058 int32_t cmp2 = [&range, &aSubtract]() { 1059 if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { 1060 return CompareToRangeEnd<TreeKind::Flat>(range->EndRef(), aSubtract); 1061 } 1062 return CompareToRangeEnd<TreeKind::ShadowIncludingDOM>(range->EndRef(), 1063 aSubtract); 1064 }(); 1065 1066 // If the existing range left overlaps the new range (aSubtract) then 1067 // cmp < 0, and cmp2 < 0 1068 // If it right overlaps the new range then cmp > 0 and cmp2 > 0 1069 // If it fully contains the new range, then cmp < 0 and cmp2 > 0 1070 1071 if (cmp2 > 0) { 1072 // We need to add a new StyledRange to the output, running from 1073 // the end of aSubtract to the end of range 1074 ErrorResult error; 1075 RefPtr<nsRange> postOverlap = 1076 nsRange::Create(aSubtract.EndRef(), range->EndRef(), error); 1077 if (NS_WARN_IF(error.Failed())) { 1078 return error.StealNSResult(); 1079 } 1080 MOZ_ASSERT(postOverlap); 1081 if (!postOverlap->Collapsed()) { 1082 // XXX(Bug 1631371) Check if this should use a fallible operation as it 1083 // pretended earlier. 1084 aOutput->InsertElementAt(0, StyledRange(postOverlap)); 1085 (*aOutput)[0].mTextRangeStyle = aRange.mTextRangeStyle; 1086 } 1087 } 1088 1089 if (cmp < 0) { 1090 // We need to add a new StyledRange to the output, running from 1091 // the start of the range to the start of aSubtract 1092 ErrorResult error; 1093 RefPtr<nsRange> preOverlap = 1094 nsRange::Create(range->StartRef(), aSubtract.StartRef(), error); 1095 if (NS_WARN_IF(error.Failed())) { 1096 return error.StealNSResult(); 1097 } 1098 MOZ_ASSERT(preOverlap); 1099 if (!preOverlap->Collapsed()) { 1100 // XXX(Bug 1631371) Check if this should use a fallible operation as it 1101 // pretended earlier. 1102 aOutput->InsertElementAt(0, StyledRange(preOverlap)); 1103 (*aOutput)[0].mTextRangeStyle = aRange.mTextRangeStyle; 1104 } 1105 } 1106 1107 return NS_OK; 1108 } 1109 1110 static void UserSelectRangesToAdd(nsRange* aItem, 1111 nsTArray<RefPtr<nsRange>>& aRangesToAdd) { 1112 // We cannot directly call IsEditorSelection() because we may be in an 1113 // inconsistent state during Collapse() (we're cleared already but we haven't 1114 // got a new focus node yet). 1115 if (!StaticPrefs::dom_selection_exclude_non_selectable_nodes() || 1116 (IsEditorNode(aItem->GetStartContainer()) && 1117 IsEditorNode(aItem->GetEndContainer()))) { 1118 // Don't mess with the selection ranges for editing, editor doesn't really 1119 // deal well with multi-range selections. 1120 aRangesToAdd.AppendElement(aItem); 1121 } else { 1122 aItem->ExcludeNonSelectableNodes(&aRangesToAdd); 1123 } 1124 } 1125 1126 static nsINode* DetermineSelectstartEventTarget( 1127 const bool aSelectionEventsOnTextControlsEnabled, const nsRange& aRange) { 1128 nsINode* target = aRange.GetStartContainer(); 1129 if (aSelectionEventsOnTextControlsEnabled) { 1130 // Get the first element which isn't in a native anonymous subtree 1131 while (target && target->IsInNativeAnonymousSubtree()) { 1132 target = target->GetParent(); 1133 } 1134 } else { 1135 if (target->IsInNativeAnonymousSubtree()) { 1136 // This is a selection under a text control, so don't dispatch the 1137 // event. 1138 target = nullptr; 1139 } 1140 } 1141 return target; 1142 } 1143 1144 /** 1145 * @return true, iff the default action should be executed. 1146 */ 1147 static bool MaybeDispatchSelectstartEvent( 1148 const nsRange& aRange, const bool aSelectionEventsOnTextControlsEnabled, 1149 Document* aDocument) { 1150 nsCOMPtr<nsINode> selectstartEventTarget = DetermineSelectstartEventTarget( 1151 aSelectionEventsOnTextControlsEnabled, aRange); 1152 1153 bool executeDefaultAction = true; 1154 1155 if (selectstartEventTarget) { 1156 nsContentUtils::DispatchTrustedEvent( 1157 aDocument, selectstartEventTarget, u"selectstart"_ns, CanBubble::eYes, 1158 Cancelable::eYes, &executeDefaultAction); 1159 } 1160 1161 return executeDefaultAction; 1162 } 1163 1164 // static 1165 bool Selection::IsUserSelectionCollapsed( 1166 const nsRange& aRange, nsTArray<RefPtr<nsRange>>& aTempRangesToAdd) { 1167 MOZ_ASSERT(aTempRangesToAdd.IsEmpty()); 1168 1169 RefPtr<nsRange> scratchRange = aRange.CloneRange(); 1170 UserSelectRangesToAdd(scratchRange, aTempRangesToAdd); 1171 const bool userSelectionCollapsed = 1172 (aTempRangesToAdd.Length() == 0) || 1173 ((aTempRangesToAdd.Length() == 1) && aTempRangesToAdd[0]->Collapsed()); 1174 1175 aTempRangesToAdd.ClearAndRetainStorage(); 1176 1177 return userSelectionCollapsed; 1178 } 1179 1180 nsresult Selection::AddRangesForUserSelectableNodes( 1181 nsRange* aRange, Maybe<size_t>* aOutIndex, 1182 const DispatchSelectstartEvent aDispatchSelectstartEvent) { 1183 MOZ_ASSERT(mUserInitiated); 1184 MOZ_ASSERT(aOutIndex); 1185 MOZ_ASSERT(aOutIndex->isNothing()); 1186 1187 if (!aRange) { 1188 return NS_ERROR_NULL_POINTER; 1189 } 1190 1191 if (!aRange->IsPositioned()) { 1192 return NS_ERROR_UNEXPECTED; 1193 } 1194 1195 AutoTArray<RefPtr<nsRange>, 4> rangesToAdd; 1196 if (mStyledRanges.Length()) { 1197 aOutIndex->emplace(mStyledRanges.Length() - 1); 1198 } 1199 1200 Document* doc = GetDocument(); 1201 1202 if (aDispatchSelectstartEvent == DispatchSelectstartEvent::Maybe && 1203 mSelectionType == SelectionType::eNormal && IsCollapsed() && 1204 !IsBlockingSelectionChangeEvents()) { 1205 // We consider a selection to be starting if we are currently collapsed, 1206 // and the selection is becoming uncollapsed, and this is caused by a 1207 // user initiated event. 1208 1209 // First, we generate the ranges to add with a scratch range, which is a 1210 // clone of the original range passed in. We do this seperately, because 1211 // the selectstart event could have caused the world to change, and 1212 // required ranges to be re-generated 1213 1214 const bool userSelectionCollapsed = 1215 IsUserSelectionCollapsed(*aRange, rangesToAdd); 1216 MOZ_ASSERT(userSelectionCollapsed || nsContentUtils::IsSafeToRunScript()); 1217 if (!userSelectionCollapsed && nsContentUtils::IsSafeToRunScript()) { 1218 // The spec currently doesn't say that we should dispatch this event 1219 // on text controls, so for now we only support doing that under a 1220 // pref, disabled by default. 1221 // See https://github.com/w3c/selection-api/issues/53. 1222 const bool executeDefaultAction = MaybeDispatchSelectstartEvent( 1223 *aRange, 1224 StaticPrefs::dom_select_events_textcontrols_selectstart_enabled(), 1225 doc); 1226 1227 if (!executeDefaultAction) { 1228 return NS_OK; 1229 } 1230 1231 // As we potentially dispatched an event to the DOM, something could have 1232 // changed under our feet. Re-generate the rangesToAdd array, and 1233 // ensure that the range we are about to add is still valid. 1234 if (!aRange->IsPositioned()) { 1235 return NS_ERROR_UNEXPECTED; 1236 } 1237 } 1238 } 1239 1240 // Generate the ranges to add 1241 UserSelectRangesToAdd(aRange, rangesToAdd); 1242 size_t newAnchorFocusIndex = 1243 GetDirection() == eDirPrevious ? 0 : rangesToAdd.Length() - 1; 1244 for (size_t i = 0; i < rangesToAdd.Length(); ++i) { 1245 Maybe<size_t> index; 1246 // `MOZ_KnownLive` needed because of broken static analysis 1247 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1622253#c1). 1248 nsresult rv = mStyledRanges.MaybeAddRangeAndTruncateOverlaps( 1249 MOZ_KnownLive(rangesToAdd[i]), &index); 1250 NS_ENSURE_SUCCESS(rv, rv); 1251 if (i == newAnchorFocusIndex) { 1252 *aOutIndex = index; 1253 rangesToAdd[i]->SetIsGenerated(false); 1254 } else { 1255 rangesToAdd[i]->SetIsGenerated(true); 1256 } 1257 } 1258 return NS_OK; 1259 } 1260 1261 nsresult Selection::AddRangesForSelectableNodes( 1262 nsRange* aRange, Maybe<size_t>* aOutIndex, 1263 const DispatchSelectstartEvent aDispatchSelectstartEvent) { 1264 MOZ_ASSERT(aOutIndex); 1265 MOZ_ASSERT(aOutIndex->isNothing()); 1266 1267 if (!aRange) { 1268 return NS_ERROR_NULL_POINTER; 1269 } 1270 1271 if (!aRange->IsPositioned()) { 1272 return NS_ERROR_UNEXPECTED; 1273 } 1274 1275 MOZ_LOG( 1276 sSelectionLog, LogLevel::Debug, 1277 ("%s: selection=%p, type=%i, range=(%p, StartOffset=%u, EndOffset=%u)", 1278 __FUNCTION__, this, static_cast<int>(GetType()), aRange, 1279 aRange->StartOffset(), aRange->EndOffset())); 1280 1281 if (mUserInitiated) { 1282 return AddRangesForUserSelectableNodes(aRange, aOutIndex, 1283 aDispatchSelectstartEvent); 1284 } 1285 1286 return mStyledRanges.MaybeAddRangeAndTruncateOverlaps(aRange, aOutIndex); 1287 } 1288 1289 nsresult Selection::StyledRanges::AddRangeAndIgnoreOverlaps( 1290 AbstractRange* aRange) { 1291 MOZ_ASSERT(aRange); 1292 MOZ_ASSERT(aRange->IsPositioned()); 1293 MOZ_ASSERT(mSelection.mSelectionType == SelectionType::eHighlight); 1294 if (aRange->IsStaticRange() && !aRange->AsStaticRange()->IsValid()) { 1295 mInvalidStaticRanges.AppendElement(StyledRange(aRange)); 1296 return NS_OK; 1297 } 1298 1299 // a common case is that we have no ranges yet 1300 if (mRanges.Length() == 0) { 1301 mRanges.AppendElement(StyledRange(aRange)); 1302 aRange->RegisterSelection(MOZ_KnownLive(mSelection)); 1303 #ifdef ACCESSIBILITY 1304 a11y::SelectionManager::SelectionRangeChanged(mSelection.GetType(), 1305 *aRange); 1306 #endif 1307 return NS_OK; 1308 } 1309 1310 Maybe<size_t> maybeStartIndex, maybeEndIndex; 1311 nsresult rv = 1312 GetIndicesForInterval(aRange->GetStartContainer(), aRange->StartOffset(), 1313 aRange->GetEndContainer(), aRange->EndOffset(), 1314 false, maybeStartIndex, maybeEndIndex); 1315 NS_ENSURE_SUCCESS(rv, rv); 1316 1317 size_t startIndex(0); 1318 if (maybeEndIndex.isNothing()) { 1319 // All ranges start after the given range. We can insert our range at 1320 // position 0. 1321 startIndex = 0; 1322 } else if (maybeStartIndex.isNothing()) { 1323 // All ranges end before the given range. We can insert our range at 1324 // the end of the array. 1325 startIndex = mRanges.Length(); 1326 } else { 1327 startIndex = *maybeStartIndex; 1328 } 1329 1330 mRanges.InsertElementAt(startIndex, StyledRange(aRange)); 1331 aRange->RegisterSelection(MOZ_KnownLive(mSelection)); 1332 #ifdef ACCESSIBILITY 1333 a11y::SelectionManager::SelectionRangeChanged(mSelection.GetType(), *aRange); 1334 #endif 1335 return NS_OK; 1336 } 1337 1338 nsresult Selection::StyledRanges::MaybeAddRangeAndTruncateOverlaps( 1339 nsRange* aRange, Maybe<size_t>* aOutIndex) { 1340 MOZ_ASSERT(aRange); 1341 MOZ_ASSERT(aRange->IsPositioned()); 1342 MOZ_ASSERT(aOutIndex); 1343 MOZ_ASSERT(aOutIndex->isNothing()); 1344 1345 // a common case is that we have no ranges yet 1346 if (mRanges.Length() == 0) { 1347 // XXX(Bug 1631371) Check if this should use a fallible operation as it 1348 // pretended earlier. 1349 mRanges.AppendElement(StyledRange(aRange)); 1350 aRange->RegisterSelection(MOZ_KnownLive(mSelection)); 1351 #ifdef ACCESSIBILITY 1352 a11y::SelectionManager::SelectionRangeChanged(mSelection.GetType(), 1353 *aRange); 1354 #endif 1355 1356 aOutIndex->emplace(0u); 1357 return NS_OK; 1358 } 1359 1360 Maybe<size_t> maybeStartIndex, maybeEndIndex; 1361 nsresult rv = 1362 GetIndicesForInterval(aRange->GetStartContainer(), aRange->StartOffset(), 1363 aRange->GetEndContainer(), aRange->EndOffset(), 1364 false, maybeStartIndex, maybeEndIndex); 1365 NS_ENSURE_SUCCESS(rv, rv); 1366 1367 size_t startIndex, endIndex; 1368 if (maybeEndIndex.isNothing()) { 1369 // All ranges start after the given range. We can insert our range at 1370 // position 0, knowing there are no overlaps (handled below) 1371 startIndex = endIndex = 0; 1372 } else if (maybeStartIndex.isNothing()) { 1373 // All ranges end before the given range. We can insert our range at 1374 // the end of the array, knowing there are no overlaps (handled below) 1375 startIndex = endIndex = mRanges.Length(); 1376 } else { 1377 startIndex = *maybeStartIndex; 1378 endIndex = *maybeEndIndex; 1379 } 1380 1381 // If the range is already contained in mRanges, silently 1382 // succeed 1383 const bool sameRange = HasEqualRangeBoundariesAt(*aRange, startIndex); 1384 if (sameRange) { 1385 aOutIndex->emplace(startIndex); 1386 return NS_OK; 1387 } 1388 1389 // Beyond this point, we will expand the selection to cover aRange. 1390 // Accessibility doesn't need to know about ranges split due to overlaps. It 1391 // just needs a range that covers any text leaf that is impacted by the 1392 // change. 1393 #ifdef ACCESSIBILITY 1394 a11y::SelectionManager::SelectionRangeChanged(mSelection.GetType(), *aRange); 1395 #endif 1396 1397 if (startIndex == endIndex) { 1398 // The new range doesn't overlap any existing ranges 1399 // XXX(Bug 1631371) Check if this should use a fallible operation as it 1400 // pretended earlier. 1401 mRanges.InsertElementAt(startIndex, StyledRange(aRange)); 1402 aRange->RegisterSelection(MOZ_KnownLive(mSelection)); 1403 aOutIndex->emplace(startIndex); 1404 return NS_OK; 1405 } 1406 1407 // We now know that at least 1 existing range overlaps with the range that 1408 // we are trying to add. In fact, the only ranges of interest are those at 1409 // the two end points, startIndex and endIndex - 1 (which may point to the 1410 // same range) as these may partially overlap the new range. Any ranges 1411 // between these indices are fully overlapped by the new range, and so can be 1412 // removed. 1413 AutoTArray<StyledRange, 2> overlaps; 1414 overlaps.AppendElement(mRanges[startIndex]); 1415 if (endIndex - 1 != startIndex) { 1416 overlaps.AppendElement(mRanges[endIndex - 1]); 1417 } 1418 1419 // Remove all the overlapping ranges 1420 for (size_t i = startIndex; i < endIndex; ++i) { 1421 mRanges[i].mRange->UnregisterSelection(mSelection); 1422 } 1423 mRanges.RemoveElementsAt(startIndex, endIndex - startIndex); 1424 1425 AutoTArray<StyledRange, 3> temp; 1426 for (const size_t i : Reversed(IntegerRange(overlaps.Length()))) { 1427 nsresult rv = SubtractRange(overlaps[i], *aRange, &temp); 1428 NS_ENSURE_SUCCESS(rv, rv); 1429 } 1430 1431 // Insert the new element into our "leftovers" array 1432 // `aRange` is positioned, so it has to have a start container. 1433 size_t insertionPoint = [&temp, &aRange]() { 1434 if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { 1435 return FindInsertionPoint(&temp, aRange->StartRef(), 1436 CompareToRangeStart<TreeKind::Flat>); 1437 }; 1438 return FindInsertionPoint( 1439 &temp, aRange->StartRef(), 1440 CompareToRangeStart<TreeKind::ShadowIncludingDOM>); 1441 }(); 1442 1443 temp.InsertElementAt(insertionPoint, StyledRange(aRange)); 1444 1445 // Merge the leftovers back in to mRanges 1446 mRanges.InsertElementsAt(startIndex, temp); 1447 1448 for (uint32_t i = 0; i < temp.Length(); ++i) { 1449 if (temp[i].mRange->IsDynamicRange()) { 1450 MOZ_KnownLive(temp[i].mRange->AsDynamicRange()) 1451 ->RegisterSelection(MOZ_KnownLive(mSelection)); 1452 // `MOZ_KnownLive` is required because of 1453 // https://bugzilla.mozilla.org/show_bug.cgi?id=1622253. 1454 } 1455 } 1456 1457 aOutIndex->emplace(startIndex + insertionPoint); 1458 return NS_OK; 1459 } 1460 1461 nsresult Selection::StyledRanges::RemoveRangeAndUnregisterSelection( 1462 AbstractRange& aRange) { 1463 // Find the range's index & remove it. We could use FindInsertionPoint to 1464 // get O(log n) time, but that requires many expensive DOM comparisons. 1465 // For even several thousand items, this is probably faster because the 1466 // comparisons are so fast. 1467 int32_t idx = -1; 1468 uint32_t i; 1469 for (i = 0; i < mRanges.Length(); i++) { 1470 if (mRanges[i].mRange == &aRange) { 1471 idx = (int32_t)i; 1472 break; 1473 } 1474 } 1475 if (idx < 0) return NS_ERROR_DOM_NOT_FOUND_ERR; 1476 1477 mRanges.RemoveElementAt(idx); 1478 aRange.UnregisterSelection(mSelection); 1479 #ifdef ACCESSIBILITY 1480 a11y::SelectionManager::SelectionRangeChanged(mSelection.GetType(), aRange); 1481 #endif 1482 1483 return NS_OK; 1484 } 1485 nsresult Selection::RemoveCollapsedRanges() { 1486 if (NeedsToLogSelectionAPI(*this)) { 1487 LogSelectionAPI(this, __FUNCTION__); 1488 LogStackForSelectionAPI(); 1489 } 1490 1491 return mStyledRanges.RemoveCollapsedRanges(); 1492 } 1493 1494 nsresult Selection::StyledRanges::RemoveCollapsedRanges() { 1495 uint32_t i = 0; 1496 while (i < mRanges.Length()) { 1497 const AbstractRange* range = mRanges[i].mRange; 1498 // If nsRange::mCrossShadowBoundaryRange exists, it means 1499 // there's a cross boundary selection, so obviously 1500 // we shouldn't remove this range. 1501 const bool collapsed = 1502 range->Collapsed() && !range->MayCrossShadowBoundary(); 1503 // Cross boundary range should always be uncollapsed. 1504 MOZ_ASSERT_IF( 1505 range->MayCrossShadowBoundary(), 1506 !range->AsDynamicRange()->CrossShadowBoundaryRangeCollapsed()); 1507 1508 if (collapsed) { 1509 nsresult rv = RemoveRangeAndUnregisterSelection(*mRanges[i].mRange); 1510 NS_ENSURE_SUCCESS(rv, rv); 1511 } else { 1512 ++i; 1513 } 1514 } 1515 return NS_OK; 1516 } 1517 1518 void Selection::Clear(nsPresContext* aPresContext, IsUnlinking aIsUnlinking) { 1519 RemoveAnchorFocusRange(); 1520 1521 mStyledRanges.UnregisterSelection(); 1522 for (uint32_t i = 0; i < mStyledRanges.Length(); ++i) { 1523 SelectFrames(aPresContext, *mStyledRanges.mRanges[i].mRange, false); 1524 } 1525 mStyledRanges.Clear(); 1526 1527 // Reset direction so for more dependable table selection range handling 1528 SetDirection(eDirNext); 1529 1530 // If this was an ATTENTION selection, change it back to normal now 1531 if (mFrameSelection && mFrameSelection->GetDisplaySelection() == 1532 nsISelectionController::SELECTION_ATTENTION) { 1533 mFrameSelection->SetDisplaySelection(nsISelectionController::SELECTION_ON); 1534 } 1535 } 1536 1537 bool Selection::StyledRanges::HasEqualRangeBoundariesAt( 1538 const AbstractRange& aRange, size_t aRangeIndex) const { 1539 if (aRangeIndex < mRanges.Length()) { 1540 const AbstractRange* range = mRanges[aRangeIndex].mRange; 1541 return range->HasEqualBoundaries(aRange); 1542 } 1543 return false; 1544 } 1545 1546 void Selection::GetRangesForInterval(nsINode& aBeginNode, uint32_t aBeginOffset, 1547 nsINode& aEndNode, uint32_t aEndOffset, 1548 bool aAllowAdjacent, 1549 nsTArray<RefPtr<nsRange>>& aReturn, 1550 mozilla::ErrorResult& aRv) { 1551 AutoTArray<nsRange*, 2> results; 1552 nsresult rv = 1553 GetDynamicRangesForIntervalArray(&aBeginNode, aBeginOffset, &aEndNode, 1554 aEndOffset, aAllowAdjacent, &results); 1555 if (NS_FAILED(rv)) { 1556 aRv.Throw(rv); 1557 return; 1558 } 1559 1560 aReturn.SetLength(results.Length()); 1561 for (size_t i = 0; i < results.Length(); ++i) { 1562 aReturn[i] = results[i]; // AddRefs 1563 } 1564 } 1565 1566 nsresult Selection::GetAbstractRangesForIntervalArray( 1567 nsINode* aBeginNode, uint32_t aBeginOffset, nsINode* aEndNode, 1568 uint32_t aEndOffset, bool aAllowAdjacent, 1569 nsTArray<AbstractRange*>* aRanges) { 1570 if (NS_WARN_IF(!aBeginNode)) { 1571 return NS_ERROR_UNEXPECTED; 1572 } 1573 1574 if (NS_WARN_IF(!aEndNode)) { 1575 return NS_ERROR_UNEXPECTED; 1576 } 1577 1578 aRanges->Clear(); 1579 Maybe<size_t> maybeStartIndex, maybeEndIndex; 1580 nsresult res = mStyledRanges.GetIndicesForInterval( 1581 aBeginNode, aBeginOffset, aEndNode, aEndOffset, aAllowAdjacent, 1582 maybeStartIndex, maybeEndIndex); 1583 NS_ENSURE_SUCCESS(res, res); 1584 1585 if (maybeStartIndex.isNothing() || maybeEndIndex.isNothing()) { 1586 return NS_OK; 1587 } 1588 1589 for (const size_t i : IntegerRange(*maybeStartIndex, *maybeEndIndex)) { 1590 // XXX(Bug 1631371) Check if this should use a fallible operation as it 1591 // pretended earlier. 1592 aRanges->AppendElement(mStyledRanges.mRanges[i].mRange); 1593 } 1594 1595 return NS_OK; 1596 } 1597 1598 nsresult Selection::GetDynamicRangesForIntervalArray( 1599 nsINode* aBeginNode, uint32_t aBeginOffset, nsINode* aEndNode, 1600 uint32_t aEndOffset, bool aAllowAdjacent, nsTArray<nsRange*>* aRanges) { 1601 MOZ_ASSERT(mSelectionType != SelectionType::eHighlight); 1602 AutoTArray<AbstractRange*, 2> abstractRanges; 1603 nsresult rv = GetAbstractRangesForIntervalArray( 1604 aBeginNode, aBeginOffset, aEndNode, aEndOffset, aAllowAdjacent, 1605 &abstractRanges); 1606 NS_ENSURE_SUCCESS(rv, rv); 1607 aRanges->Clear(); 1608 aRanges->SetCapacity(abstractRanges.Length()); 1609 for (auto* abstractRange : abstractRanges) { 1610 aRanges->AppendElement(abstractRange->AsDynamicRange()); 1611 } 1612 return NS_OK; 1613 } 1614 1615 void Selection::StyledRanges::ReorderRangesIfNecessary() { 1616 const Document* doc = mSelection.GetDocument(); 1617 if (!doc) { 1618 return; 1619 } 1620 if (mRanges.Length() < 2 && mInvalidStaticRanges.IsEmpty()) { 1621 // There is nothing to be reordered. 1622 return; 1623 } 1624 const int32_t currentDocumentGeneration = doc->GetGeneration(); 1625 const bool domMutationHasHappened = 1626 currentDocumentGeneration != mDocumentGeneration; 1627 if (domMutationHasHappened) { 1628 // After a DOM mutation, invalid static ranges might have become valid and 1629 // valid static ranges might have become invalid. 1630 StyledRangeArray invalidStaticRanges; 1631 for (StyledRangeArray::const_iterator iter = mRanges.begin(); 1632 iter != mRanges.end();) { 1633 const AbstractRange* range = iter->mRange; 1634 if (range->IsStaticRange() && !range->AsStaticRange()->IsValid()) { 1635 invalidStaticRanges.AppendElement(*iter); 1636 iter = mRanges.RemoveElementAt(iter); 1637 } else { 1638 ++iter; 1639 } 1640 } 1641 for (StyledRangeArray::const_iterator iter = mInvalidStaticRanges.begin(); 1642 iter != mInvalidStaticRanges.end();) { 1643 MOZ_ASSERT(iter->mRange->IsStaticRange()); 1644 if (iter->mRange->AsStaticRange()->IsValid()) { 1645 mRanges.AppendElement(*iter); 1646 if (!iter->mRange->IsInSelection(mSelection)) { 1647 iter->mRange->RegisterSelection(mSelection); 1648 } 1649 iter = mInvalidStaticRanges.RemoveElementAt(iter); 1650 } else { 1651 ++iter; 1652 } 1653 } 1654 mInvalidStaticRanges.AppendElements(std::move(invalidStaticRanges)); 1655 } 1656 if (domMutationHasHappened || mRangesMightHaveChanged) { 1657 // This is hot code. Proceed with caution. 1658 // This path uses a cache that keep the last 100 node/index combinations 1659 // in a stack-allocated array to save up on expensive calls to 1660 // nsINode::ComputeIndexOf() (which happen in 1661 // nsContentUtils::ComparePoints()). 1662 // The second expensive call here is the sort() below, which should be 1663 // avoided if possible. Sorting can be avoided if the ranges are still in 1664 // order. Checking the order is cheap compared to sorting (also, it fills up 1665 // the cache, which is reused by the sort call). 1666 nsContentUtils::NodeIndexCache cache; 1667 bool rangeOrderHasChanged = false; 1668 RawRangeBoundary previousStartRef; 1669 for (const StyledRange& range : mRanges) { 1670 if (!previousStartRef.IsSet()) { 1671 previousStartRef = range.mRange->StartRef().AsRaw(); 1672 continue; 1673 } 1674 // Calling ComparePoints here saves one call of 1675 // AbstractRange::StartOffset() per iteration (which is surprisingly 1676 // expensive). 1677 const Maybe<int32_t> compareResult = 1678 StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() 1679 ? nsContentUtils::ComparePoints<TreeKind::Flat>( 1680 range.mRange->StartRef(), previousStartRef, &cache) 1681 : nsContentUtils::ComparePoints<TreeKind::ShadowIncludingDOM>( 1682 range.mRange->StartRef(), previousStartRef, &cache); 1683 // If the nodes are in different subtrees, the Maybe is empty. 1684 // Since CompareToRangeStart pretends ranges to be ordered, this aligns 1685 // to that behavior. 1686 if (compareResult.valueOr(1) != 1) { 1687 rangeOrderHasChanged = true; 1688 break; 1689 } 1690 previousStartRef = range.mRange->StartRef().AsRaw(); 1691 } 1692 if (rangeOrderHasChanged) { 1693 std::function<int32_t(const StyledRange&, const StyledRange&)> compare; 1694 if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { 1695 compare = [&cache](const StyledRange& a, const StyledRange& b) { 1696 return CompareToRangeStart<TreeKind::Flat>(a.mRange->StartRef(), 1697 *b.mRange, &cache); 1698 }; 1699 } else { 1700 compare = [&cache](const StyledRange& a, const StyledRange& b) { 1701 return CompareToRangeStart<TreeKind::ShadowIncludingDOM>( 1702 a.mRange->StartRef(), *b.mRange, &cache); 1703 }; 1704 } 1705 mRanges.Sort(compare); 1706 } 1707 mDocumentGeneration = currentDocumentGeneration; 1708 mRangesMightHaveChanged = false; 1709 } 1710 } 1711 1712 nsresult Selection::StyledRanges::GetIndicesForInterval( 1713 const nsINode* aBeginNode, uint32_t aBeginOffset, const nsINode* aEndNode, 1714 uint32_t aEndOffset, bool aAllowAdjacent, Maybe<size_t>& aStartIndex, 1715 Maybe<size_t>& aEndIndex) { 1716 MOZ_ASSERT(aStartIndex.isNothing()); 1717 MOZ_ASSERT(aEndIndex.isNothing()); 1718 1719 if (NS_WARN_IF(!aBeginNode)) { 1720 return NS_ERROR_INVALID_POINTER; 1721 } 1722 1723 if (NS_WARN_IF(!aEndNode)) { 1724 return NS_ERROR_INVALID_POINTER; 1725 } 1726 1727 ReorderRangesIfNecessary(); 1728 1729 if (mRanges.Length() == 0) { 1730 return NS_OK; 1731 } 1732 1733 const bool intervalIsCollapsed = 1734 aBeginNode == aEndNode && aBeginOffset == aEndOffset; 1735 1736 // Ranges that end before the given interval and begin after the given 1737 // interval can be discarded 1738 size_t endsBeforeIndex = [this, &aEndNode, &aEndOffset]() { 1739 if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { 1740 return FindInsertionPoint( 1741 &mRanges, 1742 ConstRawRangeBoundary(aEndNode, aEndOffset, 1743 RangeBoundaryIsMutationObserved::No), 1744 &CompareToRangeStart<TreeKind::Flat>); 1745 } 1746 return FindInsertionPoint( 1747 &mRanges, 1748 ConstRawRangeBoundary(aEndNode, aEndOffset, 1749 RangeBoundaryIsMutationObserved::No), 1750 &CompareToRangeStart<TreeKind::ShadowIncludingDOM>); 1751 }(); 1752 1753 if (endsBeforeIndex == 0) { 1754 const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange; 1755 1756 // If the interval is strictly before the range at index 0, we can optimize 1757 // by returning now - all ranges start after the given interval 1758 if (!endRange->StartRef().Equals(aEndNode, aEndOffset)) { 1759 return NS_OK; 1760 } 1761 1762 // We now know that the start point of mRanges[0].mRange 1763 // equals the end of the interval. Thus, when aAllowadjacent is true, the 1764 // caller is always interested in this range. However, when excluding 1765 // adjacencies, we must remember to include the range when both it and the 1766 // given interval are collapsed to the same point 1767 if (!aAllowAdjacent && !(endRange->Collapsed() && intervalIsCollapsed)) 1768 return NS_OK; 1769 } 1770 aEndIndex.emplace(endsBeforeIndex); 1771 1772 size_t beginsAfterIndex = [this, &aBeginNode, &aBeginOffset]() { 1773 if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { 1774 return FindInsertionPoint( 1775 &mRanges, 1776 ConstRawRangeBoundary(aBeginNode, aBeginOffset, 1777 RangeBoundaryIsMutationObserved::No), 1778 &CompareToRangeEnd<TreeKind::Flat>); 1779 } 1780 return FindInsertionPoint( 1781 &mRanges, 1782 ConstRawRangeBoundary(aBeginNode, aBeginOffset, 1783 RangeBoundaryIsMutationObserved::No), 1784 &CompareToRangeEnd<TreeKind::ShadowIncludingDOM>); 1785 }(); 1786 1787 if (beginsAfterIndex == mRanges.Length()) { 1788 return NS_OK; // optimization: all ranges are strictly before us 1789 } 1790 1791 if (aAllowAdjacent) { 1792 // At this point, one of the following holds: 1793 // endsBeforeIndex == mRanges.Length(), 1794 // endsBeforeIndex points to a range whose start point does not equal the 1795 // given interval's start point 1796 // endsBeforeIndex points to a range whose start point equals the given 1797 // interval's start point 1798 // In the final case, there can be two such ranges, a collapsed range, and 1799 // an adjacent range (they will appear in mRanges in that 1800 // order). For this final case, we need to increment endsBeforeIndex, until 1801 // one of the first two possibilities hold 1802 while (endsBeforeIndex < mRanges.Length()) { 1803 const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange; 1804 if (!endRange->StartRef().Equals(aEndNode, aEndOffset)) { 1805 break; 1806 } 1807 endsBeforeIndex++; 1808 } 1809 1810 // Likewise, one of the following holds: 1811 // beginsAfterIndex == 0, 1812 // beginsAfterIndex points to a range whose end point does not equal 1813 // the given interval's end point 1814 // beginsOnOrAfter points to a range whose end point equals the given 1815 // interval's end point 1816 // In the final case, there can be two such ranges, an adjacent range, and 1817 // a collapsed range (they will appear in mRanges in that 1818 // order). For this final case, we only need to take action if both those 1819 // ranges exist, and we are pointing to the collapsed range - we need to 1820 // point to the adjacent range 1821 const AbstractRange* beginRange = mRanges[beginsAfterIndex].mRange; 1822 if (beginsAfterIndex > 0 && beginRange->Collapsed() && 1823 beginRange->EndRef().Equals(aBeginNode, aBeginOffset)) { 1824 beginRange = mRanges[beginsAfterIndex - 1].mRange; 1825 if (beginRange->EndRef().Equals(aBeginNode, aBeginOffset)) { 1826 beginsAfterIndex--; 1827 } 1828 } 1829 } else { 1830 // See above for the possibilities at this point. The only case where we 1831 // need to take action is when the range at beginsAfterIndex ends on 1832 // the given interval's start point, but that range isn't collapsed (a 1833 // collapsed range should be included in the returned results). 1834 const AbstractRange* beginRange = mRanges[beginsAfterIndex].mRange; 1835 if (beginRange->MayCrossShadowBoundaryEndRef().Equals(aBeginNode, 1836 aBeginOffset) && 1837 !beginRange->Collapsed()) { 1838 beginsAfterIndex++; 1839 } 1840 1841 // Again, see above for the meaning of endsBeforeIndex at this point. 1842 // In particular, endsBeforeIndex may point to a collaped range which 1843 // represents the point at the end of the interval - this range should be 1844 // included 1845 if (endsBeforeIndex < mRanges.Length()) { 1846 const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange; 1847 if (endRange->MayCrossShadowBoundaryStartRef().Equals(aEndNode, 1848 aEndOffset) && 1849 endRange->Collapsed()) { 1850 endsBeforeIndex++; 1851 } 1852 } 1853 } 1854 1855 NS_ASSERTION(beginsAfterIndex <= endsBeforeIndex, "Is mRanges not ordered?"); 1856 NS_ENSURE_STATE(beginsAfterIndex <= endsBeforeIndex); 1857 1858 aStartIndex.emplace(beginsAfterIndex); 1859 aEndIndex = Some(endsBeforeIndex); 1860 return NS_OK; 1861 } 1862 1863 nsIFrame* Selection::GetPrimaryFrameForAnchorNode() const { 1864 MOZ_ASSERT(mSelectionType == SelectionType::eNormal); 1865 1866 nsCOMPtr<nsIContent> content = do_QueryInterface(GetAnchorNode()); 1867 if (content && mFrameSelection) { 1868 return SelectionMovementUtils::GetFrameForNodeOffset( 1869 content, AnchorOffset(), mFrameSelection->GetHint()); 1870 } 1871 return nullptr; 1872 } 1873 1874 PrimaryFrameData Selection::GetPrimaryFrameForCaretAtFocusNode( 1875 bool aVisual) const { 1876 nsIContent* content = nsIContent::FromNodeOrNull(GetFocusNode()); 1877 if (!content || !mFrameSelection || !mFrameSelection->GetPresShell()) { 1878 return {}; 1879 } 1880 1881 MOZ_ASSERT(mFrameSelection->GetPresShell()->GetDocument() == 1882 content->GetComposedDoc()); 1883 1884 CaretAssociationHint hint = mFrameSelection->GetHint(); 1885 intl::BidiEmbeddingLevel caretBidiLevel = 1886 mFrameSelection->GetCaretBidiLevel(); 1887 return SelectionMovementUtils::GetPrimaryFrameForCaret( 1888 content, FocusOffset(), aVisual, hint, caretBidiLevel); 1889 } 1890 1891 void Selection::SelectFramesOf(nsIContent* aContent, bool aSelected) const { 1892 nsIFrame* frame = aContent->GetPrimaryFrame(); 1893 if (!frame) { 1894 return; 1895 } 1896 // The frame could be an SVG text frame, in which case we don't treat it 1897 // as a text frame. 1898 if (frame->IsTextFrame()) { 1899 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame); 1900 textFrame->SelectionStateChanged( 1901 0, textFrame->CharacterDataBuffer().GetLength(), aSelected, 1902 mSelectionType); 1903 } else { 1904 frame->SelectionStateChanged(); 1905 } 1906 } 1907 1908 nsresult Selection::SelectFramesOfInclusiveDescendantsOfContent( 1909 PostContentIterator& aPostOrderIter, nsIContent* aContent, 1910 bool aSelected) const { 1911 // If aContent doesn't have children, we should avoid to use the content 1912 // iterator for performance reason. 1913 if (!aContent->HasChildren()) { 1914 SelectFramesOf(aContent, aSelected); 1915 return NS_OK; 1916 } 1917 1918 if (NS_WARN_IF(NS_FAILED(aPostOrderIter.Init(aContent)))) { 1919 return NS_ERROR_FAILURE; 1920 } 1921 1922 for (; !aPostOrderIter.IsDone(); aPostOrderIter.Next()) { 1923 nsINode* node = aPostOrderIter.GetCurrentNode(); 1924 MOZ_ASSERT(node); 1925 nsIContent* innercontent = node->IsContent() ? node->AsContent() : nullptr; 1926 SelectFramesOf(innercontent, aSelected); 1927 } 1928 1929 return NS_OK; 1930 } 1931 1932 void Selection::SelectFramesInAllRanges(nsPresContext* aPresContext) { 1933 // this method is currently only called in a user-initiated context. 1934 // therefore it is safe to assume that we are not in a Highlight selection 1935 // and we only have to deal with nsRanges (no StaticRanges). 1936 MOZ_ASSERT(mSelectionType != SelectionType::eHighlight); 1937 for (size_t i = 0; i < mStyledRanges.Length(); ++i) { 1938 nsRange* range = mStyledRanges.mRanges[i].mRange->AsDynamicRange(); 1939 MOZ_ASSERT(range->IsInAnySelection()); 1940 SelectFrames(aPresContext, *range, range->IsInAnySelection()); 1941 } 1942 } 1943 1944 /** 1945 * The idea of this helper method is to select or deselect "top to bottom", 1946 * traversing through the frames 1947 */ 1948 nsresult Selection::SelectFrames(nsPresContext* aPresContext, 1949 AbstractRange& aRange, bool aSelect) const { 1950 if (!mFrameSelection || !aPresContext || !aPresContext->GetPresShell()) { 1951 // nothing to do 1952 return NS_OK; 1953 } 1954 1955 MOZ_DIAGNOSTIC_ASSERT_IF(!aRange.IsPositioned(), 1956 !aRange.MayCrossShadowBoundary()); 1957 1958 MOZ_DIAGNOSTIC_ASSERT(aRange.IsPositioned()); 1959 1960 const Document* const document = GetDocument(); 1961 if (MOZ_UNLIKELY(!document || 1962 aRange.GetComposedDocOfContainers() != document)) { 1963 return NS_OK; // Do nothing if the range is now in different document. 1964 } 1965 1966 if (aRange.IsStaticRange() && !aRange.AsStaticRange()->IsValid()) { 1967 // TODO jjaschke: Actions necessary to unselect invalid static ranges? 1968 return NS_OK; 1969 } 1970 1971 if (mFrameSelection->IsInTableSelectionMode()) { 1972 const nsIContent* const commonAncestorContent = 1973 nsIContent::FromNodeOrNull(aRange.GetClosestCommonInclusiveAncestor( 1974 StaticPrefs::dom_select_events_textcontrols_selectstart_enabled() 1975 ? AllowRangeCrossShadowBoundary::Yes 1976 : AllowRangeCrossShadowBoundary::No)); 1977 nsIFrame* const frame = commonAncestorContent 1978 ? commonAncestorContent->GetPrimaryFrame() 1979 : aPresContext->PresShell()->GetRootFrame(); 1980 if (frame) { 1981 if (frame->IsTextFrame()) { 1982 MOZ_ASSERT(commonAncestorContent == 1983 aRange.GetMayCrossShadowBoundaryStartContainer()); 1984 MOZ_ASSERT(commonAncestorContent == 1985 aRange.GetMayCrossShadowBoundaryEndContainer()); 1986 static_cast<nsTextFrame*>(frame)->SelectionStateChanged( 1987 aRange.MayCrossShadowBoundaryStartOffset(), 1988 aRange.MayCrossShadowBoundaryEndOffset(), aSelect, mSelectionType); 1989 } else { 1990 frame->SelectionStateChanged(); 1991 } 1992 } 1993 1994 return NS_OK; 1995 } 1996 1997 // Loop through the content iterator for each content node; for each text 1998 // node, call SetSelected on it: 1999 nsIContent* const startContent = nsIContent::FromNodeOrNull( 2000 aRange.GetMayCrossShadowBoundaryStartContainer()); 2001 if (MOZ_UNLIKELY(!startContent)) { 2002 // Don't warn, bug 1055722 2003 // XXX The range can start from a document node and such range can be 2004 // added to Selection with JS. Therefore, even in such cases, 2005 // shouldn't we handle selection in the range? 2006 return NS_ERROR_UNEXPECTED; 2007 } 2008 MOZ_DIAGNOSTIC_ASSERT(startContent->IsInComposedDoc()); 2009 2010 // We must call first one explicitly 2011 nsINode* const endNode = aRange.GetMayCrossShadowBoundaryEndContainer(); 2012 if (NS_WARN_IF(!endNode)) { 2013 // We null-checked start node above, therefore, end node should also be 2014 // non-null here. 2015 return NS_ERROR_UNEXPECTED; 2016 } 2017 const bool isFirstContentTextNode = startContent->IsText(); 2018 if (isFirstContentTextNode) { 2019 if (nsIFrame* const frame = startContent->GetPrimaryFrame()) { 2020 // The frame could be an SVG text frame, in which case we don't treat it 2021 // as a text frame. 2022 if (frame->IsTextFrame()) { 2023 const uint32_t startOffset = aRange.MayCrossShadowBoundaryStartOffset(); 2024 const uint32_t endOffset = 2025 endNode == startContent ? aRange.MayCrossShadowBoundaryEndOffset() 2026 : startContent->Length(); 2027 static_cast<nsTextFrame*>(frame)->SelectionStateChanged( 2028 startOffset, endOffset, aSelect, mSelectionType); 2029 } else { 2030 frame->SelectionStateChanged(); 2031 } 2032 } 2033 } 2034 2035 // If the range is in a node and the node is a leaf node, we don't need to 2036 // walk the subtree. 2037 if ((aRange.Collapsed() && !aRange.MayCrossShadowBoundary()) || 2038 (startContent == endNode && !startContent->HasChildren())) { 2039 if (!isFirstContentTextNode) { 2040 SelectFramesOf(startContent, aSelect); 2041 } 2042 return NS_OK; 2043 } 2044 2045 ContentSubtreeIterator subtreeIter; 2046 nsresult rv = subtreeIter.InitWithAllowCrossShadowBoundary(&aRange); 2047 if (NS_FAILED(rv)) { 2048 return rv; 2049 } 2050 if (isFirstContentTextNode && !subtreeIter.IsDone() && 2051 subtreeIter.GetCurrentNode() == startContent) { 2052 subtreeIter.Next(); // first content has already been handled. 2053 } 2054 PostContentIterator postOrderIter; 2055 for (; !subtreeIter.IsDone(); subtreeIter.Next()) { 2056 MOZ_DIAGNOSTIC_ASSERT(subtreeIter.GetCurrentNode()); 2057 if (nsIContent* const content = 2058 nsIContent::FromNodeOrNull(subtreeIter.GetCurrentNode())) { 2059 if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { 2060 SelectFramesOfFlattenedTreeOfContent(content, aSelect); 2061 } else { 2062 SelectFramesOfInclusiveDescendantsOfContent(postOrderIter, content, 2063 aSelect); 2064 } 2065 } 2066 } 2067 2068 // We must now do the last one if it is not the same as the first 2069 if (endNode == startContent || !endNode->IsText()) { 2070 return NS_OK; 2071 } 2072 2073 if (nsIFrame* const frame = endNode->AsText()->GetPrimaryFrame()) { 2074 // The frame could be an SVG text frame, in which case we'll ignore it. 2075 if (frame->IsTextFrame()) { 2076 static_cast<nsTextFrame*>(frame)->SelectionStateChanged( 2077 0, aRange.MayCrossShadowBoundaryEndOffset(), aSelect, mSelectionType); 2078 } 2079 } 2080 return NS_OK; 2081 } 2082 2083 void Selection::SelectFramesOfFlattenedTreeOfContent(nsIContent* aContent, 2084 bool aSelected) const { 2085 MOZ_ASSERT(aContent); 2086 MOZ_ASSERT(StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()); 2087 TreeIterator<FlattenedChildIterator> iter(*aContent); 2088 for (; iter.GetCurrent(); iter.GetNext()) { 2089 SelectFramesOf(iter.GetCurrent(), aSelected); 2090 } 2091 } 2092 2093 // Selection::LookUpSelection 2094 // 2095 // This function is called when a node wants to know where the selection is 2096 // over itself. 2097 // 2098 // Usually, this is called when we already know there is a selection over 2099 // the node in question, and we only need to find the boundaries of it on 2100 // that node. This is when slowCheck is false--a strict test is not needed. 2101 // Other times, the caller has no idea, and wants us to test everything, 2102 // so we are supposed to determine whether there is a selection over the 2103 // node at all. 2104 // 2105 // A previous version of this code used this flag to do less work when 2106 // inclusion was already known (slowCheck=false). However, our tree 2107 // structure allows us to quickly determine ranges overlapping the node, 2108 // so we just ignore the slowCheck flag and do the full test every time. 2109 // 2110 // PERFORMANCE: a common case is that we are doing a fast check with exactly 2111 // one range in the selection. In this case, this function is slower than 2112 // brute force because of the overhead of checking the tree. We can optimize 2113 // this case to make it faster by doing the same thing the previous version 2114 // of this function did in the case of 1 range. This would also mean that 2115 // the aSlowCheck flag would have meaning again. 2116 2117 UniquePtr<SelectionDetails> Selection::LookUpSelection( 2118 nsIContent* aContent, uint32_t aContentOffset, uint32_t aContentLength, 2119 UniquePtr<SelectionDetails> aDetailsHead, SelectionType aSelectionType) { 2120 if (!aContent) { 2121 return aDetailsHead; 2122 } 2123 2124 // it is common to have no ranges, to optimize that 2125 if (mStyledRanges.Length() == 0) { 2126 return aDetailsHead; 2127 } 2128 2129 nsTArray<AbstractRange*> overlappingRanges; 2130 SelectionNodeCache* cache = 2131 GetPresShell() ? GetPresShell()->GetSelectionNodeCache() : nullptr; 2132 if (cache && RangeCount() == 1) { 2133 const bool isFullySelected = 2134 cache->MaybeCollectNodesAndCheckIfFullySelected(aContent, this); 2135 if (isFullySelected) { 2136 auto newHead = MakeUnique<SelectionDetails>(); 2137 2138 newHead->mNext = std::move(aDetailsHead); 2139 newHead->mStart = AssertedCast<int32_t>(0); 2140 newHead->mEnd = AssertedCast<int32_t>(aContentLength); 2141 newHead->mSelectionType = aSelectionType; 2142 newHead->mHighlightData = mHighlightData; 2143 StyledRange* rd = mStyledRanges.FindRangeData(GetAbstractRangeAt(0)); 2144 if (rd) { 2145 newHead->mTextRangeStyle = rd->mTextRangeStyle; 2146 } 2147 auto detailsHead = std::move(newHead); 2148 2149 return detailsHead; 2150 } 2151 } 2152 2153 nsresult rv = GetAbstractRangesForIntervalArray( 2154 aContent, aContentOffset, aContent, aContentOffset + aContentLength, 2155 false, &overlappingRanges); 2156 if (NS_FAILED(rv)) { 2157 return aDetailsHead; 2158 } 2159 2160 if (overlappingRanges.Length() == 0) { 2161 return aDetailsHead; 2162 } 2163 2164 UniquePtr<SelectionDetails> detailsHead = std::move(aDetailsHead); 2165 2166 for (size_t i = 0; i < overlappingRanges.Length(); i++) { 2167 AbstractRange* range = overlappingRanges[i]; 2168 if (range->IsStaticRange() && !range->AsStaticRange()->IsValid()) { 2169 continue; 2170 } 2171 2172 nsINode* startNode = range->GetMayCrossShadowBoundaryStartContainer(); 2173 nsINode* endNode = range->GetMayCrossShadowBoundaryEndContainer(); 2174 uint32_t startOffset = range->MayCrossShadowBoundaryStartOffset(); 2175 uint32_t endOffset = range->MayCrossShadowBoundaryEndOffset(); 2176 2177 Maybe<uint32_t> start, end; 2178 if (startNode == aContent && endNode == aContent) { 2179 if (startOffset < (aContentOffset + aContentLength) && 2180 endOffset > aContentOffset) { 2181 // this range is totally inside the requested content range 2182 start.emplace( 2183 startOffset >= aContentOffset ? startOffset - aContentOffset : 0u); 2184 end.emplace(std::min(aContentLength, endOffset - aContentOffset)); 2185 } 2186 // otherwise, range is inside the requested node, but does not intersect 2187 // the requested content range, so ignore it 2188 } else if (startNode == aContent) { 2189 if (startOffset < (aContentOffset + aContentLength)) { 2190 // the beginning of the range is inside the requested node, but the 2191 // end is outside, select everything from there to the end 2192 start.emplace( 2193 startOffset >= aContentOffset ? startOffset - aContentOffset : 0u); 2194 end.emplace(aContentLength); 2195 } 2196 } else if (endNode == aContent) { 2197 if (endOffset > aContentOffset) { 2198 // the end of the range is inside the requested node, but the beginning 2199 // is outside, select everything from the beginning to there 2200 start.emplace(0u); 2201 end.emplace(std::min(aContentLength, endOffset - aContentOffset)); 2202 } 2203 } else { 2204 // this range does not begin or end in the requested node, but since 2205 // GetRangesForInterval returned this range, we know it overlaps. 2206 // Therefore, this node is enclosed in the range, and we select all 2207 // of it. 2208 start.emplace(0u); 2209 end.emplace(aContentLength); 2210 } 2211 if (start.isNothing()) { 2212 continue; // the ranges do not overlap the input range 2213 } 2214 2215 auto newHead = MakeUnique<SelectionDetails>(); 2216 2217 newHead->mNext = std::move(detailsHead); 2218 newHead->mStart = AssertedCast<int32_t>(*start); 2219 newHead->mEnd = AssertedCast<int32_t>(*end); 2220 newHead->mSelectionType = aSelectionType; 2221 newHead->mHighlightData = mHighlightData; 2222 StyledRange* rd = mStyledRanges.FindRangeData(range); 2223 if (rd) { 2224 newHead->mTextRangeStyle = rd->mTextRangeStyle; 2225 } 2226 detailsHead = std::move(newHead); 2227 } 2228 return detailsHead; 2229 } 2230 2231 NS_IMETHODIMP 2232 Selection::Repaint(nsPresContext* aPresContext) { 2233 int32_t arrCount = (int32_t)mStyledRanges.Length(); 2234 2235 if (arrCount < 1) return NS_OK; 2236 2237 int32_t i; 2238 2239 for (i = 0; i < arrCount; i++) { 2240 MOZ_ASSERT(mStyledRanges.mRanges[i].mRange); 2241 nsresult rv = 2242 SelectFrames(aPresContext, *mStyledRanges.mRanges[i].mRange, true); 2243 2244 if (NS_FAILED(rv)) { 2245 return rv; 2246 } 2247 } 2248 2249 return NS_OK; 2250 } 2251 2252 void Selection::SetCanCacheFrameOffset(bool aCanCacheFrameOffset) { 2253 if (!mCachedOffsetForFrame) { 2254 mCachedOffsetForFrame = new CachedOffsetForFrame; 2255 } 2256 2257 mCachedOffsetForFrame->mCanCacheFrameOffset = aCanCacheFrameOffset; 2258 2259 // clean up cached frame when turn off cache 2260 // fix bug 207936 2261 if (!aCanCacheFrameOffset) { 2262 mCachedOffsetForFrame->mLastCaretFrame = nullptr; 2263 } 2264 } 2265 2266 nsresult Selection::GetCachedFrameOffset(nsIFrame* aFrame, int32_t inOffset, 2267 nsPoint& aPoint) { 2268 if (!mCachedOffsetForFrame) { 2269 mCachedOffsetForFrame = new CachedOffsetForFrame; 2270 } 2271 2272 nsresult rv = NS_OK; 2273 if (mCachedOffsetForFrame->mCanCacheFrameOffset && 2274 mCachedOffsetForFrame->mLastCaretFrame && 2275 (aFrame == mCachedOffsetForFrame->mLastCaretFrame) && 2276 (inOffset == mCachedOffsetForFrame->mLastContentOffset)) { 2277 // get cached frame offset 2278 aPoint = mCachedOffsetForFrame->mCachedFrameOffset; 2279 } else { 2280 // Recalculate frame offset and cache it. Don't cache a frame offset if 2281 // GetPointFromOffset fails, though. 2282 rv = aFrame->GetPointFromOffset(inOffset, &aPoint); 2283 if (NS_SUCCEEDED(rv) && mCachedOffsetForFrame->mCanCacheFrameOffset) { 2284 mCachedOffsetForFrame->mCachedFrameOffset = aPoint; 2285 mCachedOffsetForFrame->mLastCaretFrame = aFrame; 2286 mCachedOffsetForFrame->mLastContentOffset = inOffset; 2287 } 2288 } 2289 2290 return rv; 2291 } 2292 2293 Element* Selection::GetAncestorLimiter() const { 2294 MOZ_ASSERT(mSelectionType == SelectionType::eNormal); 2295 2296 if (mFrameSelection) { 2297 return mFrameSelection->GetAncestorLimiter(); 2298 } 2299 return nullptr; 2300 } 2301 2302 void Selection::SetAncestorLimiter(Element* aLimiter) { 2303 if (NeedsToLogSelectionAPI(*this)) { 2304 LogSelectionAPI(this, __FUNCTION__, "aLimiter", aLimiter); 2305 LogStackForSelectionAPI(); 2306 } 2307 2308 MOZ_ASSERT(mSelectionType == SelectionType::eNormal); 2309 2310 if (mFrameSelection) { 2311 RefPtr<nsFrameSelection> frameSelection = mFrameSelection; 2312 frameSelection->SetAncestorLimiter(aLimiter); 2313 } 2314 } 2315 2316 void Selection::StyledRanges::UnregisterSelection(IsUnlinking aIsUnlinking) { 2317 uint32_t count = mRanges.Length(); 2318 for (uint32_t i = 0; i < count; ++i) { 2319 mRanges[i].mRange->UnregisterSelection(mSelection, aIsUnlinking); 2320 } 2321 } 2322 2323 void Selection::StyledRanges::Clear() { 2324 #ifdef ACCESSIBILITY 2325 for (auto& range : mRanges) { 2326 if (!a11y::SelectionManager::SelectionRangeChanged(mSelection.GetType(), 2327 *range.mRange)) { 2328 break; 2329 } 2330 } 2331 #endif 2332 mRanges.Clear(); 2333 mInvalidStaticRanges.Clear(); 2334 } 2335 2336 StyledRange* Selection::StyledRanges::FindRangeData(AbstractRange* aRange) { 2337 NS_ENSURE_TRUE(aRange, nullptr); 2338 for (uint32_t i = 0; i < mRanges.Length(); i++) { 2339 if (mRanges[i].mRange == aRange) { 2340 return &mRanges[i]; 2341 } 2342 } 2343 return nullptr; 2344 } 2345 2346 Selection::StyledRanges::StyledRangeArray::size_type 2347 Selection::StyledRanges::Length() const { 2348 return mRanges.Length(); 2349 } 2350 2351 nsresult Selection::SetTextRangeStyle(nsRange* aRange, 2352 const TextRangeStyle& aTextRangeStyle) { 2353 NS_ENSURE_ARG_POINTER(aRange); 2354 StyledRange* rd = mStyledRanges.FindRangeData(aRange); 2355 if (rd) { 2356 rd->mTextRangeStyle = aTextRangeStyle; 2357 } 2358 return NS_OK; 2359 } 2360 2361 nsresult Selection::StartAutoScrollTimer(nsIFrame* aFrame, 2362 const nsPoint& aPoint, 2363 uint32_t aDelayInMs) { 2364 MOZ_ASSERT(aFrame, "Need a frame"); 2365 MOZ_ASSERT(mSelectionType == SelectionType::eNormal); 2366 2367 if (!mFrameSelection) { 2368 return NS_OK; // nothing to do 2369 } 2370 2371 if (!mAutoScroller) { 2372 mAutoScroller = new AutoScroller(mFrameSelection); 2373 } 2374 2375 mAutoScroller->SetDelay(aDelayInMs); 2376 2377 RefPtr<AutoScroller> autoScroller{mAutoScroller}; 2378 return autoScroller->DoAutoScroll(aFrame, aPoint); 2379 } 2380 2381 nsresult Selection::StopAutoScrollTimer() { 2382 MOZ_ASSERT(mSelectionType == SelectionType::eNormal); 2383 2384 if (mAutoScroller) { 2385 mAutoScroller->Stop(AutoScroller::FurtherScrollingAllowed::kYes); 2386 } 2387 2388 return NS_OK; 2389 } 2390 2391 nsresult AutoScroller::DoAutoScroll(nsIFrame* aFrame, nsPoint aPoint) { 2392 MOZ_ASSERT(aFrame, "Need a frame"); 2393 2394 Stop(FurtherScrollingAllowed::kYes); 2395 2396 nsPresContext* presContext = aFrame->PresContext(); 2397 RefPtr<PresShell> presShell = presContext->PresShell(); 2398 nsRootPresContext* rootPC = presContext->GetRootPresContext(); 2399 if (!rootPC) { 2400 return NS_OK; 2401 } 2402 nsIFrame* rootmostFrame = rootPC->PresShell()->GetRootFrame(); 2403 AutoWeakFrame weakRootFrame(rootmostFrame); 2404 AutoWeakFrame weakFrame(aFrame); 2405 // Get the point relative to the root most frame because the scroll we are 2406 // about to do will change the coordinates of aFrame. 2407 nsPoint globalPoint = aPoint + aFrame->GetOffsetToCrossDoc(rootmostFrame); 2408 2409 bool done = false; 2410 bool didScroll; 2411 while (true) { 2412 didScroll = presShell->ScrollFrameIntoView( 2413 aFrame, Some(nsRect(aPoint, nsSize())), ScrollAxis(), ScrollAxis(), 2414 ScrollFlags::None); 2415 if (!weakFrame || !weakRootFrame) { 2416 return NS_OK; 2417 } 2418 if (!didScroll && !done) { 2419 // If aPoint is at the very edge of the root, then try to scroll anyway, 2420 // once. 2421 nsRect rootRect = rootmostFrame->GetRect(); 2422 nscoord onePx = AppUnitsPerCSSPixel(); 2423 nscoord scrollAmount = 10 * onePx; 2424 if (std::abs(rootRect.x - globalPoint.x) <= onePx) { 2425 aPoint.x -= scrollAmount; 2426 } else if (std::abs(rootRect.XMost() - globalPoint.x) <= onePx) { 2427 aPoint.x += scrollAmount; 2428 } else if (std::abs(rootRect.y - globalPoint.y) <= onePx) { 2429 aPoint.y -= scrollAmount; 2430 } else if (std::abs(rootRect.YMost() - globalPoint.y) <= onePx) { 2431 aPoint.y += scrollAmount; 2432 } else { 2433 break; 2434 } 2435 done = true; 2436 continue; 2437 } 2438 break; 2439 } 2440 2441 // Start the AutoScroll timer if necessary. 2442 // `ScrollFrameRectIntoView` above may have run script and this may have 2443 // forbidden to continue scrolling. 2444 if (didScroll && mFurtherScrollingAllowed == FurtherScrollingAllowed::kYes) { 2445 nsPoint presContextPoint = 2446 globalPoint - 2447 presShell->GetRootFrame()->GetOffsetToCrossDoc(rootmostFrame); 2448 ScheduleNextDoAutoScroll(presContext, presContextPoint); 2449 } 2450 2451 return NS_OK; 2452 } 2453 2454 void Selection::RemoveAllRanges(ErrorResult& aRv) { 2455 if (NeedsToLogSelectionAPI(*this)) { 2456 LogSelectionAPI(this, __FUNCTION__); 2457 LogStackForSelectionAPI(); 2458 } 2459 2460 RemoveAllRangesInternal(aRv); 2461 } 2462 2463 already_AddRefed<StaticRange> Selection::GetComposedRange( 2464 const AbstractRange* aRange, 2465 const Sequence<OwningNonNull<ShadowRoot>>& aShadowRoots) const { 2466 // If aIsEndNode is true, this method does the Step 5.1 and 5.2 2467 // in https://www.w3.org/TR/selection-api/#dom-selection-getcomposedranges, 2468 // otherwise it does the Step 3.1 and 3.2. 2469 auto reScope = [&aShadowRoots](nsINode*& aNode, uint32_t& aOffset, 2470 bool aIsEndNode) { 2471 MOZ_ASSERT(aNode); 2472 while (aNode) { 2473 const ShadowRoot* shadowRootOfNode = aNode->GetContainingShadow(); 2474 if (!shadowRootOfNode) { 2475 return; 2476 } 2477 2478 for (const OwningNonNull<ShadowRoot>& shadowRoot : aShadowRoots) { 2479 if (shadowRoot->IsShadowIncludingInclusiveDescendantOf( 2480 shadowRootOfNode)) { 2481 return; 2482 } 2483 } 2484 2485 const nsIContent* host = aNode->GetContainingShadowHost(); 2486 const Maybe<uint32_t> maybeIndex = host->ComputeIndexInParentContent(); 2487 MOZ_ASSERT(maybeIndex.isSome(), "not parent or anonymous child?"); 2488 if (MOZ_UNLIKELY(maybeIndex.isNothing())) { 2489 // Unlikely to happen, but still set aNode to nullptr to avoid 2490 // leaking information about the shadow tree. 2491 aNode = nullptr; 2492 return; 2493 } 2494 aOffset = maybeIndex.value(); 2495 if (aIsEndNode) { 2496 aOffset += 1; 2497 } 2498 aNode = host->GetParentNode(); 2499 } 2500 }; 2501 2502 nsINode* startNode = aRange->GetMayCrossShadowBoundaryStartContainer(); 2503 uint32_t startOffset = aRange->MayCrossShadowBoundaryStartOffset(); 2504 nsINode* endNode = aRange->GetMayCrossShadowBoundaryEndContainer(); 2505 uint32_t endOffset = aRange->MayCrossShadowBoundaryEndOffset(); 2506 2507 reScope(startNode, startOffset, false /* aIsEndNode */); 2508 reScope(endNode, endOffset, true /* aIsEndNode */); 2509 2510 RefPtr<StaticRange> composedRange = StaticRange::Create( 2511 startNode, startOffset, endNode, endOffset, IgnoreErrors()); 2512 return composedRange.forget(); 2513 } 2514 2515 void Selection::GetComposedRanges( 2516 const ShadowRootOrGetComposedRangesOptions& 2517 aShadowRootOrGetComposedRangesOptions, 2518 const Sequence<OwningNonNull<ShadowRoot>>& aShadowRoots, 2519 nsTArray<RefPtr<StaticRange>>& aComposedRanges) { 2520 aComposedRanges.SetCapacity(mStyledRanges.mRanges.Length()); 2521 2522 auto GetComposedRangesForAllRanges = 2523 [this, &aComposedRanges]( 2524 const Sequence<OwningNonNull<ShadowRoot>>& aShadowRoots) { 2525 for (const auto& range : this->mStyledRanges.mRanges) { 2526 aComposedRanges.AppendElement( 2527 GetComposedRange(range.mRange, aShadowRoots)); 2528 } 2529 }; 2530 2531 if (aShadowRootOrGetComposedRangesOptions.IsGetComposedRangesOptions()) { 2532 // { shadowRoots: ... } 2533 auto& options = 2534 aShadowRootOrGetComposedRangesOptions.GetAsGetComposedRangesOptions(); 2535 return GetComposedRangesForAllRanges(options.mShadowRoots); 2536 } 2537 2538 Sequence<OwningNonNull<ShadowRoot>> shadowRoots(aShadowRoots); 2539 2540 if (aShadowRootOrGetComposedRangesOptions.IsShadowRoot()) { 2541 // Single shadow root provide 2542 // 2543 // The order in shadowRoots doesn't matter, as we just loop 2544 // through them eventually. 2545 if (!shadowRoots.AppendElement( 2546 aShadowRootOrGetComposedRangesOptions.GetAsShadowRoot(), 2547 fallible)) { 2548 // OOM 2549 return; 2550 } 2551 } 2552 2553 // single + variadic 2554 return GetComposedRangesForAllRanges(shadowRoots); 2555 } 2556 2557 void Selection::RemoveAllRangesInternal(ErrorResult& aRv, 2558 IsUnlinking aIsUnlinking) { 2559 if (!mFrameSelection) { 2560 aRv.Throw(NS_ERROR_NOT_INITIALIZED); 2561 return; 2562 } 2563 2564 RefPtr<nsPresContext> presContext = GetPresContext(); 2565 Clear(presContext, aIsUnlinking); 2566 2567 // Turn off signal for table selection 2568 RefPtr<nsFrameSelection> frameSelection = mFrameSelection; 2569 frameSelection->ClearTableCellSelection(); 2570 2571 RefPtr<Selection> kungFuDeathGrip{this}; 2572 // Be aware, this instance may be destroyed after this call. 2573 NotifySelectionListeners(); 2574 } 2575 2576 void Selection::AddRangeJS(nsRange& aRange, ErrorResult& aRv) { 2577 if (NeedsToLogSelectionAPI(*this)) { 2578 LogSelectionAPI(this, __FUNCTION__, "aRange", aRange); 2579 LogStackForSelectionAPI(); 2580 } 2581 2582 AutoRestore<bool> calledFromJSRestorer(mCalledByJS); 2583 mCalledByJS = true; 2584 RefPtr<Document> document(GetDocument()); 2585 AddRangeAndSelectFramesAndNotifyListenersInternal(aRange, document, aRv); 2586 if (StaticPrefs::dom_selection_mimic_chrome_tostring_enabled() && 2587 !aRv.Failed()) { 2588 if (auto* presShell = GetPresShell()) { 2589 presShell->UpdateLastSelectionForToString(mFrameSelection); 2590 } 2591 } 2592 } 2593 2594 void Selection::AddRangeAndSelectFramesAndNotifyListeners(nsRange& aRange, 2595 ErrorResult& aRv) { 2596 if (NeedsToLogSelectionAPI(*this)) { 2597 LogSelectionAPI(this, __FUNCTION__, "aRange", aRange); 2598 LogStackForSelectionAPI(); 2599 } 2600 2601 RefPtr<Document> document(GetDocument()); 2602 return AddRangeAndSelectFramesAndNotifyListenersInternal(aRange, document, 2603 aRv); 2604 } 2605 2606 void Selection::AddRangeAndSelectFramesAndNotifyListenersInternal( 2607 nsRange& aRange, Document* aDocument, ErrorResult& aRv) { 2608 RefPtr<nsRange> range = &aRange; 2609 if (aRange.IsInAnySelection()) { 2610 if (aRange.IsInSelection(*this)) { 2611 // If we already have the range, we don't need to handle this except 2612 // setting the interline position. 2613 if (mSelectionType == SelectionType::eNormal) { 2614 SetInterlinePosition(InterlinePosition::StartOfNextLine); 2615 } 2616 return; 2617 } 2618 if (mSelectionType != SelectionType::eNormal && 2619 mSelectionType != SelectionType::eHighlight) { 2620 range = aRange.CloneRange(); 2621 } 2622 } 2623 2624 nsINode* rangeRoot = range->GetRoot(); 2625 if (aDocument != rangeRoot && 2626 (!rangeRoot || aDocument != rangeRoot->GetComposedDoc())) { 2627 // http://w3c.github.io/selection-api/#dom-selection-addrange 2628 // "... if the root of the range's boundary points are the document 2629 // associated with context object. Otherwise, this method must do nothing." 2630 return; 2631 } 2632 2633 // MaybeAddTableCellRange might flush frame and `NotifySelectionListeners` 2634 // below might destruct `this`. 2635 RefPtr<Selection> kungFuDeathGrip(this); 2636 2637 // This inserts a table cell range in proper document order 2638 // and returns NS_OK if range doesn't contain just one table cell 2639 Maybe<size_t> maybeRangeIndex; 2640 nsresult result = MaybeAddTableCellRange(*range, &maybeRangeIndex); 2641 if (NS_FAILED(result)) { 2642 aRv.Throw(result); 2643 return; 2644 } 2645 2646 if (maybeRangeIndex.isNothing()) { 2647 result = AddRangesForSelectableNodes(range, &maybeRangeIndex, 2648 DispatchSelectstartEvent::Maybe); 2649 if (NS_FAILED(result)) { 2650 aRv.Throw(result); 2651 return; 2652 } 2653 if (maybeRangeIndex.isNothing()) { 2654 return; 2655 } 2656 } 2657 2658 MOZ_ASSERT(*maybeRangeIndex < mStyledRanges.Length()); 2659 2660 SetAnchorFocusRange(*maybeRangeIndex); 2661 2662 // Make sure the caret appears on the next line, if at a newline 2663 if (mSelectionType == SelectionType::eNormal) { 2664 SetInterlinePosition(InterlinePosition::StartOfNextLine); 2665 } 2666 2667 if (!mFrameSelection) { 2668 return; // nothing to do 2669 } 2670 2671 RefPtr<nsPresContext> presContext = GetPresContext(); 2672 SelectFrames(presContext, *range, true); 2673 2674 // Be aware, this instance may be destroyed after this call. 2675 NotifySelectionListeners(); 2676 // Range order is guaranteed after adding a range. 2677 // Therefore, this flag can be reset to avoid 2678 // another unnecessary and costly reordering. 2679 mStyledRanges.mRangesMightHaveChanged = false; 2680 } 2681 2682 void Selection::AddHighlightRangeAndSelectFramesAndNotifyListeners( 2683 AbstractRange& aRange) { 2684 MOZ_ASSERT(mSelectionType == SelectionType::eHighlight); 2685 nsresult rv = mStyledRanges.AddRangeAndIgnoreOverlaps(&aRange); 2686 if (NS_FAILED(rv)) { 2687 return; 2688 } 2689 2690 if (!mFrameSelection) { 2691 return; // nothing to do 2692 } 2693 2694 RefPtr<nsPresContext> presContext = GetPresContext(); 2695 SelectFrames(presContext, aRange, true); 2696 2697 // Be aware, this instance may be destroyed after this call. 2698 RefPtr<Selection> kungFuDeathGrip(this); 2699 NotifySelectionListeners(); 2700 // Range order is guaranteed after adding a range. 2701 // Therefore, this flag can be reset to avoid 2702 // another unnecessary and costly reordering. 2703 mStyledRanges.mRangesMightHaveChanged = false; 2704 } 2705 2706 // Selection::RemoveRangeAndUnselectFramesAndNotifyListeners 2707 // 2708 // Removes the given range from the selection. The tricky part is updating 2709 // the flags on the frames that indicate whether they have a selection or 2710 // not. There could be several selection ranges on the frame, and clearing 2711 // the bit would cause the selection to not be drawn, even when there is 2712 // another range on the frame (bug 346185). 2713 // 2714 // We therefore find any ranges that intersect the same nodes as the range 2715 // being removed, and cause them to set the selected bits back on their 2716 // selected frames after we've cleared the bit from ours. 2717 2718 void Selection::RemoveRangeAndUnselectFramesAndNotifyListeners( 2719 AbstractRange& aRange, ErrorResult& aRv) { 2720 if (NeedsToLogSelectionAPI(*this)) { 2721 LogSelectionAPI(this, __FUNCTION__, "aRange", aRange); 2722 LogStackForSelectionAPI(); 2723 } 2724 2725 nsresult rv = mStyledRanges.RemoveRangeAndUnregisterSelection(aRange); 2726 if (NS_FAILED(rv)) { 2727 aRv.Throw(rv); 2728 return; 2729 } 2730 2731 nsINode* beginNode = aRange.GetStartContainer(); 2732 nsINode* endNode = aRange.GetEndContainer(); 2733 2734 if (!beginNode || !endNode) { 2735 // Detached range; nothing else to do here. 2736 return; 2737 } 2738 2739 // find out the length of the end node, so we can select all of it 2740 uint32_t beginOffset, endOffset; 2741 if (endNode->IsText()) { 2742 // Get the length of the text. We can't just use the offset because 2743 // another range could be touching this text node but not intersect our 2744 // range. 2745 beginOffset = 0; 2746 endOffset = endNode->AsText()->TextLength(); 2747 } else { 2748 // For non-text nodes, the given offsets should be sufficient. 2749 beginOffset = aRange.StartOffset(); 2750 endOffset = aRange.EndOffset(); 2751 } 2752 2753 // clear the selected bit from the removed range's frames 2754 RefPtr<nsPresContext> presContext = GetPresContext(); 2755 SelectFrames(presContext, aRange, false); 2756 2757 // add back the selected bit for each range touching our nodes 2758 nsTArray<AbstractRange*> affectedRanges; 2759 rv = GetAbstractRangesForIntervalArray(beginNode, beginOffset, endNode, 2760 endOffset, true, &affectedRanges); 2761 if (NS_FAILED(rv)) { 2762 aRv.Throw(rv); 2763 return; 2764 } 2765 for (uint32_t i = 0; i < affectedRanges.Length(); i++) { 2766 MOZ_ASSERT(affectedRanges[i]); 2767 SelectFrames(presContext, *affectedRanges[i], true); 2768 } 2769 2770 if (&aRange == mAnchorFocusRange) { 2771 const size_t rangeCount = mStyledRanges.Length(); 2772 if (rangeCount) { 2773 SetAnchorFocusRange(rangeCount - 1); 2774 } else { 2775 RemoveAnchorFocusRange(); 2776 } 2777 2778 // When the selection is user-created it makes sense to scroll the range 2779 // into view. The spell-check selection, however, is created and destroyed 2780 // in the background. We don't want to scroll in this case or the view 2781 // might appear to be moving randomly (bug 337871). 2782 if (mSelectionType != SelectionType::eSpellCheck && rangeCount) { 2783 ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION); 2784 } 2785 } 2786 2787 if (!mFrameSelection) return; // nothing to do 2788 2789 RefPtr<Selection> kungFuDeathGrip{this}; 2790 // Be aware, this instance may be destroyed after this call. 2791 NotifySelectionListeners(); 2792 } 2793 2794 /* 2795 * Collapse sets the whole selection to be one point. 2796 */ 2797 void Selection::CollapseJS(nsINode* aContainer, uint32_t aOffset, 2798 ErrorResult& aRv) { 2799 if (NeedsToLogSelectionAPI(*this)) { 2800 LogSelectionAPI(this, __FUNCTION__, "aContainer", aContainer, "aOffset", 2801 aOffset); 2802 LogStackForSelectionAPI(); 2803 } 2804 2805 AutoRestore<bool> calledFromJSRestorer(mCalledByJS); 2806 mCalledByJS = true; 2807 if (!aContainer) { 2808 RemoveAllRangesInternal(aRv); 2809 return; 2810 } 2811 CollapseInternal(InLimiter::eNo, RawRangeBoundary(aContainer, aOffset), aRv); 2812 } 2813 2814 void Selection::CollapseInLimiter(const RawRangeBoundary& aPoint, 2815 ErrorResult& aRv) { 2816 if (NeedsToLogSelectionAPI(*this)) { 2817 LogSelectionAPI(this, __FUNCTION__, "aPoint", aPoint); 2818 LogStackForSelectionAPI(); 2819 } 2820 2821 CollapseInternal(InLimiter::eYes, aPoint, aRv); 2822 } 2823 2824 void Selection::CollapseInternal(InLimiter aInLimiter, 2825 const RawRangeBoundary& aPoint, 2826 ErrorResult& aRv) { 2827 if (!mFrameSelection) { 2828 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection 2829 return; 2830 } 2831 2832 if (!aPoint.IsSet()) { 2833 aRv.Throw(NS_ERROR_INVALID_ARG); 2834 return; 2835 } 2836 2837 if (aPoint.GetContainer()->NodeType() == nsINode::DOCUMENT_TYPE_NODE) { 2838 aRv.ThrowInvalidNodeTypeError(kNoDocumentTypeNodeError); 2839 return; 2840 } 2841 2842 // RawRangeBoundary::IsSetAndValid() checks if the point actually refers 2843 // a child of the container when IsSet() is true. If its offset hasn't been 2844 // computed yet, this just checks it with its mRef. So, we can avoid 2845 // computing offset here. 2846 if (!aPoint.IsSetAndValid()) { 2847 aRv.ThrowIndexSizeError("The offset is out of range."); 2848 return; 2849 } 2850 2851 if (!HasSameRootOrSameComposedDoc(*aPoint.GetContainer())) { 2852 // Return with no error 2853 return; 2854 } 2855 2856 RefPtr<nsFrameSelection> frameSelection = mFrameSelection; 2857 frameSelection->InvalidateDesiredCaretPos(); 2858 if (aInLimiter == InLimiter::eYes && 2859 !frameSelection->NodeIsInLimiters(aPoint.GetContainer())) { 2860 aRv.Throw(NS_ERROR_FAILURE); 2861 return; 2862 } 2863 nsresult result; 2864 2865 RefPtr<nsPresContext> presContext = GetPresContext(); 2866 if (!presContext || 2867 presContext->Document() != aPoint.GetContainer()->OwnerDoc()) { 2868 aRv.Throw(NS_ERROR_FAILURE); 2869 return; 2870 } 2871 2872 // Delete all of the current ranges 2873 Clear(presContext); 2874 2875 // Turn off signal for table selection 2876 frameSelection->ClearTableCellSelection(); 2877 2878 // Hack to display the caret on the right line (bug 1237236). 2879 frameSelection->SetHint(ComputeCaretAssociationHint( 2880 frameSelection->GetHint(), frameSelection->GetCaretBidiLevel(), aPoint)); 2881 2882 RefPtr<nsRange> range = nsRange::Create(aPoint.GetContainer()); 2883 result = range->CollapseTo(aPoint); 2884 if (NS_FAILED(result)) { 2885 aRv.Throw(result); 2886 return; 2887 } 2888 2889 #ifdef DEBUG_SELECTION 2890 nsCOMPtr<nsIContent> content = do_QueryInterface(aPoint.GetContainer()); 2891 nsCOMPtr<Document> doc = do_QueryInterface(aPoint.GetContainer()); 2892 printf("Sel. Collapse to %p %s %d\n", container.get(), 2893 content ? nsAtomCString(content->NodeInfo()->NameAtom()).get() 2894 : (doc ? "DOCUMENT" : "???"), 2895 aPoint.Offset()); 2896 #endif 2897 2898 Maybe<size_t> maybeRangeIndex; 2899 result = AddRangesForSelectableNodes(range, &maybeRangeIndex, 2900 DispatchSelectstartEvent::Maybe); 2901 if (NS_FAILED(result)) { 2902 aRv.Throw(result); 2903 return; 2904 } 2905 SetAnchorFocusRange(0); 2906 SelectFrames(presContext, *range, true); 2907 2908 RefPtr<Selection> kungFuDeathGrip{this}; 2909 // Be aware, this instance may be destroyed after this call. 2910 NotifySelectionListeners(); 2911 } 2912 2913 /* 2914 * Sets the whole selection to be one point 2915 * at the start of the current selection 2916 */ 2917 void Selection::CollapseToStartJS(ErrorResult& aRv) { 2918 if (NeedsToLogSelectionAPI(*this)) { 2919 LogSelectionAPI(this, __FUNCTION__); 2920 LogStackForSelectionAPI(); 2921 } 2922 2923 AutoRestore<bool> calledFromJSRestorer(mCalledByJS); 2924 mCalledByJS = true; 2925 CollapseToStart(aRv); 2926 } 2927 2928 void Selection::CollapseToStart(ErrorResult& aRv) { 2929 if (!mCalledByJS && NeedsToLogSelectionAPI(*this)) { 2930 LogSelectionAPI(this, __FUNCTION__); 2931 LogStackForSelectionAPI(); 2932 } 2933 2934 if (RangeCount() == 0) { 2935 aRv.ThrowInvalidStateError(kNoRangeExistsError); 2936 return; 2937 } 2938 2939 // Get the first range 2940 const AbstractRange* firstRange = mStyledRanges.mRanges[0].mRange; 2941 if (!firstRange) { 2942 aRv.Throw(NS_ERROR_FAILURE); 2943 return; 2944 } 2945 2946 if (mFrameSelection) { 2947 mFrameSelection->AddChangeReasons( 2948 nsISelectionListener::COLLAPSETOSTART_REASON); 2949 } 2950 nsINode* container = firstRange->GetStartContainer(); 2951 if (!container) { 2952 aRv.Throw(NS_ERROR_FAILURE); 2953 return; 2954 } 2955 CollapseInternal(InLimiter::eNo, 2956 RawRangeBoundary(container, firstRange->StartOffset()), aRv); 2957 } 2958 2959 /* 2960 * Sets the whole selection to be one point 2961 * at the end of the current selection 2962 */ 2963 void Selection::CollapseToEndJS(ErrorResult& aRv) { 2964 if (NeedsToLogSelectionAPI(*this)) { 2965 LogSelectionAPI(this, __FUNCTION__); 2966 LogStackForSelectionAPI(); 2967 } 2968 2969 AutoRestore<bool> calledFromJSRestorer(mCalledByJS); 2970 mCalledByJS = true; 2971 CollapseToEnd(aRv); 2972 } 2973 2974 void Selection::CollapseToEnd(ErrorResult& aRv) { 2975 if (!mCalledByJS && NeedsToLogSelectionAPI(*this)) { 2976 LogSelectionAPI(this, __FUNCTION__); 2977 LogStackForSelectionAPI(); 2978 } 2979 2980 uint32_t cnt = RangeCount(); 2981 if (cnt == 0) { 2982 aRv.ThrowInvalidStateError(kNoRangeExistsError); 2983 return; 2984 } 2985 2986 // Get the last range 2987 const AbstractRange* lastRange = mStyledRanges.mRanges[cnt - 1].mRange; 2988 if (!lastRange) { 2989 aRv.Throw(NS_ERROR_FAILURE); 2990 return; 2991 } 2992 2993 if (mFrameSelection) { 2994 mFrameSelection->AddChangeReasons( 2995 nsISelectionListener::COLLAPSETOEND_REASON); 2996 } 2997 nsINode* container = lastRange->GetEndContainer(); 2998 if (!container) { 2999 aRv.Throw(NS_ERROR_FAILURE); 3000 return; 3001 } 3002 CollapseInternal(InLimiter::eNo, 3003 RawRangeBoundary(container, lastRange->EndOffset()), aRv); 3004 } 3005 3006 void Selection::GetType(nsAString& aOutType) const { 3007 if (!RangeCount()) { 3008 aOutType.AssignLiteral("None"); 3009 } else if (IsCollapsed()) { 3010 aOutType.AssignLiteral("Caret"); 3011 } else { 3012 aOutType.AssignLiteral("Range"); 3013 } 3014 } 3015 3016 nsRange* Selection::GetRangeAt(uint32_t aIndex, ErrorResult& aRv) { 3017 nsRange* range = GetRangeAt(aIndex); 3018 if (!range) { 3019 aRv.ThrowIndexSizeError(nsPrintfCString("%u is out of range", aIndex)); 3020 return nullptr; 3021 } 3022 3023 return range; 3024 } 3025 3026 AbstractRange* Selection::GetAbstractRangeAt(uint32_t aIndex) const { 3027 StyledRange empty(nullptr); 3028 return mStyledRanges.mRanges.SafeElementAt(aIndex, empty).mRange; 3029 } 3030 3031 // https://www.w3.org/TR/selection-api/#dom-selection-direction 3032 void Selection::GetDirection(nsAString& aDirection) const { 3033 if (mStyledRanges.mRanges.IsEmpty() || 3034 (mFrameSelection && (mFrameSelection->IsDoubleClickSelection() || 3035 mFrameSelection->IsTripleClickSelection()))) { 3036 // Empty range and double/triple clicks result a directionless selection. 3037 aDirection.AssignLiteral("none"); 3038 } else if (mDirection == nsDirection::eDirNext) { 3039 // This is the default direction. It could be that the direction 3040 // is really "forward", or the direction is "none" if the selection 3041 // is collapsed. 3042 if (AreNormalAndCrossShadowBoundaryRangesCollapsed()) { 3043 aDirection.AssignLiteral("none"); 3044 return; 3045 } 3046 aDirection.AssignLiteral("forward"); 3047 } else { 3048 MOZ_ASSERT(!AreNormalAndCrossShadowBoundaryRangesCollapsed()); 3049 aDirection.AssignLiteral("backward"); 3050 } 3051 } 3052 3053 nsRange* Selection::GetRangeAt(uint32_t aIndex) const { 3054 // This method per IDL spec returns a dynamic range. 3055 // Therefore, it must be ensured that it is only called 3056 // for a selection which contains dynamic ranges exclusively. 3057 // Highlight Selections are allowed to contain StaticRanges, 3058 // therefore this method must not be called. 3059 MOZ_ASSERT(mSelectionType != SelectionType::eHighlight); 3060 AbstractRange* abstractRange = GetAbstractRangeAt(aIndex); 3061 if (!abstractRange) { 3062 return nullptr; 3063 } 3064 return abstractRange->AsDynamicRange(); 3065 } 3066 3067 nsresult Selection::SetAnchorFocusToRange(nsRange* aRange) { 3068 NS_ENSURE_STATE(mAnchorFocusRange); 3069 3070 const DispatchSelectstartEvent dispatchSelectstartEvent = 3071 IsCollapsed() ? DispatchSelectstartEvent::Maybe 3072 : DispatchSelectstartEvent::No; 3073 3074 nsresult rv = 3075 mStyledRanges.RemoveRangeAndUnregisterSelection(*mAnchorFocusRange); 3076 if (NS_FAILED(rv)) { 3077 return rv; 3078 } 3079 3080 Maybe<size_t> maybeOutIndex; 3081 rv = AddRangesForSelectableNodes(aRange, &maybeOutIndex, 3082 dispatchSelectstartEvent); 3083 if (NS_FAILED(rv)) { 3084 return rv; 3085 } 3086 if (maybeOutIndex.isSome()) { 3087 SetAnchorFocusRange(*maybeOutIndex); 3088 } else { 3089 RemoveAnchorFocusRange(); 3090 } 3091 3092 return NS_OK; 3093 } 3094 3095 void Selection::ReplaceAnchorFocusRange(nsRange* aRange) { 3096 NS_ENSURE_TRUE_VOID(mAnchorFocusRange); 3097 RefPtr<nsPresContext> presContext = GetPresContext(); 3098 if (presContext) { 3099 SelectFrames(presContext, *mAnchorFocusRange, false); 3100 SetAnchorFocusToRange(aRange); 3101 SelectFrames(presContext, *mAnchorFocusRange, true); 3102 } 3103 } 3104 3105 void Selection::AdjustAnchorFocusForMultiRange(nsDirection aDirection) { 3106 if (aDirection == mDirection) { 3107 return; 3108 } 3109 SetDirection(aDirection); 3110 3111 if (RangeCount() <= 1) { 3112 return; 3113 } 3114 3115 nsRange* firstRange = GetRangeAt(0); 3116 nsRange* lastRange = GetRangeAt(RangeCount() - 1); 3117 3118 if (mDirection == eDirPrevious) { 3119 firstRange->SetIsGenerated(false); 3120 lastRange->SetIsGenerated(true); 3121 SetAnchorFocusRange(0); 3122 } else { // aDir == eDirNext 3123 firstRange->SetIsGenerated(true); 3124 lastRange->SetIsGenerated(false); 3125 SetAnchorFocusRange(RangeCount() - 1); 3126 } 3127 } 3128 3129 /* 3130 * Extend extends the selection away from the anchor. 3131 * We don't need to know the direction, because we always change the focus. 3132 */ 3133 void Selection::ExtendJS(nsINode& aContainer, uint32_t aOffset, 3134 ErrorResult& aRv) { 3135 if (NeedsToLogSelectionAPI(*this)) { 3136 LogSelectionAPI(this, __FUNCTION__, "aContainer", &aContainer, "aOffset", 3137 aOffset); 3138 LogStackForSelectionAPI(); 3139 } 3140 3141 AutoRestore<bool> calledFromJSRestorer(mCalledByJS); 3142 mCalledByJS = true; 3143 Extend(aContainer, aOffset, aRv); 3144 } 3145 3146 nsresult Selection::Extend(nsINode* aContainer, uint32_t aOffset) { 3147 if (NeedsToLogSelectionAPI(*this)) { 3148 LogSelectionAPI(this, __FUNCTION__, "aContainer", aContainer, "aOffset", 3149 aOffset); 3150 LogStackForSelectionAPI(); 3151 } 3152 3153 if (!aContainer) { 3154 return NS_ERROR_INVALID_ARG; 3155 } 3156 3157 ErrorResult result; 3158 Extend(*aContainer, aOffset, result); 3159 return result.StealNSResult(); 3160 } 3161 3162 void Selection::Extend(nsINode& aContainer, uint32_t aOffset, 3163 ErrorResult& aRv) { 3164 /* 3165 Notes which might come in handy for extend: 3166 3167 We can tell the direction of the selection by asking for the anchors 3168 selection if the begin is less than the end then we know the selection is to 3169 the "right", else it is a backwards selection. Notation: a = anchor, 1 = old 3170 cursor, 2 = new cursor. 3171 3172 if (a <= 1 && 1 <=2) a,1,2 or (a1,2) 3173 if (a < 2 && 1 > 2) a,2,1 3174 if (1 < a && a <2) 1,a,2 3175 if (a > 2 && 2 >1) 1,2,a 3176 if (2 < a && a <1) 2,a,1 3177 if (a > 1 && 1 >2) 2,1,a 3178 then execute 3179 a 1 2 select from 1 to 2 3180 a 2 1 deselect from 2 to 1 3181 1 a 2 deselect from 1 to a select from a to 2 3182 1 2 a deselect from 1 to 2 3183 2 1 a = continue selection from 2 to 1 3184 */ 3185 3186 // First, find the range containing the old focus point: 3187 if (!mAnchorFocusRange) { 3188 aRv.ThrowInvalidStateError(kNoRangeExistsError); 3189 return; 3190 } 3191 3192 if (!mFrameSelection) { 3193 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection 3194 return; 3195 } 3196 3197 if (!HasSameRootOrSameComposedDoc(aContainer)) { 3198 // Return with no error 3199 return; 3200 } 3201 3202 if (!mFrameSelection->NodeIsInLimiters(&aContainer)) { 3203 aRv.Throw(NS_ERROR_FAILURE); 3204 return; 3205 } 3206 3207 if (aContainer.GetFrameSelection() != mFrameSelection) { 3208 NS_ASSERTION( 3209 false, 3210 nsFmtCString( 3211 FMT_STRING("mFrameSelection is {} which is expected as " 3212 "aContainer.GetFrameSelection() ({})"), 3213 mozilla::ToString(mFrameSelection).c_str(), 3214 mozilla::ToString(RefPtr{aContainer.GetFrameSelection()}).c_str()) 3215 .get()); 3216 aRv.Throw(NS_ERROR_FAILURE); 3217 return; 3218 } 3219 3220 RefPtr<nsPresContext> presContext = GetPresContext(); 3221 if (!presContext || presContext->Document() != aContainer.OwnerDoc()) { 3222 aRv.Throw(NS_ERROR_FAILURE); 3223 return; 3224 } 3225 3226 #ifdef DEBUG_SELECTION 3227 nsDirection oldDirection = GetDirection(); 3228 #endif 3229 nsINode* anchorNode = GetMayCrossShadowBoundaryAnchorNode(); 3230 nsINode* focusNode = GetMayCrossShadowBoundaryFocusNode(); 3231 const uint32_t anchorOffset = MayCrossShadowBoundaryAnchorOffset(); 3232 const uint32_t focusOffset = MayCrossShadowBoundaryFocusOffset(); 3233 3234 RefPtr<nsRange> range = mAnchorFocusRange->CloneRange(); 3235 3236 nsINode* startNode = range->GetMayCrossShadowBoundaryStartContainer(); 3237 nsINode* endNode = range->GetMayCrossShadowBoundaryEndContainer(); 3238 const uint32_t startOffset = range->MayCrossShadowBoundaryStartOffset(); 3239 const uint32_t endOffset = range->MayCrossShadowBoundaryEndOffset(); 3240 3241 bool shouldClearRange = false; 3242 3243 auto ComparePoints = [](const nsINode* aNode1, const uint32_t aOffset1, 3244 const nsINode* aNode2, const uint32_t aOffset2) { 3245 if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { 3246 return nsContentUtils::ComparePointsWithIndices<TreeKind::Flat>( 3247 aNode1, aOffset1, aNode2, aOffset2); 3248 } 3249 return nsContentUtils::ComparePointsWithIndices< 3250 TreeKind::ShadowIncludingDOM>(aNode1, aOffset1, aNode2, aOffset2); 3251 }; 3252 const Maybe<int32_t> anchorOldFocusOrder = 3253 ComparePoints(anchorNode, anchorOffset, focusNode, focusOffset); 3254 shouldClearRange |= !anchorOldFocusOrder; 3255 const Maybe<int32_t> oldFocusNewFocusOrder = 3256 ComparePoints(focusNode, focusOffset, &aContainer, aOffset); 3257 shouldClearRange |= !oldFocusNewFocusOrder; 3258 const Maybe<int32_t> anchorNewFocusOrder = 3259 ComparePoints(anchorNode, anchorOffset, &aContainer, aOffset); 3260 shouldClearRange |= !anchorNewFocusOrder; 3261 3262 // If the points are disconnected, the range will be collapsed below, 3263 // resulting in a range that selects nothing. 3264 nsresult res; 3265 if (shouldClearRange) { 3266 // Repaint the current range with the selection removed. 3267 SelectFrames(presContext, *range, false); 3268 3269 res = range->CollapseTo(&aContainer, aOffset); 3270 if (NS_FAILED(res)) { 3271 aRv.Throw(res); 3272 return; 3273 } 3274 3275 res = SetAnchorFocusToRange(range); 3276 if (NS_FAILED(res)) { 3277 aRv.Throw(res); 3278 return; 3279 } 3280 } else { 3281 RefPtr<nsRange> difRange = nsRange::Create(&aContainer); 3282 if ((*anchorOldFocusOrder == 0 && *anchorNewFocusOrder < 0) || 3283 (*anchorOldFocusOrder <= 0 && 3284 *oldFocusNewFocusOrder < 0)) { // a1,2 a,1,2 3285 // select from 1 to 2 unless they are collapsed 3286 range->SetEnd(aContainer, aOffset, aRv, 3287 AllowRangeCrossShadowBoundary::Yes); 3288 if (aRv.Failed()) { 3289 return; 3290 } 3291 SetDirection(eDirNext); 3292 res = difRange->SetStartAndEnd( 3293 focusNode, focusOffset, 3294 range->GetMayCrossShadowBoundaryEndContainer(), 3295 range->MayCrossShadowBoundaryEndOffset(), 3296 AllowRangeCrossShadowBoundary::Yes); 3297 if (NS_FAILED(res)) { 3298 aRv.Throw(res); 3299 return; 3300 } 3301 SelectFrames(presContext, *difRange, true); 3302 res = SetAnchorFocusToRange(range); 3303 if (NS_FAILED(res)) { 3304 aRv.Throw(res); 3305 return; 3306 } 3307 } else if (*anchorOldFocusOrder == 0 && 3308 *anchorNewFocusOrder > 0) { // 2, a1 3309 // select from 2 to 1a 3310 SetDirection(eDirPrevious); 3311 range->SetStart(aContainer, aOffset, aRv, 3312 AllowRangeCrossShadowBoundary::Yes); 3313 if (aRv.Failed()) { 3314 return; 3315 } 3316 SelectFrames(presContext, *range, true); 3317 res = SetAnchorFocusToRange(range); 3318 if (NS_FAILED(res)) { 3319 aRv.Throw(res); 3320 return; 3321 } 3322 } else if (*anchorNewFocusOrder <= 0 && 3323 *oldFocusNewFocusOrder >= 0) { // a,2,1 or a2,1 or a,21 or a21 3324 // deselect from 2 to 1 3325 res = 3326 difRange->SetStartAndEnd(&aContainer, aOffset, focusNode, focusOffset, 3327 AllowRangeCrossShadowBoundary::Yes); 3328 if (NS_FAILED(res)) { 3329 aRv.Throw(res); 3330 return; 3331 } 3332 3333 range->SetEnd(aContainer, aOffset, aRv, 3334 AllowRangeCrossShadowBoundary::Yes); 3335 if (aRv.Failed()) { 3336 return; 3337 } 3338 res = SetAnchorFocusToRange(range); 3339 if (NS_FAILED(res)) { 3340 aRv.Throw(res); 3341 return; 3342 } 3343 SelectFrames(presContext, *difRange, false); // deselect now 3344 difRange->SetEnd(range->GetMayCrossShadowBoundaryEndContainer(), 3345 range->MayCrossShadowBoundaryEndOffset(), 3346 AllowRangeCrossShadowBoundary::Yes); 3347 SelectFrames(presContext, *difRange, true); // must reselect last node 3348 // maybe more 3349 } else if (*anchorOldFocusOrder >= 0 && 3350 *anchorNewFocusOrder <= 0) { // 1,a,2 or 1a,2 or 1,a2 or 1a2 3351 if (GetDirection() == eDirPrevious) { 3352 res = range->SetStart(endNode, endOffset, 3353 AllowRangeCrossShadowBoundary::Yes); 3354 if (NS_FAILED(res)) { 3355 aRv.Throw(res); 3356 return; 3357 } 3358 } 3359 SetDirection(eDirNext); 3360 range->SetEnd(aContainer, aOffset, aRv, 3361 AllowRangeCrossShadowBoundary::Yes); 3362 if (aRv.Failed()) { 3363 return; 3364 } 3365 if (focusNode != anchorNode || 3366 focusOffset != anchorOffset) { // if collapsed diff dont do anything 3367 res = difRange->SetStart(focusNode, focusOffset, 3368 AllowRangeCrossShadowBoundary::Yes); 3369 nsresult tmp = difRange->SetEnd(anchorNode, anchorOffset, 3370 AllowRangeCrossShadowBoundary::Yes); 3371 if (NS_FAILED(tmp)) { 3372 res = tmp; 3373 } 3374 if (NS_FAILED(res)) { 3375 aRv.Throw(res); 3376 return; 3377 } 3378 res = SetAnchorFocusToRange(range); 3379 if (NS_FAILED(res)) { 3380 aRv.Throw(res); 3381 return; 3382 } 3383 // deselect from 1 to a 3384 SelectFrames(presContext, *difRange, false); 3385 } else { 3386 res = SetAnchorFocusToRange(range); 3387 if (NS_FAILED(res)) { 3388 aRv.Throw(res); 3389 return; 3390 } 3391 } 3392 // select from a to 2 3393 SelectFrames(presContext, *range, true); 3394 } else if (*oldFocusNewFocusOrder <= 0 && 3395 *anchorNewFocusOrder >= 0) { // 1,2,a or 12,a or 1,2a or 12a 3396 // deselect from 1 to 2 3397 res = 3398 difRange->SetStartAndEnd(focusNode, focusOffset, &aContainer, aOffset, 3399 AllowRangeCrossShadowBoundary::Yes); 3400 if (NS_FAILED(res)) { 3401 aRv.Throw(res); 3402 return; 3403 } 3404 3405 SetDirection(eDirPrevious); 3406 range->SetStart(aContainer, aOffset, aRv, 3407 AllowRangeCrossShadowBoundary::Yes); 3408 if (aRv.Failed()) { 3409 return; 3410 } 3411 3412 res = SetAnchorFocusToRange(range); 3413 if (NS_FAILED(res)) { 3414 aRv.Throw(res); 3415 return; 3416 } 3417 SelectFrames(presContext, *difRange, false); 3418 difRange->SetStart(range->GetMayCrossShadowBoundaryStartContainer(), 3419 range->MayCrossShadowBoundaryStartOffset(), 3420 AllowRangeCrossShadowBoundary::Yes); 3421 SelectFrames(presContext, *difRange, true); // must reselect last node 3422 } else if (*anchorNewFocusOrder >= 0 && 3423 *anchorOldFocusOrder <= 0) { // 2,a,1 or 2a,1 or 2,a1 or 2a1 3424 if (GetDirection() == eDirNext) { 3425 range->SetEnd(startNode, startOffset, 3426 AllowRangeCrossShadowBoundary::Yes); 3427 } 3428 SetDirection(eDirPrevious); 3429 range->SetStart(aContainer, aOffset, aRv, 3430 AllowRangeCrossShadowBoundary::Yes); 3431 if (aRv.Failed()) { 3432 return; 3433 } 3434 // deselect from a to 1 3435 if (focusNode != anchorNode || 3436 focusOffset != anchorOffset) { // if collapsed diff dont do anything 3437 res = difRange->SetStartAndEnd(anchorNode, anchorOffset, focusNode, 3438 focusOffset, 3439 AllowRangeCrossShadowBoundary::Yes); 3440 nsresult tmp = SetAnchorFocusToRange(range); 3441 if (NS_FAILED(tmp)) { 3442 res = tmp; 3443 } 3444 if (NS_FAILED(res)) { 3445 aRv.Throw(res); 3446 return; 3447 } 3448 SelectFrames(presContext, *difRange, false); 3449 } else { 3450 res = SetAnchorFocusToRange(range); 3451 if (NS_FAILED(res)) { 3452 aRv.Throw(res); 3453 return; 3454 } 3455 } 3456 // select from 2 to a 3457 SelectFrames(presContext, *range, true); 3458 } else if (*oldFocusNewFocusOrder >= 0 && 3459 *anchorOldFocusOrder >= 0) { // 2,1,a or 21,a or 2,1a or 21a 3460 // select from 2 to 1 3461 range->SetStart(aContainer, aOffset, aRv, 3462 AllowRangeCrossShadowBoundary::Yes); 3463 if (aRv.Failed()) { 3464 return; 3465 } 3466 SetDirection(eDirPrevious); 3467 res = difRange->SetStartAndEnd( 3468 range->GetStartContainer(), range->StartOffset(), focusNode, 3469 focusOffset, AllowRangeCrossShadowBoundary::Yes); 3470 if (NS_FAILED(res)) { 3471 aRv.Throw(res); 3472 return; 3473 } 3474 3475 SelectFrames(presContext, *difRange, true); 3476 res = SetAnchorFocusToRange(range); 3477 if (NS_FAILED(res)) { 3478 aRv.Throw(res); 3479 return; 3480 } 3481 } 3482 } 3483 3484 if (mStyledRanges.Length() > 1) { 3485 SelectFramesInAllRanges(presContext); 3486 } 3487 3488 DEBUG_OUT_RANGE(range); 3489 #ifdef DEBUG_SELECTION 3490 if (GetDirection() != oldDirection) { 3491 printf(" direction changed to %s\n", 3492 GetDirection() == eDirNext ? "eDirNext" : "eDirPrevious"); 3493 } 3494 nsCOMPtr<nsIContent> content = do_QueryInterface(&aContainer); 3495 printf("Sel. Extend to %p %s %d\n", content.get(), 3496 nsAtomCString(content->NodeInfo()->NameAtom()).get(), aOffset); 3497 #endif 3498 3499 RefPtr<Selection> kungFuDeathGrip{this}; 3500 // Be aware, this instance may be destroyed after this call. 3501 NotifySelectionListeners(); 3502 } 3503 3504 void Selection::SelectAllChildrenJS(nsINode& aNode, ErrorResult& aRv) { 3505 if (NeedsToLogSelectionAPI(*this)) { 3506 LogSelectionAPI(this, __FUNCTION__, "aNode", &aNode); 3507 LogStackForSelectionAPI(); 3508 } 3509 3510 AutoRestore<bool> calledFromJSRestorer(mCalledByJS); 3511 mCalledByJS = true; 3512 SelectAllChildren(aNode, aRv); 3513 if (StaticPrefs::dom_selection_mimic_chrome_tostring_enabled() && 3514 !aRv.Failed()) { 3515 if (auto* presShell = GetPresShell()) { 3516 presShell->UpdateLastSelectionForToString(mFrameSelection); 3517 } 3518 } 3519 } 3520 3521 void Selection::SelectAllChildren(nsINode& aNode, ErrorResult& aRv) { 3522 if (!mCalledByJS && NeedsToLogSelectionAPI(*this)) { 3523 LogSelectionAPI(this, __FUNCTION__, "aNode", &aNode); 3524 LogStackForSelectionAPI(); 3525 } 3526 3527 if (aNode.NodeType() == nsINode::DOCUMENT_TYPE_NODE) { 3528 aRv.ThrowInvalidNodeTypeError(kNoDocumentTypeNodeError); 3529 return; 3530 } 3531 3532 if (!HasSameRootOrSameComposedDoc(aNode)) { 3533 // Return with no error 3534 return; 3535 } 3536 3537 if (mFrameSelection) { 3538 mFrameSelection->AddChangeReasons(nsISelectionListener::SELECTALL_REASON); 3539 } 3540 3541 // Chrome moves focus when aNode is outside of active editing host. 3542 // So, we don't need to respect the limiter with this method. 3543 SetStartAndEndInternal(InLimiter::eNo, RawRangeBoundary(&aNode, 0u), 3544 RawRangeBoundary(&aNode, aNode.GetChildCount()), 3545 eDirNext, aRv); 3546 } 3547 3548 bool Selection::ContainsNode(nsINode& aNode, bool aAllowPartial, 3549 ErrorResult& aRv) { 3550 nsresult rv; 3551 if (mStyledRanges.Length() == 0) { 3552 return false; 3553 } 3554 3555 // XXXbz this duplicates the GetNodeLength code in nsRange.cpp 3556 uint32_t nodeLength; 3557 auto* nodeAsCharData = CharacterData::FromNode(aNode); 3558 if (nodeAsCharData) { 3559 nodeLength = nodeAsCharData->TextLength(); 3560 } else { 3561 nodeLength = aNode.GetChildCount(); 3562 } 3563 3564 nsTArray<AbstractRange*> overlappingRanges; 3565 rv = GetAbstractRangesForIntervalArray(&aNode, 0, &aNode, nodeLength, false, 3566 &overlappingRanges); 3567 if (NS_FAILED(rv)) { 3568 aRv.Throw(rv); 3569 return false; 3570 } 3571 if (overlappingRanges.Length() == 0) return false; // no ranges overlap 3572 3573 // if the caller said partial intersections are OK, we're done 3574 if (aAllowPartial) { 3575 return true; 3576 } 3577 3578 // text nodes always count as inside 3579 if (nodeAsCharData) { 3580 return true; 3581 } 3582 3583 // The caller wants to know if the node is entirely within the given range, 3584 // so we have to check all intersecting ranges. 3585 for (uint32_t i = 0; i < overlappingRanges.Length(); i++) { 3586 bool nodeStartsBeforeRange, nodeEndsAfterRange; 3587 if (NS_SUCCEEDED(RangeUtils::CompareNodeToRange( 3588 &aNode, overlappingRanges[i], &nodeStartsBeforeRange, 3589 &nodeEndsAfterRange))) { 3590 if (!nodeStartsBeforeRange && !nodeEndsAfterRange) { 3591 return true; 3592 } 3593 } 3594 } 3595 return false; 3596 } 3597 3598 class PointInRectChecker : public mozilla::RectCallback { 3599 public: 3600 explicit PointInRectChecker(const nsPoint& aPoint) 3601 : mPoint(aPoint), mMatchFound(false) {} 3602 3603 void AddRect(const nsRect& aRect) override { 3604 mMatchFound = mMatchFound || aRect.Contains(mPoint); 3605 } 3606 3607 bool MatchFound() { return mMatchFound; } 3608 3609 private: 3610 nsPoint mPoint; 3611 bool mMatchFound; 3612 }; 3613 3614 bool Selection::ContainsPoint(const nsPoint& aPoint) { 3615 if (IsCollapsed()) { 3616 return false; 3617 } 3618 PointInRectChecker checker(aPoint); 3619 const uint32_t rangeCount = RangeCount(); 3620 for (const uint32_t i : IntegerRange(rangeCount)) { 3621 MOZ_ASSERT(RangeCount() == rangeCount); 3622 nsRange* range = GetRangeAt(i); 3623 MOZ_ASSERT(range); 3624 nsRange::CollectClientRectsAndText( 3625 &checker, nullptr, range, range->GetStartContainer(), 3626 range->StartOffset(), range->GetEndContainer(), range->EndOffset(), 3627 true, false); 3628 if (checker.MatchFound()) { 3629 return true; 3630 } 3631 } 3632 return false; 3633 } 3634 3635 void Selection::MaybeNotifyAccessibleCaretEventHub(PresShell* aPresShell) { 3636 MOZ_ASSERT(mSelectionType == SelectionType::eNormal); 3637 3638 if (!mAccessibleCaretEventHub && aPresShell) { 3639 mAccessibleCaretEventHub = aPresShell->GetAccessibleCaretEventHub(); 3640 } 3641 } 3642 3643 void Selection::StopNotifyingAccessibleCaretEventHub() { 3644 MOZ_ASSERT(mSelectionType == SelectionType::eNormal); 3645 3646 mAccessibleCaretEventHub = nullptr; 3647 } 3648 3649 nsPresContext* Selection::GetPresContext() const { 3650 PresShell* presShell = GetPresShell(); 3651 return presShell ? presShell->GetPresContext() : nullptr; 3652 } 3653 3654 PresShell* Selection::GetPresShell() const { 3655 if (!mFrameSelection) { 3656 return nullptr; // nothing to do 3657 } 3658 return mFrameSelection->GetPresShell(); 3659 } 3660 3661 Document* Selection::GetDocument() const { 3662 PresShell* presShell = GetPresShell(); 3663 return presShell ? presShell->GetDocument() : nullptr; 3664 } 3665 3666 nsIFrame* Selection::GetSelectionAnchorGeometry(SelectionRegion aRegion, 3667 nsRect* aRect) { 3668 if (!mFrameSelection) return nullptr; // nothing to do 3669 3670 NS_ENSURE_TRUE(aRect, nullptr); 3671 3672 aRect->SetRect(0, 0, 0, 0); 3673 3674 switch (aRegion) { 3675 case nsISelectionController::SELECTION_ANCHOR_REGION: 3676 case nsISelectionController::SELECTION_FOCUS_REGION: 3677 return GetSelectionEndPointGeometry(aRegion, aRect); 3678 case nsISelectionController::SELECTION_WHOLE_SELECTION: 3679 break; 3680 default: 3681 return nullptr; 3682 } 3683 3684 NS_ASSERTION(aRegion == nsISelectionController::SELECTION_WHOLE_SELECTION, 3685 "should only be SELECTION_WHOLE_SELECTION here"); 3686 3687 nsRect anchorRect; 3688 nsIFrame* anchorFrame = GetSelectionEndPointGeometry( 3689 nsISelectionController::SELECTION_ANCHOR_REGION, &anchorRect); 3690 if (!anchorFrame) return nullptr; 3691 3692 nsRect focusRect; 3693 nsIFrame* focusFrame = GetSelectionEndPointGeometry( 3694 nsISelectionController::SELECTION_FOCUS_REGION, &focusRect); 3695 if (!focusFrame) return nullptr; 3696 3697 NS_ASSERTION(anchorFrame->PresContext() == focusFrame->PresContext(), 3698 "points of selection in different documents?"); 3699 // make focusRect relative to anchorFrame 3700 focusRect += focusFrame->GetOffsetTo(anchorFrame); 3701 3702 *aRect = anchorRect.UnionEdges(focusRect); 3703 return anchorFrame; 3704 } 3705 3706 nsIFrame* Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion, 3707 nsRect* aRect) { 3708 if (!mFrameSelection) return nullptr; // nothing to do 3709 3710 NS_ENSURE_TRUE(aRect, nullptr); 3711 3712 aRect->SetRect(0, 0, 0, 0); 3713 3714 nsINode* node = nullptr; 3715 uint32_t nodeOffset = 0; 3716 3717 switch (aRegion) { 3718 case nsISelectionController::SELECTION_ANCHOR_REGION: 3719 node = GetAnchorNode(); 3720 nodeOffset = AnchorOffset(); 3721 break; 3722 case nsISelectionController::SELECTION_FOCUS_REGION: 3723 node = GetFocusNode(); 3724 nodeOffset = FocusOffset(); 3725 break; 3726 default: 3727 return nullptr; 3728 } 3729 3730 if (!node) return nullptr; 3731 3732 nsCOMPtr<nsIContent> content = do_QueryInterface(node); 3733 NS_ENSURE_TRUE(content.get(), nullptr); 3734 FrameAndOffset frameAndOffset = SelectionMovementUtils::GetFrameForNodeOffset( 3735 content, nodeOffset, mFrameSelection->GetHint()); 3736 if (!frameAndOffset) { 3737 return nullptr; 3738 } 3739 3740 SelectionMovementUtils::AdjustFrameForLineStart( 3741 frameAndOffset.mFrame, frameAndOffset.mOffsetInFrameContent); 3742 3743 // Figure out what node type we have, then get the 3744 // appropriate rect for its nodeOffset. 3745 bool isText = node->IsText(); 3746 3747 nsPoint pt(0, 0); 3748 if (isText) { 3749 nsIFrame* childFrame = nullptr; 3750 int32_t frameOffset = 0; 3751 nsresult rv = frameAndOffset->GetChildFrameContainingOffset( 3752 // FIXME: nodeOffset is offset in content (same as node) but 3753 // frameAndOffset.mFrame may be a frame for its descendant. Therefore, 3754 // frameAndOffset.mOffsetInFrameContent should be used here. 3755 nodeOffset, mFrameSelection->GetHint() == CaretAssociationHint::After, 3756 &frameOffset, &childFrame); 3757 if (NS_FAILED(rv)) return nullptr; 3758 if (!childFrame) return nullptr; 3759 3760 frameAndOffset.mFrame = childFrame; 3761 3762 // Get the coordinates of the offset into the text frame. 3763 rv = GetCachedFrameOffset( 3764 frameAndOffset.mFrame, 3765 static_cast<int32_t>(frameAndOffset.mOffsetInFrameContent), pt); 3766 if (NS_FAILED(rv)) return nullptr; 3767 } 3768 3769 // Return the rect relative to the frame, with zero inline-size. The 3770 // inline-position is either 'pt' (if we're a text node) or otherwise just 3771 // the physical "end" edge of the frame (which we express as the frame's own 3772 // width or height, since the returned position is relative to the frame). 3773 // The block position and size are set so as to fill the frame in that axis. 3774 // (i.e. block-position of 0, and block-size matching the frame's own block 3775 // size). 3776 const WritingMode wm = frameAndOffset->GetWritingMode(); 3777 // Helper to determine the inline-axis position for the aRect outparam. 3778 auto GetInlinePosition = [&]() { 3779 if (isText) { 3780 return wm.IsVertical() ? pt.y : pt.x; 3781 } 3782 // Return the frame's physical end edge of its inline axis, relative to the 3783 // frame. That's just its height or width. 3784 // TODO(dholbert): This seems to work, but perhaps we really want the 3785 // inline-end edge (rather than physical end of inline axis)? (i.e. if we 3786 // have direction:rtl, maybe this code would want to return 0 instead of 3787 // height/width?) 3788 return frameAndOffset->ISize(wm); 3789 }; 3790 3791 // Set the inline position and block-size. Leave inline size and block 3792 // position set to 0, as discussed above. 3793 if (wm.IsVertical()) { 3794 aRect->y = GetInlinePosition(); 3795 aRect->SetWidth(frameAndOffset->BSize(wm)); 3796 } else { 3797 aRect->x = GetInlinePosition(); 3798 aRect->SetHeight(frameAndOffset->BSize(wm)); 3799 } 3800 3801 return frameAndOffset; 3802 } 3803 3804 NS_IMETHODIMP 3805 Selection::ScrollSelectionIntoViewEvent::Run() { 3806 if (!mSelection) { 3807 // event revoked 3808 return NS_OK; 3809 } 3810 3811 const RefPtr<Selection> selection{mSelection}; 3812 selection->mScrollEvent.Forget(); 3813 selection->ScrollIntoView(mRegion, mVerticalScroll, mHorizontalScroll, mFlags, 3814 SelectionScrollMode::SyncFlush); 3815 return NS_OK; 3816 } 3817 3818 nsresult Selection::PostScrollSelectionIntoViewEvent(SelectionRegion aRegion, 3819 ScrollFlags aFlags, 3820 ScrollAxis aVertical, 3821 ScrollAxis aHorizontal) { 3822 // If we've already posted an event, revoke it and place a new one at the 3823 // end of the queue to make sure that any new pending reflow events are 3824 // processed before we scroll. This will insure that we scroll to the 3825 // correct place on screen. 3826 mScrollEvent.Revoke(); 3827 nsPresContext* presContext = GetPresContext(); 3828 NS_ENSURE_STATE(presContext); 3829 nsRefreshDriver* refreshDriver = presContext->RefreshDriver(); 3830 NS_ENSURE_STATE(refreshDriver); 3831 3832 mScrollEvent = new ScrollSelectionIntoViewEvent(this, aRegion, aVertical, 3833 aHorizontal, aFlags); 3834 refreshDriver->AddEarlyRunner(mScrollEvent.get()); 3835 return NS_OK; 3836 } 3837 3838 nsresult Selection::ScrollIntoView(SelectionRegion aRegion, 3839 ScrollAxis aVertical, ScrollAxis aHorizontal, 3840 ScrollFlags aScrollFlags, 3841 SelectionScrollMode aMode) { 3842 if (!mFrameSelection) { 3843 return NS_ERROR_NOT_INITIALIZED; 3844 } 3845 3846 RefPtr<PresShell> presShell = mFrameSelection->GetPresShell(); 3847 if (!presShell || !presShell->GetDocument()) { 3848 return NS_OK; 3849 } 3850 3851 if (mFrameSelection->IsBatching()) { 3852 return NS_OK; 3853 } 3854 3855 if (aMode == SelectionScrollMode::Async) { 3856 return PostScrollSelectionIntoViewEvent(aRegion, aScrollFlags, aVertical, 3857 aHorizontal); 3858 } 3859 3860 MOZ_ASSERT(aMode == SelectionScrollMode::SyncFlush || 3861 aMode == SelectionScrollMode::SyncNoFlush); 3862 3863 // From this point on, the presShell may get destroyed by the calls below, so 3864 // hold on to it using a strong reference to ensure the safety of the 3865 // accesses to frame pointers in the callees. 3866 RefPtr<PresShell> kungFuDeathGrip(presShell); 3867 3868 // Now that text frame character offsets are always valid (though not 3869 // necessarily correct), the worst that will happen if we don't flush here 3870 // is that some callers might scroll to the wrong place. Those should 3871 // either manually flush if they're in a safe position for it or use the 3872 // async version of this method. 3873 if (aMode == SelectionScrollMode::SyncFlush) { 3874 presShell->GetDocument()->FlushPendingNotifications(FlushType::Layout); 3875 3876 // Reget the presshell, since it might have been Destroy'ed. 3877 presShell = mFrameSelection ? mFrameSelection->GetPresShell() : nullptr; 3878 if (!presShell) { 3879 return NS_OK; 3880 } 3881 } 3882 3883 nsRect rect; 3884 nsIFrame* frame = GetSelectionAnchorGeometry(aRegion, &rect); 3885 if (!frame) { 3886 return NS_ERROR_FAILURE; 3887 } 3888 3889 presShell->ScrollFrameIntoView(frame, Some(rect), aVertical, aHorizontal, 3890 aScrollFlags); 3891 return NS_OK; 3892 } 3893 3894 void Selection::AddSelectionListener(nsISelectionListener* aNewListener) { 3895 MOZ_ASSERT(aNewListener); 3896 mSelectionListeners.AppendElement(aNewListener); // AddRefs 3897 } 3898 3899 void Selection::RemoveSelectionListener( 3900 nsISelectionListener* aListenerToRemove) { 3901 mSelectionListeners.RemoveElement(aListenerToRemove); // Releases 3902 } 3903 3904 Element* Selection::StyledRanges::GetCommonEditingHost() const { 3905 Element* editingHost = nullptr; 3906 for (const StyledRange& rangeData : mRanges) { 3907 const AbstractRange* range = rangeData.mRange; 3908 MOZ_ASSERT(range); 3909 nsINode* commonAncestorNode = range->GetClosestCommonInclusiveAncestor(); 3910 if (!commonAncestorNode || !commonAncestorNode->IsContent()) { 3911 return nullptr; 3912 } 3913 nsIContent* commonAncestor = commonAncestorNode->AsContent(); 3914 Element* foundEditingHost = commonAncestor->GetEditingHost(); 3915 // Even when common ancestor is a non-editable element in a contenteditable 3916 // element, we don't need to move focus to the contenteditable element 3917 // because Chromium doesn't set focus to it. 3918 if (!foundEditingHost) { 3919 return nullptr; 3920 } 3921 if (!editingHost) { 3922 editingHost = foundEditingHost; 3923 continue; 3924 } 3925 if (editingHost == foundEditingHost) { 3926 continue; 3927 } 3928 if (foundEditingHost->IsInclusiveDescendantOf(editingHost)) { 3929 continue; 3930 } 3931 if (editingHost->IsInclusiveDescendantOf(foundEditingHost)) { 3932 editingHost = foundEditingHost; 3933 continue; 3934 } 3935 // editingHost and foundEditingHost are not a descendant of the other. 3936 // So, there is no common editing host. 3937 return nullptr; 3938 } 3939 return editingHost; 3940 } 3941 3942 void Selection::StyledRanges::MaybeFocusCommonEditingHost( 3943 PresShell* aPresShell) const { 3944 if (!aPresShell) { 3945 return; 3946 } 3947 3948 nsPresContext* presContext = aPresShell->GetPresContext(); 3949 if (!presContext) { 3950 return; 3951 } 3952 3953 Document* document = aPresShell->GetDocument(); 3954 if (!document) { 3955 return; 3956 } 3957 3958 nsPIDOMWindowOuter* window = document->GetWindow(); 3959 // If the document is in design mode or doesn't have contenteditable 3960 // element, we don't need to move focus. 3961 if (window && !document->IsInDesignMode() && 3962 nsContentUtils::GetHTMLEditor(presContext)) { 3963 RefPtr<Element> newEditingHost = GetCommonEditingHost(); 3964 RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager(); 3965 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; 3966 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant( 3967 window, nsFocusManager::eOnlyCurrentWindow, 3968 getter_AddRefs(focusedWindow)); 3969 nsCOMPtr<Element> focusedElement = do_QueryInterface(focusedContent); 3970 // When all selected ranges are in an editing host, it should take focus. 3971 // But otherwise, we shouldn't move focus since Chromium doesn't move 3972 // focus but only selection range is updated. 3973 if (newEditingHost && newEditingHost != focusedElement) { 3974 MOZ_ASSERT(!newEditingHost->IsInNativeAnonymousSubtree()); 3975 // Note that don't steal focus from focused window if the window doesn't 3976 // have focus. Additionally, although when an element gets focus, we 3977 // usually scroll to the element, but in this case, we shouldn't do it 3978 // because Chrome does not do so. 3979 fm->SetFocus(newEditingHost, nsIFocusManager::FLAG_NOSWITCHFRAME | 3980 nsIFocusManager::FLAG_NOSCROLL); 3981 } 3982 } 3983 } 3984 3985 void Selection::NotifySelectionListeners(bool aCalledByJS) { 3986 AutoRestore<bool> calledFromJSRestorer(mCalledByJS); 3987 mCalledByJS = aCalledByJS; 3988 NotifySelectionListeners(); 3989 } 3990 3991 void Selection::NotifySelectionListeners() { 3992 if (!mFrameSelection) { 3993 return; // nothing to do 3994 } 3995 3996 MOZ_LOG(sSelectionLog, LogLevel::Debug, 3997 ("%s: selection=%p", __FUNCTION__, this)); 3998 3999 mStyledRanges.mRangesMightHaveChanged = true; 4000 4001 // This flag will be set to Double or Triple if a selection by double click or 4002 // triple click is detected. As soon as the selection is modified, it needs to 4003 // be reset to NotApplicable. 4004 mFrameSelection->SetClickSelectionType(ClickSelectionType::NotApplicable); 4005 4006 // If we're batching changes, record our batching flag and bail out, we'll be 4007 // called once the batch ends. 4008 if (mFrameSelection->IsBatching()) { 4009 mChangesDuringBatching = true; 4010 return; 4011 } 4012 // If being called at end of batching, `mFrameSelection->IsBatching()` will 4013 // return false. In this case, this method will only be called if 4014 // `mChangesDuringBatching` was true. 4015 // (see `nsFrameSelection::EndBatchChanges()`). 4016 // Since arriving here means that batching ended, the flag needs to be reset. 4017 mChangesDuringBatching = false; 4018 4019 // Our internal code should not move focus with using this class while 4020 // this moves focus nor from selection listeners. 4021 AutoRestore<bool> calledByJSRestorer(mCalledByJS); 4022 mCalledByJS = false; 4023 4024 // When normal selection is changed by Selection API, we need to move focus 4025 // if common ancestor of all ranges are in an editing host. Note that we 4026 // don't need to move focus *to* the other focusable node because other 4027 // browsers don't do it either. 4028 if (mSelectionType == SelectionType::eNormal && 4029 calledByJSRestorer.SavedValue()) { 4030 RefPtr<PresShell> presShell = GetPresShell(); 4031 mStyledRanges.MaybeFocusCommonEditingHost(presShell); 4032 } 4033 4034 nsCOMPtr<Document> doc; 4035 if (PresShell* presShell = GetPresShell()) { 4036 doc = presShell->GetDocument(); 4037 presShell->ScheduleContentRelevancyUpdate(ContentRelevancyReason::Selected); 4038 } 4039 4040 RefPtr<nsFrameSelection> frameSelection = mFrameSelection; 4041 4042 // We've notified all selection listeners even when some of them are removed 4043 // (and may be destroyed) during notifying one of them. Therefore, we should 4044 // copy all listeners to the local variable first. 4045 const CopyableAutoTArray<nsCOMPtr<nsISelectionListener>, 5> 4046 selectionListeners = mSelectionListeners; 4047 4048 int32_t amount = static_cast<int32_t>(frameSelection->GetCaretMoveAmount()); 4049 int16_t reason = frameSelection->PopChangeReasons(); 4050 if (calledByJSRestorer.SavedValue()) { 4051 reason |= nsISelectionListener::JS_REASON; 4052 } 4053 if (mSelectionType == SelectionType::eNormal) { 4054 if (mNotifyAutoCopy) { 4055 AutoCopyListener::OnSelectionChange(doc, *this, reason); 4056 } 4057 4058 if (mAccessibleCaretEventHub) { 4059 RefPtr<AccessibleCaretEventHub> hub(mAccessibleCaretEventHub); 4060 hub->OnSelectionChange(doc, this, reason); 4061 } 4062 4063 if (mSelectionChangeEventDispatcher) { 4064 RefPtr<SelectionChangeEventDispatcher> dispatcher( 4065 mSelectionChangeEventDispatcher); 4066 dispatcher->OnSelectionChange(doc, this, reason); 4067 } 4068 } 4069 for (const auto& listener : selectionListeners) { 4070 // MOZ_KnownLive because 'selectionListeners' is guaranteed to 4071 // keep it alive. 4072 // 4073 // This can go away once 4074 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed. 4075 MOZ_KnownLive(listener)->NotifySelectionChanged(doc, this, reason, amount); 4076 } 4077 } 4078 4079 void Selection::StartBatchChanges(const char* aDetails) { 4080 if (mFrameSelection) { 4081 RefPtr<nsFrameSelection> frameSelection = mFrameSelection; 4082 frameSelection->StartBatchChanges(aDetails); 4083 } 4084 } 4085 4086 void Selection::EndBatchChanges(const char* aDetails, int16_t aReasons) { 4087 if (mFrameSelection) { 4088 RefPtr<nsFrameSelection> frameSelection = mFrameSelection; 4089 frameSelection->EndBatchChanges(aDetails, aReasons); 4090 } 4091 } 4092 4093 void Selection::AddSelectionChangeBlocker() { mSelectionChangeBlockerCount++; } 4094 4095 void Selection::RemoveSelectionChangeBlocker() { 4096 MOZ_ASSERT(mSelectionChangeBlockerCount > 0, 4097 "mSelectionChangeBlockerCount has an invalid value - " 4098 "maybe you have a mismatched RemoveSelectionChangeBlocker?"); 4099 mSelectionChangeBlockerCount--; 4100 } 4101 4102 bool Selection::IsBlockingSelectionChangeEvents() const { 4103 return mSelectionChangeBlockerCount > 0; 4104 } 4105 4106 void Selection::DeleteFromDocument(ErrorResult& aRv) { 4107 if (NeedsToLogSelectionAPI(*this)) { 4108 LogSelectionAPI(this, __FUNCTION__); 4109 LogStackForSelectionAPI(); 4110 } 4111 4112 if (mSelectionType != SelectionType::eNormal) { 4113 return; // Nothing to do. 4114 } 4115 4116 // If we're already collapsed, then we do nothing (bug 719503). 4117 if (IsCollapsed()) { 4118 return; 4119 } 4120 4121 // nsRange::DeleteContents() may run script, let's store all ranges first. 4122 AutoTArray<RefPtr<nsRange>, 1> ranges; 4123 MOZ_ASSERT(RangeCount() == mStyledRanges.mRanges.Length()); 4124 ranges.SetCapacity(RangeCount()); 4125 for (uint32_t index : IntegerRange(RangeCount())) { 4126 ranges.AppendElement(mStyledRanges.mRanges[index].mRange->AsDynamicRange()); 4127 } 4128 for (const auto& range : ranges) { 4129 MOZ_KnownLive(range)->DeleteContents(aRv); 4130 if (aRv.Failed()) { 4131 return; 4132 } 4133 } 4134 4135 // Collapse to the new location. 4136 // If we deleted one character, then we move back one element. 4137 // FIXME We don't know how to do this past frame boundaries yet. 4138 if (AnchorOffset() > 0) { 4139 RefPtr<nsINode> anchor = GetAnchorNode(); 4140 CollapseInLimiter(anchor, AnchorOffset()); 4141 } 4142 #ifdef DEBUG 4143 else { 4144 printf("Don't know how to set selection back past frame boundary\n"); 4145 } 4146 #endif 4147 } 4148 4149 void Selection::Modify(const nsAString& aAlter, const nsAString& aDirection, 4150 const nsAString& aGranularity) { 4151 if (NeedsToLogSelectionAPI(*this)) { 4152 LogSelectionAPI(this, __FUNCTION__, "aAlter", aAlter, "aDirection", 4153 aDirection, "aGranularity", aGranularity); 4154 LogStackForSelectionAPI(); 4155 } 4156 4157 if (!mFrameSelection) { 4158 return; 4159 } 4160 4161 if (!GetAnchorFocusRange() || !GetFocusNode()) { 4162 return; 4163 } 4164 4165 if (!aAlter.LowerCaseEqualsLiteral("move") && 4166 !aAlter.LowerCaseEqualsLiteral("extend")) { 4167 return; 4168 } 4169 4170 if (!aDirection.LowerCaseEqualsLiteral("forward") && 4171 !aDirection.LowerCaseEqualsLiteral("backward") && 4172 !aDirection.LowerCaseEqualsLiteral("left") && 4173 !aDirection.LowerCaseEqualsLiteral("right")) { 4174 return; 4175 } 4176 4177 // Make sure the layout is up to date as we access bidi information below. 4178 if (RefPtr<Document> doc = GetDocument()) { 4179 doc->FlushPendingNotifications(FlushType::Layout); 4180 } 4181 4182 // Line moves are always visual. 4183 bool visual = aDirection.LowerCaseEqualsLiteral("left") || 4184 aDirection.LowerCaseEqualsLiteral("right") || 4185 aGranularity.LowerCaseEqualsLiteral("line"); 4186 4187 bool forward = aDirection.LowerCaseEqualsLiteral("forward") || 4188 aDirection.LowerCaseEqualsLiteral("right"); 4189 4190 bool extend = aAlter.LowerCaseEqualsLiteral("extend"); 4191 4192 nsSelectionAmount amount; 4193 if (aGranularity.LowerCaseEqualsLiteral("character")) { 4194 amount = eSelectCluster; 4195 } else if (aGranularity.LowerCaseEqualsLiteral("word")) { 4196 amount = eSelectWordNoSpace; 4197 } else if (aGranularity.LowerCaseEqualsLiteral("line")) { 4198 amount = eSelectLine; 4199 } else if (aGranularity.LowerCaseEqualsLiteral("lineboundary")) { 4200 amount = forward ? eSelectEndLine : eSelectBeginLine; 4201 } else if (aGranularity.LowerCaseEqualsLiteral("sentence") || 4202 aGranularity.LowerCaseEqualsLiteral("sentenceboundary") || 4203 aGranularity.LowerCaseEqualsLiteral("paragraph") || 4204 aGranularity.LowerCaseEqualsLiteral("paragraphboundary") || 4205 aGranularity.LowerCaseEqualsLiteral("documentboundary")) { 4206 Document* document = GetParentObject(); 4207 if (document) { 4208 AutoTArray<nsString, 1> params; 4209 params.AppendElement(aGranularity); 4210 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, 4211 document, nsContentUtils::eDOM_PROPERTIES, 4212 "SelectionModifyGranualirtyUnsupported", 4213 params); 4214 } 4215 return; 4216 } else { 4217 return; 4218 } 4219 4220 // If the anchor doesn't equal the focus and we try to move without first 4221 // collapsing the selection, MoveCaret will collapse the selection and quit. 4222 // To avoid this, we need to collapse the selection first. 4223 nsresult rv = NS_OK; 4224 if (!extend) { 4225 RefPtr<nsINode> focusNode = GetFocusNode(); 4226 // We should have checked earlier that there was a focus node. 4227 if (!focusNode) { 4228 return; 4229 } 4230 uint32_t focusOffset = FocusOffset(); 4231 CollapseInLimiter(focusNode, focusOffset); 4232 } 4233 4234 // If the paragraph direction of the focused frame is right-to-left, 4235 // we may have to swap the direction of movement. 4236 const PrimaryFrameData frameForFocus = 4237 GetPrimaryFrameForCaretAtFocusNode(visual); 4238 if (frameForFocus) { 4239 if (visual) { 4240 // FYI: This was done during a call of GetPrimaryFrameForCaretAtFocusNode. 4241 // Therefore, this may not be intended by the original author. 4242 mFrameSelection->SetHint(frameForFocus.mHint); 4243 } 4244 mozilla::intl::BidiDirection paraDir = 4245 nsBidiPresUtils::ParagraphDirection(frameForFocus.mFrame); 4246 4247 if (paraDir == mozilla::intl::BidiDirection::RTL && visual) { 4248 if (amount == eSelectBeginLine) { 4249 amount = eSelectEndLine; 4250 forward = !forward; 4251 } else if (amount == eSelectEndLine) { 4252 amount = eSelectBeginLine; 4253 forward = !forward; 4254 } 4255 } 4256 } 4257 4258 // MoveCaret will return an error if it can't move in the specified 4259 // direction, but we just ignore this error unless it's a line move, in which 4260 // case we call nsISelectionController::CompleteMove to move the cursor to 4261 // the beginning/end of the line. 4262 RefPtr<nsFrameSelection> frameSelection = mFrameSelection; 4263 rv = frameSelection->MoveCaret( 4264 forward ? eDirNext : eDirPrevious, 4265 nsFrameSelection::ExtendSelection(extend), amount, 4266 visual ? nsFrameSelection::eVisual : nsFrameSelection::eLogical); 4267 4268 if (aGranularity.LowerCaseEqualsLiteral("line") && NS_FAILED(rv)) { 4269 RefPtr<PresShell> presShell = frameSelection->GetPresShell(); 4270 if (!presShell) { 4271 return; 4272 } 4273 presShell->CompleteMove(forward, extend); 4274 } 4275 } 4276 4277 void Selection::SetBaseAndExtentJS(nsINode& aAnchorNode, uint32_t aAnchorOffset, 4278 nsINode& aFocusNode, uint32_t aFocusOffset, 4279 ErrorResult& aRv) { 4280 if (NeedsToLogSelectionAPI(*this)) { 4281 LogSelectionAPI(this, __FUNCTION__, "aAnchorNode", aAnchorNode, 4282 "aAnchorOffset", aAnchorOffset, "aFocusNode", aFocusNode, 4283 "aFocusOffset", aFocusOffset); 4284 LogStackForSelectionAPI(); 4285 } 4286 4287 AutoRestore<bool> calledFromJSRestorer(mCalledByJS); 4288 mCalledByJS = true; 4289 SetBaseAndExtent(aAnchorNode, aAnchorOffset, aFocusNode, aFocusOffset, aRv); 4290 if (StaticPrefs::dom_selection_mimic_chrome_tostring_enabled() && 4291 !aRv.Failed()) { 4292 if (auto* presShell = GetPresShell()) { 4293 presShell->UpdateLastSelectionForToString(mFrameSelection); 4294 } 4295 } 4296 } 4297 4298 void Selection::SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset, 4299 nsINode& aFocusNode, uint32_t aFocusOffset, 4300 ErrorResult& aRv) { 4301 if (aAnchorOffset > aAnchorNode.Length()) { 4302 aRv.ThrowIndexSizeError(nsPrintfCString( 4303 "The anchor offset value %u is out of range", aAnchorOffset)); 4304 return; 4305 } 4306 if (aFocusOffset > aFocusNode.Length()) { 4307 aRv.ThrowIndexSizeError(nsPrintfCString( 4308 "The focus offset value %u is out of range", aFocusOffset)); 4309 return; 4310 } 4311 4312 SetBaseAndExtent(RawRangeBoundary{&aAnchorNode, aAnchorOffset}, 4313 RawRangeBoundary{&aFocusNode, aFocusOffset}, aRv); 4314 } 4315 4316 void Selection::SetBaseAndExtent(const RawRangeBoundary& aAnchorRef, 4317 const RawRangeBoundary& aFocusRef, 4318 ErrorResult& aRv) { 4319 if (!mCalledByJS && NeedsToLogSelectionAPI(*this)) { 4320 LogSelectionAPI(this, __FUNCTION__, "aAnchorRef", aAnchorRef, "aFocusRef", 4321 aFocusRef); 4322 LogStackForSelectionAPI(); 4323 } 4324 4325 SetBaseAndExtentInternal(InLimiter::eNo, aAnchorRef, aFocusRef, aRv); 4326 } 4327 4328 void Selection::SetBaseAndExtentInLimiter(const RawRangeBoundary& aAnchorRef, 4329 const RawRangeBoundary& aFocusRef, 4330 ErrorResult& aRv) { 4331 if (NeedsToLogSelectionAPI(*this)) { 4332 LogSelectionAPI(this, __FUNCTION__, "aAnchorRef", aAnchorRef, "aFocusRef", 4333 aFocusRef); 4334 LogStackForSelectionAPI(); 4335 } 4336 4337 SetBaseAndExtentInternal(InLimiter::eYes, aAnchorRef, aFocusRef, aRv); 4338 } 4339 4340 void Selection::SetBaseAndExtentInternal(InLimiter aInLimiter, 4341 const RawRangeBoundary& aAnchorRef, 4342 const RawRangeBoundary& aFocusRef, 4343 ErrorResult& aRv) { 4344 if (!mFrameSelection) { 4345 aRv.Throw(NS_ERROR_NOT_INITIALIZED); 4346 return; 4347 } 4348 4349 if (NS_WARN_IF(!aAnchorRef.IsSet()) || NS_WARN_IF(!aFocusRef.IsSet())) { 4350 aRv.Throw(NS_ERROR_INVALID_ARG); 4351 return; 4352 } 4353 4354 if (!HasSameRootOrSameComposedDoc(*aAnchorRef.GetContainer()) || 4355 !HasSameRootOrSameComposedDoc(*aFocusRef.GetContainer())) { 4356 // Return with no error 4357 return; 4358 } 4359 4360 // Prevent "selectionchange" event temporarily because it should be fired 4361 // after we set the direction. 4362 // XXX If they are disconnected, shouldn't we return error before allocating 4363 // new nsRange instance? 4364 SelectionBatcher batch(this, __FUNCTION__); 4365 const Maybe<int32_t> order = 4366 StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() && 4367 !IsEditorSelection() 4368 ? nsContentUtils::ComparePoints<TreeKind::Flat>(aAnchorRef, aFocusRef) 4369 : nsContentUtils::ComparePoints<TreeKind::ShadowIncludingDOM>( 4370 aAnchorRef, aFocusRef); 4371 if (order && (*order <= 0)) { 4372 SetStartAndEndInternal(aInLimiter, aAnchorRef, aFocusRef, eDirNext, aRv); 4373 return; 4374 } 4375 4376 // If there's no `order`, the range will be collapsed, unless another error is 4377 // detected before. 4378 SetStartAndEndInternal(aInLimiter, aFocusRef, aAnchorRef, eDirPrevious, aRv); 4379 } 4380 4381 void Selection::SetStartAndEndInLimiter(const RawRangeBoundary& aStartRef, 4382 const RawRangeBoundary& aEndRef, 4383 ErrorResult& aRv) { 4384 if (NeedsToLogSelectionAPI(*this)) { 4385 LogSelectionAPI(this, __FUNCTION__, "aStartRef", aStartRef, "aEndRef", 4386 aEndRef); 4387 LogStackForSelectionAPI(); 4388 } 4389 4390 SetStartAndEndInternal(InLimiter::eYes, aStartRef, aEndRef, eDirNext, aRv); 4391 } 4392 4393 Result<Ok, nsresult> Selection::SetStartAndEndInLimiter( 4394 nsINode& aStartContainer, uint32_t aStartOffset, nsINode& aEndContainer, 4395 uint32_t aEndOffset, nsDirection aDirection, int16_t aReason) { 4396 MOZ_ASSERT(aDirection == eDirPrevious || aDirection == eDirNext); 4397 if (NeedsToLogSelectionAPI(*this)) { 4398 LogSelectionAPI(this, __FUNCTION__, "aStartContainer", aStartContainer, 4399 "aStartOffset", aStartOffset, "aEndContainer", 4400 aEndContainer, "aEndOffset", aEndOffset, "nsDirection", 4401 aDirection, "aReason", aReason); 4402 LogStackForSelectionAPI(); 4403 } 4404 4405 if (mFrameSelection) { 4406 mFrameSelection->AddChangeReasons(aReason); 4407 } 4408 4409 ErrorResult error; 4410 SetStartAndEndInternal( 4411 InLimiter::eYes, RawRangeBoundary(&aStartContainer, aStartOffset), 4412 RawRangeBoundary(&aEndContainer, aEndOffset), aDirection, error); 4413 MOZ_TRY(error.StealNSResult()); 4414 return Ok(); 4415 } 4416 4417 void Selection::SetStartAndEnd(const RawRangeBoundary& aStartRef, 4418 const RawRangeBoundary& aEndRef, 4419 ErrorResult& aRv) { 4420 if (NeedsToLogSelectionAPI(*this)) { 4421 LogSelectionAPI(this, __FUNCTION__, "aStartRef", aStartRef, "aEndRef", 4422 aEndRef); 4423 LogStackForSelectionAPI(); 4424 } 4425 4426 SetStartAndEndInternal(InLimiter::eNo, aStartRef, aEndRef, eDirNext, aRv); 4427 } 4428 4429 void Selection::SetStartAndEndInternal(InLimiter aInLimiter, 4430 const RawRangeBoundary& aStartRef, 4431 const RawRangeBoundary& aEndRef, 4432 nsDirection aDirection, 4433 ErrorResult& aRv) { 4434 if (NS_WARN_IF(!aStartRef.IsSet()) || NS_WARN_IF(!aEndRef.IsSet())) { 4435 aRv.Throw(NS_ERROR_INVALID_ARG); 4436 return; 4437 } 4438 4439 // Don't fire "selectionchange" event until everything done. 4440 SelectionBatcher batch(this, __FUNCTION__); 4441 4442 if (aInLimiter == InLimiter::eYes) { 4443 if (!mFrameSelection || 4444 !mFrameSelection->NodeIsInLimiters(aStartRef.GetContainer())) { 4445 aRv.Throw(NS_ERROR_FAILURE); 4446 return; 4447 } 4448 if (aStartRef.GetContainer() != aEndRef.GetContainer() && 4449 !mFrameSelection->NodeIsInLimiters(aEndRef.GetContainer())) { 4450 aRv.Throw(NS_ERROR_FAILURE); 4451 return; 4452 } 4453 } 4454 4455 RefPtr<nsRange> newRange = nsRange::Create( 4456 aStartRef, aEndRef, aRv, 4457 StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() && 4458 aInLimiter == InLimiter::eNo 4459 ? AllowRangeCrossShadowBoundary::Yes 4460 : AllowRangeCrossShadowBoundary::No); 4461 if (aRv.Failed()) { 4462 return; 4463 } 4464 4465 RemoveAllRangesInternal(aRv); 4466 if (aRv.Failed()) { 4467 return; 4468 } 4469 4470 RefPtr<Document> document(GetDocument()); 4471 AddRangeAndSelectFramesAndNotifyListenersInternal(*newRange, document, aRv); 4472 if (aRv.Failed()) { 4473 return; 4474 } 4475 4476 // Adding a range may set 2 or more ranges if there are non-selectable 4477 // contents only when this change is caused by a user operation. Therefore, 4478 // we need to select frames with the result in such case. 4479 if (mUserInitiated) { 4480 RefPtr<nsPresContext> presContext = GetPresContext(); 4481 if (mStyledRanges.Length() > 1 && presContext) { 4482 SelectFramesInAllRanges(presContext); 4483 } 4484 } 4485 4486 SetDirection(aDirection); 4487 } 4488 4489 /** SelectionLanguageChange modifies the cursor Bidi level after a change in 4490 * keyboard direction 4491 * @param aLangRTL is true if the new language is right-to-left or false if the 4492 * new language is left-to-right 4493 */ 4494 nsresult Selection::SelectionLanguageChange(bool aLangRTL) { 4495 if (!mFrameSelection) { 4496 return NS_ERROR_NOT_INITIALIZED; 4497 } 4498 4499 RefPtr<nsFrameSelection> frameSelection = mFrameSelection; 4500 4501 // if the direction of the language hasn't changed, nothing to do 4502 mozilla::intl::BidiEmbeddingLevel kbdBidiLevel = 4503 aLangRTL ? mozilla::intl::BidiEmbeddingLevel::RTL() 4504 : mozilla::intl::BidiEmbeddingLevel::LTR(); 4505 if (kbdBidiLevel == frameSelection->mKbdBidiLevel) { 4506 return NS_OK; 4507 } 4508 4509 frameSelection->mKbdBidiLevel = kbdBidiLevel; 4510 4511 PrimaryFrameData focusFrameData = GetPrimaryFrameForCaretAtFocusNode(false); 4512 if (!focusFrameData.mFrame) { 4513 return NS_ERROR_FAILURE; 4514 } 4515 4516 auto [frameStart, frameEnd] = focusFrameData.mFrame->GetOffsets(); 4517 RefPtr<nsPresContext> context = GetPresContext(); 4518 mozilla::intl::BidiEmbeddingLevel levelBefore, levelAfter; 4519 if (!context) { 4520 return NS_ERROR_FAILURE; 4521 } 4522 4523 mozilla::intl::BidiEmbeddingLevel level = 4524 focusFrameData.mFrame->GetEmbeddingLevel(); 4525 int32_t focusOffset = static_cast<int32_t>(FocusOffset()); 4526 if ((focusOffset != frameStart) && (focusOffset != frameEnd)) 4527 // the cursor is not at a frame boundary, so the level of both the 4528 // characters (logically) before and after the cursor is equal to the frame 4529 // level 4530 levelBefore = levelAfter = level; 4531 else { 4532 // the cursor is at a frame boundary, so use GetPrevNextBidiLevels to find 4533 // the level of the characters before and after the cursor 4534 nsCOMPtr<nsIContent> focusContent = do_QueryInterface(GetFocusNode()); 4535 nsPrevNextBidiLevels levels = 4536 frameSelection->GetPrevNextBidiLevels(focusContent, focusOffset, false); 4537 4538 levelBefore = levels.mLevelBefore; 4539 levelAfter = levels.mLevelAfter; 4540 } 4541 4542 if (levelBefore.IsSameDirection(levelAfter)) { 4543 // if cursor is between two characters with the same orientation, changing 4544 // the keyboard language must toggle the cursor level between the level of 4545 // the character with the lowest level (if the new language corresponds to 4546 // the orientation of that character) and this level plus 1 (if the new 4547 // language corresponds to the opposite orientation) 4548 if ((level != levelBefore) && (level != levelAfter)) { 4549 level = std::min(levelBefore, levelAfter); 4550 } 4551 if (level.IsSameDirection(kbdBidiLevel)) { 4552 frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(level); 4553 } else { 4554 frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint( 4555 mozilla::intl::BidiEmbeddingLevel(level + 1)); 4556 } 4557 } else { 4558 // if cursor is between characters with opposite orientations, changing the 4559 // keyboard language must change the cursor level to that of the adjacent 4560 // character with the orientation corresponding to the new language. 4561 if (levelBefore.IsSameDirection(kbdBidiLevel)) { 4562 frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(levelBefore); 4563 } else { 4564 frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(levelAfter); 4565 } 4566 } 4567 4568 // The caret might have moved, so invalidate the desired position 4569 // for future usages of up-arrow or down-arrow 4570 frameSelection->InvalidateDesiredCaretPos(); 4571 4572 return NS_OK; 4573 } 4574 4575 void Selection::SetColors(const nsAString& aForegroundColor, 4576 const nsAString& aBackgroundColor, 4577 const nsAString& aAltForegroundColor, 4578 const nsAString& aAltBackgroundColor, 4579 ErrorResult& aRv) { 4580 if (mSelectionType != SelectionType::eFind) { 4581 aRv.Throw(NS_ERROR_FAILURE); 4582 return; 4583 } 4584 4585 mCustomColors.reset(new SelectionCustomColors); 4586 4587 constexpr auto currentColorStr = u"currentColor"_ns; 4588 constexpr auto transparentStr = u"transparent"_ns; 4589 4590 if (!aForegroundColor.Equals(currentColorStr)) { 4591 nscolor foregroundColor; 4592 nsAttrValue aForegroundColorValue; 4593 aForegroundColorValue.ParseColor(aForegroundColor); 4594 if (!aForegroundColorValue.GetColorValue(foregroundColor)) { 4595 aRv.Throw(NS_ERROR_INVALID_ARG); 4596 return; 4597 } 4598 mCustomColors->mForegroundColor = Some(foregroundColor); 4599 } else { 4600 mCustomColors->mForegroundColor = Nothing(); 4601 } 4602 4603 if (!aBackgroundColor.Equals(transparentStr)) { 4604 nscolor backgroundColor; 4605 nsAttrValue aBackgroundColorValue; 4606 aBackgroundColorValue.ParseColor(aBackgroundColor); 4607 if (!aBackgroundColorValue.GetColorValue(backgroundColor)) { 4608 aRv.Throw(NS_ERROR_INVALID_ARG); 4609 return; 4610 } 4611 mCustomColors->mBackgroundColor = Some(backgroundColor); 4612 } else { 4613 mCustomColors->mBackgroundColor = Nothing(); 4614 } 4615 4616 if (!aAltForegroundColor.Equals(currentColorStr)) { 4617 nscolor altForegroundColor; 4618 nsAttrValue aAltForegroundColorValue; 4619 aAltForegroundColorValue.ParseColor(aAltForegroundColor); 4620 if (!aAltForegroundColorValue.GetColorValue(altForegroundColor)) { 4621 aRv.Throw(NS_ERROR_INVALID_ARG); 4622 return; 4623 } 4624 mCustomColors->mAltForegroundColor = Some(altForegroundColor); 4625 } else { 4626 mCustomColors->mAltForegroundColor = Nothing(); 4627 } 4628 4629 if (!aAltBackgroundColor.Equals(transparentStr)) { 4630 nscolor altBackgroundColor; 4631 nsAttrValue aAltBackgroundColorValue; 4632 aAltBackgroundColorValue.ParseColor(aAltBackgroundColor); 4633 if (!aAltBackgroundColorValue.GetColorValue(altBackgroundColor)) { 4634 aRv.Throw(NS_ERROR_INVALID_ARG); 4635 return; 4636 } 4637 mCustomColors->mAltBackgroundColor = Some(altBackgroundColor); 4638 } else { 4639 mCustomColors->mAltBackgroundColor = Nothing(); 4640 } 4641 } 4642 4643 void Selection::ResetColors() { mCustomColors = nullptr; } 4644 4645 void Selection::SetHighlightSelectionData( 4646 dom::HighlightSelectionData aHighlightSelectionData) { 4647 MOZ_ASSERT(mSelectionType == SelectionType::eHighlight); 4648 mHighlightData = std::move(aHighlightSelectionData); 4649 } 4650 4651 JSObject* Selection::WrapObject(JSContext* aCx, 4652 JS::Handle<JSObject*> aGivenProto) { 4653 return mozilla::dom::Selection_Binding::Wrap(aCx, this, aGivenProto); 4654 } 4655 4656 // AutoHideSelectionChanges 4657 AutoHideSelectionChanges::AutoHideSelectionChanges( 4658 const nsFrameSelection* aFrame) 4659 : AutoHideSelectionChanges(aFrame ? &aFrame->NormalSelection() : nullptr) {} 4660 4661 bool Selection::HasSameRootOrSameComposedDoc(const nsINode& aNode) { 4662 nsINode* root = aNode.SubtreeRoot(); 4663 Document* doc = GetDocument(); 4664 return doc == root || (root && doc == root->GetComposedDoc()); 4665 }