summaryrefslogtreecommitdiffstats
path: root/dom/base/Selection.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/Selection.h')
-rw-r--r--dom/base/Selection.h1134
1 files changed, 1134 insertions, 0 deletions
diff --git a/dom/base/Selection.h b/dom/base/Selection.h
new file mode 100644
index 0000000000..9f031ab3cf
--- /dev/null
+++ b/dom/base/Selection.h
@@ -0,0 +1,1134 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_Selection_h__
+#define mozilla_Selection_h__
+
+#include "mozilla/AutoRestore.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/PresShellForwards.h"
+#include "mozilla/RangeBoundary.h"
+#include "mozilla/SelectionChangeEventDispatcher.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/dom/Highlight.h"
+#include "mozilla/dom/StyledRange.h"
+#include "nsDirection.h"
+#include "nsISelectionController.h"
+#include "nsISelectionListener.h"
+#include "nsRange.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsThreadUtils.h"
+#include "nsWeakReference.h"
+#include "nsWrapperCache.h"
+
+struct CachedOffsetForFrame;
+class AutoScroller;
+class nsIFrame;
+class nsFrameSelection;
+class nsPIDOMWindowOuter;
+struct SelectionDetails;
+struct SelectionCustomColors;
+class nsCopySupport;
+class nsHTMLCopyEncoder;
+class nsPresContext;
+struct nsPoint;
+struct nsRect;
+
+namespace mozilla {
+class AccessibleCaretEventHub;
+class ErrorResult;
+class HTMLEditor;
+class PostContentIterator;
+enum class CaretAssociationHint;
+enum class TableSelectionMode : uint32_t;
+struct AutoPrepareFocusRange;
+struct PrimaryFrameData;
+namespace dom {
+class DocGroup;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+
+namespace dom {
+
+// Note, the ownership of mozilla::dom::Selection depends on which way the
+// object is created. When nsFrameSelection has created Selection,
+// addreffing/releasing the Selection object is aggregated to nsFrameSelection.
+// Otherwise normal addref/release is used. This ensures that nsFrameSelection
+// is never deleted before its Selections.
+class Selection final : public nsSupportsWeakReference,
+ public nsWrapperCache,
+ public SupportsWeakPtr {
+ protected:
+ virtual ~Selection();
+
+ public:
+ /**
+ * @param aFrameSelection can be nullptr.
+ */
+ explicit Selection(SelectionType aSelectionType,
+ nsFrameSelection* aFrameSelection);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Selection)
+
+ /**
+ * Match this up with EndbatchChanges. will stop ui updates while multiple
+ * selection methods are called
+ *
+ * @param aDetails string to explian why this is called. This won't be
+ * stored nor exposed to selection listeners etc. Just for logging.
+ */
+ void StartBatchChanges(const char* aDetails);
+
+ /**
+ * Match this up with StartBatchChanges
+ *
+ * @param aDetails string to explian why this is called. This won't be
+ * stored nor exposed to selection listeners etc. Just for logging.
+ * @param aReasons potentially multiple of the reasons defined in
+ * nsISelectionListener.idl
+ */
+ void EndBatchChanges(const char* aDetails,
+ int16_t aReason = nsISelectionListener::NO_REASON);
+
+ /**
+ * NotifyAutoCopy() starts to notify AutoCopyListener of selection changes.
+ */
+ void NotifyAutoCopy() {
+ MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
+
+ mNotifyAutoCopy = true;
+ }
+
+ /**
+ * MaybeNotifyAccessibleCaretEventHub() starts to notify
+ * AccessibleCaretEventHub of selection change if aPresShell has it.
+ */
+ void MaybeNotifyAccessibleCaretEventHub(PresShell* aPresShell);
+
+ /**
+ * StopNotifyingAccessibleCaretEventHub() stops notifying
+ * AccessibleCaretEventHub of selection change.
+ */
+ void StopNotifyingAccessibleCaretEventHub();
+
+ /**
+ * EnableSelectionChangeEvent() starts to notify
+ * SelectionChangeEventDispatcher of selection change to dispatch a
+ * selectionchange event at every selection change.
+ */
+ void EnableSelectionChangeEvent() {
+ if (!mSelectionChangeEventDispatcher) {
+ mSelectionChangeEventDispatcher = new SelectionChangeEventDispatcher();
+ }
+ }
+
+ // Required for WebIDL bindings, see
+ // https://developer.mozilla.org/en-US/docs/Mozilla/WebIDL_bindings#Adding_WebIDL_bindings_to_a_class.
+ Document* GetParentObject() const;
+
+ DocGroup* GetDocGroup() const;
+
+ // utility methods for scrolling the selection into view
+ nsPresContext* GetPresContext() const;
+ PresShell* GetPresShell() const;
+ nsFrameSelection* GetFrameSelection() const { return mFrameSelection; }
+ // Returns a rect containing the selection region, and frame that that
+ // position is relative to. For SELECTION_ANCHOR_REGION or
+ // SELECTION_FOCUS_REGION the rect is a zero-width rectangle. For
+ // SELECTION_WHOLE_SELECTION the rect contains both the anchor and focus
+ // region rects.
+ nsIFrame* GetSelectionAnchorGeometry(SelectionRegion aRegion, nsRect* aRect);
+ // Returns the position of the region (SELECTION_ANCHOR_REGION or
+ // SELECTION_FOCUS_REGION only), and frame that that position is relative to.
+ // The 'position' is a zero-width rectangle.
+ nsIFrame* GetSelectionEndPointGeometry(SelectionRegion aRegion,
+ nsRect* aRect);
+
+ nsresult PostScrollSelectionIntoViewEvent(SelectionRegion aRegion,
+ int32_t aFlags,
+ ScrollAxis aVertical,
+ ScrollAxis aHorizontal);
+ enum {
+ SCROLL_SYNCHRONOUS = 1 << 1,
+ SCROLL_FIRST_ANCESTOR_ONLY = 1 << 2,
+ SCROLL_DO_FLUSH =
+ 1 << 3, // only matters if SCROLL_SYNCHRONOUS is passed too
+ SCROLL_OVERFLOW_HIDDEN = 1 << 5,
+ SCROLL_FOR_CARET_MOVE = 1 << 6
+ };
+ // If aFlags doesn't contain SCROLL_SYNCHRONOUS, then we'll flush when
+ // the scroll event fires so we make sure to scroll to the right place.
+ // Otherwise, if SCROLL_DO_FLUSH is also in aFlags, then this method will
+ // flush layout and you MUST hold a strong ref on 'this' for the duration
+ // of this call. This might destroy arbitrary layout objects.
+ MOZ_CAN_RUN_SCRIPT nsresult
+ ScrollIntoView(SelectionRegion aRegion, ScrollAxis aVertical = ScrollAxis(),
+ ScrollAxis aHorizontal = ScrollAxis(), int32_t aFlags = 0);
+
+ private:
+ static bool IsUserSelectionCollapsed(
+ const nsRange& aRange, nsTArray<RefPtr<nsRange>>& aTempRangesToAdd);
+ /**
+ * https://w3c.github.io/selection-api/#selectstart-event.
+ */
+ enum class DispatchSelectstartEvent {
+ No,
+ Maybe,
+ };
+
+ /**
+ * See `AddRangesForSelectableNodes`.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult AddRangesForUserSelectableNodes(
+ nsRange* aRange, Maybe<size_t>* aOutIndex,
+ const DispatchSelectstartEvent aDispatchSelectstartEvent);
+
+ /**
+ * Adds aRange to this Selection. If mUserInitiated is true,
+ * then aRange is first scanned for -moz-user-select:none nodes and split up
+ * into multiple ranges to exclude those before adding the resulting ranges
+ * to this Selection.
+ *
+ * @param aOutIndex points to the range last added, if at least one was added.
+ * If aRange is already contained, it points to the range
+ * containing it. Nothing() if mStyledRanges.mRanges was
+ * empty and no range was added.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult AddRangesForSelectableNodes(
+ nsRange* aRange, Maybe<size_t>* aOutIndex,
+ DispatchSelectstartEvent aDispatchSelectstartEvent);
+
+ public:
+ nsresult RemoveCollapsedRanges();
+ void Clear(nsPresContext* aPresContext);
+ MOZ_CAN_RUN_SCRIPT nsresult CollapseInLimiter(nsINode* aContainer,
+ uint32_t aOffset) {
+ if (!aContainer) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return CollapseInLimiter(RawRangeBoundary(aContainer, aOffset));
+ }
+ MOZ_CAN_RUN_SCRIPT nsresult
+ CollapseInLimiter(const RawRangeBoundary& aPoint) {
+ ErrorResult result;
+ CollapseInLimiter(aPoint, result);
+ return result.StealNSResult();
+ }
+ MOZ_CAN_RUN_SCRIPT void CollapseInLimiter(const RawRangeBoundary& aPoint,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT nsresult Extend(nsINode* aContainer, uint32_t aOffset);
+
+ /**
+ * See mStyledRanges.mRanges.
+ */
+ nsRange* GetRangeAt(uint32_t aIndex) const;
+
+ /**
+ * @brief Get the |AbstractRange| at |aIndex|.
+ *
+ * This method is safe to be called for every selection type.
+ * However, |StaticRange|s only occur for |SelectionType::eHighlight|.
+ * If the SelectionType may be eHighlight, this method must be called instead
+ * of |GetRangeAt()|.
+ *
+ * Returns null if |aIndex| is out of bounds.
+ */
+ AbstractRange* GetAbstractRangeAt(uint32_t aIndex) const;
+ // Get the anchor-to-focus range if we don't care which end is
+ // anchor and which end is focus.
+ const nsRange* GetAnchorFocusRange() const { return mAnchorFocusRange; }
+
+ nsDirection GetDirection() const { return mDirection; }
+
+ void SetDirection(nsDirection aDir) { mDirection = aDir; }
+ MOZ_CAN_RUN_SCRIPT nsresult SetAnchorFocusToRange(nsRange* aRange);
+
+ MOZ_CAN_RUN_SCRIPT void ReplaceAnchorFocusRange(nsRange* aRange);
+
+ void AdjustAnchorFocusForMultiRange(nsDirection aDirection);
+
+ nsIFrame* GetPrimaryFrameForAnchorNode() const;
+
+ /**
+ * Get primary frame and some other data for putting caret or extending
+ * selection at the focus point.
+ */
+ PrimaryFrameData GetPrimaryFrameForCaretAtFocusNode(bool aVisual) const;
+
+ UniquePtr<SelectionDetails> LookUpSelection(
+ nsIContent* aContent, uint32_t aContentOffset, uint32_t aContentLength,
+ UniquePtr<SelectionDetails> aDetailsHead, SelectionType aSelectionType,
+ bool aSlowCheck);
+
+ NS_IMETHOD Repaint(nsPresContext* aPresContext);
+
+ MOZ_CAN_RUN_SCRIPT
+ nsresult StartAutoScrollTimer(nsIFrame* aFrame, const nsPoint& aPoint,
+ uint32_t aDelayInMs);
+
+ nsresult StopAutoScrollTimer();
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL methods
+ nsINode* GetAnchorNode(CallerType aCallerType = CallerType::System) const {
+ const RangeBoundary& anchor = AnchorRef();
+ nsINode* anchorNode = anchor.IsSet() ? anchor.Container() : nullptr;
+ if (!anchorNode || aCallerType == CallerType::System ||
+ !anchorNode->ChromeOnlyAccess()) {
+ return anchorNode;
+ }
+ // anchor is nsIContent as ChromeOnlyAccess is nsIContent-only
+ return anchorNode->AsContent()->FindFirstNonChromeOnlyAccessContent();
+ }
+ uint32_t AnchorOffset(CallerType aCallerType = CallerType::System) const {
+ const RangeBoundary& anchor = AnchorRef();
+ if (aCallerType != CallerType::System && anchor.IsSet() &&
+ anchor.Container()->ChromeOnlyAccess()) {
+ return 0;
+ }
+ const Maybe<uint32_t> offset =
+ anchor.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
+ return offset ? *offset : 0;
+ }
+ nsINode* GetFocusNode(CallerType aCallerType = CallerType::System) const {
+ const RangeBoundary& focus = FocusRef();
+ nsINode* focusNode = focus.IsSet() ? focus.Container() : nullptr;
+ if (!focusNode || aCallerType == CallerType::System ||
+ !focusNode->ChromeOnlyAccess()) {
+ return focusNode;
+ }
+ // focus is nsIContent as ChromeOnlyAccess is nsIContent-only
+ return focusNode->AsContent()->FindFirstNonChromeOnlyAccessContent();
+ }
+ uint32_t FocusOffset(CallerType aCallerType = CallerType::System) const {
+ const RangeBoundary& focus = FocusRef();
+ if (aCallerType != CallerType::System && focus.IsSet() &&
+ focus.Container()->ChromeOnlyAccess()) {
+ return 0;
+ }
+ const Maybe<uint32_t> offset =
+ focus.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
+ return offset ? *offset : 0;
+ }
+
+ nsIContent* GetChildAtAnchorOffset() {
+ const RangeBoundary& anchor = AnchorRef();
+ return anchor.IsSet() ? anchor.GetChildAtOffset() : nullptr;
+ }
+ nsIContent* GetChildAtFocusOffset() {
+ const RangeBoundary& focus = FocusRef();
+ return focus.IsSet() ? focus.GetChildAtOffset() : nullptr;
+ }
+
+ const RangeBoundary& AnchorRef() const;
+ const RangeBoundary& FocusRef() const;
+
+ /*
+ * IsCollapsed -- is the whole selection just one point, or unset?
+ */
+ bool IsCollapsed() const {
+ size_t cnt = mStyledRanges.Length();
+ if (cnt == 0) {
+ return true;
+ }
+
+ if (cnt != 1) {
+ return false;
+ }
+
+ return mStyledRanges.mRanges[0].mRange->Collapsed();
+ }
+
+ // *JS() methods are mapped to Selection.*().
+ // They may move focus only when the range represents normal selection.
+ // These methods shouldn't be used by non-JS callers.
+ MOZ_CAN_RUN_SCRIPT void CollapseJS(nsINode* aContainer, uint32_t aOffset,
+ mozilla::ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void CollapseToStartJS(mozilla::ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void CollapseToEndJS(mozilla::ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void ExtendJS(nsINode& aContainer, uint32_t aOffset,
+ mozilla::ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void SelectAllChildrenJS(nsINode& aNode,
+ mozilla::ErrorResult& aRv);
+
+ /**
+ * Deletes this selection from document the nodes belong to.
+ * Only if this has `SelectionType::eNormal`.
+ */
+ MOZ_CAN_RUN_SCRIPT void DeleteFromDocument(mozilla::ErrorResult& aRv);
+
+ uint32_t RangeCount() const { return mStyledRanges.Length(); }
+
+ void GetType(nsAString& aOutType) const;
+
+ nsRange* GetRangeAt(uint32_t aIndex, mozilla::ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void AddRangeJS(nsRange& aRange,
+ mozilla::ErrorResult& aRv);
+
+ /**
+ * Callers need to keep `aRange` alive.
+ */
+ MOZ_CAN_RUN_SCRIPT void RemoveRangeAndUnselectFramesAndNotifyListeners(
+ AbstractRange& aRange, mozilla::ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void RemoveAllRanges(mozilla::ErrorResult& aRv);
+
+ /**
+ * Whether Stringify should flush layout or not.
+ */
+ enum class FlushFrames { No, Yes };
+ MOZ_CAN_RUN_SCRIPT
+ void Stringify(nsAString& aResult, FlushFrames = FlushFrames::Yes);
+
+ /**
+ * Indicates whether the node is part of the selection. If partlyContained
+ * is true, the function returns true when some part of the node
+ * is part of the selection. If partlyContained is false, the
+ * function only returns true when the entire node is part of the selection.
+ */
+ bool ContainsNode(nsINode& aNode, bool aPartlyContained,
+ mozilla::ErrorResult& aRv);
+
+ /**
+ * Check to see if the given point is contained within the selection area. In
+ * particular, this iterates through all the rects that make up the selection,
+ * not just the bounding box, and checks to see if the given point is
+ * contained in any one of them.
+ * @param aPoint The point to check, relative to the root frame.
+ */
+ bool ContainsPoint(const nsPoint& aPoint);
+
+ /**
+ * Modifies the selection. Note that the parameters are case-insensitive.
+ *
+ * @param alter can be one of { "move", "extend" }
+ * - "move" collapses the selection to the end of the selection and
+ * applies the movement direction/granularity to the collapsed
+ * selection.
+ * - "extend" leaves the start of the selection unchanged, and applies
+ * movement direction/granularity to the end of the selection.
+ * @param direction can be one of { "forward", "backward", "left", "right" }
+ * @param granularity can be one of { "character", "word",
+ * "line", "lineboundary" }
+ *
+ * @throws NS_ERROR_NOT_IMPLEMENTED if the granularity is "sentence",
+ * "sentenceboundary", "paragraph", "paragraphboundary", or
+ * "documentboundary". Throws NS_ERROR_INVALID_ARG if alter, direction,
+ * or granularity has an unrecognized value.
+ */
+ MOZ_CAN_RUN_SCRIPT void Modify(const nsAString& aAlter,
+ const nsAString& aDirection,
+ const nsAString& aGranularity,
+ mozilla::ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT
+ void SetBaseAndExtentJS(nsINode& aAnchorNode, uint32_t aAnchorOffset,
+ nsINode& aFocusNode, uint32_t aFocusOffset,
+ mozilla::ErrorResult& aRv);
+
+ bool GetInterlinePositionJS(mozilla::ErrorResult& aRv) const;
+ void SetInterlinePositionJS(bool aHintRight, mozilla::ErrorResult& aRv);
+
+ enum class InterlinePosition : uint8_t {
+ // Caret should be put at end of line (i.e., before the line break)
+ EndOfLine,
+ // Caret should be put at start of next line (i.e., after the line break)
+ StartOfNextLine,
+ // Undefined means only what is not EndOfLine nor StartOfNextLine.
+ // `SetInterlinePosition` should never be called with this value, and
+ // if `GetInterlinePosition` returns this, it means that the instance has
+ // not been initialized or cleared by the cycle collector or something.
+ // If a method needs to consider whether to call `SetInterlinePosition` or
+ // not call, this value can be used for the latter.
+ Undefined,
+ };
+ InterlinePosition GetInterlinePosition() const;
+ nsresult SetInterlinePosition(InterlinePosition aInterlinePosition);
+
+ Nullable<int16_t> GetCaretBidiLevel(mozilla::ErrorResult& aRv) const;
+ void SetCaretBidiLevel(const Nullable<int16_t>& aCaretBidiLevel,
+ mozilla::ErrorResult& aRv);
+
+ void ToStringWithFormat(const nsAString& aFormatType, uint32_t aFlags,
+ int32_t aWrapColumn, nsAString& aReturn,
+ mozilla::ErrorResult& aRv);
+ void AddSelectionListener(nsISelectionListener* aListener);
+ void RemoveSelectionListener(nsISelectionListener* aListener);
+
+ RawSelectionType RawType() const {
+ return ToRawSelectionType(mSelectionType);
+ }
+ SelectionType Type() const { return mSelectionType; }
+
+ /**
+ * @brief Sets highlight selection properties.
+ *
+ * This includes the highlight name as well as its priority and type.
+ */
+ void SetHighlightSelectionData(
+ HighlightSelectionData aHighlightSelectionData);
+
+ /**
+ * See documentation of `GetRangesForInterval` in Selection.webidl.
+ *
+ * @param aReturn references, not copies, of the internal ranges.
+ */
+ void GetRangesForInterval(nsINode& aBeginNode, uint32_t aBeginOffset,
+ nsINode& aEndNode, uint32_t aEndOffset,
+ bool aAllowAdjacent,
+ nsTArray<RefPtr<nsRange>>& aReturn,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void ScrollIntoView(int16_t aRegion, bool aIsSynchronous,
+ int16_t aVPercent, int16_t aHPercent,
+ ErrorResult& aRv);
+
+ void SetColors(const nsAString& aForeColor, const nsAString& aBackColor,
+ const nsAString& aAltForeColor, const nsAString& aAltBackColor,
+ ErrorResult& aRv);
+
+ void ResetColors();
+
+ /**
+ * Non-JS callers should use the following
+ * collapse/collapseToStart/extend/etc methods, instead of the *JS
+ * versions that bindings call.
+ */
+
+ /**
+ * Collapses the selection to a single point, at the specified offset
+ * in the given node. When the selection is collapsed, and the content
+ * is focused and editable, the caret will blink there.
+ * @param aContainer The given node where the selection will be set
+ * @param aOffset Where in given dom node to place the selection (the
+ * offset into the given node)
+ */
+ MOZ_CAN_RUN_SCRIPT void CollapseInLimiter(nsINode& aContainer,
+ uint32_t aOffset,
+ ErrorResult& aRv) {
+ CollapseInLimiter(RawRangeBoundary(&aContainer, aOffset), aRv);
+ }
+
+ private:
+ enum class InLimiter {
+ // If eYes, the method may reset selection limiter and move focus if the
+ // given range is out of the limiter.
+ eYes,
+ // If eNo, the method won't reset selection limiter. So, if given range
+ // is out of bounds, the method may return error.
+ eNo,
+ };
+ MOZ_CAN_RUN_SCRIPT
+ void CollapseInternal(InLimiter aInLimiter, const RawRangeBoundary& aPoint,
+ ErrorResult& aRv);
+
+ public:
+ /**
+ * Collapses the whole selection to a single point at the start
+ * of the current selection (irrespective of direction). If content
+ * is focused and editable, the caret will blink there.
+ */
+ MOZ_CAN_RUN_SCRIPT void CollapseToStart(mozilla::ErrorResult& aRv);
+
+ /**
+ * Collapses the whole selection to a single point at the end
+ * of the current selection (irrespective of direction). If content
+ * is focused and editable, the caret will blink there.
+ */
+ MOZ_CAN_RUN_SCRIPT void CollapseToEnd(mozilla::ErrorResult& aRv);
+
+ /**
+ * Extends the selection by moving the selection end to the specified node and
+ * offset, preserving the selection begin position. The new selection end
+ * result will always be from the anchorNode to the new focusNode, regardless
+ * of direction.
+ *
+ * @param aContainer The node where the selection will be extended to
+ * @param aOffset Where in aContainer to place the offset of the new
+ * selection end.
+ */
+ MOZ_CAN_RUN_SCRIPT void Extend(nsINode& aContainer, uint32_t aOffset,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void AddRangeAndSelectFramesAndNotifyListeners(
+ nsRange& aRange, mozilla::ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void AddHighlightRangeAndSelectFramesAndNotifyListeners(
+ AbstractRange& aRange);
+
+ /**
+ * Adds all children of the specified node to the selection.
+ * @param aNode the parent of the children to be added to the selection.
+ */
+ MOZ_CAN_RUN_SCRIPT void SelectAllChildren(nsINode& aNode,
+ mozilla::ErrorResult& aRv);
+
+ /**
+ * SetStartAndEnd() removes all ranges and sets new range as given range.
+ * Different from SetBaseAndExtent(), this won't compare the DOM points of
+ * aStartRef and aEndRef for performance nor set direction to eDirPrevious.
+ * Note that this may reset the limiter and move focus. If you don't want
+ * that, use SetStartAndEndInLimiter() instead.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void SetStartAndEnd(const RawRangeBoundary& aStartRef,
+ const RawRangeBoundary& aEndRef, ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT
+ void SetStartAndEnd(nsINode& aStartContainer, uint32_t aStartOffset,
+ nsINode& aEndContainer, uint32_t aEndOffset,
+ ErrorResult& aRv) {
+ SetStartAndEnd(RawRangeBoundary(&aStartContainer, aStartOffset),
+ RawRangeBoundary(&aEndContainer, aEndOffset), aRv);
+ }
+
+ /**
+ * SetStartAndEndInLimiter() is similar to SetStartAndEnd(), but this respects
+ * the selection limiter. If all or part of given range is not in the
+ * limiter, this returns error.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void SetStartAndEndInLimiter(const RawRangeBoundary& aStartRef,
+ const RawRangeBoundary& aEndRef,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT
+ void SetStartAndEndInLimiter(nsINode& aStartContainer, uint32_t aStartOffset,
+ nsINode& aEndContainer, uint32_t aEndOffset,
+ ErrorResult& aRv) {
+ SetStartAndEndInLimiter(RawRangeBoundary(&aStartContainer, aStartOffset),
+ RawRangeBoundary(&aEndContainer, aEndOffset), aRv);
+ }
+ MOZ_CAN_RUN_SCRIPT
+ Result<Ok, nsresult> SetStartAndEndInLimiter(
+ nsINode& aStartContainer, uint32_t aStartOffset, nsINode& aEndContainer,
+ uint32_t aEndOffset, nsDirection aDirection, int16_t aReason);
+
+ /**
+ * SetBaseAndExtent() is alternative of the JS API for internal use.
+ * Different from SetStartAndEnd(), this sets anchor and focus points as
+ * specified, then if anchor point is after focus node, this sets the
+ * direction to eDirPrevious.
+ * Note that this may reset the limiter and move focus. If you don't want
+ * that, use SetBaseAndExtentInLimier() instead.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset,
+ nsINode& aFocusNode, uint32_t aFocusOffset,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT
+ void SetBaseAndExtent(const RawRangeBoundary& aAnchorRef,
+ const RawRangeBoundary& aFocusRef, ErrorResult& aRv);
+
+ /**
+ * SetBaseAndExtentInLimiter() is similar to SetBaseAndExtent(), but this
+ * respects the selection limiter. If all or part of given range is not in
+ * the limiter, this returns error.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void SetBaseAndExtentInLimiter(nsINode& aAnchorNode, uint32_t aAnchorOffset,
+ nsINode& aFocusNode, uint32_t aFocusOffset,
+ ErrorResult& aRv) {
+ SetBaseAndExtentInLimiter(RawRangeBoundary(&aAnchorNode, aAnchorOffset),
+ RawRangeBoundary(&aFocusNode, aFocusOffset), aRv);
+ }
+ MOZ_CAN_RUN_SCRIPT
+ void SetBaseAndExtentInLimiter(const RawRangeBoundary& aAnchorRef,
+ const RawRangeBoundary& aFocusRef,
+ ErrorResult& aRv);
+
+ void AddSelectionChangeBlocker();
+ void RemoveSelectionChangeBlocker();
+ bool IsBlockingSelectionChangeEvents() const;
+
+ // Whether this selection is focused in an editable element.
+ bool IsEditorSelection() const;
+
+ /**
+ * Set the painting style for the range. The range must be a range in
+ * the selection. The textRangeStyle will be used by text frame
+ * when it is painting the selection.
+ */
+ nsresult SetTextRangeStyle(nsRange* aRange,
+ const TextRangeStyle& aTextRangeStyle);
+
+ // Methods to manipulate our mFrameSelection's ancestor limiter.
+ nsIContent* GetAncestorLimiter() const;
+ void SetAncestorLimiter(nsIContent* aLimiter);
+
+ /*
+ * Frame Offset cache can be used just during calling
+ * nsEditor::EndPlaceHolderTransaction. EndPlaceHolderTransaction will give
+ * rise to reflow/refreshing view/scroll, and call times of
+ * nsTextFrame::GetPointFromOffset whose return value is to be cached. see
+ * bugs 35296 and 199412
+ */
+ void SetCanCacheFrameOffset(bool aCanCacheFrameOffset);
+
+ // Selection::GetAbstractRangesForIntervalArray
+ //
+ // Fills a nsTArray with the ranges overlapping the range specified by
+ // the given endpoints. Ranges in the selection exactly adjacent to the
+ // input range are not returned unless aAllowAdjacent is set.
+ //
+ // For example, if the following ranges were in the selection
+ // (assume everything is within the same node)
+ //
+ // Start Offset: 0 2 7 9
+ // End Offset: 2 5 9 10
+ //
+ // and passed aBeginOffset of 2 and aEndOffset of 9, then with
+ // aAllowAdjacent set, all the ranges should be returned. If
+ // aAllowAdjacent was false, the ranges [2, 5] and [7, 9] only
+ // should be returned
+ //
+ // Now that overlapping ranges are disallowed, there can be a maximum of
+ // 2 adjacent ranges
+ nsresult GetAbstractRangesForIntervalArray(nsINode* aBeginNode,
+ uint32_t aBeginOffset,
+ nsINode* aEndNode,
+ uint32_t aEndOffset,
+ bool aAllowAdjacent,
+ nsTArray<AbstractRange*>* aRanges);
+
+ /**
+ * Converts the results of |GetAbstractRangesForIntervalArray()| to |nsRange|.
+ *
+ * |StaticRange|s can only occur in Selections of type |eHighlight|.
+ * Therefore, this method must not be called for this selection type
+ * as not every |AbstractRange| can be cast to |nsRange|.
+ */
+ nsresult GetDynamicRangesForIntervalArray(
+ nsINode* aBeginNode, uint32_t aBeginOffset, nsINode* aEndNode,
+ uint32_t aEndOffset, bool aAllowAdjacent, nsTArray<nsRange*>* aRanges);
+
+ /**
+ * Modifies the cursor Bidi level after a change in keyboard direction
+ * @param langRTL is true if the new language is right-to-left or
+ * false if the new language is left-to-right.
+ */
+ nsresult SelectionLanguageChange(bool aLangRTL);
+
+ private:
+ bool HasSameRootOrSameComposedDoc(const nsINode& aNode);
+
+ // XXX Please don't add additional uses of this method, it's only for
+ // XXX supporting broken code (bug 1245883) in the following classes:
+ friend class ::nsCopySupport;
+ friend class ::nsHTMLCopyEncoder;
+ MOZ_CAN_RUN_SCRIPT
+ void AddRangeAndSelectFramesAndNotifyListenersInternal(nsRange& aRange,
+ Document* aDocument,
+ ErrorResult&);
+
+ // Get the cached value for nsTextFrame::GetPointFromOffset.
+ nsresult GetCachedFrameOffset(nsIFrame* aFrame, int32_t inOffset,
+ nsPoint& aPoint);
+
+ MOZ_CAN_RUN_SCRIPT
+ void SetStartAndEndInternal(InLimiter aInLimiter,
+ const RawRangeBoundary& aStartRef,
+ const RawRangeBoundary& aEndRef,
+ nsDirection aDirection, ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT
+ void SetBaseAndExtentInternal(InLimiter aInLimiter,
+ const RawRangeBoundary& aAnchorRef,
+ const RawRangeBoundary& aFocusRef,
+ ErrorResult& aRv);
+
+ public:
+ SelectionType GetType() const { return mSelectionType; }
+
+ SelectionCustomColors* GetCustomColors() const { return mCustomColors.get(); }
+
+ MOZ_CAN_RUN_SCRIPT void NotifySelectionListeners(bool aCalledByJS);
+ MOZ_CAN_RUN_SCRIPT void NotifySelectionListeners();
+
+ friend struct AutoUserInitiated;
+ struct MOZ_RAII AutoUserInitiated {
+ explicit AutoUserInitiated(Selection& aSelectionRef)
+ : AutoUserInitiated(&aSelectionRef) {}
+ explicit AutoUserInitiated(Selection* aSelection)
+ : mSavedValue(aSelection->mUserInitiated) {
+ aSelection->mUserInitiated = true;
+ }
+ AutoRestore<bool> mSavedValue;
+ };
+
+ private:
+ friend struct mozilla::AutoPrepareFocusRange;
+ class ScrollSelectionIntoViewEvent;
+ friend class ScrollSelectionIntoViewEvent;
+
+ class ScrollSelectionIntoViewEvent : public Runnable {
+ public:
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_DECL_NSIRUNNABLE
+
+ ScrollSelectionIntoViewEvent(Selection* aSelection, SelectionRegion aRegion,
+ ScrollAxis aVertical, ScrollAxis aHorizontal,
+ int32_t aFlags)
+ : Runnable("dom::Selection::ScrollSelectionIntoViewEvent"),
+ mSelection(aSelection),
+ mRegion(aRegion),
+ mVerticalScroll(aVertical),
+ mHorizontalScroll(aHorizontal),
+ mFlags(aFlags) {
+ NS_ASSERTION(aSelection, "null parameter");
+ }
+ void Revoke() { mSelection = nullptr; }
+
+ private:
+ Selection* mSelection;
+ SelectionRegion mRegion;
+ ScrollAxis mVerticalScroll;
+ ScrollAxis mHorizontalScroll;
+ int32_t mFlags;
+ };
+
+ /**
+ * Set mAnchorFocusRange to mStyledRanges.mRanges[aIndex] if aIndex is a valid
+ * index.
+ */
+ void SetAnchorFocusRange(size_t aIndex);
+ void RemoveAnchorFocusRange() { mAnchorFocusRange = nullptr; }
+ void SelectFramesOf(nsIContent* aContent, bool aSelected) const;
+
+ /**
+ * https://dom.spec.whatwg.org/#concept-tree-inclusive-descendant.
+ */
+ nsresult SelectFramesOfInclusiveDescendantsOfContent(
+ PostContentIterator& aPostOrderIter, nsIContent* aContent,
+ bool aSelected) const;
+
+ nsresult SelectFrames(nsPresContext* aPresContext, AbstractRange& aRange,
+ bool aSelect) const;
+
+ /**
+ * SelectFramesInAllRanges() calls SelectFrames() for all current
+ * ranges.
+ */
+ void SelectFramesInAllRanges(nsPresContext* aPresContext);
+
+ /**
+ * @param aOutIndex If some, points to the index of the range in
+ * mStyledRanges.mRanges so that it's always in [0, mStyledRanges.Length()].
+ * Otherwise, if nothing, this didn't add the range to mStyledRanges.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult MaybeAddTableCellRange(nsRange& aRange,
+ Maybe<size_t>* aOutIndex);
+
+ Document* GetDocument() const;
+
+ MOZ_CAN_RUN_SCRIPT void RemoveAllRangesInternal(mozilla::ErrorResult& aRv);
+
+ void Disconnect();
+
+ struct StyledRanges {
+ explicit StyledRanges(Selection& aSelection) : mSelection(aSelection) {}
+ void Clear();
+
+ StyledRange* FindRangeData(AbstractRange* aRange);
+
+ using StyledRangeArray = AutoTArray<StyledRange, 1>;
+
+ StyledRangeArray::size_type Length() const;
+
+ nsresult RemoveCollapsedRanges();
+
+ nsresult RemoveRangeAndUnregisterSelection(AbstractRange& aRange);
+
+ /**
+ * Binary searches the given sorted array of ranges for the insertion point
+ * for the given node/offset. The given comparator is used, and the index
+ * where the point should appear in the array is returned.
+
+ * If there is an item in the array equal to the input point (aPointNode,
+ * aPointOffset), we will return the index of this item.
+ *
+ * @return the index where the point should appear in the array. In
+ * [0, `aElementArray->Length()`].
+ */
+ static size_t FindInsertionPoint(
+ const nsTArray<StyledRange>* aElementArray, const nsINode& aPointNode,
+ uint32_t aPointOffset,
+ int32_t (*aComparator)(const nsINode&, uint32_t, const AbstractRange&));
+
+ /**
+ * Works on the same principle as GetRangesForIntervalArray, however
+ * instead this returns the indices into mRanges between which
+ * the overlapping ranges lie.
+ *
+ * @param aStartIndex If some, aEndIndex will also be some and the value of
+ * aStartIndex will be less or equal than aEndIndex. If
+ * nothing, aEndIndex will also be nothing and it means
+ * that there is no range which in the range.
+ * @param aEndIndex If some, the value is less than mRanges.Length().
+ */
+ nsresult GetIndicesForInterval(const nsINode* aBeginNode,
+ uint32_t aBeginOffset,
+ const nsINode* aEndNode, uint32_t aEndOffset,
+ bool aAllowAdjacent,
+ Maybe<size_t>& aStartIndex,
+ Maybe<size_t>& aEndIndex);
+
+ bool HasEqualRangeBoundariesAt(const AbstractRange& aRange,
+ size_t aRangeIndex) const;
+
+ /**
+ * Preserves the sorting and disjunctiveness of mRanges.
+ *
+ * @param aOutIndex If some, will point to the index of the added range, or
+ * if aRange is already contained, to the one containing
+ * it. Hence it'll always be in [0, mRanges.Length()).
+ * This is nothing only when the method returns an error.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult
+ MaybeAddRangeAndTruncateOverlaps(nsRange* aRange, Maybe<size_t>* aOutIndex);
+
+ /**
+ * Adds the range even if there are overlaps.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult
+ AddRangeAndIgnoreOverlaps(AbstractRange* aRange);
+
+ /**
+ * GetCommonEditingHost() returns common editing host of all
+ * ranges if there is. If at least one of the ranges is in non-editable
+ * element, returns nullptr. See following examples for the detail:
+ *
+ * <div id="a" contenteditable>
+ * an[cestor
+ * <div id="b" contenteditable="false">
+ * non-editable
+ * <div id="c" contenteditable>
+ * desc]endant
+ * in this case, this returns div#a because div#c is also in div#a.
+ *
+ * <div id="a" contenteditable>
+ * an[ce]stor
+ * <div id="b" contenteditable="false">
+ * non-editable
+ * <div id="c" contenteditable>
+ * de[sc]endant
+ * in this case, this returns div#a because second range is also in div#a
+ * and common ancestor of the range (i.e., div#c) is editable.
+ *
+ * <div id="a" contenteditable>
+ * an[ce]stor
+ * <div id="b" contenteditable="false">
+ * [non]-editable
+ * <div id="c" contenteditable>
+ * de[sc]endant
+ * in this case, this returns nullptr because the second range is in
+ * non-editable area.
+ */
+ Element* GetCommonEditingHost() const;
+
+ MOZ_CAN_RUN_SCRIPT void MaybeFocusCommonEditingHost(
+ PresShell* aPresShell) const;
+
+ static nsresult SubtractRange(StyledRange& aRange, nsRange& aSubtract,
+ nsTArray<StyledRange>* aOutput);
+
+ void UnregisterSelection();
+
+ // `mRanges` always needs to be sorted by the Range's start point.
+ // Especially when dealing with `StaticRange`s this is not guaranteed
+ // automatically. Therefore this method should be called before paint to
+ // ensure that any potential DOM mutations are incorporated in `mRanges`
+ // order. This method will also move invalid `StaticRange`s into
+ // `mInvalidStaticRanges` (and previously-invalid-now-valid-again
+ // `StaticRange`s back into `mRanges`).
+ void ReorderRangesIfNecessary();
+
+ // These are the ranges inside this selection. They are kept sorted in order
+ // of DOM start position.
+ //
+ // This data structure is sorted by the range beginnings. As the ranges are
+ // disjoint, it is also implicitly sorted by the range endings. This allows
+ // us to perform binary searches when searching for existence of a range,
+ // giving us O(log n) search time.
+ //
+ // Inserting a new range requires finding the overlapping interval,
+ // requiring two binary searches plus up to an additional 6 DOM comparisons.
+ // If this proves to be a performance concern, then an interval tree may be
+ // a possible solution, allowing the calculation of the overlap interval in
+ // O(log n) time, though this would require rebalancing and other overhead.
+ StyledRangeArray mRanges;
+
+ // With introduction of the custom highlight API, Selection must be able to
+ // hold `StaticRange`s as well. If they become invalid (eg. end is before
+ // start), they must be excluded from painting, but still kept.
+ // mRanges needs to contain valid ranges sorted correctly only. Therefore,
+ // invalid static ranges are being stored in this array, which is being kept
+ // up to date in `ReorderRangesIfNecessary()`.
+ StyledRangeArray mInvalidStaticRanges;
+
+ Selection& mSelection;
+
+ // The Document's generation for which `mRanges` have been ordered.
+ int32_t mDocumentGeneration{0};
+ // This flag indicates that ranges may have changed. It is set to true in
+ // `Selection::NotifySelectionListeners().`
+ bool mRangesMightHaveChanged{false};
+ };
+
+ StyledRanges mStyledRanges{*this};
+
+ RefPtr<nsRange> mAnchorFocusRange;
+ RefPtr<nsFrameSelection> mFrameSelection;
+ RefPtr<AccessibleCaretEventHub> mAccessibleCaretEventHub;
+ RefPtr<SelectionChangeEventDispatcher> mSelectionChangeEventDispatcher;
+ RefPtr<AutoScroller> mAutoScroller;
+ nsTArray<nsCOMPtr<nsISelectionListener>> mSelectionListeners;
+ nsRevocableEventPtr<ScrollSelectionIntoViewEvent> mScrollEvent;
+ CachedOffsetForFrame* mCachedOffsetForFrame;
+ nsDirection mDirection;
+ const SelectionType mSelectionType;
+ HighlightSelectionData mHighlightData;
+ UniquePtr<SelectionCustomColors> mCustomColors;
+
+ // Non-zero if we don't want any changes we make to the selection to be
+ // visible to content. If non-zero, content won't be notified about changes.
+ uint32_t mSelectionChangeBlockerCount;
+
+ /**
+ * True if the current selection operation was initiated by user action.
+ * It determines whether we exclude -moz-user-select:none nodes or not,
+ * as well as whether selectstart events will be fired.
+ */
+ bool mUserInitiated;
+
+ /**
+ * When the selection change is caused by a call of Selection API,
+ * mCalledByJS is true. Otherwise, false.
+ */
+ bool mCalledByJS;
+
+ /**
+ * true if AutoCopyListner::OnSelectionChange() should be called.
+ */
+ bool mNotifyAutoCopy;
+};
+
+// Stack-class to turn on/off selection batching.
+class MOZ_STACK_CLASS SelectionBatcher final {
+ private:
+ const RefPtr<Selection> mSelection;
+ const int16_t mReasons;
+ const char* const mRequesterFuncName;
+
+ public:
+ /**
+ * @param aRequesterFuncName function name which wants the selection batch.
+ * This won't be stored nor exposed to selection listeners etc, used only for
+ * logging. This MUST be living when the destructor runs.
+ */
+ // TODO: Mark these constructors `MOZ_CAN_RUN_SCRIPT` because the destructor
+ // may run script via nsISelectionListener.
+ explicit SelectionBatcher(Selection& aSelectionRef,
+ const char* aRequesterFuncName,
+ int16_t aReasons = nsISelectionListener::NO_REASON)
+ : SelectionBatcher(&aSelectionRef, aRequesterFuncName, aReasons) {}
+ explicit SelectionBatcher(Selection* aSelection,
+ const char* aRequesterFuncName,
+ int16_t aReasons = nsISelectionListener::NO_REASON)
+ : mSelection(aSelection),
+ mReasons(aReasons),
+ mRequesterFuncName(aRequesterFuncName) {
+ if (mSelection) {
+ mSelection->StartBatchChanges(mRequesterFuncName);
+ }
+ }
+
+ ~SelectionBatcher() {
+ if (mSelection) {
+ mSelection->EndBatchChanges(mRequesterFuncName, mReasons);
+ }
+ }
+};
+
+class MOZ_RAII AutoHideSelectionChanges final {
+ public:
+ explicit AutoHideSelectionChanges(const nsFrameSelection* aFrame);
+
+ explicit AutoHideSelectionChanges(Selection& aSelectionRef)
+ : AutoHideSelectionChanges(&aSelectionRef) {}
+
+ ~AutoHideSelectionChanges() {
+ if (mSelection) {
+ mSelection->RemoveSelectionChangeBlocker();
+ }
+ }
+
+ private:
+ explicit AutoHideSelectionChanges(Selection* aSelection)
+ : mSelection(aSelection) {
+ if (mSelection) {
+ mSelection->AddSelectionChangeBlocker();
+ }
+ }
+
+ RefPtr<Selection> mSelection;
+};
+
+} // namespace dom
+
+inline bool IsValidRawSelectionType(RawSelectionType aRawSelectionType) {
+ return aRawSelectionType >= nsISelectionController::SELECTION_NONE &&
+ aRawSelectionType <= nsISelectionController::SELECTION_URLSTRIKEOUT;
+}
+
+inline SelectionType ToSelectionType(RawSelectionType aRawSelectionType) {
+ if (!IsValidRawSelectionType(aRawSelectionType)) {
+ return SelectionType::eInvalid;
+ }
+ return static_cast<SelectionType>(aRawSelectionType);
+}
+
+inline RawSelectionType ToRawSelectionType(SelectionType aSelectionType) {
+ MOZ_ASSERT(aSelectionType != SelectionType::eInvalid);
+ return static_cast<RawSelectionType>(aSelectionType);
+}
+
+inline RawSelectionType ToRawSelectionType(TextRangeType aTextRangeType) {
+ return ToRawSelectionType(ToSelectionType(aTextRangeType));
+}
+
+inline SelectionTypeMask ToSelectionTypeMask(SelectionType aSelectionType) {
+ MOZ_ASSERT(aSelectionType != SelectionType::eInvalid);
+ return aSelectionType == SelectionType::eNone
+ ? 0
+ : static_cast<SelectionTypeMask>(
+ 1 << (static_cast<uint8_t>(aSelectionType) - 1));
+}
+
+inline std::ostream& operator<<(
+ std::ostream& aStream, const dom::Selection::InterlinePosition& aPosition) {
+ using InterlinePosition = dom::Selection::InterlinePosition;
+ switch (aPosition) {
+ case InterlinePosition::EndOfLine:
+ return aStream << "InterlinePosition::EndOfLine";
+ case InterlinePosition::StartOfNextLine:
+ return aStream << "InterlinePosition::StartOfNextLine";
+ case InterlinePosition::Undefined:
+ return aStream << "InterlinePosition::Undefined";
+ default:
+ MOZ_ASSERT_UNREACHABLE("Illegal value");
+ return aStream << "<Illegal value>";
+ }
+}
+
+} // namespace mozilla
+
+#endif // mozilla_Selection_h__