1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * Implementation of nsFrameSelection
11 #include "nsFrameSelection.h"
13 #include "mozilla/Attributes.h"
14 #include "mozilla/AutoRestore.h"
15 #include "mozilla/BasePrincipal.h"
16 #include "mozilla/EventStates.h"
17 #include "mozilla/HTMLEditor.h"
18 #include "mozilla/Logging.h"
19 #include "mozilla/PresShell.h"
20 #include "mozilla/ScrollTypes.h"
21 #include "mozilla/StaticAnalysisFunctions.h"
22 #include "mozilla/StaticPrefs_bidi.h"
23 #include "mozilla/StaticPrefs_dom.h"
24 #include "mozilla/StaticPrefs_layout.h"
29 #include "nsISelectionListener.h"
30 #include "nsContentCID.h"
31 #include "nsDeviceContext.h"
32 #include "nsIContent.h"
34 #include "nsITableCellLayout.h"
36 #include "nsTableWrapperFrame.h"
37 #include "nsTableCellFrame.h"
38 #include "nsIScrollableFrame.h"
39 #include "nsCCUncollectableMarker.h"
40 #include "nsTextFragment.h"
42 #include "nsContentUtils.h"
43 #include "nsCSSFrameConstructor.h"
45 #include "nsGkAtoms.h"
46 #include "nsIFrameTraversal.h"
47 #include "nsLayoutUtils.h"
48 #include "nsLayoutCID.h"
49 #include "nsBidiPresUtils.h"
50 static NS_DEFINE_CID(kFrameTraversalCID
, NS_FRAMETRAVERSAL_CID
);
51 #include "nsTextFrame.h"
53 #include "nsThreadUtils.h"
54 #include "mozilla/Preferences.h"
56 #include "mozilla/PresShell.h"
57 #include "nsPresContext.h"
60 #include "mozilla/MouseEvents.h"
61 #include "mozilla/TextEvents.h"
64 #include "mozilla/dom/Document.h"
66 #include "nsISelectionController.h" //for the enums
67 #include "nsCopySupport.h"
68 #include "nsIClipboard.h"
69 #include "nsIFrameInlines.h"
72 #include "mozilla/AutoCopyListener.h"
73 #include "mozilla/dom/Element.h"
74 #include "mozilla/dom/Selection.h"
75 #include "mozilla/dom/ShadowRoot.h"
76 #include "mozilla/dom/StaticRange.h"
77 #include "mozilla/dom/Text.h"
78 #include "mozilla/ErrorResult.h"
79 #include "mozilla/dom/SelectionBinding.h"
80 #include "mozilla/AsyncEventDispatcher.h"
81 #include "mozilla/Telemetry.h"
82 #include "mozilla/layers/ScrollInputMethods.h"
84 #include "nsFocusManager.h"
85 #include "nsPIDOMWindow.h"
87 using namespace mozilla
;
88 using namespace mozilla::dom
;
89 using mozilla::layers::ScrollInputMethod
;
91 static LazyLogModule
sFrameSelectionLog("FrameSelection");
93 //#define DEBUG_TABLE 1
96 * Add cells to the selection inside of the given cells range.
98 * @param aTable [in] HTML table element
99 * @param aStartRowIndex [in] row index where the cells range starts
100 * @param aStartColumnIndex [in] column index where the cells range starts
101 * @param aEndRowIndex [in] row index where the cells range ends
102 * @param aEndColumnIndex [in] column index where the cells range ends
104 static nsresult
AddCellsToSelection(const nsIContent
* aTableContent
,
105 int32_t aStartRowIndex
,
106 int32_t aStartColumnIndex
,
107 int32_t aEndRowIndex
,
108 int32_t aEndColumnIndex
,
109 Selection
& aNormalSelection
);
111 static nsAtom
* GetTag(nsINode
* aNode
);
113 static nsINode
* GetClosestInclusiveTableCellAncestor(nsINode
* aDomNode
);
114 MOZ_CAN_RUN_SCRIPT_BOUNDARY
static nsresult
CreateAndAddRange(
115 nsINode
* aContainer
, int32_t aOffset
, Selection
& aNormalSelection
);
116 static nsresult
SelectCellElement(nsIContent
* aCellElement
,
117 Selection
& aNormalSelection
);
120 static nsresult
UpdateSelectionCacheOnRepaintSelection(Selection
* aSel
);
124 static void printRange(nsRange
* aDomRange
);
125 # define DEBUG_OUT_RANGE(x) printRange(x)
127 # define DEBUG_OUT_RANGE(x)
128 #endif // PRINT_RANGE
130 /******************************************************************************
132 ******************************************************************************/
134 //#define DEBUG_SELECTION // uncomment for printf describing every collapse and
135 // extend. #define DEBUG_NAVIGATION
137 //#define DEBUG_TABLE_SELECTION 1
139 nsPeekOffsetStruct::nsPeekOffsetStruct(
140 nsSelectionAmount aAmount
, nsDirection aDirection
, int32_t aStartOffset
,
141 nsPoint aDesiredCaretPos
, bool aJumpLines
, bool aScrollViewStop
,
142 bool aIsKeyboardSelect
, bool aVisual
, bool aExtend
,
143 ForceEditableRegion aForceEditableRegion
,
144 EWordMovementType aWordMovementType
, bool aTrimSpaces
)
146 mDirection(aDirection
),
147 mStartOffset(aStartOffset
),
148 mDesiredCaretPos(aDesiredCaretPos
),
149 mWordMovementType(aWordMovementType
),
150 mJumpLines(aJumpLines
),
151 mTrimSpaces(aTrimSpaces
),
152 mScrollViewStop(aScrollViewStop
),
153 mIsKeyboardSelect(aIsKeyboardSelect
),
156 mForceEditableRegion(aForceEditableRegion
== ForceEditableRegion::Yes
),
158 mResultFrame(nullptr),
160 mAttach(CARET_ASSOCIATE_BEFORE
) {}
162 // Array which contains index of each SelecionType in Selection::mDOMSelections.
163 // For avoiding using if nor switch to retrieve the index, this needs to have
164 // -1 for SelectionTypes which won't be created its Selection instance.
165 static const int8_t kIndexOfSelections
[] = {
166 -1, // SelectionType::eInvalid
167 -1, // SelectionType::eNone
168 0, // SelectionType::eNormal
169 1, // SelectionType::eSpellCheck
170 2, // SelectionType::eIMERawClause
171 3, // SelectionType::eIMESelectedRawClause
172 4, // SelectionType::eIMEConvertedClause
173 5, // SelectionType::eIMESelectedClause
174 6, // SelectionType::eAccessibility
175 7, // SelectionType::eFind
176 8, // SelectionType::eURLSecondary
177 9, // SelectionType::eURLStrikeout
180 inline int8_t GetIndexFromSelectionType(SelectionType aSelectionType
) {
181 // The enum value of eInvalid is -1 and the others are sequential value
182 // starting from 0. Therefore, |SelectionType + 1| is the index of
183 // kIndexOfSelections.
184 return kIndexOfSelections
[static_cast<int8_t>(aSelectionType
) + 1];
188 The limiter is used specifically for the text areas and textfields
189 In that case it is the DIV tag that is anonymously created for the text
190 areas/fields. Text nodes and BR nodes fall beneath it. In the case of a
191 BR node the limiter will be the parent and the offset will point before or
192 after the BR node. In the case of the text node the parent content is
193 the text node itself and the offset will be the exact character position.
194 The offset is not important to check for validity. Simply look at the
195 passed in content. If it equals the limiter then the selection point is valid.
196 If its parent it the limiter then the point is also valid. In the case of
197 NO limiter all points are valid since you are in a topmost iframe. (browser
200 bool nsFrameSelection::IsValidSelectionPoint(nsINode
* aNode
) const {
205 nsIContent
* limiter
= GetLimiter();
206 if (limiter
&& limiter
!= aNode
&& limiter
!= aNode
->GetParent()) {
207 // if newfocus == the limiter. that's ok. but if not there and not parent
209 return false; // not in the right content. tLimiter said so
212 limiter
= GetAncestorLimiter();
213 return !limiter
|| aNode
->IsInclusiveDescendantOf(limiter
);
217 struct MOZ_RAII AutoPrepareFocusRange
{
218 AutoPrepareFocusRange(Selection
* aSelection
,
219 const bool aMultiRangeSelection
) {
220 MOZ_ASSERT(aSelection
);
221 MOZ_ASSERT(aSelection
->GetType() == SelectionType::eNormal
);
223 if (aSelection
->mStyledRanges
.mRanges
.Length() <= 1) {
227 if (aSelection
->mFrameSelection
->IsUserSelectionReason()) {
228 mUserSelect
.emplace(aSelection
);
231 nsTArray
<StyledRange
>& ranges
= aSelection
->mStyledRanges
.mRanges
;
232 if (!aSelection
->mUserInitiated
|| aMultiRangeSelection
) {
233 // Scripted command or the user is starting a new explicit multi-range
235 for (StyledRange
& entry
: ranges
) {
236 entry
.mRange
->SetIsGenerated(false);
241 if (!IsAnchorRelativeOperation(
242 aSelection
->mFrameSelection
->mSelectionChangeReasons
)) {
246 // This operation is against the anchor but our current mAnchorFocusRange
247 // represents the focus in a multi-range selection. The anchor from a user
248 // perspective is the most distant generated range on the opposite side.
249 // Find that range and make it the mAnchorFocusRange.
250 nsRange
* const newAnchorFocusRange
=
251 FindGeneratedRangeMostDistantFromAnchor(*aSelection
);
253 if (!newAnchorFocusRange
) {
254 // There are no generated ranges - that's fine.
258 // Setup the new mAnchorFocusRange and mark the old one as generated.
259 if (aSelection
->mAnchorFocusRange
) {
260 aSelection
->mAnchorFocusRange
->SetIsGenerated(true);
263 newAnchorFocusRange
->SetIsGenerated(false);
264 aSelection
->mAnchorFocusRange
= newAnchorFocusRange
;
266 RemoveGeneratedRanges(*aSelection
);
268 if (aSelection
->mFrameSelection
) {
269 aSelection
->mFrameSelection
->InvalidateDesiredCaretPos();
274 static nsRange
* FindGeneratedRangeMostDistantFromAnchor(
275 const Selection
& aSelection
) {
276 const nsTArray
<StyledRange
>& ranges
= aSelection
.mStyledRanges
.mRanges
;
277 const size_t len
= ranges
.Length();
278 nsRange
* result
{nullptr};
279 if (aSelection
.GetDirection() == eDirNext
) {
280 for (size_t i
= 0; i
< len
; ++i
) {
281 if (ranges
[i
].mRange
->IsGenerated()) {
282 result
= ranges
[i
].mRange
;
289 if (ranges
[i
].mRange
->IsGenerated()) {
290 result
= ranges
[i
].mRange
;
299 static void RemoveGeneratedRanges(Selection
& aSelection
) {
300 RefPtr
<nsPresContext
> presContext
= aSelection
.GetPresContext();
301 nsTArray
<StyledRange
>& ranges
= aSelection
.mStyledRanges
.mRanges
;
302 size_t i
= ranges
.Length();
304 nsRange
* range
= ranges
[i
].mRange
;
305 if (range
->IsGenerated()) {
306 range
->UnregisterSelection();
307 aSelection
.SelectFrames(presContext
, range
, false);
308 ranges
.RemoveElementAt(i
);
314 * @aParam aSelectionChangeReasons can be multiple of the reasons defined in
315 nsISelectionListener.idl.
317 static bool IsAnchorRelativeOperation(const int16_t aSelectionChangeReasons
) {
318 return aSelectionChangeReasons
&
319 (nsISelectionListener::DRAG_REASON
|
320 nsISelectionListener::MOUSEDOWN_REASON
|
321 nsISelectionListener::MOUSEUP_REASON
|
322 nsISelectionListener::COLLAPSETOSTART_REASON
);
325 Maybe
<Selection::AutoUserInitiated
> mUserSelect
;
328 } // namespace mozilla
330 ////////////BEGIN nsFrameSelection methods
332 template Result
<RefPtr
<nsRange
>, nsresult
>
333 nsFrameSelection::CreateRangeExtendedToSomewhere(
334 nsDirection aDirection
, const nsSelectionAmount aAmount
,
335 CaretMovementStyle aMovementStyle
);
336 template Result
<RefPtr
<StaticRange
>, nsresult
>
337 nsFrameSelection::CreateRangeExtendedToSomewhere(
338 nsDirection aDirection
, const nsSelectionAmount aAmount
,
339 CaretMovementStyle aMovementStyle
);
341 nsFrameSelection::nsFrameSelection(PresShell
* aPresShell
, nsIContent
* aLimiter
,
342 const bool aAccessibleCaretEnabled
) {
343 for (size_t i
= 0; i
< ArrayLength(mDomSelections
); i
++) {
344 mDomSelections
[i
] = new Selection(kPresentSelectionTypes
[i
], this);
348 // On macOS, cache the current selection to send to service menu of macOS.
349 bool enableAutoCopy
= true;
350 AutoCopyListener::Init(nsIClipboard::kSelectionCache
);
351 #else // #ifdef XP_MACOSX
352 // Check to see if the auto-copy pref is enabled and make the normal
353 // Selection notifies auto-copy listener of its changes.
354 bool enableAutoCopy
= AutoCopyListener::IsPrefEnabled();
355 if (enableAutoCopy
) {
356 AutoCopyListener::Init(nsIClipboard::kSelectionClipboard
);
358 #endif // #ifdef XP_MACOSX #else
360 if (enableAutoCopy
) {
361 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
362 if (mDomSelections
[index
]) {
363 mDomSelections
[index
]->NotifyAutoCopy();
367 mPresShell
= aPresShell
;
369 mLimiters
.mLimiter
= aLimiter
;
371 // This should only ever be initialized on the main thread, so we are OK here.
372 MOZ_ASSERT(NS_IsMainThread());
374 mAccessibleCaretEnabled
= aAccessibleCaretEnabled
;
375 if (mAccessibleCaretEnabled
) {
376 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
377 mDomSelections
[index
]->MaybeNotifyAccessibleCaretEventHub(aPresShell
);
380 bool plaintextControl
= (aLimiter
!= nullptr);
381 bool initSelectEvents
=
382 plaintextControl
? StaticPrefs::dom_select_events_textcontrols_enabled()
383 : StaticPrefs::dom_select_events_enabled();
385 Document
* doc
= aPresShell
->GetDocument();
386 if (initSelectEvents
|| (doc
&& doc
->NodePrincipal()->IsSystemPrincipal())) {
387 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
388 if (mDomSelections
[index
]) {
389 mDomSelections
[index
]->EnableSelectionChangeEvent();
394 nsFrameSelection::~nsFrameSelection() = default;
396 NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameSelection
)
398 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameSelection
)
399 for (size_t i
= 0; i
< ArrayLength(tmp
->mDomSelections
); ++i
) {
400 tmp
->mDomSelections
[i
] = nullptr;
403 NS_IMPL_CYCLE_COLLECTION_UNLINK(
404 mTableSelection
.mClosestInclusiveTableCellAncestor
)
405 tmp
->mTableSelection
.mMode
= TableSelectionMode::None
;
406 tmp
->mTableSelection
.mDragSelectingCells
= false;
407 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection
.mStartSelectedCell
)
408 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection
.mEndSelectedCell
)
409 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection
.mAppendStartSelectedCell
)
410 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection
.mUnselectCellOnMouseUp
)
411 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMaintainedRange
.mRange
)
412 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiters
.mLimiter
)
413 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiters
.mAncestorLimiter
)
414 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
415 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameSelection
)
416 if (tmp
->mPresShell
&& tmp
->mPresShell
->GetDocument() &&
417 nsCCUncollectableMarker::InGeneration(
418 cb
, tmp
->mPresShell
->GetDocument()->GetMarkedCCGeneration())) {
419 return NS_SUCCESS_INTERRUPTED_TRAVERSE
;
421 for (size_t i
= 0; i
< ArrayLength(tmp
->mDomSelections
); ++i
) {
422 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDomSelections
[i
])
425 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
426 mTableSelection
.mClosestInclusiveTableCellAncestor
)
427 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection
.mStartSelectedCell
)
428 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection
.mEndSelectedCell
)
429 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection
.mAppendStartSelectedCell
)
430 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection
.mUnselectCellOnMouseUp
)
431 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMaintainedRange
.mRange
)
432 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiters
.mLimiter
)
433 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiters
.mAncestorLimiter
)
434 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
436 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsFrameSelection
, AddRef
)
437 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsFrameSelection
, Release
)
439 bool nsFrameSelection::Caret::IsVisualMovement(
440 bool aContinueSelection
, CaretMovementStyle aMovementStyle
) const {
441 int32_t movementFlag
= StaticPrefs::bidi_edit_caret_movement_style();
442 return aMovementStyle
== eVisual
||
443 (aMovementStyle
== eUsePrefStyle
&&
444 (movementFlag
== 1 || (movementFlag
== 2 && !aContinueSelection
)));
447 // Get the x (or y, in vertical writing mode) position requested
448 // by the Key Handling for line-up/down
449 nsresult
nsFrameSelection::DesiredCaretPos::FetchPos(
450 nsPoint
& aDesiredCaretPos
, const PresShell
& aPresShell
,
451 Selection
& aNormalSelection
) const {
452 MOZ_ASSERT(aNormalSelection
.GetType() == SelectionType::eNormal
);
455 aDesiredCaretPos
= mValue
;
459 RefPtr
<nsCaret
> caret
= aPresShell
.GetCaret();
461 return NS_ERROR_NULL_POINTER
;
464 caret
->SetSelection(&aNormalSelection
);
467 nsIFrame
* caretFrame
= caret
->GetGeometry(&coord
);
469 return NS_ERROR_FAILURE
;
471 nsPoint
viewOffset(0, 0);
472 nsView
* view
= nullptr;
473 caretFrame
->GetOffsetFromView(viewOffset
, &view
);
477 aDesiredCaretPos
= coord
.TopLeft();
481 void nsFrameSelection::InvalidateDesiredCaretPos() // do not listen to
482 // mDesiredCaretPos.mValue;
483 // you must get another.
485 mDesiredCaretPos
.Invalidate();
488 void nsFrameSelection::DesiredCaretPos::Invalidate() { mIsSet
= false; }
490 void nsFrameSelection::DesiredCaretPos::Set(const nsPoint
& aPos
) {
495 nsresult
nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(
496 nsIFrame
* aFrame
, const nsPoint
& aPoint
, nsIFrame
** aRetFrame
,
497 nsPoint
& aRetPoint
) const {
499 // The whole point of this method is to return a frame and point that
500 // that lie within the same valid subtree as the anchor node's frame,
501 // for use with the method GetContentAndOffsetsFromPoint().
503 // A valid subtree is defined to be one where all the content nodes in
504 // the tree have a valid parent-child relationship.
506 // If the anchor frame and aFrame are in the same subtree, aFrame will
507 // be returned in aRetFrame. If they are in different subtrees, we
508 // return the frame for the root of the subtree.
511 if (!aFrame
|| !aRetFrame
) {
512 return NS_ERROR_NULL_POINTER
;
519 // Get the frame and content for the selection's anchor point!
522 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
523 if (!mDomSelections
[index
]) {
524 return NS_ERROR_NULL_POINTER
;
527 nsCOMPtr
<nsIContent
> anchorContent
=
528 do_QueryInterface(mDomSelections
[index
]->GetAnchorNode());
529 if (!anchorContent
) {
530 return NS_ERROR_FAILURE
;
534 // Now find the root of the subtree containing the anchor's content.
537 NS_ENSURE_STATE(mPresShell
);
538 RefPtr
<PresShell
> presShell
= mPresShell
;
539 nsIContent
* anchorRoot
= anchorContent
->GetSelectionRootContent(presShell
);
540 NS_ENSURE_TRUE(anchorRoot
, NS_ERROR_UNEXPECTED
);
543 // Now find the root of the subtree containing aFrame's content.
546 nsCOMPtr
<nsIContent
> content
= aFrame
->GetContent();
549 nsIContent
* contentRoot
= content
->GetSelectionRootContent(presShell
);
550 NS_ENSURE_TRUE(contentRoot
, NS_ERROR_UNEXPECTED
);
552 if (anchorRoot
== contentRoot
) {
553 // If the aFrame's content isn't the capturing content, it should be
554 // a descendant. At this time, we can return simply.
555 nsIContent
* capturedContent
= PresShell::GetCapturingContent();
556 if (capturedContent
!= content
) {
560 // Find the frame under the mouse cursor with the root frame.
561 // At this time, don't use the anchor's frame because it may not have
562 // fixed positioned frames.
563 nsIFrame
* rootFrame
= presShell
->GetRootFrame();
564 nsPoint ptInRoot
= aPoint
+ aFrame
->GetOffsetTo(rootFrame
);
565 nsIFrame
* cursorFrame
=
566 nsLayoutUtils::GetFrameForPoint(RelativeTo
{rootFrame
}, ptInRoot
);
568 // If the mouse cursor in on a frame which is descendant of same
569 // selection root, we can expand the selection to the frame.
570 if (cursorFrame
&& cursorFrame
->PresShell() == presShell
) {
571 nsCOMPtr
<nsIContent
> cursorContent
= cursorFrame
->GetContent();
572 NS_ENSURE_TRUE(cursorContent
, NS_ERROR_FAILURE
);
573 nsIContent
* cursorContentRoot
=
574 cursorContent
->GetSelectionRootContent(presShell
);
575 NS_ENSURE_TRUE(cursorContentRoot
, NS_ERROR_UNEXPECTED
);
576 if (cursorContentRoot
== anchorRoot
) {
577 *aRetFrame
= cursorFrame
;
578 aRetPoint
= aPoint
+ aFrame
->GetOffsetTo(cursorFrame
);
582 // Otherwise, e.g., the cursor isn't on any frames (e.g., the mouse
583 // cursor is out of the window), we should use the frame of the anchor
589 // When we can't find a frame which is under the mouse cursor and has a same
590 // selection root as the anchor node's, we should return the selection root
594 *aRetFrame
= anchorRoot
->GetPrimaryFrame();
597 return NS_ERROR_FAILURE
;
601 // Now make sure that aRetPoint is converted to the same coordinate
602 // system used by aRetFrame.
605 aRetPoint
= aPoint
+ aFrame
->GetOffsetTo(*aRetFrame
);
610 void nsFrameSelection::SetCaretBidiLevelAndMaybeSchedulePaint(
611 nsBidiLevel aLevel
) {
612 // If the current level is undefined, we have just inserted new text.
613 // In this case, we don't want to reset the keyboard language
614 mCaret
.mBidiLevel
= aLevel
;
616 RefPtr
<nsCaret
> caret
;
617 if (mPresShell
&& (caret
= mPresShell
->GetCaret())) {
618 caret
->SchedulePaint();
622 nsBidiLevel
nsFrameSelection::GetCaretBidiLevel() const {
623 return mCaret
.mBidiLevel
;
626 void nsFrameSelection::UndefineCaretBidiLevel() {
627 mCaret
.mBidiLevel
|= BIDI_LEVEL_UNDEFINED
;
631 void printRange(nsRange
* aDomRange
) {
633 printf("NULL Range\n");
635 nsINode
* startNode
= aDomRange
->GetStartContainer();
636 nsINode
* endNode
= aDomRange
->GetEndContainer();
637 int32_t startOffset
= aDomRange
->StartOffset();
638 int32_t endOffset
= aDomRange
->EndOffset();
640 printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n",
641 (unsigned long)aDomRange
, (unsigned long)startNode
, (long)startOffset
,
642 (unsigned long)endNode
, (long)endOffset
);
644 #endif /* PRINT_RANGE */
646 static nsAtom
* GetTag(nsINode
* aNode
) {
647 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aNode
);
649 MOZ_ASSERT_UNREACHABLE("bad node passed to GetTag()");
653 return content
->NodeInfo()->NameAtom();
657 * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor.
659 static nsINode
* GetClosestInclusiveTableCellAncestor(nsINode
* aDomNode
) {
660 if (!aDomNode
) return nullptr;
661 nsINode
* current
= aDomNode
;
662 // Start with current node and look for a table cell
664 nsAtom
* tag
= GetTag(current
);
665 if (tag
== nsGkAtoms::td
|| tag
== nsGkAtoms::th
) return current
;
666 current
= current
->GetParent();
671 static nsDirection
GetCaretDirection(const nsIFrame
& aFrame
,
672 nsDirection aDirection
,
673 bool aVisualMovement
) {
674 const nsBidiDirection paragraphDirection
=
675 nsBidiPresUtils::ParagraphDirection(&aFrame
);
676 return (aVisualMovement
&& paragraphDirection
== NSBIDI_RTL
)
677 ? nsDirection(1 - aDirection
)
681 nsresult
nsFrameSelection::MoveCaret(nsDirection aDirection
,
682 bool aContinueSelection
,
683 const nsSelectionAmount aAmount
,
684 CaretMovementStyle aMovementStyle
) {
685 NS_ENSURE_STATE(mPresShell
);
686 // Flush out layout, since we need it to be up to date to do caret
688 OwningNonNull
<PresShell
> presShell(*mPresShell
);
689 presShell
->FlushPendingNotifications(FlushType::Layout
);
695 nsPresContext
* context
= mPresShell
->GetPresContext();
697 return NS_ERROR_FAILURE
;
700 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
701 const RefPtr
<Selection
> sel
= mDomSelections
[index
];
703 return NS_ERROR_NULL_POINTER
;
706 int32_t scrollFlags
= Selection::SCROLL_FOR_CARET_MOVE
;
707 if (sel
->IsEditorSelection()) {
708 // If caret moves in editor, it should cause scrolling even if it's in
709 // overflow: hidden;.
710 scrollFlags
|= Selection::SCROLL_OVERFLOW_HIDDEN
;
713 int32_t caretStyle
= StaticPrefs::layout_selection_caret_style();
716 && aAmount
!= eSelectLine
719 // Put caret at the selection edge in the |aDirection| direction.
723 const bool doCollapse
= !sel
->IsCollapsed() && !aContinueSelection
&&
724 caretStyle
== 2 && aAmount
<= eSelectLine
;
726 if (aDirection
== eDirPrevious
) {
727 SetChangeReasons(nsISelectionListener::COLLAPSETOSTART_REASON
);
728 mCaret
.mHint
= CARET_ASSOCIATE_AFTER
;
730 SetChangeReasons(nsISelectionListener::COLLAPSETOEND_REASON
);
731 mCaret
.mHint
= CARET_ASSOCIATE_BEFORE
;
734 SetChangeReasons(nsISelectionListener::KEYPRESS_REASON
);
737 AutoPrepareFocusRange
prep(sel
, false);
739 // we must keep this around and revalidate it when its just UP/DOWN
740 nsPoint
desiredPos(0, 0);
742 if (aAmount
== eSelectLine
) {
743 nsresult result
= mDesiredCaretPos
.FetchPos(desiredPos
, *mPresShell
, *sel
);
744 if (NS_FAILED(result
)) {
747 mDesiredCaretPos
.Set(desiredPos
);
750 bool visualMovement
=
751 mCaret
.IsVisualMovement(aContinueSelection
, aMovementStyle
);
752 nsIFrame
* frame
= sel
->GetPrimaryFrameForFocusNode(visualMovement
);
754 return NS_ERROR_FAILURE
;
757 Result
<bool, nsresult
> isIntraLineCaretMove
= IsIntraLineCaretMove(aAmount
);
758 nsDirection direction
{aDirection
};
759 if (isIntraLineCaretMove
.isErr()) {
760 return isIntraLineCaretMove
.unwrapErr();
762 if (isIntraLineCaretMove
.inspect()) {
763 // Forget old caret position for moving caret to different line since
764 // caret position may be changed.
765 mDesiredCaretPos
.Invalidate();
766 direction
= GetCaretDirection(*frame
, aDirection
, visualMovement
);
770 const nsRange
* anchorFocusRange
= sel
->GetAnchorFocusRange();
771 if (anchorFocusRange
) {
772 RefPtr
<nsINode
> node
;
774 if (visualMovement
&& nsBidiPresUtils::IsReversedDirectionFrame(frame
)) {
775 direction
= nsDirection(1 - direction
);
777 if (direction
== eDirPrevious
) {
778 node
= anchorFocusRange
->GetStartContainer();
779 offset
= anchorFocusRange
->StartOffset();
781 node
= anchorFocusRange
->GetEndContainer();
782 offset
= anchorFocusRange
->EndOffset();
784 sel
->CollapseInLimiter(node
, offset
);
786 sel
->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION
,
787 ScrollAxis(), ScrollAxis(), scrollFlags
);
791 CaretAssociateHint
tHint(mCaret
.mHint
); // temporary variable so we dont set
792 // mCaret.mHint until it is necessary
794 Result
<nsPeekOffsetStruct
, nsresult
> result
= PeekOffsetForCaretMove(
795 direction
, aContinueSelection
, aAmount
, aMovementStyle
, desiredPos
);
797 if (result
.isOk() && result
.inspect().mResultContent
) {
798 const nsPeekOffsetStruct
& pos
= result
.inspect();
800 int32_t currentOffset
, frameStart
, frameEnd
;
802 if (aAmount
<= eSelectWordNoSpace
) {
803 // For left/right, PeekOffset() sets pos.mResultFrame correctly, but does
804 // not set pos.mAttachForward, so determine the hint here based on the
805 // result frame and offset: If we're at the end of a text frame, set the
806 // hint to ASSOCIATE_BEFORE to indicate that we want the caret displayed
807 // at the end of this frame, not at the beginning of the next one.
808 theFrame
= pos
.mResultFrame
;
809 theFrame
->GetOffsets(frameStart
, frameEnd
);
810 currentOffset
= pos
.mContentOffset
;
811 if (frameEnd
== currentOffset
&& !(frameStart
== 0 && frameEnd
== 0))
812 tHint
= CARET_ASSOCIATE_BEFORE
;
814 tHint
= CARET_ASSOCIATE_AFTER
;
816 // For up/down and home/end, pos.mResultFrame might not be set correctly,
817 // or not at all. In these cases, get the frame based on the content and
818 // hint returned by PeekOffset().
820 theFrame
= GetFrameForNodeOffset(pos
.mResultContent
, pos
.mContentOffset
,
821 tHint
, ¤tOffset
);
822 if (!theFrame
) return NS_ERROR_FAILURE
;
824 theFrame
->GetOffsets(frameStart
, frameEnd
);
827 if (context
->BidiEnabled()) {
829 case eSelectBeginLine
:
830 case eSelectEndLine
: {
831 // In Bidi contexts, PeekOffset calculates pos.mContentOffset
832 // differently depending on whether the movement is visual or logical.
833 // For visual movement, pos.mContentOffset depends on the direction-
834 // ality of the first/last frame on the line (theFrame), and the caret
835 // directionality must correspond.
836 FrameBidiData bidiData
= theFrame
->GetBidiData();
837 SetCaretBidiLevelAndMaybeSchedulePaint(
838 visualMovement
? bidiData
.embeddingLevel
: bidiData
.baseLevel
);
842 // If the current position is not a frame boundary, it's enough just
843 // to take the Bidi level of the current frame
844 if ((pos
.mContentOffset
!= frameStart
&&
845 pos
.mContentOffset
!= frameEnd
) ||
846 eSelectLine
== aAmount
) {
847 SetCaretBidiLevelAndMaybeSchedulePaint(
848 theFrame
->GetEmbeddingLevel());
850 BidiLevelFromMove(mPresShell
, pos
.mResultContent
,
851 pos
.mContentOffset
, aAmount
, tHint
);
855 // "pos" is on the stack, so pos.mResultContent has stack lifetime, so using
856 // MOZ_KnownLive is ok.
857 const FocusMode focusMode
= aContinueSelection
858 ? FocusMode::kExtendSelection
859 : FocusMode::kCollapseToNewPoint
;
860 rv
= TakeFocus(MOZ_KnownLive(pos
.mResultContent
), pos
.mContentOffset
,
861 pos
.mContentOffset
, tHint
, focusMode
);
862 } else if (aAmount
<= eSelectWordNoSpace
&& direction
== eDirNext
&&
863 !aContinueSelection
) {
864 // Collapse selection if PeekOffset failed, we either
865 // 1. bumped into the BRFrame, bug 207623
866 // 2. had select-all in a text input (DIV range), bug 352759.
867 bool isBRFrame
= frame
->IsBrFrame();
868 RefPtr
<nsINode
> node
= sel
->GetFocusNode();
869 sel
->CollapseInLimiter(node
, sel
->FocusOffset());
870 // Note: 'frame' might be dead here.
872 mCaret
.mHint
= CARET_ASSOCIATE_BEFORE
; // We're now at the end of the
873 // frame to the left.
877 rv
= result
.isErr() ? result
.unwrapErr() : NS_OK
;
879 if (NS_SUCCEEDED(rv
)) {
880 rv
= sel
->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION
,
881 ScrollAxis(), ScrollAxis(), scrollFlags
);
887 Result
<nsPeekOffsetStruct
, nsresult
> nsFrameSelection::PeekOffsetForCaretMove(
888 nsDirection aDirection
, bool aContinueSelection
,
889 const nsSelectionAmount aAmount
, CaretMovementStyle aMovementStyle
,
890 const nsPoint
& aDesiredCaretPos
) const {
891 Selection
* selection
=
892 mDomSelections
[GetIndexFromSelectionType(SelectionType::eNormal
)];
894 return Err(NS_ERROR_NULL_POINTER
);
897 const bool visualMovement
=
898 mCaret
.IsVisualMovement(aContinueSelection
, aMovementStyle
);
900 int32_t offsetused
= 0;
902 selection
->GetPrimaryFrameForFocusNode(visualMovement
, &offsetused
);
904 return Err(NS_ERROR_FAILURE
);
907 const auto kForceEditableRegion
=
908 selection
->IsEditorSelection()
909 ? nsPeekOffsetStruct::ForceEditableRegion::Yes
910 : nsPeekOffsetStruct::ForceEditableRegion::No
;
912 // set data using mLimiters.mLimiter to stop on scroll views. If we have a
913 // limiter then we stop peeking when we hit scrollable views. If no limiter
914 // then just let it go ahead
915 nsPeekOffsetStruct
pos(aAmount
, aDirection
, offsetused
, aDesiredCaretPos
,
916 true, !!mLimiters
.mLimiter
, true, visualMovement
,
917 aContinueSelection
, kForceEditableRegion
);
918 nsresult rv
= frame
->PeekOffset(&pos
);
925 nsPrevNextBidiLevels
nsFrameSelection::GetPrevNextBidiLevels(
926 nsIContent
* aNode
, uint32_t aContentOffset
, bool aJumpLines
) const {
927 return GetPrevNextBidiLevels(aNode
, aContentOffset
, mCaret
.mHint
, aJumpLines
);
931 nsPrevNextBidiLevels
nsFrameSelection::GetPrevNextBidiLevels(
932 nsIContent
* aNode
, uint32_t aContentOffset
, CaretAssociateHint aHint
,
934 // Get the level of the frames on each side
935 nsIFrame
* currentFrame
;
936 int32_t currentOffset
;
937 int32_t frameStart
, frameEnd
;
938 nsDirection direction
;
940 nsPrevNextBidiLevels levels
;
941 levels
.SetData(nullptr, nullptr, 0, 0);
944 GetFrameForNodeOffset(aNode
, aContentOffset
, aHint
, ¤tOffset
);
945 if (!currentFrame
) return levels
;
947 currentFrame
->GetOffsets(frameStart
, frameEnd
);
949 if (0 == frameStart
&& 0 == frameEnd
)
950 direction
= eDirPrevious
;
951 else if (frameStart
== currentOffset
)
952 direction
= eDirPrevious
;
953 else if (frameEnd
== currentOffset
)
954 direction
= eDirNext
;
956 // we are neither at the beginning nor at the end of the frame, so we have
958 nsBidiLevel currentLevel
= currentFrame
->GetEmbeddingLevel();
959 levels
.SetData(currentFrame
, currentFrame
, currentLevel
, currentLevel
);
965 ->GetFrameFromDirection(direction
, false, aJumpLines
, true, false)
968 FrameBidiData currentBidi
= currentFrame
->GetBidiData();
969 nsBidiLevel currentLevel
= currentBidi
.embeddingLevel
;
970 nsBidiLevel newLevel
=
971 newFrame
? newFrame
->GetEmbeddingLevel() : currentBidi
.baseLevel
;
973 // If not jumping lines, disregard br frames, since they might be positioned
975 // XXX This could be removed once bug 339786 is fixed.
977 if (currentFrame
->IsBrFrame()) {
978 currentFrame
= nullptr;
979 currentLevel
= currentBidi
.baseLevel
;
981 if (newFrame
&& newFrame
->IsBrFrame()) {
983 newLevel
= currentBidi
.baseLevel
;
987 if (direction
== eDirNext
)
988 levels
.SetData(currentFrame
, newFrame
, currentLevel
, newLevel
);
990 levels
.SetData(newFrame
, currentFrame
, newLevel
, currentLevel
);
995 nsresult
nsFrameSelection::GetFrameFromLevel(nsIFrame
* aFrameIn
,
996 nsDirection aDirection
,
997 nsBidiLevel aBidiLevel
,
998 nsIFrame
** aFrameOut
) const {
999 NS_ENSURE_STATE(mPresShell
);
1000 nsBidiLevel foundLevel
= 0;
1001 nsIFrame
* foundFrame
= aFrameIn
;
1003 nsCOMPtr
<nsIFrameEnumerator
> frameTraversal
;
1005 nsCOMPtr
<nsIFrameTraversal
> trav(
1006 do_CreateInstance(kFrameTraversalCID
, &result
));
1007 if (NS_FAILED(result
)) return result
;
1010 trav
->NewFrameTraversal(getter_AddRefs(frameTraversal
),
1011 mPresShell
->GetPresContext(), aFrameIn
, eLeaf
,
1013 false, // aLockInScrollView
1014 false, // aFollowOOFs
1015 false // aSkipPopupChecks
1017 if (NS_FAILED(result
)) return result
;
1020 *aFrameOut
= foundFrame
;
1021 foundFrame
= frameTraversal
->Traverse(aDirection
== eDirNext
);
1022 if (!foundFrame
) return NS_ERROR_FAILURE
;
1023 foundLevel
= foundFrame
->GetEmbeddingLevel();
1025 } while (foundLevel
> aBidiLevel
);
1030 nsresult
nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount
) {
1031 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1032 if (!mDomSelections
[index
]) {
1033 return NS_ERROR_NULL_POINTER
;
1036 mMaintainedRange
.MaintainAnchorFocusRange(*mDomSelections
[index
], aAmount
);
1041 void nsFrameSelection::BidiLevelFromMove(PresShell
* aPresShell
,
1043 uint32_t aContentOffset
,
1044 nsSelectionAmount aAmount
,
1045 CaretAssociateHint aHint
) {
1047 // Movement within the line: the new cursor Bidi level is the level of the
1048 // last character moved over
1049 case eSelectCharacter
:
1050 case eSelectCluster
:
1052 case eSelectWordNoSpace
:
1053 case eSelectBeginLine
:
1054 case eSelectEndLine
:
1055 case eSelectNoAmount
: {
1056 nsPrevNextBidiLevels levels
=
1057 GetPrevNextBidiLevels(aNode
, aContentOffset
, aHint
, false);
1059 SetCaretBidiLevelAndMaybeSchedulePaint(aHint
== CARET_ASSOCIATE_BEFORE
1060 ? levels
.mLevelBefore
1061 : levels
.mLevelAfter
);
1065 // Up and Down: the new cursor Bidi level is the smaller of the two
1066 surrounding characters case eSelectLine: case eSelectParagraph:
1067 GetPrevNextBidiLevels(aContext, aNode, aContentOffset, &firstFrame,
1068 &secondFrame, &firstLevel, &secondLevel);
1069 aPresShell->SetCaretBidiLevelAndMaybeSchedulePaint(std::min(firstLevel,
1070 secondLevel)); break;
1074 UndefineCaretBidiLevel();
1078 void nsFrameSelection::BidiLevelFromClick(nsIContent
* aNode
,
1079 uint32_t aContentOffset
) {
1080 nsIFrame
* clickInFrame
= nullptr;
1081 int32_t OffsetNotUsed
;
1083 clickInFrame
= GetFrameForNodeOffset(aNode
, aContentOffset
, mCaret
.mHint
,
1085 if (!clickInFrame
) return;
1087 SetCaretBidiLevelAndMaybeSchedulePaint(clickInFrame
->GetEmbeddingLevel());
1090 void nsFrameSelection::MaintainedRange::AdjustNormalSelection(
1091 const nsIContent
* aContent
, const int32_t aOffset
,
1092 Selection
& aNormalSelection
) const {
1093 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
1095 if (!mRange
|| !aContent
) {
1099 nsINode
* rangeStartNode
= mRange
->GetStartContainer();
1100 nsINode
* rangeEndNode
= mRange
->GetEndContainer();
1101 int32_t rangeStartOffset
= mRange
->StartOffset();
1102 int32_t rangeEndOffset
= mRange
->EndOffset();
1104 const Maybe
<int32_t> relToStart
= nsContentUtils::ComparePoints(
1105 rangeStartNode
, rangeStartOffset
, aContent
, aOffset
);
1106 if (NS_WARN_IF(!relToStart
)) {
1107 // Potentially handle this properly when Selection across Shadow DOM
1108 // boundary is implemented
1109 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
1113 const Maybe
<int32_t> relToEnd
= nsContentUtils::ComparePoints(
1114 rangeEndNode
, rangeEndOffset
, aContent
, aOffset
);
1115 if (NS_WARN_IF(!relToEnd
)) {
1116 // Potentially handle this properly when Selection across Shadow DOM
1117 // boundary is implemented
1118 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
1122 // If aContent/aOffset is inside (or at the edge of) the maintained
1123 // selection, or if it is on the "anchor" side of the maintained selection,
1124 // we need to do something.
1125 if ((*relToStart
<= 0 && *relToEnd
>= 0) ||
1126 (*relToStart
> 0 && aNormalSelection
.GetDirection() == eDirNext
) ||
1127 (*relToEnd
< 0 && aNormalSelection
.GetDirection() == eDirPrevious
)) {
1128 // Set the current range to the maintained range.
1129 aNormalSelection
.ReplaceAnchorFocusRange(mRange
);
1130 // Set the direction of the selection so that the anchor will be on the
1131 // far side of the maintained selection, relative to aContent/aOffset.
1132 aNormalSelection
.SetDirection(*relToStart
> 0 ? eDirPrevious
: eDirNext
);
1136 void nsFrameSelection::MaintainedRange::AdjustContentOffsets(
1137 nsIFrame::ContentOffsets
& aOffsets
, const bool aScrollViewStop
) const {
1138 // Adjust offsets according to maintained amount
1139 if (mRange
&& mAmount
!= eSelectNoAmount
) {
1140 nsINode
* rangenode
= mRange
->GetStartContainer();
1141 int32_t rangeOffset
= mRange
->StartOffset();
1142 const Maybe
<int32_t> relativePosition
= nsContentUtils::ComparePoints(
1143 rangenode
, rangeOffset
, aOffsets
.content
, aOffsets
.offset
);
1144 if (NS_WARN_IF(!relativePosition
)) {
1145 // Potentially handle this properly when Selection across Shadow DOM
1146 // boundary is implemented
1147 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
1151 nsDirection direction
= *relativePosition
> 0 ? eDirPrevious
: eDirNext
;
1152 nsSelectionAmount amount
= mAmount
;
1153 if (amount
== eSelectBeginLine
&& direction
== eDirNext
) {
1154 amount
= eSelectEndLine
;
1158 nsIFrame
* frame
= GetFrameForNodeOffset(aOffsets
.content
, aOffsets
.offset
,
1159 CARET_ASSOCIATE_AFTER
, &offset
);
1161 if (frame
&& amount
== eSelectWord
&& direction
== eDirPrevious
) {
1162 // To avoid selecting the previous word when at start of word,
1163 // first move one character forward.
1164 nsPeekOffsetStruct
charPos(eSelectCharacter
, eDirNext
, offset
,
1165 nsPoint(0, 0), false, aScrollViewStop
, false,
1167 if (NS_SUCCEEDED(frame
->PeekOffset(&charPos
))) {
1168 frame
= charPos
.mResultFrame
;
1169 offset
= charPos
.mContentOffset
;
1173 nsPeekOffsetStruct
pos(amount
, direction
, offset
, nsPoint(0, 0), false,
1174 aScrollViewStop
, false, false, false);
1176 if (frame
&& NS_SUCCEEDED(frame
->PeekOffset(&pos
)) && pos
.mResultContent
) {
1177 aOffsets
.content
= pos
.mResultContent
;
1178 aOffsets
.offset
= pos
.mContentOffset
;
1183 void nsFrameSelection::MaintainedRange::MaintainAnchorFocusRange(
1184 const Selection
& aNormalSelection
, const nsSelectionAmount aAmount
) {
1185 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
1189 const nsRange
* anchorFocusRange
= aNormalSelection
.GetAnchorFocusRange();
1190 if (anchorFocusRange
&& aAmount
!= eSelectNoAmount
) {
1191 mRange
= anchorFocusRange
->CloneRange();
1198 nsresult
nsFrameSelection::HandleClick(nsIContent
* aNewFocus
,
1199 uint32_t aContentOffset
,
1200 uint32_t aContentEndOffset
,
1201 const FocusMode aFocusMode
,
1202 CaretAssociateHint aHint
) {
1203 if (!aNewFocus
) return NS_ERROR_INVALID_ARG
;
1205 if (MOZ_LOG_TEST(sFrameSelectionLog
, LogLevel::Debug
)) {
1206 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1207 MOZ_LOG(sFrameSelectionLog
, LogLevel::Debug
,
1208 ("%s: selection=%p, new focus=%p, offsets=(%u,%u), focus mode=%i",
1210 mDomSelections
[index
] ? mDomSelections
[index
].get() : nullptr,
1211 aNewFocus
, aContentOffset
, aContentEndOffset
,
1212 static_cast<int>(aFocusMode
)));
1215 mDesiredCaretPos
.Invalidate();
1217 if (aFocusMode
!= FocusMode::kExtendSelection
) {
1218 mMaintainedRange
.mRange
= nullptr;
1219 if (!IsValidSelectionPoint(aNewFocus
)) {
1220 mLimiters
.mAncestorLimiter
= nullptr;
1224 // Don't take focus when dragging off of a table
1225 if (!mTableSelection
.mDragSelectingCells
) {
1226 BidiLevelFromClick(aNewFocus
, aContentOffset
);
1227 SetChangeReasons(nsISelectionListener::MOUSEDOWN_REASON
+
1228 nsISelectionListener::DRAG_REASON
);
1230 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1231 RefPtr
<Selection
> selection
= mDomSelections
[index
];
1232 MOZ_ASSERT(selection
);
1234 if (aFocusMode
== FocusMode::kExtendSelection
) {
1235 mMaintainedRange
.AdjustNormalSelection(aNewFocus
, aContentOffset
,
1239 AutoPrepareFocusRange
prep(selection
,
1240 aFocusMode
== FocusMode::kMultiRangeSelection
);
1241 return TakeFocus(aNewFocus
, aContentOffset
, aContentEndOffset
, aHint
,
1248 void nsFrameSelection::HandleDrag(nsIFrame
* aFrame
, const nsPoint
& aPoint
) {
1249 if (!aFrame
|| !mPresShell
) {
1254 nsIFrame
* newFrame
= 0;
1257 result
= ConstrainFrameAndPointToAnchorSubtree(aFrame
, aPoint
, &newFrame
,
1259 if (NS_FAILED(result
)) return;
1260 if (!newFrame
) return;
1262 nsIFrame::ContentOffsets offsets
=
1263 newFrame
->GetContentOffsetsFromPoint(newPoint
);
1264 if (!offsets
.content
) return;
1266 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1267 RefPtr
<Selection
> selection
= mDomSelections
[index
];
1268 if (newFrame
->IsSelected() && selection
) {
1269 // `MOZ_KnownLive` required because of
1270 // https://bugzilla.mozilla.org/show_bug.cgi?id=1636889.
1271 mMaintainedRange
.AdjustNormalSelection(MOZ_KnownLive(offsets
.content
),
1272 offsets
.offset
, *selection
);
1275 const bool scrollViewStop
= mLimiters
.mLimiter
!= nullptr;
1276 mMaintainedRange
.AdjustContentOffsets(offsets
, scrollViewStop
);
1278 // TODO: no click has happened, rename `HandleClick`.
1279 HandleClick(MOZ_KnownLive(offsets
.content
) /* bug 1636889 */, offsets
.offset
,
1280 offsets
.offset
, FocusMode::kExtendSelection
, offsets
.associate
);
1283 nsresult
nsFrameSelection::StartAutoScrollTimer(nsIFrame
* aFrame
,
1284 const nsPoint
& aPoint
,
1286 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1287 if (!mDomSelections
[index
]) {
1288 return NS_ERROR_NULL_POINTER
;
1291 RefPtr
<Selection
> selection
= mDomSelections
[index
];
1292 return selection
->StartAutoScrollTimer(aFrame
, aPoint
, aDelay
);
1295 void nsFrameSelection::StopAutoScrollTimer() {
1296 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1297 if (!mDomSelections
[index
]) {
1301 mDomSelections
[index
]->StopAutoScrollTimer();
1305 nsINode
* nsFrameSelection::TableSelection::IsContentInActivelyEditableTableCell(
1306 nsPresContext
* aContext
, nsIContent
* aContent
) {
1311 RefPtr
<HTMLEditor
> htmlEditor
= nsContentUtils::GetHTMLEditor(aContext
);
1316 nsINode
* inclusiveTableCellAncestor
=
1317 GetClosestInclusiveTableCellAncestor(aContent
);
1318 if (!inclusiveTableCellAncestor
) {
1322 const Element
* editorHostNode
= htmlEditor
->GetActiveEditingHost();
1323 if (!editorHostNode
) {
1327 const bool editableCell
=
1328 inclusiveTableCellAncestor
->IsInclusiveDescendantOf(editorHostNode
);
1329 return editableCell
? inclusiveTableCellAncestor
: nullptr;
1333 struct ParentAndOffset
{
1334 explicit ParentAndOffset(const nsINode
& aNode
)
1335 : mParent
{aNode
.GetParent()},
1336 mOffset
{mParent
? mParent
->ComputeIndexOf(&aNode
) : 0} {}
1340 // 0, if there's no parent.
1346 hard to go from nodes to frames, easy the other way!
1348 nsresult
nsFrameSelection::TakeFocus(nsIContent
* const aNewFocus
,
1349 uint32_t aContentOffset
,
1350 uint32_t aContentEndOffset
,
1351 CaretAssociateHint aHint
,
1352 const FocusMode aFocusMode
) {
1354 return NS_ERROR_NULL_POINTER
;
1357 NS_ENSURE_STATE(mPresShell
);
1359 if (!IsValidSelectionPoint(aNewFocus
)) {
1360 return NS_ERROR_FAILURE
;
1363 MOZ_LOG(sFrameSelectionLog
, LogLevel::Verbose
,
1364 ("%s: new focus=%p, offsets=(%u, %u), hint=%i, focusMode=%i",
1365 __FUNCTION__
, aNewFocus
, aContentOffset
, aContentEndOffset
,
1366 static_cast<int>(aHint
), static_cast<int>(aFocusMode
)));
1368 mPresShell
->FrameSelectionWillTakeFocus(*this);
1370 // Clear all table selection data
1371 mTableSelection
.mMode
= TableSelectionMode::None
;
1372 mTableSelection
.mDragSelectingCells
= false;
1373 mTableSelection
.mStartSelectedCell
= nullptr;
1374 mTableSelection
.mEndSelectedCell
= nullptr;
1375 mTableSelection
.mAppendStartSelectedCell
= nullptr;
1376 mCaret
.mHint
= aHint
;
1378 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1379 if (!mDomSelections
[index
]) return NS_ERROR_NULL_POINTER
;
1381 Maybe
<Selection::AutoUserInitiated
> userSelect
;
1382 if (IsUserSelectionReason()) {
1383 userSelect
.emplace(mDomSelections
[index
]);
1386 // traverse through document and unselect crap here
1387 switch (aFocusMode
) {
1388 case FocusMode::kCollapseToNewPoint
:
1390 case FocusMode::kMultiRangeSelection
: {
1391 // single click? setting cursor down
1392 const Batching saveBatching
=
1393 mBatching
; // hack to use the collapse code.
1394 mBatching
.mCounter
= 1;
1396 if (aFocusMode
== FocusMode::kMultiRangeSelection
) {
1397 // Remove existing collapsed ranges as there's no point in having
1398 // non-anchor/focus collapsed ranges.
1399 mDomSelections
[index
]->RemoveCollapsedRanges();
1402 RefPtr
<nsRange
> newRange
= nsRange::Create(
1403 aNewFocus
, aContentOffset
, aNewFocus
, aContentOffset
, error
);
1404 if (NS_WARN_IF(error
.Failed())) {
1405 return error
.StealNSResult();
1407 MOZ_ASSERT(newRange
);
1408 const RefPtr
<Selection
> selection
{mDomSelections
[index
]};
1409 selection
->AddRangeAndSelectFramesAndNotifyListeners(*newRange
,
1412 bool oldDesiredPosSet
=
1413 mDesiredCaretPos
.mIsSet
; // need to keep old desired
1414 // position if it was set.
1415 RefPtr
<Selection
> selection
= mDomSelections
[index
];
1416 selection
->CollapseInLimiter(aNewFocus
, aContentOffset
);
1417 mDesiredCaretPos
.mIsSet
=
1418 oldDesiredPosSet
; // now reset desired pos back.
1421 mBatching
= saveBatching
;
1423 if (aContentEndOffset
!= aContentOffset
) {
1424 mDomSelections
[index
]->Extend(aNewFocus
, aContentEndOffset
);
1427 // find out if we are inside a table. if so, find out which one and which
1428 // cell once we do that, the next time we get a takefocus, check the
1429 // parent tree. if we are no longer inside same table ,cell then switch to
1430 // table selection mode. BUT only do this in an editor
1432 NS_ENSURE_STATE(mPresShell
);
1433 RefPtr
<nsPresContext
> context
= mPresShell
->GetPresContext();
1434 mTableSelection
.mClosestInclusiveTableCellAncestor
= nullptr;
1435 if (nsINode
* inclusiveTableCellAncestor
=
1436 TableSelection::IsContentInActivelyEditableTableCell(context
,
1438 mTableSelection
.mClosestInclusiveTableCellAncestor
=
1439 inclusiveTableCellAncestor
;
1440 MOZ_LOG(sFrameSelectionLog
, LogLevel::Debug
,
1441 ("%s: Collapsing into new cell", __FUNCTION__
));
1446 case FocusMode::kExtendSelection
: {
1447 // Now update the range list:
1448 nsINode
* inclusiveTableCellAncestor
=
1449 GetClosestInclusiveTableCellAncestor(aNewFocus
);
1450 if (mTableSelection
.mClosestInclusiveTableCellAncestor
&&
1451 inclusiveTableCellAncestor
&&
1452 inclusiveTableCellAncestor
!=
1454 .mClosestInclusiveTableCellAncestor
) // switch to cell
1457 MOZ_LOG(sFrameSelectionLog
, LogLevel::Debug
,
1458 ("%s: moving into new cell", __FUNCTION__
));
1460 WidgetMouseEvent
event(false, eVoidEvent
, nullptr,
1461 WidgetMouseEvent::eReal
);
1463 // Start selecting in the cell we were in before
1464 ParentAndOffset parentAndOffset
{
1465 *mTableSelection
.mClosestInclusiveTableCellAncestor
};
1466 if (parentAndOffset
.mParent
) {
1467 const nsresult result
= HandleTableSelection(
1468 parentAndOffset
.mParent
, parentAndOffset
.mOffset
,
1469 TableSelectionMode::Cell
, &event
);
1470 if (NS_WARN_IF(NS_FAILED(result
))) {
1475 // Find the parent of this new cell and extend selection to it
1476 parentAndOffset
= ParentAndOffset
{*inclusiveTableCellAncestor
};
1478 // XXXX We need to REALLY get the current key shift state
1479 // (we'd need to add event listener -- let's not bother for now)
1480 event
.mModifiers
&= ~MODIFIER_SHIFT
; // aContinueSelection;
1481 if (parentAndOffset
.mParent
) {
1482 mTableSelection
.mClosestInclusiveTableCellAncestor
=
1483 inclusiveTableCellAncestor
;
1484 // Continue selection into next cell
1485 const nsresult result
= HandleTableSelection(
1486 parentAndOffset
.mParent
, parentAndOffset
.mOffset
,
1487 TableSelectionMode::Cell
, &event
);
1488 if (NS_WARN_IF(NS_FAILED(result
))) {
1493 // XXXX Problem: Shift+click in browser is appending text selection to
1494 // selected table!!!
1495 // is this the place to erase seleced cells ?????
1496 if (mDomSelections
[index
]->GetDirection() == eDirNext
&&
1497 aContentEndOffset
> aContentOffset
) // didn't go far enough
1499 mDomSelections
[index
]->Extend(
1500 aNewFocus
, aContentEndOffset
); // this will only redraw the diff
1502 mDomSelections
[index
]->Extend(aNewFocus
, aContentOffset
);
1508 // Don't notify selection listeners if batching is on:
1513 // Be aware, the Selection instance may be destroyed after this call.
1514 return NotifySelectionListeners(SelectionType::eNormal
);
1517 UniquePtr
<SelectionDetails
> nsFrameSelection::LookUpSelection(
1518 nsIContent
* aContent
, int32_t aContentOffset
, int32_t aContentLength
,
1519 bool aSlowCheck
) const {
1520 if (!aContent
|| !mPresShell
) {
1524 UniquePtr
<SelectionDetails
> details
;
1526 for (size_t j
= 0; j
< ArrayLength(mDomSelections
); j
++) {
1527 if (mDomSelections
[j
]) {
1528 details
= mDomSelections
[j
]->LookUpSelection(
1529 aContent
, aContentOffset
, aContentLength
, std::move(details
),
1530 kPresentSelectionTypes
[j
], aSlowCheck
);
1537 void nsFrameSelection::SetDragState(bool aState
) {
1538 if (mDragState
== aState
) return;
1540 mDragState
= aState
;
1543 mTableSelection
.mDragSelectingCells
= false;
1544 // Notify that reason is mouse up.
1545 SetChangeReasons(nsISelectionListener::MOUSEUP_REASON
);
1546 // Be aware, the Selection instance may be destroyed after this call.
1547 NotifySelectionListeners(SelectionType::eNormal
);
1551 Selection
* nsFrameSelection::GetSelection(SelectionType aSelectionType
) const {
1552 int8_t index
= GetIndexFromSelectionType(aSelectionType
);
1553 if (index
< 0) return nullptr;
1555 return mDomSelections
[index
];
1558 nsresult
nsFrameSelection::ScrollSelectionIntoView(SelectionType aSelectionType
,
1559 SelectionRegion aRegion
,
1560 int16_t aFlags
) const {
1561 int8_t index
= GetIndexFromSelectionType(aSelectionType
);
1562 if (index
< 0) return NS_ERROR_INVALID_ARG
;
1564 if (!mDomSelections
[index
]) return NS_ERROR_NULL_POINTER
;
1566 ScrollAxis verticalScroll
= ScrollAxis();
1567 int32_t flags
= Selection::SCROLL_DO_FLUSH
;
1568 if (aFlags
& nsISelectionController::SCROLL_SYNCHRONOUS
) {
1569 flags
|= Selection::SCROLL_SYNCHRONOUS
;
1570 } else if (aFlags
& nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY
) {
1571 flags
|= Selection::SCROLL_FIRST_ANCESTOR_ONLY
;
1573 if (aFlags
& nsISelectionController::SCROLL_OVERFLOW_HIDDEN
) {
1574 flags
|= Selection::SCROLL_OVERFLOW_HIDDEN
;
1576 if (aFlags
& nsISelectionController::SCROLL_CENTER_VERTICALLY
) {
1578 ScrollAxis(kScrollToCenter
, WhenToScroll::IfNotFullyVisible
);
1580 if (aFlags
& nsISelectionController::SCROLL_FOR_CARET_MOVE
) {
1581 flags
|= Selection::SCROLL_FOR_CARET_MOVE
;
1584 // After ScrollSelectionIntoView(), the pending notifications might be
1585 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
1586 RefPtr
<Selection
> sel
= mDomSelections
[index
];
1587 return sel
->ScrollIntoView(aRegion
, verticalScroll
, ScrollAxis(), flags
);
1590 nsresult
nsFrameSelection::RepaintSelection(SelectionType aSelectionType
) {
1591 int8_t index
= GetIndexFromSelectionType(aSelectionType
);
1592 if (index
< 0) return NS_ERROR_INVALID_ARG
;
1593 if (!mDomSelections
[index
]) return NS_ERROR_NULL_POINTER
;
1594 NS_ENSURE_STATE(mPresShell
);
1596 // On macOS, update the selection cache to the new active selection
1597 // aka the current selection.
1599 // Check that we're in the an active window and, if this is Web content,
1600 // in the frontmost tab.
1601 Document
* doc
= mPresShell
->GetDocument();
1602 if (doc
&& IsInActiveTab(doc
) && aSelectionType
== SelectionType::eNormal
) {
1603 UpdateSelectionCacheOnRepaintSelection(mDomSelections
[index
]);
1606 return mDomSelections
[index
]->Repaint(mPresShell
->GetPresContext());
1609 static bool IsDisplayContents(const nsIContent
* aContent
) {
1610 return aContent
->IsElement() && aContent
->AsElement()->IsDisplayContents();
1614 nsIFrame
* nsFrameSelection::GetFrameForNodeOffset(nsIContent
* aNode
,
1616 CaretAssociateHint aHint
,
1617 int32_t* aReturnOffset
) {
1618 if (!aNode
|| !aReturnOffset
) return nullptr;
1620 if (aOffset
< 0) return nullptr;
1622 if (!aNode
->GetPrimaryFrame() && !IsDisplayContents(aNode
)) {
1626 nsIFrame
* returnFrame
= nullptr;
1627 nsCOMPtr
<nsIContent
> theNode
;
1630 *aReturnOffset
= aOffset
;
1634 if (aNode
->IsElement()) {
1635 int32_t childIndex
= 0;
1636 int32_t numChildren
= theNode
->GetChildCount();
1638 if (aHint
== CARET_ASSOCIATE_BEFORE
) {
1640 childIndex
= aOffset
- 1;
1642 childIndex
= aOffset
;
1645 NS_ASSERTION(aHint
== CARET_ASSOCIATE_AFTER
, "unknown direction");
1646 if (aOffset
>= numChildren
) {
1647 if (numChildren
> 0) {
1648 childIndex
= numChildren
- 1;
1653 childIndex
= aOffset
;
1657 if (childIndex
> 0 || numChildren
> 0) {
1658 nsCOMPtr
<nsIContent
> childNode
=
1659 theNode
->GetChildAt_Deprecated(childIndex
);
1665 theNode
= childNode
;
1668 // Now that we have the child node, check if it too
1669 // can contain children. If so, descend into child.
1670 if (theNode
->IsElement() && theNode
->GetChildCount() &&
1671 !theNode
->HasIndependentSelection()) {
1673 aOffset
= aOffset
> childIndex
? theNode
->GetChildCount() : 0;
1676 // Check to see if theNode is a text node. If it is, translate
1677 // aOffset into an offset into the text node.
1679 RefPtr
<Text
> textNode
= theNode
->GetAsText();
1681 if (theNode
->GetPrimaryFrame()) {
1682 if (aOffset
> childIndex
) {
1683 uint32_t textLength
= textNode
->Length();
1685 *aReturnOffset
= (int32_t)textLength
;
1690 int32_t numChildren
= aNode
->GetChildCount();
1691 int32_t newChildIndex
= aHint
== CARET_ASSOCIATE_BEFORE
1695 if (newChildIndex
>= 0 && newChildIndex
< numChildren
) {
1696 nsCOMPtr
<nsIContent
> newChildNode
=
1697 aNode
->GetChildAt_Deprecated(newChildIndex
);
1698 if (!newChildNode
) {
1702 aNode
= newChildNode
;
1704 aHint
== CARET_ASSOCIATE_BEFORE
? aNode
->GetChildCount() : 0;
1707 // newChildIndex is illegal which means we're at first or last
1708 // child. Just use original node to get the frame.
1716 // If the node is a ShadowRoot, the frame needs to be adjusted,
1717 // because a ShadowRoot does not get a frame. Its children are rendered
1718 // as children of the host.
1719 if (ShadowRoot
* shadow
= ShadowRoot::FromNode(theNode
)) {
1720 theNode
= shadow
->GetHost();
1723 returnFrame
= theNode
->GetPrimaryFrame();
1725 if (aHint
== CARET_ASSOCIATE_BEFORE
) {
1733 int32_t end
= theNode
->GetChildCount();
1734 if (aOffset
< end
) {
1746 if (!returnFrame
) return nullptr;
1748 // If we ended up here and were asked to position the caret after a visible
1749 // break, let's return the frame on the next line instead if it exists.
1750 if (aOffset
> 0 && (uint32_t)aOffset
>= aNode
->Length() &&
1751 theNode
== aNode
->GetLastChild()) {
1753 nsLayoutUtils::IsInvisibleBreak(theNode
, &newFrame
);
1755 returnFrame
= newFrame
;
1760 // find the child frame containing the offset we want
1761 returnFrame
->GetChildFrameContainingOffset(
1762 *aReturnOffset
, aHint
== CARET_ASSOCIATE_AFTER
, &aOffset
, &returnFrame
);
1766 nsIFrame
* nsFrameSelection::GetFrameToPageSelect() const {
1767 if (NS_WARN_IF(!mPresShell
)) {
1771 nsIFrame
* rootFrameToSelect
;
1772 if (mLimiters
.mLimiter
) {
1773 rootFrameToSelect
= mLimiters
.mLimiter
->GetPrimaryFrame();
1774 if (NS_WARN_IF(!rootFrameToSelect
)) {
1777 } else if (mLimiters
.mAncestorLimiter
) {
1778 rootFrameToSelect
= mLimiters
.mAncestorLimiter
->GetPrimaryFrame();
1779 if (NS_WARN_IF(!rootFrameToSelect
)) {
1783 rootFrameToSelect
= mPresShell
->GetRootScrollFrame();
1784 if (NS_WARN_IF(!rootFrameToSelect
)) {
1789 nsCOMPtr
<nsIContent
> contentToSelect
= mPresShell
->GetContentForScrolling();
1790 if (contentToSelect
) {
1791 // If there is selected content, look for nearest and vertical scrollable
1792 // parent under the root frame.
1793 for (nsIFrame
* frame
= contentToSelect
->GetPrimaryFrame();
1794 frame
&& frame
!= rootFrameToSelect
; frame
= frame
->GetParent()) {
1795 nsIScrollableFrame
* scrollableFrame
= do_QueryFrame(frame
);
1796 if (!scrollableFrame
) {
1799 ScrollStyles scrollStyles
= scrollableFrame
->GetScrollStyles();
1800 if (scrollStyles
.mVertical
== StyleOverflow::Hidden
) {
1803 uint32_t directions
= scrollableFrame
->GetAvailableScrollingDirections();
1804 if (directions
& nsIScrollableFrame::VERTICAL
) {
1805 // If there is sub scrollable frame, let's use its page size to select.
1810 // Otherwise, i.e., there is no scrollable frame or only the root frame is
1811 // scrollable, let's return the root frame because Shift + PageUp/PageDown
1812 // should expand the selection in the root content even if it's not
1814 return rootFrameToSelect
;
1817 nsresult
nsFrameSelection::PageMove(bool aForward
, bool aExtend
,
1819 SelectionIntoView aSelectionIntoView
) {
1822 // expected behavior for PageMove is to scroll AND move the caret
1823 // and remain relative position of the caret in view. see Bug 4302.
1825 // Get the scrollable frame. If aFrame is not scrollable, this is nullptr.
1826 nsIScrollableFrame
* scrollableFrame
= aFrame
->GetScrollTargetFrame();
1827 // Get the scrolled frame. If aFrame is not scrollable, this is aFrame
1829 nsIFrame
* scrolledFrame
=
1830 scrollableFrame
? scrollableFrame
->GetScrolledFrame() : aFrame
;
1831 if (!scrolledFrame
) {
1835 // find out where the caret is.
1836 // we should know mDesiredCaretPos.mValue value of nsFrameSelection, but I
1837 // havent seen that behavior in other windows applications yet.
1838 RefPtr
<Selection
> selection
= GetSelection(SelectionType::eNormal
);
1844 nsIFrame
* caretFrame
= nsCaret::GetGeometry(selection
, &caretPos
);
1849 // If the scrolled frame is outside of current selection limiter,
1850 // we need to scroll the frame but keep moving selection in the limiter.
1851 nsIFrame
* frameToClick
= scrolledFrame
;
1852 if (!IsValidSelectionPoint(scrolledFrame
->GetContent())) {
1853 frameToClick
= GetFrameToPageSelect();
1854 if (NS_WARN_IF(!frameToClick
)) {
1859 if (scrollableFrame
) {
1860 // If there is a scrollable frame, adjust pseudo-click position with page
1862 // XXX This may scroll more than one page if ScrollSelectionIntoView is
1863 // called later because caret may not fully visible. E.g., if
1864 // clicking line will be visible only half height with scrolling
1865 // the frame, ScrollSelectionIntoView additionally scrolls to show
1866 // the caret entirely.
1868 caretPos
.y
+= scrollableFrame
->GetPageScrollAmount().height
;
1870 caretPos
.y
-= scrollableFrame
->GetPageScrollAmount().height
;
1873 // Otherwise, adjust pseudo-click position with the frame size.
1875 caretPos
.y
+= frameToClick
->GetSize().height
;
1877 caretPos
.y
-= frameToClick
->GetSize().height
;
1881 caretPos
+= caretFrame
->GetOffsetTo(frameToClick
);
1883 // get a content at desired location
1884 nsPoint desiredPoint
;
1885 desiredPoint
.x
= caretPos
.x
;
1886 desiredPoint
.y
= caretPos
.y
+ caretPos
.height
/ 2;
1887 nsIFrame::ContentOffsets offsets
=
1888 frameToClick
->GetContentOffsetsFromPoint(desiredPoint
);
1890 if (!offsets
.content
) {
1891 // XXX Do we need to handle ScrollSelectionIntoView in this case?
1895 // First, place the caret.
1896 bool selectionChanged
;
1898 // We don't want any script to run until we check whether selection is
1899 // modified by HandleClick.
1900 SelectionBatcher
ensureNoSelectionChangeNotifications(selection
);
1902 RangeBoundary oldAnchor
= selection
->AnchorRef();
1903 RangeBoundary oldFocus
= selection
->FocusRef();
1905 const FocusMode focusMode
=
1906 aExtend
? FocusMode::kExtendSelection
: FocusMode::kCollapseToNewPoint
;
1907 HandleClick(MOZ_KnownLive(offsets
.content
) /* bug 1636889 */,
1908 offsets
.offset
, offsets
.offset
, focusMode
,
1909 CARET_ASSOCIATE_AFTER
);
1911 selectionChanged
= selection
->AnchorRef() != oldAnchor
||
1912 selection
->FocusRef() != oldFocus
;
1915 bool doScrollSelectionIntoView
= !(
1916 aSelectionIntoView
== SelectionIntoView::IfChanged
&& !selectionChanged
);
1918 // Then, scroll the given frame one page.
1919 if (scrollableFrame
) {
1920 mozilla::Telemetry::Accumulate(
1921 mozilla::Telemetry::SCROLL_INPUT_METHODS
,
1922 (uint32_t)ScrollInputMethod::MainThreadScrollPage
);
1924 // If we'll call ScrollSelectionIntoView later and selection wasn't
1925 // changed and we scroll outside of selection limiter, we shouldn't use
1926 // smooth scroll here because nsIScrollableFrame uses normal runnable,
1927 // but ScrollSelectionIntoView uses early runner and it cancels the
1928 // pending smooth scroll. Therefore, if we used smooth scroll in such
1929 // case, ScrollSelectionIntoView would scroll to show caret instead of
1930 // page scroll of an element outside selection limiter.
1931 ScrollMode scrollMode
= doScrollSelectionIntoView
&& !selectionChanged
&&
1932 scrolledFrame
!= frameToClick
1933 ? ScrollMode::Instant
1934 : ScrollMode::Smooth
;
1935 scrollableFrame
->ScrollBy(nsIntPoint(0, aForward
? 1 : -1),
1936 ScrollUnit::PAGES
, scrollMode
);
1939 // Finally, scroll selection into view if requested.
1940 if (!doScrollSelectionIntoView
) {
1943 return ScrollSelectionIntoView(
1944 SelectionType::eNormal
, nsISelectionController::SELECTION_FOCUS_REGION
,
1945 nsISelectionController::SCROLL_SYNCHRONOUS
|
1946 nsISelectionController::SCROLL_FOR_CARET_MOVE
);
1949 nsresult
nsFrameSelection::PhysicalMove(int16_t aDirection
, int16_t aAmount
,
1951 NS_ENSURE_STATE(mPresShell
);
1952 // Flush out layout, since we need it to be up to date to do caret
1954 OwningNonNull
<PresShell
> presShell(*mPresShell
);
1955 presShell
->FlushPendingNotifications(FlushType::Layout
);
1961 // Check that parameters are safe
1962 if (aDirection
< 0 || aDirection
> 3 || aAmount
< 0 || aAmount
> 1) {
1963 return NS_ERROR_FAILURE
;
1966 nsPresContext
* context
= mPresShell
->GetPresContext();
1968 return NS_ERROR_FAILURE
;
1971 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1972 RefPtr
<Selection
> sel
= mDomSelections
[index
];
1974 return NS_ERROR_NULL_POINTER
;
1977 // Map the abstract movement amounts (0-1) to direction-specific
1979 static const nsSelectionAmount inlineAmount
[] = {eSelectCluster
, eSelectWord
};
1980 static const nsSelectionAmount blockPrevAmount
[] = {eSelectLine
,
1982 static const nsSelectionAmount blockNextAmount
[] = {eSelectLine
,
1985 struct PhysicalToLogicalMapping
{
1986 nsDirection direction
;
1987 const nsSelectionAmount
* amounts
;
1989 static const PhysicalToLogicalMapping verticalLR
[4] = {
1990 {eDirPrevious
, blockPrevAmount
}, // left
1991 {eDirNext
, blockNextAmount
}, // right
1992 {eDirPrevious
, inlineAmount
}, // up
1993 {eDirNext
, inlineAmount
} // down
1995 static const PhysicalToLogicalMapping verticalRL
[4] = {
1996 {eDirNext
, blockNextAmount
},
1997 {eDirPrevious
, blockPrevAmount
},
1998 {eDirPrevious
, inlineAmount
},
1999 {eDirNext
, inlineAmount
}};
2000 static const PhysicalToLogicalMapping horizontal
[4] = {
2001 {eDirPrevious
, inlineAmount
},
2002 {eDirNext
, inlineAmount
},
2003 {eDirPrevious
, blockPrevAmount
},
2004 {eDirNext
, blockNextAmount
}};
2007 nsIFrame
* frame
= sel
->GetPrimaryFrameForFocusNode(true);
2009 if (!frame
->Style()->IsTextCombined()) {
2010 wm
= frame
->GetWritingMode();
2012 // Using different direction for horizontal-in-vertical would
2013 // make it hard to navigate via keyboard. Inherit the moving
2014 // direction from its parent.
2015 MOZ_ASSERT(frame
->IsTextFrame());
2016 wm
= frame
->GetParent()->GetWritingMode();
2017 MOZ_ASSERT(wm
.IsVertical(),
2019 "can only appear in vertical text");
2023 const PhysicalToLogicalMapping
& mapping
=
2025 ? wm
.IsVerticalLR() ? verticalLR
[aDirection
] : verticalRL
[aDirection
]
2026 : horizontal
[aDirection
];
2029 MoveCaret(mapping
.direction
, aExtend
, mapping
.amounts
[aAmount
], eVisual
);
2030 if (NS_FAILED(rv
)) {
2031 // If we tried to do a line move, but couldn't move in the given direction,
2032 // then we'll "promote" this to a line-edge move instead.
2033 if (mapping
.amounts
[aAmount
] == eSelectLine
) {
2034 rv
= MoveCaret(mapping
.direction
, aExtend
, mapping
.amounts
[aAmount
+ 1],
2037 // And if it was a next-word move that failed (which can happen when
2038 // eat_space_to_next_word is true, see bug 1153237), then just move forward
2039 // to the line-edge.
2040 else if (mapping
.amounts
[aAmount
] == eSelectWord
&&
2041 mapping
.direction
== eDirNext
) {
2042 rv
= MoveCaret(eDirNext
, aExtend
, eSelectEndLine
, eVisual
);
2049 nsresult
nsFrameSelection::CharacterMove(bool aForward
, bool aExtend
) {
2050 return MoveCaret(aForward
? eDirNext
: eDirPrevious
, aExtend
, eSelectCluster
,
2054 nsresult
nsFrameSelection::WordMove(bool aForward
, bool aExtend
) {
2055 return MoveCaret(aForward
? eDirNext
: eDirPrevious
, aExtend
, eSelectWord
,
2059 nsresult
nsFrameSelection::LineMove(bool aForward
, bool aExtend
) {
2060 return MoveCaret(aForward
? eDirNext
: eDirPrevious
, aExtend
, eSelectLine
,
2064 nsresult
nsFrameSelection::IntraLineMove(bool aForward
, bool aExtend
) {
2066 return MoveCaret(eDirNext
, aExtend
, eSelectEndLine
, eLogical
);
2068 return MoveCaret(eDirPrevious
, aExtend
, eSelectBeginLine
, eLogical
);
2072 template <typename RangeType
>
2073 Result
<RefPtr
<RangeType
>, nsresult
>
2074 nsFrameSelection::CreateRangeExtendedToSomewhere(
2075 nsDirection aDirection
, const nsSelectionAmount aAmount
,
2076 CaretMovementStyle aMovementStyle
) {
2077 MOZ_ASSERT(aDirection
== eDirNext
|| aDirection
== eDirPrevious
);
2078 MOZ_ASSERT(aAmount
== eSelectCharacter
|| aAmount
== eSelectCluster
||
2079 aAmount
== eSelectWord
|| aAmount
== eSelectBeginLine
||
2080 aAmount
== eSelectEndLine
);
2081 MOZ_ASSERT(aMovementStyle
== eLogical
|| aMovementStyle
== eVisual
||
2082 aMovementStyle
== eUsePrefStyle
);
2085 return Err(NS_ERROR_UNEXPECTED
);
2087 OwningNonNull
<PresShell
> presShell(*mPresShell
);
2088 presShell
->FlushPendingNotifications(FlushType::Layout
);
2090 return Err(NS_ERROR_FAILURE
);
2092 Selection
* selection
=
2093 mDomSelections
[GetIndexFromSelectionType(SelectionType::eNormal
)];
2094 if (!selection
|| selection
->RangeCount() != 1) {
2095 return Err(NS_ERROR_FAILURE
);
2097 RefPtr
<const nsRange
> firstRange
= selection
->GetRangeAt(0);
2098 if (!firstRange
|| !firstRange
->IsPositioned()) {
2099 return Err(NS_ERROR_FAILURE
);
2101 Result
<nsPeekOffsetStruct
, nsresult
> result
= PeekOffsetForCaretMove(
2102 aDirection
, true, aAmount
, aMovementStyle
, nsPoint(0, 0));
2103 if (result
.isErr()) {
2104 return Err(NS_ERROR_FAILURE
);
2106 const nsPeekOffsetStruct
& pos
= result
.inspect();
2107 RefPtr
<RangeType
> range
;
2108 if (NS_WARN_IF(!pos
.mResultContent
)) {
2111 if (aDirection
== eDirPrevious
) {
2112 range
= RangeType::Create(
2113 RawRangeBoundary(pos
.mResultContent
, pos
.mContentOffset
),
2114 firstRange
->EndRef(), IgnoreErrors());
2116 range
= RangeType::Create(
2117 firstRange
->StartRef(),
2118 RawRangeBoundary(pos
.mResultContent
, pos
.mContentOffset
),
2124 //////////END FRAMESELECTION
2126 void nsFrameSelection::StartBatchChanges() { mBatching
.mCounter
++; }
2128 void nsFrameSelection::EndBatchChanges(int16_t aReasons
) {
2129 MOZ_ASSERT(mBatching
.mCounter
> 0, "Bad mBatching.mCounter");
2130 mBatching
.mCounter
--;
2132 if (mBatching
.mCounter
== 0 && mBatching
.mChangesDuringBatching
) {
2133 AddChangeReasons(aReasons
);
2134 mBatching
.mChangesDuringBatching
= false;
2135 // Be aware, the Selection instance may be destroyed after this call.
2136 NotifySelectionListeners(SelectionType::eNormal
);
2140 nsresult
nsFrameSelection::NotifySelectionListeners(
2141 SelectionType aSelectionType
) {
2142 int8_t index
= GetIndexFromSelectionType(aSelectionType
);
2143 if (index
>= 0 && mDomSelections
[index
]) {
2144 RefPtr
<Selection
> selection
= mDomSelections
[index
];
2145 return selection
->NotifySelectionListeners();
2147 return NS_ERROR_FAILURE
;
2150 // Start of Table Selection methods
2152 static bool IsCell(nsIContent
* aContent
) {
2153 return aContent
->IsAnyOfHTMLElements(nsGkAtoms::td
, nsGkAtoms::th
);
2157 nsITableCellLayout
* nsFrameSelection::GetCellLayout(
2158 const nsIContent
* aCellContent
) {
2159 nsITableCellLayout
* cellLayoutObject
=
2160 do_QueryFrame(aCellContent
->GetPrimaryFrame());
2161 return cellLayoutObject
;
2164 nsresult
nsFrameSelection::ClearNormalSelection() {
2165 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
2166 if (!mDomSelections
[index
]) return NS_ERROR_NULL_POINTER
;
2169 mDomSelections
[index
]->RemoveAllRanges(err
);
2170 return err
.StealNSResult();
2173 static nsIContent
* GetFirstSelectedContent(const nsRange
* aRange
) {
2178 MOZ_ASSERT(aRange
->GetStartContainer(), "Must have start parent!");
2179 MOZ_ASSERT(aRange
->GetStartContainer()->IsElement(), "Unexpected parent");
2181 return aRange
->GetChildAtStartOffset();
2184 // Table selection support.
2185 // TODO: Separate table methods into a separate nsITableSelection interface
2186 nsresult
nsFrameSelection::HandleTableSelection(nsINode
* aParentContent
,
2187 int32_t aContentOffset
,
2188 TableSelectionMode aTarget
,
2189 WidgetMouseEvent
* aMouseEvent
) {
2190 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
2191 RefPtr
<Selection
> selection
= mDomSelections
[index
];
2193 return NS_ERROR_NULL_POINTER
;
2196 return mTableSelection
.HandleSelection(aParentContent
, aContentOffset
,
2197 aTarget
, aMouseEvent
, mDragState
,
2201 nsresult
nsFrameSelection::TableSelection::HandleSelection(
2202 nsINode
* aParentContent
, int32_t aContentOffset
, TableSelectionMode aTarget
,
2203 WidgetMouseEvent
* aMouseEvent
, bool aDragState
,
2204 Selection
& aNormalSelection
) {
2205 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2207 NS_ENSURE_TRUE(aParentContent
, NS_ERROR_NULL_POINTER
);
2208 NS_ENSURE_TRUE(aMouseEvent
, NS_ERROR_NULL_POINTER
);
2210 if (aDragState
&& mDragSelectingCells
&&
2211 aTarget
== TableSelectionMode::Table
) {
2212 // We were selecting cells and user drags mouse in table border or inbetween
2218 RefPtr
<nsIContent
> childContent
=
2219 aParentContent
->GetChildAt_Deprecated(aContentOffset
);
2221 // When doing table selection, always set the direction to next so
2222 // we can be sure that anchorNode's offset always points to the
2224 aNormalSelection
.SetDirection(eDirNext
);
2226 // Stack-class to wrap all table selection changes in
2227 // BeginBatchChanges() / EndBatchChanges()
2228 SelectionBatcher
selectionBatcher(&aNormalSelection
);
2230 if (aDragState
&& mDragSelectingCells
) {
2231 return HandleDragSelecting(aTarget
, childContent
, aMouseEvent
,
2235 return HandleMouseUpOrDown(aTarget
, aDragState
, childContent
, aParentContent
,
2236 aContentOffset
, aMouseEvent
, aNormalSelection
);
2239 class nsFrameSelection::TableSelection::RowAndColumnRelation
{
2241 static Result
<RowAndColumnRelation
, nsresult
> Create(
2242 const nsIContent
* aFirst
, const nsIContent
* aSecond
) {
2243 RowAndColumnRelation result
;
2245 nsresult errorResult
=
2246 GetCellIndexes(aFirst
, result
.mFirst
.mRow
, result
.mFirst
.mColumn
);
2247 if (NS_FAILED(errorResult
)) {
2248 return Err(errorResult
);
2252 GetCellIndexes(aSecond
, result
.mSecond
.mRow
, result
.mSecond
.mColumn
);
2253 if (NS_FAILED(errorResult
)) {
2254 return Err(errorResult
);
2260 bool IsSameColumn() const { return mFirst
.mColumn
== mSecond
.mColumn
; }
2262 bool IsSameRow() const { return mFirst
.mRow
== mSecond
.mRow
; }
2265 RowAndColumnRelation() = default;
2267 struct RowAndColumn
{
2269 int32_t mColumn
= 0;
2272 RowAndColumn mFirst
;
2273 RowAndColumn mSecond
;
2276 nsresult
nsFrameSelection::TableSelection::HandleDragSelecting(
2277 TableSelectionMode aTarget
, nsIContent
* aChildContent
,
2278 const WidgetMouseEvent
* aMouseEvent
, Selection
& aNormalSelection
) {
2279 // We are drag-selecting
2280 if (aTarget
!= TableSelectionMode::Table
) {
2281 // If dragging in the same cell as last event, do nothing
2282 if (mEndSelectedCell
== aChildContent
) {
2286 #ifdef DEBUG_TABLE_SELECTION
2288 " mStartSelectedCell = %p, "
2289 "mEndSelectedCell = %p, aChildContent = %p "
2291 mStartSelectedCell
.get(), mEndSelectedCell
.get(), aChildContent
);
2293 // aTarget can be any "cell mode",
2294 // so we can easily drag-select rows and columns
2295 // Once we are in row or column mode,
2296 // we can drift into any cell to stay in that mode
2297 // even if aTarget = TableSelectionMode::Cell
2299 if (mMode
== TableSelectionMode::Row
||
2300 mMode
== TableSelectionMode::Column
) {
2301 if (mEndSelectedCell
) {
2302 Result
<RowAndColumnRelation
, nsresult
> rowAndColumnRelation
=
2303 RowAndColumnRelation::Create(mEndSelectedCell
, aChildContent
);
2305 if (rowAndColumnRelation
.isErr()) {
2306 return rowAndColumnRelation
.unwrapErr();
2309 if ((mMode
== TableSelectionMode::Row
&&
2310 rowAndColumnRelation
.inspect().IsSameRow()) ||
2311 (mMode
== TableSelectionMode::Column
&&
2312 rowAndColumnRelation
.inspect().IsSameColumn())) {
2316 #ifdef DEBUG_TABLE_SELECTION
2317 printf(" Dragged into a new column or row\n");
2319 // Continue dragging row or column selection
2321 return SelectRowOrColumn(aChildContent
, aNormalSelection
);
2323 if (mMode
== TableSelectionMode::Cell
) {
2324 #ifdef DEBUG_TABLE_SELECTION
2325 printf("HandleTableSelection: Dragged into a new cell\n");
2327 // Trick for quick selection of rows and columns
2328 // Hold down shift, then start selecting in one direction
2329 // If next cell dragged into is in same row, select entire row,
2330 // if next cell is in same column, select entire column
2331 if (mStartSelectedCell
&& aMouseEvent
->IsShift()) {
2332 Result
<RowAndColumnRelation
, nsresult
> rowAndColumnRelation
=
2333 RowAndColumnRelation::Create(mStartSelectedCell
, aChildContent
);
2334 if (rowAndColumnRelation
.isErr()) {
2335 return rowAndColumnRelation
.unwrapErr();
2338 if (rowAndColumnRelation
.inspect().IsSameRow() ||
2339 rowAndColumnRelation
.inspect().IsSameColumn()) {
2340 // Force new selection block
2341 mStartSelectedCell
= nullptr;
2342 aNormalSelection
.RemoveAllRanges(IgnoreErrors());
2344 if (rowAndColumnRelation
.inspect().IsSameRow()) {
2345 mMode
= TableSelectionMode::Row
;
2347 mMode
= TableSelectionMode::Column
;
2350 return SelectRowOrColumn(aChildContent
, aNormalSelection
);
2354 // Reselect block of cells to new end location
2355 return SelectBlockOfCells(mStartSelectedCell
, aChildContent
,
2359 // Do nothing if dragging in table, but outside a cell
2363 nsresult
nsFrameSelection::TableSelection::HandleMouseUpOrDown(
2364 TableSelectionMode aTarget
, bool aDragState
, nsIContent
* aChildContent
,
2365 nsINode
* aParentContent
, int32_t aContentOffset
,
2366 const WidgetMouseEvent
* aMouseEvent
, Selection
& aNormalSelection
) {
2367 nsresult result
= NS_OK
;
2368 // Not dragging -- mouse event is down or up
2370 #ifdef DEBUG_TABLE_SELECTION
2371 printf("HandleTableSelection: Mouse down event\n");
2373 // Clear cell we stored in mouse-down
2374 mUnselectCellOnMouseUp
= nullptr;
2376 if (aTarget
== TableSelectionMode::Cell
) {
2377 bool isSelected
= false;
2379 // Check if we have other selected cells
2380 nsIContent
* previousCellNode
=
2381 GetFirstSelectedContent(GetFirstCellRange(aNormalSelection
));
2382 if (previousCellNode
) {
2383 // We have at least 1 other selected cell
2385 // Check if new cell is already selected
2386 nsIFrame
* cellFrame
= aChildContent
->GetPrimaryFrame();
2388 return NS_ERROR_NULL_POINTER
;
2390 isSelected
= cellFrame
->IsSelected();
2392 // No cells selected -- remove non-cell selection
2393 aNormalSelection
.RemoveAllRanges(IgnoreErrors());
2395 mDragSelectingCells
= true; // Signal to start drag-cell-selection
2397 // Set start for new drag-selection block (not appended)
2398 mStartSelectedCell
= aChildContent
;
2399 // The initial block end is same as the start
2400 mEndSelectedCell
= aChildContent
;
2403 // Remember this cell to (possibly) unselect it on mouseup
2404 mUnselectCellOnMouseUp
= aChildContent
;
2405 #ifdef DEBUG_TABLE_SELECTION
2407 "HandleTableSelection: Saving "
2408 "mUnselectCellOnMouseUp\n");
2411 // Select an unselected cell
2412 // but first remove existing selection if not in same table
2413 if (previousCellNode
&&
2414 !IsInSameTable(previousCellNode
, aChildContent
)) {
2415 aNormalSelection
.RemoveAllRanges(IgnoreErrors());
2416 // Reset selection mode that is cleared in RemoveAllRanges
2420 return ::SelectCellElement(aChildContent
, aNormalSelection
);
2425 if (aTarget
== TableSelectionMode::Table
) {
2426 // TODO: We currently select entire table when clicked between cells,
2427 // should we restrict to only around border?
2428 // *** How do we get location data for cell and click?
2429 mDragSelectingCells
= false;
2430 mStartSelectedCell
= nullptr;
2431 mEndSelectedCell
= nullptr;
2433 // Remove existing selection and select the table
2434 aNormalSelection
.RemoveAllRanges(IgnoreErrors());
2435 return CreateAndAddRange(aParentContent
, aContentOffset
,
2438 if (aTarget
== TableSelectionMode::Row
||
2439 aTarget
== TableSelectionMode::Column
) {
2440 #ifdef DEBUG_TABLE_SELECTION
2441 printf("aTarget == %d\n", aTarget
);
2444 // Start drag-selecting mode so multiple rows/cols can be selected
2445 // Note: Currently, nsIFrame::GetDataForTableSelection
2446 // will never call us for row or column selection on mouse down
2447 mDragSelectingCells
= true;
2449 // Force new selection block
2450 mStartSelectedCell
= nullptr;
2451 aNormalSelection
.RemoveAllRanges(IgnoreErrors());
2452 // Always do this AFTER RemoveAllRanges
2455 return SelectRowOrColumn(aChildContent
, aNormalSelection
);
2458 #ifdef DEBUG_TABLE_SELECTION
2460 "HandleTableSelection: Mouse UP event. "
2461 "mDragSelectingCells=%d, "
2462 "mStartSelectedCell=%p\n",
2463 mDragSelectingCells
, mStartSelectedCell
.get());
2465 // First check if we are extending a block selection
2466 uint32_t rangeCount
= aNormalSelection
.RangeCount();
2468 if (rangeCount
> 0 && aMouseEvent
->IsShift() && mAppendStartSelectedCell
&&
2469 mAppendStartSelectedCell
!= aChildContent
) {
2470 // Shift key is down: append a block selection
2471 mDragSelectingCells
= false;
2473 return SelectBlockOfCells(mAppendStartSelectedCell
, aChildContent
,
2477 if (mDragSelectingCells
) {
2478 mAppendStartSelectedCell
= mStartSelectedCell
;
2481 mDragSelectingCells
= false;
2482 mStartSelectedCell
= nullptr;
2483 mEndSelectedCell
= nullptr;
2485 // Any other mouseup actions require that Ctrl or Cmd key is pressed
2486 // else stop table selection mode
2487 bool doMouseUpAction
= false;
2489 doMouseUpAction
= aMouseEvent
->IsMeta();
2491 doMouseUpAction
= aMouseEvent
->IsControl();
2493 if (!doMouseUpAction
) {
2494 #ifdef DEBUG_TABLE_SELECTION
2496 "HandleTableSelection: Ending cell selection on mouseup: "
2497 "mAppendStartSelectedCell=%p\n",
2498 mAppendStartSelectedCell
.get());
2502 // Unselect a cell only if it wasn't
2503 // just selected on mousedown
2504 if (aChildContent
== mUnselectCellOnMouseUp
) {
2505 // Scan ranges to find the cell to unselect (the selection range to
2507 // XXXbz it's really weird that this lives outside the loop, so once we
2508 // find one we keep looking at it even if we find no more cells...
2509 nsINode
* previousCellParent
= nullptr;
2510 #ifdef DEBUG_TABLE_SELECTION
2512 "HandleTableSelection: Unselecting "
2513 "mUnselectCellOnMouseUp; "
2517 for (uint32_t i
= 0; i
< rangeCount
; i
++) {
2518 // Strong reference, because sometimes we want to remove
2519 // this range, and then we might be the only owner.
2520 RefPtr
<nsRange
> range
= aNormalSelection
.GetRangeAt(i
);
2522 return NS_ERROR_NULL_POINTER
;
2525 nsINode
* container
= range
->GetStartContainer();
2527 return NS_ERROR_NULL_POINTER
;
2530 int32_t offset
= range
->StartOffset();
2531 // Be sure previous selection is a table cell
2532 nsIContent
* child
= range
->GetChildAtStartOffset();
2533 if (child
&& IsCell(child
)) {
2534 previousCellParent
= container
;
2537 // We're done if we didn't find parent of a previously-selected cell
2538 if (!previousCellParent
) {
2542 if (previousCellParent
== aParentContent
&& offset
== aContentOffset
) {
2543 // Cell is already selected
2544 if (rangeCount
== 1) {
2545 #ifdef DEBUG_TABLE_SELECTION
2546 printf("HandleTableSelection: Unselecting single selected cell\n");
2548 // This was the only cell selected.
2549 // Collapse to "normal" selection inside the cell
2550 mStartSelectedCell
= nullptr;
2551 mEndSelectedCell
= nullptr;
2552 mAppendStartSelectedCell
= nullptr;
2553 // TODO: We need a "Collapse to just before deepest child" routine
2554 // Even better, should we collapse to just after the LAST deepest
2556 // (i.e., at the end of the cell's contents)?
2557 return aNormalSelection
.CollapseInLimiter(aChildContent
, 0);
2559 #ifdef DEBUG_TABLE_SELECTION
2561 "HandleTableSelection: Removing cell from multi-cell "
2564 // Unselecting the start of previous block
2565 // XXX What do we use now!
2566 if (aChildContent
== mAppendStartSelectedCell
) {
2567 mAppendStartSelectedCell
= nullptr;
2570 // Deselect cell by removing its range from selection
2572 aNormalSelection
.RemoveRangeAndUnselectFramesAndNotifyListeners(
2574 return err
.StealNSResult();
2577 mUnselectCellOnMouseUp
= nullptr;
2583 nsresult
nsFrameSelection::TableSelection::SelectBlockOfCells(
2584 nsIContent
* aStartCell
, nsIContent
* aEndCell
, Selection
& aNormalSelection
) {
2585 NS_ENSURE_TRUE(aStartCell
, NS_ERROR_NULL_POINTER
);
2586 NS_ENSURE_TRUE(aEndCell
, NS_ERROR_NULL_POINTER
);
2587 mEndSelectedCell
= aEndCell
;
2589 nsresult result
= NS_OK
;
2591 // If new end cell is in a different table, do nothing
2592 const RefPtr
<const nsIContent
> table
= IsInSameTable(aStartCell
, aEndCell
);
2597 // Get starting and ending cells' location in the cellmap
2598 int32_t startRowIndex
, startColIndex
, endRowIndex
, endColIndex
;
2599 result
= GetCellIndexes(aStartCell
, startRowIndex
, startColIndex
);
2600 if (NS_FAILED(result
)) return result
;
2601 result
= GetCellIndexes(aEndCell
, endRowIndex
, endColIndex
);
2602 if (NS_FAILED(result
)) return result
;
2604 if (mDragSelectingCells
) {
2605 // Drag selecting: remove selected cells outside of new block limits
2606 // TODO: `UnselectCells`'s return value shouldn't be ignored.
2607 UnselectCells(table
, startRowIndex
, startColIndex
, endRowIndex
, endColIndex
,
2608 true, aNormalSelection
);
2611 // Note that we select block in the direction of user's mouse dragging,
2612 // which means start cell may be after the end cell in either row or column
2613 return AddCellsToSelection(table
, startRowIndex
, startColIndex
, endRowIndex
,
2614 endColIndex
, aNormalSelection
);
2617 nsresult
nsFrameSelection::TableSelection::UnselectCells(
2618 const nsIContent
* aTableContent
, int32_t aStartRowIndex
,
2619 int32_t aStartColumnIndex
, int32_t aEndRowIndex
, int32_t aEndColumnIndex
,
2620 bool aRemoveOutsideOfCellRange
, mozilla::dom::Selection
& aNormalSelection
) {
2621 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2623 nsTableWrapperFrame
* tableFrame
=
2624 do_QueryFrame(aTableContent
->GetPrimaryFrame());
2625 if (!tableFrame
) return NS_ERROR_FAILURE
;
2627 int32_t minRowIndex
= std::min(aStartRowIndex
, aEndRowIndex
);
2628 int32_t maxRowIndex
= std::max(aStartRowIndex
, aEndRowIndex
);
2629 int32_t minColIndex
= std::min(aStartColumnIndex
, aEndColumnIndex
);
2630 int32_t maxColIndex
= std::max(aStartColumnIndex
, aEndColumnIndex
);
2632 // Strong reference because we sometimes remove the range
2633 RefPtr
<nsRange
> range
= GetFirstCellRange(aNormalSelection
);
2634 nsIContent
* cellNode
= GetFirstSelectedContent(range
);
2635 MOZ_ASSERT(!range
|| cellNode
, "Must have cellNode if had a range");
2637 int32_t curRowIndex
, curColIndex
;
2639 nsresult result
= GetCellIndexes(cellNode
, curRowIndex
, curColIndex
);
2640 if (NS_FAILED(result
)) return result
;
2642 #ifdef DEBUG_TABLE_SELECTION
2643 if (!range
) printf("RemoveCellsToSelection -- range is null\n");
2647 if (aRemoveOutsideOfCellRange
) {
2648 if (curRowIndex
< minRowIndex
|| curRowIndex
> maxRowIndex
||
2649 curColIndex
< minColIndex
|| curColIndex
> maxColIndex
) {
2650 aNormalSelection
.RemoveRangeAndUnselectFramesAndNotifyListeners(
2651 *range
, IgnoreErrors());
2652 // Since we've removed the range, decrement pointer to next range
2653 mSelectedCellIndex
--;
2657 // Remove cell from selection if it belongs to the given cells range or
2658 // it is spanned onto the cells range.
2659 nsTableCellFrame
* cellFrame
=
2660 tableFrame
->GetCellFrameAt(curRowIndex
, curColIndex
);
2662 uint32_t origRowIndex
= cellFrame
->RowIndex();
2663 uint32_t origColIndex
= cellFrame
->ColIndex();
2664 uint32_t actualRowSpan
=
2665 tableFrame
->GetEffectiveRowSpanAt(origRowIndex
, origColIndex
);
2666 uint32_t actualColSpan
=
2667 tableFrame
->GetEffectiveColSpanAt(curRowIndex
, curColIndex
);
2668 if (origRowIndex
<= static_cast<uint32_t>(maxRowIndex
) &&
2670 origRowIndex
+ actualRowSpan
- 1 >=
2671 static_cast<uint32_t>(minRowIndex
) &&
2672 origColIndex
<= static_cast<uint32_t>(maxColIndex
) &&
2674 origColIndex
+ actualColSpan
- 1 >=
2675 static_cast<uint32_t>(minColIndex
)) {
2676 aNormalSelection
.RemoveRangeAndUnselectFramesAndNotifyListeners(
2677 *range
, IgnoreErrors());
2678 // Since we've removed the range, decrement pointer to next range
2679 mSelectedCellIndex
--;
2684 range
= GetNextCellRange(aNormalSelection
);
2685 cellNode
= GetFirstSelectedContent(range
);
2686 MOZ_ASSERT(!range
|| cellNode
, "Must have cellNode if had a range");
2692 nsresult
SelectCellElement(nsIContent
* aCellElement
,
2693 Selection
& aNormalSelection
) {
2694 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2696 nsIContent
* parent
= aCellElement
->GetParent();
2699 int32_t offset
= parent
->ComputeIndexOf(aCellElement
);
2701 return CreateAndAddRange(parent
, offset
, aNormalSelection
);
2704 static nsresult
AddCellsToSelection(const nsIContent
* aTableContent
,
2705 int32_t aStartRowIndex
,
2706 int32_t aStartColumnIndex
,
2707 int32_t aEndRowIndex
,
2708 int32_t aEndColumnIndex
,
2709 Selection
& aNormalSelection
) {
2710 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2712 nsTableWrapperFrame
* tableFrame
=
2713 do_QueryFrame(aTableContent
->GetPrimaryFrame());
2714 if (!tableFrame
) { // Check that |table| is a table.
2715 return NS_ERROR_FAILURE
;
2718 nsresult result
= NS_OK
;
2719 uint32_t row
= aStartRowIndex
;
2721 uint32_t col
= aStartColumnIndex
;
2723 nsTableCellFrame
* cellFrame
= tableFrame
->GetCellFrameAt(row
, col
);
2725 // Skip cells that are spanned from previous locations or are already
2728 uint32_t origRow
= cellFrame
->RowIndex();
2729 uint32_t origCol
= cellFrame
->ColIndex();
2730 if (origRow
== row
&& origCol
== col
&& !cellFrame
->IsSelected()) {
2731 result
= SelectCellElement(cellFrame
->GetContent(), aNormalSelection
);
2732 if (NS_FAILED(result
)) {
2737 // Done when we reach end column
2738 if (col
== static_cast<uint32_t>(aEndColumnIndex
)) {
2742 if (aStartColumnIndex
< aEndColumnIndex
) {
2748 if (row
== static_cast<uint32_t>(aEndRowIndex
)) {
2752 if (aStartRowIndex
< aEndRowIndex
) {
2761 nsresult
nsFrameSelection::RemoveCellsFromSelection(nsIContent
* aTable
,
2762 int32_t aStartRowIndex
,
2763 int32_t aStartColumnIndex
,
2764 int32_t aEndRowIndex
,
2765 int32_t aEndColumnIndex
) {
2766 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
2767 const RefPtr
<mozilla::dom::Selection
> selection
= mDomSelections
[index
];
2769 return NS_ERROR_NULL_POINTER
;
2772 return mTableSelection
.UnselectCells(aTable
, aStartRowIndex
,
2773 aStartColumnIndex
, aEndRowIndex
,
2774 aEndColumnIndex
, false, *selection
);
2777 nsresult
nsFrameSelection::RestrictCellsToSelection(nsIContent
* aTable
,
2778 int32_t aStartRowIndex
,
2779 int32_t aStartColumnIndex
,
2780 int32_t aEndRowIndex
,
2781 int32_t aEndColumnIndex
) {
2782 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
2783 const RefPtr
<mozilla::dom::Selection
> selection
= mDomSelections
[index
];
2785 return NS_ERROR_NULL_POINTER
;
2788 return mTableSelection
.UnselectCells(aTable
, aStartRowIndex
,
2789 aStartColumnIndex
, aEndRowIndex
,
2790 aEndColumnIndex
, true, *selection
);
2793 Result
<nsFrameSelection::TableSelection::FirstAndLastCell
, nsresult
>
2794 nsFrameSelection::TableSelection::FindFirstAndLastCellOfRowOrColumn(
2795 const nsIContent
& aCellContent
) const {
2796 const nsIContent
* table
= GetParentTable(&aCellContent
);
2798 return Err(NS_ERROR_NULL_POINTER
);
2801 // Get table and cell layout interfaces to access
2802 // cell data based on cellmap location
2803 // Frames are not ref counted, so don't use an nsCOMPtr
2804 nsTableWrapperFrame
* tableFrame
= do_QueryFrame(table
->GetPrimaryFrame());
2806 return Err(NS_ERROR_FAILURE
);
2808 nsITableCellLayout
* cellLayout
= GetCellLayout(&aCellContent
);
2810 return Err(NS_ERROR_FAILURE
);
2813 // Get location of target cell:
2814 int32_t rowIndex
, colIndex
;
2815 nsresult result
= cellLayout
->GetCellIndexes(rowIndex
, colIndex
);
2816 if (NS_FAILED(result
)) {
2820 // Be sure we start at proper beginning
2821 // (This allows us to select row or col given ANY cell!)
2822 if (mMode
== TableSelectionMode::Row
) {
2825 if (mMode
== TableSelectionMode::Column
) {
2829 FirstAndLastCell firstAndLastCell
;
2831 // Loop through all cells in column or row to find first and last
2832 nsCOMPtr
<nsIContent
> curCellContent
=
2833 tableFrame
->GetCellAt(rowIndex
, colIndex
);
2834 if (!curCellContent
) {
2838 if (!firstAndLastCell
.mFirst
) {
2839 firstAndLastCell
.mFirst
= curCellContent
;
2842 firstAndLastCell
.mLast
= std::move(curCellContent
);
2844 // Move to next cell in cellmap, skipping spanned locations
2845 if (mMode
== TableSelectionMode::Row
) {
2846 colIndex
+= tableFrame
->GetEffectiveRowSpanAt(rowIndex
, colIndex
);
2848 rowIndex
+= tableFrame
->GetEffectiveRowSpanAt(rowIndex
, colIndex
);
2851 return firstAndLastCell
;
2854 nsresult
nsFrameSelection::TableSelection::SelectRowOrColumn(
2855 nsIContent
* aCellContent
, Selection
& aNormalSelection
) {
2856 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2858 if (!aCellContent
) {
2859 return NS_ERROR_NULL_POINTER
;
2862 Result
<FirstAndLastCell
, nsresult
> firstAndLastCell
=
2863 FindFirstAndLastCellOfRowOrColumn(*aCellContent
);
2864 if (firstAndLastCell
.isErr()) {
2865 return firstAndLastCell
.unwrapErr();
2868 // Use SelectBlockOfCells:
2869 // This will replace existing selection,
2870 // but allow unselecting by dragging out of selected region
2871 if (firstAndLastCell
.inspect().mFirst
&& firstAndLastCell
.inspect().mLast
) {
2874 if (!mStartSelectedCell
) {
2875 // We are starting a new block, so select the first cell
2876 rv
= ::SelectCellElement(firstAndLastCell
.inspect().mFirst
,
2878 if (NS_FAILED(rv
)) {
2881 mStartSelectedCell
= firstAndLastCell
.inspect().mFirst
;
2884 rv
= SelectBlockOfCells(mStartSelectedCell
,
2885 firstAndLastCell
.inspect().mLast
, aNormalSelection
);
2887 // This gets set to the cell at end of row/col,
2888 // but we need it to be the cell under cursor
2889 mEndSelectedCell
= aCellContent
;
2894 // This is a more efficient strategy that appends row to current selection,
2895 // but doesn't allow dragging OFF of an existing selection to unselect!
2897 // Loop through all cells in column or row
2898 result
= tableLayout
->GetCellDataAt(rowIndex
, colIndex
,
2899 getter_AddRefs(cellElement
),
2900 curRowIndex
, curColIndex
,
2902 actualRowSpan
, actualColSpan
,
2904 if (NS_FAILED(result
)) return result
;
2905 // We're done when cell is not found
2906 if (!cellElement
) break;
2909 // Check spans else we infinitely loop
2910 NS_ASSERTION(actualColSpan
, "actualColSpan is 0!");
2911 NS_ASSERTION(actualRowSpan
, "actualRowSpan is 0!");
2913 // Skip cells that are already selected or span from outside our region
2914 if (!isSelected
&& rowIndex
== curRowIndex
&& colIndex
== curColIndex
)
2916 result
= SelectCellElement(cellElement
);
2917 if (NS_FAILED(result
)) return result
;
2919 // Move to next row or column in cellmap, skipping spanned locations
2920 if (mMode
== TableSelectionMode::Row
)
2921 colIndex
+= actualColSpan
;
2923 rowIndex
+= actualRowSpan
;
2925 while (cellElement
);
2932 nsIContent
* nsFrameSelection::GetFirstCellNodeInRange(const nsRange
* aRange
) {
2933 if (!aRange
) return nullptr;
2935 nsIContent
* childContent
= aRange
->GetChildAtStartOffset();
2936 if (!childContent
) return nullptr;
2937 // Don't return node if not a cell
2938 if (!IsCell(childContent
)) return nullptr;
2940 return childContent
;
2943 nsRange
* nsFrameSelection::TableSelection::GetFirstCellRange(
2944 const mozilla::dom::Selection
& aNormalSelection
) {
2945 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2947 nsRange
* firstRange
= aNormalSelection
.GetRangeAt(0);
2948 if (!GetFirstCellNodeInRange(firstRange
)) {
2952 // Setup for next cell
2953 mSelectedCellIndex
= 1;
2958 nsRange
* nsFrameSelection::TableSelection::GetNextCellRange(
2959 const mozilla::dom::Selection
& aNormalSelection
) {
2960 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2962 nsRange
* range
= aNormalSelection
.GetRangeAt(mSelectedCellIndex
);
2964 // Get first node in next range of selection - test if it's a cell
2965 if (!GetFirstCellNodeInRange(range
)) {
2969 // Setup for next cell
2970 mSelectedCellIndex
++;
2976 nsresult
nsFrameSelection::GetCellIndexes(const nsIContent
* aCell
,
2978 int32_t& aColIndex
) {
2979 if (!aCell
) return NS_ERROR_NULL_POINTER
;
2981 aColIndex
= 0; // initialize out params
2984 nsITableCellLayout
* cellLayoutObject
= GetCellLayout(aCell
);
2985 if (!cellLayoutObject
) return NS_ERROR_FAILURE
;
2986 return cellLayoutObject
->GetCellIndexes(aRowIndex
, aColIndex
);
2990 nsIContent
* nsFrameSelection::IsInSameTable(const nsIContent
* aContent1
,
2991 const nsIContent
* aContent2
) {
2992 if (!aContent1
|| !aContent2
) return nullptr;
2994 nsIContent
* tableNode1
= GetParentTable(aContent1
);
2995 nsIContent
* tableNode2
= GetParentTable(aContent2
);
2997 // Must be in the same table. Note that we want to return false for
2998 // the test if both tables are null.
2999 return (tableNode1
== tableNode2
) ? tableNode1
: nullptr;
3003 nsIContent
* nsFrameSelection::GetParentTable(const nsIContent
* aCell
) {
3008 for (nsIContent
* parent
= aCell
->GetParent(); parent
;
3009 parent
= parent
->GetParent()) {
3010 if (parent
->IsHTMLElement(nsGkAtoms::table
)) {
3018 nsresult
nsFrameSelection::SelectCellElement(nsIContent
* aCellElement
) {
3019 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
3020 const RefPtr
<Selection
> selection
= mDomSelections
[index
];
3022 return NS_ERROR_NULL_POINTER
;
3025 return ::SelectCellElement(aCellElement
, *selection
);
3028 nsresult
CreateAndAddRange(nsINode
* aContainer
, int32_t aOffset
,
3029 Selection
& aNormalSelection
) {
3030 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
3033 return NS_ERROR_NULL_POINTER
;
3036 // Set range around child at given offset
3038 RefPtr
<nsRange
> range
=
3039 nsRange::Create(aContainer
, aOffset
, aContainer
, aOffset
+ 1, error
);
3040 if (NS_WARN_IF(error
.Failed())) {
3041 return error
.StealNSResult();
3046 aNormalSelection
.AddRangeAndSelectFramesAndNotifyListeners(*range
, err
);
3047 return err
.StealNSResult();
3050 // End of Table Selection
3052 void nsFrameSelection::SetAncestorLimiter(nsIContent
* aLimiter
) {
3053 if (mLimiters
.mAncestorLimiter
!= aLimiter
) {
3054 mLimiters
.mAncestorLimiter
= aLimiter
;
3055 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
3056 if (!mDomSelections
[index
]) return;
3058 if (!IsValidSelectionPoint(mDomSelections
[index
]->GetFocusNode())) {
3059 ClearNormalSelection();
3060 if (mLimiters
.mAncestorLimiter
) {
3061 SetChangeReasons(nsISelectionListener::NO_REASON
);
3062 nsCOMPtr
<nsIContent
> limiter(mLimiters
.mAncestorLimiter
);
3063 TakeFocus(limiter
, 0, 0, CARET_ASSOCIATE_BEFORE
,
3064 FocusMode::kCollapseToNewPoint
);
3070 void nsFrameSelection::SetDelayedCaretData(WidgetMouseEvent
* aMouseEvent
) {
3072 mDelayedMouseEvent
.mIsValid
= true;
3073 mDelayedMouseEvent
.mIsShift
= aMouseEvent
->IsShift();
3074 mDelayedMouseEvent
.mClickCount
= aMouseEvent
->mClickCount
;
3076 mDelayedMouseEvent
.mIsValid
= false;
3080 void nsFrameSelection::DisconnectFromPresShell() {
3081 if (mAccessibleCaretEnabled
) {
3082 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
3083 mDomSelections
[index
]->StopNotifyingAccessibleCaretEventHub();
3086 StopAutoScrollTimer();
3087 for (size_t i
= 0; i
< ArrayLength(mDomSelections
); i
++) {
3088 mDomSelections
[i
]->Clear(nullptr);
3090 mPresShell
= nullptr;
3097 * Update the selection cache on repaint to handle when a pre-existing
3098 * selection becomes active aka the current selection.
3100 * 1. Change the current selection by click n dragging another selection.
3101 * - Make a selection on content page. Make a selection in a text editor.
3102 * - You can click n drag the content selection to make it active again.
3103 * 2. Change the current selection when switching to a tab with a selection.
3104 * - Make selection in tab.
3105 * - Switching tabs will make its respective selection active.
3107 * Therefore, we only update the selection cache on a repaint
3108 * if the current selection being repainted is not an empty selection.
3110 * If the current selection is empty. The current selection cache
3111 * would be cleared by AutoCopyListener::OnSelectionChange().
3113 static nsresult
UpdateSelectionCacheOnRepaintSelection(Selection
* aSel
) {
3114 PresShell
* presShell
= aSel
->GetPresShell();
3118 nsCOMPtr
<Document
> aDoc
= presShell
->GetDocument();
3120 if (aDoc
&& aSel
&& !aSel
->IsCollapsed()) {
3121 return nsCopySupport::EncodeDocumentWithContextAndPutToClipboard(
3122 aSel
, aDoc
, nsIClipboard::kSelectionCache
, false);
3129 // mozilla::AutoCopyListener
3131 int16_t AutoCopyListener::sClipboardID
= -1;
3135 * On every selection change, we copy to the clipboard anew, creating a
3136 * HTML buffer, a transferable, an nsISupportsString and
3137 * a huge mess every time. This is basically what
3138 * nsCopySupport::EncodeDocumentWithContextAndPutToClipboard() does to move the
3139 * selection into the clipboard for Edit->Copy.
3141 * What we should do, to make our end of the deal faster:
3142 * Create a singleton transferable with our own magic converter. When selection
3143 * changes (use a quick cache to detect ``real'' changes), we put the new
3144 * Selection in the transferable. Our magic converter will take care of
3145 * transferable->whatever-other-format when the time comes to actually
3146 * hand over the clipboard contents.
3149 * - which X clipboard should we populate?
3150 * - should we use a different one than Edit->Copy, so that inadvertant
3151 * selections (or simple clicks, which currently cause a selection
3152 * notification, regardless of if they're in the document which currently has
3153 * selection!) don't lose the contents of the ``application''? Or should we
3154 * just put some intelligence in the ``is this a real selection?'' code to
3155 * protect our selection against clicks in other documents that don't create
3157 * - maybe we should just never clear the X clipboard? That would make this
3158 * problem just go away, which is very tempting.
3161 * nsIClipboard::kSelectionCache is the flag for current selection cache.
3162 * Set the current selection cache on the parent process in
3163 * widget cocoa nsClipboard whenever selection changes.
3167 void AutoCopyListener::OnSelectionChange(Document
* aDocument
,
3168 Selection
& aSelection
,
3170 MOZ_ASSERT(IsValidClipboardID(sClipboardID
));
3172 if (sClipboardID
== nsIClipboard::kSelectionCache
) {
3173 // Do nothing if this isn't in the active window and,
3174 // in the case of Web content, in the frontmost tab.
3175 if (!aDocument
|| !IsInActiveTab(aDocument
)) {
3180 static const int16_t kResasonsToHandle
=
3181 nsISelectionListener::MOUSEUP_REASON
|
3182 nsISelectionListener::SELECTALL_REASON
|
3183 nsISelectionListener::KEYPRESS_REASON
;
3184 if (!(aReason
& kResasonsToHandle
)) {
3185 return; // Don't care if we are still dragging.
3188 if (!aDocument
|| aSelection
.IsCollapsed()) {
3189 #ifdef DEBUG_CLIPBOARD
3190 fprintf(stderr
, "CLIPBOARD: no selection/collapsed selection\n");
3192 if (sClipboardID
!= nsIClipboard::kSelectionCache
) {
3193 // XXX Should we clear X clipboard?
3197 // If on macOS, clear the current selection transferable cached
3198 // on the parent process (nsClipboard) when the selection is empty.
3199 DebugOnly
<nsresult
> rv
= nsCopySupport::ClearSelectionCache();
3200 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3201 "nsCopySupport::ClearSelectionCache() failed");
3205 DebugOnly
<nsresult
> rv
=
3206 nsCopySupport::EncodeDocumentWithContextAndPutToClipboard(
3207 &aSelection
, aDocument
, sClipboardID
, false);
3208 NS_WARNING_ASSERTION(
3210 "nsCopySupport::EncodeDocumentWithContextAndPutToClipboard() failed");