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"
25 #include "mozilla/Unused.h"
30 #include "nsISelectionListener.h"
31 #include "nsContentCID.h"
32 #include "nsDeviceContext.h"
33 #include "nsIContent.h"
35 #include "nsITableCellLayout.h"
37 #include "nsTableWrapperFrame.h"
38 #include "nsTableCellFrame.h"
39 #include "nsIScrollableFrame.h"
40 #include "nsCCUncollectableMarker.h"
41 #include "nsTextFragment.h"
43 #include "nsContentUtils.h"
44 #include "nsCSSFrameConstructor.h"
46 #include "nsGkAtoms.h"
47 #include "nsIFrameTraversal.h"
48 #include "nsLayoutUtils.h"
49 #include "nsLayoutCID.h"
50 #include "nsBidiPresUtils.h"
51 static NS_DEFINE_CID(kFrameTraversalCID
, NS_FRAMETRAVERSAL_CID
);
52 #include "nsTextFrame.h"
54 #include "nsThreadUtils.h"
55 #include "mozilla/Preferences.h"
57 #include "mozilla/PresShell.h"
58 #include "nsPresContext.h"
61 #include "mozilla/MouseEvents.h"
62 #include "mozilla/TextEvents.h"
65 #include "mozilla/dom/Document.h"
67 #include "nsISelectionController.h" //for the enums
68 #include "nsCopySupport.h"
69 #include "nsIClipboard.h"
70 #include "nsIFrameInlines.h"
73 #include "mozilla/AutoCopyListener.h"
74 #include "mozilla/dom/Element.h"
75 #include "mozilla/dom/Selection.h"
76 #include "mozilla/dom/ShadowRoot.h"
77 #include "mozilla/dom/StaticRange.h"
78 #include "mozilla/dom/Text.h"
79 #include "mozilla/ErrorResult.h"
80 #include "mozilla/dom/SelectionBinding.h"
81 #include "mozilla/AsyncEventDispatcher.h"
82 #include "mozilla/Telemetry.h"
83 #include "mozilla/layers/ScrollInputMethods.h"
85 #include "nsFocusManager.h"
86 #include "nsPIDOMWindow.h"
88 using namespace mozilla
;
89 using namespace mozilla::dom
;
90 using mozilla::layers::ScrollInputMethod
;
92 static LazyLogModule
sFrameSelectionLog("FrameSelection");
94 //#define DEBUG_TABLE 1
97 * Add cells to the selection inside of the given cells range.
99 * @param aTable [in] HTML table element
100 * @param aStartRowIndex [in] row index where the cells range starts
101 * @param aStartColumnIndex [in] column index where the cells range starts
102 * @param aEndRowIndex [in] row index where the cells range ends
103 * @param aEndColumnIndex [in] column index where the cells range ends
105 static nsresult
AddCellsToSelection(const nsIContent
* aTableContent
,
106 int32_t aStartRowIndex
,
107 int32_t aStartColumnIndex
,
108 int32_t aEndRowIndex
,
109 int32_t aEndColumnIndex
,
110 Selection
& aNormalSelection
);
112 static nsAtom
* GetTag(nsINode
* aNode
);
114 static nsINode
* GetClosestInclusiveTableCellAncestor(nsINode
* aDomNode
);
115 MOZ_CAN_RUN_SCRIPT_BOUNDARY
static nsresult
CreateAndAddRange(
116 nsINode
* aContainer
, int32_t aOffset
, Selection
& aNormalSelection
);
117 static nsresult
SelectCellElement(nsIContent
* aCellElement
,
118 Selection
& aNormalSelection
);
121 static nsresult
UpdateSelectionCacheOnRepaintSelection(Selection
* aSel
);
125 static void printRange(nsRange
* aDomRange
);
126 # define DEBUG_OUT_RANGE(x) printRange(x)
128 # define DEBUG_OUT_RANGE(x)
129 #endif // PRINT_RANGE
131 /******************************************************************************
133 ******************************************************************************/
135 //#define DEBUG_SELECTION // uncomment for printf describing every collapse and
136 // extend. #define DEBUG_NAVIGATION
138 //#define DEBUG_TABLE_SELECTION 1
140 nsPeekOffsetStruct::nsPeekOffsetStruct(
141 nsSelectionAmount aAmount
, nsDirection aDirection
, int32_t aStartOffset
,
142 nsPoint aDesiredCaretPos
, bool aJumpLines
, bool aScrollViewStop
,
143 bool aIsKeyboardSelect
, bool aVisual
, bool aExtend
,
144 ForceEditableRegion aForceEditableRegion
,
145 EWordMovementType aWordMovementType
, bool aTrimSpaces
)
147 mDirection(aDirection
),
148 mStartOffset(aStartOffset
),
149 mDesiredCaretPos(aDesiredCaretPos
),
150 mWordMovementType(aWordMovementType
),
151 mJumpLines(aJumpLines
),
152 mTrimSpaces(aTrimSpaces
),
153 mScrollViewStop(aScrollViewStop
),
154 mIsKeyboardSelect(aIsKeyboardSelect
),
157 mForceEditableRegion(aForceEditableRegion
== ForceEditableRegion::Yes
),
159 mResultFrame(nullptr),
161 mAttach(CARET_ASSOCIATE_BEFORE
) {}
163 // Array which contains index of each SelecionType in Selection::mDOMSelections.
164 // For avoiding using if nor switch to retrieve the index, this needs to have
165 // -1 for SelectionTypes which won't be created its Selection instance.
166 static const int8_t kIndexOfSelections
[] = {
167 -1, // SelectionType::eInvalid
168 -1, // SelectionType::eNone
169 0, // SelectionType::eNormal
170 1, // SelectionType::eSpellCheck
171 2, // SelectionType::eIMERawClause
172 3, // SelectionType::eIMESelectedRawClause
173 4, // SelectionType::eIMEConvertedClause
174 5, // SelectionType::eIMESelectedClause
175 6, // SelectionType::eAccessibility
176 7, // SelectionType::eFind
177 8, // SelectionType::eURLSecondary
178 9, // SelectionType::eURLStrikeout
181 inline int8_t GetIndexFromSelectionType(SelectionType aSelectionType
) {
182 // The enum value of eInvalid is -1 and the others are sequential value
183 // starting from 0. Therefore, |SelectionType + 1| is the index of
184 // kIndexOfSelections.
185 return kIndexOfSelections
[static_cast<int8_t>(aSelectionType
) + 1];
189 The limiter is used specifically for the text areas and textfields
190 In that case it is the DIV tag that is anonymously created for the text
191 areas/fields. Text nodes and BR nodes fall beneath it. In the case of a
192 BR node the limiter will be the parent and the offset will point before or
193 after the BR node. In the case of the text node the parent content is
194 the text node itself and the offset will be the exact character position.
195 The offset is not important to check for validity. Simply look at the
196 passed in content. If it equals the limiter then the selection point is valid.
197 If its parent it the limiter then the point is also valid. In the case of
198 NO limiter all points are valid since you are in a topmost iframe. (browser
201 bool nsFrameSelection::IsValidSelectionPoint(nsINode
* aNode
) const {
206 nsIContent
* limiter
= GetLimiter();
207 if (limiter
&& limiter
!= aNode
&& limiter
!= aNode
->GetParent()) {
208 // if newfocus == the limiter. that's ok. but if not there and not parent
210 return false; // not in the right content. tLimiter said so
213 limiter
= GetAncestorLimiter();
214 return !limiter
|| aNode
->IsInclusiveDescendantOf(limiter
);
218 struct MOZ_RAII AutoPrepareFocusRange
{
219 AutoPrepareFocusRange(Selection
* aSelection
,
220 const bool aMultiRangeSelection
) {
221 MOZ_ASSERT(aSelection
);
222 MOZ_ASSERT(aSelection
->GetType() == SelectionType::eNormal
);
224 if (aSelection
->mStyledRanges
.mRanges
.Length() <= 1) {
228 if (aSelection
->mFrameSelection
->IsUserSelectionReason()) {
229 mUserSelect
.emplace(aSelection
);
232 nsTArray
<StyledRange
>& ranges
= aSelection
->mStyledRanges
.mRanges
;
233 if (!aSelection
->mUserInitiated
|| aMultiRangeSelection
) {
234 // Scripted command or the user is starting a new explicit multi-range
236 for (StyledRange
& entry
: ranges
) {
237 entry
.mRange
->SetIsGenerated(false);
242 if (!IsAnchorRelativeOperation(
243 aSelection
->mFrameSelection
->mSelectionChangeReasons
)) {
247 // This operation is against the anchor but our current mAnchorFocusRange
248 // represents the focus in a multi-range selection. The anchor from a user
249 // perspective is the most distant generated range on the opposite side.
250 // Find that range and make it the mAnchorFocusRange.
251 nsRange
* const newAnchorFocusRange
=
252 FindGeneratedRangeMostDistantFromAnchor(*aSelection
);
254 if (!newAnchorFocusRange
) {
255 // There are no generated ranges - that's fine.
259 // Setup the new mAnchorFocusRange and mark the old one as generated.
260 if (aSelection
->mAnchorFocusRange
) {
261 aSelection
->mAnchorFocusRange
->SetIsGenerated(true);
264 newAnchorFocusRange
->SetIsGenerated(false);
265 aSelection
->mAnchorFocusRange
= newAnchorFocusRange
;
267 RemoveGeneratedRanges(*aSelection
);
269 if (aSelection
->mFrameSelection
) {
270 aSelection
->mFrameSelection
->InvalidateDesiredCaretPos();
275 static nsRange
* FindGeneratedRangeMostDistantFromAnchor(
276 const Selection
& aSelection
) {
277 const nsTArray
<StyledRange
>& ranges
= aSelection
.mStyledRanges
.mRanges
;
278 const size_t len
= ranges
.Length();
279 nsRange
* result
{nullptr};
280 if (aSelection
.GetDirection() == eDirNext
) {
281 for (size_t i
= 0; i
< len
; ++i
) {
282 if (ranges
[i
].mRange
->IsGenerated()) {
283 result
= ranges
[i
].mRange
;
290 if (ranges
[i
].mRange
->IsGenerated()) {
291 result
= ranges
[i
].mRange
;
300 static void RemoveGeneratedRanges(Selection
& aSelection
) {
301 RefPtr
<nsPresContext
> presContext
= aSelection
.GetPresContext();
302 nsTArray
<StyledRange
>& ranges
= aSelection
.mStyledRanges
.mRanges
;
303 size_t i
= ranges
.Length();
305 nsRange
* range
= ranges
[i
].mRange
;
306 if (range
->IsGenerated()) {
307 range
->UnregisterSelection();
308 aSelection
.SelectFrames(presContext
, range
, false);
309 ranges
.RemoveElementAt(i
);
315 * @aParam aSelectionChangeReasons can be multiple of the reasons defined in
316 nsISelectionListener.idl.
318 static bool IsAnchorRelativeOperation(const int16_t aSelectionChangeReasons
) {
319 return aSelectionChangeReasons
&
320 (nsISelectionListener::DRAG_REASON
|
321 nsISelectionListener::MOUSEDOWN_REASON
|
322 nsISelectionListener::MOUSEUP_REASON
|
323 nsISelectionListener::COLLAPSETOSTART_REASON
);
326 Maybe
<Selection::AutoUserInitiated
> mUserSelect
;
329 } // namespace mozilla
331 ////////////BEGIN nsFrameSelection methods
333 template Result
<RefPtr
<nsRange
>, nsresult
>
334 nsFrameSelection::CreateRangeExtendedToSomewhere(
335 nsDirection aDirection
, const nsSelectionAmount aAmount
,
336 CaretMovementStyle aMovementStyle
);
337 template Result
<RefPtr
<StaticRange
>, nsresult
>
338 nsFrameSelection::CreateRangeExtendedToSomewhere(
339 nsDirection aDirection
, const nsSelectionAmount aAmount
,
340 CaretMovementStyle aMovementStyle
);
342 nsFrameSelection::nsFrameSelection(PresShell
* aPresShell
, nsIContent
* aLimiter
,
343 const bool aAccessibleCaretEnabled
) {
344 for (size_t i
= 0; i
< ArrayLength(mDomSelections
); i
++) {
345 mDomSelections
[i
] = new Selection(kPresentSelectionTypes
[i
], this);
349 // On macOS, cache the current selection to send to service menu of macOS.
350 bool enableAutoCopy
= true;
351 AutoCopyListener::Init(nsIClipboard::kSelectionCache
);
352 #else // #ifdef XP_MACOSX
353 // Check to see if the auto-copy pref is enabled and make the normal
354 // Selection notifies auto-copy listener of its changes.
355 bool enableAutoCopy
= AutoCopyListener::IsPrefEnabled();
356 if (enableAutoCopy
) {
357 AutoCopyListener::Init(nsIClipboard::kSelectionClipboard
);
359 #endif // #ifdef XP_MACOSX #else
361 if (enableAutoCopy
) {
362 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
363 if (mDomSelections
[index
]) {
364 mDomSelections
[index
]->NotifyAutoCopy();
368 mPresShell
= aPresShell
;
370 mLimiters
.mLimiter
= aLimiter
;
372 // This should only ever be initialized on the main thread, so we are OK here.
373 MOZ_ASSERT(NS_IsMainThread());
375 mAccessibleCaretEnabled
= aAccessibleCaretEnabled
;
376 if (mAccessibleCaretEnabled
) {
377 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
378 mDomSelections
[index
]->MaybeNotifyAccessibleCaretEventHub(aPresShell
);
381 bool plaintextControl
= (aLimiter
!= nullptr);
382 bool initSelectEvents
=
383 plaintextControl
? StaticPrefs::dom_select_events_textcontrols_enabled()
384 : StaticPrefs::dom_select_events_enabled();
386 Document
* doc
= aPresShell
->GetDocument();
387 if (initSelectEvents
|| (doc
&& doc
->NodePrincipal()->IsSystemPrincipal())) {
388 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
389 if (mDomSelections
[index
]) {
390 mDomSelections
[index
]->EnableSelectionChangeEvent();
395 nsFrameSelection::~nsFrameSelection() = default;
397 NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameSelection
)
399 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameSelection
)
400 for (size_t i
= 0; i
< ArrayLength(tmp
->mDomSelections
); ++i
) {
401 tmp
->mDomSelections
[i
] = nullptr;
404 NS_IMPL_CYCLE_COLLECTION_UNLINK(
405 mTableSelection
.mClosestInclusiveTableCellAncestor
)
406 tmp
->mTableSelection
.mMode
= TableSelectionMode::None
;
407 tmp
->mTableSelection
.mDragSelectingCells
= false;
408 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection
.mStartSelectedCell
)
409 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection
.mEndSelectedCell
)
410 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection
.mAppendStartSelectedCell
)
411 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection
.mUnselectCellOnMouseUp
)
412 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMaintainedRange
.mRange
)
413 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiters
.mLimiter
)
414 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiters
.mAncestorLimiter
)
415 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
416 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameSelection
)
417 if (tmp
->mPresShell
&& tmp
->mPresShell
->GetDocument() &&
418 nsCCUncollectableMarker::InGeneration(
419 cb
, tmp
->mPresShell
->GetDocument()->GetMarkedCCGeneration())) {
420 return NS_SUCCESS_INTERRUPTED_TRAVERSE
;
422 for (size_t i
= 0; i
< ArrayLength(tmp
->mDomSelections
); ++i
) {
423 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDomSelections
[i
])
426 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
427 mTableSelection
.mClosestInclusiveTableCellAncestor
)
428 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection
.mStartSelectedCell
)
429 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection
.mEndSelectedCell
)
430 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection
.mAppendStartSelectedCell
)
431 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection
.mUnselectCellOnMouseUp
)
432 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMaintainedRange
.mRange
)
433 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiters
.mLimiter
)
434 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiters
.mAncestorLimiter
)
435 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
437 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsFrameSelection
, AddRef
)
438 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsFrameSelection
, Release
)
440 bool nsFrameSelection::Caret::IsVisualMovement(
441 bool aContinueSelection
, CaretMovementStyle aMovementStyle
) const {
442 int32_t movementFlag
= StaticPrefs::bidi_edit_caret_movement_style();
443 return aMovementStyle
== eVisual
||
444 (aMovementStyle
== eUsePrefStyle
&&
445 (movementFlag
== 1 || (movementFlag
== 2 && !aContinueSelection
)));
448 // Get the x (or y, in vertical writing mode) position requested
449 // by the Key Handling for line-up/down
450 nsresult
nsFrameSelection::DesiredCaretPos::FetchPos(
451 nsPoint
& aDesiredCaretPos
, const PresShell
& aPresShell
,
452 Selection
& aNormalSelection
) const {
453 MOZ_ASSERT(aNormalSelection
.GetType() == SelectionType::eNormal
);
456 aDesiredCaretPos
= mValue
;
460 RefPtr
<nsCaret
> caret
= aPresShell
.GetCaret();
462 return NS_ERROR_NULL_POINTER
;
465 caret
->SetSelection(&aNormalSelection
);
468 nsIFrame
* caretFrame
= caret
->GetGeometry(&coord
);
470 return NS_ERROR_FAILURE
;
472 nsPoint
viewOffset(0, 0);
473 nsView
* view
= nullptr;
474 caretFrame
->GetOffsetFromView(viewOffset
, &view
);
478 aDesiredCaretPos
= coord
.TopLeft();
482 void nsFrameSelection::InvalidateDesiredCaretPos() // do not listen to
483 // mDesiredCaretPos.mValue;
484 // you must get another.
486 mDesiredCaretPos
.Invalidate();
489 void nsFrameSelection::DesiredCaretPos::Invalidate() { mIsSet
= false; }
491 void nsFrameSelection::DesiredCaretPos::Set(const nsPoint
& aPos
) {
496 nsresult
nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(
497 nsIFrame
* aFrame
, const nsPoint
& aPoint
, nsIFrame
** aRetFrame
,
498 nsPoint
& aRetPoint
) const {
500 // The whole point of this method is to return a frame and point that
501 // that lie within the same valid subtree as the anchor node's frame,
502 // for use with the method GetContentAndOffsetsFromPoint().
504 // A valid subtree is defined to be one where all the content nodes in
505 // the tree have a valid parent-child relationship.
507 // If the anchor frame and aFrame are in the same subtree, aFrame will
508 // be returned in aRetFrame. If they are in different subtrees, we
509 // return the frame for the root of the subtree.
512 if (!aFrame
|| !aRetFrame
) {
513 return NS_ERROR_NULL_POINTER
;
520 // Get the frame and content for the selection's anchor point!
523 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
524 if (!mDomSelections
[index
]) {
525 return NS_ERROR_NULL_POINTER
;
528 nsCOMPtr
<nsIContent
> anchorContent
=
529 do_QueryInterface(mDomSelections
[index
]->GetAnchorNode());
530 if (!anchorContent
) {
531 return NS_ERROR_FAILURE
;
535 // Now find the root of the subtree containing the anchor's content.
538 NS_ENSURE_STATE(mPresShell
);
539 RefPtr
<PresShell
> presShell
= mPresShell
;
540 nsIContent
* anchorRoot
= anchorContent
->GetSelectionRootContent(presShell
);
541 NS_ENSURE_TRUE(anchorRoot
, NS_ERROR_UNEXPECTED
);
544 // Now find the root of the subtree containing aFrame's content.
547 nsCOMPtr
<nsIContent
> content
= aFrame
->GetContent();
550 nsIContent
* contentRoot
= content
->GetSelectionRootContent(presShell
);
551 NS_ENSURE_TRUE(contentRoot
, NS_ERROR_UNEXPECTED
);
553 if (anchorRoot
== contentRoot
) {
554 // If the aFrame's content isn't the capturing content, it should be
555 // a descendant. At this time, we can return simply.
556 nsIContent
* capturedContent
= PresShell::GetCapturingContent();
557 if (capturedContent
!= content
) {
561 // Find the frame under the mouse cursor with the root frame.
562 // At this time, don't use the anchor's frame because it may not have
563 // fixed positioned frames.
564 nsIFrame
* rootFrame
= presShell
->GetRootFrame();
565 nsPoint ptInRoot
= aPoint
+ aFrame
->GetOffsetTo(rootFrame
);
566 nsIFrame
* cursorFrame
=
567 nsLayoutUtils::GetFrameForPoint(RelativeTo
{rootFrame
}, ptInRoot
);
569 // If the mouse cursor in on a frame which is descendant of same
570 // selection root, we can expand the selection to the frame.
571 if (cursorFrame
&& cursorFrame
->PresShell() == presShell
) {
572 nsCOMPtr
<nsIContent
> cursorContent
= cursorFrame
->GetContent();
573 NS_ENSURE_TRUE(cursorContent
, NS_ERROR_FAILURE
);
574 nsIContent
* cursorContentRoot
=
575 cursorContent
->GetSelectionRootContent(presShell
);
576 NS_ENSURE_TRUE(cursorContentRoot
, NS_ERROR_UNEXPECTED
);
577 if (cursorContentRoot
== anchorRoot
) {
578 *aRetFrame
= cursorFrame
;
579 aRetPoint
= aPoint
+ aFrame
->GetOffsetTo(cursorFrame
);
583 // Otherwise, e.g., the cursor isn't on any frames (e.g., the mouse
584 // cursor is out of the window), we should use the frame of the anchor
590 // When we can't find a frame which is under the mouse cursor and has a same
591 // selection root as the anchor node's, we should return the selection root
595 *aRetFrame
= anchorRoot
->GetPrimaryFrame();
598 return NS_ERROR_FAILURE
;
602 // Now make sure that aRetPoint is converted to the same coordinate
603 // system used by aRetFrame.
606 aRetPoint
= aPoint
+ aFrame
->GetOffsetTo(*aRetFrame
);
611 void nsFrameSelection::SetCaretBidiLevelAndMaybeSchedulePaint(
612 nsBidiLevel aLevel
) {
613 // If the current level is undefined, we have just inserted new text.
614 // In this case, we don't want to reset the keyboard language
615 mCaret
.mBidiLevel
= aLevel
;
617 RefPtr
<nsCaret
> caret
;
618 if (mPresShell
&& (caret
= mPresShell
->GetCaret())) {
619 caret
->SchedulePaint();
623 nsBidiLevel
nsFrameSelection::GetCaretBidiLevel() const {
624 return mCaret
.mBidiLevel
;
627 void nsFrameSelection::UndefineCaretBidiLevel() {
628 mCaret
.mBidiLevel
|= BIDI_LEVEL_UNDEFINED
;
632 void printRange(nsRange
* aDomRange
) {
634 printf("NULL Range\n");
636 nsINode
* startNode
= aDomRange
->GetStartContainer();
637 nsINode
* endNode
= aDomRange
->GetEndContainer();
638 int32_t startOffset
= aDomRange
->StartOffset();
639 int32_t endOffset
= aDomRange
->EndOffset();
641 printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n",
642 (unsigned long)aDomRange
, (unsigned long)startNode
, (long)startOffset
,
643 (unsigned long)endNode
, (long)endOffset
);
645 #endif /* PRINT_RANGE */
647 static nsAtom
* GetTag(nsINode
* aNode
) {
648 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aNode
);
650 MOZ_ASSERT_UNREACHABLE("bad node passed to GetTag()");
654 return content
->NodeInfo()->NameAtom();
658 * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor.
660 static nsINode
* GetClosestInclusiveTableCellAncestor(nsINode
* aDomNode
) {
661 if (!aDomNode
) return nullptr;
662 nsINode
* current
= aDomNode
;
663 // Start with current node and look for a table cell
665 nsAtom
* tag
= GetTag(current
);
666 if (tag
== nsGkAtoms::td
|| tag
== nsGkAtoms::th
) return current
;
667 current
= current
->GetParent();
672 static nsDirection
GetCaretDirection(const nsIFrame
& aFrame
,
673 nsDirection aDirection
,
674 bool aVisualMovement
) {
675 const nsBidiDirection paragraphDirection
=
676 nsBidiPresUtils::ParagraphDirection(&aFrame
);
677 return (aVisualMovement
&& paragraphDirection
== NSBIDI_RTL
)
678 ? nsDirection(1 - aDirection
)
682 nsresult
nsFrameSelection::MoveCaret(nsDirection aDirection
,
683 bool aContinueSelection
,
684 const nsSelectionAmount aAmount
,
685 CaretMovementStyle aMovementStyle
) {
686 NS_ENSURE_STATE(mPresShell
);
687 // Flush out layout, since we need it to be up to date to do caret
689 OwningNonNull
<PresShell
> presShell(*mPresShell
);
690 presShell
->FlushPendingNotifications(FlushType::Layout
);
696 nsPresContext
* context
= mPresShell
->GetPresContext();
698 return NS_ERROR_FAILURE
;
701 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
702 const RefPtr
<Selection
> sel
= mDomSelections
[index
];
704 return NS_ERROR_NULL_POINTER
;
707 int32_t scrollFlags
= Selection::SCROLL_FOR_CARET_MOVE
;
708 if (sel
->IsEditorSelection()) {
709 // If caret moves in editor, it should cause scrolling even if it's in
710 // overflow: hidden;.
711 scrollFlags
|= Selection::SCROLL_OVERFLOW_HIDDEN
;
714 int32_t caretStyle
= StaticPrefs::layout_selection_caret_style();
717 && aAmount
!= eSelectLine
720 // Put caret at the selection edge in the |aDirection| direction.
724 const bool doCollapse
= !sel
->IsCollapsed() && !aContinueSelection
&&
725 caretStyle
== 2 && aAmount
<= eSelectLine
;
727 if (aDirection
== eDirPrevious
) {
728 SetChangeReasons(nsISelectionListener::COLLAPSETOSTART_REASON
);
729 mCaret
.mHint
= CARET_ASSOCIATE_AFTER
;
731 SetChangeReasons(nsISelectionListener::COLLAPSETOEND_REASON
);
732 mCaret
.mHint
= CARET_ASSOCIATE_BEFORE
;
735 SetChangeReasons(nsISelectionListener::KEYPRESS_REASON
);
738 AutoPrepareFocusRange
prep(sel
, false);
740 // we must keep this around and revalidate it when its just UP/DOWN
741 nsPoint
desiredPos(0, 0);
743 if (aAmount
== eSelectLine
) {
744 nsresult result
= mDesiredCaretPos
.FetchPos(desiredPos
, *mPresShell
, *sel
);
745 if (NS_FAILED(result
)) {
748 mDesiredCaretPos
.Set(desiredPos
);
751 bool visualMovement
=
752 mCaret
.IsVisualMovement(aContinueSelection
, aMovementStyle
);
753 nsIFrame
* frame
= sel
->GetPrimaryFrameForFocusNode(visualMovement
);
755 return NS_ERROR_FAILURE
;
758 Result
<bool, nsresult
> isIntraLineCaretMove
= IsIntraLineCaretMove(aAmount
);
759 nsDirection direction
{aDirection
};
760 if (isIntraLineCaretMove
.isErr()) {
761 return isIntraLineCaretMove
.unwrapErr();
763 if (isIntraLineCaretMove
.inspect()) {
764 // Forget old caret position for moving caret to different line since
765 // caret position may be changed.
766 mDesiredCaretPos
.Invalidate();
767 direction
= GetCaretDirection(*frame
, aDirection
, visualMovement
);
771 const nsRange
* anchorFocusRange
= sel
->GetAnchorFocusRange();
772 if (anchorFocusRange
) {
773 RefPtr
<nsINode
> node
;
775 if (visualMovement
&& nsBidiPresUtils::IsReversedDirectionFrame(frame
)) {
776 direction
= nsDirection(1 - direction
);
778 if (direction
== eDirPrevious
) {
779 node
= anchorFocusRange
->GetStartContainer();
780 offset
= anchorFocusRange
->StartOffset();
782 node
= anchorFocusRange
->GetEndContainer();
783 offset
= anchorFocusRange
->EndOffset();
785 sel
->CollapseInLimiter(node
, offset
);
787 sel
->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION
,
788 ScrollAxis(), ScrollAxis(), scrollFlags
);
792 CaretAssociateHint
tHint(mCaret
.mHint
); // temporary variable so we dont set
793 // mCaret.mHint until it is necessary
795 Result
<nsPeekOffsetStruct
, nsresult
> result
= PeekOffsetForCaretMove(
796 direction
, aContinueSelection
, aAmount
, aMovementStyle
, desiredPos
);
798 if (result
.isOk() && result
.inspect().mResultContent
) {
799 const nsPeekOffsetStruct
& pos
= result
.inspect();
801 int32_t currentOffset
, frameStart
, frameEnd
;
803 if (aAmount
<= eSelectWordNoSpace
) {
804 // For left/right, PeekOffset() sets pos.mResultFrame correctly, but does
805 // not set pos.mAttachForward, so determine the hint here based on the
806 // result frame and offset: If we're at the end of a text frame, set the
807 // hint to ASSOCIATE_BEFORE to indicate that we want the caret displayed
808 // at the end of this frame, not at the beginning of the next one.
809 theFrame
= pos
.mResultFrame
;
810 theFrame
->GetOffsets(frameStart
, frameEnd
);
811 currentOffset
= pos
.mContentOffset
;
812 if (frameEnd
== currentOffset
&& !(frameStart
== 0 && frameEnd
== 0))
813 tHint
= CARET_ASSOCIATE_BEFORE
;
815 tHint
= CARET_ASSOCIATE_AFTER
;
817 // For up/down and home/end, pos.mResultFrame might not be set correctly,
818 // or not at all. In these cases, get the frame based on the content and
819 // hint returned by PeekOffset().
821 theFrame
= GetFrameForNodeOffset(pos
.mResultContent
, pos
.mContentOffset
,
822 tHint
, ¤tOffset
);
823 if (!theFrame
) return NS_ERROR_FAILURE
;
825 theFrame
->GetOffsets(frameStart
, frameEnd
);
828 if (context
->BidiEnabled()) {
830 case eSelectBeginLine
:
831 case eSelectEndLine
: {
832 // In Bidi contexts, PeekOffset calculates pos.mContentOffset
833 // differently depending on whether the movement is visual or logical.
834 // For visual movement, pos.mContentOffset depends on the direction-
835 // ality of the first/last frame on the line (theFrame), and the caret
836 // directionality must correspond.
837 FrameBidiData bidiData
= theFrame
->GetBidiData();
838 SetCaretBidiLevelAndMaybeSchedulePaint(
839 visualMovement
? bidiData
.embeddingLevel
: bidiData
.baseLevel
);
843 // If the current position is not a frame boundary, it's enough just
844 // to take the Bidi level of the current frame
845 if ((pos
.mContentOffset
!= frameStart
&&
846 pos
.mContentOffset
!= frameEnd
) ||
847 eSelectLine
== aAmount
) {
848 SetCaretBidiLevelAndMaybeSchedulePaint(
849 theFrame
->GetEmbeddingLevel());
851 BidiLevelFromMove(mPresShell
, pos
.mResultContent
,
852 pos
.mContentOffset
, aAmount
, tHint
);
856 // "pos" is on the stack, so pos.mResultContent has stack lifetime, so using
857 // MOZ_KnownLive is ok.
858 const FocusMode focusMode
= aContinueSelection
859 ? FocusMode::kExtendSelection
860 : FocusMode::kCollapseToNewPoint
;
861 rv
= TakeFocus(MOZ_KnownLive(*pos
.mResultContent
), pos
.mContentOffset
,
862 pos
.mContentOffset
, tHint
, focusMode
);
863 } else if (aAmount
<= eSelectWordNoSpace
&& direction
== eDirNext
&&
864 !aContinueSelection
) {
865 // Collapse selection if PeekOffset failed, we either
866 // 1. bumped into the BRFrame, bug 207623
867 // 2. had select-all in a text input (DIV range), bug 352759.
868 bool isBRFrame
= frame
->IsBrFrame();
869 RefPtr
<nsINode
> node
= sel
->GetFocusNode();
870 sel
->CollapseInLimiter(node
, sel
->FocusOffset());
871 // Note: 'frame' might be dead here.
873 mCaret
.mHint
= CARET_ASSOCIATE_BEFORE
; // We're now at the end of the
874 // frame to the left.
878 rv
= result
.isErr() ? result
.unwrapErr() : NS_OK
;
880 if (NS_SUCCEEDED(rv
)) {
881 rv
= sel
->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION
,
882 ScrollAxis(), ScrollAxis(), scrollFlags
);
888 Result
<nsPeekOffsetStruct
, nsresult
> nsFrameSelection::PeekOffsetForCaretMove(
889 nsDirection aDirection
, bool aContinueSelection
,
890 const nsSelectionAmount aAmount
, CaretMovementStyle aMovementStyle
,
891 const nsPoint
& aDesiredCaretPos
) const {
892 Selection
* selection
=
893 mDomSelections
[GetIndexFromSelectionType(SelectionType::eNormal
)];
895 return Err(NS_ERROR_NULL_POINTER
);
898 const bool visualMovement
=
899 mCaret
.IsVisualMovement(aContinueSelection
, aMovementStyle
);
901 int32_t offsetused
= 0;
903 selection
->GetPrimaryFrameForFocusNode(visualMovement
, &offsetused
);
905 return Err(NS_ERROR_FAILURE
);
908 const auto kForceEditableRegion
=
909 selection
->IsEditorSelection()
910 ? nsPeekOffsetStruct::ForceEditableRegion::Yes
911 : nsPeekOffsetStruct::ForceEditableRegion::No
;
913 // set data using mLimiters.mLimiter to stop on scroll views. If we have a
914 // limiter then we stop peeking when we hit scrollable views. If no limiter
915 // then just let it go ahead
916 nsPeekOffsetStruct
pos(aAmount
, aDirection
, offsetused
, aDesiredCaretPos
,
917 true, !!mLimiters
.mLimiter
, true, visualMovement
,
918 aContinueSelection
, kForceEditableRegion
);
919 nsresult rv
= frame
->PeekOffset(&pos
);
926 nsPrevNextBidiLevels
nsFrameSelection::GetPrevNextBidiLevels(
927 nsIContent
* aNode
, uint32_t aContentOffset
, bool aJumpLines
) const {
928 return GetPrevNextBidiLevels(aNode
, aContentOffset
, mCaret
.mHint
, aJumpLines
);
932 nsPrevNextBidiLevels
nsFrameSelection::GetPrevNextBidiLevels(
933 nsIContent
* aNode
, uint32_t aContentOffset
, CaretAssociateHint aHint
,
935 // Get the level of the frames on each side
936 nsIFrame
* currentFrame
;
937 int32_t currentOffset
;
938 int32_t frameStart
, frameEnd
;
939 nsDirection direction
;
941 nsPrevNextBidiLevels levels
;
942 levels
.SetData(nullptr, nullptr, 0, 0);
945 GetFrameForNodeOffset(aNode
, aContentOffset
, aHint
, ¤tOffset
);
946 if (!currentFrame
) return levels
;
948 currentFrame
->GetOffsets(frameStart
, frameEnd
);
950 if (0 == frameStart
&& 0 == frameEnd
)
951 direction
= eDirPrevious
;
952 else if (frameStart
== currentOffset
)
953 direction
= eDirPrevious
;
954 else if (frameEnd
== currentOffset
)
955 direction
= eDirNext
;
957 // we are neither at the beginning nor at the end of the frame, so we have
959 nsBidiLevel currentLevel
= currentFrame
->GetEmbeddingLevel();
960 levels
.SetData(currentFrame
, currentFrame
, currentLevel
, currentLevel
);
966 ->GetFrameFromDirection(direction
, false, aJumpLines
, true, false)
969 FrameBidiData currentBidi
= currentFrame
->GetBidiData();
970 nsBidiLevel currentLevel
= currentBidi
.embeddingLevel
;
971 nsBidiLevel newLevel
=
972 newFrame
? newFrame
->GetEmbeddingLevel() : currentBidi
.baseLevel
;
974 // If not jumping lines, disregard br frames, since they might be positioned
976 // XXX This could be removed once bug 339786 is fixed.
978 if (currentFrame
->IsBrFrame()) {
979 currentFrame
= nullptr;
980 currentLevel
= currentBidi
.baseLevel
;
982 if (newFrame
&& newFrame
->IsBrFrame()) {
984 newLevel
= currentBidi
.baseLevel
;
988 if (direction
== eDirNext
)
989 levels
.SetData(currentFrame
, newFrame
, currentLevel
, newLevel
);
991 levels
.SetData(newFrame
, currentFrame
, newLevel
, currentLevel
);
996 nsresult
nsFrameSelection::GetFrameFromLevel(nsIFrame
* aFrameIn
,
997 nsDirection aDirection
,
998 nsBidiLevel aBidiLevel
,
999 nsIFrame
** aFrameOut
) const {
1000 NS_ENSURE_STATE(mPresShell
);
1001 nsBidiLevel foundLevel
= 0;
1002 nsIFrame
* foundFrame
= aFrameIn
;
1004 nsCOMPtr
<nsIFrameEnumerator
> frameTraversal
;
1006 nsCOMPtr
<nsIFrameTraversal
> trav(
1007 do_CreateInstance(kFrameTraversalCID
, &result
));
1008 if (NS_FAILED(result
)) return result
;
1011 trav
->NewFrameTraversal(getter_AddRefs(frameTraversal
),
1012 mPresShell
->GetPresContext(), aFrameIn
, eLeaf
,
1014 false, // aLockInScrollView
1015 false, // aFollowOOFs
1016 false // aSkipPopupChecks
1018 if (NS_FAILED(result
)) return result
;
1021 *aFrameOut
= foundFrame
;
1022 foundFrame
= frameTraversal
->Traverse(aDirection
== eDirNext
);
1023 if (!foundFrame
) return NS_ERROR_FAILURE
;
1024 foundLevel
= foundFrame
->GetEmbeddingLevel();
1026 } while (foundLevel
> aBidiLevel
);
1031 nsresult
nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount
) {
1032 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1033 if (!mDomSelections
[index
]) {
1034 return NS_ERROR_NULL_POINTER
;
1037 mMaintainedRange
.MaintainAnchorFocusRange(*mDomSelections
[index
], aAmount
);
1042 void nsFrameSelection::BidiLevelFromMove(PresShell
* aPresShell
,
1044 uint32_t aContentOffset
,
1045 nsSelectionAmount aAmount
,
1046 CaretAssociateHint aHint
) {
1048 // Movement within the line: the new cursor Bidi level is the level of the
1049 // last character moved over
1050 case eSelectCharacter
:
1051 case eSelectCluster
:
1053 case eSelectWordNoSpace
:
1054 case eSelectBeginLine
:
1055 case eSelectEndLine
:
1056 case eSelectNoAmount
: {
1057 nsPrevNextBidiLevels levels
=
1058 GetPrevNextBidiLevels(aNode
, aContentOffset
, aHint
, false);
1060 SetCaretBidiLevelAndMaybeSchedulePaint(aHint
== CARET_ASSOCIATE_BEFORE
1061 ? levels
.mLevelBefore
1062 : levels
.mLevelAfter
);
1066 // Up and Down: the new cursor Bidi level is the smaller of the two
1067 surrounding characters case eSelectLine: case eSelectParagraph:
1068 GetPrevNextBidiLevels(aContext, aNode, aContentOffset, &firstFrame,
1069 &secondFrame, &firstLevel, &secondLevel);
1070 aPresShell->SetCaretBidiLevelAndMaybeSchedulePaint(std::min(firstLevel,
1071 secondLevel)); break;
1075 UndefineCaretBidiLevel();
1079 void nsFrameSelection::BidiLevelFromClick(nsIContent
* aNode
,
1080 uint32_t aContentOffset
) {
1081 nsIFrame
* clickInFrame
= nullptr;
1082 int32_t OffsetNotUsed
;
1084 clickInFrame
= GetFrameForNodeOffset(aNode
, aContentOffset
, mCaret
.mHint
,
1086 if (!clickInFrame
) return;
1088 SetCaretBidiLevelAndMaybeSchedulePaint(clickInFrame
->GetEmbeddingLevel());
1091 void nsFrameSelection::MaintainedRange::AdjustNormalSelection(
1092 const nsIContent
* aContent
, const int32_t aOffset
,
1093 Selection
& aNormalSelection
) const {
1094 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
1096 if (!mRange
|| !aContent
) {
1100 nsINode
* rangeStartNode
= mRange
->GetStartContainer();
1101 nsINode
* rangeEndNode
= mRange
->GetEndContainer();
1102 int32_t rangeStartOffset
= mRange
->StartOffset();
1103 int32_t rangeEndOffset
= mRange
->EndOffset();
1105 const Maybe
<int32_t> relToStart
= nsContentUtils::ComparePoints(
1106 rangeStartNode
, rangeStartOffset
, aContent
, aOffset
);
1107 if (NS_WARN_IF(!relToStart
)) {
1108 // Potentially handle this properly when Selection across Shadow DOM
1109 // boundary is implemented
1110 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
1114 const Maybe
<int32_t> relToEnd
= nsContentUtils::ComparePoints(
1115 rangeEndNode
, rangeEndOffset
, aContent
, aOffset
);
1116 if (NS_WARN_IF(!relToEnd
)) {
1117 // Potentially handle this properly when Selection across Shadow DOM
1118 // boundary is implemented
1119 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
1123 // If aContent/aOffset is inside (or at the edge of) the maintained
1124 // selection, or if it is on the "anchor" side of the maintained selection,
1125 // we need to do something.
1126 if ((*relToStart
<= 0 && *relToEnd
>= 0) ||
1127 (*relToStart
> 0 && aNormalSelection
.GetDirection() == eDirNext
) ||
1128 (*relToEnd
< 0 && aNormalSelection
.GetDirection() == eDirPrevious
)) {
1129 // Set the current range to the maintained range.
1130 aNormalSelection
.ReplaceAnchorFocusRange(mRange
);
1131 // Set the direction of the selection so that the anchor will be on the
1132 // far side of the maintained selection, relative to aContent/aOffset.
1133 aNormalSelection
.SetDirection(*relToStart
> 0 ? eDirPrevious
: eDirNext
);
1137 void nsFrameSelection::MaintainedRange::AdjustContentOffsets(
1138 nsIFrame::ContentOffsets
& aOffsets
, const bool aScrollViewStop
) const {
1139 // Adjust offsets according to maintained amount
1140 if (mRange
&& mAmount
!= eSelectNoAmount
) {
1141 nsINode
* rangenode
= mRange
->GetStartContainer();
1142 int32_t rangeOffset
= mRange
->StartOffset();
1143 const Maybe
<int32_t> relativePosition
= nsContentUtils::ComparePoints(
1144 rangenode
, rangeOffset
, aOffsets
.content
, aOffsets
.offset
);
1145 if (NS_WARN_IF(!relativePosition
)) {
1146 // Potentially handle this properly when Selection across Shadow DOM
1147 // boundary is implemented
1148 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
1152 nsDirection direction
= *relativePosition
> 0 ? eDirPrevious
: eDirNext
;
1153 nsSelectionAmount amount
= mAmount
;
1154 if (amount
== eSelectBeginLine
&& direction
== eDirNext
) {
1155 amount
= eSelectEndLine
;
1159 nsIFrame
* frame
= GetFrameForNodeOffset(aOffsets
.content
, aOffsets
.offset
,
1160 CARET_ASSOCIATE_AFTER
, &offset
);
1162 if (frame
&& amount
== eSelectWord
&& direction
== eDirPrevious
) {
1163 // To avoid selecting the previous word when at start of word,
1164 // first move one character forward.
1165 nsPeekOffsetStruct
charPos(eSelectCharacter
, eDirNext
, offset
,
1166 nsPoint(0, 0), false, aScrollViewStop
, false,
1168 if (NS_SUCCEEDED(frame
->PeekOffset(&charPos
))) {
1169 frame
= charPos
.mResultFrame
;
1170 offset
= charPos
.mContentOffset
;
1174 nsPeekOffsetStruct
pos(amount
, direction
, offset
, nsPoint(0, 0), false,
1175 aScrollViewStop
, false, false, false);
1177 if (frame
&& NS_SUCCEEDED(frame
->PeekOffset(&pos
)) && pos
.mResultContent
) {
1178 aOffsets
.content
= pos
.mResultContent
;
1179 aOffsets
.offset
= pos
.mContentOffset
;
1184 void nsFrameSelection::MaintainedRange::MaintainAnchorFocusRange(
1185 const Selection
& aNormalSelection
, const nsSelectionAmount aAmount
) {
1186 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
1190 const nsRange
* anchorFocusRange
= aNormalSelection
.GetAnchorFocusRange();
1191 if (anchorFocusRange
&& aAmount
!= eSelectNoAmount
) {
1192 mRange
= anchorFocusRange
->CloneRange();
1199 nsresult
nsFrameSelection::HandleClick(nsIContent
* aNewFocus
,
1200 uint32_t aContentOffset
,
1201 uint32_t aContentEndOffset
,
1202 const FocusMode aFocusMode
,
1203 CaretAssociateHint aHint
) {
1204 if (!aNewFocus
) return NS_ERROR_INVALID_ARG
;
1206 if (MOZ_LOG_TEST(sFrameSelectionLog
, LogLevel::Debug
)) {
1207 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1208 MOZ_LOG(sFrameSelectionLog
, LogLevel::Debug
,
1209 ("%s: selection=%p, new focus=%p, offsets=(%u,%u), focus mode=%i",
1211 mDomSelections
[index
] ? mDomSelections
[index
].get() : nullptr,
1212 aNewFocus
, aContentOffset
, aContentEndOffset
,
1213 static_cast<int>(aFocusMode
)));
1216 mDesiredCaretPos
.Invalidate();
1218 if (aFocusMode
!= FocusMode::kExtendSelection
) {
1219 mMaintainedRange
.mRange
= nullptr;
1220 if (!IsValidSelectionPoint(aNewFocus
)) {
1221 mLimiters
.mAncestorLimiter
= nullptr;
1225 // Don't take focus when dragging off of a table
1226 if (!mTableSelection
.mDragSelectingCells
) {
1227 BidiLevelFromClick(aNewFocus
, aContentOffset
);
1228 SetChangeReasons(nsISelectionListener::MOUSEDOWN_REASON
+
1229 nsISelectionListener::DRAG_REASON
);
1231 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1232 RefPtr
<Selection
> selection
= mDomSelections
[index
];
1233 MOZ_ASSERT(selection
);
1235 if (aFocusMode
== FocusMode::kExtendSelection
) {
1236 mMaintainedRange
.AdjustNormalSelection(aNewFocus
, aContentOffset
,
1240 AutoPrepareFocusRange
prep(selection
,
1241 aFocusMode
== FocusMode::kMultiRangeSelection
);
1242 return TakeFocus(*aNewFocus
, aContentOffset
, aContentEndOffset
, aHint
,
1249 void nsFrameSelection::HandleDrag(nsIFrame
* aFrame
, const nsPoint
& aPoint
) {
1250 if (!aFrame
|| !mPresShell
) {
1255 nsIFrame
* newFrame
= 0;
1258 result
= ConstrainFrameAndPointToAnchorSubtree(aFrame
, aPoint
, &newFrame
,
1260 if (NS_FAILED(result
)) return;
1261 if (!newFrame
) return;
1263 nsIFrame::ContentOffsets offsets
=
1264 newFrame
->GetContentOffsetsFromPoint(newPoint
);
1265 if (!offsets
.content
) return;
1267 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1268 RefPtr
<Selection
> selection
= mDomSelections
[index
];
1269 if (newFrame
->IsSelected() && selection
) {
1270 // `MOZ_KnownLive` required because of
1271 // https://bugzilla.mozilla.org/show_bug.cgi?id=1636889.
1272 mMaintainedRange
.AdjustNormalSelection(MOZ_KnownLive(offsets
.content
),
1273 offsets
.offset
, *selection
);
1276 const bool scrollViewStop
= mLimiters
.mLimiter
!= nullptr;
1277 mMaintainedRange
.AdjustContentOffsets(offsets
, scrollViewStop
);
1279 // TODO: no click has happened, rename `HandleClick`.
1280 HandleClick(MOZ_KnownLive(offsets
.content
) /* bug 1636889 */, offsets
.offset
,
1281 offsets
.offset
, FocusMode::kExtendSelection
, offsets
.associate
);
1284 nsresult
nsFrameSelection::StartAutoScrollTimer(nsIFrame
* aFrame
,
1285 const nsPoint
& aPoint
,
1287 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1288 if (!mDomSelections
[index
]) {
1289 return NS_ERROR_NULL_POINTER
;
1292 RefPtr
<Selection
> selection
= mDomSelections
[index
];
1293 return selection
->StartAutoScrollTimer(aFrame
, aPoint
, aDelay
);
1296 void nsFrameSelection::StopAutoScrollTimer() {
1297 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1298 if (!mDomSelections
[index
]) {
1302 mDomSelections
[index
]->StopAutoScrollTimer();
1306 nsINode
* nsFrameSelection::TableSelection::IsContentInActivelyEditableTableCell(
1307 nsPresContext
* aContext
, nsIContent
* aContent
) {
1312 RefPtr
<HTMLEditor
> htmlEditor
= nsContentUtils::GetHTMLEditor(aContext
);
1317 nsINode
* inclusiveTableCellAncestor
=
1318 GetClosestInclusiveTableCellAncestor(aContent
);
1319 if (!inclusiveTableCellAncestor
) {
1323 const Element
* editorHostNode
= htmlEditor
->GetActiveEditingHost();
1324 if (!editorHostNode
) {
1328 const bool editableCell
=
1329 inclusiveTableCellAncestor
->IsInclusiveDescendantOf(editorHostNode
);
1330 return editableCell
? inclusiveTableCellAncestor
: nullptr;
1334 struct ParentAndOffset
{
1335 explicit ParentAndOffset(const nsINode
& aNode
)
1336 : mParent
{aNode
.GetParent()},
1337 mOffset
{mParent
? mParent
->ComputeIndexOf(&aNode
) : 0} {}
1341 // 0, if there's no parent.
1347 hard to go from nodes to frames, easy the other way!
1349 nsresult
nsFrameSelection::TakeFocus(nsIContent
& aNewFocus
,
1350 uint32_t aContentOffset
,
1351 uint32_t aContentEndOffset
,
1352 CaretAssociateHint aHint
,
1353 const FocusMode aFocusMode
) {
1354 NS_ENSURE_STATE(mPresShell
);
1356 if (!IsValidSelectionPoint(&aNewFocus
)) {
1357 return NS_ERROR_FAILURE
;
1360 MOZ_LOG(sFrameSelectionLog
, LogLevel::Verbose
,
1361 ("%s: new focus=%p, offsets=(%u, %u), hint=%i, focusMode=%i",
1362 __FUNCTION__
, &aNewFocus
, aContentOffset
, aContentEndOffset
,
1363 static_cast<int>(aHint
), static_cast<int>(aFocusMode
)));
1365 mPresShell
->FrameSelectionWillTakeFocus(*this);
1367 // Clear all table selection data
1368 mTableSelection
.mMode
= TableSelectionMode::None
;
1369 mTableSelection
.mDragSelectingCells
= false;
1370 mTableSelection
.mStartSelectedCell
= nullptr;
1371 mTableSelection
.mEndSelectedCell
= nullptr;
1372 mTableSelection
.mAppendStartSelectedCell
= nullptr;
1373 mCaret
.mHint
= aHint
;
1375 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1376 if (!mDomSelections
[index
]) return NS_ERROR_NULL_POINTER
;
1378 Maybe
<Selection::AutoUserInitiated
> userSelect
;
1379 if (IsUserSelectionReason()) {
1380 userSelect
.emplace(mDomSelections
[index
]);
1383 // traverse through document and unselect crap here
1384 switch (aFocusMode
) {
1385 case FocusMode::kCollapseToNewPoint
:
1387 case FocusMode::kMultiRangeSelection
: {
1388 // single click? setting cursor down
1389 const Batching saveBatching
=
1390 mBatching
; // hack to use the collapse code.
1391 mBatching
.mCounter
= 1;
1393 if (aFocusMode
== FocusMode::kMultiRangeSelection
) {
1394 // Remove existing collapsed ranges as there's no point in having
1395 // non-anchor/focus collapsed ranges.
1396 mDomSelections
[index
]->RemoveCollapsedRanges();
1399 RefPtr
<nsRange
> newRange
= nsRange::Create(
1400 &aNewFocus
, aContentOffset
, &aNewFocus
, aContentOffset
, error
);
1401 if (NS_WARN_IF(error
.Failed())) {
1402 return error
.StealNSResult();
1404 MOZ_ASSERT(newRange
);
1405 const RefPtr
<Selection
> selection
{mDomSelections
[index
]};
1406 selection
->AddRangeAndSelectFramesAndNotifyListeners(*newRange
,
1409 bool oldDesiredPosSet
=
1410 mDesiredCaretPos
.mIsSet
; // need to keep old desired
1411 // position if it was set.
1412 RefPtr
<Selection
> selection
= mDomSelections
[index
];
1413 selection
->CollapseInLimiter(&aNewFocus
, aContentOffset
);
1414 mDesiredCaretPos
.mIsSet
=
1415 oldDesiredPosSet
; // now reset desired pos back.
1418 mBatching
= saveBatching
;
1420 if (aContentEndOffset
!= aContentOffset
) {
1421 mDomSelections
[index
]->Extend(&aNewFocus
, aContentEndOffset
);
1424 // find out if we are inside a table. if so, find out which one and which
1425 // cell once we do that, the next time we get a takefocus, check the
1426 // parent tree. if we are no longer inside same table ,cell then switch to
1427 // table selection mode. BUT only do this in an editor
1429 NS_ENSURE_STATE(mPresShell
);
1430 RefPtr
<nsPresContext
> context
= mPresShell
->GetPresContext();
1431 mTableSelection
.mClosestInclusiveTableCellAncestor
= nullptr;
1432 if (nsINode
* inclusiveTableCellAncestor
=
1433 TableSelection::IsContentInActivelyEditableTableCell(
1434 context
, &aNewFocus
)) {
1435 mTableSelection
.mClosestInclusiveTableCellAncestor
=
1436 inclusiveTableCellAncestor
;
1437 MOZ_LOG(sFrameSelectionLog
, LogLevel::Debug
,
1438 ("%s: Collapsing into new cell", __FUNCTION__
));
1443 case FocusMode::kExtendSelection
: {
1444 // Now update the range list:
1445 nsINode
* inclusiveTableCellAncestor
=
1446 GetClosestInclusiveTableCellAncestor(&aNewFocus
);
1447 if (mTableSelection
.mClosestInclusiveTableCellAncestor
&&
1448 inclusiveTableCellAncestor
&&
1449 inclusiveTableCellAncestor
!=
1451 .mClosestInclusiveTableCellAncestor
) // switch to cell
1454 MOZ_LOG(sFrameSelectionLog
, LogLevel::Debug
,
1455 ("%s: moving into new cell", __FUNCTION__
));
1457 WidgetMouseEvent
event(false, eVoidEvent
, nullptr,
1458 WidgetMouseEvent::eReal
);
1460 // Start selecting in the cell we were in before
1461 ParentAndOffset parentAndOffset
{
1462 *mTableSelection
.mClosestInclusiveTableCellAncestor
};
1463 if (parentAndOffset
.mParent
) {
1464 const nsresult result
= HandleTableSelection(
1465 parentAndOffset
.mParent
, parentAndOffset
.mOffset
,
1466 TableSelectionMode::Cell
, &event
);
1467 if (NS_WARN_IF(NS_FAILED(result
))) {
1472 // Find the parent of this new cell and extend selection to it
1473 parentAndOffset
= ParentAndOffset
{*inclusiveTableCellAncestor
};
1475 // XXXX We need to REALLY get the current key shift state
1476 // (we'd need to add event listener -- let's not bother for now)
1477 event
.mModifiers
&= ~MODIFIER_SHIFT
; // aContinueSelection;
1478 if (parentAndOffset
.mParent
) {
1479 mTableSelection
.mClosestInclusiveTableCellAncestor
=
1480 inclusiveTableCellAncestor
;
1481 // Continue selection into next cell
1482 const nsresult result
= HandleTableSelection(
1483 parentAndOffset
.mParent
, parentAndOffset
.mOffset
,
1484 TableSelectionMode::Cell
, &event
);
1485 if (NS_WARN_IF(NS_FAILED(result
))) {
1490 // XXXX Problem: Shift+click in browser is appending text selection to
1491 // selected table!!!
1492 // is this the place to erase seleced cells ?????
1493 if (mDomSelections
[index
]->GetDirection() == eDirNext
&&
1494 aContentEndOffset
> aContentOffset
) // didn't go far enough
1496 mDomSelections
[index
]->Extend(
1497 &aNewFocus
, aContentEndOffset
); // this will only redraw the diff
1499 mDomSelections
[index
]->Extend(&aNewFocus
, aContentOffset
);
1505 // Don't notify selection listeners if batching is on:
1510 // Be aware, the Selection instance may be destroyed after this call.
1511 return NotifySelectionListeners(SelectionType::eNormal
);
1514 UniquePtr
<SelectionDetails
> nsFrameSelection::LookUpSelection(
1515 nsIContent
* aContent
, int32_t aContentOffset
, int32_t aContentLength
,
1516 bool aSlowCheck
) const {
1517 if (!aContent
|| !mPresShell
) {
1521 UniquePtr
<SelectionDetails
> details
;
1523 for (size_t j
= 0; j
< ArrayLength(mDomSelections
); j
++) {
1524 if (mDomSelections
[j
]) {
1525 details
= mDomSelections
[j
]->LookUpSelection(
1526 aContent
, aContentOffset
, aContentLength
, std::move(details
),
1527 kPresentSelectionTypes
[j
], aSlowCheck
);
1534 void nsFrameSelection::SetDragState(bool aState
) {
1535 if (mDragState
== aState
) return;
1537 mDragState
= aState
;
1540 mTableSelection
.mDragSelectingCells
= false;
1541 // Notify that reason is mouse up.
1542 SetChangeReasons(nsISelectionListener::MOUSEUP_REASON
);
1543 // Be aware, the Selection instance may be destroyed after this call.
1544 NotifySelectionListeners(SelectionType::eNormal
);
1548 Selection
* nsFrameSelection::GetSelection(SelectionType aSelectionType
) const {
1549 int8_t index
= GetIndexFromSelectionType(aSelectionType
);
1550 if (index
< 0) return nullptr;
1552 return mDomSelections
[index
];
1555 nsresult
nsFrameSelection::ScrollSelectionIntoView(SelectionType aSelectionType
,
1556 SelectionRegion aRegion
,
1557 int16_t aFlags
) const {
1558 int8_t index
= GetIndexFromSelectionType(aSelectionType
);
1559 if (index
< 0) return NS_ERROR_INVALID_ARG
;
1561 if (!mDomSelections
[index
]) return NS_ERROR_NULL_POINTER
;
1563 ScrollAxis verticalScroll
= ScrollAxis();
1564 int32_t flags
= Selection::SCROLL_DO_FLUSH
;
1565 if (aFlags
& nsISelectionController::SCROLL_SYNCHRONOUS
) {
1566 flags
|= Selection::SCROLL_SYNCHRONOUS
;
1567 } else if (aFlags
& nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY
) {
1568 flags
|= Selection::SCROLL_FIRST_ANCESTOR_ONLY
;
1570 if (aFlags
& nsISelectionController::SCROLL_OVERFLOW_HIDDEN
) {
1571 flags
|= Selection::SCROLL_OVERFLOW_HIDDEN
;
1573 if (aFlags
& nsISelectionController::SCROLL_CENTER_VERTICALLY
) {
1575 ScrollAxis(kScrollToCenter
, WhenToScroll::IfNotFullyVisible
);
1577 if (aFlags
& nsISelectionController::SCROLL_FOR_CARET_MOVE
) {
1578 flags
|= Selection::SCROLL_FOR_CARET_MOVE
;
1581 // After ScrollSelectionIntoView(), the pending notifications might be
1582 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
1583 RefPtr
<Selection
> sel
= mDomSelections
[index
];
1584 return sel
->ScrollIntoView(aRegion
, verticalScroll
, ScrollAxis(), flags
);
1587 nsresult
nsFrameSelection::RepaintSelection(SelectionType aSelectionType
) {
1588 int8_t index
= GetIndexFromSelectionType(aSelectionType
);
1589 if (index
< 0) return NS_ERROR_INVALID_ARG
;
1590 if (!mDomSelections
[index
]) return NS_ERROR_NULL_POINTER
;
1591 NS_ENSURE_STATE(mPresShell
);
1593 // On macOS, update the selection cache to the new active selection
1594 // aka the current selection.
1596 // Check that we're in the an active window and, if this is Web content,
1597 // in the frontmost tab.
1598 Document
* doc
= mPresShell
->GetDocument();
1599 if (doc
&& IsInActiveTab(doc
) && aSelectionType
== SelectionType::eNormal
) {
1600 UpdateSelectionCacheOnRepaintSelection(mDomSelections
[index
]);
1603 return mDomSelections
[index
]->Repaint(mPresShell
->GetPresContext());
1606 static bool IsDisplayContents(const nsIContent
* aContent
) {
1607 return aContent
->IsElement() && aContent
->AsElement()->IsDisplayContents();
1611 nsIFrame
* nsFrameSelection::GetFrameForNodeOffset(nsIContent
* aNode
,
1613 CaretAssociateHint aHint
,
1614 int32_t* aReturnOffset
) {
1615 if (!aNode
|| !aReturnOffset
) return nullptr;
1617 if (aOffset
< 0) return nullptr;
1619 if (!aNode
->GetPrimaryFrame() && !IsDisplayContents(aNode
)) {
1623 nsIFrame
* returnFrame
= nullptr;
1624 nsCOMPtr
<nsIContent
> theNode
;
1627 *aReturnOffset
= aOffset
;
1631 if (aNode
->IsElement()) {
1632 int32_t childIndex
= 0;
1633 int32_t numChildren
= theNode
->GetChildCount();
1635 if (aHint
== CARET_ASSOCIATE_BEFORE
) {
1637 childIndex
= aOffset
- 1;
1639 childIndex
= aOffset
;
1642 NS_ASSERTION(aHint
== CARET_ASSOCIATE_AFTER
, "unknown direction");
1643 if (aOffset
>= numChildren
) {
1644 if (numChildren
> 0) {
1645 childIndex
= numChildren
- 1;
1650 childIndex
= aOffset
;
1654 if (childIndex
> 0 || numChildren
> 0) {
1655 nsCOMPtr
<nsIContent
> childNode
=
1656 theNode
->GetChildAt_Deprecated(childIndex
);
1662 theNode
= childNode
;
1665 // Now that we have the child node, check if it too
1666 // can contain children. If so, descend into child.
1667 if (theNode
->IsElement() && theNode
->GetChildCount() &&
1668 !theNode
->HasIndependentSelection()) {
1670 aOffset
= aOffset
> childIndex
? theNode
->GetChildCount() : 0;
1673 // Check to see if theNode is a text node. If it is, translate
1674 // aOffset into an offset into the text node.
1676 RefPtr
<Text
> textNode
= theNode
->GetAsText();
1678 if (theNode
->GetPrimaryFrame()) {
1679 if (aOffset
> childIndex
) {
1680 uint32_t textLength
= textNode
->Length();
1682 *aReturnOffset
= (int32_t)textLength
;
1687 int32_t numChildren
= aNode
->GetChildCount();
1688 int32_t newChildIndex
= aHint
== CARET_ASSOCIATE_BEFORE
1692 if (newChildIndex
>= 0 && newChildIndex
< numChildren
) {
1693 nsCOMPtr
<nsIContent
> newChildNode
=
1694 aNode
->GetChildAt_Deprecated(newChildIndex
);
1695 if (!newChildNode
) {
1699 aNode
= newChildNode
;
1701 aHint
== CARET_ASSOCIATE_BEFORE
? aNode
->GetChildCount() : 0;
1704 // newChildIndex is illegal which means we're at first or last
1705 // child. Just use original node to get the frame.
1713 // If the node is a ShadowRoot, the frame needs to be adjusted,
1714 // because a ShadowRoot does not get a frame. Its children are rendered
1715 // as children of the host.
1716 if (ShadowRoot
* shadow
= ShadowRoot::FromNode(theNode
)) {
1717 theNode
= shadow
->GetHost();
1720 returnFrame
= theNode
->GetPrimaryFrame();
1722 if (aHint
== CARET_ASSOCIATE_BEFORE
) {
1730 int32_t end
= theNode
->GetChildCount();
1731 if (aOffset
< end
) {
1743 if (!returnFrame
) return nullptr;
1745 // If we ended up here and were asked to position the caret after a visible
1746 // break, let's return the frame on the next line instead if it exists.
1747 if (aOffset
> 0 && (uint32_t)aOffset
>= aNode
->Length() &&
1748 theNode
== aNode
->GetLastChild()) {
1750 nsLayoutUtils::IsInvisibleBreak(theNode
, &newFrame
);
1752 returnFrame
= newFrame
;
1757 // find the child frame containing the offset we want
1758 returnFrame
->GetChildFrameContainingOffset(
1759 *aReturnOffset
, aHint
== CARET_ASSOCIATE_AFTER
, &aOffset
, &returnFrame
);
1763 nsIFrame
* nsFrameSelection::GetFrameToPageSelect() const {
1764 if (NS_WARN_IF(!mPresShell
)) {
1768 nsIFrame
* rootFrameToSelect
;
1769 if (mLimiters
.mLimiter
) {
1770 rootFrameToSelect
= mLimiters
.mLimiter
->GetPrimaryFrame();
1771 if (NS_WARN_IF(!rootFrameToSelect
)) {
1774 } else if (mLimiters
.mAncestorLimiter
) {
1775 rootFrameToSelect
= mLimiters
.mAncestorLimiter
->GetPrimaryFrame();
1776 if (NS_WARN_IF(!rootFrameToSelect
)) {
1780 rootFrameToSelect
= mPresShell
->GetRootScrollFrame();
1781 if (NS_WARN_IF(!rootFrameToSelect
)) {
1786 nsCOMPtr
<nsIContent
> contentToSelect
= mPresShell
->GetContentForScrolling();
1787 if (contentToSelect
) {
1788 // If there is selected content, look for nearest and vertical scrollable
1789 // parent under the root frame.
1790 for (nsIFrame
* frame
= contentToSelect
->GetPrimaryFrame();
1791 frame
&& frame
!= rootFrameToSelect
; frame
= frame
->GetParent()) {
1792 nsIScrollableFrame
* scrollableFrame
= do_QueryFrame(frame
);
1793 if (!scrollableFrame
) {
1796 ScrollStyles scrollStyles
= scrollableFrame
->GetScrollStyles();
1797 if (scrollStyles
.mVertical
== StyleOverflow::Hidden
) {
1800 layers::ScrollDirections directions
=
1801 scrollableFrame
->GetAvailableScrollingDirections();
1802 if (directions
.contains(layers::ScrollDirection::eVertical
)) {
1803 // If there is sub scrollable frame, let's use its page size to select.
1808 // Otherwise, i.e., there is no scrollable frame or only the root frame is
1809 // scrollable, let's return the root frame because Shift + PageUp/PageDown
1810 // should expand the selection in the root content even if it's not
1812 return rootFrameToSelect
;
1815 nsresult
nsFrameSelection::PageMove(bool aForward
, bool aExtend
,
1817 SelectionIntoView aSelectionIntoView
) {
1820 // expected behavior for PageMove is to scroll AND move the caret
1821 // and remain relative position of the caret in view. see Bug 4302.
1823 // Get the scrollable frame. If aFrame is not scrollable, this is nullptr.
1824 nsIScrollableFrame
* scrollableFrame
= aFrame
->GetScrollTargetFrame();
1825 // Get the scrolled frame. If aFrame is not scrollable, this is aFrame
1827 nsIFrame
* scrolledFrame
=
1828 scrollableFrame
? scrollableFrame
->GetScrolledFrame() : aFrame
;
1829 if (!scrolledFrame
) {
1833 // find out where the caret is.
1834 // we should know mDesiredCaretPos.mValue value of nsFrameSelection, but I
1835 // havent seen that behavior in other windows applications yet.
1836 RefPtr
<Selection
> selection
= GetSelection(SelectionType::eNormal
);
1842 nsIFrame
* caretFrame
= nsCaret::GetGeometry(selection
, &caretPos
);
1847 // If the scrolled frame is outside of current selection limiter,
1848 // we need to scroll the frame but keep moving selection in the limiter.
1849 nsIFrame
* frameToClick
= scrolledFrame
;
1850 if (!IsValidSelectionPoint(scrolledFrame
->GetContent())) {
1851 frameToClick
= GetFrameToPageSelect();
1852 if (NS_WARN_IF(!frameToClick
)) {
1857 if (scrollableFrame
) {
1858 // If there is a scrollable frame, adjust pseudo-click position with page
1860 // XXX This may scroll more than one page if ScrollSelectionIntoView is
1861 // called later because caret may not fully visible. E.g., if
1862 // clicking line will be visible only half height with scrolling
1863 // the frame, ScrollSelectionIntoView additionally scrolls to show
1864 // the caret entirely.
1866 caretPos
.y
+= scrollableFrame
->GetPageScrollAmount().height
;
1868 caretPos
.y
-= scrollableFrame
->GetPageScrollAmount().height
;
1871 // Otherwise, adjust pseudo-click position with the frame size.
1873 caretPos
.y
+= frameToClick
->GetSize().height
;
1875 caretPos
.y
-= frameToClick
->GetSize().height
;
1879 caretPos
+= caretFrame
->GetOffsetTo(frameToClick
);
1881 // get a content at desired location
1882 nsPoint desiredPoint
;
1883 desiredPoint
.x
= caretPos
.x
;
1884 desiredPoint
.y
= caretPos
.y
+ caretPos
.height
/ 2;
1885 nsIFrame::ContentOffsets offsets
=
1886 frameToClick
->GetContentOffsetsFromPoint(desiredPoint
);
1888 if (!offsets
.content
) {
1889 // XXX Do we need to handle ScrollSelectionIntoView in this case?
1893 // First, place the caret.
1894 bool selectionChanged
;
1896 // We don't want any script to run until we check whether selection is
1897 // modified by HandleClick.
1898 SelectionBatcher
ensureNoSelectionChangeNotifications(selection
);
1900 RangeBoundary oldAnchor
= selection
->AnchorRef();
1901 RangeBoundary oldFocus
= selection
->FocusRef();
1903 const FocusMode focusMode
=
1904 aExtend
? FocusMode::kExtendSelection
: FocusMode::kCollapseToNewPoint
;
1905 HandleClick(MOZ_KnownLive(offsets
.content
) /* bug 1636889 */,
1906 offsets
.offset
, offsets
.offset
, focusMode
,
1907 CARET_ASSOCIATE_AFTER
);
1909 selectionChanged
= selection
->AnchorRef() != oldAnchor
||
1910 selection
->FocusRef() != oldFocus
;
1913 bool doScrollSelectionIntoView
= !(
1914 aSelectionIntoView
== SelectionIntoView::IfChanged
&& !selectionChanged
);
1916 // Then, scroll the given frame one page.
1917 if (scrollableFrame
) {
1918 mozilla::Telemetry::Accumulate(
1919 mozilla::Telemetry::SCROLL_INPUT_METHODS
,
1920 (uint32_t)ScrollInputMethod::MainThreadScrollPage
);
1922 // If we'll call ScrollSelectionIntoView later and selection wasn't
1923 // changed and we scroll outside of selection limiter, we shouldn't use
1924 // smooth scroll here because nsIScrollableFrame uses normal runnable,
1925 // but ScrollSelectionIntoView uses early runner and it cancels the
1926 // pending smooth scroll. Therefore, if we used smooth scroll in such
1927 // case, ScrollSelectionIntoView would scroll to show caret instead of
1928 // page scroll of an element outside selection limiter.
1929 ScrollMode scrollMode
= doScrollSelectionIntoView
&& !selectionChanged
&&
1930 scrolledFrame
!= frameToClick
1931 ? ScrollMode::Instant
1932 : ScrollMode::Smooth
;
1933 scrollableFrame
->ScrollBy(nsIntPoint(0, aForward
? 1 : -1),
1934 ScrollUnit::PAGES
, scrollMode
);
1937 // Finally, scroll selection into view if requested.
1938 if (!doScrollSelectionIntoView
) {
1941 return ScrollSelectionIntoView(
1942 SelectionType::eNormal
, nsISelectionController::SELECTION_FOCUS_REGION
,
1943 nsISelectionController::SCROLL_SYNCHRONOUS
|
1944 nsISelectionController::SCROLL_FOR_CARET_MOVE
);
1947 nsresult
nsFrameSelection::PhysicalMove(int16_t aDirection
, int16_t aAmount
,
1949 NS_ENSURE_STATE(mPresShell
);
1950 // Flush out layout, since we need it to be up to date to do caret
1952 OwningNonNull
<PresShell
> presShell(*mPresShell
);
1953 presShell
->FlushPendingNotifications(FlushType::Layout
);
1959 // Check that parameters are safe
1960 if (aDirection
< 0 || aDirection
> 3 || aAmount
< 0 || aAmount
> 1) {
1961 return NS_ERROR_FAILURE
;
1964 nsPresContext
* context
= mPresShell
->GetPresContext();
1966 return NS_ERROR_FAILURE
;
1969 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1970 RefPtr
<Selection
> sel
= mDomSelections
[index
];
1972 return NS_ERROR_NULL_POINTER
;
1975 // Map the abstract movement amounts (0-1) to direction-specific
1977 static const nsSelectionAmount inlineAmount
[] = {eSelectCluster
, eSelectWord
};
1978 static const nsSelectionAmount blockPrevAmount
[] = {eSelectLine
,
1980 static const nsSelectionAmount blockNextAmount
[] = {eSelectLine
,
1983 struct PhysicalToLogicalMapping
{
1984 nsDirection direction
;
1985 const nsSelectionAmount
* amounts
;
1987 static const PhysicalToLogicalMapping verticalLR
[4] = {
1988 {eDirPrevious
, blockPrevAmount
}, // left
1989 {eDirNext
, blockNextAmount
}, // right
1990 {eDirPrevious
, inlineAmount
}, // up
1991 {eDirNext
, inlineAmount
} // down
1993 static const PhysicalToLogicalMapping verticalRL
[4] = {
1994 {eDirNext
, blockNextAmount
},
1995 {eDirPrevious
, blockPrevAmount
},
1996 {eDirPrevious
, inlineAmount
},
1997 {eDirNext
, inlineAmount
}};
1998 static const PhysicalToLogicalMapping horizontal
[4] = {
1999 {eDirPrevious
, inlineAmount
},
2000 {eDirNext
, inlineAmount
},
2001 {eDirPrevious
, blockPrevAmount
},
2002 {eDirNext
, blockNextAmount
}};
2005 nsIFrame
* frame
= sel
->GetPrimaryFrameForFocusNode(true);
2007 if (!frame
->Style()->IsTextCombined()) {
2008 wm
= frame
->GetWritingMode();
2010 // Using different direction for horizontal-in-vertical would
2011 // make it hard to navigate via keyboard. Inherit the moving
2012 // direction from its parent.
2013 MOZ_ASSERT(frame
->IsTextFrame());
2014 wm
= frame
->GetParent()->GetWritingMode();
2015 MOZ_ASSERT(wm
.IsVertical(),
2017 "can only appear in vertical text");
2021 const PhysicalToLogicalMapping
& mapping
=
2023 ? wm
.IsVerticalLR() ? verticalLR
[aDirection
] : verticalRL
[aDirection
]
2024 : horizontal
[aDirection
];
2027 MoveCaret(mapping
.direction
, aExtend
, mapping
.amounts
[aAmount
], eVisual
);
2028 if (NS_FAILED(rv
)) {
2029 // If we tried to do a line move, but couldn't move in the given direction,
2030 // then we'll "promote" this to a line-edge move instead.
2031 if (mapping
.amounts
[aAmount
] == eSelectLine
) {
2032 rv
= MoveCaret(mapping
.direction
, aExtend
, mapping
.amounts
[aAmount
+ 1],
2035 // And if it was a next-word move that failed (which can happen when
2036 // eat_space_to_next_word is true, see bug 1153237), then just move forward
2037 // to the line-edge.
2038 else if (mapping
.amounts
[aAmount
] == eSelectWord
&&
2039 mapping
.direction
== eDirNext
) {
2040 rv
= MoveCaret(eDirNext
, aExtend
, eSelectEndLine
, eVisual
);
2047 nsresult
nsFrameSelection::CharacterMove(bool aForward
, bool aExtend
) {
2048 return MoveCaret(aForward
? eDirNext
: eDirPrevious
, aExtend
, eSelectCluster
,
2052 nsresult
nsFrameSelection::WordMove(bool aForward
, bool aExtend
) {
2053 return MoveCaret(aForward
? eDirNext
: eDirPrevious
, aExtend
, eSelectWord
,
2057 nsresult
nsFrameSelection::LineMove(bool aForward
, bool aExtend
) {
2058 return MoveCaret(aForward
? eDirNext
: eDirPrevious
, aExtend
, eSelectLine
,
2062 nsresult
nsFrameSelection::IntraLineMove(bool aForward
, bool aExtend
) {
2064 return MoveCaret(eDirNext
, aExtend
, eSelectEndLine
, eLogical
);
2066 return MoveCaret(eDirPrevious
, aExtend
, eSelectBeginLine
, eLogical
);
2070 template <typename RangeType
>
2071 Result
<RefPtr
<RangeType
>, nsresult
>
2072 nsFrameSelection::CreateRangeExtendedToSomewhere(
2073 nsDirection aDirection
, const nsSelectionAmount aAmount
,
2074 CaretMovementStyle aMovementStyle
) {
2075 MOZ_ASSERT(aDirection
== eDirNext
|| aDirection
== eDirPrevious
);
2076 MOZ_ASSERT(aAmount
== eSelectCharacter
|| aAmount
== eSelectCluster
||
2077 aAmount
== eSelectWord
|| aAmount
== eSelectBeginLine
||
2078 aAmount
== eSelectEndLine
);
2079 MOZ_ASSERT(aMovementStyle
== eLogical
|| aMovementStyle
== eVisual
||
2080 aMovementStyle
== eUsePrefStyle
);
2083 return Err(NS_ERROR_UNEXPECTED
);
2085 OwningNonNull
<PresShell
> presShell(*mPresShell
);
2086 presShell
->FlushPendingNotifications(FlushType::Layout
);
2088 return Err(NS_ERROR_FAILURE
);
2090 Selection
* selection
=
2091 mDomSelections
[GetIndexFromSelectionType(SelectionType::eNormal
)];
2092 if (!selection
|| selection
->RangeCount() != 1) {
2093 return Err(NS_ERROR_FAILURE
);
2095 RefPtr
<const nsRange
> firstRange
= selection
->GetRangeAt(0);
2096 if (!firstRange
|| !firstRange
->IsPositioned()) {
2097 return Err(NS_ERROR_FAILURE
);
2099 Result
<nsPeekOffsetStruct
, nsresult
> result
= PeekOffsetForCaretMove(
2100 aDirection
, true, aAmount
, aMovementStyle
, nsPoint(0, 0));
2101 if (result
.isErr()) {
2102 return Err(NS_ERROR_FAILURE
);
2104 const nsPeekOffsetStruct
& pos
= result
.inspect();
2105 RefPtr
<RangeType
> range
;
2106 if (NS_WARN_IF(!pos
.mResultContent
)) {
2109 if (aDirection
== eDirPrevious
) {
2110 range
= RangeType::Create(
2111 RawRangeBoundary(pos
.mResultContent
, pos
.mContentOffset
),
2112 firstRange
->EndRef(), IgnoreErrors());
2114 range
= RangeType::Create(
2115 firstRange
->StartRef(),
2116 RawRangeBoundary(pos
.mResultContent
, pos
.mContentOffset
),
2122 //////////END FRAMESELECTION
2124 void nsFrameSelection::StartBatchChanges() { mBatching
.mCounter
++; }
2126 void nsFrameSelection::EndBatchChanges(int16_t aReasons
) {
2127 MOZ_ASSERT(mBatching
.mCounter
> 0, "Bad mBatching.mCounter");
2128 mBatching
.mCounter
--;
2130 if (mBatching
.mCounter
== 0 && mBatching
.mChangesDuringBatching
) {
2131 AddChangeReasons(aReasons
);
2132 mBatching
.mChangesDuringBatching
= false;
2133 // Be aware, the Selection instance may be destroyed after this call.
2134 NotifySelectionListeners(SelectionType::eNormal
);
2138 nsresult
nsFrameSelection::NotifySelectionListeners(
2139 SelectionType aSelectionType
) {
2140 int8_t index
= GetIndexFromSelectionType(aSelectionType
);
2141 if (index
>= 0 && mDomSelections
[index
]) {
2142 RefPtr
<Selection
> selection
= mDomSelections
[index
];
2143 return selection
->NotifySelectionListeners();
2145 return NS_ERROR_FAILURE
;
2148 // Start of Table Selection methods
2150 static bool IsCell(nsIContent
* aContent
) {
2151 return aContent
->IsAnyOfHTMLElements(nsGkAtoms::td
, nsGkAtoms::th
);
2155 nsITableCellLayout
* nsFrameSelection::GetCellLayout(
2156 const nsIContent
* aCellContent
) {
2157 nsITableCellLayout
* cellLayoutObject
=
2158 do_QueryFrame(aCellContent
->GetPrimaryFrame());
2159 return cellLayoutObject
;
2162 nsresult
nsFrameSelection::ClearNormalSelection() {
2163 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
2164 if (!mDomSelections
[index
]) return NS_ERROR_NULL_POINTER
;
2167 mDomSelections
[index
]->RemoveAllRanges(err
);
2168 return err
.StealNSResult();
2171 static nsIContent
* GetFirstSelectedContent(const nsRange
* aRange
) {
2176 MOZ_ASSERT(aRange
->GetStartContainer(), "Must have start parent!");
2177 MOZ_ASSERT(aRange
->GetStartContainer()->IsElement(), "Unexpected parent");
2179 return aRange
->GetChildAtStartOffset();
2182 // Table selection support.
2183 // TODO: Separate table methods into a separate nsITableSelection interface
2184 nsresult
nsFrameSelection::HandleTableSelection(nsINode
* aParentContent
,
2185 int32_t aContentOffset
,
2186 TableSelectionMode aTarget
,
2187 WidgetMouseEvent
* aMouseEvent
) {
2188 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
2189 RefPtr
<Selection
> selection
= mDomSelections
[index
];
2191 return NS_ERROR_NULL_POINTER
;
2194 return mTableSelection
.HandleSelection(aParentContent
, aContentOffset
,
2195 aTarget
, aMouseEvent
, mDragState
,
2199 nsresult
nsFrameSelection::TableSelection::HandleSelection(
2200 nsINode
* aParentContent
, int32_t aContentOffset
, TableSelectionMode aTarget
,
2201 WidgetMouseEvent
* aMouseEvent
, bool aDragState
,
2202 Selection
& aNormalSelection
) {
2203 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2205 NS_ENSURE_TRUE(aParentContent
, NS_ERROR_NULL_POINTER
);
2206 NS_ENSURE_TRUE(aMouseEvent
, NS_ERROR_NULL_POINTER
);
2208 if (aDragState
&& mDragSelectingCells
&&
2209 aTarget
== TableSelectionMode::Table
) {
2210 // We were selecting cells and user drags mouse in table border or inbetween
2216 RefPtr
<nsIContent
> childContent
=
2217 aParentContent
->GetChildAt_Deprecated(aContentOffset
);
2219 // When doing table selection, always set the direction to next so
2220 // we can be sure that anchorNode's offset always points to the
2222 aNormalSelection
.SetDirection(eDirNext
);
2224 // Stack-class to wrap all table selection changes in
2225 // BeginBatchChanges() / EndBatchChanges()
2226 SelectionBatcher
selectionBatcher(&aNormalSelection
);
2228 if (aDragState
&& mDragSelectingCells
) {
2229 return HandleDragSelecting(aTarget
, childContent
, aMouseEvent
,
2233 return HandleMouseUpOrDown(aTarget
, aDragState
, childContent
, aParentContent
,
2234 aContentOffset
, aMouseEvent
, aNormalSelection
);
2237 class nsFrameSelection::TableSelection::RowAndColumnRelation
{
2239 static Result
<RowAndColumnRelation
, nsresult
> Create(
2240 const nsIContent
* aFirst
, const nsIContent
* aSecond
) {
2241 RowAndColumnRelation result
;
2243 nsresult errorResult
=
2244 GetCellIndexes(aFirst
, result
.mFirst
.mRow
, result
.mFirst
.mColumn
);
2245 if (NS_FAILED(errorResult
)) {
2246 return Err(errorResult
);
2250 GetCellIndexes(aSecond
, result
.mSecond
.mRow
, result
.mSecond
.mColumn
);
2251 if (NS_FAILED(errorResult
)) {
2252 return Err(errorResult
);
2258 bool IsSameColumn() const { return mFirst
.mColumn
== mSecond
.mColumn
; }
2260 bool IsSameRow() const { return mFirst
.mRow
== mSecond
.mRow
; }
2263 RowAndColumnRelation() = default;
2265 struct RowAndColumn
{
2267 int32_t mColumn
= 0;
2270 RowAndColumn mFirst
;
2271 RowAndColumn mSecond
;
2274 nsresult
nsFrameSelection::TableSelection::HandleDragSelecting(
2275 TableSelectionMode aTarget
, nsIContent
* aChildContent
,
2276 const WidgetMouseEvent
* aMouseEvent
, Selection
& aNormalSelection
) {
2277 // We are drag-selecting
2278 if (aTarget
!= TableSelectionMode::Table
) {
2279 // If dragging in the same cell as last event, do nothing
2280 if (mEndSelectedCell
== aChildContent
) {
2284 #ifdef DEBUG_TABLE_SELECTION
2286 " mStartSelectedCell = %p, "
2287 "mEndSelectedCell = %p, aChildContent = %p "
2289 mStartSelectedCell
.get(), mEndSelectedCell
.get(), aChildContent
);
2291 // aTarget can be any "cell mode",
2292 // so we can easily drag-select rows and columns
2293 // Once we are in row or column mode,
2294 // we can drift into any cell to stay in that mode
2295 // even if aTarget = TableSelectionMode::Cell
2297 if (mMode
== TableSelectionMode::Row
||
2298 mMode
== TableSelectionMode::Column
) {
2299 if (mEndSelectedCell
) {
2300 Result
<RowAndColumnRelation
, nsresult
> rowAndColumnRelation
=
2301 RowAndColumnRelation::Create(mEndSelectedCell
, aChildContent
);
2303 if (rowAndColumnRelation
.isErr()) {
2304 return rowAndColumnRelation
.unwrapErr();
2307 if ((mMode
== TableSelectionMode::Row
&&
2308 rowAndColumnRelation
.inspect().IsSameRow()) ||
2309 (mMode
== TableSelectionMode::Column
&&
2310 rowAndColumnRelation
.inspect().IsSameColumn())) {
2314 #ifdef DEBUG_TABLE_SELECTION
2315 printf(" Dragged into a new column or row\n");
2317 // Continue dragging row or column selection
2319 return SelectRowOrColumn(aChildContent
, aNormalSelection
);
2321 if (mMode
== TableSelectionMode::Cell
) {
2322 #ifdef DEBUG_TABLE_SELECTION
2323 printf("HandleTableSelection: Dragged into a new cell\n");
2325 // Trick for quick selection of rows and columns
2326 // Hold down shift, then start selecting in one direction
2327 // If next cell dragged into is in same row, select entire row,
2328 // if next cell is in same column, select entire column
2329 if (mStartSelectedCell
&& aMouseEvent
->IsShift()) {
2330 Result
<RowAndColumnRelation
, nsresult
> rowAndColumnRelation
=
2331 RowAndColumnRelation::Create(mStartSelectedCell
, aChildContent
);
2332 if (rowAndColumnRelation
.isErr()) {
2333 return rowAndColumnRelation
.unwrapErr();
2336 if (rowAndColumnRelation
.inspect().IsSameRow() ||
2337 rowAndColumnRelation
.inspect().IsSameColumn()) {
2338 // Force new selection block
2339 mStartSelectedCell
= nullptr;
2340 aNormalSelection
.RemoveAllRanges(IgnoreErrors());
2342 if (rowAndColumnRelation
.inspect().IsSameRow()) {
2343 mMode
= TableSelectionMode::Row
;
2345 mMode
= TableSelectionMode::Column
;
2348 return SelectRowOrColumn(aChildContent
, aNormalSelection
);
2352 // Reselect block of cells to new end location
2353 return SelectBlockOfCells(mStartSelectedCell
, aChildContent
,
2357 // Do nothing if dragging in table, but outside a cell
2361 nsresult
nsFrameSelection::TableSelection::HandleMouseUpOrDown(
2362 TableSelectionMode aTarget
, bool aDragState
, nsIContent
* aChildContent
,
2363 nsINode
* aParentContent
, int32_t aContentOffset
,
2364 const WidgetMouseEvent
* aMouseEvent
, Selection
& aNormalSelection
) {
2365 nsresult result
= NS_OK
;
2366 // Not dragging -- mouse event is down or up
2368 #ifdef DEBUG_TABLE_SELECTION
2369 printf("HandleTableSelection: Mouse down event\n");
2371 // Clear cell we stored in mouse-down
2372 mUnselectCellOnMouseUp
= nullptr;
2374 if (aTarget
== TableSelectionMode::Cell
) {
2375 bool isSelected
= false;
2377 // Check if we have other selected cells
2378 nsIContent
* previousCellNode
=
2379 GetFirstSelectedContent(GetFirstCellRange(aNormalSelection
));
2380 if (previousCellNode
) {
2381 // We have at least 1 other selected cell
2383 // Check if new cell is already selected
2384 nsIFrame
* cellFrame
= aChildContent
->GetPrimaryFrame();
2386 return NS_ERROR_NULL_POINTER
;
2388 isSelected
= cellFrame
->IsSelected();
2390 // No cells selected -- remove non-cell selection
2391 aNormalSelection
.RemoveAllRanges(IgnoreErrors());
2393 mDragSelectingCells
= true; // Signal to start drag-cell-selection
2395 // Set start for new drag-selection block (not appended)
2396 mStartSelectedCell
= aChildContent
;
2397 // The initial block end is same as the start
2398 mEndSelectedCell
= aChildContent
;
2401 // Remember this cell to (possibly) unselect it on mouseup
2402 mUnselectCellOnMouseUp
= aChildContent
;
2403 #ifdef DEBUG_TABLE_SELECTION
2405 "HandleTableSelection: Saving "
2406 "mUnselectCellOnMouseUp\n");
2409 // Select an unselected cell
2410 // but first remove existing selection if not in same table
2411 if (previousCellNode
&&
2412 !IsInSameTable(previousCellNode
, aChildContent
)) {
2413 aNormalSelection
.RemoveAllRanges(IgnoreErrors());
2414 // Reset selection mode that is cleared in RemoveAllRanges
2418 return ::SelectCellElement(aChildContent
, aNormalSelection
);
2423 if (aTarget
== TableSelectionMode::Table
) {
2424 // TODO: We currently select entire table when clicked between cells,
2425 // should we restrict to only around border?
2426 // *** How do we get location data for cell and click?
2427 mDragSelectingCells
= false;
2428 mStartSelectedCell
= nullptr;
2429 mEndSelectedCell
= nullptr;
2431 // Remove existing selection and select the table
2432 aNormalSelection
.RemoveAllRanges(IgnoreErrors());
2433 return CreateAndAddRange(aParentContent
, aContentOffset
,
2436 if (aTarget
== TableSelectionMode::Row
||
2437 aTarget
== TableSelectionMode::Column
) {
2438 #ifdef DEBUG_TABLE_SELECTION
2439 printf("aTarget == %d\n", aTarget
);
2442 // Start drag-selecting mode so multiple rows/cols can be selected
2443 // Note: Currently, nsIFrame::GetDataForTableSelection
2444 // will never call us for row or column selection on mouse down
2445 mDragSelectingCells
= true;
2447 // Force new selection block
2448 mStartSelectedCell
= nullptr;
2449 aNormalSelection
.RemoveAllRanges(IgnoreErrors());
2450 // Always do this AFTER RemoveAllRanges
2453 return SelectRowOrColumn(aChildContent
, aNormalSelection
);
2456 #ifdef DEBUG_TABLE_SELECTION
2458 "HandleTableSelection: Mouse UP event. "
2459 "mDragSelectingCells=%d, "
2460 "mStartSelectedCell=%p\n",
2461 mDragSelectingCells
, mStartSelectedCell
.get());
2463 // First check if we are extending a block selection
2464 uint32_t rangeCount
= aNormalSelection
.RangeCount();
2466 if (rangeCount
> 0 && aMouseEvent
->IsShift() && mAppendStartSelectedCell
&&
2467 mAppendStartSelectedCell
!= aChildContent
) {
2468 // Shift key is down: append a block selection
2469 mDragSelectingCells
= false;
2471 return SelectBlockOfCells(mAppendStartSelectedCell
, aChildContent
,
2475 if (mDragSelectingCells
) {
2476 mAppendStartSelectedCell
= mStartSelectedCell
;
2479 mDragSelectingCells
= false;
2480 mStartSelectedCell
= nullptr;
2481 mEndSelectedCell
= nullptr;
2483 // Any other mouseup actions require that Ctrl or Cmd key is pressed
2484 // else stop table selection mode
2485 bool doMouseUpAction
= false;
2487 doMouseUpAction
= aMouseEvent
->IsMeta();
2489 doMouseUpAction
= aMouseEvent
->IsControl();
2491 if (!doMouseUpAction
) {
2492 #ifdef DEBUG_TABLE_SELECTION
2494 "HandleTableSelection: Ending cell selection on mouseup: "
2495 "mAppendStartSelectedCell=%p\n",
2496 mAppendStartSelectedCell
.get());
2500 // Unselect a cell only if it wasn't
2501 // just selected on mousedown
2502 if (aChildContent
== mUnselectCellOnMouseUp
) {
2503 // Scan ranges to find the cell to unselect (the selection range to
2505 // XXXbz it's really weird that this lives outside the loop, so once we
2506 // find one we keep looking at it even if we find no more cells...
2507 nsINode
* previousCellParent
= nullptr;
2508 #ifdef DEBUG_TABLE_SELECTION
2510 "HandleTableSelection: Unselecting "
2511 "mUnselectCellOnMouseUp; "
2515 for (uint32_t i
= 0; i
< rangeCount
; i
++) {
2516 // Strong reference, because sometimes we want to remove
2517 // this range, and then we might be the only owner.
2518 RefPtr
<nsRange
> range
= aNormalSelection
.GetRangeAt(i
);
2520 return NS_ERROR_NULL_POINTER
;
2523 nsINode
* container
= range
->GetStartContainer();
2525 return NS_ERROR_NULL_POINTER
;
2528 int32_t offset
= range
->StartOffset();
2529 // Be sure previous selection is a table cell
2530 nsIContent
* child
= range
->GetChildAtStartOffset();
2531 if (child
&& IsCell(child
)) {
2532 previousCellParent
= container
;
2535 // We're done if we didn't find parent of a previously-selected cell
2536 if (!previousCellParent
) {
2540 if (previousCellParent
== aParentContent
&& offset
== aContentOffset
) {
2541 // Cell is already selected
2542 if (rangeCount
== 1) {
2543 #ifdef DEBUG_TABLE_SELECTION
2544 printf("HandleTableSelection: Unselecting single selected cell\n");
2546 // This was the only cell selected.
2547 // Collapse to "normal" selection inside the cell
2548 mStartSelectedCell
= nullptr;
2549 mEndSelectedCell
= nullptr;
2550 mAppendStartSelectedCell
= nullptr;
2551 // TODO: We need a "Collapse to just before deepest child" routine
2552 // Even better, should we collapse to just after the LAST deepest
2554 // (i.e., at the end of the cell's contents)?
2555 return aNormalSelection
.CollapseInLimiter(aChildContent
, 0);
2557 #ifdef DEBUG_TABLE_SELECTION
2559 "HandleTableSelection: Removing cell from multi-cell "
2562 // Unselecting the start of previous block
2563 // XXX What do we use now!
2564 if (aChildContent
== mAppendStartSelectedCell
) {
2565 mAppendStartSelectedCell
= nullptr;
2568 // Deselect cell by removing its range from selection
2570 aNormalSelection
.RemoveRangeAndUnselectFramesAndNotifyListeners(
2572 return err
.StealNSResult();
2575 mUnselectCellOnMouseUp
= nullptr;
2581 nsresult
nsFrameSelection::TableSelection::SelectBlockOfCells(
2582 nsIContent
* aStartCell
, nsIContent
* aEndCell
, Selection
& aNormalSelection
) {
2583 NS_ENSURE_TRUE(aStartCell
, NS_ERROR_NULL_POINTER
);
2584 NS_ENSURE_TRUE(aEndCell
, NS_ERROR_NULL_POINTER
);
2585 mEndSelectedCell
= aEndCell
;
2587 nsresult result
= NS_OK
;
2589 // If new end cell is in a different table, do nothing
2590 const RefPtr
<const nsIContent
> table
= IsInSameTable(aStartCell
, aEndCell
);
2595 // Get starting and ending cells' location in the cellmap
2596 int32_t startRowIndex
, startColIndex
, endRowIndex
, endColIndex
;
2597 result
= GetCellIndexes(aStartCell
, startRowIndex
, startColIndex
);
2598 if (NS_FAILED(result
)) return result
;
2599 result
= GetCellIndexes(aEndCell
, endRowIndex
, endColIndex
);
2600 if (NS_FAILED(result
)) return result
;
2602 if (mDragSelectingCells
) {
2603 // Drag selecting: remove selected cells outside of new block limits
2604 // TODO: `UnselectCells`'s return value shouldn't be ignored.
2605 UnselectCells(table
, startRowIndex
, startColIndex
, endRowIndex
, endColIndex
,
2606 true, aNormalSelection
);
2609 // Note that we select block in the direction of user's mouse dragging,
2610 // which means start cell may be after the end cell in either row or column
2611 return AddCellsToSelection(table
, startRowIndex
, startColIndex
, endRowIndex
,
2612 endColIndex
, aNormalSelection
);
2615 nsresult
nsFrameSelection::TableSelection::UnselectCells(
2616 const nsIContent
* aTableContent
, int32_t aStartRowIndex
,
2617 int32_t aStartColumnIndex
, int32_t aEndRowIndex
, int32_t aEndColumnIndex
,
2618 bool aRemoveOutsideOfCellRange
, mozilla::dom::Selection
& aNormalSelection
) {
2619 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2621 nsTableWrapperFrame
* tableFrame
=
2622 do_QueryFrame(aTableContent
->GetPrimaryFrame());
2623 if (!tableFrame
) return NS_ERROR_FAILURE
;
2625 int32_t minRowIndex
= std::min(aStartRowIndex
, aEndRowIndex
);
2626 int32_t maxRowIndex
= std::max(aStartRowIndex
, aEndRowIndex
);
2627 int32_t minColIndex
= std::min(aStartColumnIndex
, aEndColumnIndex
);
2628 int32_t maxColIndex
= std::max(aStartColumnIndex
, aEndColumnIndex
);
2630 // Strong reference because we sometimes remove the range
2631 RefPtr
<nsRange
> range
= GetFirstCellRange(aNormalSelection
);
2632 nsIContent
* cellNode
= GetFirstSelectedContent(range
);
2633 MOZ_ASSERT(!range
|| cellNode
, "Must have cellNode if had a range");
2635 int32_t curRowIndex
, curColIndex
;
2637 nsresult result
= GetCellIndexes(cellNode
, curRowIndex
, curColIndex
);
2638 if (NS_FAILED(result
)) return result
;
2640 #ifdef DEBUG_TABLE_SELECTION
2641 if (!range
) printf("RemoveCellsToSelection -- range is null\n");
2645 if (aRemoveOutsideOfCellRange
) {
2646 if (curRowIndex
< minRowIndex
|| curRowIndex
> maxRowIndex
||
2647 curColIndex
< minColIndex
|| curColIndex
> maxColIndex
) {
2648 aNormalSelection
.RemoveRangeAndUnselectFramesAndNotifyListeners(
2649 *range
, IgnoreErrors());
2650 // Since we've removed the range, decrement pointer to next range
2651 mSelectedCellIndex
--;
2655 // Remove cell from selection if it belongs to the given cells range or
2656 // it is spanned onto the cells range.
2657 nsTableCellFrame
* cellFrame
=
2658 tableFrame
->GetCellFrameAt(curRowIndex
, curColIndex
);
2660 uint32_t origRowIndex
= cellFrame
->RowIndex();
2661 uint32_t origColIndex
= cellFrame
->ColIndex();
2662 uint32_t actualRowSpan
=
2663 tableFrame
->GetEffectiveRowSpanAt(origRowIndex
, origColIndex
);
2664 uint32_t actualColSpan
=
2665 tableFrame
->GetEffectiveColSpanAt(curRowIndex
, curColIndex
);
2666 if (origRowIndex
<= static_cast<uint32_t>(maxRowIndex
) &&
2668 origRowIndex
+ actualRowSpan
- 1 >=
2669 static_cast<uint32_t>(minRowIndex
) &&
2670 origColIndex
<= static_cast<uint32_t>(maxColIndex
) &&
2672 origColIndex
+ actualColSpan
- 1 >=
2673 static_cast<uint32_t>(minColIndex
)) {
2674 aNormalSelection
.RemoveRangeAndUnselectFramesAndNotifyListeners(
2675 *range
, IgnoreErrors());
2676 // Since we've removed the range, decrement pointer to next range
2677 mSelectedCellIndex
--;
2682 range
= GetNextCellRange(aNormalSelection
);
2683 cellNode
= GetFirstSelectedContent(range
);
2684 MOZ_ASSERT(!range
|| cellNode
, "Must have cellNode if had a range");
2690 nsresult
SelectCellElement(nsIContent
* aCellElement
,
2691 Selection
& aNormalSelection
) {
2692 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2694 nsIContent
* parent
= aCellElement
->GetParent();
2697 int32_t offset
= parent
->ComputeIndexOf(aCellElement
);
2699 return CreateAndAddRange(parent
, offset
, aNormalSelection
);
2702 static nsresult
AddCellsToSelection(const nsIContent
* aTableContent
,
2703 int32_t aStartRowIndex
,
2704 int32_t aStartColumnIndex
,
2705 int32_t aEndRowIndex
,
2706 int32_t aEndColumnIndex
,
2707 Selection
& aNormalSelection
) {
2708 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2710 nsTableWrapperFrame
* tableFrame
=
2711 do_QueryFrame(aTableContent
->GetPrimaryFrame());
2712 if (!tableFrame
) { // Check that |table| is a table.
2713 return NS_ERROR_FAILURE
;
2716 nsresult result
= NS_OK
;
2717 uint32_t row
= aStartRowIndex
;
2719 uint32_t col
= aStartColumnIndex
;
2721 nsTableCellFrame
* cellFrame
= tableFrame
->GetCellFrameAt(row
, col
);
2723 // Skip cells that are spanned from previous locations or are already
2726 uint32_t origRow
= cellFrame
->RowIndex();
2727 uint32_t origCol
= cellFrame
->ColIndex();
2728 if (origRow
== row
&& origCol
== col
&& !cellFrame
->IsSelected()) {
2729 result
= SelectCellElement(cellFrame
->GetContent(), aNormalSelection
);
2730 if (NS_FAILED(result
)) {
2735 // Done when we reach end column
2736 if (col
== static_cast<uint32_t>(aEndColumnIndex
)) {
2740 if (aStartColumnIndex
< aEndColumnIndex
) {
2746 if (row
== static_cast<uint32_t>(aEndRowIndex
)) {
2750 if (aStartRowIndex
< aEndRowIndex
) {
2759 nsresult
nsFrameSelection::RemoveCellsFromSelection(nsIContent
* aTable
,
2760 int32_t aStartRowIndex
,
2761 int32_t aStartColumnIndex
,
2762 int32_t aEndRowIndex
,
2763 int32_t aEndColumnIndex
) {
2764 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
2765 const RefPtr
<mozilla::dom::Selection
> selection
= mDomSelections
[index
];
2767 return NS_ERROR_NULL_POINTER
;
2770 return mTableSelection
.UnselectCells(aTable
, aStartRowIndex
,
2771 aStartColumnIndex
, aEndRowIndex
,
2772 aEndColumnIndex
, false, *selection
);
2775 nsresult
nsFrameSelection::RestrictCellsToSelection(nsIContent
* aTable
,
2776 int32_t aStartRowIndex
,
2777 int32_t aStartColumnIndex
,
2778 int32_t aEndRowIndex
,
2779 int32_t aEndColumnIndex
) {
2780 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
2781 const RefPtr
<mozilla::dom::Selection
> selection
= mDomSelections
[index
];
2783 return NS_ERROR_NULL_POINTER
;
2786 return mTableSelection
.UnselectCells(aTable
, aStartRowIndex
,
2787 aStartColumnIndex
, aEndRowIndex
,
2788 aEndColumnIndex
, true, *selection
);
2791 Result
<nsFrameSelection::TableSelection::FirstAndLastCell
, nsresult
>
2792 nsFrameSelection::TableSelection::FindFirstAndLastCellOfRowOrColumn(
2793 const nsIContent
& aCellContent
) const {
2794 const nsIContent
* table
= GetParentTable(&aCellContent
);
2796 return Err(NS_ERROR_NULL_POINTER
);
2799 // Get table and cell layout interfaces to access
2800 // cell data based on cellmap location
2801 // Frames are not ref counted, so don't use an nsCOMPtr
2802 nsTableWrapperFrame
* tableFrame
= do_QueryFrame(table
->GetPrimaryFrame());
2804 return Err(NS_ERROR_FAILURE
);
2806 nsITableCellLayout
* cellLayout
= GetCellLayout(&aCellContent
);
2808 return Err(NS_ERROR_FAILURE
);
2811 // Get location of target cell:
2812 int32_t rowIndex
, colIndex
;
2813 nsresult result
= cellLayout
->GetCellIndexes(rowIndex
, colIndex
);
2814 if (NS_FAILED(result
)) {
2818 // Be sure we start at proper beginning
2819 // (This allows us to select row or col given ANY cell!)
2820 if (mMode
== TableSelectionMode::Row
) {
2823 if (mMode
== TableSelectionMode::Column
) {
2827 FirstAndLastCell firstAndLastCell
;
2829 // Loop through all cells in column or row to find first and last
2830 nsCOMPtr
<nsIContent
> curCellContent
=
2831 tableFrame
->GetCellAt(rowIndex
, colIndex
);
2832 if (!curCellContent
) {
2836 if (!firstAndLastCell
.mFirst
) {
2837 firstAndLastCell
.mFirst
= curCellContent
;
2840 firstAndLastCell
.mLast
= std::move(curCellContent
);
2842 // Move to next cell in cellmap, skipping spanned locations
2843 if (mMode
== TableSelectionMode::Row
) {
2844 colIndex
+= tableFrame
->GetEffectiveRowSpanAt(rowIndex
, colIndex
);
2846 rowIndex
+= tableFrame
->GetEffectiveRowSpanAt(rowIndex
, colIndex
);
2849 return firstAndLastCell
;
2852 nsresult
nsFrameSelection::TableSelection::SelectRowOrColumn(
2853 nsIContent
* aCellContent
, Selection
& aNormalSelection
) {
2854 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2856 if (!aCellContent
) {
2857 return NS_ERROR_NULL_POINTER
;
2860 Result
<FirstAndLastCell
, nsresult
> firstAndLastCell
=
2861 FindFirstAndLastCellOfRowOrColumn(*aCellContent
);
2862 if (firstAndLastCell
.isErr()) {
2863 return firstAndLastCell
.unwrapErr();
2866 // Use SelectBlockOfCells:
2867 // This will replace existing selection,
2868 // but allow unselecting by dragging out of selected region
2869 if (firstAndLastCell
.inspect().mFirst
&& firstAndLastCell
.inspect().mLast
) {
2872 if (!mStartSelectedCell
) {
2873 // We are starting a new block, so select the first cell
2874 rv
= ::SelectCellElement(firstAndLastCell
.inspect().mFirst
,
2876 if (NS_FAILED(rv
)) {
2879 mStartSelectedCell
= firstAndLastCell
.inspect().mFirst
;
2882 rv
= SelectBlockOfCells(mStartSelectedCell
,
2883 firstAndLastCell
.inspect().mLast
, aNormalSelection
);
2885 // This gets set to the cell at end of row/col,
2886 // but we need it to be the cell under cursor
2887 mEndSelectedCell
= aCellContent
;
2892 // This is a more efficient strategy that appends row to current selection,
2893 // but doesn't allow dragging OFF of an existing selection to unselect!
2895 // Loop through all cells in column or row
2896 result
= tableLayout
->GetCellDataAt(rowIndex
, colIndex
,
2897 getter_AddRefs(cellElement
),
2898 curRowIndex
, curColIndex
,
2900 actualRowSpan
, actualColSpan
,
2902 if (NS_FAILED(result
)) return result
;
2903 // We're done when cell is not found
2904 if (!cellElement
) break;
2907 // Check spans else we infinitely loop
2908 NS_ASSERTION(actualColSpan
, "actualColSpan is 0!");
2909 NS_ASSERTION(actualRowSpan
, "actualRowSpan is 0!");
2911 // Skip cells that are already selected or span from outside our region
2912 if (!isSelected
&& rowIndex
== curRowIndex
&& colIndex
== curColIndex
)
2914 result
= SelectCellElement(cellElement
);
2915 if (NS_FAILED(result
)) return result
;
2917 // Move to next row or column in cellmap, skipping spanned locations
2918 if (mMode
== TableSelectionMode::Row
)
2919 colIndex
+= actualColSpan
;
2921 rowIndex
+= actualRowSpan
;
2923 while (cellElement
);
2930 nsIContent
* nsFrameSelection::GetFirstCellNodeInRange(const nsRange
* aRange
) {
2931 if (!aRange
) return nullptr;
2933 nsIContent
* childContent
= aRange
->GetChildAtStartOffset();
2934 if (!childContent
) return nullptr;
2935 // Don't return node if not a cell
2936 if (!IsCell(childContent
)) return nullptr;
2938 return childContent
;
2941 nsRange
* nsFrameSelection::TableSelection::GetFirstCellRange(
2942 const mozilla::dom::Selection
& aNormalSelection
) {
2943 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2945 nsRange
* firstRange
= aNormalSelection
.GetRangeAt(0);
2946 if (!GetFirstCellNodeInRange(firstRange
)) {
2950 // Setup for next cell
2951 mSelectedCellIndex
= 1;
2956 nsRange
* nsFrameSelection::TableSelection::GetNextCellRange(
2957 const mozilla::dom::Selection
& aNormalSelection
) {
2958 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2960 nsRange
* range
= aNormalSelection
.GetRangeAt(mSelectedCellIndex
);
2962 // Get first node in next range of selection - test if it's a cell
2963 if (!GetFirstCellNodeInRange(range
)) {
2967 // Setup for next cell
2968 mSelectedCellIndex
++;
2974 nsresult
nsFrameSelection::GetCellIndexes(const nsIContent
* aCell
,
2976 int32_t& aColIndex
) {
2977 if (!aCell
) return NS_ERROR_NULL_POINTER
;
2979 aColIndex
= 0; // initialize out params
2982 nsITableCellLayout
* cellLayoutObject
= GetCellLayout(aCell
);
2983 if (!cellLayoutObject
) return NS_ERROR_FAILURE
;
2984 return cellLayoutObject
->GetCellIndexes(aRowIndex
, aColIndex
);
2988 nsIContent
* nsFrameSelection::IsInSameTable(const nsIContent
* aContent1
,
2989 const nsIContent
* aContent2
) {
2990 if (!aContent1
|| !aContent2
) return nullptr;
2992 nsIContent
* tableNode1
= GetParentTable(aContent1
);
2993 nsIContent
* tableNode2
= GetParentTable(aContent2
);
2995 // Must be in the same table. Note that we want to return false for
2996 // the test if both tables are null.
2997 return (tableNode1
== tableNode2
) ? tableNode1
: nullptr;
3001 nsIContent
* nsFrameSelection::GetParentTable(const nsIContent
* aCell
) {
3006 for (nsIContent
* parent
= aCell
->GetParent(); parent
;
3007 parent
= parent
->GetParent()) {
3008 if (parent
->IsHTMLElement(nsGkAtoms::table
)) {
3016 nsresult
nsFrameSelection::SelectCellElement(nsIContent
* aCellElement
) {
3017 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
3018 const RefPtr
<Selection
> selection
= mDomSelections
[index
];
3020 return NS_ERROR_NULL_POINTER
;
3023 return ::SelectCellElement(aCellElement
, *selection
);
3026 nsresult
CreateAndAddRange(nsINode
* aContainer
, int32_t aOffset
,
3027 Selection
& aNormalSelection
) {
3028 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
3031 return NS_ERROR_NULL_POINTER
;
3034 // Set range around child at given offset
3036 RefPtr
<nsRange
> range
=
3037 nsRange::Create(aContainer
, aOffset
, aContainer
, aOffset
+ 1, error
);
3038 if (NS_WARN_IF(error
.Failed())) {
3039 return error
.StealNSResult();
3044 aNormalSelection
.AddRangeAndSelectFramesAndNotifyListeners(*range
, err
);
3045 return err
.StealNSResult();
3048 // End of Table Selection
3050 void nsFrameSelection::SetAncestorLimiter(nsIContent
* aLimiter
) {
3051 if (mLimiters
.mAncestorLimiter
!= aLimiter
) {
3052 mLimiters
.mAncestorLimiter
= aLimiter
;
3053 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
3054 if (!mDomSelections
[index
]) return;
3056 if (!IsValidSelectionPoint(mDomSelections
[index
]->GetFocusNode())) {
3057 ClearNormalSelection();
3058 if (mLimiters
.mAncestorLimiter
) {
3059 SetChangeReasons(nsISelectionListener::NO_REASON
);
3060 nsCOMPtr
<nsIContent
> limiter(mLimiters
.mAncestorLimiter
);
3061 const nsresult rv
= TakeFocus(*limiter
, 0, 0, CARET_ASSOCIATE_BEFORE
,
3062 FocusMode::kCollapseToNewPoint
);
3063 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
3064 // TODO: in case of failure, propagate it to the callers.
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");