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/intl/BidiEmbeddingLevel.h"
14 #include "mozilla/Attributes.h"
15 #include "mozilla/AutoRestore.h"
16 #include "mozilla/BasePrincipal.h"
17 #include "mozilla/HTMLEditor.h"
18 #include "mozilla/IntegerRange.h"
19 #include "mozilla/Logging.h"
20 #include "mozilla/PresShell.h"
21 #include "mozilla/ScrollTypes.h"
22 #include "mozilla/StaticAnalysisFunctions.h"
23 #include "mozilla/StaticPrefs_bidi.h"
24 #include "mozilla/StaticPrefs_dom.h"
25 #include "mozilla/StaticPrefs_layout.h"
26 #include "mozilla/Unused.h"
31 #include "nsISelectionListener.h"
32 #include "nsContentCID.h"
33 #include "nsDeviceContext.h"
34 #include "nsIContent.h"
36 #include "nsITableCellLayout.h"
38 #include "nsTableWrapperFrame.h"
39 #include "nsTableCellFrame.h"
40 #include "nsIScrollableFrame.h"
41 #include "nsCCUncollectableMarker.h"
42 #include "nsTextFragment.h"
44 #include "nsContentUtils.h"
45 #include "nsCSSFrameConstructor.h"
47 #include "nsGkAtoms.h"
48 #include "nsIFrameTraversal.h"
49 #include "nsLayoutUtils.h"
50 #include "nsLayoutCID.h"
51 #include "nsBidiPresUtils.h"
52 static NS_DEFINE_CID(kFrameTraversalCID
, NS_FRAMETRAVERSAL_CID
);
53 #include "nsTextFrame.h"
55 #include "nsThreadUtils.h"
56 #include "mozilla/Preferences.h"
58 #include "mozilla/PresShell.h"
59 #include "nsPresContext.h"
62 #include "mozilla/MouseEvents.h"
63 #include "mozilla/TextEvents.h"
66 #include "mozilla/dom/Document.h"
68 #include "nsISelectionController.h" //for the enums
69 #include "nsCopySupport.h"
70 #include "nsIClipboard.h"
71 #include "nsIFrameInlines.h"
74 #include "mozilla/AutoCopyListener.h"
75 #include "mozilla/dom/Element.h"
76 #include "mozilla/dom/Selection.h"
77 #include "mozilla/dom/ShadowRoot.h"
78 #include "mozilla/dom/StaticRange.h"
79 #include "mozilla/dom/Text.h"
80 #include "mozilla/ErrorResult.h"
81 #include "mozilla/dom/SelectionBinding.h"
82 #include "mozilla/AsyncEventDispatcher.h"
83 #include "mozilla/Telemetry.h"
85 #include "nsFocusManager.h"
86 #include "nsPIDOMWindow.h"
88 using namespace mozilla
;
89 using namespace mozilla::dom
;
91 static LazyLogModule
sFrameSelectionLog("FrameSelection");
93 //#define DEBUG_TABLE 1
96 * Add cells to the selection inside of the given cells range.
98 * @param aTable [in] HTML table element
99 * @param aStartRowIndex [in] row index where the cells range starts
100 * @param aStartColumnIndex [in] column index where the cells range starts
101 * @param aEndRowIndex [in] row index where the cells range ends
102 * @param aEndColumnIndex [in] column index where the cells range ends
104 static nsresult
AddCellsToSelection(const nsIContent
* aTableContent
,
105 int32_t aStartRowIndex
,
106 int32_t aStartColumnIndex
,
107 int32_t aEndRowIndex
,
108 int32_t aEndColumnIndex
,
109 Selection
& aNormalSelection
);
111 static nsAtom
* GetTag(nsINode
* aNode
);
113 static nsINode
* GetClosestInclusiveTableCellAncestor(nsINode
* aDomNode
);
114 MOZ_CAN_RUN_SCRIPT_BOUNDARY
static nsresult
CreateAndAddRange(
115 nsINode
* aContainer
, int32_t aOffset
, Selection
& aNormalSelection
);
116 static nsresult
SelectCellElement(nsIContent
* aCellElement
,
117 Selection
& aNormalSelection
);
120 static nsresult
UpdateSelectionCacheOnRepaintSelection(Selection
* aSel
);
124 static void printRange(nsRange
* aDomRange
);
125 # define DEBUG_OUT_RANGE(x) printRange(x)
127 # define DEBUG_OUT_RANGE(x)
128 #endif // PRINT_RANGE
130 /******************************************************************************
132 ******************************************************************************/
134 //#define DEBUG_SELECTION // uncomment for printf describing every collapse and
135 // extend. #define DEBUG_NAVIGATION
137 //#define DEBUG_TABLE_SELECTION 1
139 nsPeekOffsetStruct::nsPeekOffsetStruct(
140 nsSelectionAmount aAmount
, nsDirection aDirection
, int32_t aStartOffset
,
141 nsPoint aDesiredCaretPos
, bool aJumpLines
, bool aScrollViewStop
,
142 bool aIsKeyboardSelect
, bool aVisual
, bool aExtend
,
143 ForceEditableRegion aForceEditableRegion
,
144 EWordMovementType aWordMovementType
, bool aTrimSpaces
)
146 mDirection(aDirection
),
147 mStartOffset(aStartOffset
),
148 mDesiredCaretPos(aDesiredCaretPos
),
149 mWordMovementType(aWordMovementType
),
150 mJumpLines(aJumpLines
),
151 mTrimSpaces(aTrimSpaces
),
152 mScrollViewStop(aScrollViewStop
),
153 mIsKeyboardSelect(aIsKeyboardSelect
),
156 mForceEditableRegion(aForceEditableRegion
== ForceEditableRegion::Yes
),
158 mResultFrame(nullptr),
160 mAttach(CARET_ASSOCIATE_BEFORE
) {}
162 // Array which contains index of each SelecionType in Selection::mDOMSelections.
163 // For avoiding using if nor switch to retrieve the index, this needs to have
164 // -1 for SelectionTypes which won't be created its Selection instance.
165 static const int8_t kIndexOfSelections
[] = {
166 -1, // SelectionType::eInvalid
167 -1, // SelectionType::eNone
168 0, // SelectionType::eNormal
169 1, // SelectionType::eSpellCheck
170 2, // SelectionType::eIMERawClause
171 3, // SelectionType::eIMESelectedRawClause
172 4, // SelectionType::eIMEConvertedClause
173 5, // SelectionType::eIMESelectedClause
174 6, // SelectionType::eAccessibility
175 7, // SelectionType::eFind
176 8, // SelectionType::eURLSecondary
177 9, // SelectionType::eURLStrikeout
180 inline int8_t GetIndexFromSelectionType(SelectionType aSelectionType
) {
181 // The enum value of eInvalid is -1 and the others are sequential value
182 // starting from 0. Therefore, |SelectionType + 1| is the index of
183 // kIndexOfSelections.
184 return kIndexOfSelections
[static_cast<int8_t>(aSelectionType
) + 1];
188 The limiter is used specifically for the text areas and textfields
189 In that case it is the DIV tag that is anonymously created for the text
190 areas/fields. Text nodes and BR nodes fall beneath it. In the case of a
191 BR node the limiter will be the parent and the offset will point before or
192 after the BR node. In the case of the text node the parent content is
193 the text node itself and the offset will be the exact character position.
194 The offset is not important to check for validity. Simply look at the
195 passed in content. If it equals the limiter then the selection point is valid.
196 If its parent it the limiter then the point is also valid. In the case of
197 NO limiter all points are valid since you are in a topmost iframe. (browser
200 bool nsFrameSelection::IsValidSelectionPoint(nsINode
* aNode
) const {
205 nsIContent
* limiter
= GetLimiter();
206 if (limiter
&& limiter
!= aNode
&& limiter
!= aNode
->GetParent()) {
207 // if newfocus == the limiter. that's ok. but if not there and not parent
209 return false; // not in the right content. tLimiter said so
212 limiter
= GetAncestorLimiter();
213 return !limiter
|| aNode
->IsInclusiveDescendantOf(limiter
);
217 struct MOZ_RAII AutoPrepareFocusRange
{
218 AutoPrepareFocusRange(Selection
* aSelection
,
219 const bool aMultiRangeSelection
) {
220 MOZ_ASSERT(aSelection
);
221 MOZ_ASSERT(aSelection
->GetType() == SelectionType::eNormal
);
223 if (aSelection
->mStyledRanges
.mRanges
.Length() <= 1) {
227 if (aSelection
->mFrameSelection
->IsUserSelectionReason()) {
228 mUserSelect
.emplace(aSelection
);
231 nsTArray
<StyledRange
>& ranges
= aSelection
->mStyledRanges
.mRanges
;
232 if (!aSelection
->mUserInitiated
|| aMultiRangeSelection
) {
233 // Scripted command or the user is starting a new explicit multi-range
235 for (StyledRange
& entry
: ranges
) {
236 entry
.mRange
->SetIsGenerated(false);
241 if (!IsAnchorRelativeOperation(
242 aSelection
->mFrameSelection
->mSelectionChangeReasons
)) {
246 // This operation is against the anchor but our current mAnchorFocusRange
247 // represents the focus in a multi-range selection. The anchor from a user
248 // perspective is the most distant generated range on the opposite side.
249 // Find that range and make it the mAnchorFocusRange.
250 nsRange
* const newAnchorFocusRange
=
251 FindGeneratedRangeMostDistantFromAnchor(*aSelection
);
253 if (!newAnchorFocusRange
) {
254 // There are no generated ranges - that's fine.
258 // Setup the new mAnchorFocusRange and mark the old one as generated.
259 if (aSelection
->mAnchorFocusRange
) {
260 aSelection
->mAnchorFocusRange
->SetIsGenerated(true);
263 newAnchorFocusRange
->SetIsGenerated(false);
264 aSelection
->mAnchorFocusRange
= newAnchorFocusRange
;
266 RemoveGeneratedRanges(*aSelection
);
268 if (aSelection
->mFrameSelection
) {
269 aSelection
->mFrameSelection
->InvalidateDesiredCaretPos();
274 static nsRange
* FindGeneratedRangeMostDistantFromAnchor(
275 const Selection
& aSelection
) {
276 const nsTArray
<StyledRange
>& ranges
= aSelection
.mStyledRanges
.mRanges
;
277 const size_t len
= ranges
.Length();
278 nsRange
* result
{nullptr};
279 if (aSelection
.GetDirection() == eDirNext
) {
280 for (size_t i
= 0; i
< len
; ++i
) {
281 if (ranges
[i
].mRange
->IsGenerated()) {
282 result
= ranges
[i
].mRange
;
289 if (ranges
[i
].mRange
->IsGenerated()) {
290 result
= ranges
[i
].mRange
;
299 static void RemoveGeneratedRanges(Selection
& aSelection
) {
300 RefPtr
<nsPresContext
> presContext
= aSelection
.GetPresContext();
301 nsTArray
<StyledRange
>& ranges
= aSelection
.mStyledRanges
.mRanges
;
302 size_t i
= ranges
.Length();
304 nsRange
* range
= ranges
[i
].mRange
;
305 if (range
->IsGenerated()) {
306 range
->UnregisterSelection();
307 aSelection
.SelectFrames(presContext
, range
, false);
308 ranges
.RemoveElementAt(i
);
314 * @aParam aSelectionChangeReasons can be multiple of the reasons defined in
315 nsISelectionListener.idl.
317 static bool IsAnchorRelativeOperation(const int16_t aSelectionChangeReasons
) {
318 return aSelectionChangeReasons
&
319 (nsISelectionListener::DRAG_REASON
|
320 nsISelectionListener::MOUSEDOWN_REASON
|
321 nsISelectionListener::MOUSEUP_REASON
|
322 nsISelectionListener::COLLAPSETOSTART_REASON
);
325 Maybe
<Selection::AutoUserInitiated
> mUserSelect
;
328 } // namespace mozilla
330 ////////////BEGIN nsFrameSelection methods
332 template Result
<RefPtr
<nsRange
>, nsresult
>
333 nsFrameSelection::CreateRangeExtendedToSomewhere(
334 nsDirection aDirection
, const nsSelectionAmount aAmount
,
335 CaretMovementStyle aMovementStyle
);
336 template Result
<RefPtr
<StaticRange
>, nsresult
>
337 nsFrameSelection::CreateRangeExtendedToSomewhere(
338 nsDirection aDirection
, const nsSelectionAmount aAmount
,
339 CaretMovementStyle aMovementStyle
);
341 nsFrameSelection::nsFrameSelection(PresShell
* aPresShell
, nsIContent
* aLimiter
,
342 const bool aAccessibleCaretEnabled
) {
343 for (size_t i
= 0; i
< ArrayLength(mDomSelections
); i
++) {
344 mDomSelections
[i
] = new Selection(kPresentSelectionTypes
[i
], this);
348 // On macOS, cache the current selection to send to service menu of macOS.
349 bool enableAutoCopy
= true;
350 AutoCopyListener::Init(nsIClipboard::kSelectionCache
);
351 #else // #ifdef XP_MACOSX
352 // Check to see if the auto-copy pref is enabled and make the normal
353 // Selection notifies auto-copy listener of its changes.
354 bool enableAutoCopy
= AutoCopyListener::IsPrefEnabled();
355 if (enableAutoCopy
) {
356 AutoCopyListener::Init(nsIClipboard::kSelectionClipboard
);
358 #endif // #ifdef XP_MACOSX #else
360 if (enableAutoCopy
) {
361 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
362 if (mDomSelections
[index
]) {
363 mDomSelections
[index
]->NotifyAutoCopy();
367 mPresShell
= aPresShell
;
369 mLimiters
.mLimiter
= aLimiter
;
371 // This should only ever be initialized on the main thread, so we are OK here.
372 MOZ_ASSERT(NS_IsMainThread());
374 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
376 mAccessibleCaretEnabled
= aAccessibleCaretEnabled
;
377 if (mAccessibleCaretEnabled
) {
378 mDomSelections
[index
]->MaybeNotifyAccessibleCaretEventHub(aPresShell
);
381 if (mDomSelections
[index
]) {
382 mDomSelections
[index
]->EnableSelectionChangeEvent();
386 nsFrameSelection::~nsFrameSelection() = default;
388 NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameSelection
)
390 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameSelection
)
391 for (size_t i
= 0; i
< ArrayLength(tmp
->mDomSelections
); ++i
) {
392 tmp
->mDomSelections
[i
] = nullptr;
395 NS_IMPL_CYCLE_COLLECTION_UNLINK(
396 mTableSelection
.mClosestInclusiveTableCellAncestor
)
397 tmp
->mTableSelection
.mMode
= TableSelectionMode::None
;
398 tmp
->mTableSelection
.mDragSelectingCells
= false;
399 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection
.mStartSelectedCell
)
400 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection
.mEndSelectedCell
)
401 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection
.mAppendStartSelectedCell
)
402 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection
.mUnselectCellOnMouseUp
)
403 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMaintainedRange
.mRange
)
404 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiters
.mLimiter
)
405 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiters
.mAncestorLimiter
)
406 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
407 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameSelection
)
408 if (tmp
->mPresShell
&& tmp
->mPresShell
->GetDocument() &&
409 nsCCUncollectableMarker::InGeneration(
410 cb
, tmp
->mPresShell
->GetDocument()->GetMarkedCCGeneration())) {
411 return NS_SUCCESS_INTERRUPTED_TRAVERSE
;
413 for (size_t i
= 0; i
< ArrayLength(tmp
->mDomSelections
); ++i
) {
414 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDomSelections
[i
])
417 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
418 mTableSelection
.mClosestInclusiveTableCellAncestor
)
419 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection
.mStartSelectedCell
)
420 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection
.mEndSelectedCell
)
421 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection
.mAppendStartSelectedCell
)
422 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection
.mUnselectCellOnMouseUp
)
423 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMaintainedRange
.mRange
)
424 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiters
.mLimiter
)
425 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiters
.mAncestorLimiter
)
426 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
428 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsFrameSelection
, AddRef
)
429 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsFrameSelection
, Release
)
431 bool nsFrameSelection::Caret::IsVisualMovement(
432 bool aContinueSelection
, CaretMovementStyle aMovementStyle
) const {
433 int32_t movementFlag
= StaticPrefs::bidi_edit_caret_movement_style();
434 return aMovementStyle
== eVisual
||
435 (aMovementStyle
== eUsePrefStyle
&&
436 (movementFlag
== 1 || (movementFlag
== 2 && !aContinueSelection
)));
439 // Get the x (or y, in vertical writing mode) position requested
440 // by the Key Handling for line-up/down
441 nsresult
nsFrameSelection::DesiredCaretPos::FetchPos(
442 nsPoint
& aDesiredCaretPos
, const PresShell
& aPresShell
,
443 Selection
& aNormalSelection
) const {
444 MOZ_ASSERT(aNormalSelection
.GetType() == SelectionType::eNormal
);
447 aDesiredCaretPos
= mValue
;
451 RefPtr
<nsCaret
> caret
= aPresShell
.GetCaret();
453 return NS_ERROR_NULL_POINTER
;
456 caret
->SetSelection(&aNormalSelection
);
459 nsIFrame
* caretFrame
= caret
->GetGeometry(&coord
);
461 return NS_ERROR_FAILURE
;
463 nsPoint
viewOffset(0, 0);
464 nsView
* view
= nullptr;
465 caretFrame
->GetOffsetFromView(viewOffset
, &view
);
469 aDesiredCaretPos
= coord
.TopLeft();
473 void nsFrameSelection::InvalidateDesiredCaretPos() // do not listen to
474 // mDesiredCaretPos.mValue;
475 // you must get another.
477 mDesiredCaretPos
.Invalidate();
480 void nsFrameSelection::DesiredCaretPos::Invalidate() { mIsSet
= false; }
482 void nsFrameSelection::DesiredCaretPos::Set(const nsPoint
& aPos
) {
487 nsresult
nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(
488 nsIFrame
* aFrame
, const nsPoint
& aPoint
, nsIFrame
** aRetFrame
,
489 nsPoint
& aRetPoint
) const {
491 // The whole point of this method is to return a frame and point that
492 // that lie within the same valid subtree as the anchor node's frame,
493 // for use with the method GetContentAndOffsetsFromPoint().
495 // A valid subtree is defined to be one where all the content nodes in
496 // the tree have a valid parent-child relationship.
498 // If the anchor frame and aFrame are in the same subtree, aFrame will
499 // be returned in aRetFrame. If they are in different subtrees, we
500 // return the frame for the root of the subtree.
503 if (!aFrame
|| !aRetFrame
) {
504 return NS_ERROR_NULL_POINTER
;
511 // Get the frame and content for the selection's anchor point!
514 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
515 if (!mDomSelections
[index
]) {
516 return NS_ERROR_NULL_POINTER
;
519 nsCOMPtr
<nsIContent
> anchorContent
=
520 do_QueryInterface(mDomSelections
[index
]->GetAnchorNode());
521 if (!anchorContent
) {
522 return NS_ERROR_FAILURE
;
526 // Now find the root of the subtree containing the anchor's content.
529 NS_ENSURE_STATE(mPresShell
);
530 RefPtr
<PresShell
> presShell
= mPresShell
;
531 nsIContent
* anchorRoot
= anchorContent
->GetSelectionRootContent(presShell
);
532 NS_ENSURE_TRUE(anchorRoot
, NS_ERROR_UNEXPECTED
);
535 // Now find the root of the subtree containing aFrame's content.
538 nsCOMPtr
<nsIContent
> content
= aFrame
->GetContent();
541 nsIContent
* contentRoot
= content
->GetSelectionRootContent(presShell
);
542 NS_ENSURE_TRUE(contentRoot
, NS_ERROR_UNEXPECTED
);
544 if (anchorRoot
== contentRoot
) {
545 // If the aFrame's content isn't the capturing content, it should be
546 // a descendant. At this time, we can return simply.
547 nsIContent
* capturedContent
= PresShell::GetCapturingContent();
548 if (capturedContent
!= content
) {
552 // Find the frame under the mouse cursor with the root frame.
553 // At this time, don't use the anchor's frame because it may not have
554 // fixed positioned frames.
555 nsIFrame
* rootFrame
= presShell
->GetRootFrame();
556 nsPoint ptInRoot
= aPoint
+ aFrame
->GetOffsetTo(rootFrame
);
557 nsIFrame
* cursorFrame
=
558 nsLayoutUtils::GetFrameForPoint(RelativeTo
{rootFrame
}, ptInRoot
);
560 // If the mouse cursor in on a frame which is descendant of same
561 // selection root, we can expand the selection to the frame.
562 if (cursorFrame
&& cursorFrame
->PresShell() == presShell
) {
563 nsCOMPtr
<nsIContent
> cursorContent
= cursorFrame
->GetContent();
564 NS_ENSURE_TRUE(cursorContent
, NS_ERROR_FAILURE
);
565 nsIContent
* cursorContentRoot
=
566 cursorContent
->GetSelectionRootContent(presShell
);
567 NS_ENSURE_TRUE(cursorContentRoot
, NS_ERROR_UNEXPECTED
);
568 if (cursorContentRoot
== anchorRoot
) {
569 *aRetFrame
= cursorFrame
;
570 aRetPoint
= aPoint
+ aFrame
->GetOffsetTo(cursorFrame
);
574 // Otherwise, e.g., the cursor isn't on any frames (e.g., the mouse
575 // cursor is out of the window), we should use the frame of the anchor
581 // When we can't find a frame which is under the mouse cursor and has a same
582 // selection root as the anchor node's, we should return the selection root
586 *aRetFrame
= anchorRoot
->GetPrimaryFrame();
589 return NS_ERROR_FAILURE
;
593 // Now make sure that aRetPoint is converted to the same coordinate
594 // system used by aRetFrame.
597 aRetPoint
= aPoint
+ aFrame
->GetOffsetTo(*aRetFrame
);
602 void nsFrameSelection::SetCaretBidiLevelAndMaybeSchedulePaint(
603 mozilla::intl::BidiEmbeddingLevel aLevel
) {
604 // If the current level is undefined, we have just inserted new text.
605 // In this case, we don't want to reset the keyboard language
606 mCaret
.mBidiLevel
= aLevel
;
608 RefPtr
<nsCaret
> caret
;
609 if (mPresShell
&& (caret
= mPresShell
->GetCaret())) {
610 caret
->SchedulePaint();
614 mozilla::intl::BidiEmbeddingLevel
nsFrameSelection::GetCaretBidiLevel() const {
615 return mCaret
.mBidiLevel
;
618 void nsFrameSelection::UndefineCaretBidiLevel() {
619 mCaret
.mBidiLevel
= mozilla::intl::BidiEmbeddingLevel(mCaret
.mBidiLevel
|
620 BIDI_LEVEL_UNDEFINED
);
624 void printRange(nsRange
* aDomRange
) {
626 printf("NULL Range\n");
628 nsINode
* startNode
= aDomRange
->GetStartContainer();
629 nsINode
* endNode
= aDomRange
->GetEndContainer();
630 int32_t startOffset
= aDomRange
->StartOffset();
631 int32_t endOffset
= aDomRange
->EndOffset();
633 printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n",
634 (unsigned long)aDomRange
, (unsigned long)startNode
, (long)startOffset
,
635 (unsigned long)endNode
, (long)endOffset
);
637 #endif /* PRINT_RANGE */
639 static nsAtom
* GetTag(nsINode
* aNode
) {
640 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aNode
);
642 MOZ_ASSERT_UNREACHABLE("bad node passed to GetTag()");
646 return content
->NodeInfo()->NameAtom();
650 * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor.
652 static nsINode
* GetClosestInclusiveTableCellAncestor(nsINode
* aDomNode
) {
653 if (!aDomNode
) return nullptr;
654 nsINode
* current
= aDomNode
;
655 // Start with current node and look for a table cell
657 nsAtom
* tag
= GetTag(current
);
658 if (tag
== nsGkAtoms::td
|| tag
== nsGkAtoms::th
) return current
;
659 current
= current
->GetParent();
664 static nsDirection
GetCaretDirection(const nsIFrame
& aFrame
,
665 nsDirection aDirection
,
666 bool aVisualMovement
) {
667 const mozilla::intl::BidiDirection paragraphDirection
=
668 nsBidiPresUtils::ParagraphDirection(&aFrame
);
669 return (aVisualMovement
&&
670 paragraphDirection
== mozilla::intl::BidiDirection::RTL
)
671 ? nsDirection(1 - aDirection
)
675 nsresult
nsFrameSelection::MoveCaret(nsDirection aDirection
,
676 bool aContinueSelection
,
677 const nsSelectionAmount aAmount
,
678 CaretMovementStyle aMovementStyle
) {
679 NS_ENSURE_STATE(mPresShell
);
680 // Flush out layout, since we need it to be up to date to do caret
682 OwningNonNull
<PresShell
> presShell(*mPresShell
);
683 presShell
->FlushPendingNotifications(FlushType::Layout
);
689 nsPresContext
* context
= mPresShell
->GetPresContext();
691 return NS_ERROR_FAILURE
;
694 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
695 const RefPtr
<Selection
> sel
= mDomSelections
[index
];
697 return NS_ERROR_NULL_POINTER
;
700 int32_t scrollFlags
= Selection::SCROLL_FOR_CARET_MOVE
;
701 if (sel
->IsEditorSelection()) {
702 // If caret moves in editor, it should cause scrolling even if it's in
703 // overflow: hidden;.
704 scrollFlags
|= Selection::SCROLL_OVERFLOW_HIDDEN
;
707 const bool doCollapse
= [&] {
708 if (sel
->IsCollapsed() || aContinueSelection
) {
711 if (aAmount
> eSelectLine
) {
714 int32_t caretStyle
= StaticPrefs::layout_selection_caret_style();
715 return caretStyle
== 2 || (caretStyle
== 0 && aAmount
!= eSelectLine
);
719 if (aDirection
== eDirPrevious
) {
720 SetChangeReasons(nsISelectionListener::COLLAPSETOSTART_REASON
);
721 mCaret
.mHint
= CARET_ASSOCIATE_AFTER
;
723 SetChangeReasons(nsISelectionListener::COLLAPSETOEND_REASON
);
724 mCaret
.mHint
= CARET_ASSOCIATE_BEFORE
;
727 SetChangeReasons(nsISelectionListener::KEYPRESS_REASON
);
730 mCaretMoveAmount
= aAmount
;
732 AutoPrepareFocusRange
prep(sel
, false);
734 // we must keep this around and revalidate it when its just UP/DOWN
735 nsPoint
desiredPos(0, 0);
737 if (aAmount
== eSelectLine
) {
738 nsresult result
= mDesiredCaretPos
.FetchPos(desiredPos
, *mPresShell
, *sel
);
739 if (NS_FAILED(result
)) {
742 mDesiredCaretPos
.Set(desiredPos
);
745 bool visualMovement
=
746 mCaret
.IsVisualMovement(aContinueSelection
, aMovementStyle
);
747 nsIFrame
* frame
= sel
->GetPrimaryFrameForFocusNode(visualMovement
);
749 return NS_ERROR_FAILURE
;
752 Result
<bool, nsresult
> isIntraLineCaretMove
= IsIntraLineCaretMove(aAmount
);
753 nsDirection direction
{aDirection
};
754 if (isIntraLineCaretMove
.isErr()) {
755 return isIntraLineCaretMove
.unwrapErr();
757 if (isIntraLineCaretMove
.inspect()) {
758 // Forget old caret position for moving caret to different line since
759 // caret position may be changed.
760 mDesiredCaretPos
.Invalidate();
761 direction
= GetCaretDirection(*frame
, aDirection
, visualMovement
);
765 const nsRange
* anchorFocusRange
= sel
->GetAnchorFocusRange();
766 if (anchorFocusRange
) {
767 RefPtr
<nsINode
> node
;
769 if (visualMovement
&& nsBidiPresUtils::IsReversedDirectionFrame(frame
)) {
770 direction
= nsDirection(1 - direction
);
772 if (direction
== eDirPrevious
) {
773 node
= anchorFocusRange
->GetStartContainer();
774 offset
= anchorFocusRange
->StartOffset();
776 node
= anchorFocusRange
->GetEndContainer();
777 offset
= anchorFocusRange
->EndOffset();
779 sel
->CollapseInLimiter(node
, offset
);
781 sel
->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION
,
782 ScrollAxis(), ScrollAxis(), scrollFlags
);
786 CaretAssociateHint
tHint(mCaret
.mHint
); // temporary variable so we dont set
787 // mCaret.mHint until it is necessary
789 Result
<nsPeekOffsetStruct
, nsresult
> result
= PeekOffsetForCaretMove(
790 direction
, aContinueSelection
, aAmount
, aMovementStyle
, desiredPos
);
792 if (result
.isOk() && result
.inspect().mResultContent
) {
793 const nsPeekOffsetStruct
& pos
= result
.inspect();
795 int32_t currentOffset
, frameStart
, frameEnd
;
797 if (aAmount
<= eSelectWordNoSpace
) {
798 // For left/right, PeekOffset() sets pos.mResultFrame correctly, but does
799 // not set pos.mAttachForward, so determine the hint here based on the
800 // result frame and offset: If we're at the end of a text frame, set the
801 // hint to ASSOCIATE_BEFORE to indicate that we want the caret displayed
802 // at the end of this frame, not at the beginning of the next one.
803 theFrame
= pos
.mResultFrame
;
804 std::tie(frameStart
, frameEnd
) = theFrame
->GetOffsets();
805 currentOffset
= pos
.mContentOffset
;
806 if (frameEnd
== currentOffset
&& !(frameStart
== 0 && frameEnd
== 0))
807 tHint
= CARET_ASSOCIATE_BEFORE
;
809 tHint
= CARET_ASSOCIATE_AFTER
;
811 // For up/down and home/end, pos.mResultFrame might not be set correctly,
812 // or not at all. In these cases, get the frame based on the content and
813 // hint returned by PeekOffset().
815 theFrame
= GetFrameForNodeOffset(pos
.mResultContent
, pos
.mContentOffset
,
816 tHint
, ¤tOffset
);
817 if (!theFrame
) return NS_ERROR_FAILURE
;
819 std::tie(frameStart
, frameEnd
) = theFrame
->GetOffsets();
822 if (context
->BidiEnabled()) {
824 case eSelectBeginLine
:
825 case eSelectEndLine
: {
826 // In Bidi contexts, PeekOffset calculates pos.mContentOffset
827 // differently depending on whether the movement is visual or logical.
828 // For visual movement, pos.mContentOffset depends on the direction-
829 // ality of the first/last frame on the line (theFrame), and the caret
830 // directionality must correspond.
831 FrameBidiData bidiData
= theFrame
->GetBidiData();
832 SetCaretBidiLevelAndMaybeSchedulePaint(
833 visualMovement
? bidiData
.embeddingLevel
: bidiData
.baseLevel
);
837 // If the current position is not a frame boundary, it's enough just
838 // to take the Bidi level of the current frame
839 if ((pos
.mContentOffset
!= frameStart
&&
840 pos
.mContentOffset
!= frameEnd
) ||
841 eSelectLine
== aAmount
) {
842 SetCaretBidiLevelAndMaybeSchedulePaint(
843 theFrame
->GetEmbeddingLevel());
845 BidiLevelFromMove(mPresShell
, pos
.mResultContent
,
846 pos
.mContentOffset
, aAmount
, tHint
);
850 // "pos" is on the stack, so pos.mResultContent has stack lifetime, so using
851 // MOZ_KnownLive is ok.
852 const FocusMode focusMode
= aContinueSelection
853 ? FocusMode::kExtendSelection
854 : FocusMode::kCollapseToNewPoint
;
855 rv
= TakeFocus(MOZ_KnownLive(*pos
.mResultContent
), pos
.mContentOffset
,
856 pos
.mContentOffset
, tHint
, focusMode
);
857 } else if (aAmount
<= eSelectWordNoSpace
&& direction
== eDirNext
&&
858 !aContinueSelection
) {
859 // Collapse selection if PeekOffset failed, we either
860 // 1. bumped into the BRFrame, bug 207623
861 // 2. had select-all in a text input (DIV range), bug 352759.
862 bool isBRFrame
= frame
->IsBrFrame();
863 RefPtr
<nsINode
> node
= sel
->GetFocusNode();
864 sel
->CollapseInLimiter(node
, sel
->FocusOffset());
865 // Note: 'frame' might be dead here.
867 mCaret
.mHint
= CARET_ASSOCIATE_BEFORE
; // We're now at the end of the
868 // frame to the left.
872 rv
= result
.isErr() ? result
.unwrapErr() : NS_OK
;
874 if (NS_SUCCEEDED(rv
)) {
875 rv
= sel
->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION
,
876 ScrollAxis(), ScrollAxis(), scrollFlags
);
882 Result
<nsPeekOffsetStruct
, nsresult
> nsFrameSelection::PeekOffsetForCaretMove(
883 nsDirection aDirection
, bool aContinueSelection
,
884 const nsSelectionAmount aAmount
, CaretMovementStyle aMovementStyle
,
885 const nsPoint
& aDesiredCaretPos
) const {
886 Selection
* selection
=
887 mDomSelections
[GetIndexFromSelectionType(SelectionType::eNormal
)];
889 return Err(NS_ERROR_NULL_POINTER
);
892 const bool visualMovement
=
893 mCaret
.IsVisualMovement(aContinueSelection
, aMovementStyle
);
895 int32_t offsetused
= 0;
897 selection
->GetPrimaryFrameForFocusNode(visualMovement
, &offsetused
);
899 return Err(NS_ERROR_FAILURE
);
902 const auto kForceEditableRegion
=
903 selection
->IsEditorSelection()
904 ? nsPeekOffsetStruct::ForceEditableRegion::Yes
905 : nsPeekOffsetStruct::ForceEditableRegion::No
;
907 // set data using mLimiters.mLimiter to stop on scroll views. If we have a
908 // limiter then we stop peeking when we hit scrollable views. If no limiter
909 // then just let it go ahead
910 nsPeekOffsetStruct
pos(aAmount
, aDirection
, offsetused
, aDesiredCaretPos
,
911 true, !!mLimiters
.mLimiter
, true, visualMovement
,
912 aContinueSelection
, kForceEditableRegion
);
913 nsresult rv
= frame
->PeekOffset(&pos
);
920 nsPrevNextBidiLevels
nsFrameSelection::GetPrevNextBidiLevels(
921 nsIContent
* aNode
, uint32_t aContentOffset
, bool aJumpLines
) const {
922 return GetPrevNextBidiLevels(aNode
, aContentOffset
, mCaret
.mHint
, aJumpLines
);
926 nsPrevNextBidiLevels
nsFrameSelection::GetPrevNextBidiLevels(
927 nsIContent
* aNode
, uint32_t aContentOffset
, CaretAssociateHint aHint
,
929 // Get the level of the frames on each side
930 nsIFrame
* currentFrame
;
931 int32_t currentOffset
;
932 nsDirection direction
;
934 nsPrevNextBidiLevels levels
{};
935 levels
.SetData(nullptr, nullptr, mozilla::intl::BidiEmbeddingLevel::LTR(),
936 mozilla::intl::BidiEmbeddingLevel::LTR());
938 currentFrame
= GetFrameForNodeOffset(
939 aNode
, static_cast<int32_t>(aContentOffset
), aHint
, ¤tOffset
);
944 auto [frameStart
, frameEnd
] = currentFrame
->GetOffsets();
946 if (0 == frameStart
&& 0 == frameEnd
) {
947 direction
= eDirPrevious
;
948 } else if (frameStart
== currentOffset
) {
949 direction
= eDirPrevious
;
950 } else if (frameEnd
== currentOffset
) {
951 direction
= eDirNext
;
953 // we are neither at the beginning nor at the end of the frame, so we have
955 mozilla::intl::BidiEmbeddingLevel currentLevel
=
956 currentFrame
->GetEmbeddingLevel();
957 levels
.SetData(currentFrame
, currentFrame
, currentLevel
, currentLevel
);
963 ->GetFrameFromDirection(direction
, false, aJumpLines
, true, false)
966 FrameBidiData currentBidi
= currentFrame
->GetBidiData();
967 mozilla::intl::BidiEmbeddingLevel currentLevel
= currentBidi
.embeddingLevel
;
968 mozilla::intl::BidiEmbeddingLevel newLevel
=
969 newFrame
? newFrame
->GetEmbeddingLevel() : currentBidi
.baseLevel
;
971 // If not jumping lines, disregard br frames, since they might be positioned
973 // XXX This could be removed once bug 339786 is fixed.
975 if (currentFrame
->IsBrFrame()) {
976 currentFrame
= nullptr;
977 currentLevel
= currentBidi
.baseLevel
;
979 if (newFrame
&& newFrame
->IsBrFrame()) {
981 newLevel
= currentBidi
.baseLevel
;
985 if (direction
== eDirNext
)
986 levels
.SetData(currentFrame
, newFrame
, currentLevel
, newLevel
);
988 levels
.SetData(newFrame
, currentFrame
, newLevel
, currentLevel
);
993 nsresult
nsFrameSelection::GetFrameFromLevel(
994 nsIFrame
* aFrameIn
, nsDirection aDirection
,
995 mozilla::intl::BidiEmbeddingLevel aBidiLevel
, nsIFrame
** aFrameOut
) const {
996 NS_ENSURE_STATE(mPresShell
);
997 mozilla::intl::BidiEmbeddingLevel foundLevel
=
998 mozilla::intl::BidiEmbeddingLevel::LTR();
999 nsIFrame
* foundFrame
= aFrameIn
;
1001 nsCOMPtr
<nsIFrameEnumerator
> frameTraversal
;
1003 nsCOMPtr
<nsIFrameTraversal
> trav(
1004 do_CreateInstance(kFrameTraversalCID
, &result
));
1005 if (NS_FAILED(result
)) return result
;
1008 trav
->NewFrameTraversal(getter_AddRefs(frameTraversal
),
1009 mPresShell
->GetPresContext(), aFrameIn
, eLeaf
,
1011 false, // aLockInScrollView
1012 false, // aFollowOOFs
1013 false // aSkipPopupChecks
1015 if (NS_FAILED(result
)) return result
;
1018 *aFrameOut
= foundFrame
;
1019 foundFrame
= frameTraversal
->Traverse(aDirection
== eDirNext
);
1020 if (!foundFrame
) return NS_ERROR_FAILURE
;
1021 foundLevel
= foundFrame
->GetEmbeddingLevel();
1023 } while (foundLevel
> aBidiLevel
);
1028 nsresult
nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount
) {
1029 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
1030 if (!mDomSelections
[index
]) {
1031 return NS_ERROR_NULL_POINTER
;
1034 mMaintainedRange
.MaintainAnchorFocusRange(*mDomSelections
[index
], aAmount
);
1039 void nsFrameSelection::BidiLevelFromMove(PresShell
* aPresShell
,
1041 uint32_t aContentOffset
,
1042 nsSelectionAmount aAmount
,
1043 CaretAssociateHint aHint
) {
1045 // Movement within the line: the new cursor Bidi level is the level of the
1046 // last character moved over
1047 case eSelectCharacter
:
1048 case eSelectCluster
:
1050 case eSelectWordNoSpace
:
1051 case eSelectBeginLine
:
1052 case eSelectEndLine
:
1053 case eSelectNoAmount
: {
1054 nsPrevNextBidiLevels levels
=
1055 GetPrevNextBidiLevels(aNode
, aContentOffset
, aHint
, false);
1057 SetCaretBidiLevelAndMaybeSchedulePaint(aHint
== CARET_ASSOCIATE_BEFORE
1058 ? levels
.mLevelBefore
1059 : levels
.mLevelAfter
);
1063 // Up and Down: the new cursor Bidi level is the smaller of the two
1064 surrounding characters case eSelectLine: case eSelectParagraph:
1065 GetPrevNextBidiLevels(aContext, aNode, aContentOffset, &firstFrame,
1066 &secondFrame, &firstLevel, &secondLevel);
1067 aPresShell->SetCaretBidiLevelAndMaybeSchedulePaint(std::min(firstLevel,
1068 secondLevel)); break;
1072 UndefineCaretBidiLevel();
1076 void nsFrameSelection::BidiLevelFromClick(nsIContent
* aNode
,
1077 uint32_t aContentOffset
) {
1078 nsIFrame
* clickInFrame
= nullptr;
1079 int32_t OffsetNotUsed
;
1081 clickInFrame
= GetFrameForNodeOffset(aNode
, aContentOffset
, mCaret
.mHint
,
1083 if (!clickInFrame
) return;
1085 SetCaretBidiLevelAndMaybeSchedulePaint(clickInFrame
->GetEmbeddingLevel());
1088 void nsFrameSelection::MaintainedRange::AdjustNormalSelection(
1089 const nsIContent
* aContent
, const int32_t aOffset
,
1090 Selection
& aNormalSelection
) const {
1091 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
1093 if (!mRange
|| !aContent
) {
1097 nsINode
* rangeStartNode
= mRange
->GetStartContainer();
1098 nsINode
* rangeEndNode
= mRange
->GetEndContainer();
1099 const uint32_t rangeStartOffset
= mRange
->StartOffset();
1100 const uint32_t rangeEndOffset
= mRange
->EndOffset();
1102 NS_ASSERTION(aOffset
>= 0, "aOffset should not be negative");
1103 const Maybe
<int32_t> relToStart
=
1104 nsContentUtils::ComparePoints_AllowNegativeOffsets(
1105 rangeStartNode
, rangeStartOffset
, aContent
, aOffset
);
1106 if (NS_WARN_IF(!relToStart
)) {
1107 // Potentially handle this properly when Selection across Shadow DOM
1108 // boundary is implemented
1109 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
1113 const Maybe
<int32_t> relToEnd
=
1114 nsContentUtils::ComparePoints_AllowNegativeOffsets(
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
* editingHost
= htmlEditor
->ComputeEditingHost();
1328 const bool editableCell
=
1329 inclusiveTableCellAncestor
->IsInclusiveDescendantOf(editingHost
);
1330 return editableCell
? inclusiveTableCellAncestor
: nullptr;
1334 struct ParentAndOffset
{
1335 explicit ParentAndOffset(const nsINode
& aNode
)
1336 : mParent
{aNode
.GetParent()},
1337 mOffset
{mParent
? mParent
->ComputeIndexOf_Deprecated(&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 RefPtr
<Selection
> selection
= mDomSelections
[index
];
1395 if (aFocusMode
== FocusMode::kMultiRangeSelection
) {
1396 // Remove existing collapsed ranges as there's no point in having
1397 // non-anchor/focus collapsed ranges.
1398 selection
->RemoveCollapsedRanges();
1401 RefPtr
<nsRange
> newRange
= nsRange::Create(
1402 &aNewFocus
, aContentOffset
, &aNewFocus
, aContentOffset
, error
);
1403 if (NS_WARN_IF(error
.Failed())) {
1404 return error
.StealNSResult();
1406 MOZ_ASSERT(newRange
);
1407 selection
->AddRangeAndSelectFramesAndNotifyListeners(*newRange
,
1410 bool oldDesiredPosSet
=
1411 mDesiredCaretPos
.mIsSet
; // need to keep old desired
1412 // position if it was set.
1413 selection
->CollapseInLimiter(&aNewFocus
, aContentOffset
);
1414 mDesiredCaretPos
.mIsSet
=
1415 oldDesiredPosSet
; // now reset desired pos back.
1418 mBatching
= saveBatching
;
1420 if (aContentEndOffset
!= aContentOffset
) {
1421 selection
->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 RefPtr
<Selection
> selection
= mDomSelections
[index
];
1491 // XXXX Problem: Shift+click in browser is appending text selection to
1492 // selected table!!!
1493 // is this the place to erase selected cells ?????
1495 (selection
->GetDirection() == eDirNext
&&
1496 aContentEndOffset
> aContentOffset
) // didn't go far enough
1497 ? aContentEndOffset
// this will only redraw the diff
1499 selection
->Extend(&aNewFocus
, offset
);
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 // TODO: Layout should use `uint32_t` for handling offset in DOM nodes
1522 // (for example: bug 1735262)
1523 MOZ_ASSERT(aContentOffset
>= 0);
1524 MOZ_ASSERT(aContentLength
>= 0);
1525 if (MOZ_UNLIKELY(aContentOffset
< 0) || MOZ_UNLIKELY(aContentLength
< 0)) {
1529 UniquePtr
<SelectionDetails
> details
;
1531 for (size_t j
= 0; j
< ArrayLength(mDomSelections
); j
++) {
1532 if (mDomSelections
[j
]) {
1533 details
= mDomSelections
[j
]->LookUpSelection(
1534 aContent
, static_cast<uint32_t>(aContentOffset
),
1535 static_cast<uint32_t>(aContentLength
), std::move(details
),
1536 kPresentSelectionTypes
[j
], aSlowCheck
);
1543 void nsFrameSelection::SetDragState(bool aState
) {
1544 if (mDragState
== aState
) return;
1546 mDragState
= aState
;
1549 mTableSelection
.mDragSelectingCells
= false;
1550 // Notify that reason is mouse up.
1551 SetChangeReasons(nsISelectionListener::MOUSEUP_REASON
);
1553 // flag is set to false in `NotifySelectionListeners`.
1554 // since this function call is part of click event, this would immediately
1555 // reset the flag, rendering it useless.
1556 AutoRestore
<bool> restoreIsDoubleClickSelectionFlag(
1557 mIsDoubleClickSelection
);
1558 // Be aware, the Selection instance may be destroyed after this call.
1559 NotifySelectionListeners(SelectionType::eNormal
);
1563 Selection
* nsFrameSelection::GetSelection(SelectionType aSelectionType
) const {
1564 int8_t index
= GetIndexFromSelectionType(aSelectionType
);
1565 if (index
< 0) return nullptr;
1567 return mDomSelections
[index
];
1570 nsresult
nsFrameSelection::ScrollSelectionIntoView(SelectionType aSelectionType
,
1571 SelectionRegion aRegion
,
1572 int16_t aFlags
) const {
1573 int8_t index
= GetIndexFromSelectionType(aSelectionType
);
1574 if (index
< 0) return NS_ERROR_INVALID_ARG
;
1576 if (!mDomSelections
[index
]) return NS_ERROR_NULL_POINTER
;
1578 ScrollAxis verticalScroll
= ScrollAxis();
1579 int32_t flags
= Selection::SCROLL_DO_FLUSH
;
1580 if (aFlags
& nsISelectionController::SCROLL_SYNCHRONOUS
) {
1581 flags
|= Selection::SCROLL_SYNCHRONOUS
;
1582 } else if (aFlags
& nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY
) {
1583 flags
|= Selection::SCROLL_FIRST_ANCESTOR_ONLY
;
1585 if (aFlags
& nsISelectionController::SCROLL_OVERFLOW_HIDDEN
) {
1586 flags
|= Selection::SCROLL_OVERFLOW_HIDDEN
;
1588 if (aFlags
& nsISelectionController::SCROLL_CENTER_VERTICALLY
) {
1590 ScrollAxis(kScrollToCenter
, WhenToScroll::IfNotFullyVisible
);
1592 if (aFlags
& nsISelectionController::SCROLL_FOR_CARET_MOVE
) {
1593 flags
|= Selection::SCROLL_FOR_CARET_MOVE
;
1596 // After ScrollSelectionIntoView(), the pending notifications might be
1597 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
1598 RefPtr
<Selection
> sel
= mDomSelections
[index
];
1599 return sel
->ScrollIntoView(aRegion
, verticalScroll
, ScrollAxis(), flags
);
1602 nsresult
nsFrameSelection::RepaintSelection(SelectionType aSelectionType
) {
1603 int8_t index
= GetIndexFromSelectionType(aSelectionType
);
1604 if (index
< 0) return NS_ERROR_INVALID_ARG
;
1605 if (!mDomSelections
[index
]) return NS_ERROR_NULL_POINTER
;
1606 NS_ENSURE_STATE(mPresShell
);
1608 // On macOS, update the selection cache to the new active selection
1609 // aka the current selection.
1611 // Check that we're in the an active window and, if this is Web content,
1612 // in the frontmost tab.
1613 Document
* doc
= mPresShell
->GetDocument();
1614 if (doc
&& IsInActiveTab(doc
) && aSelectionType
== SelectionType::eNormal
) {
1615 UpdateSelectionCacheOnRepaintSelection(mDomSelections
[index
]);
1618 return mDomSelections
[index
]->Repaint(mPresShell
->GetPresContext());
1621 static bool IsDisplayContents(const nsIContent
* aContent
) {
1622 return aContent
->IsElement() && aContent
->AsElement()->IsDisplayContents();
1625 bool nsFrameSelection::AdjustFrameForLineStart(nsIFrame
*& aFrame
,
1626 int32_t& aFrameOffset
) {
1627 if (!aFrame
->HasSignificantTerminalNewline()) {
1631 auto [start
, end
] = aFrame
->GetOffsets();
1632 if (aFrameOffset
!= end
) {
1636 nsIFrame
* nextSibling
= aFrame
->GetNextSibling();
1641 aFrame
= nextSibling
;
1642 std::tie(start
, end
) = aFrame
->GetOffsets();
1643 aFrameOffset
= start
;
1648 nsIFrame
* nsFrameSelection::GetFrameForNodeOffset(nsIContent
* aNode
,
1650 CaretAssociateHint aHint
,
1651 int32_t* aReturnOffset
) {
1652 if (!aNode
|| !aReturnOffset
) return nullptr;
1654 if (aOffset
< 0) return nullptr;
1656 if (!aNode
->GetPrimaryFrame() && !IsDisplayContents(aNode
)) {
1660 nsIFrame
* returnFrame
= nullptr;
1661 nsCOMPtr
<nsIContent
> theNode
;
1664 *aReturnOffset
= aOffset
;
1668 if (aNode
->IsElement()) {
1669 int32_t childIndex
= 0;
1670 int32_t numChildren
= theNode
->GetChildCount();
1672 if (aHint
== CARET_ASSOCIATE_BEFORE
) {
1674 childIndex
= aOffset
- 1;
1676 childIndex
= aOffset
;
1679 NS_ASSERTION(aHint
== CARET_ASSOCIATE_AFTER
, "unknown direction");
1680 if (aOffset
>= numChildren
) {
1681 if (numChildren
> 0) {
1682 childIndex
= numChildren
- 1;
1687 childIndex
= aOffset
;
1691 if (childIndex
> 0 || numChildren
> 0) {
1692 nsCOMPtr
<nsIContent
> childNode
=
1693 theNode
->GetChildAt_Deprecated(childIndex
);
1699 theNode
= childNode
;
1702 // Now that we have the child node, check if it too
1703 // can contain children. If so, descend into child.
1704 if (theNode
->IsElement() && theNode
->GetChildCount() &&
1705 !theNode
->HasIndependentSelection()) {
1707 aOffset
= aOffset
> childIndex
? theNode
->GetChildCount() : 0;
1710 // Check to see if theNode is a text node. If it is, translate
1711 // aOffset into an offset into the text node.
1713 RefPtr
<Text
> textNode
= theNode
->GetAsText();
1715 if (theNode
->GetPrimaryFrame()) {
1716 if (aOffset
> childIndex
) {
1717 uint32_t textLength
= textNode
->Length();
1719 *aReturnOffset
= (int32_t)textLength
;
1724 int32_t numChildren
= aNode
->GetChildCount();
1725 int32_t newChildIndex
= aHint
== CARET_ASSOCIATE_BEFORE
1729 if (newChildIndex
>= 0 && newChildIndex
< numChildren
) {
1730 nsCOMPtr
<nsIContent
> newChildNode
=
1731 aNode
->GetChildAt_Deprecated(newChildIndex
);
1732 if (!newChildNode
) {
1736 aNode
= newChildNode
;
1738 aHint
== CARET_ASSOCIATE_BEFORE
? aNode
->GetChildCount() : 0;
1741 // newChildIndex is illegal which means we're at first or last
1742 // child. Just use original node to get the frame.
1750 // If the node is a ShadowRoot, the frame needs to be adjusted,
1751 // because a ShadowRoot does not get a frame. Its children are rendered
1752 // as children of the host.
1753 if (ShadowRoot
* shadow
= ShadowRoot::FromNode(theNode
)) {
1754 theNode
= shadow
->GetHost();
1757 returnFrame
= theNode
->GetPrimaryFrame();
1759 if (aHint
== CARET_ASSOCIATE_BEFORE
) {
1767 int32_t end
= theNode
->GetChildCount();
1768 if (aOffset
< end
) {
1780 if (!returnFrame
) return nullptr;
1782 // If we ended up here and were asked to position the caret after a visible
1783 // break, let's return the frame on the next line instead if it exists.
1784 if (aOffset
> 0 && (uint32_t)aOffset
>= aNode
->Length() &&
1785 theNode
== aNode
->GetLastChild()) {
1787 nsLayoutUtils::IsInvisibleBreak(theNode
, &newFrame
);
1789 returnFrame
= newFrame
;
1794 // find the child frame containing the offset we want
1795 returnFrame
->GetChildFrameContainingOffset(
1796 *aReturnOffset
, aHint
== CARET_ASSOCIATE_AFTER
, &aOffset
, &returnFrame
);
1800 nsIFrame
* nsFrameSelection::GetFrameToPageSelect() const {
1801 if (NS_WARN_IF(!mPresShell
)) {
1805 nsIFrame
* rootFrameToSelect
;
1806 if (mLimiters
.mLimiter
) {
1807 rootFrameToSelect
= mLimiters
.mLimiter
->GetPrimaryFrame();
1808 if (NS_WARN_IF(!rootFrameToSelect
)) {
1811 } else if (mLimiters
.mAncestorLimiter
) {
1812 rootFrameToSelect
= mLimiters
.mAncestorLimiter
->GetPrimaryFrame();
1813 if (NS_WARN_IF(!rootFrameToSelect
)) {
1817 rootFrameToSelect
= mPresShell
->GetRootScrollFrame();
1818 if (NS_WARN_IF(!rootFrameToSelect
)) {
1823 nsCOMPtr
<nsIContent
> contentToSelect
= mPresShell
->GetContentForScrolling();
1824 if (contentToSelect
) {
1825 // If there is selected content, look for nearest and vertical scrollable
1826 // parent under the root frame.
1827 for (nsIFrame
* frame
= contentToSelect
->GetPrimaryFrame();
1828 frame
&& frame
!= rootFrameToSelect
; frame
= frame
->GetParent()) {
1829 nsIScrollableFrame
* scrollableFrame
= do_QueryFrame(frame
);
1830 if (!scrollableFrame
) {
1833 ScrollStyles scrollStyles
= scrollableFrame
->GetScrollStyles();
1834 if (scrollStyles
.mVertical
== StyleOverflow::Hidden
) {
1837 layers::ScrollDirections directions
=
1838 scrollableFrame
->GetAvailableScrollingDirections();
1839 if (directions
.contains(layers::ScrollDirection::eVertical
)) {
1840 // If there is sub scrollable frame, let's use its page size to select.
1845 // Otherwise, i.e., there is no scrollable frame or only the root frame is
1846 // scrollable, let's return the root frame because Shift + PageUp/PageDown
1847 // should expand the selection in the root content even if it's not
1849 return rootFrameToSelect
;
1852 nsresult
nsFrameSelection::PageMove(bool aForward
, bool aExtend
,
1854 SelectionIntoView aSelectionIntoView
) {
1857 // expected behavior for PageMove is to scroll AND move the caret
1858 // and remain relative position of the caret in view. see Bug 4302.
1860 // Get the scrollable frame. If aFrame is not scrollable, this is nullptr.
1861 nsIScrollableFrame
* scrollableFrame
= aFrame
->GetScrollTargetFrame();
1862 // Get the scrolled frame. If aFrame is not scrollable, this is aFrame
1864 nsIFrame
* scrolledFrame
=
1865 scrollableFrame
? scrollableFrame
->GetScrolledFrame() : aFrame
;
1866 if (!scrolledFrame
) {
1870 // find out where the caret is.
1871 // we should know mDesiredCaretPos.mValue value of nsFrameSelection, but I
1872 // havent seen that behavior in other windows applications yet.
1873 RefPtr
<Selection
> selection
= GetSelection(SelectionType::eNormal
);
1879 nsIFrame
* caretFrame
= nsCaret::GetGeometry(selection
, &caretPos
);
1884 // If the scrolled frame is outside of current selection limiter,
1885 // we need to scroll the frame but keep moving selection in the limiter.
1886 nsIFrame
* frameToClick
= scrolledFrame
;
1887 if (!IsValidSelectionPoint(scrolledFrame
->GetContent())) {
1888 frameToClick
= GetFrameToPageSelect();
1889 if (NS_WARN_IF(!frameToClick
)) {
1894 if (scrollableFrame
) {
1895 // If there is a scrollable frame, adjust pseudo-click position with page
1897 // XXX This may scroll more than one page if ScrollSelectionIntoView is
1898 // called later because caret may not fully visible. E.g., if
1899 // clicking line will be visible only half height with scrolling
1900 // the frame, ScrollSelectionIntoView additionally scrolls to show
1901 // the caret entirely.
1903 caretPos
.y
+= scrollableFrame
->GetPageScrollAmount().height
;
1905 caretPos
.y
-= scrollableFrame
->GetPageScrollAmount().height
;
1908 // Otherwise, adjust pseudo-click position with the frame size.
1910 caretPos
.y
+= frameToClick
->GetSize().height
;
1912 caretPos
.y
-= frameToClick
->GetSize().height
;
1916 caretPos
+= caretFrame
->GetOffsetTo(frameToClick
);
1918 // get a content at desired location
1919 nsPoint desiredPoint
;
1920 desiredPoint
.x
= caretPos
.x
;
1921 desiredPoint
.y
= caretPos
.y
+ caretPos
.height
/ 2;
1922 nsIFrame::ContentOffsets offsets
=
1923 frameToClick
->GetContentOffsetsFromPoint(desiredPoint
);
1925 if (!offsets
.content
) {
1926 // XXX Do we need to handle ScrollSelectionIntoView in this case?
1930 // First, place the caret.
1931 bool selectionChanged
;
1933 // We don't want any script to run until we check whether selection is
1934 // modified by HandleClick.
1935 SelectionBatcher
ensureNoSelectionChangeNotifications(selection
,
1938 RangeBoundary oldAnchor
= selection
->AnchorRef();
1939 RangeBoundary oldFocus
= selection
->FocusRef();
1941 const FocusMode focusMode
=
1942 aExtend
? FocusMode::kExtendSelection
: FocusMode::kCollapseToNewPoint
;
1943 HandleClick(MOZ_KnownLive(offsets
.content
) /* bug 1636889 */,
1944 offsets
.offset
, offsets
.offset
, focusMode
,
1945 CARET_ASSOCIATE_AFTER
);
1947 selectionChanged
= selection
->AnchorRef() != oldAnchor
||
1948 selection
->FocusRef() != oldFocus
;
1951 bool doScrollSelectionIntoView
= !(
1952 aSelectionIntoView
== SelectionIntoView::IfChanged
&& !selectionChanged
);
1954 // Then, scroll the given frame one page.
1955 if (scrollableFrame
) {
1956 // If we'll call ScrollSelectionIntoView later and selection wasn't
1957 // changed and we scroll outside of selection limiter, we shouldn't use
1958 // smooth scroll here because nsIScrollableFrame uses normal runnable,
1959 // but ScrollSelectionIntoView uses early runner and it cancels the
1960 // pending smooth scroll. Therefore, if we used smooth scroll in such
1961 // case, ScrollSelectionIntoView would scroll to show caret instead of
1962 // page scroll of an element outside selection limiter.
1963 ScrollMode scrollMode
= doScrollSelectionIntoView
&& !selectionChanged
&&
1964 scrolledFrame
!= frameToClick
1965 ? ScrollMode::Instant
1966 : ScrollMode::Smooth
;
1967 scrollableFrame
->ScrollBy(nsIntPoint(0, aForward
? 1 : -1),
1968 ScrollUnit::PAGES
, scrollMode
);
1971 // Finally, scroll selection into view if requested.
1972 if (!doScrollSelectionIntoView
) {
1975 return ScrollSelectionIntoView(
1976 SelectionType::eNormal
, nsISelectionController::SELECTION_FOCUS_REGION
,
1977 nsISelectionController::SCROLL_SYNCHRONOUS
|
1978 nsISelectionController::SCROLL_FOR_CARET_MOVE
);
1981 nsresult
nsFrameSelection::PhysicalMove(int16_t aDirection
, int16_t aAmount
,
1983 NS_ENSURE_STATE(mPresShell
);
1984 // Flush out layout, since we need it to be up to date to do caret
1986 OwningNonNull
<PresShell
> presShell(*mPresShell
);
1987 presShell
->FlushPendingNotifications(FlushType::Layout
);
1993 // Check that parameters are safe
1994 if (aDirection
< 0 || aDirection
> 3 || aAmount
< 0 || aAmount
> 1) {
1995 return NS_ERROR_FAILURE
;
1998 nsPresContext
* context
= mPresShell
->GetPresContext();
2000 return NS_ERROR_FAILURE
;
2003 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
2004 RefPtr
<Selection
> sel
= mDomSelections
[index
];
2006 return NS_ERROR_NULL_POINTER
;
2009 // Map the abstract movement amounts (0-1) to direction-specific
2011 static const nsSelectionAmount inlineAmount
[] = {eSelectCluster
, eSelectWord
};
2012 static const nsSelectionAmount blockPrevAmount
[] = {eSelectLine
,
2014 static const nsSelectionAmount blockNextAmount
[] = {eSelectLine
,
2017 struct PhysicalToLogicalMapping
{
2018 nsDirection direction
;
2019 const nsSelectionAmount
* amounts
;
2021 static const PhysicalToLogicalMapping verticalLR
[4] = {
2022 {eDirPrevious
, blockPrevAmount
}, // left
2023 {eDirNext
, blockNextAmount
}, // right
2024 {eDirPrevious
, inlineAmount
}, // up
2025 {eDirNext
, inlineAmount
} // down
2027 static const PhysicalToLogicalMapping verticalRL
[4] = {
2028 {eDirNext
, blockNextAmount
},
2029 {eDirPrevious
, blockPrevAmount
},
2030 {eDirPrevious
, inlineAmount
},
2031 {eDirNext
, inlineAmount
}};
2032 static const PhysicalToLogicalMapping horizontal
[4] = {
2033 {eDirPrevious
, inlineAmount
},
2034 {eDirNext
, inlineAmount
},
2035 {eDirPrevious
, blockPrevAmount
},
2036 {eDirNext
, blockNextAmount
}};
2039 nsIFrame
* frame
= sel
->GetPrimaryFrameForFocusNode(true);
2041 if (!frame
->Style()->IsTextCombined()) {
2042 wm
= frame
->GetWritingMode();
2044 // Using different direction for horizontal-in-vertical would
2045 // make it hard to navigate via keyboard. Inherit the moving
2046 // direction from its parent.
2047 MOZ_ASSERT(frame
->IsTextFrame());
2048 wm
= frame
->GetParent()->GetWritingMode();
2049 MOZ_ASSERT(wm
.IsVertical(),
2051 "can only appear in vertical text");
2055 const PhysicalToLogicalMapping
& mapping
=
2057 ? wm
.IsVerticalLR() ? verticalLR
[aDirection
] : verticalRL
[aDirection
]
2058 : horizontal
[aDirection
];
2061 MoveCaret(mapping
.direction
, aExtend
, mapping
.amounts
[aAmount
], eVisual
);
2062 if (NS_FAILED(rv
)) {
2063 // If we tried to do a line move, but couldn't move in the given direction,
2064 // then we'll "promote" this to a line-edge move instead.
2065 if (mapping
.amounts
[aAmount
] == eSelectLine
) {
2066 rv
= MoveCaret(mapping
.direction
, aExtend
, mapping
.amounts
[aAmount
+ 1],
2069 // And if it was a next-word move that failed (which can happen when
2070 // eat_space_to_next_word is true, see bug 1153237), then just move forward
2071 // to the line-edge.
2072 else if (mapping
.amounts
[aAmount
] == eSelectWord
&&
2073 mapping
.direction
== eDirNext
) {
2074 rv
= MoveCaret(eDirNext
, aExtend
, eSelectEndLine
, eVisual
);
2081 nsresult
nsFrameSelection::CharacterMove(bool aForward
, bool aExtend
) {
2082 return MoveCaret(aForward
? eDirNext
: eDirPrevious
, aExtend
, eSelectCluster
,
2086 nsresult
nsFrameSelection::WordMove(bool aForward
, bool aExtend
) {
2087 return MoveCaret(aForward
? eDirNext
: eDirPrevious
, aExtend
, eSelectWord
,
2091 nsresult
nsFrameSelection::LineMove(bool aForward
, bool aExtend
) {
2092 return MoveCaret(aForward
? eDirNext
: eDirPrevious
, aExtend
, eSelectLine
,
2096 nsresult
nsFrameSelection::IntraLineMove(bool aForward
, bool aExtend
) {
2098 return MoveCaret(eDirNext
, aExtend
, eSelectEndLine
, eLogical
);
2100 return MoveCaret(eDirPrevious
, aExtend
, eSelectBeginLine
, eLogical
);
2104 template <typename RangeType
>
2105 Result
<RefPtr
<RangeType
>, nsresult
>
2106 nsFrameSelection::CreateRangeExtendedToSomewhere(
2107 nsDirection aDirection
, const nsSelectionAmount aAmount
,
2108 CaretMovementStyle aMovementStyle
) {
2109 MOZ_ASSERT(aDirection
== eDirNext
|| aDirection
== eDirPrevious
);
2110 MOZ_ASSERT(aAmount
== eSelectCharacter
|| aAmount
== eSelectCluster
||
2111 aAmount
== eSelectWord
|| aAmount
== eSelectBeginLine
||
2112 aAmount
== eSelectEndLine
);
2113 MOZ_ASSERT(aMovementStyle
== eLogical
|| aMovementStyle
== eVisual
||
2114 aMovementStyle
== eUsePrefStyle
);
2117 return Err(NS_ERROR_UNEXPECTED
);
2119 OwningNonNull
<PresShell
> presShell(*mPresShell
);
2120 presShell
->FlushPendingNotifications(FlushType::Layout
);
2122 return Err(NS_ERROR_FAILURE
);
2124 Selection
* selection
=
2125 mDomSelections
[GetIndexFromSelectionType(SelectionType::eNormal
)];
2126 if (!selection
|| selection
->RangeCount() != 1) {
2127 return Err(NS_ERROR_FAILURE
);
2129 RefPtr
<const nsRange
> firstRange
= selection
->GetRangeAt(0);
2130 if (!firstRange
|| !firstRange
->IsPositioned()) {
2131 return Err(NS_ERROR_FAILURE
);
2133 Result
<nsPeekOffsetStruct
, nsresult
> result
= PeekOffsetForCaretMove(
2134 aDirection
, true, aAmount
, aMovementStyle
, nsPoint(0, 0));
2135 if (result
.isErr()) {
2136 return Err(NS_ERROR_FAILURE
);
2138 const nsPeekOffsetStruct
& pos
= result
.inspect();
2139 RefPtr
<RangeType
> range
;
2140 if (NS_WARN_IF(!pos
.mResultContent
)) {
2143 if (aDirection
== eDirPrevious
) {
2144 range
= RangeType::Create(
2145 RawRangeBoundary(pos
.mResultContent
, pos
.mContentOffset
),
2146 firstRange
->EndRef(), IgnoreErrors());
2148 range
= RangeType::Create(
2149 firstRange
->StartRef(),
2150 RawRangeBoundary(pos
.mResultContent
, pos
.mContentOffset
),
2156 //////////END FRAMESELECTION
2158 LazyLogModule
gBatchLog("SelectionBatch");
2160 void nsFrameSelection::StartBatchChanges(const char* aRequesterFuncName
) {
2161 MOZ_LOG(gBatchLog
, LogLevel::Info
,
2162 ("%p%snsFrameSelection::StartBatchChanges(%s)", this,
2163 std::string((mBatching
.mCounter
+ 1) * 2, ' ').c_str(),
2164 aRequesterFuncName
));
2165 mBatching
.mCounter
++;
2168 void nsFrameSelection::EndBatchChanges(const char* aRequesterFuncName
,
2170 MOZ_LOG(gBatchLog
, LogLevel::Info
,
2171 ("%p%snsFrameSelection::EndBatchChanges (%s, %s)", this,
2172 std::string(mBatching
.mCounter
* 2, ' ').c_str(), aRequesterFuncName
,
2173 SelectionChangeReasonsToCString(aReasons
).get()));
2174 MOZ_ASSERT(mBatching
.mCounter
> 0, "Bad mBatching.mCounter");
2175 mBatching
.mCounter
--;
2177 if (mBatching
.mCounter
== 0 && mBatching
.mChangesDuringBatching
) {
2178 AddChangeReasons(aReasons
);
2179 mCaretMoveAmount
= eSelectNoAmount
;
2180 mBatching
.mChangesDuringBatching
= false;
2181 // Be aware, the Selection instance may be destroyed after this call.
2182 NotifySelectionListeners(SelectionType::eNormal
);
2186 nsresult
nsFrameSelection::NotifySelectionListeners(
2187 SelectionType aSelectionType
) {
2188 int8_t index
= GetIndexFromSelectionType(aSelectionType
);
2189 if (index
>= 0 && mDomSelections
[index
]) {
2190 RefPtr
<Selection
> selection
= mDomSelections
[index
];
2191 selection
->NotifySelectionListeners();
2192 mCaretMoveAmount
= eSelectNoAmount
;
2195 return NS_ERROR_FAILURE
;
2198 // Start of Table Selection methods
2200 static bool IsCell(nsIContent
* aContent
) {
2201 return aContent
->IsAnyOfHTMLElements(nsGkAtoms::td
, nsGkAtoms::th
);
2205 nsITableCellLayout
* nsFrameSelection::GetCellLayout(
2206 const nsIContent
* aCellContent
) {
2207 nsITableCellLayout
* cellLayoutObject
=
2208 do_QueryFrame(aCellContent
->GetPrimaryFrame());
2209 return cellLayoutObject
;
2212 nsresult
nsFrameSelection::ClearNormalSelection() {
2213 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
2214 RefPtr
<Selection
> selection
= mDomSelections
[index
];
2216 return NS_ERROR_NULL_POINTER
;
2220 selection
->RemoveAllRanges(err
);
2221 return err
.StealNSResult();
2224 static nsIContent
* GetFirstSelectedContent(const nsRange
* aRange
) {
2229 MOZ_ASSERT(aRange
->GetStartContainer(), "Must have start parent!");
2230 MOZ_ASSERT(aRange
->GetStartContainer()->IsElement(), "Unexpected parent");
2232 return aRange
->GetChildAtStartOffset();
2235 // Table selection support.
2236 // TODO: Separate table methods into a separate nsITableSelection interface
2237 nsresult
nsFrameSelection::HandleTableSelection(nsINode
* aParentContent
,
2238 int32_t aContentOffset
,
2239 TableSelectionMode aTarget
,
2240 WidgetMouseEvent
* aMouseEvent
) {
2241 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
2242 RefPtr
<Selection
> selection
= mDomSelections
[index
];
2244 return NS_ERROR_NULL_POINTER
;
2247 return mTableSelection
.HandleSelection(aParentContent
, aContentOffset
,
2248 aTarget
, aMouseEvent
, mDragState
,
2252 nsresult
nsFrameSelection::TableSelection::HandleSelection(
2253 nsINode
* aParentContent
, int32_t aContentOffset
, TableSelectionMode aTarget
,
2254 WidgetMouseEvent
* aMouseEvent
, bool aDragState
,
2255 Selection
& aNormalSelection
) {
2256 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2258 NS_ENSURE_TRUE(aParentContent
, NS_ERROR_NULL_POINTER
);
2259 NS_ENSURE_TRUE(aMouseEvent
, NS_ERROR_NULL_POINTER
);
2261 if (aDragState
&& mDragSelectingCells
&&
2262 aTarget
== TableSelectionMode::Table
) {
2263 // We were selecting cells and user drags mouse in table border or inbetween
2269 RefPtr
<nsIContent
> childContent
=
2270 aParentContent
->GetChildAt_Deprecated(aContentOffset
);
2272 // When doing table selection, always set the direction to next so
2273 // we can be sure that anchorNode's offset always points to the
2275 aNormalSelection
.SetDirection(eDirNext
);
2277 // Stack-class to wrap all table selection changes in
2278 // BeginBatchChanges() / EndBatchChanges()
2279 SelectionBatcher
selectionBatcher(&aNormalSelection
, __FUNCTION__
);
2281 if (aDragState
&& mDragSelectingCells
) {
2282 return HandleDragSelecting(aTarget
, childContent
, aMouseEvent
,
2286 return HandleMouseUpOrDown(aTarget
, aDragState
, childContent
, aParentContent
,
2287 aContentOffset
, aMouseEvent
, aNormalSelection
);
2290 class nsFrameSelection::TableSelection::RowAndColumnRelation
{
2292 static Result
<RowAndColumnRelation
, nsresult
> Create(
2293 const nsIContent
* aFirst
, const nsIContent
* aSecond
) {
2294 RowAndColumnRelation result
;
2296 nsresult errorResult
=
2297 GetCellIndexes(aFirst
, result
.mFirst
.mRow
, result
.mFirst
.mColumn
);
2298 if (NS_FAILED(errorResult
)) {
2299 return Err(errorResult
);
2303 GetCellIndexes(aSecond
, result
.mSecond
.mRow
, result
.mSecond
.mColumn
);
2304 if (NS_FAILED(errorResult
)) {
2305 return Err(errorResult
);
2311 bool IsSameColumn() const { return mFirst
.mColumn
== mSecond
.mColumn
; }
2313 bool IsSameRow() const { return mFirst
.mRow
== mSecond
.mRow
; }
2316 RowAndColumnRelation() = default;
2318 struct RowAndColumn
{
2320 int32_t mColumn
= 0;
2323 RowAndColumn mFirst
;
2324 RowAndColumn mSecond
;
2327 nsresult
nsFrameSelection::TableSelection::HandleDragSelecting(
2328 TableSelectionMode aTarget
, nsIContent
* aChildContent
,
2329 const WidgetMouseEvent
* aMouseEvent
, Selection
& aNormalSelection
) {
2330 // We are drag-selecting
2331 if (aTarget
!= TableSelectionMode::Table
) {
2332 // If dragging in the same cell as last event, do nothing
2333 if (mEndSelectedCell
== aChildContent
) {
2337 #ifdef DEBUG_TABLE_SELECTION
2339 " mStartSelectedCell = %p, "
2340 "mEndSelectedCell = %p, aChildContent = %p "
2342 mStartSelectedCell
.get(), mEndSelectedCell
.get(), aChildContent
);
2344 // aTarget can be any "cell mode",
2345 // so we can easily drag-select rows and columns
2346 // Once we are in row or column mode,
2347 // we can drift into any cell to stay in that mode
2348 // even if aTarget = TableSelectionMode::Cell
2350 if (mMode
== TableSelectionMode::Row
||
2351 mMode
== TableSelectionMode::Column
) {
2352 if (mEndSelectedCell
) {
2353 Result
<RowAndColumnRelation
, nsresult
> rowAndColumnRelation
=
2354 RowAndColumnRelation::Create(mEndSelectedCell
, aChildContent
);
2356 if (rowAndColumnRelation
.isErr()) {
2357 return rowAndColumnRelation
.unwrapErr();
2360 if ((mMode
== TableSelectionMode::Row
&&
2361 rowAndColumnRelation
.inspect().IsSameRow()) ||
2362 (mMode
== TableSelectionMode::Column
&&
2363 rowAndColumnRelation
.inspect().IsSameColumn())) {
2367 #ifdef DEBUG_TABLE_SELECTION
2368 printf(" Dragged into a new column or row\n");
2370 // Continue dragging row or column selection
2372 return SelectRowOrColumn(aChildContent
, aNormalSelection
);
2374 if (mMode
== TableSelectionMode::Cell
) {
2375 #ifdef DEBUG_TABLE_SELECTION
2376 printf("HandleTableSelection: Dragged into a new cell\n");
2378 // Trick for quick selection of rows and columns
2379 // Hold down shift, then start selecting in one direction
2380 // If next cell dragged into is in same row, select entire row,
2381 // if next cell is in same column, select entire column
2382 if (mStartSelectedCell
&& aMouseEvent
->IsShift()) {
2383 Result
<RowAndColumnRelation
, nsresult
> rowAndColumnRelation
=
2384 RowAndColumnRelation::Create(mStartSelectedCell
, aChildContent
);
2385 if (rowAndColumnRelation
.isErr()) {
2386 return rowAndColumnRelation
.unwrapErr();
2389 if (rowAndColumnRelation
.inspect().IsSameRow() ||
2390 rowAndColumnRelation
.inspect().IsSameColumn()) {
2391 // Force new selection block
2392 mStartSelectedCell
= nullptr;
2393 aNormalSelection
.RemoveAllRanges(IgnoreErrors());
2395 if (rowAndColumnRelation
.inspect().IsSameRow()) {
2396 mMode
= TableSelectionMode::Row
;
2398 mMode
= TableSelectionMode::Column
;
2401 return SelectRowOrColumn(aChildContent
, aNormalSelection
);
2405 // Reselect block of cells to new end location
2406 return SelectBlockOfCells(mStartSelectedCell
, aChildContent
,
2410 // Do nothing if dragging in table, but outside a cell
2414 nsresult
nsFrameSelection::TableSelection::HandleMouseUpOrDown(
2415 TableSelectionMode aTarget
, bool aDragState
, nsIContent
* aChildContent
,
2416 nsINode
* aParentContent
, int32_t aContentOffset
,
2417 const WidgetMouseEvent
* aMouseEvent
, Selection
& aNormalSelection
) {
2418 nsresult result
= NS_OK
;
2419 // Not dragging -- mouse event is down or up
2421 #ifdef DEBUG_TABLE_SELECTION
2422 printf("HandleTableSelection: Mouse down event\n");
2424 // Clear cell we stored in mouse-down
2425 mUnselectCellOnMouseUp
= nullptr;
2427 if (aTarget
== TableSelectionMode::Cell
) {
2428 bool isSelected
= false;
2430 // Check if we have other selected cells
2431 nsIContent
* previousCellNode
=
2432 GetFirstSelectedContent(GetFirstCellRange(aNormalSelection
));
2433 if (previousCellNode
) {
2434 // We have at least 1 other selected cell
2436 // Check if new cell is already selected
2437 nsIFrame
* cellFrame
= aChildContent
->GetPrimaryFrame();
2439 return NS_ERROR_NULL_POINTER
;
2441 isSelected
= cellFrame
->IsSelected();
2443 // No cells selected -- remove non-cell selection
2444 aNormalSelection
.RemoveAllRanges(IgnoreErrors());
2446 mDragSelectingCells
= true; // Signal to start drag-cell-selection
2448 // Set start for new drag-selection block (not appended)
2449 mStartSelectedCell
= aChildContent
;
2450 // The initial block end is same as the start
2451 mEndSelectedCell
= aChildContent
;
2454 // Remember this cell to (possibly) unselect it on mouseup
2455 mUnselectCellOnMouseUp
= aChildContent
;
2456 #ifdef DEBUG_TABLE_SELECTION
2458 "HandleTableSelection: Saving "
2459 "mUnselectCellOnMouseUp\n");
2462 // Select an unselected cell
2463 // but first remove existing selection if not in same table
2464 if (previousCellNode
&&
2465 !IsInSameTable(previousCellNode
, aChildContent
)) {
2466 aNormalSelection
.RemoveAllRanges(IgnoreErrors());
2467 // Reset selection mode that is cleared in RemoveAllRanges
2471 return ::SelectCellElement(aChildContent
, aNormalSelection
);
2476 if (aTarget
== TableSelectionMode::Table
) {
2477 // TODO: We currently select entire table when clicked between cells,
2478 // should we restrict to only around border?
2479 // *** How do we get location data for cell and click?
2480 mDragSelectingCells
= false;
2481 mStartSelectedCell
= nullptr;
2482 mEndSelectedCell
= nullptr;
2484 // Remove existing selection and select the table
2485 aNormalSelection
.RemoveAllRanges(IgnoreErrors());
2486 return CreateAndAddRange(aParentContent
, aContentOffset
,
2489 if (aTarget
== TableSelectionMode::Row
||
2490 aTarget
== TableSelectionMode::Column
) {
2491 #ifdef DEBUG_TABLE_SELECTION
2492 printf("aTarget == %d\n", aTarget
);
2495 // Start drag-selecting mode so multiple rows/cols can be selected
2496 // Note: Currently, nsIFrame::GetDataForTableSelection
2497 // will never call us for row or column selection on mouse down
2498 mDragSelectingCells
= true;
2500 // Force new selection block
2501 mStartSelectedCell
= nullptr;
2502 aNormalSelection
.RemoveAllRanges(IgnoreErrors());
2503 // Always do this AFTER RemoveAllRanges
2506 return SelectRowOrColumn(aChildContent
, aNormalSelection
);
2509 #ifdef DEBUG_TABLE_SELECTION
2511 "HandleTableSelection: Mouse UP event. "
2512 "mDragSelectingCells=%d, "
2513 "mStartSelectedCell=%p\n",
2514 mDragSelectingCells
, mStartSelectedCell
.get());
2516 // First check if we are extending a block selection
2517 const uint32_t rangeCount
= aNormalSelection
.RangeCount();
2519 if (rangeCount
> 0 && aMouseEvent
->IsShift() && mAppendStartSelectedCell
&&
2520 mAppendStartSelectedCell
!= aChildContent
) {
2521 // Shift key is down: append a block selection
2522 mDragSelectingCells
= false;
2524 return SelectBlockOfCells(mAppendStartSelectedCell
, aChildContent
,
2528 if (mDragSelectingCells
) {
2529 mAppendStartSelectedCell
= mStartSelectedCell
;
2532 mDragSelectingCells
= false;
2533 mStartSelectedCell
= nullptr;
2534 mEndSelectedCell
= nullptr;
2536 // Any other mouseup actions require that Ctrl or Cmd key is pressed
2537 // else stop table selection mode
2538 bool doMouseUpAction
= false;
2540 doMouseUpAction
= aMouseEvent
->IsMeta();
2542 doMouseUpAction
= aMouseEvent
->IsControl();
2544 if (!doMouseUpAction
) {
2545 #ifdef DEBUG_TABLE_SELECTION
2547 "HandleTableSelection: Ending cell selection on mouseup: "
2548 "mAppendStartSelectedCell=%p\n",
2549 mAppendStartSelectedCell
.get());
2553 // Unselect a cell only if it wasn't
2554 // just selected on mousedown
2555 if (aChildContent
== mUnselectCellOnMouseUp
) {
2556 // Scan ranges to find the cell to unselect (the selection range to
2558 // XXXbz it's really weird that this lives outside the loop, so once we
2559 // find one we keep looking at it even if we find no more cells...
2560 nsINode
* previousCellParent
= nullptr;
2561 #ifdef DEBUG_TABLE_SELECTION
2563 "HandleTableSelection: Unselecting "
2564 "mUnselectCellOnMouseUp; "
2568 for (const uint32_t i
: IntegerRange(rangeCount
)) {
2569 MOZ_ASSERT(aNormalSelection
.RangeCount() == rangeCount
);
2570 // Strong reference, because sometimes we want to remove
2571 // this range, and then we might be the only owner.
2572 RefPtr
<nsRange
> range
= aNormalSelection
.GetRangeAt(i
);
2573 if (MOZ_UNLIKELY(!range
)) {
2574 return NS_ERROR_NULL_POINTER
;
2577 nsINode
* container
= range
->GetStartContainer();
2579 return NS_ERROR_NULL_POINTER
;
2582 int32_t offset
= range
->StartOffset();
2583 // Be sure previous selection is a table cell
2584 nsIContent
* child
= range
->GetChildAtStartOffset();
2585 if (child
&& IsCell(child
)) {
2586 previousCellParent
= container
;
2589 // We're done if we didn't find parent of a previously-selected cell
2590 if (!previousCellParent
) {
2594 if (previousCellParent
== aParentContent
&& offset
== aContentOffset
) {
2595 // Cell is already selected
2596 if (rangeCount
== 1) {
2597 #ifdef DEBUG_TABLE_SELECTION
2598 printf("HandleTableSelection: Unselecting single selected cell\n");
2600 // This was the only cell selected.
2601 // Collapse to "normal" selection inside the cell
2602 mStartSelectedCell
= nullptr;
2603 mEndSelectedCell
= nullptr;
2604 mAppendStartSelectedCell
= nullptr;
2605 // TODO: We need a "Collapse to just before deepest child" routine
2606 // Even better, should we collapse to just after the LAST deepest
2608 // (i.e., at the end of the cell's contents)?
2609 return aNormalSelection
.CollapseInLimiter(aChildContent
, 0);
2611 #ifdef DEBUG_TABLE_SELECTION
2613 "HandleTableSelection: Removing cell from multi-cell "
2616 // Unselecting the start of previous block
2617 // XXX What do we use now!
2618 if (aChildContent
== mAppendStartSelectedCell
) {
2619 mAppendStartSelectedCell
= nullptr;
2622 // Deselect cell by removing its range from selection
2624 aNormalSelection
.RemoveRangeAndUnselectFramesAndNotifyListeners(
2626 return err
.StealNSResult();
2629 mUnselectCellOnMouseUp
= nullptr;
2635 nsresult
nsFrameSelection::TableSelection::SelectBlockOfCells(
2636 nsIContent
* aStartCell
, nsIContent
* aEndCell
, Selection
& aNormalSelection
) {
2637 NS_ENSURE_TRUE(aStartCell
, NS_ERROR_NULL_POINTER
);
2638 NS_ENSURE_TRUE(aEndCell
, NS_ERROR_NULL_POINTER
);
2639 mEndSelectedCell
= aEndCell
;
2641 nsresult result
= NS_OK
;
2643 // If new end cell is in a different table, do nothing
2644 const RefPtr
<const nsIContent
> table
= IsInSameTable(aStartCell
, aEndCell
);
2649 // Get starting and ending cells' location in the cellmap
2650 int32_t startRowIndex
, startColIndex
, endRowIndex
, endColIndex
;
2651 result
= GetCellIndexes(aStartCell
, startRowIndex
, startColIndex
);
2652 if (NS_FAILED(result
)) return result
;
2653 result
= GetCellIndexes(aEndCell
, endRowIndex
, endColIndex
);
2654 if (NS_FAILED(result
)) return result
;
2656 if (mDragSelectingCells
) {
2657 // Drag selecting: remove selected cells outside of new block limits
2658 // TODO: `UnselectCells`'s return value shouldn't be ignored.
2659 UnselectCells(table
, startRowIndex
, startColIndex
, endRowIndex
, endColIndex
,
2660 true, aNormalSelection
);
2663 // Note that we select block in the direction of user's mouse dragging,
2664 // which means start cell may be after the end cell in either row or column
2665 return AddCellsToSelection(table
, startRowIndex
, startColIndex
, endRowIndex
,
2666 endColIndex
, aNormalSelection
);
2669 nsresult
nsFrameSelection::TableSelection::UnselectCells(
2670 const nsIContent
* aTableContent
, int32_t aStartRowIndex
,
2671 int32_t aStartColumnIndex
, int32_t aEndRowIndex
, int32_t aEndColumnIndex
,
2672 bool aRemoveOutsideOfCellRange
, mozilla::dom::Selection
& aNormalSelection
) {
2673 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2675 nsTableWrapperFrame
* tableFrame
=
2676 do_QueryFrame(aTableContent
->GetPrimaryFrame());
2677 if (!tableFrame
) return NS_ERROR_FAILURE
;
2679 int32_t minRowIndex
= std::min(aStartRowIndex
, aEndRowIndex
);
2680 int32_t maxRowIndex
= std::max(aStartRowIndex
, aEndRowIndex
);
2681 int32_t minColIndex
= std::min(aStartColumnIndex
, aEndColumnIndex
);
2682 int32_t maxColIndex
= std::max(aStartColumnIndex
, aEndColumnIndex
);
2684 // Strong reference because we sometimes remove the range
2685 RefPtr
<nsRange
> range
= GetFirstCellRange(aNormalSelection
);
2686 nsIContent
* cellNode
= GetFirstSelectedContent(range
);
2687 MOZ_ASSERT(!range
|| cellNode
, "Must have cellNode if had a range");
2689 int32_t curRowIndex
, curColIndex
;
2691 nsresult result
= GetCellIndexes(cellNode
, curRowIndex
, curColIndex
);
2692 if (NS_FAILED(result
)) return result
;
2694 #ifdef DEBUG_TABLE_SELECTION
2695 if (!range
) printf("RemoveCellsToSelection -- range is null\n");
2699 if (aRemoveOutsideOfCellRange
) {
2700 if (curRowIndex
< minRowIndex
|| curRowIndex
> maxRowIndex
||
2701 curColIndex
< minColIndex
|| curColIndex
> maxColIndex
) {
2702 aNormalSelection
.RemoveRangeAndUnselectFramesAndNotifyListeners(
2703 *range
, IgnoreErrors());
2704 // Since we've removed the range, decrement pointer to next range
2705 mSelectedCellIndex
--;
2709 // Remove cell from selection if it belongs to the given cells range or
2710 // it is spanned onto the cells range.
2711 nsTableCellFrame
* cellFrame
=
2712 tableFrame
->GetCellFrameAt(curRowIndex
, curColIndex
);
2714 uint32_t origRowIndex
= cellFrame
->RowIndex();
2715 uint32_t origColIndex
= cellFrame
->ColIndex();
2716 uint32_t actualRowSpan
=
2717 tableFrame
->GetEffectiveRowSpanAt(origRowIndex
, origColIndex
);
2718 uint32_t actualColSpan
=
2719 tableFrame
->GetEffectiveColSpanAt(curRowIndex
, curColIndex
);
2720 if (origRowIndex
<= static_cast<uint32_t>(maxRowIndex
) &&
2722 origRowIndex
+ actualRowSpan
- 1 >=
2723 static_cast<uint32_t>(minRowIndex
) &&
2724 origColIndex
<= static_cast<uint32_t>(maxColIndex
) &&
2726 origColIndex
+ actualColSpan
- 1 >=
2727 static_cast<uint32_t>(minColIndex
)) {
2728 aNormalSelection
.RemoveRangeAndUnselectFramesAndNotifyListeners(
2729 *range
, IgnoreErrors());
2730 // Since we've removed the range, decrement pointer to next range
2731 mSelectedCellIndex
--;
2736 range
= GetNextCellRange(aNormalSelection
);
2737 cellNode
= GetFirstSelectedContent(range
);
2738 MOZ_ASSERT(!range
|| cellNode
, "Must have cellNode if had a range");
2744 nsresult
SelectCellElement(nsIContent
* aCellElement
,
2745 Selection
& aNormalSelection
) {
2746 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2748 nsIContent
* parent
= aCellElement
->GetParent();
2751 const int32_t offset
= parent
->ComputeIndexOf_Deprecated(aCellElement
);
2753 return CreateAndAddRange(parent
, offset
, aNormalSelection
);
2756 static nsresult
AddCellsToSelection(const nsIContent
* aTableContent
,
2757 int32_t aStartRowIndex
,
2758 int32_t aStartColumnIndex
,
2759 int32_t aEndRowIndex
,
2760 int32_t aEndColumnIndex
,
2761 Selection
& aNormalSelection
) {
2762 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2764 nsTableWrapperFrame
* tableFrame
=
2765 do_QueryFrame(aTableContent
->GetPrimaryFrame());
2766 if (!tableFrame
) { // Check that |table| is a table.
2767 return NS_ERROR_FAILURE
;
2770 nsresult result
= NS_OK
;
2771 uint32_t row
= aStartRowIndex
;
2773 uint32_t col
= aStartColumnIndex
;
2775 nsTableCellFrame
* cellFrame
= tableFrame
->GetCellFrameAt(row
, col
);
2777 // Skip cells that are spanned from previous locations or are already
2780 uint32_t origRow
= cellFrame
->RowIndex();
2781 uint32_t origCol
= cellFrame
->ColIndex();
2782 if (origRow
== row
&& origCol
== col
&& !cellFrame
->IsSelected()) {
2783 result
= SelectCellElement(cellFrame
->GetContent(), aNormalSelection
);
2784 if (NS_FAILED(result
)) {
2789 // Done when we reach end column
2790 if (col
== static_cast<uint32_t>(aEndColumnIndex
)) {
2794 if (aStartColumnIndex
< aEndColumnIndex
) {
2800 if (row
== static_cast<uint32_t>(aEndRowIndex
)) {
2804 if (aStartRowIndex
< aEndRowIndex
) {
2813 nsresult
nsFrameSelection::RemoveCellsFromSelection(nsIContent
* aTable
,
2814 int32_t aStartRowIndex
,
2815 int32_t aStartColumnIndex
,
2816 int32_t aEndRowIndex
,
2817 int32_t aEndColumnIndex
) {
2818 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
2819 const RefPtr
<mozilla::dom::Selection
> selection
= mDomSelections
[index
];
2821 return NS_ERROR_NULL_POINTER
;
2824 return mTableSelection
.UnselectCells(aTable
, aStartRowIndex
,
2825 aStartColumnIndex
, aEndRowIndex
,
2826 aEndColumnIndex
, false, *selection
);
2829 nsresult
nsFrameSelection::RestrictCellsToSelection(nsIContent
* aTable
,
2830 int32_t aStartRowIndex
,
2831 int32_t aStartColumnIndex
,
2832 int32_t aEndRowIndex
,
2833 int32_t aEndColumnIndex
) {
2834 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
2835 const RefPtr
<mozilla::dom::Selection
> selection
= mDomSelections
[index
];
2837 return NS_ERROR_NULL_POINTER
;
2840 return mTableSelection
.UnselectCells(aTable
, aStartRowIndex
,
2841 aStartColumnIndex
, aEndRowIndex
,
2842 aEndColumnIndex
, true, *selection
);
2845 Result
<nsFrameSelection::TableSelection::FirstAndLastCell
, nsresult
>
2846 nsFrameSelection::TableSelection::FindFirstAndLastCellOfRowOrColumn(
2847 const nsIContent
& aCellContent
) const {
2848 const nsIContent
* table
= GetParentTable(&aCellContent
);
2850 return Err(NS_ERROR_NULL_POINTER
);
2853 // Get table and cell layout interfaces to access
2854 // cell data based on cellmap location
2855 // Frames are not ref counted, so don't use an nsCOMPtr
2856 nsTableWrapperFrame
* tableFrame
= do_QueryFrame(table
->GetPrimaryFrame());
2858 return Err(NS_ERROR_FAILURE
);
2860 nsITableCellLayout
* cellLayout
= GetCellLayout(&aCellContent
);
2862 return Err(NS_ERROR_FAILURE
);
2865 // Get location of target cell:
2866 int32_t rowIndex
, colIndex
;
2867 nsresult result
= cellLayout
->GetCellIndexes(rowIndex
, colIndex
);
2868 if (NS_FAILED(result
)) {
2872 // Be sure we start at proper beginning
2873 // (This allows us to select row or col given ANY cell!)
2874 if (mMode
== TableSelectionMode::Row
) {
2877 if (mMode
== TableSelectionMode::Column
) {
2881 FirstAndLastCell firstAndLastCell
;
2883 // Loop through all cells in column or row to find first and last
2884 nsCOMPtr
<nsIContent
> curCellContent
=
2885 tableFrame
->GetCellAt(rowIndex
, colIndex
);
2886 if (!curCellContent
) {
2890 if (!firstAndLastCell
.mFirst
) {
2891 firstAndLastCell
.mFirst
= curCellContent
;
2894 firstAndLastCell
.mLast
= std::move(curCellContent
);
2896 // Move to next cell in cellmap, skipping spanned locations
2897 if (mMode
== TableSelectionMode::Row
) {
2898 colIndex
+= tableFrame
->GetEffectiveRowSpanAt(rowIndex
, colIndex
);
2900 rowIndex
+= tableFrame
->GetEffectiveRowSpanAt(rowIndex
, colIndex
);
2903 return firstAndLastCell
;
2906 nsresult
nsFrameSelection::TableSelection::SelectRowOrColumn(
2907 nsIContent
* aCellContent
, Selection
& aNormalSelection
) {
2908 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2910 if (!aCellContent
) {
2911 return NS_ERROR_NULL_POINTER
;
2914 Result
<FirstAndLastCell
, nsresult
> firstAndLastCell
=
2915 FindFirstAndLastCellOfRowOrColumn(*aCellContent
);
2916 if (firstAndLastCell
.isErr()) {
2917 return firstAndLastCell
.unwrapErr();
2920 // Use SelectBlockOfCells:
2921 // This will replace existing selection,
2922 // but allow unselecting by dragging out of selected region
2923 if (firstAndLastCell
.inspect().mFirst
&& firstAndLastCell
.inspect().mLast
) {
2926 if (!mStartSelectedCell
) {
2927 // We are starting a new block, so select the first cell
2928 rv
= ::SelectCellElement(firstAndLastCell
.inspect().mFirst
,
2930 if (NS_FAILED(rv
)) {
2933 mStartSelectedCell
= firstAndLastCell
.inspect().mFirst
;
2936 rv
= SelectBlockOfCells(mStartSelectedCell
,
2937 firstAndLastCell
.inspect().mLast
, aNormalSelection
);
2939 // This gets set to the cell at end of row/col,
2940 // but we need it to be the cell under cursor
2941 mEndSelectedCell
= aCellContent
;
2946 // This is a more efficient strategy that appends row to current selection,
2947 // but doesn't allow dragging OFF of an existing selection to unselect!
2949 // Loop through all cells in column or row
2950 result
= tableLayout
->GetCellDataAt(rowIndex
, colIndex
,
2951 getter_AddRefs(cellElement
),
2952 curRowIndex
, curColIndex
,
2954 actualRowSpan
, actualColSpan
,
2956 if (NS_FAILED(result
)) return result
;
2957 // We're done when cell is not found
2958 if (!cellElement
) break;
2961 // Check spans else we infinitely loop
2962 NS_ASSERTION(actualColSpan
, "actualColSpan is 0!");
2963 NS_ASSERTION(actualRowSpan
, "actualRowSpan is 0!");
2965 // Skip cells that are already selected or span from outside our region
2966 if (!isSelected
&& rowIndex
== curRowIndex
&& colIndex
== curColIndex
)
2968 result
= SelectCellElement(cellElement
);
2969 if (NS_FAILED(result
)) return result
;
2971 // Move to next row or column in cellmap, skipping spanned locations
2972 if (mMode
== TableSelectionMode::Row
)
2973 colIndex
+= actualColSpan
;
2975 rowIndex
+= actualRowSpan
;
2977 while (cellElement
);
2984 nsIContent
* nsFrameSelection::GetFirstCellNodeInRange(const nsRange
* aRange
) {
2985 if (!aRange
) return nullptr;
2987 nsIContent
* childContent
= aRange
->GetChildAtStartOffset();
2988 if (!childContent
) return nullptr;
2989 // Don't return node if not a cell
2990 if (!IsCell(childContent
)) return nullptr;
2992 return childContent
;
2995 nsRange
* nsFrameSelection::TableSelection::GetFirstCellRange(
2996 const mozilla::dom::Selection
& aNormalSelection
) {
2997 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
2999 nsRange
* firstRange
= aNormalSelection
.GetRangeAt(0);
3000 if (!GetFirstCellNodeInRange(firstRange
)) {
3004 // Setup for next cell
3005 mSelectedCellIndex
= 1;
3010 nsRange
* nsFrameSelection::TableSelection::GetNextCellRange(
3011 const mozilla::dom::Selection
& aNormalSelection
) {
3012 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
3015 aNormalSelection
.GetRangeAt(AssertedCast
<uint32_t>(mSelectedCellIndex
));
3017 // Get first node in next range of selection - test if it's a cell
3018 if (!GetFirstCellNodeInRange(range
)) {
3022 // Setup for next cell
3023 mSelectedCellIndex
++;
3029 nsresult
nsFrameSelection::GetCellIndexes(const nsIContent
* aCell
,
3031 int32_t& aColIndex
) {
3032 if (!aCell
) return NS_ERROR_NULL_POINTER
;
3034 aColIndex
= 0; // initialize out params
3037 nsITableCellLayout
* cellLayoutObject
= GetCellLayout(aCell
);
3038 if (!cellLayoutObject
) return NS_ERROR_FAILURE
;
3039 return cellLayoutObject
->GetCellIndexes(aRowIndex
, aColIndex
);
3043 nsIContent
* nsFrameSelection::IsInSameTable(const nsIContent
* aContent1
,
3044 const nsIContent
* aContent2
) {
3045 if (!aContent1
|| !aContent2
) return nullptr;
3047 nsIContent
* tableNode1
= GetParentTable(aContent1
);
3048 nsIContent
* tableNode2
= GetParentTable(aContent2
);
3050 // Must be in the same table. Note that we want to return false for
3051 // the test if both tables are null.
3052 return (tableNode1
== tableNode2
) ? tableNode1
: nullptr;
3056 nsIContent
* nsFrameSelection::GetParentTable(const nsIContent
* aCell
) {
3061 for (nsIContent
* parent
= aCell
->GetParent(); parent
;
3062 parent
= parent
->GetParent()) {
3063 if (parent
->IsHTMLElement(nsGkAtoms::table
)) {
3071 nsresult
nsFrameSelection::SelectCellElement(nsIContent
* aCellElement
) {
3072 const int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
3073 const RefPtr
<Selection
> selection
= mDomSelections
[index
];
3075 return NS_ERROR_NULL_POINTER
;
3078 return ::SelectCellElement(aCellElement
, *selection
);
3081 nsresult
CreateAndAddRange(nsINode
* aContainer
, int32_t aOffset
,
3082 Selection
& aNormalSelection
) {
3083 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
3086 return NS_ERROR_NULL_POINTER
;
3089 // Set range around child at given offset
3091 RefPtr
<nsRange
> range
=
3092 nsRange::Create(aContainer
, aOffset
, aContainer
, aOffset
+ 1, error
);
3093 if (NS_WARN_IF(error
.Failed())) {
3094 return error
.StealNSResult();
3099 aNormalSelection
.AddRangeAndSelectFramesAndNotifyListeners(*range
, err
);
3100 return err
.StealNSResult();
3103 // End of Table Selection
3105 void nsFrameSelection::SetAncestorLimiter(nsIContent
* aLimiter
) {
3106 if (mLimiters
.mAncestorLimiter
!= aLimiter
) {
3107 mLimiters
.mAncestorLimiter
= aLimiter
;
3108 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
3109 if (!mDomSelections
[index
]) return;
3111 if (!IsValidSelectionPoint(mDomSelections
[index
]->GetFocusNode())) {
3112 ClearNormalSelection();
3113 if (mLimiters
.mAncestorLimiter
) {
3114 SetChangeReasons(nsISelectionListener::NO_REASON
);
3115 nsCOMPtr
<nsIContent
> limiter(mLimiters
.mAncestorLimiter
);
3116 const nsresult rv
= TakeFocus(*limiter
, 0, 0, CARET_ASSOCIATE_BEFORE
,
3117 FocusMode::kCollapseToNewPoint
);
3118 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
3119 // TODO: in case of failure, propagate it to the callers.
3125 void nsFrameSelection::SetDelayedCaretData(WidgetMouseEvent
* aMouseEvent
) {
3127 mDelayedMouseEvent
.mIsValid
= true;
3128 mDelayedMouseEvent
.mIsShift
= aMouseEvent
->IsShift();
3129 mDelayedMouseEvent
.mClickCount
= aMouseEvent
->mClickCount
;
3131 mDelayedMouseEvent
.mIsValid
= false;
3135 void nsFrameSelection::DisconnectFromPresShell() {
3136 if (mAccessibleCaretEnabled
) {
3137 int8_t index
= GetIndexFromSelectionType(SelectionType::eNormal
);
3138 mDomSelections
[index
]->StopNotifyingAccessibleCaretEventHub();
3141 StopAutoScrollTimer();
3142 for (size_t i
= 0; i
< ArrayLength(mDomSelections
); i
++) {
3143 mDomSelections
[i
]->Clear(nullptr);
3145 mPresShell
= nullptr;
3152 * Update the selection cache on repaint to handle when a pre-existing
3153 * selection becomes active aka the current selection.
3155 * 1. Change the current selection by click n dragging another selection.
3156 * - Make a selection on content page. Make a selection in a text editor.
3157 * - You can click n drag the content selection to make it active again.
3158 * 2. Change the current selection when switching to a tab with a selection.
3159 * - Make selection in tab.
3160 * - Switching tabs will make its respective selection active.
3162 * Therefore, we only update the selection cache on a repaint
3163 * if the current selection being repainted is not an empty selection.
3165 * If the current selection is empty. The current selection cache
3166 * would be cleared by AutoCopyListener::OnSelectionChange().
3168 static nsresult
UpdateSelectionCacheOnRepaintSelection(Selection
* aSel
) {
3169 PresShell
* presShell
= aSel
->GetPresShell();
3173 nsCOMPtr
<Document
> aDoc
= presShell
->GetDocument();
3175 if (aDoc
&& aSel
&& !aSel
->IsCollapsed()) {
3176 return nsCopySupport::EncodeDocumentWithContextAndPutToClipboard(
3177 aSel
, aDoc
, nsIClipboard::kSelectionCache
, false);
3184 // mozilla::AutoCopyListener
3186 int16_t AutoCopyListener::sClipboardID
= -1;
3190 * On every selection change, we copy to the clipboard anew, creating a
3191 * HTML buffer, a transferable, an nsISupportsString and
3192 * a huge mess every time. This is basically what
3193 * nsCopySupport::EncodeDocumentWithContextAndPutToClipboard() does to move the
3194 * selection into the clipboard for Edit->Copy.
3196 * What we should do, to make our end of the deal faster:
3197 * Create a singleton transferable with our own magic converter. When selection
3198 * changes (use a quick cache to detect ``real'' changes), we put the new
3199 * Selection in the transferable. Our magic converter will take care of
3200 * transferable->whatever-other-format when the time comes to actually
3201 * hand over the clipboard contents.
3204 * - which X clipboard should we populate?
3205 * - should we use a different one than Edit->Copy, so that inadvertant
3206 * selections (or simple clicks, which currently cause a selection
3207 * notification, regardless of if they're in the document which currently has
3208 * selection!) don't lose the contents of the ``application''? Or should we
3209 * just put some intelligence in the ``is this a real selection?'' code to
3210 * protect our selection against clicks in other documents that don't create
3212 * - maybe we should just never clear the X clipboard? That would make this
3213 * problem just go away, which is very tempting.
3216 * nsIClipboard::kSelectionCache is the flag for current selection cache.
3217 * Set the current selection cache on the parent process in
3218 * widget cocoa nsClipboard whenever selection changes.
3222 void AutoCopyListener::OnSelectionChange(Document
* aDocument
,
3223 Selection
& aSelection
,
3225 MOZ_ASSERT(IsValidClipboardID(sClipboardID
));
3227 if (sClipboardID
== nsIClipboard::kSelectionCache
) {
3228 // Do nothing if this isn't in the active window and,
3229 // in the case of Web content, in the frontmost tab.
3230 if (!aDocument
|| !IsInActiveTab(aDocument
)) {
3235 static const int16_t kResasonsToHandle
=
3236 nsISelectionListener::MOUSEUP_REASON
|
3237 nsISelectionListener::SELECTALL_REASON
|
3238 nsISelectionListener::KEYPRESS_REASON
;
3239 if (!(aReason
& kResasonsToHandle
)) {
3240 return; // Don't care if we are still dragging.
3243 if (!aDocument
|| aSelection
.IsCollapsed()) {
3244 #ifdef DEBUG_CLIPBOARD
3245 fprintf(stderr
, "CLIPBOARD: no selection/collapsed selection\n");
3247 if (sClipboardID
!= nsIClipboard::kSelectionCache
) {
3248 // XXX Should we clear X clipboard?
3252 // If on macOS, clear the current selection transferable cached
3253 // on the parent process (nsClipboard) when the selection is empty.
3254 DebugOnly
<nsresult
> rv
= nsCopySupport::ClearSelectionCache();
3255 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3256 "nsCopySupport::ClearSelectionCache() failed");
3260 DebugOnly
<nsresult
> rv
=
3261 nsCopySupport::EncodeDocumentWithContextAndPutToClipboard(
3262 &aSelection
, aDocument
, sClipboardID
, false);
3263 NS_WARNING_ASSERTION(
3265 "nsCopySupport::EncodeDocumentWithContextAndPutToClipboard() failed");