1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef nsFrameSelection_h___
8 #define nsFrameSelection_h___
9 
10 #include "mozilla/intl/BidiEmbeddingLevel.h"
11 #include "mozilla/Assertions.h"
12 #include "mozilla/Attributes.h"
13 #include "mozilla/EventForwards.h"
14 #include "mozilla/dom/Selection.h"
15 #include "mozilla/Result.h"
16 #include "mozilla/TextRange.h"
17 #include "mozilla/UniquePtr.h"
18 #include "nsIFrame.h"
19 #include "nsIContent.h"
20 #include "nsISelectionController.h"
21 #include "nsISelectionListener.h"
22 #include "nsITableCellLayout.h"
23 #include "WordMovementType.h"
24 #include "CaretAssociationHint.h"
25 #include "nsBidiPresUtils.h"
26 
27 class nsRange;
28 
29 #define BIDI_LEVEL_UNDEFINED mozilla::intl::BidiEmbeddingLevel(0x80)
30 
31 //----------------------------------------------------------------------
32 
33 // Selection interface
34 
35 struct SelectionDetails {
SelectionDetailsSelectionDetails36   SelectionDetails()
37       : mStart(), mEnd(), mSelectionType(mozilla::SelectionType::eInvalid) {
38     MOZ_COUNT_CTOR(SelectionDetails);
39   }
40   MOZ_COUNTED_DTOR(SelectionDetails)
41 
42   int32_t mStart;
43   int32_t mEnd;
44   mozilla::SelectionType mSelectionType;
45   mozilla::TextRangeStyle mTextRangeStyle;
46   mozilla::UniquePtr<SelectionDetails> mNext;
47 };
48 
49 struct SelectionCustomColors {
50 #ifdef NS_BUILD_REFCNT_LOGGING
51   MOZ_COUNTED_DEFAULT_CTOR(SelectionCustomColors)
52   MOZ_COUNTED_DTOR(SelectionCustomColors)
53 #endif
54   mozilla::Maybe<nscolor> mForegroundColor;
55   mozilla::Maybe<nscolor> mBackgroundColor;
56   mozilla::Maybe<nscolor> mAltForegroundColor;
57   mozilla::Maybe<nscolor> mAltBackgroundColor;
58 };
59 
60 namespace mozilla {
61 class PresShell;
62 }  // namespace mozilla
63 
64 /** PeekOffsetStruct is used to group various arguments (both input and output)
65  *  that are passed to nsIFrame::PeekOffset(). See below for the description of
66  *  individual arguments.
67  */
68 struct MOZ_STACK_CLASS nsPeekOffsetStruct {
69   enum class ForceEditableRegion {
70     No,
71     Yes,
72   };
73 
74   nsPeekOffsetStruct(
75       nsSelectionAmount aAmount, nsDirection aDirection, int32_t aStartOffset,
76       nsPoint aDesiredCaretPos, bool aJumpLines, bool aScrollViewStop,
77       bool aIsKeyboardSelect, bool aVisual, bool aExtend,
78       ForceEditableRegion = ForceEditableRegion::No,
79       mozilla::EWordMovementType aWordMovementType = mozilla::eDefaultBehavior,
80       bool aTrimSpaces = true);
81 
82   // Note: Most arguments (input and output) are only used with certain values
83   // of mAmount. These values are indicated for each argument below.
84   // Arguments with no such indication are used with all values of mAmount.
85 
86   /*** Input arguments ***/
87   // Note: The value of some of the input arguments may be changed upon exit.
88 
89   // The type of movement requested (by character, word, line, etc.)
90   nsSelectionAmount mAmount;
91 
92   // eDirPrevious or eDirNext.
93   //
94   // Note for visual bidi movement:
95   //   * eDirPrevious means 'left-then-up' if the containing block is LTR,
96   //     'right-then-up' if it is RTL.
97   //   * eDirNext means 'right-then-down' if the containing block is LTR,
98   //     'left-then-down' if it is RTL.
99   //   * Between paragraphs, eDirPrevious means "go to the visual end of
100   //     the previous paragraph", and eDirNext means "go to the visual
101   //     beginning of the next paragraph".
102   //
103   // Used with: eSelectCharacter, eSelectWord, eSelectLine, eSelectParagraph.
104   const nsDirection mDirection;
105 
106   // Offset into the content of the current frame where the peek starts.
107   //
108   // Used with: eSelectCharacter, eSelectWord
109   int32_t mStartOffset;
110 
111   // The desired inline coordinate for the caret (one of .x or .y will be used,
112   // depending on line's writing mode)
113   //
114   // Used with: eSelectLine.
115   const nsPoint mDesiredCaretPos;
116 
117   // An enum that determines whether to prefer the start or end of a word or to
118   // use the default beahvior, which is a combination of direction and the
119   // platform-based pref "layout.word_select.eat_space_to_next_word"
120   mozilla::EWordMovementType mWordMovementType;
121 
122   // Whether to allow jumping across line boundaries.
123   //
124   // Used with: eSelectCharacter, eSelectWord.
125   const bool mJumpLines;
126 
127   // mTrimSpaces: Whether we should trim spaces at begin/end of content
128   const bool mTrimSpaces;
129 
130   // Whether to stop when reaching a scroll view boundary.
131   //
132   // Used with: eSelectCharacter, eSelectWord, eSelectLine.
133   const bool mScrollViewStop;
134 
135   // Whether the peeking is done in response to a keyboard action.
136   //
137   // Used with: eSelectWord.
138   const bool mIsKeyboardSelect;
139 
140   // Whether bidi caret behavior is visual (true) or logical (false).
141   //
142   // Used with: eSelectCharacter, eSelectWord, eSelectBeginLine, eSelectEndLine.
143   const bool mVisual;
144 
145   // Whether the selection is being extended or moved.
146   const bool mExtend;
147 
148   // If true, the offset has to end up in an editable node, otherwise we'll keep
149   // searching.
150   const bool mForceEditableRegion;
151 
152   /*** Output arguments ***/
153 
154   // Content reached as a result of the peek.
155   nsCOMPtr<nsIContent> mResultContent;
156 
157   // Frame reached as a result of the peek.
158   //
159   // Used with: eSelectCharacter, eSelectWord.
160   nsIFrame* mResultFrame;
161 
162   // Offset into content reached as a result of the peek.
163   int32_t mContentOffset;
164 
165   // When the result position is between two frames, indicates which of the two
166   // frames the caret should be painted in. false means "the end of the frame
167   // logically before the caret", true means "the beginning of the frame
168   // logically after the caret".
169   //
170   // Used with: eSelectLine, eSelectBeginLine, eSelectEndLine.
171   mozilla::CaretAssociationHint mAttach;
172 };
173 
174 struct nsPrevNextBidiLevels {
SetDatansPrevNextBidiLevels175   void SetData(nsIFrame* aFrameBefore, nsIFrame* aFrameAfter,
176                mozilla::intl::BidiEmbeddingLevel aLevelBefore,
177                mozilla::intl::BidiEmbeddingLevel aLevelAfter) {
178     mFrameBefore = aFrameBefore;
179     mFrameAfter = aFrameAfter;
180     mLevelBefore = aLevelBefore;
181     mLevelAfter = aLevelAfter;
182   }
183   nsIFrame* mFrameBefore;
184   nsIFrame* mFrameAfter;
185   mozilla::intl::BidiEmbeddingLevel mLevelBefore;
186   mozilla::intl::BidiEmbeddingLevel mLevelAfter;
187 };
188 
189 namespace mozilla {
190 class SelectionChangeEventDispatcher;
191 namespace dom {
192 class Selection;
193 }  // namespace dom
194 
195 /**
196  * Constants for places that want to handle table selections.  These
197  * indicate what part of a table is being selected.
198  */
199 enum class TableSelectionMode : uint32_t {
200   None,     /* Nothing being selected; not valid in all cases. */
201   Cell,     /* A cell is being selected. */
202   Row,      /* A row is being selected. */
203   Column,   /* A column is being selected. */
204   Table,    /* A table (including cells and captions) is being selected. */
205   AllCells, /* All the cells in a table are being selected. */
206 };
207 
208 }  // namespace mozilla
209 class nsIScrollableFrame;
210 
211 class nsFrameSelection final {
212  public:
213   typedef mozilla::CaretAssociationHint CaretAssociateHint;
214 
215   /*interfaces for addref and release and queryinterface*/
216 
217   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsFrameSelection)
218   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsFrameSelection)
219 
220   enum class FocusMode {
221     kExtendSelection,     /** Keep old anchor point. */
222     kCollapseToNewPoint,  /** Collapses the Selection to the new point. */
223     kMultiRangeSelection, /** Keeps existing non-collapsed ranges and marks them
224                              as generated. */
225   };
226 
227   /**
228    * HandleClick will take the focus to the new frame at the new offset and
229    * will either extend the selection from the old anchor, or replace the old
230    * anchor. the old anchor and focus position may also be used to deselect
231    * things
232    *
233    * @param aNewfocus is the content that wants the focus
234    *
235    * @param aContentOffset is the content offset of the parent aNewFocus
236    *
237    * @param aContentOffsetEnd is the content offset of the parent aNewFocus and
238    * is specified different when you need to select to and include both start
239    * and end points
240    *
241    * @param aHint will tell the selection which direction geometrically to
242    * actually show the caret on. 1 = end of this line 0 = beginning of this line
243    */
244   MOZ_CAN_RUN_SCRIPT nsresult HandleClick(nsIContent* aNewFocus,
245                                           uint32_t aContentOffset,
246                                           uint32_t aContentEndOffset,
247                                           FocusMode aFocusMode,
248                                           CaretAssociateHint aHint);
249 
250   /**
251    * HandleDrag extends the selection to contain the frame closest to aPoint.
252    *
253    * @param aPresContext is the context to use when figuring out what frame
254    * contains the point.
255    *
256    * @param aFrame is the parent of all frames to use when searching for the
257    * closest frame to the point.
258    *
259    * @param aPoint is relative to aFrame
260    */
261   MOZ_CAN_RUN_SCRIPT void HandleDrag(nsIFrame* aFrame, const nsPoint& aPoint);
262 
263   /**
264    * HandleTableSelection will set selection to a table, cell, etc
265    * depending on information contained in aFlags
266    *
267    * @param aParentContent is the paretent of either a table or cell that user
268    * clicked or dragged the mouse in
269    *
270    * @param aContentOffset is the offset of the table or cell
271    *
272    * @param aTarget indicates what to select
273    *   * TableSelectionMode::Cell
274    *       We should select a cell (content points to the cell)
275    *   * TableSelectionMode::Row
276    *       We should select a row (content points to any cell in row)
277    *   * TableSelectionMode::Column
278    *       We should select a row (content points to any cell in column)
279    *   * TableSelectionMode::Table
280    *       We should select a table (content points to the table)
281    *   * TableSelectionMode::AllCells
282    *       We should select all cells (content points to any cell in table)
283    *
284    * @param aMouseEvent passed in so we can get where event occurred
285    * and what keys are pressed
286    */
287   // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
288   [[nodiscard]] MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
289   HandleTableSelection(nsINode* aParentContent, int32_t aContentOffset,
290                        mozilla::TableSelectionMode aTarget,
291                        mozilla::WidgetMouseEvent* aMouseEvent);
292 
293   /**
294    * Add cell to the selection with `SelectionType::eNormal`.
295    *
296    * @param  aCell  [in] HTML td element.
297    */
298   nsresult SelectCellElement(nsIContent* aCell);
299 
300  public:
301   /**
302    * Remove cells from selection inside of the given cell range.
303    *
304    * @param  aTable             [in] HTML table element
305    * @param  aStartRowIndex     [in] row index where the cells range starts
306    * @param  aStartColumnIndex  [in] column index where the cells range starts
307    * @param  aEndRowIndex       [in] row index where the cells range ends
308    * @param  aEndColumnIndex    [in] column index where the cells range ends
309    */
310   // TODO: annotate this with `MOZ_CAN_RUN_SCRIPT` instead.
311   MOZ_CAN_RUN_SCRIPT_BOUNDARY
312   nsresult RemoveCellsFromSelection(nsIContent* aTable, int32_t aStartRowIndex,
313                                     int32_t aStartColumnIndex,
314                                     int32_t aEndRowIndex,
315                                     int32_t aEndColumnIndex);
316 
317   /**
318    * Remove cells from selection outside of the given cell range.
319    *
320    * @param  aTable             [in] HTML table element
321    * @param  aStartRowIndex     [in] row index where the cells range starts
322    * @param  aStartColumnIndex  [in] column index where the cells range starts
323    * @param  aEndRowIndex       [in] row index where the cells range ends
324    * @param  aEndColumnIndex    [in] column index where the cells range ends
325    */
326   // TODO: annotate this with `MOZ_CAN_RUN_SCRIPT` instead.
327   MOZ_CAN_RUN_SCRIPT_BOUNDARY
328   nsresult RestrictCellsToSelection(nsIContent* aTable, int32_t aStartRowIndex,
329                                     int32_t aStartColumnIndex,
330                                     int32_t aEndRowIndex,
331                                     int32_t aEndColumnIndex);
332 
333   /**
334    * StartAutoScrollTimer is responsible for scrolling frames so that
335    * aPoint is always visible, and for selecting any frame that contains
336    * aPoint. The timer will also reset itself to fire again if we have
337    * not scrolled to the end of the document.
338    *
339    * @param aFrame is the outermost frame to use when searching for
340    * the closest frame for the point, i.e. the frame that is capturing
341    * the mouse
342    *
343    * @param aPoint is relative to aFrame.
344    *
345    * @param aDelay is the timer's interval.
346    */
347   MOZ_CAN_RUN_SCRIPT
348   nsresult StartAutoScrollTimer(nsIFrame* aFrame, const nsPoint& aPoint,
349                                 uint32_t aDelay);
350 
351   /**
352    * Stops any active auto scroll timer.
353    */
354   void StopAutoScrollTimer();
355 
356   /**
357    * Returns in frame coordinates the selection beginning and ending with the
358    * type of selection given
359    *
360    * @param aContent is the content asking
361    * @param aContentOffset is the starting content boundary
362    * @param aContentLength is the length of the content piece asking
363    * @param aSlowCheck will check using slow method with no shortcuts
364    */
365   mozilla::UniquePtr<SelectionDetails> LookUpSelection(nsIContent* aContent,
366                                                        int32_t aContentOffset,
367                                                        int32_t aContentLength,
368                                                        bool aSlowCheck) const;
369 
370   /**
371    * Sets the drag state to aState for resons of drag state.
372    *
373    * @param aState is the new state of drag
374    */
375   MOZ_CAN_RUN_SCRIPT
376   void SetDragState(bool aState);
377 
378   /**
379    * Gets the drag state to aState for resons of drag state.
380    *
381    * @param aState will hold the state of drag
382    */
GetDragState()383   bool GetDragState() const { return mDragState; }
384 
385   /**
386    * If we are in table cell selection mode. aka ctrl click in table cell
387    */
IsInTableSelectionMode()388   bool IsInTableSelectionMode() const {
389     return mTableSelection.mMode != mozilla::TableSelectionMode::None;
390   }
ClearTableCellSelection()391   void ClearTableCellSelection() {
392     mTableSelection.mMode = mozilla::TableSelectionMode::None;
393   }
394 
395   /**
396    * No query interface for selection. must use this method now.
397    *
398    * @param aSelectionType The selection type what you want.
399    */
400   mozilla::dom::Selection* GetSelection(
401       mozilla::SelectionType aSelectionType) const;
402 
403   /**
404    * ScrollSelectionIntoView scrolls a region of the selection,
405    * so that it is visible in the scrolled view.
406    *
407    * @param aSelectionType the selection to scroll into view.
408    *
409    * @param aRegion the region inside the selection to scroll into view.
410    *
411    * @param aFlags the scroll flags.  Valid bits include:
412    *   * SCROLL_SYNCHRONOUS: when set, scrolls the selection into view
413    *     before returning. If not set, posts a request which is processed
414    *     at some point after the method returns.
415    *   * SCROLL_FIRST_ANCESTOR_ONLY: if set, only the first ancestor will be
416    *     scrolled into view.
417    */
418   // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
419   MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
420   ScrollSelectionIntoView(mozilla::SelectionType aSelectionType,
421                           SelectionRegion aRegion, int16_t aFlags) const;
422 
423   /**
424    * RepaintSelection repaints the selected frames that are inside the
425    * selection specified by aSelectionType.
426    *
427    * @param aSelectionType The selection type what you want to repaint.
428    */
429   nsresult RepaintSelection(mozilla::SelectionType aSelectionType);
430 
431   bool IsValidSelectionPoint(nsINode* aNode) const;
432 
433   static bool AdjustFrameForLineStart(nsIFrame*& aFrame, int32_t& aFrameOffset);
434 
435   /**
436    * Given a node and its child offset, return the nsIFrame and the offset into
437    * that frame.
438    *
439    * @param aNode input parameter for the node to look at
440    *              TODO: Make this `const nsIContent*` for `ContentEventHandler`.
441    * @param aOffset offset into above node.
442    * @param aReturnOffset will contain offset into frame.
443    */
444   static nsIFrame* GetFrameForNodeOffset(nsIContent* aNode, int32_t aOffset,
445                                          CaretAssociateHint aHint,
446                                          int32_t* aReturnOffset);
447 
448   /**
449    * GetFrameToPageSelect() returns a frame which is ancestor limit of
450    * per-page selection.  The frame may not be scrollable.  E.g.,
451    * when selection ancestor limit is set to a frame of an editing host of
452    * contenteditable element and it's not scrollable.
453    */
454   nsIFrame* GetFrameToPageSelect() const;
455 
456   /**
457    * This method moves caret (if aExtend is false) or expands selection (if
458    * aExtend is true).  Then, scrolls aFrame one page.  Finally, this may
459    * call ScrollSelectionIntoView() for making focus of selection visible
460    * but depending on aSelectionIntoView value.
461    *
462    * @param aForward if true, scroll forward if not scroll backward
463    * @param aExtend  if true, extend selection to the new point
464    * @param aFrame   the frame to scroll or container of per-page selection.
465    *                 if aExtend is true and selection may have ancestor limit,
466    *                 should set result of GetFrameToPageSelect().
467    * @param aSelectionIntoView
468    *                 If IfChanged, this makes selection into view only when
469    *                 selection is modified by the call.
470    *                 If Yes, this makes selection into view always.
471    */
472   enum class SelectionIntoView { IfChanged, Yes };
473   MOZ_CAN_RUN_SCRIPT nsresult PageMove(bool aForward, bool aExtend,
474                                        nsIFrame* aFrame,
475                                        SelectionIntoView aSelectionIntoView);
476 
SetHint(CaretAssociateHint aHintRight)477   void SetHint(CaretAssociateHint aHintRight) { mCaret.mHint = aHintRight; }
GetHint()478   CaretAssociateHint GetHint() const { return mCaret.mHint; }
479 
480   void SetCaretBidiLevelAndMaybeSchedulePaint(
481       mozilla::intl::BidiEmbeddingLevel aLevel);
482 
483   /**
484    * GetCaretBidiLevel gets the caret bidi level.
485    */
486   mozilla::intl::BidiEmbeddingLevel GetCaretBidiLevel() const;
487 
488   /**
489    * UndefineCaretBidiLevel sets the caret bidi level to "undefined".
490    */
491   void UndefineCaretBidiLevel();
492 
493   /**
494    * PhysicalMove will generally be called from the nsiselectioncontroller
495    * implementations. the effect being the selection will move one unit
496    * 'aAmount' in the given aDirection.
497    * @param aDirection  the direction to move the selection
498    * @param aAmount     amount of movement (char/line; word/page; eol/doc)
499    * @param aExtend     continue selection
500    */
501   // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
502   MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult PhysicalMove(int16_t aDirection,
503                                                     int16_t aAmount,
504                                                     bool aExtend);
505 
506   /**
507    * CharacterMove will generally be called from the nsiselectioncontroller
508    * implementations. the effect being the selection will move one character
509    * left or right.
510    * @param aForward move forward in document.
511    * @param aExtend continue selection
512    */
513   // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
514   MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult CharacterMove(bool aForward,
515                                                      bool aExtend);
516 
517   /**
518    * WordMove will generally be called from the nsiselectioncontroller
519    * implementations. the effect being the selection will move one word left or
520    * right.
521    * @param aForward move forward in document.
522    * @param aExtend continue selection
523    */
524   // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
525   MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult WordMove(bool aForward, bool aExtend);
526 
527   /**
528    * LineMove will generally be called from the nsiselectioncontroller
529    * implementations. the effect being the selection will move one line up or
530    * down.
531    * @param aForward move forward in document.
532    * @param aExtend continue selection
533    */
534   // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
535   MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult LineMove(bool aForward, bool aExtend);
536 
537   /**
538    * IntraLineMove will generally be called from the nsiselectioncontroller
539    * implementations. the effect being the selection will move to beginning or
540    * end of line
541    * @param aForward move forward in document.
542    * @param aExtend continue selection
543    */
544   // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
545   MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult IntraLineMove(bool aForward,
546                                                      bool aExtend);
547 
548   /**
549    * CreateRangeExtendedToNextGraphemeClusterBoundary() returns range which is
550    * extended from normal selection range to start of next grapheme cluster
551    * boundary.
552    */
553   template <typename RangeType>
554   MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
CreateRangeExtendedToNextGraphemeClusterBoundary()555   CreateRangeExtendedToNextGraphemeClusterBoundary() {
556     return CreateRangeExtendedToSomewhere<RangeType>(eDirNext, eSelectCluster,
557                                                      eLogical);
558   }
559 
560   /**
561    * CreateRangeExtendedToPreviousCharacterBoundary() returns range which is
562    * extended from normal selection range to start of previous character
563    * boundary.
564    */
565   template <typename RangeType>
566   MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
CreateRangeExtendedToPreviousCharacterBoundary()567   CreateRangeExtendedToPreviousCharacterBoundary() {
568     return CreateRangeExtendedToSomewhere<RangeType>(
569         eDirPrevious, eSelectCharacter, eLogical);
570   }
571 
572   /**
573    * CreateRangeExtendedToNextWordBoundary() returns range which is
574    * extended from normal selection range to start of next word boundary.
575    */
576   template <typename RangeType>
577   MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
CreateRangeExtendedToNextWordBoundary()578   CreateRangeExtendedToNextWordBoundary() {
579     return CreateRangeExtendedToSomewhere<RangeType>(eDirNext, eSelectWord,
580                                                      eLogical);
581   }
582 
583   /**
584    * CreateRangeExtendedToPreviousWordBoundary() returns range which is
585    * extended from normal selection range to start of previous word boundary.
586    */
587   template <typename RangeType>
588   MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
CreateRangeExtendedToPreviousWordBoundary()589   CreateRangeExtendedToPreviousWordBoundary() {
590     return CreateRangeExtendedToSomewhere<RangeType>(eDirPrevious, eSelectWord,
591                                                      eLogical);
592   }
593 
594   /**
595    * CreateRangeExtendedToPreviousHardLineBreak() returns range which is
596    * extended from normal selection range to previous hard line break.
597    */
598   template <typename RangeType>
599   MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
CreateRangeExtendedToPreviousHardLineBreak()600   CreateRangeExtendedToPreviousHardLineBreak() {
601     return CreateRangeExtendedToSomewhere<RangeType>(
602         eDirPrevious, eSelectBeginLine, eLogical);
603   }
604 
605   /**
606    * CreateRangeExtendedToNextHardLineBreak() returns range which is extended
607    * from normal selection range to next hard line break.
608    */
609   template <typename RangeType>
610   MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
CreateRangeExtendedToNextHardLineBreak()611   CreateRangeExtendedToNextHardLineBreak() {
612     return CreateRangeExtendedToSomewhere<RangeType>(eDirNext, eSelectEndLine,
613                                                      eLogical);
614   }
615 
616   /** Sets/Gets The display selection enum.
617    */
SetDisplaySelection(int16_t aState)618   void SetDisplaySelection(int16_t aState) { mDisplaySelection = aState; }
GetDisplaySelection()619   int16_t GetDisplaySelection() const { return mDisplaySelection; }
620 
621   /**
622    * This method can be used to store the data received during a MouseDown
623    * event so that we can place the caret during the MouseUp event.
624    *
625    * @param aMouseEvent the event received by the selection MouseDown
626    * handling method. A nullptr value can be use to tell this method
627    * that any data is storing is no longer valid.
628    */
629   void SetDelayedCaretData(mozilla::WidgetMouseEvent* aMouseEvent);
630 
631   /**
632    * Get the delayed MouseDown event data necessary to place the
633    * caret during MouseUp processing.
634    *
635    * @return a pointer to the event received
636    * by the selection during MouseDown processing. It can be nullptr
637    * if the data is no longer valid.
638    */
HasDelayedCaretData()639   bool HasDelayedCaretData() const { return mDelayedMouseEvent.mIsValid; }
IsShiftDownInDelayedCaretData()640   bool IsShiftDownInDelayedCaretData() const {
641     NS_ASSERTION(mDelayedMouseEvent.mIsValid, "No valid delayed caret data");
642     return mDelayedMouseEvent.mIsShift;
643   }
GetClickCountInDelayedCaretData()644   uint32_t GetClickCountInDelayedCaretData() const {
645     NS_ASSERTION(mDelayedMouseEvent.mIsValid, "No valid delayed caret data");
646     return mDelayedMouseEvent.mClickCount;
647   }
648 
MouseDownRecorded()649   bool MouseDownRecorded() const {
650     return !GetDragState() && HasDelayedCaretData() &&
651            GetClickCountInDelayedCaretData() < 2;
652   }
653 
654   /**
655    * Get the content node that limits the selection
656    *
657    * When searching up a nodes for parents, as in a text edit field
658    * in an browser page, we must stop at this node else we reach into the
659    * parent page, which is very bad!
660    */
GetLimiter()661   nsIContent* GetLimiter() const { return mLimiters.mLimiter; }
662 
GetAncestorLimiter()663   nsIContent* GetAncestorLimiter() const { return mLimiters.mAncestorLimiter; }
664   MOZ_CAN_RUN_SCRIPT_BOUNDARY void SetAncestorLimiter(nsIContent* aLimiter);
665 
666   /**
667    * GetPrevNextBidiLevels will return the frames and associated Bidi levels of
668    * the characters logically before and after a (collapsed) selection.
669    *
670    * @param aNode is the node containing the selection
671    * @param aContentOffset is the offset of the selection in the node
672    * @param aJumpLines
673    *   If true, look across line boundaries.
674    *   If false, behave as if there were base-level frames at line edges.
675    *
676    * @return A struct holding the before/after frame and the before/after
677    * level.
678    *
679    * At the beginning and end of each line there is assumed to be a frame with
680    * Bidi level equal to the paragraph embedding level.
681    *
682    * In these cases the before frame and after frame respectively will be
683    * nullptr.
684    */
685   nsPrevNextBidiLevels GetPrevNextBidiLevels(nsIContent* aNode,
686                                              uint32_t aContentOffset,
687                                              bool aJumpLines) const;
688 
689   /**
690    * GetFrameFromLevel will scan in a given direction
691    * until it finds a frame with a Bidi level less than or equal to a given
692    * level. It will return the last frame before this.
693    *
694    * @param aPresContext is the context to use
695    * @param aFrameIn is the frame to start from
696    * @param aDirection is the direction to scan
697    * @param aBidiLevel is the level to search for
698    * @param aFrameOut will hold the frame returned
699    */
700   nsresult GetFrameFromLevel(nsIFrame* aFrameIn, nsDirection aDirection,
701                              mozilla::intl::BidiEmbeddingLevel aBidiLevel,
702                              nsIFrame** aFrameOut) const;
703 
704   /**
705    * MaintainSelection will track the normal selection as being "sticky".
706    * Dragging or extending selection will never allow for a subset
707    * (or the whole) of the maintained selection to become unselected.
708    * Primary use: double click selecting then dragging on second click
709    *
710    * @param aAmount the initial amount of text selected (word, line or
711    * paragraph). For "line", use eSelectBeginLine.
712    */
713   nsresult MaintainSelection(nsSelectionAmount aAmount = eSelectNoAmount);
714 
715   MOZ_CAN_RUN_SCRIPT nsresult ConstrainFrameAndPointToAnchorSubtree(
716       nsIFrame* aFrame, const nsPoint& aPoint, nsIFrame** aRetFrame,
717       nsPoint& aRetPoint) const;
718 
719   /**
720    * @param aPresShell is the parameter to be used for most of the other calls
721    * for callbacks etc
722    *
723    * @param aLimiter limits the selection to nodes with aLimiter parents
724    *
725    * @param aAccessibleCaretEnabled true if we should enable the accessible
726    * caret.
727    */
728   nsFrameSelection(mozilla::PresShell* aPresShell, nsIContent* aLimiter,
729                    bool aAccessibleCaretEnabled);
730 
731   /**
732    * @param aRequesterFuncName function name which wants to start the batch.
733    * This won't be stored nor exposed to selection listeners etc, used only for
734    * logging.
735    */
736   void StartBatchChanges(const char* aRequesterFuncName);
737 
738   /**
739    * @param aRequesterFuncName function name which wants to end the batch.
740    * This won't be stored nor exposed to selection listeners etc, used only for
741    * logging.
742    * @param aReasons potentially multiple of the reasons defined in
743    * nsISelectionListener.idl
744    */
745   MOZ_CAN_RUN_SCRIPT_BOUNDARY void EndBatchChanges(
746       const char* aRequesterFuncName,
747       int16_t aReasons = nsISelectionListener::NO_REASON);
748 
GetPresShell()749   mozilla::PresShell* GetPresShell() const { return mPresShell; }
750 
751   void DisconnectFromPresShell();
752   MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult ClearNormalSelection();
753 
754   // Table selection support.
755   static nsITableCellLayout* GetCellLayout(const nsIContent* aCellContent);
756 
757  private:
758   ~nsFrameSelection();
759 
760   // TODO: in case an error is returned, it sometimes refers to a programming
761   // error, in other cases to runtime errors. This deserves to be cleaned up.
762   [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
763   TakeFocus(nsIContent& aNewFocus, uint32_t aContentOffset,
764             uint32_t aContentEndOffset, CaretAssociateHint aHint,
765             FocusMode aFocusMode);
766 
767   /**
768    * After moving the caret, its Bidi level is set according to the following
769    * rules:
770    *
771    * After moving over a character with left/right arrow, set to the Bidi level
772    * of the last moved over character. After Home and End, set to the paragraph
773    * embedding level. After up/down arrow, PageUp/Down, set to the lower level
774    * of the 2 surrounding characters. After mouse click, set to the level of the
775    * current frame.
776    *
777    * The following two methods use GetPrevNextBidiLevels to determine the new
778    * Bidi level. BidiLevelFromMove is called when the caret is moved in response
779    * to a keyboard event
780    *
781    * @param aPresShell is the presentation shell
782    * @param aNode is the content node
783    * @param aContentOffset is the new caret position, as an offset into aNode
784    * @param aAmount is the amount of the move that gave the caret its new
785    * position
786    * @param aHint is the hint indicating in what logical direction the caret
787    * moved
788    */
789   void BidiLevelFromMove(mozilla::PresShell* aPresShell, nsIContent* aNode,
790                          uint32_t aContentOffset, nsSelectionAmount aAmount,
791                          CaretAssociateHint aHint);
792   /**
793    * BidiLevelFromClick is called when the caret is repositioned by clicking the
794    * mouse
795    *
796    * @param aNode is the content node
797    * @param aContentOffset is the new caret position, as an offset into aNode
798    */
799   void BidiLevelFromClick(nsIContent* aNewFocus, uint32_t aContentOffset);
800 
801   static nsPrevNextBidiLevels GetPrevNextBidiLevels(nsIContent* aNode,
802                                                     uint32_t aContentOffset,
803                                                     CaretAssociateHint aHint,
804                                                     bool aJumpLines);
805 
806   /**
807    * @param aReasons potentially multiple of the reasons defined in
808    * nsISelectionListener.idl.
809    */
SetChangeReasons(int16_t aReasons)810   void SetChangeReasons(int16_t aReasons) {
811     mSelectionChangeReasons = aReasons;
812   }
813 
814   /**
815    * @param aReasons potentially multiple of the reasons defined in
816    * nsISelectionListener.idl.
817    */
AddChangeReasons(int16_t aReasons)818   void AddChangeReasons(int16_t aReasons) {
819     mSelectionChangeReasons |= aReasons;
820   }
821 
822   /**
823    * @return potentially multiple of the reasons defined in
824    * nsISelectionListener.idl.
825    */
PopChangeReasons()826   int16_t PopChangeReasons() {
827     int16_t retval = mSelectionChangeReasons;
828     mSelectionChangeReasons = nsISelectionListener::NO_REASON;
829     return retval;
830   }
831 
IsUserSelectionReason()832   bool IsUserSelectionReason() const {
833     return (mSelectionChangeReasons &
834             (nsISelectionListener::DRAG_REASON |
835              nsISelectionListener::MOUSEDOWN_REASON |
836              nsISelectionListener::MOUSEUP_REASON |
837              nsISelectionListener::KEYPRESS_REASON)) !=
838            nsISelectionListener::NO_REASON;
839   }
840 
841   friend class mozilla::dom::Selection;
842   friend class mozilla::SelectionChangeEventDispatcher;
843   friend struct mozilla::AutoPrepareFocusRange;
844 
845   /*HELPER METHODS*/
846   // Whether MoveCaret should use logical or visual movement,
847   // or follow the bidi.edit.caret_movement_style preference.
848   enum CaretMovementStyle { eLogical, eVisual, eUsePrefStyle };
849   MOZ_CAN_RUN_SCRIPT nsresult MoveCaret(nsDirection aDirection,
850                                         bool aContinueSelection,
851                                         nsSelectionAmount aAmount,
852                                         CaretMovementStyle aMovementStyle);
853 
854   /**
855    * PeekOffsetForCaretMove() only peek offset for caret move.  I.e., won't
856    * change selection ranges nor bidi information.
857    */
858   mozilla::Result<nsPeekOffsetStruct, nsresult> PeekOffsetForCaretMove(
859       nsDirection aDirection, bool aContinueSelection,
860       const nsSelectionAmount aAmount, CaretMovementStyle aMovementStyle,
861       const nsPoint& aDesiredCaretPos) const;
862 
863   /**
864    * CreateRangeExtendedToSomewhere() is common method to implement
865    * CreateRangeExtendedTo*().  This method creates a range extended from
866    * normal selection range.
867    */
868   template <typename RangeType>
869   MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
870   CreateRangeExtendedToSomewhere(nsDirection aDirection,
871                                  const nsSelectionAmount aAmount,
872                                  CaretMovementStyle aMovementStyle);
873 
874   /**
875    * IsIntraLineCaretMove() is a helper method for PeekOffsetForCaretMove()
876    * and CreateRangeExtendedToSomwhereFromNormalSelection().  This returns
877    * whether aAmount is intra line move or is crossing hard line break.
878    * This returns error if aMount is not supported by the methods.
879    */
IsIntraLineCaretMove(nsSelectionAmount aAmount)880   static mozilla::Result<bool, nsresult> IsIntraLineCaretMove(
881       nsSelectionAmount aAmount) {
882     switch (aAmount) {
883       case eSelectCharacter:
884       case eSelectCluster:
885       case eSelectWord:
886       case eSelectWordNoSpace:
887       case eSelectBeginLine:
888       case eSelectEndLine:
889         return true;
890       case eSelectLine:
891         return false;
892       default:
893         return mozilla::Err(NS_ERROR_FAILURE);
894     }
895   }
896 
897   void InvalidateDesiredCaretPos();  // do not listen to mDesiredCaretPos.mValue
898                                      // you must get another.
899 
IsBatching()900   bool IsBatching() const { return mBatching.mCounter > 0; }
901 
SetChangesDuringBatchingFlag()902   void SetChangesDuringBatchingFlag() {
903     MOZ_ASSERT(mBatching.mCounter > 0);
904 
905     mBatching.mChangesDuringBatching = true;
906   }
907 
908   // nsFrameSelection may get deleted when calling this,
909   // so remember to use nsCOMPtr when needed.
910   MOZ_CAN_RUN_SCRIPT
911   nsresult NotifySelectionListeners(mozilla::SelectionType aSelectionType);
912 
913   static nsresult GetCellIndexes(const nsIContent* aCell, int32_t& aRowIndex,
914                                  int32_t& aColIndex);
915 
916   static nsIContent* GetFirstCellNodeInRange(const nsRange* aRange);
917   // Returns non-null table if in same table, null otherwise
918   static nsIContent* IsInSameTable(const nsIContent* aContent1,
919                                    const nsIContent* aContent2);
920   // Might return null
921   static nsIContent* GetParentTable(const nsIContent* aCellNode);
922 
923   ////////////BEGIN nsFrameSelection members
924 
925   RefPtr<mozilla::dom::Selection>
926       mDomSelections[sizeof(mozilla::kPresentSelectionTypes) /
927                      sizeof(mozilla::SelectionType)];
928 
929   struct TableSelection {
930     // Get our first range, if its first selected node is a cell.  If this does
931     // not return null, then the first node in the returned range is a cell
932     // (according to GetFirstCellNodeInRange).
933     nsRange* GetFirstCellRange(const mozilla::dom::Selection& aNormalSelection);
934 
935     // Get our next range, if its first selected node is a cell.  If this does
936     // not return null, then the first node in the returned range is a cell
937     // (according to GetFirstCellNodeInRange).
938     nsRange* GetNextCellRange(const mozilla::dom::Selection& aNormalSelection);
939 
940     [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
941     HandleSelection(nsINode* aParentContent, int32_t aContentOffset,
942                     mozilla::TableSelectionMode aTarget,
943                     mozilla::WidgetMouseEvent* aMouseEvent, bool aDragState,
944                     mozilla::dom::Selection& aNormalSelection);
945 
946     /**
947      * @return the closest inclusive table cell ancestor
948      *         (https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor) of
949      *         aContent, if it is actively editable.
950      */
951     static nsINode* IsContentInActivelyEditableTableCell(
952         nsPresContext* aContext, nsIContent* aContent);
953 
954     // TODO: annotate this with `MOZ_CAN_RUN_SCRIPT` instead.
955     MOZ_CAN_RUN_SCRIPT_BOUNDARY
956     nsresult SelectBlockOfCells(nsIContent* aStartCell, nsIContent* aEndCell,
957                                 mozilla::dom::Selection& aNormalSelection);
958 
959     nsresult SelectRowOrColumn(nsIContent* aCellContent,
960                                mozilla::dom::Selection& aNormalSelection);
961 
962     MOZ_CAN_RUN_SCRIPT nsresult
963     UnselectCells(const nsIContent* aTable, int32_t aStartRowIndex,
964                   int32_t aStartColumnIndex, int32_t aEndRowIndex,
965                   int32_t aEndColumnIndex, bool aRemoveOutsideOfCellRange,
966                   mozilla::dom::Selection& aNormalSelection);
967 
968     nsCOMPtr<nsINode>
969         mClosestInclusiveTableCellAncestor;  // used to snap to table selection
970     nsCOMPtr<nsIContent> mStartSelectedCell;
971     nsCOMPtr<nsIContent> mEndSelectedCell;
972     nsCOMPtr<nsIContent> mAppendStartSelectedCell;
973     nsCOMPtr<nsIContent> mUnselectCellOnMouseUp;
974     mozilla::TableSelectionMode mMode = mozilla::TableSelectionMode::None;
975     int32_t mSelectedCellIndex = 0;
976     bool mDragSelectingCells = false;
977 
978    private:
979     struct MOZ_STACK_CLASS FirstAndLastCell {
980       nsCOMPtr<nsIContent> mFirst;
981       nsCOMPtr<nsIContent> mLast;
982     };
983 
984     mozilla::Result<FirstAndLastCell, nsresult>
985     FindFirstAndLastCellOfRowOrColumn(const nsIContent& aCellContent) const;
986 
987     [[nodiscard]] MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult HandleDragSelecting(
988         mozilla::TableSelectionMode aTarget, nsIContent* aChildContent,
989         const mozilla::WidgetMouseEvent* aMouseEvent,
990         mozilla::dom::Selection& aNormalSelection);
991 
992     [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleMouseUpOrDown(
993         mozilla::TableSelectionMode aTarget, bool aDragState,
994         nsIContent* aChildContent, nsINode* aParentContent,
995         int32_t aContentOffset, const mozilla::WidgetMouseEvent* aMouseEvent,
996         mozilla::dom::Selection& aNormalSelection);
997 
998     class MOZ_STACK_CLASS RowAndColumnRelation;
999   };
1000 
1001   TableSelection mTableSelection;
1002 
1003   struct MaintainedRange {
1004     /**
1005      * Ensure anchor and focus of aNormalSelection are ordered appropriately
1006      * relative to the maintained range.
1007      */
1008     MOZ_CAN_RUN_SCRIPT void AdjustNormalSelection(
1009         const nsIContent* aContent, int32_t aOffset,
1010         mozilla::dom::Selection& aNormalSelection) const;
1011 
1012     /**
1013      * @param aScrollViewStop see `nsPeekOffsetStruct::mScrollViewStop`.
1014      */
1015     void AdjustContentOffsets(nsIFrame::ContentOffsets& aOffsets,
1016                               bool aScrollViewStop) const;
1017 
1018     void MaintainAnchorFocusRange(
1019         const mozilla::dom::Selection& aNormalSelection,
1020         nsSelectionAmount aAmount);
1021 
1022     RefPtr<nsRange> mRange;
1023     nsSelectionAmount mAmount = eSelectNoAmount;
1024   };
1025 
1026   MaintainedRange mMaintainedRange;
1027 
1028   struct Batching {
1029     uint32_t mCounter = 0;
1030     bool mChangesDuringBatching = false;
1031   };
1032 
1033   Batching mBatching;
1034 
1035   struct Limiters {
1036     // Limit selection navigation to a child of this node.
1037     nsCOMPtr<nsIContent> mLimiter;
1038     // Limit selection navigation to a descendant of this node.
1039     nsCOMPtr<nsIContent> mAncestorLimiter;
1040   };
1041 
1042   Limiters mLimiters;
1043 
1044   mozilla::PresShell* mPresShell = nullptr;
1045   // Reasons for notifications of selection changing.
1046   // Can be multiple of the reasons defined in nsISelectionListener.idl.
1047   int16_t mSelectionChangeReasons = nsISelectionListener::NO_REASON;
1048   // For visual display purposes.
1049   int16_t mDisplaySelection = nsISelectionController::SELECTION_OFF;
1050 
1051   struct Caret {
1052     // Hint to tell if the selection is at the end of this line or beginning of
1053     // next.
1054     CaretAssociateHint mHint = mozilla::CARET_ASSOCIATE_BEFORE;
1055     mozilla::intl::BidiEmbeddingLevel mBidiLevel = BIDI_LEVEL_UNDEFINED;
1056 
1057     bool IsVisualMovement(bool aContinueSelection,
1058                           CaretMovementStyle aMovementStyle) const;
1059   };
1060 
1061   Caret mCaret;
1062 
1063   mozilla::intl::BidiEmbeddingLevel mKbdBidiLevel =
1064       mozilla::intl::BidiEmbeddingLevel::LTR();
1065 
1066   class DesiredCaretPos {
1067    public:
1068     // the position requested by the Key Handling for up down
1069     nsresult FetchPos(nsPoint& aDesiredCaretPos,
1070                       const mozilla::PresShell& aPresShell,
1071                       mozilla::dom::Selection& aNormalSelection) const;
1072 
1073     void Set(const nsPoint& aPos);
1074 
1075     void Invalidate();
1076 
1077     bool mIsSet = false;
1078 
1079    private:
1080     nsPoint mValue;
1081   };
1082 
1083   DesiredCaretPos mDesiredCaretPos;
1084 
1085   struct DelayedMouseEvent {
1086     bool mIsValid = false;
1087     // These values are not used since they are only valid when mIsValid is
1088     // true, and setting mIsValid  always overrides these values.
1089     bool mIsShift = false;
1090     uint32_t mClickCount = 0;
1091   };
1092 
1093   DelayedMouseEvent mDelayedMouseEvent;
1094 
1095   bool mDragState = false;  // for drag purposes
1096   bool mAccessibleCaretEnabled = false;
1097 };
1098 
1099 #endif /* nsFrameSelection_h___ */
1100