summaryrefslogtreecommitdiffstats
path: root/layout/generic/nsFrameSelection.h
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--layout/generic/nsFrameSelection.h1196
1 files changed, 1196 insertions, 0 deletions
diff --git a/layout/generic/nsFrameSelection.h b/layout/generic/nsFrameSelection.h
new file mode 100644
index 0000000000..76c4b21766
--- /dev/null
+++ b/layout/generic/nsFrameSelection.h
@@ -0,0 +1,1196 @@
+/* -*- 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 nsFrameSelection_h___
+#define nsFrameSelection_h___
+
+#include <stdint.h>
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CompactPair.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/Result.h"
+#include "mozilla/TextRange.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIFrame.h"
+#include "nsIContent.h"
+#include "nsISelectionController.h"
+#include "nsISelectionListener.h"
+#include "nsITableCellLayout.h"
+#include "WordMovementType.h"
+#include "CaretAssociationHint.h"
+#include "nsBidiPresUtils.h"
+
+class nsRange;
+
+#define BIDI_LEVEL_UNDEFINED mozilla::intl::BidiEmbeddingLevel(0x80)
+
+//----------------------------------------------------------------------
+
+// Selection interface
+
+struct SelectionDetails {
+ SelectionDetails()
+ : mStart(), mEnd(), mSelectionType(mozilla::SelectionType::eInvalid) {
+ MOZ_COUNT_CTOR(SelectionDetails);
+ }
+ MOZ_COUNTED_DTOR(SelectionDetails)
+
+ int32_t mStart;
+ int32_t mEnd;
+ mozilla::SelectionType mSelectionType;
+ RefPtr<const nsAtom> mHighlightName;
+ mozilla::TextRangeStyle mTextRangeStyle;
+ mozilla::UniquePtr<SelectionDetails> mNext;
+};
+
+struct SelectionCustomColors {
+#ifdef NS_BUILD_REFCNT_LOGGING
+ MOZ_COUNTED_DEFAULT_CTOR(SelectionCustomColors)
+ MOZ_COUNTED_DTOR(SelectionCustomColors)
+#endif
+ mozilla::Maybe<nscolor> mForegroundColor;
+ mozilla::Maybe<nscolor> mBackgroundColor;
+ mozilla::Maybe<nscolor> mAltForegroundColor;
+ mozilla::Maybe<nscolor> mAltBackgroundColor;
+};
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+/** PeekOffsetStruct is used to group various arguments (both input and output)
+ * that are passed to nsIFrame::PeekOffset(). See below for the description of
+ * individual arguments.
+ */
+
+namespace mozilla {
+
+enum class PeekOffsetOption : uint8_t {
+ // Whether to allow jumping across line boundaries.
+ //
+ // Used with: eSelectCharacter, eSelectWord.
+ JumpLines,
+
+ // Whether we should preserve or trim spaces at begin/end of content
+ PreserveSpaces,
+
+ // Whether to stop when reaching a scroll view boundary.
+ //
+ // Used with: eSelectCharacter, eSelectWord, eSelectLine.
+ ScrollViewStop,
+
+ // Whether the peeking is done in response to a keyboard action.
+ //
+ // Used with: eSelectWord.
+ IsKeyboardSelect,
+
+ // Whether bidi caret behavior is visual (set) or logical (unset).
+ //
+ // Used with: eSelectCharacter, eSelectWord, eSelectBeginLine, eSelectEndLine.
+ Visual,
+
+ // Whether the selection is being extended or moved.
+ Extend,
+
+ // If true, the offset has to end up in an editable node, otherwise we'll keep
+ // searching.
+ ForceEditableRegion,
+};
+
+using PeekOffsetOptions = EnumSet<PeekOffsetOption>;
+
+struct MOZ_STACK_CLASS PeekOffsetStruct {
+ PeekOffsetStruct(nsSelectionAmount aAmount, nsDirection aDirection,
+ int32_t aStartOffset, nsPoint aDesiredCaretPos,
+ // Passing by value here is intentional because EnumSet
+ // is optimized as uint*_t in opt builds.
+ const PeekOffsetOptions aOptions,
+ EWordMovementType aWordMovementType = eDefaultBehavior);
+
+ // Note: Most arguments (input and output) are only used with certain values
+ // of mAmount. These values are indicated for each argument below.
+ // Arguments with no such indication are used with all values of mAmount.
+
+ /*** Input arguments ***/
+ // Note: The value of some of the input arguments may be changed upon exit.
+
+ // The type of movement requested (by character, word, line, etc.)
+ nsSelectionAmount mAmount;
+
+ // eDirPrevious or eDirNext.
+ //
+ // Note for visual bidi movement:
+ // * eDirPrevious means 'left-then-up' if the containing block is LTR,
+ // 'right-then-up' if it is RTL.
+ // * eDirNext means 'right-then-down' if the containing block is LTR,
+ // 'left-then-down' if it is RTL.
+ // * Between paragraphs, eDirPrevious means "go to the visual end of
+ // the previous paragraph", and eDirNext means "go to the visual
+ // beginning of the next paragraph".
+ //
+ // Used with: eSelectCharacter, eSelectWord, eSelectLine, eSelectParagraph.
+ const nsDirection mDirection;
+
+ // Offset into the content of the current frame where the peek starts.
+ //
+ // Used with: eSelectCharacter, eSelectWord
+ int32_t mStartOffset;
+
+ // The desired inline coordinate for the caret (one of .x or .y will be used,
+ // depending on line's writing mode)
+ //
+ // Used with: eSelectLine.
+ const nsPoint mDesiredCaretPos;
+
+ // An enum that determines whether to prefer the start or end of a word or to
+ // use the default behavior, which is a combination of direction and the
+ // platform-based pref "layout.word_select.eat_space_to_next_word"
+ EWordMovementType mWordMovementType;
+
+ PeekOffsetOptions mOptions;
+
+ /*** Output arguments ***/
+
+ // Content reached as a result of the peek.
+ nsCOMPtr<nsIContent> mResultContent;
+
+ // Frame reached as a result of the peek.
+ //
+ // Used with: eSelectCharacter, eSelectWord.
+ nsIFrame* mResultFrame;
+
+ // Offset into content reached as a result of the peek.
+ int32_t mContentOffset;
+
+ // When the result position is between two frames, indicates which of the two
+ // frames the caret should be painted in. false means "the end of the frame
+ // logically before the caret", true means "the beginning of the frame
+ // logically after the caret".
+ //
+ // Used with: eSelectLine, eSelectBeginLine, eSelectEndLine.
+ CaretAssociationHint mAttach;
+};
+
+} // namespace mozilla
+
+struct nsPrevNextBidiLevels {
+ void SetData(nsIFrame* aFrameBefore, nsIFrame* aFrameAfter,
+ mozilla::intl::BidiEmbeddingLevel aLevelBefore,
+ mozilla::intl::BidiEmbeddingLevel aLevelAfter) {
+ mFrameBefore = aFrameBefore;
+ mFrameAfter = aFrameAfter;
+ mLevelBefore = aLevelBefore;
+ mLevelAfter = aLevelAfter;
+ }
+ nsIFrame* mFrameBefore;
+ nsIFrame* mFrameAfter;
+ mozilla::intl::BidiEmbeddingLevel mLevelBefore;
+ mozilla::intl::BidiEmbeddingLevel mLevelAfter;
+};
+
+namespace mozilla {
+class SelectionChangeEventDispatcher;
+namespace dom {
+class Highlight;
+class Selection;
+} // namespace dom
+
+/**
+ * Constants for places that want to handle table selections. These
+ * indicate what part of a table is being selected.
+ */
+enum class TableSelectionMode : uint32_t {
+ None, /* Nothing being selected; not valid in all cases. */
+ Cell, /* A cell is being selected. */
+ Row, /* A row is being selected. */
+ Column, /* A column is being selected. */
+ Table, /* A table (including cells and captions) is being selected. */
+ AllCells, /* All the cells in a table are being selected. */
+};
+
+} // namespace mozilla
+class nsIScrollableFrame;
+
+class nsFrameSelection final {
+ public:
+ typedef mozilla::CaretAssociationHint CaretAssociateHint;
+
+ /*interfaces for addref and release and queryinterface*/
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsFrameSelection)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsFrameSelection)
+
+ enum class FocusMode {
+ kExtendSelection, /** Keep old anchor point. */
+ kCollapseToNewPoint, /** Collapses the Selection to the new point. */
+ kMultiRangeSelection, /** Keeps existing non-collapsed ranges and marks them
+ as generated. */
+ };
+
+ /**
+ * HandleClick will take the focus to the new frame at the new offset and
+ * will either extend the selection from the old anchor, or replace the old
+ * anchor. the old anchor and focus position may also be used to deselect
+ * things
+ *
+ * @param aNewfocus is the content that wants the focus
+ *
+ * @param aContentOffset is the content offset of the parent aNewFocus
+ *
+ * @param aContentOffsetEnd is the content offset of the parent aNewFocus and
+ * is specified different when you need to select to and include both start
+ * and end points
+ *
+ * @param aHint will tell the selection which direction geometrically to
+ * actually show the caret on. 1 = end of this line 0 = beginning of this line
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult HandleClick(nsIContent* aNewFocus,
+ uint32_t aContentOffset,
+ uint32_t aContentEndOffset,
+ FocusMode aFocusMode,
+ CaretAssociateHint aHint);
+
+ public:
+ /**
+ * Sets flag to true if a selection is created by doubleclick or
+ * long tapping a word.
+ *
+ * @param aIsDoubleClickSelection True if the selection is created by
+ * doubleclick or long tap over a word.
+ */
+ void SetIsDoubleClickSelection(bool aIsDoubleClickSelection) {
+ mIsDoubleClickSelection = aIsDoubleClickSelection;
+ }
+
+ /**
+ * Returns true if the selection was created by doubleclick or
+ * long tap over a word.
+ */
+ [[nodiscard]] bool IsDoubleClickSelection() const {
+ return mIsDoubleClickSelection;
+ }
+
+ /**
+ * HandleDrag extends the selection to contain the frame closest to aPoint.
+ *
+ * @param aPresContext is the context to use when figuring out what frame
+ * contains the point.
+ *
+ * @param aFrame is the parent of all frames to use when searching for the
+ * closest frame to the point.
+ *
+ * @param aPoint is relative to aFrame
+ */
+ MOZ_CAN_RUN_SCRIPT void HandleDrag(nsIFrame* aFrame, const nsPoint& aPoint);
+
+ /**
+ * HandleTableSelection will set selection to a table, cell, etc
+ * depending on information contained in aFlags
+ *
+ * @param aParentContent is the paretent of either a table or cell that user
+ * clicked or dragged the mouse in
+ *
+ * @param aContentOffset is the offset of the table or cell
+ *
+ * @param aTarget indicates what to select
+ * * TableSelectionMode::Cell
+ * We should select a cell (content points to the cell)
+ * * TableSelectionMode::Row
+ * We should select a row (content points to any cell in row)
+ * * TableSelectionMode::Column
+ * We should select a row (content points to any cell in column)
+ * * TableSelectionMode::Table
+ * We should select a table (content points to the table)
+ * * TableSelectionMode::AllCells
+ * We should select all cells (content points to any cell in table)
+ *
+ * @param aMouseEvent passed in so we can get where event occurred
+ * and what keys are pressed
+ */
+ // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
+ HandleTableSelection(nsINode* aParentContent, int32_t aContentOffset,
+ mozilla::TableSelectionMode aTarget,
+ mozilla::WidgetMouseEvent* aMouseEvent);
+
+ /**
+ * Add cell to the selection with `SelectionType::eNormal`.
+ *
+ * @param aCell [in] HTML td element.
+ */
+ nsresult SelectCellElement(nsIContent* aCell);
+
+ public:
+ /**
+ * Remove cells from selection inside of the given cell range.
+ *
+ * @param aTable [in] HTML table element
+ * @param aStartRowIndex [in] row index where the cells range starts
+ * @param aStartColumnIndex [in] column index where the cells range starts
+ * @param aEndRowIndex [in] row index where the cells range ends
+ * @param aEndColumnIndex [in] column index where the cells range ends
+ */
+ // TODO: annotate this with `MOZ_CAN_RUN_SCRIPT` instead.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ nsresult RemoveCellsFromSelection(nsIContent* aTable, int32_t aStartRowIndex,
+ int32_t aStartColumnIndex,
+ int32_t aEndRowIndex,
+ int32_t aEndColumnIndex);
+
+ /**
+ * Remove cells from selection outside of the given cell range.
+ *
+ * @param aTable [in] HTML table element
+ * @param aStartRowIndex [in] row index where the cells range starts
+ * @param aStartColumnIndex [in] column index where the cells range starts
+ * @param aEndRowIndex [in] row index where the cells range ends
+ * @param aEndColumnIndex [in] column index where the cells range ends
+ */
+ // TODO: annotate this with `MOZ_CAN_RUN_SCRIPT` instead.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ nsresult RestrictCellsToSelection(nsIContent* aTable, int32_t aStartRowIndex,
+ int32_t aStartColumnIndex,
+ int32_t aEndRowIndex,
+ int32_t aEndColumnIndex);
+
+ /**
+ * StartAutoScrollTimer is responsible for scrolling frames so that
+ * aPoint is always visible, and for selecting any frame that contains
+ * aPoint. The timer will also reset itself to fire again if we have
+ * not scrolled to the end of the document.
+ *
+ * @param aFrame is the outermost frame to use when searching for
+ * the closest frame for the point, i.e. the frame that is capturing
+ * the mouse
+ *
+ * @param aPoint is relative to aFrame.
+ *
+ * @param aDelay is the timer's interval.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsresult StartAutoScrollTimer(nsIFrame* aFrame, const nsPoint& aPoint,
+ uint32_t aDelay);
+
+ /**
+ * Stops any active auto scroll timer.
+ */
+ void StopAutoScrollTimer();
+
+ /**
+ * Returns in frame coordinates the selection beginning and ending with the
+ * type of selection given
+ *
+ * @param aContent is the content asking
+ * @param aContentOffset is the starting content boundary
+ * @param aContentLength is the length of the content piece asking
+ * @param aSlowCheck will check using slow method with no shortcuts
+ */
+ mozilla::UniquePtr<SelectionDetails> LookUpSelection(nsIContent* aContent,
+ int32_t aContentOffset,
+ int32_t aContentLength,
+ bool aSlowCheck) const;
+
+ /**
+ * Sets the drag state to aState for resons of drag state.
+ *
+ * @param aState is the new state of drag
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void SetDragState(bool aState);
+
+ /**
+ * Gets the drag state to aState for resons of drag state.
+ *
+ * @param aState will hold the state of drag
+ */
+ bool GetDragState() const { return mDragState; }
+
+ /**
+ * If we are in table cell selection mode. aka ctrl click in table cell
+ */
+ bool IsInTableSelectionMode() const {
+ return mTableSelection.mMode != mozilla::TableSelectionMode::None;
+ }
+ void ClearTableCellSelection() {
+ mTableSelection.mMode = mozilla::TableSelectionMode::None;
+ }
+
+ /**
+ * No query interface for selection. must use this method now.
+ *
+ * @param aSelectionType The selection type what you want.
+ */
+ mozilla::dom::Selection* GetSelection(
+ mozilla::SelectionType aSelectionType) const;
+
+ /**
+ * @brief Adds a highlight selection for `aHighlight`.
+ */
+ MOZ_CAN_RUN_SCRIPT void AddHighlightSelection(
+ const nsAtom* aHighlightName, const mozilla::dom::Highlight& aHighlight);
+ /**
+ * @brief Removes the Highlight selection identified by `aHighlightName`.
+ */
+ MOZ_CAN_RUN_SCRIPT void RemoveHighlightSelection(
+ const nsAtom* aHighlightName);
+
+ /**
+ * @brief Adds a new range to the highlight selection.
+ *
+ * If there is no highlight selection for the given highlight yet, it is
+ * created using |AddHighlightSelection|.
+ */
+ MOZ_CAN_RUN_SCRIPT void AddHighlightSelectionRange(
+ const nsAtom* aHighlightName, const mozilla::dom::Highlight& aHighlight,
+ mozilla::dom::AbstractRange& aRange);
+
+ /**
+ * @brief Removes a range from a highlight selection.
+ */
+ MOZ_CAN_RUN_SCRIPT void RemoveHighlightSelectionRange(
+ const nsAtom* aHighlightName, mozilla::dom::AbstractRange& aRange);
+ /**
+ * ScrollSelectionIntoView scrolls a region of the selection,
+ * so that it is visible in the scrolled view.
+ *
+ * @param aSelectionType the selection to scroll into view.
+ *
+ * @param aRegion the region inside the selection to scroll into view.
+ *
+ * @param aFlags the scroll flags. Valid bits include:
+ * * SCROLL_SYNCHRONOUS: when set, scrolls the selection into view
+ * before returning. If not set, posts a request which is processed
+ * at some point after the method returns.
+ * * SCROLL_FIRST_ANCESTOR_ONLY: if set, only the first ancestor will be
+ * scrolled into view.
+ */
+ // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
+ ScrollSelectionIntoView(mozilla::SelectionType aSelectionType,
+ SelectionRegion aRegion, int16_t aFlags) const;
+
+ /**
+ * RepaintSelection repaints the selected frames that are inside the
+ * selection specified by aSelectionType.
+ *
+ * @param aSelectionType The selection type what you want to repaint.
+ */
+ nsresult RepaintSelection(mozilla::SelectionType aSelectionType);
+
+ bool IsValidSelectionPoint(nsINode* aNode) const;
+
+ static bool AdjustFrameForLineStart(nsIFrame*& aFrame, int32_t& aFrameOffset);
+
+ /**
+ * Given a node and its child offset, return the nsIFrame and the offset into
+ * that frame.
+ *
+ * @param aNode input parameter for the node to look at
+ * TODO: Make this `const nsIContent*` for `ContentEventHandler`.
+ * @param aOffset offset into above node.
+ * @param aReturnOffset will contain offset into frame.
+ */
+ static nsIFrame* GetFrameForNodeOffset(nsIContent* aNode, int32_t aOffset,
+ CaretAssociateHint aHint,
+ int32_t* aReturnOffset);
+
+ /**
+ * GetFrameToPageSelect() returns a frame which is ancestor limit of
+ * per-page selection. The frame may not be scrollable. E.g.,
+ * when selection ancestor limit is set to a frame of an editing host of
+ * contenteditable element and it's not scrollable.
+ */
+ nsIFrame* GetFrameToPageSelect() const;
+
+ /**
+ * This method moves caret (if aExtend is false) or expands selection (if
+ * aExtend is true). Then, scrolls aFrame one page. Finally, this may
+ * call ScrollSelectionIntoView() for making focus of selection visible
+ * but depending on aSelectionIntoView value.
+ *
+ * @param aForward if true, scroll forward if not scroll backward
+ * @param aExtend if true, extend selection to the new point
+ * @param aFrame the frame to scroll or container of per-page selection.
+ * if aExtend is true and selection may have ancestor limit,
+ * should set result of GetFrameToPageSelect().
+ * @param aSelectionIntoView
+ * If IfChanged, this makes selection into view only when
+ * selection is modified by the call.
+ * If Yes, this makes selection into view always.
+ */
+ enum class SelectionIntoView { IfChanged, Yes };
+ MOZ_CAN_RUN_SCRIPT nsresult PageMove(bool aForward, bool aExtend,
+ nsIFrame* aFrame,
+ SelectionIntoView aSelectionIntoView);
+
+ void SetHint(CaretAssociateHint aHintRight) { mCaret.mHint = aHintRight; }
+ CaretAssociateHint GetHint() const { return mCaret.mHint; }
+
+ void SetCaretBidiLevelAndMaybeSchedulePaint(
+ mozilla::intl::BidiEmbeddingLevel aLevel);
+
+ /**
+ * GetCaretBidiLevel gets the caret bidi level.
+ */
+ mozilla::intl::BidiEmbeddingLevel GetCaretBidiLevel() const;
+
+ /**
+ * UndefineCaretBidiLevel sets the caret bidi level to "undefined".
+ */
+ void UndefineCaretBidiLevel();
+
+ /**
+ * PhysicalMove will generally be called from the nsiselectioncontroller
+ * implementations. the effect being the selection will move one unit
+ * 'aAmount' in the given aDirection.
+ * @param aDirection the direction to move the selection
+ * @param aAmount amount of movement (char/line; word/page; eol/doc)
+ * @param aExtend continue selection
+ */
+ // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult PhysicalMove(int16_t aDirection,
+ int16_t aAmount,
+ bool aExtend);
+
+ /**
+ * CharacterMove will generally be called from the nsiselectioncontroller
+ * implementations. the effect being the selection will move one character
+ * left or right.
+ * @param aForward move forward in document.
+ * @param aExtend continue selection
+ */
+ // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult CharacterMove(bool aForward,
+ bool aExtend);
+
+ /**
+ * WordMove will generally be called from the nsiselectioncontroller
+ * implementations. the effect being the selection will move one word left or
+ * right.
+ * @param aForward move forward in document.
+ * @param aExtend continue selection
+ */
+ // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult WordMove(bool aForward, bool aExtend);
+
+ /**
+ * LineMove will generally be called from the nsiselectioncontroller
+ * implementations. the effect being the selection will move one line up or
+ * down.
+ * @param aForward move forward in document.
+ * @param aExtend continue selection
+ */
+ // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult LineMove(bool aForward, bool aExtend);
+
+ /**
+ * IntraLineMove will generally be called from the nsiselectioncontroller
+ * implementations. the effect being the selection will move to beginning or
+ * end of line
+ * @param aForward move forward in document.
+ * @param aExtend continue selection
+ */
+ // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult IntraLineMove(bool aForward,
+ bool aExtend);
+
+ /**
+ * CreateRangeExtendedToNextGraphemeClusterBoundary() returns range which is
+ * extended from normal selection range to start of next grapheme cluster
+ * boundary.
+ */
+ template <typename RangeType>
+ MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
+ CreateRangeExtendedToNextGraphemeClusterBoundary() {
+ return CreateRangeExtendedToSomewhere<RangeType>(eDirNext, eSelectCluster,
+ eLogical);
+ }
+
+ /**
+ * CreateRangeExtendedToPreviousCharacterBoundary() returns range which is
+ * extended from normal selection range to start of previous character
+ * boundary.
+ */
+ template <typename RangeType>
+ MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
+ CreateRangeExtendedToPreviousCharacterBoundary() {
+ return CreateRangeExtendedToSomewhere<RangeType>(
+ eDirPrevious, eSelectCharacter, eLogical);
+ }
+
+ /**
+ * CreateRangeExtendedToNextWordBoundary() returns range which is
+ * extended from normal selection range to start of next word boundary.
+ */
+ template <typename RangeType>
+ MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
+ CreateRangeExtendedToNextWordBoundary() {
+ return CreateRangeExtendedToSomewhere<RangeType>(eDirNext, eSelectWord,
+ eLogical);
+ }
+
+ /**
+ * CreateRangeExtendedToPreviousWordBoundary() returns range which is
+ * extended from normal selection range to start of previous word boundary.
+ */
+ template <typename RangeType>
+ MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
+ CreateRangeExtendedToPreviousWordBoundary() {
+ return CreateRangeExtendedToSomewhere<RangeType>(eDirPrevious, eSelectWord,
+ eLogical);
+ }
+
+ /**
+ * CreateRangeExtendedToPreviousHardLineBreak() returns range which is
+ * extended from normal selection range to previous hard line break.
+ */
+ template <typename RangeType>
+ MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
+ CreateRangeExtendedToPreviousHardLineBreak() {
+ return CreateRangeExtendedToSomewhere<RangeType>(
+ eDirPrevious, eSelectBeginLine, eLogical);
+ }
+
+ /**
+ * CreateRangeExtendedToNextHardLineBreak() returns range which is extended
+ * from normal selection range to next hard line break.
+ */
+ template <typename RangeType>
+ MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
+ CreateRangeExtendedToNextHardLineBreak() {
+ return CreateRangeExtendedToSomewhere<RangeType>(eDirNext, eSelectEndLine,
+ eLogical);
+ }
+
+ /** Sets/Gets The display selection enum.
+ */
+ void SetDisplaySelection(int16_t aState) { mDisplaySelection = aState; }
+ int16_t GetDisplaySelection() const { return mDisplaySelection; }
+
+ /**
+ * This method can be used to store the data received during a MouseDown
+ * event so that we can place the caret during the MouseUp event.
+ *
+ * @param aMouseEvent the event received by the selection MouseDown
+ * handling method. A nullptr value can be use to tell this method
+ * that any data is storing is no longer valid.
+ */
+ void SetDelayedCaretData(mozilla::WidgetMouseEvent* aMouseEvent);
+
+ /**
+ * Get the delayed MouseDown event data necessary to place the
+ * caret during MouseUp processing.
+ *
+ * @return a pointer to the event received
+ * by the selection during MouseDown processing. It can be nullptr
+ * if the data is no longer valid.
+ */
+ bool HasDelayedCaretData() const { return mDelayedMouseEvent.mIsValid; }
+ bool IsShiftDownInDelayedCaretData() const {
+ NS_ASSERTION(mDelayedMouseEvent.mIsValid, "No valid delayed caret data");
+ return mDelayedMouseEvent.mIsShift;
+ }
+ uint32_t GetClickCountInDelayedCaretData() const {
+ NS_ASSERTION(mDelayedMouseEvent.mIsValid, "No valid delayed caret data");
+ return mDelayedMouseEvent.mClickCount;
+ }
+
+ bool MouseDownRecorded() const {
+ return !GetDragState() && HasDelayedCaretData() &&
+ GetClickCountInDelayedCaretData() < 2;
+ }
+
+ /**
+ * Get the content node that limits the selection
+ *
+ * When searching up a nodes for parents, as in a text edit field
+ * in an browser page, we must stop at this node else we reach into the
+ * parent page, which is very bad!
+ */
+ nsIContent* GetLimiter() const { return mLimiters.mLimiter; }
+
+ nsIContent* GetAncestorLimiter() const { return mLimiters.mAncestorLimiter; }
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void SetAncestorLimiter(nsIContent* aLimiter);
+
+ /**
+ * GetPrevNextBidiLevels will return the frames and associated Bidi levels of
+ * the characters logically before and after a (collapsed) selection.
+ *
+ * @param aNode is the node containing the selection
+ * @param aContentOffset is the offset of the selection in the node
+ * @param aJumpLines
+ * If true, look across line boundaries.
+ * If false, behave as if there were base-level frames at line edges.
+ *
+ * @return A struct holding the before/after frame and the before/after
+ * level.
+ *
+ * At the beginning and end of each line there is assumed to be a frame with
+ * Bidi level equal to the paragraph embedding level.
+ *
+ * In these cases the before frame and after frame respectively will be
+ * nullptr.
+ */
+ nsPrevNextBidiLevels GetPrevNextBidiLevels(nsIContent* aNode,
+ uint32_t aContentOffset,
+ bool aJumpLines) const;
+
+ /**
+ * GetFrameFromLevel will scan in a given direction
+ * until it finds a frame with a Bidi level less than or equal to a given
+ * level. It will return the last frame before this.
+ *
+ * @param aPresContext is the context to use
+ * @param aFrameIn is the frame to start from
+ * @param aDirection is the direction to scan
+ * @param aBidiLevel is the level to search for
+ * @param aFrameOut will hold the frame returned
+ */
+ nsresult GetFrameFromLevel(nsIFrame* aFrameIn, nsDirection aDirection,
+ mozilla::intl::BidiEmbeddingLevel aBidiLevel,
+ nsIFrame** aFrameOut) const;
+
+ /**
+ * MaintainSelection will track the normal selection as being "sticky".
+ * Dragging or extending selection will never allow for a subset
+ * (or the whole) of the maintained selection to become unselected.
+ * Primary use: double click selecting then dragging on second click
+ *
+ * @param aAmount the initial amount of text selected (word, line or
+ * paragraph). For "line", use eSelectBeginLine.
+ */
+ nsresult MaintainSelection(nsSelectionAmount aAmount = eSelectNoAmount);
+
+ MOZ_CAN_RUN_SCRIPT nsresult ConstrainFrameAndPointToAnchorSubtree(
+ nsIFrame* aFrame, const nsPoint& aPoint, nsIFrame** aRetFrame,
+ nsPoint& aRetPoint) const;
+
+ /**
+ * @param aPresShell is the parameter to be used for most of the other calls
+ * for callbacks etc
+ *
+ * @param aLimiter limits the selection to nodes with aLimiter parents
+ *
+ * @param aAccessibleCaretEnabled true if we should enable the accessible
+ * caret.
+ */
+ nsFrameSelection(mozilla::PresShell* aPresShell, nsIContent* aLimiter,
+ bool aAccessibleCaretEnabled);
+
+ /**
+ * @param aRequesterFuncName function name which wants to start the batch.
+ * This won't be stored nor exposed to selection listeners etc, used only for
+ * logging.
+ */
+ void StartBatchChanges(const char* aRequesterFuncName);
+
+ /**
+ * @param aRequesterFuncName function name which wants to end the batch.
+ * This won't be stored nor exposed to selection listeners etc, used only for
+ * logging.
+ * @param aReasons potentially multiple of the reasons defined in
+ * nsISelectionListener.idl
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void EndBatchChanges(
+ const char* aRequesterFuncName,
+ int16_t aReasons = nsISelectionListener::NO_REASON);
+
+ mozilla::PresShell* GetPresShell() const { return mPresShell; }
+
+ void DisconnectFromPresShell();
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult ClearNormalSelection();
+
+ // Table selection support.
+ static nsITableCellLayout* GetCellLayout(const nsIContent* aCellContent);
+
+ private:
+ ~nsFrameSelection();
+
+ // TODO: in case an error is returned, it sometimes refers to a programming
+ // error, in other cases to runtime errors. This deserves to be cleaned up.
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ TakeFocus(nsIContent& aNewFocus, uint32_t aContentOffset,
+ uint32_t aContentEndOffset, CaretAssociateHint aHint,
+ FocusMode aFocusMode);
+
+ /**
+ * After moving the caret, its Bidi level is set according to the following
+ * rules:
+ *
+ * After moving over a character with left/right arrow, set to the Bidi level
+ * of the last moved over character. After Home and End, set to the paragraph
+ * embedding level. After up/down arrow, PageUp/Down, set to the lower level
+ * of the 2 surrounding characters. After mouse click, set to the level of the
+ * current frame.
+ *
+ * The following two methods use GetPrevNextBidiLevels to determine the new
+ * Bidi level. BidiLevelFromMove is called when the caret is moved in response
+ * to a keyboard event
+ *
+ * @param aPresShell is the presentation shell
+ * @param aNode is the content node
+ * @param aContentOffset is the new caret position, as an offset into aNode
+ * @param aAmount is the amount of the move that gave the caret its new
+ * position
+ * @param aHint is the hint indicating in what logical direction the caret
+ * moved
+ */
+ void BidiLevelFromMove(mozilla::PresShell* aPresShell, nsIContent* aNode,
+ uint32_t aContentOffset, nsSelectionAmount aAmount,
+ CaretAssociateHint aHint);
+ /**
+ * BidiLevelFromClick is called when the caret is repositioned by clicking the
+ * mouse
+ *
+ * @param aNode is the content node
+ * @param aContentOffset is the new caret position, as an offset into aNode
+ */
+ void BidiLevelFromClick(nsIContent* aNewFocus, uint32_t aContentOffset);
+
+ static nsPrevNextBidiLevels GetPrevNextBidiLevels(nsIContent* aNode,
+ uint32_t aContentOffset,
+ CaretAssociateHint aHint,
+ bool aJumpLines);
+
+ /**
+ * @param aReasons potentially multiple of the reasons defined in
+ * nsISelectionListener.idl.
+ */
+ void SetChangeReasons(int16_t aReasons) {
+ mSelectionChangeReasons = aReasons;
+ }
+
+ /**
+ * @param aReasons potentially multiple of the reasons defined in
+ * nsISelectionListener.idl.
+ */
+ void AddChangeReasons(int16_t aReasons) {
+ mSelectionChangeReasons |= aReasons;
+ }
+
+ /**
+ * @return potentially multiple of the reasons defined in
+ * nsISelectionListener.idl.
+ */
+ int16_t PopChangeReasons() {
+ int16_t retval = mSelectionChangeReasons;
+ mSelectionChangeReasons = nsISelectionListener::NO_REASON;
+ return retval;
+ }
+
+ nsSelectionAmount GetCaretMoveAmount() { return mCaretMoveAmount; }
+
+ bool IsUserSelectionReason() const {
+ return (mSelectionChangeReasons &
+ (nsISelectionListener::DRAG_REASON |
+ nsISelectionListener::MOUSEDOWN_REASON |
+ nsISelectionListener::MOUSEUP_REASON |
+ nsISelectionListener::KEYPRESS_REASON)) !=
+ nsISelectionListener::NO_REASON;
+ }
+
+ friend class mozilla::dom::Selection;
+ friend class mozilla::SelectionChangeEventDispatcher;
+ friend struct mozilla::AutoPrepareFocusRange;
+
+ /*HELPER METHODS*/
+ // Whether MoveCaret should use logical or visual movement,
+ // or follow the bidi.edit.caret_movement_style preference.
+ enum CaretMovementStyle { eLogical, eVisual, eUsePrefStyle };
+ MOZ_CAN_RUN_SCRIPT nsresult MoveCaret(nsDirection aDirection,
+ bool aContinueSelection,
+ nsSelectionAmount aAmount,
+ CaretMovementStyle aMovementStyle);
+
+ /**
+ * PeekOffsetForCaretMove() only peek offset for caret move. I.e., won't
+ * change selection ranges nor bidi information.
+ */
+ mozilla::Result<mozilla::PeekOffsetStruct, nsresult> PeekOffsetForCaretMove(
+ nsDirection aDirection, bool aContinueSelection,
+ const nsSelectionAmount aAmount, CaretMovementStyle aMovementStyle,
+ const nsPoint& aDesiredCaretPos) const;
+
+ /**
+ * CreateRangeExtendedToSomewhere() is common method to implement
+ * CreateRangeExtendedTo*(). This method creates a range extended from
+ * normal selection range.
+ */
+ template <typename RangeType>
+ MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
+ CreateRangeExtendedToSomewhere(nsDirection aDirection,
+ const nsSelectionAmount aAmount,
+ CaretMovementStyle aMovementStyle);
+
+ /**
+ * IsIntraLineCaretMove() is a helper method for PeekOffsetForCaretMove()
+ * and CreateRangeExtendedToSomwhereFromNormalSelection(). This returns
+ * whether aAmount is intra line move or is crossing hard line break.
+ * This returns error if aMount is not supported by the methods.
+ */
+ static mozilla::Result<bool, nsresult> IsIntraLineCaretMove(
+ nsSelectionAmount aAmount) {
+ switch (aAmount) {
+ case eSelectCharacter:
+ case eSelectCluster:
+ case eSelectWord:
+ case eSelectWordNoSpace:
+ case eSelectBeginLine:
+ case eSelectEndLine:
+ return true;
+ case eSelectLine:
+ return false;
+ default:
+ return mozilla::Err(NS_ERROR_FAILURE);
+ }
+ }
+
+ void InvalidateDesiredCaretPos(); // do not listen to mDesiredCaretPos.mValue
+ // you must get another.
+
+ bool IsBatching() const { return mBatching.mCounter > 0; }
+
+ void SetChangesDuringBatchingFlag() {
+ MOZ_ASSERT(mBatching.mCounter > 0);
+
+ mBatching.mChangesDuringBatching = true;
+ }
+
+ // nsFrameSelection may get deleted when calling this,
+ // so remember to use nsCOMPtr when needed.
+ MOZ_CAN_RUN_SCRIPT
+ nsresult NotifySelectionListeners(mozilla::SelectionType aSelectionType);
+
+ static nsresult GetCellIndexes(const nsIContent* aCell, int32_t& aRowIndex,
+ int32_t& aColIndex);
+
+ static nsIContent* GetFirstCellNodeInRange(const nsRange* aRange);
+ // Returns non-null table if in same table, null otherwise
+ static nsIContent* IsInSameTable(const nsIContent* aContent1,
+ const nsIContent* aContent2);
+ // Might return null
+ static nsIContent* GetParentTable(const nsIContent* aCellNode);
+
+ ////////////BEGIN nsFrameSelection members
+
+ RefPtr<mozilla::dom::Selection>
+ mDomSelections[sizeof(mozilla::kPresentSelectionTypes) /
+ sizeof(mozilla::SelectionType)];
+
+ nsTArray<mozilla::CompactPair<RefPtr<const nsAtom>,
+ RefPtr<mozilla::dom::Selection>>>
+ mHighlightSelections;
+
+ struct TableSelection {
+ // Get our first range, if its first selected node is a cell. If this does
+ // not return null, then the first node in the returned range is a cell
+ // (according to GetFirstCellNodeInRange).
+ nsRange* GetFirstCellRange(const mozilla::dom::Selection& aNormalSelection);
+
+ // Get our next range, if its first selected node is a cell. If this does
+ // not return null, then the first node in the returned range is a cell
+ // (according to GetFirstCellNodeInRange).
+ nsRange* GetNextCellRange(const mozilla::dom::Selection& aNormalSelection);
+
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ HandleSelection(nsINode* aParentContent, int32_t aContentOffset,
+ mozilla::TableSelectionMode aTarget,
+ mozilla::WidgetMouseEvent* aMouseEvent, bool aDragState,
+ mozilla::dom::Selection& aNormalSelection);
+
+ /**
+ * @return the closest inclusive table cell ancestor
+ * (https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor) of
+ * aContent, if it is actively editable.
+ */
+ static nsINode* IsContentInActivelyEditableTableCell(
+ nsPresContext* aContext, nsIContent* aContent);
+
+ // TODO: annotate this with `MOZ_CAN_RUN_SCRIPT` instead.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ nsresult SelectBlockOfCells(nsIContent* aStartCell, nsIContent* aEndCell,
+ mozilla::dom::Selection& aNormalSelection);
+
+ nsresult SelectRowOrColumn(nsIContent* aCellContent,
+ mozilla::dom::Selection& aNormalSelection);
+
+ MOZ_CAN_RUN_SCRIPT nsresult
+ UnselectCells(const nsIContent* aTable, int32_t aStartRowIndex,
+ int32_t aStartColumnIndex, int32_t aEndRowIndex,
+ int32_t aEndColumnIndex, bool aRemoveOutsideOfCellRange,
+ mozilla::dom::Selection& aNormalSelection);
+
+ nsCOMPtr<nsINode>
+ mClosestInclusiveTableCellAncestor; // used to snap to table selection
+ nsCOMPtr<nsIContent> mStartSelectedCell;
+ nsCOMPtr<nsIContent> mEndSelectedCell;
+ nsCOMPtr<nsIContent> mAppendStartSelectedCell;
+ nsCOMPtr<nsIContent> mUnselectCellOnMouseUp;
+ mozilla::TableSelectionMode mMode = mozilla::TableSelectionMode::None;
+ int32_t mSelectedCellIndex = 0;
+ bool mDragSelectingCells = false;
+
+ private:
+ struct MOZ_STACK_CLASS FirstAndLastCell {
+ nsCOMPtr<nsIContent> mFirst;
+ nsCOMPtr<nsIContent> mLast;
+ };
+
+ mozilla::Result<FirstAndLastCell, nsresult>
+ FindFirstAndLastCellOfRowOrColumn(const nsIContent& aCellContent) const;
+
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult HandleDragSelecting(
+ mozilla::TableSelectionMode aTarget, nsIContent* aChildContent,
+ const mozilla::WidgetMouseEvent* aMouseEvent,
+ mozilla::dom::Selection& aNormalSelection);
+
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleMouseUpOrDown(
+ mozilla::TableSelectionMode aTarget, bool aDragState,
+ nsIContent* aChildContent, nsINode* aParentContent,
+ int32_t aContentOffset, const mozilla::WidgetMouseEvent* aMouseEvent,
+ mozilla::dom::Selection& aNormalSelection);
+
+ class MOZ_STACK_CLASS RowAndColumnRelation;
+ };
+
+ TableSelection mTableSelection;
+
+ struct MaintainedRange {
+ /**
+ * Ensure anchor and focus of aNormalSelection are ordered appropriately
+ * relative to the maintained range.
+ */
+ MOZ_CAN_RUN_SCRIPT void AdjustNormalSelection(
+ const nsIContent* aContent, int32_t aOffset,
+ mozilla::dom::Selection& aNormalSelection) const;
+
+ /**
+ * @param aScrollViewStop see `PeekOffsetOption::ScrollViewStop`.
+ */
+ void AdjustContentOffsets(nsIFrame::ContentOffsets& aOffsets,
+ bool aScrollViewStop) const;
+
+ void MaintainAnchorFocusRange(
+ const mozilla::dom::Selection& aNormalSelection,
+ nsSelectionAmount aAmount);
+
+ RefPtr<nsRange> mRange;
+ nsSelectionAmount mAmount = eSelectNoAmount;
+ };
+
+ MaintainedRange mMaintainedRange;
+
+ struct Batching {
+ uint32_t mCounter = 0;
+ bool mChangesDuringBatching = false;
+ };
+
+ Batching mBatching;
+
+ struct Limiters {
+ // Limit selection navigation to a child of this node.
+ nsCOMPtr<nsIContent> mLimiter;
+ // Limit selection navigation to a descendant of this node.
+ nsCOMPtr<nsIContent> mAncestorLimiter;
+ };
+
+ Limiters mLimiters;
+
+ mozilla::PresShell* mPresShell = nullptr;
+ // Reasons for notifications of selection changing.
+ // Can be multiple of the reasons defined in nsISelectionListener.idl.
+ int16_t mSelectionChangeReasons = nsISelectionListener::NO_REASON;
+ // For visual display purposes.
+ int16_t mDisplaySelection = nsISelectionController::SELECTION_OFF;
+ nsSelectionAmount mCaretMoveAmount = eSelectNoAmount;
+
+ struct Caret {
+ // Hint to tell if the selection is at the end of this line or beginning of
+ // next.
+ CaretAssociateHint mHint = mozilla::CARET_ASSOCIATE_BEFORE;
+ mozilla::intl::BidiEmbeddingLevel mBidiLevel = BIDI_LEVEL_UNDEFINED;
+
+ bool IsVisualMovement(bool aContinueSelection,
+ CaretMovementStyle aMovementStyle) const;
+ };
+
+ Caret mCaret;
+
+ mozilla::intl::BidiEmbeddingLevel mKbdBidiLevel =
+ mozilla::intl::BidiEmbeddingLevel::LTR();
+
+ class DesiredCaretPos {
+ public:
+ // the position requested by the Key Handling for up down
+ nsresult FetchPos(nsPoint& aDesiredCaretPos,
+ const mozilla::PresShell& aPresShell,
+ mozilla::dom::Selection& aNormalSelection) const;
+
+ void Set(const nsPoint& aPos);
+
+ void Invalidate();
+
+ bool mIsSet = false;
+
+ private:
+ nsPoint mValue;
+ };
+
+ DesiredCaretPos mDesiredCaretPos;
+
+ struct DelayedMouseEvent {
+ bool mIsValid = false;
+ // These values are not used since they are only valid when mIsValid is
+ // true, and setting mIsValid always overrides these values.
+ bool mIsShift = false;
+ uint32_t mClickCount = 0;
+ };
+
+ DelayedMouseEvent mDelayedMouseEvent;
+
+ bool mDragState = false; // for drag purposes
+ bool mAccessibleCaretEnabled = false;
+
+ // Records if a selection was created by doubleclicking a word.
+ // This information is needed later on to determine if a leading
+ // or trailing whitespace needs to be removed as well to achieve
+ // native behaviour on macOS.
+ bool mIsDoubleClickSelection{false};
+};
+
+/**
+ * Selection Batcher class that supports multiple FrameSelections.
+ */
+class MOZ_STACK_CLASS AutoFrameSelectionBatcher {
+ public:
+ explicit AutoFrameSelectionBatcher(const char* aFunctionName,
+ size_t aEstimatedSize = 1)
+ : mFunctionName(aFunctionName) {
+ mFrameSelections.SetCapacity(aEstimatedSize);
+ }
+ ~AutoFrameSelectionBatcher() {
+ for (const auto& frameSelection : mFrameSelections) {
+ frameSelection->EndBatchChanges(mFunctionName);
+ }
+ }
+ void AddFrameSelection(nsFrameSelection* aFrameSelection) {
+ if (!aFrameSelection) {
+ return;
+ }
+ aFrameSelection->StartBatchChanges(mFunctionName);
+ mFrameSelections.AppendElement(aFrameSelection);
+ }
+
+ private:
+ const char* mFunctionName;
+ AutoTArray<RefPtr<nsFrameSelection>, 1> mFrameSelections;
+};
+
+#endif /* nsFrameSelection_h___ */