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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef mozilla_Selection_h__
8 #define mozilla_Selection_h__
10 #include "mozilla/AutoRestore.h"
11 #include "mozilla/EventForwards.h"
12 #include "mozilla/PresShellForwards.h"
13 #include "mozilla/RangeBoundary.h"
14 #include "mozilla/SelectionChangeEventDispatcher.h"
15 #include "mozilla/UniquePtr.h"
16 #include "mozilla/WeakPtr.h"
17 #include "mozilla/dom/Highlight.h"
18 #include "mozilla/dom/StyledRange.h"
19 #include "nsDirection.h"
20 #include "nsISelectionController.h"
21 #include "nsISelectionListener.h"
23 #include "nsTArrayForwardDeclare.h"
24 #include "nsThreadUtils.h"
25 #include "nsWeakReference.h"
26 #include "nsWrapperCache.h"
28 struct CachedOffsetForFrame
;
31 class nsFrameSelection
;
32 class nsPIDOMWindowOuter
;
33 struct SelectionDetails
;
34 struct SelectionCustomColors
;
36 class nsHTMLCopyEncoder
;
42 class AccessibleCaretEventHub
;
45 class PostContentIterator
;
46 enum class CaretAssociationHint
;
47 enum class TableSelectionMode
: uint32_t;
48 struct AutoPrepareFocusRange
;
49 struct PrimaryFrameData
;
53 } // namespace mozilla
59 // Note, the ownership of mozilla::dom::Selection depends on which way the
60 // object is created. When nsFrameSelection has created Selection,
61 // addreffing/releasing the Selection object is aggregated to nsFrameSelection.
62 // Otherwise normal addref/release is used. This ensures that nsFrameSelection
63 // is never deleted before its Selections.
64 class Selection final
: public nsSupportsWeakReference
,
65 public nsWrapperCache
,
66 public SupportsWeakPtr
{
67 using AllowRangeCrossShadowBoundary
=
68 mozilla::dom::AllowRangeCrossShadowBoundary
;
75 * @param aFrameSelection can be nullptr.
77 explicit Selection(SelectionType aSelectionType
,
78 nsFrameSelection
* aFrameSelection
);
80 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
81 NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Selection
)
84 * Match this up with EndbatchChanges. will stop ui updates while multiple
85 * selection methods are called
87 * @param aDetails string to explian why this is called. This won't be
88 * stored nor exposed to selection listeners etc. Just for logging.
90 void StartBatchChanges(const char* aDetails
);
93 * Match this up with StartBatchChanges
95 * @param aDetails string to explian why this is called. This won't be
96 * stored nor exposed to selection listeners etc. Just for logging.
97 * @param aReasons potentially multiple of the reasons defined in
98 * nsISelectionListener.idl
100 void EndBatchChanges(const char* aDetails
,
101 int16_t aReason
= nsISelectionListener::NO_REASON
);
104 * NotifyAutoCopy() starts to notify AutoCopyListener of selection changes.
106 void NotifyAutoCopy() {
107 MOZ_ASSERT(mSelectionType
== SelectionType::eNormal
);
109 mNotifyAutoCopy
= true;
113 * MaybeNotifyAccessibleCaretEventHub() starts to notify
114 * AccessibleCaretEventHub of selection change if aPresShell has it.
116 void MaybeNotifyAccessibleCaretEventHub(PresShell
* aPresShell
);
119 * StopNotifyingAccessibleCaretEventHub() stops notifying
120 * AccessibleCaretEventHub of selection change.
122 void StopNotifyingAccessibleCaretEventHub();
125 * EnableSelectionChangeEvent() starts to notify
126 * SelectionChangeEventDispatcher of selection change to dispatch a
127 * selectionchange event at every selection change.
129 void EnableSelectionChangeEvent() {
130 if (!mSelectionChangeEventDispatcher
) {
131 mSelectionChangeEventDispatcher
= new SelectionChangeEventDispatcher();
135 // Required for WebIDL bindings, see
136 // https://developer.mozilla.org/en-US/docs/Mozilla/WebIDL_bindings#Adding_WebIDL_bindings_to_a_class.
137 Document
* GetParentObject() const;
139 DocGroup
* GetDocGroup() const;
141 // utility methods for scrolling the selection into view
142 nsPresContext
* GetPresContext() const;
143 PresShell
* GetPresShell() const;
144 nsFrameSelection
* GetFrameSelection() const { return mFrameSelection
; }
145 // Returns a rect containing the selection region, and frame that that
146 // position is relative to. For SELECTION_ANCHOR_REGION or
147 // SELECTION_FOCUS_REGION the rect is a zero-width rectangle. For
148 // SELECTION_WHOLE_SELECTION the rect contains both the anchor and focus
150 nsIFrame
* GetSelectionAnchorGeometry(SelectionRegion aRegion
, nsRect
* aRect
);
151 // Returns the position of the region (SELECTION_ANCHOR_REGION or
152 // SELECTION_FOCUS_REGION only), and frame that that position is relative to.
153 // The 'position' is a zero-width rectangle.
154 nsIFrame
* GetSelectionEndPointGeometry(SelectionRegion aRegion
,
157 nsresult
PostScrollSelectionIntoViewEvent(SelectionRegion aRegion
,
159 ScrollAxis aVertical
,
160 ScrollAxis aHorizontal
);
162 SCROLL_SYNCHRONOUS
= 1 << 1,
163 SCROLL_FIRST_ANCESTOR_ONLY
= 1 << 2,
165 1 << 3, // only matters if SCROLL_SYNCHRONOUS is passed too
166 SCROLL_OVERFLOW_HIDDEN
= 1 << 5,
167 SCROLL_FOR_CARET_MOVE
= 1 << 6
169 // If aFlags doesn't contain SCROLL_SYNCHRONOUS, then we'll flush when
170 // the scroll event fires so we make sure to scroll to the right place.
171 // Otherwise, if SCROLL_DO_FLUSH is also in aFlags, then this method will
172 // flush layout and you MUST hold a strong ref on 'this' for the duration
173 // of this call. This might destroy arbitrary layout objects.
174 MOZ_CAN_RUN_SCRIPT nsresult
175 ScrollIntoView(SelectionRegion aRegion
, ScrollAxis aVertical
= ScrollAxis(),
176 ScrollAxis aHorizontal
= ScrollAxis(), int32_t aFlags
= 0);
179 static bool IsUserSelectionCollapsed(
180 const nsRange
& aRange
, nsTArray
<RefPtr
<nsRange
>>& aTempRangesToAdd
);
182 * https://w3c.github.io/selection-api/#selectstart-event.
184 enum class DispatchSelectstartEvent
{
190 * See `AddRangesForSelectableNodes`.
192 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
AddRangesForUserSelectableNodes(
193 nsRange
* aRange
, Maybe
<size_t>* aOutIndex
,
194 const DispatchSelectstartEvent aDispatchSelectstartEvent
);
197 * Adds aRange to this Selection. If mUserInitiated is true,
198 * then aRange is first scanned for -moz-user-select:none nodes and split up
199 * into multiple ranges to exclude those before adding the resulting ranges
202 * @param aOutIndex points to the range last added, if at least one was added.
203 * If aRange is already contained, it points to the range
204 * containing it. Nothing() if mStyledRanges.mRanges was
205 * empty and no range was added.
207 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
AddRangesForSelectableNodes(
208 nsRange
* aRange
, Maybe
<size_t>* aOutIndex
,
209 DispatchSelectstartEvent aDispatchSelectstartEvent
);
211 already_AddRefed
<StaticRange
> GetComposedRange(
212 const AbstractRange
* aRange
,
213 const Sequence
<OwningNonNull
<ShadowRoot
>>& aShadowRoots
) const;
216 nsresult
RemoveCollapsedRanges();
217 void Clear(nsPresContext
* aPresContext
);
218 MOZ_CAN_RUN_SCRIPT nsresult
CollapseInLimiter(nsINode
* aContainer
,
221 return NS_ERROR_INVALID_ARG
;
223 return CollapseInLimiter(RawRangeBoundary(aContainer
, aOffset
));
225 MOZ_CAN_RUN_SCRIPT nsresult
226 CollapseInLimiter(const RawRangeBoundary
& aPoint
) {
228 CollapseInLimiter(aPoint
, result
);
229 return result
.StealNSResult();
231 MOZ_CAN_RUN_SCRIPT
void CollapseInLimiter(const RawRangeBoundary
& aPoint
,
234 MOZ_CAN_RUN_SCRIPT nsresult
Extend(nsINode
* aContainer
, uint32_t aOffset
);
237 * See mStyledRanges.mRanges.
239 nsRange
* GetRangeAt(uint32_t aIndex
) const;
242 * @brief Get the |AbstractRange| at |aIndex|.
244 * This method is safe to be called for every selection type.
245 * However, |StaticRange|s only occur for |SelectionType::eHighlight|.
246 * If the SelectionType may be eHighlight, this method must be called instead
249 * Returns null if |aIndex| is out of bounds.
251 AbstractRange
* GetAbstractRangeAt(uint32_t aIndex
) const;
252 // Get the anchor-to-focus range if we don't care which end is
253 // anchor and which end is focus.
254 const nsRange
* GetAnchorFocusRange() const { return mAnchorFocusRange
; }
256 void GetDirection(nsAString
& aDirection
) const;
258 nsDirection
GetDirection() const { return mDirection
; }
260 void SetDirection(nsDirection aDir
) { mDirection
= aDir
; }
261 MOZ_CAN_RUN_SCRIPT nsresult
SetAnchorFocusToRange(nsRange
* aRange
);
263 MOZ_CAN_RUN_SCRIPT
void ReplaceAnchorFocusRange(nsRange
* aRange
);
265 void AdjustAnchorFocusForMultiRange(nsDirection aDirection
);
267 nsIFrame
* GetPrimaryFrameForAnchorNode() const;
270 * Get primary frame and some other data for putting caret or extending
271 * selection at the focus point.
273 PrimaryFrameData
GetPrimaryFrameForCaretAtFocusNode(bool aVisual
) const;
275 UniquePtr
<SelectionDetails
> LookUpSelection(
276 nsIContent
* aContent
, uint32_t aContentOffset
, uint32_t aContentLength
,
277 UniquePtr
<SelectionDetails
> aDetailsHead
, SelectionType aSelectionType
,
280 NS_IMETHOD
Repaint(nsPresContext
* aPresContext
);
283 nsresult
StartAutoScrollTimer(nsIFrame
* aFrame
, const nsPoint
& aPoint
,
284 uint32_t aDelayInMs
);
286 nsresult
StopAutoScrollTimer();
288 JSObject
* WrapObject(JSContext
* aCx
,
289 JS::Handle
<JSObject
*> aGivenProto
) override
;
292 nsINode
* GetAnchorNode(CallerType aCallerType
= CallerType::System
) const {
293 const RangeBoundary
& anchor
= AnchorRef();
294 nsINode
* anchorNode
= anchor
.IsSet() ? anchor
.Container() : nullptr;
295 if (!anchorNode
|| aCallerType
== CallerType::System
||
296 !anchorNode
->ChromeOnlyAccess()) {
299 // anchor is nsIContent as ChromeOnlyAccess is nsIContent-only
300 return anchorNode
->AsContent()->FindFirstNonChromeOnlyAccessContent();
302 uint32_t AnchorOffset(CallerType aCallerType
= CallerType::System
) const {
303 const RangeBoundary
& anchor
= AnchorRef();
304 if (aCallerType
!= CallerType::System
&& anchor
.IsSet() &&
305 anchor
.Container()->ChromeOnlyAccess()) {
308 const Maybe
<uint32_t> offset
=
309 anchor
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
);
310 return offset
? *offset
: 0;
312 nsINode
* GetFocusNode(CallerType aCallerType
= CallerType::System
) const {
313 const RangeBoundary
& focus
= FocusRef();
314 nsINode
* focusNode
= focus
.IsSet() ? focus
.Container() : nullptr;
315 if (!focusNode
|| aCallerType
== CallerType::System
||
316 !focusNode
->ChromeOnlyAccess()) {
319 // focus is nsIContent as ChromeOnlyAccess is nsIContent-only
320 return focusNode
->AsContent()->FindFirstNonChromeOnlyAccessContent();
322 uint32_t FocusOffset(CallerType aCallerType
= CallerType::System
) const {
323 const RangeBoundary
& focus
= FocusRef();
324 if (aCallerType
!= CallerType::System
&& focus
.IsSet() &&
325 focus
.Container()->ChromeOnlyAccess()) {
328 const Maybe
<uint32_t> offset
=
329 focus
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
);
330 return offset
? *offset
: 0;
333 nsINode
* GetMayCrossShadowBoundaryAnchorNode() const {
334 const RangeBoundary
& anchor
= AnchorRef(AllowRangeCrossShadowBoundary::Yes
);
335 return anchor
.IsSet() ? anchor
.Container() : nullptr;
338 uint32_t MayCrossShadowBoundaryAnchorOffset() const {
339 const RangeBoundary
& anchor
= AnchorRef(AllowRangeCrossShadowBoundary::Yes
);
340 const Maybe
<uint32_t> offset
=
341 anchor
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
);
342 return offset
? *offset
: 0;
345 nsINode
* GetMayCrossShadowBoundaryFocusNode() const {
346 const RangeBoundary
& focus
= FocusRef(AllowRangeCrossShadowBoundary::Yes
);
347 return focus
.IsSet() ? focus
.Container() : nullptr;
350 uint32_t MayCrossShadowBoundaryFocusOffset() const {
351 const RangeBoundary
& focus
= FocusRef(AllowRangeCrossShadowBoundary::Yes
);
352 const Maybe
<uint32_t> offset
=
353 focus
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
);
354 return offset
? *offset
: 0;
357 nsIContent
* GetChildAtAnchorOffset() {
358 const RangeBoundary
& anchor
= AnchorRef();
359 return anchor
.IsSet() ? anchor
.GetChildAtOffset() : nullptr;
361 nsIContent
* GetChildAtFocusOffset() {
362 const RangeBoundary
& focus
= FocusRef();
363 return focus
.IsSet() ? focus
.GetChildAtOffset() : nullptr;
366 const RangeBoundary
& AnchorRef(
367 AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary
=
368 AllowRangeCrossShadowBoundary::No
) const;
369 const RangeBoundary
& FocusRef(
370 AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary
=
371 AllowRangeCrossShadowBoundary::No
) const;
374 * IsCollapsed -- is the whole selection just one point, or unset?
376 bool IsCollapsed() const {
377 size_t cnt
= mStyledRanges
.Length();
386 return mStyledRanges
.mRanges
[0].mRange
->Collapsed();
389 // Returns whether both normal range and cross-shadow-boundary
390 // range are collapsed.
392 // If StaticPrefs::dom_shadowdom_selection_across_boundary_enabled is
393 // disabled, this method always returns result as nsRange::IsCollapsed.
394 bool AreNormalAndCrossShadowBoundaryRangesCollapsed() const {
395 if (!IsCollapsed()) {
399 size_t cnt
= mStyledRanges
.Length();
404 AbstractRange
* range
= mStyledRanges
.mRanges
[0].mRange
;
406 range
->MayCrossShadowBoundary(),
407 !range
->AsDynamicRange()->CrossShadowBoundaryRangeCollapsed());
408 // Returns false if nsRange::mCrossBoundaryRange exists,
410 return !range
->MayCrossShadowBoundary();
413 // *JS() methods are mapped to Selection.*().
414 // They may move focus only when the range represents normal selection.
415 // These methods shouldn't be used by non-JS callers.
416 MOZ_CAN_RUN_SCRIPT
void CollapseJS(nsINode
* aContainer
, uint32_t aOffset
,
417 mozilla::ErrorResult
& aRv
);
418 MOZ_CAN_RUN_SCRIPT
void CollapseToStartJS(mozilla::ErrorResult
& aRv
);
419 MOZ_CAN_RUN_SCRIPT
void CollapseToEndJS(mozilla::ErrorResult
& aRv
);
421 MOZ_CAN_RUN_SCRIPT
void ExtendJS(nsINode
& aContainer
, uint32_t aOffset
,
422 mozilla::ErrorResult
& aRv
);
424 MOZ_CAN_RUN_SCRIPT
void SelectAllChildrenJS(nsINode
& aNode
,
425 mozilla::ErrorResult
& aRv
);
428 * Deletes this selection from document the nodes belong to.
429 * Only if this has `SelectionType::eNormal`.
431 MOZ_CAN_RUN_SCRIPT
void DeleteFromDocument(mozilla::ErrorResult
& aRv
);
433 uint32_t RangeCount() const { return mStyledRanges
.Length(); }
435 void GetType(nsAString
& aOutType
) const;
437 nsRange
* GetRangeAt(uint32_t aIndex
, mozilla::ErrorResult
& aRv
);
438 MOZ_CAN_RUN_SCRIPT
void AddRangeJS(nsRange
& aRange
,
439 mozilla::ErrorResult
& aRv
);
442 * Callers need to keep `aRange` alive.
444 MOZ_CAN_RUN_SCRIPT
void RemoveRangeAndUnselectFramesAndNotifyListeners(
445 AbstractRange
& aRange
, mozilla::ErrorResult
& aRv
);
447 MOZ_CAN_RUN_SCRIPT
void RemoveAllRanges(mozilla::ErrorResult
& aRv
);
449 void GetComposedRanges(
450 const Sequence
<OwningNonNull
<ShadowRoot
>>& aShadowRoots
,
451 nsTArray
<RefPtr
<StaticRange
>>& aComposedRanges
);
454 * Whether Stringify should flush layout or not.
456 enum class FlushFrames
{ No
, Yes
};
458 void Stringify(nsAString
& aResult
, FlushFrames
= FlushFrames::Yes
);
461 * Indicates whether the node is part of the selection. If partlyContained
462 * is true, the function returns true when some part of the node
463 * is part of the selection. If partlyContained is false, the
464 * function only returns true when the entire node is part of the selection.
466 bool ContainsNode(nsINode
& aNode
, bool aPartlyContained
,
467 mozilla::ErrorResult
& aRv
);
470 * Check to see if the given point is contained within the selection area. In
471 * particular, this iterates through all the rects that make up the selection,
472 * not just the bounding box, and checks to see if the given point is
473 * contained in any one of them.
474 * @param aPoint The point to check, relative to the root frame.
476 bool ContainsPoint(const nsPoint
& aPoint
);
479 * Modifies the selection. Note that the parameters are case-insensitive.
481 * @param alter can be one of { "move", "extend" }
482 * - "move" collapses the selection to the end of the selection and
483 * applies the movement direction/granularity to the collapsed
485 * - "extend" leaves the start of the selection unchanged, and applies
486 * movement direction/granularity to the end of the selection.
487 * @param direction can be one of { "forward", "backward", "left", "right" }
488 * @param granularity can be one of { "character", "word",
489 * "line", "lineboundary" }
491 * @throws NS_ERROR_NOT_IMPLEMENTED if the granularity is "sentence",
492 * "sentenceboundary", "paragraph", "paragraphboundary", or
493 * "documentboundary". Throws NS_ERROR_INVALID_ARG if alter, direction,
494 * or granularity has an unrecognized value.
496 MOZ_CAN_RUN_SCRIPT
void Modify(const nsAString
& aAlter
,
497 const nsAString
& aDirection
,
498 const nsAString
& aGranularity
,
499 mozilla::ErrorResult
& aRv
);
502 void SetBaseAndExtentJS(nsINode
& aAnchorNode
, uint32_t aAnchorOffset
,
503 nsINode
& aFocusNode
, uint32_t aFocusOffset
,
504 mozilla::ErrorResult
& aRv
);
506 bool GetInterlinePositionJS(mozilla::ErrorResult
& aRv
) const;
507 void SetInterlinePositionJS(bool aHintRight
, mozilla::ErrorResult
& aRv
);
509 enum class InterlinePosition
: uint8_t {
510 // Caret should be put at end of line (i.e., before the line break)
512 // Caret should be put at start of next line (i.e., after the line break)
514 // Undefined means only what is not EndOfLine nor StartOfNextLine.
515 // `SetInterlinePosition` should never be called with this value, and
516 // if `GetInterlinePosition` returns this, it means that the instance has
517 // not been initialized or cleared by the cycle collector or something.
518 // If a method needs to consider whether to call `SetInterlinePosition` or
519 // not call, this value can be used for the latter.
522 InterlinePosition
GetInterlinePosition() const;
523 nsresult
SetInterlinePosition(InterlinePosition aInterlinePosition
);
525 Nullable
<int16_t> GetCaretBidiLevel(mozilla::ErrorResult
& aRv
) const;
526 void SetCaretBidiLevel(const Nullable
<int16_t>& aCaretBidiLevel
,
527 mozilla::ErrorResult
& aRv
);
529 void ToStringWithFormat(const nsAString
& aFormatType
, uint32_t aFlags
,
530 int32_t aWrapColumn
, nsAString
& aReturn
,
531 mozilla::ErrorResult
& aRv
);
532 void AddSelectionListener(nsISelectionListener
* aListener
);
533 void RemoveSelectionListener(nsISelectionListener
* aListener
);
535 RawSelectionType
RawType() const {
536 return ToRawSelectionType(mSelectionType
);
538 SelectionType
Type() const { return mSelectionType
; }
541 * @brief Sets highlight selection properties.
543 * This includes the highlight name as well as its priority and type.
545 void SetHighlightSelectionData(
546 HighlightSelectionData aHighlightSelectionData
);
549 * See documentation of `GetRangesForInterval` in Selection.webidl.
551 * @param aReturn references, not copies, of the internal ranges.
553 void GetRangesForInterval(nsINode
& aBeginNode
, uint32_t aBeginOffset
,
554 nsINode
& aEndNode
, uint32_t aEndOffset
,
556 nsTArray
<RefPtr
<nsRange
>>& aReturn
,
559 MOZ_CAN_RUN_SCRIPT
void ScrollIntoView(int16_t aRegion
, bool aIsSynchronous
,
560 int16_t aVPercent
, int16_t aHPercent
,
563 void SetColors(const nsAString
& aForeColor
, const nsAString
& aBackColor
,
564 const nsAString
& aAltForeColor
, const nsAString
& aAltBackColor
,
570 * Non-JS callers should use the following
571 * collapse/collapseToStart/extend/etc methods, instead of the *JS
572 * versions that bindings call.
576 * Collapses the selection to a single point, at the specified offset
577 * in the given node. When the selection is collapsed, and the content
578 * is focused and editable, the caret will blink there.
579 * @param aContainer The given node where the selection will be set
580 * @param aOffset Where in given dom node to place the selection (the
581 * offset into the given node)
583 MOZ_CAN_RUN_SCRIPT
void CollapseInLimiter(nsINode
& aContainer
,
586 CollapseInLimiter(RawRangeBoundary(&aContainer
, aOffset
), aRv
);
590 enum class InLimiter
{
591 // If eYes, the method may reset selection limiter and move focus if the
592 // given range is out of the limiter.
594 // If eNo, the method won't reset selection limiter. So, if given range
595 // is out of bounds, the method may return error.
599 void CollapseInternal(InLimiter aInLimiter
, const RawRangeBoundary
& aPoint
,
604 * Collapses the whole selection to a single point at the start
605 * of the current selection (irrespective of direction). If content
606 * is focused and editable, the caret will blink there.
608 MOZ_CAN_RUN_SCRIPT
void CollapseToStart(mozilla::ErrorResult
& aRv
);
611 * Collapses the whole selection to a single point at the end
612 * of the current selection (irrespective of direction). If content
613 * is focused and editable, the caret will blink there.
615 MOZ_CAN_RUN_SCRIPT
void CollapseToEnd(mozilla::ErrorResult
& aRv
);
618 * Extends the selection by moving the selection end to the specified node and
619 * offset, preserving the selection begin position. The new selection end
620 * result will always be from the anchorNode to the new focusNode, regardless
623 * @param aContainer The node where the selection will be extended to
624 * @param aOffset Where in aContainer to place the offset of the new
627 MOZ_CAN_RUN_SCRIPT
void Extend(nsINode
& aContainer
, uint32_t aOffset
,
630 MOZ_CAN_RUN_SCRIPT
void AddRangeAndSelectFramesAndNotifyListeners(
631 nsRange
& aRange
, mozilla::ErrorResult
& aRv
);
633 MOZ_CAN_RUN_SCRIPT
void AddHighlightRangeAndSelectFramesAndNotifyListeners(
634 AbstractRange
& aRange
);
637 * Adds all children of the specified node to the selection.
638 * @param aNode the parent of the children to be added to the selection.
640 MOZ_CAN_RUN_SCRIPT
void SelectAllChildren(nsINode
& aNode
,
641 mozilla::ErrorResult
& aRv
);
644 * SetStartAndEnd() removes all ranges and sets new range as given range.
645 * Different from SetBaseAndExtent(), this won't compare the DOM points of
646 * aStartRef and aEndRef for performance nor set direction to eDirPrevious.
647 * Note that this may reset the limiter and move focus. If you don't want
648 * that, use SetStartAndEndInLimiter() instead.
651 void SetStartAndEnd(const RawRangeBoundary
& aStartRef
,
652 const RawRangeBoundary
& aEndRef
, ErrorResult
& aRv
);
654 void SetStartAndEnd(nsINode
& aStartContainer
, uint32_t aStartOffset
,
655 nsINode
& aEndContainer
, uint32_t aEndOffset
,
657 SetStartAndEnd(RawRangeBoundary(&aStartContainer
, aStartOffset
),
658 RawRangeBoundary(&aEndContainer
, aEndOffset
), aRv
);
662 * SetStartAndEndInLimiter() is similar to SetStartAndEnd(), but this respects
663 * the selection limiter. If all or part of given range is not in the
664 * limiter, this returns error.
667 void SetStartAndEndInLimiter(const RawRangeBoundary
& aStartRef
,
668 const RawRangeBoundary
& aEndRef
,
671 void SetStartAndEndInLimiter(nsINode
& aStartContainer
, uint32_t aStartOffset
,
672 nsINode
& aEndContainer
, uint32_t aEndOffset
,
674 SetStartAndEndInLimiter(RawRangeBoundary(&aStartContainer
, aStartOffset
),
675 RawRangeBoundary(&aEndContainer
, aEndOffset
), aRv
);
678 Result
<Ok
, nsresult
> SetStartAndEndInLimiter(
679 nsINode
& aStartContainer
, uint32_t aStartOffset
, nsINode
& aEndContainer
,
680 uint32_t aEndOffset
, nsDirection aDirection
, int16_t aReason
);
683 * SetBaseAndExtent() is alternative of the JS API for internal use.
684 * Different from SetStartAndEnd(), this sets anchor and focus points as
685 * specified, then if anchor point is after focus node, this sets the
686 * direction to eDirPrevious.
687 * Note that this may reset the limiter and move focus. If you don't want
688 * that, use SetBaseAndExtentInLimier() instead.
691 void SetBaseAndExtent(nsINode
& aAnchorNode
, uint32_t aAnchorOffset
,
692 nsINode
& aFocusNode
, uint32_t aFocusOffset
,
695 void SetBaseAndExtent(const RawRangeBoundary
& aAnchorRef
,
696 const RawRangeBoundary
& aFocusRef
, ErrorResult
& aRv
);
699 * SetBaseAndExtentInLimiter() is similar to SetBaseAndExtent(), but this
700 * respects the selection limiter. If all or part of given range is not in
701 * the limiter, this returns error.
704 void SetBaseAndExtentInLimiter(nsINode
& aAnchorNode
, uint32_t aAnchorOffset
,
705 nsINode
& aFocusNode
, uint32_t aFocusOffset
,
707 SetBaseAndExtentInLimiter(RawRangeBoundary(&aAnchorNode
, aAnchorOffset
),
708 RawRangeBoundary(&aFocusNode
, aFocusOffset
), aRv
);
711 void SetBaseAndExtentInLimiter(const RawRangeBoundary
& aAnchorRef
,
712 const RawRangeBoundary
& aFocusRef
,
715 void AddSelectionChangeBlocker();
716 void RemoveSelectionChangeBlocker();
717 bool IsBlockingSelectionChangeEvents() const;
719 // Whether this selection is focused in an editable element.
720 bool IsEditorSelection() const;
723 * Set the painting style for the range. The range must be a range in
724 * the selection. The textRangeStyle will be used by text frame
725 * when it is painting the selection.
727 nsresult
SetTextRangeStyle(nsRange
* aRange
,
728 const TextRangeStyle
& aTextRangeStyle
);
730 // Methods to manipulate our mFrameSelection's ancestor limiter.
731 nsIContent
* GetAncestorLimiter() const;
732 void SetAncestorLimiter(nsIContent
* aLimiter
);
735 * Frame Offset cache can be used just during calling
736 * nsEditor::EndPlaceHolderTransaction. EndPlaceHolderTransaction will give
737 * rise to reflow/refreshing view/scroll, and call times of
738 * nsTextFrame::GetPointFromOffset whose return value is to be cached. see
739 * bugs 35296 and 199412
741 void SetCanCacheFrameOffset(bool aCanCacheFrameOffset
);
743 // Selection::GetAbstractRangesForIntervalArray
745 // Fills a nsTArray with the ranges overlapping the range specified by
746 // the given endpoints. Ranges in the selection exactly adjacent to the
747 // input range are not returned unless aAllowAdjacent is set.
749 // For example, if the following ranges were in the selection
750 // (assume everything is within the same node)
752 // Start Offset: 0 2 7 9
753 // End Offset: 2 5 9 10
755 // and passed aBeginOffset of 2 and aEndOffset of 9, then with
756 // aAllowAdjacent set, all the ranges should be returned. If
757 // aAllowAdjacent was false, the ranges [2, 5] and [7, 9] only
758 // should be returned
760 // Now that overlapping ranges are disallowed, there can be a maximum of
762 nsresult
GetAbstractRangesForIntervalArray(nsINode
* aBeginNode
,
763 uint32_t aBeginOffset
,
767 nsTArray
<AbstractRange
*>* aRanges
);
770 * Converts the results of |GetAbstractRangesForIntervalArray()| to |nsRange|.
772 * |StaticRange|s can only occur in Selections of type |eHighlight|.
773 * Therefore, this method must not be called for this selection type
774 * as not every |AbstractRange| can be cast to |nsRange|.
776 nsresult
GetDynamicRangesForIntervalArray(
777 nsINode
* aBeginNode
, uint32_t aBeginOffset
, nsINode
* aEndNode
,
778 uint32_t aEndOffset
, bool aAllowAdjacent
, nsTArray
<nsRange
*>* aRanges
);
781 * Modifies the cursor Bidi level after a change in keyboard direction
782 * @param langRTL is true if the new language is right-to-left or
783 * false if the new language is left-to-right.
785 nsresult
SelectionLanguageChange(bool aLangRTL
);
788 bool HasSameRootOrSameComposedDoc(const nsINode
& aNode
);
790 // XXX Please don't add additional uses of this method, it's only for
791 // XXX supporting broken code (bug 1245883) in the following classes:
792 friend class ::nsCopySupport
;
793 friend class ::nsHTMLCopyEncoder
;
795 void AddRangeAndSelectFramesAndNotifyListenersInternal(nsRange
& aRange
,
799 // Get the cached value for nsTextFrame::GetPointFromOffset.
800 nsresult
GetCachedFrameOffset(nsIFrame
* aFrame
, int32_t inOffset
,
804 void SetStartAndEndInternal(InLimiter aInLimiter
,
805 const RawRangeBoundary
& aStartRef
,
806 const RawRangeBoundary
& aEndRef
,
807 nsDirection aDirection
, ErrorResult
& aRv
);
809 void SetBaseAndExtentInternal(InLimiter aInLimiter
,
810 const RawRangeBoundary
& aAnchorRef
,
811 const RawRangeBoundary
& aFocusRef
,
815 SelectionType
GetType() const { return mSelectionType
; }
817 SelectionCustomColors
* GetCustomColors() const { return mCustomColors
.get(); }
819 MOZ_CAN_RUN_SCRIPT
void NotifySelectionListeners(bool aCalledByJS
);
820 MOZ_CAN_RUN_SCRIPT
void NotifySelectionListeners();
822 friend struct AutoUserInitiated
;
823 struct MOZ_RAII AutoUserInitiated
{
824 explicit AutoUserInitiated(Selection
& aSelectionRef
)
825 : AutoUserInitiated(&aSelectionRef
) {}
826 explicit AutoUserInitiated(Selection
* aSelection
)
827 : mSavedValue(aSelection
->mUserInitiated
) {
828 aSelection
->mUserInitiated
= true;
830 AutoRestore
<bool> mSavedValue
;
834 friend struct mozilla::AutoPrepareFocusRange
;
835 class ScrollSelectionIntoViewEvent
;
836 friend class ScrollSelectionIntoViewEvent
;
838 class ScrollSelectionIntoViewEvent
: public Runnable
{
840 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_DECL_NSIRUNNABLE
842 ScrollSelectionIntoViewEvent(Selection
* aSelection
, SelectionRegion aRegion
,
843 ScrollAxis aVertical
, ScrollAxis aHorizontal
,
845 : Runnable("dom::Selection::ScrollSelectionIntoViewEvent"),
846 mSelection(aSelection
),
848 mVerticalScroll(aVertical
),
849 mHorizontalScroll(aHorizontal
),
851 NS_ASSERTION(aSelection
, "null parameter");
853 void Revoke() { mSelection
= nullptr; }
856 Selection
* mSelection
;
857 SelectionRegion mRegion
;
858 ScrollAxis mVerticalScroll
;
859 ScrollAxis mHorizontalScroll
;
864 * Set mAnchorFocusRange to mStyledRanges.mRanges[aIndex] if aIndex is a valid
867 void SetAnchorFocusRange(size_t aIndex
);
868 void RemoveAnchorFocusRange() { mAnchorFocusRange
= nullptr; }
869 void SelectFramesOf(nsIContent
* aContent
, bool aSelected
) const;
872 * https://dom.spec.whatwg.org/#concept-tree-inclusive-descendant.
874 nsresult
SelectFramesOfInclusiveDescendantsOfContent(
875 PostContentIterator
& aPostOrderIter
, nsIContent
* aContent
,
876 bool aSelected
) const;
879 * https://dom.spec.whatwg.org/#concept-shadow-including-descendant
881 void SelectFramesOfShadowIncludingDescendantsOfContent(nsIContent
* aContent
,
882 bool aSelected
) const;
884 nsresult
SelectFrames(nsPresContext
* aPresContext
, AbstractRange
& aRange
,
888 * SelectFramesInAllRanges() calls SelectFrames() for all current
891 void SelectFramesInAllRanges(nsPresContext
* aPresContext
);
894 * @param aOutIndex If some, points to the index of the range in
895 * mStyledRanges.mRanges so that it's always in [0, mStyledRanges.Length()].
896 * Otherwise, if nothing, this didn't add the range to mStyledRanges.
898 MOZ_CAN_RUN_SCRIPT nsresult
MaybeAddTableCellRange(nsRange
& aRange
,
899 Maybe
<size_t>* aOutIndex
);
901 Document
* GetDocument() const;
903 MOZ_CAN_RUN_SCRIPT
void RemoveAllRangesInternal(mozilla::ErrorResult
& aRv
);
907 struct StyledRanges
{
908 explicit StyledRanges(Selection
& aSelection
) : mSelection(aSelection
) {}
911 StyledRange
* FindRangeData(AbstractRange
* aRange
);
913 using StyledRangeArray
= AutoTArray
<StyledRange
, 1>;
915 StyledRangeArray::size_type
Length() const;
917 nsresult
RemoveCollapsedRanges();
919 nsresult
RemoveRangeAndUnregisterSelection(AbstractRange
& aRange
);
922 * Binary searches the given sorted array of ranges for the insertion point
923 * for the given node/offset. The given comparator is used, and the index
924 * where the point should appear in the array is returned.
926 * If there is an item in the array equal to the input point (aPointNode,
927 * aPointOffset), we will return the index of this item.
929 * @return the index where the point should appear in the array. In
930 * [0, `aElementArray->Length()`].
932 static size_t FindInsertionPoint(
933 const nsTArray
<StyledRange
>* aElementArray
, const nsINode
& aPointNode
,
934 uint32_t aPointOffset
,
935 int32_t (*aComparator
)(const nsINode
&, uint32_t, const AbstractRange
&));
938 * Works on the same principle as GetRangesForIntervalArray, however
939 * instead this returns the indices into mRanges between which
940 * the overlapping ranges lie.
942 * @param aStartIndex If some, aEndIndex will also be some and the value of
943 * aStartIndex will be less or equal than aEndIndex. If
944 * nothing, aEndIndex will also be nothing and it means
945 * that there is no range which in the range.
946 * @param aEndIndex If some, the value is less than mRanges.Length().
948 nsresult
GetIndicesForInterval(const nsINode
* aBeginNode
,
949 uint32_t aBeginOffset
,
950 const nsINode
* aEndNode
, uint32_t aEndOffset
,
952 Maybe
<size_t>& aStartIndex
,
953 Maybe
<size_t>& aEndIndex
);
955 bool HasEqualRangeBoundariesAt(const AbstractRange
& aRange
,
956 size_t aRangeIndex
) const;
959 * Preserves the sorting and disjunctiveness of mRanges.
961 * @param aOutIndex If some, will point to the index of the added range, or
962 * if aRange is already contained, to the one containing
963 * it. Hence it'll always be in [0, mRanges.Length()).
964 * This is nothing only when the method returns an error.
966 MOZ_CAN_RUN_SCRIPT nsresult
967 MaybeAddRangeAndTruncateOverlaps(nsRange
* aRange
, Maybe
<size_t>* aOutIndex
);
970 * Adds the range even if there are overlaps.
972 MOZ_CAN_RUN_SCRIPT nsresult
973 AddRangeAndIgnoreOverlaps(AbstractRange
* aRange
);
976 * GetCommonEditingHost() returns common editing host of all
977 * ranges if there is. If at least one of the ranges is in non-editable
978 * element, returns nullptr. See following examples for the detail:
980 * <div id="a" contenteditable>
982 * <div id="b" contenteditable="false">
984 * <div id="c" contenteditable>
986 * in this case, this returns div#a because div#c is also in div#a.
988 * <div id="a" contenteditable>
990 * <div id="b" contenteditable="false">
992 * <div id="c" contenteditable>
994 * in this case, this returns div#a because second range is also in div#a
995 * and common ancestor of the range (i.e., div#c) is editable.
997 * <div id="a" contenteditable>
999 * <div id="b" contenteditable="false">
1001 * <div id="c" contenteditable>
1003 * in this case, this returns nullptr because the second range is in
1004 * non-editable area.
1006 Element
* GetCommonEditingHost() const;
1008 MOZ_CAN_RUN_SCRIPT
void MaybeFocusCommonEditingHost(
1009 PresShell
* aPresShell
) const;
1011 static nsresult
SubtractRange(StyledRange
& aRange
, nsRange
& aSubtract
,
1012 nsTArray
<StyledRange
>* aOutput
);
1014 void UnregisterSelection();
1016 // `mRanges` always needs to be sorted by the Range's start point.
1017 // Especially when dealing with `StaticRange`s this is not guaranteed
1018 // automatically. Therefore this method should be called before paint to
1019 // ensure that any potential DOM mutations are incorporated in `mRanges`
1020 // order. This method will also move invalid `StaticRange`s into
1021 // `mInvalidStaticRanges` (and previously-invalid-now-valid-again
1022 // `StaticRange`s back into `mRanges`).
1023 void ReorderRangesIfNecessary();
1025 // These are the ranges inside this selection. They are kept sorted in order
1026 // of DOM start position.
1028 // This data structure is sorted by the range beginnings. As the ranges are
1029 // disjoint, it is also implicitly sorted by the range endings. This allows
1030 // us to perform binary searches when searching for existence of a range,
1031 // giving us O(log n) search time.
1033 // Inserting a new range requires finding the overlapping interval,
1034 // requiring two binary searches plus up to an additional 6 DOM comparisons.
1035 // If this proves to be a performance concern, then an interval tree may be
1036 // a possible solution, allowing the calculation of the overlap interval in
1037 // O(log n) time, though this would require rebalancing and other overhead.
1038 StyledRangeArray mRanges
;
1040 // With introduction of the custom highlight API, Selection must be able to
1041 // hold `StaticRange`s as well. If they become invalid (eg. end is before
1042 // start), they must be excluded from painting, but still kept.
1043 // mRanges needs to contain valid ranges sorted correctly only. Therefore,
1044 // invalid static ranges are being stored in this array, which is being kept
1045 // up to date in `ReorderRangesIfNecessary()`.
1046 StyledRangeArray mInvalidStaticRanges
;
1048 Selection
& mSelection
;
1050 // The Document's generation for which `mRanges` have been ordered.
1051 int32_t mDocumentGeneration
{0};
1052 // This flag indicates that ranges may have changed. It is set to true in
1053 // `Selection::NotifySelectionListeners().`
1054 bool mRangesMightHaveChanged
{false};
1057 StyledRanges mStyledRanges
{*this};
1059 RefPtr
<nsRange
> mAnchorFocusRange
;
1060 RefPtr
<nsFrameSelection
> mFrameSelection
;
1061 RefPtr
<AccessibleCaretEventHub
> mAccessibleCaretEventHub
;
1062 RefPtr
<SelectionChangeEventDispatcher
> mSelectionChangeEventDispatcher
;
1063 RefPtr
<AutoScroller
> mAutoScroller
;
1064 nsTArray
<nsCOMPtr
<nsISelectionListener
>> mSelectionListeners
;
1065 nsRevocableEventPtr
<ScrollSelectionIntoViewEvent
> mScrollEvent
;
1066 CachedOffsetForFrame
* mCachedOffsetForFrame
;
1067 nsDirection mDirection
;
1068 const SelectionType mSelectionType
;
1069 HighlightSelectionData mHighlightData
;
1070 UniquePtr
<SelectionCustomColors
> mCustomColors
;
1072 // Non-zero if we don't want any changes we make to the selection to be
1073 // visible to content. If non-zero, content won't be notified about changes.
1074 uint32_t mSelectionChangeBlockerCount
;
1077 * True if the current selection operation was initiated by user action.
1078 * It determines whether we exclude -moz-user-select:none nodes or not,
1079 * as well as whether selectstart events will be fired.
1081 bool mUserInitiated
;
1084 * When the selection change is caused by a call of Selection API,
1085 * mCalledByJS is true. Otherwise, false.
1090 * true if AutoCopyListner::OnSelectionChange() should be called.
1092 bool mNotifyAutoCopy
;
1095 // Stack-class to turn on/off selection batching.
1096 class MOZ_STACK_CLASS SelectionBatcher final
{
1098 const RefPtr
<Selection
> mSelection
;
1099 const int16_t mReasons
;
1100 const char* const mRequesterFuncName
;
1104 * @param aRequesterFuncName function name which wants the selection batch.
1105 * This won't be stored nor exposed to selection listeners etc, used only for
1106 * logging. This MUST be living when the destructor runs.
1108 // TODO: Mark these constructors `MOZ_CAN_RUN_SCRIPT` because the destructor
1109 // may run script via nsISelectionListener.
1110 explicit SelectionBatcher(Selection
& aSelectionRef
,
1111 const char* aRequesterFuncName
,
1112 int16_t aReasons
= nsISelectionListener::NO_REASON
)
1113 : SelectionBatcher(&aSelectionRef
, aRequesterFuncName
, aReasons
) {}
1114 explicit SelectionBatcher(Selection
* aSelection
,
1115 const char* aRequesterFuncName
,
1116 int16_t aReasons
= nsISelectionListener::NO_REASON
)
1117 : mSelection(aSelection
),
1119 mRequesterFuncName(aRequesterFuncName
) {
1121 mSelection
->StartBatchChanges(mRequesterFuncName
);
1125 ~SelectionBatcher() {
1127 mSelection
->EndBatchChanges(mRequesterFuncName
, mReasons
);
1132 class MOZ_RAII AutoHideSelectionChanges final
{
1134 explicit AutoHideSelectionChanges(const nsFrameSelection
* aFrame
);
1136 explicit AutoHideSelectionChanges(Selection
& aSelectionRef
)
1137 : AutoHideSelectionChanges(&aSelectionRef
) {}
1139 ~AutoHideSelectionChanges() {
1141 mSelection
->RemoveSelectionChangeBlocker();
1146 explicit AutoHideSelectionChanges(Selection
* aSelection
)
1147 : mSelection(aSelection
) {
1149 mSelection
->AddSelectionChangeBlocker();
1153 RefPtr
<Selection
> mSelection
;
1158 inline bool IsValidRawSelectionType(RawSelectionType aRawSelectionType
) {
1159 return aRawSelectionType
>= nsISelectionController::SELECTION_NONE
&&
1160 aRawSelectionType
<= nsISelectionController::SELECTION_URLSTRIKEOUT
;
1163 inline SelectionType
ToSelectionType(RawSelectionType aRawSelectionType
) {
1164 if (!IsValidRawSelectionType(aRawSelectionType
)) {
1165 return SelectionType::eInvalid
;
1167 return static_cast<SelectionType
>(aRawSelectionType
);
1170 inline RawSelectionType
ToRawSelectionType(SelectionType aSelectionType
) {
1171 MOZ_ASSERT(aSelectionType
!= SelectionType::eInvalid
);
1172 return static_cast<RawSelectionType
>(aSelectionType
);
1175 inline RawSelectionType
ToRawSelectionType(TextRangeType aTextRangeType
) {
1176 return ToRawSelectionType(ToSelectionType(aTextRangeType
));
1179 inline SelectionTypeMask
ToSelectionTypeMask(SelectionType aSelectionType
) {
1180 MOZ_ASSERT(aSelectionType
!= SelectionType::eInvalid
);
1181 return aSelectionType
== SelectionType::eNone
1183 : static_cast<SelectionTypeMask
>(
1184 1 << (static_cast<uint8_t>(aSelectionType
) - 1));
1187 inline std::ostream
& operator<<(
1188 std::ostream
& aStream
, const dom::Selection::InterlinePosition
& aPosition
) {
1189 using InterlinePosition
= dom::Selection::InterlinePosition
;
1190 switch (aPosition
) {
1191 case InterlinePosition::EndOfLine
:
1192 return aStream
<< "InterlinePosition::EndOfLine";
1193 case InterlinePosition::StartOfNextLine
:
1194 return aStream
<< "InterlinePosition::StartOfNextLine";
1195 case InterlinePosition::Undefined
:
1196 return aStream
<< "InterlinePosition::Undefined";
1198 MOZ_ASSERT_UNREACHABLE("Illegal value");
1199 return aStream
<< "<Illegal value>";
1203 } // namespace mozilla
1205 #endif // mozilla_Selection_h__