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