/* -*- 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/dom/StyledRange.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 "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 TableSelectionMode : uint32_t; struct AutoPrepareFocusRange; 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>& 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* 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* 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) { CollapseInternal(InLimiter::eYes, aPoint, aRv); } MOZ_CAN_RUN_SCRIPT nsresult Extend(nsINode* aContainer, uint32_t aOffset); /** * See mStyledRanges.mRanges. */ nsRange* GetRangeAt(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; nsIFrame* GetPrimaryFrameForFocusNode(bool aVisual, int32_t* aOffsetUsed = nullptr) const; UniquePtr LookUpSelection( nsIContent* aContent, uint32_t aContentOffset, uint32_t aContentLength, UniquePtr 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 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 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 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( nsRange& 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 GetCaretBidiLevel(mozilla::ErrorResult& aRv) const; void SetCaretBidiLevel(const Nullable& 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; } /** * 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>& 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(ErrorResult& aRv); /** * 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) { CollapseInternal(InLimiter::eYes, 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); /** * 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) { SetStartAndEndInternal(InLimiter::eNo, aStartRef, aEndRef, eDirNext, 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) { SetStartAndEndInternal(InLimiter::eYes, aStartRef, aEndRef, eDirNext, 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 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) { SetBaseAndExtentInternal(InLimiter::eNo, aAnchorRef, aFocusRef, 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) { SetBaseAndExtentInternal(InLimiter::eYes, aAnchorRef, aFocusRef, 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::GetRangesForIntervalArray // // 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 GetRangesForIntervalArray(nsINode* aBeginNode, uint32_t aBeginOffset, nsINode* aEndNode, uint32_t aEndOffset, bool aAllowAdjacent, nsTArray* 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 AddRangeAndSelectFramesAndNotifyListeners(nsRange& aRange, Document* aDocument, ErrorResult&); // This is helper method for GetPrimaryFrameForFocusNode. // If aVisual is true, this returns caret frame. // If false, this returns primary frame. nsIFrame* GetPrimaryOrCaretFrameForNodeOffset(nsIContent* aContent, uint32_t aOffset, int32_t* aOffsetUsed, bool aVisual) const; // 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 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, nsRange* 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* aOutIndex); Document* GetDocument() const; void Disconnect(); struct StyledRanges { void Clear(); StyledRange* FindRangeData(nsRange* aRange); using Elements = AutoTArray; Elements::size_type Length() const; nsresult RemoveCollapsedRanges(); nsresult RemoveRangeAndUnregisterSelection(nsRange& 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* aElementArray, const nsINode& aPointNode, uint32_t aPointOffset, int32_t (*aComparator)(const nsINode&, uint32_t, const nsRange&)); /** * 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& aStartIndex, Maybe& aEndIndex) const; bool HasEqualRangeBoundariesAt(const nsRange& 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* aOutIndex, Selection& aSelection); /** * 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: * *
* an[cestor *
* non-editable *
* desc]endant * in this case, this returns div#a because div#c is also in div#a. * *
* an[ce]stor *
* non-editable *
* 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. * *
* an[ce]stor *
* [non]-editable *
* 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* aOutput); void UnregisterSelection(); // 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. Elements mRanges; }; StyledRanges mStyledRanges; RefPtr mAnchorFocusRange; RefPtr mFrameSelection; RefPtr mAccessibleCaretEventHub; RefPtr mSelectionChangeEventDispatcher; RefPtr mAutoScroller; nsTArray> mSelectionListeners; nsRevocableEventPtr mScrollEvent; CachedOffsetForFrame* mCachedOffsetForFrame; nsDirection mDirection; const SelectionType mSelectionType; UniquePtr 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 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 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(aRawSelectionType); } inline RawSelectionType ToRawSelectionType(SelectionType aSelectionType) { MOZ_ASSERT(aSelectionType != SelectionType::eInvalid); return static_cast(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( 1 << (static_cast(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 << ""; } } } // namespace mozilla #endif // mozilla_Selection_h__