tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AccessibleCaretManager.h (19752B)


      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 AccessibleCaretManager_h
      8 #define AccessibleCaretManager_h
      9 
     10 #include "AccessibleCaret.h"
     11 #include "mozilla/AlreadyAddRefed.h"
     12 #include "mozilla/Attributes.h"
     13 #include "mozilla/EnumSet.h"
     14 #include "mozilla/EventForwards.h"
     15 #include "mozilla/UniquePtr.h"
     16 #include "mozilla/dom/CaretStateChangedEvent.h"
     17 #include "mozilla/dom/MouseEventBinding.h"
     18 #include "nsCOMPtr.h"
     19 #include "nsCoord.h"
     20 #include "nsIFrame.h"
     21 #include "nsISelectionListener.h"
     22 
     23 class nsFrameSelection;
     24 class nsIContent;
     25 
     26 struct nsPoint;
     27 
     28 namespace mozilla {
     29 class PresShell;
     30 struct FrameAndOffset;  // defined in SelectionMovementUtils.h
     31 namespace dom {
     32 class Element;
     33 class Selection;
     34 }  // namespace dom
     35 
     36 // -----------------------------------------------------------------------------
     37 // AccessibleCaretManager does not deal with events or callbacks directly. It
     38 // relies on AccessibleCaretEventHub to call its public methods to do the work.
     39 // All codes needed to interact with PresShell, Selection, and AccessibleCaret
     40 // should be written in AccessibleCaretManager.
     41 //
     42 // None the public methods in AccessibleCaretManager will flush layout or style
     43 // prior to performing its task. The caller must ensure the layout is up to
     44 // date.
     45 // TODO: it's unclear, whether that's true. `OnSelectionChanged` calls
     46 // `UpdateCarets`, which may flush layout.
     47 //
     48 // Please see the wiki page for more information.
     49 // https://wiki.mozilla.org/AccessibleCaret
     50 //
     51 class AccessibleCaretManager {
     52 public:
     53  // @param aPresShell may be nullptr for testing.
     54  explicit AccessibleCaretManager(PresShell* aPresShell);
     55  virtual ~AccessibleCaretManager() = default;
     56 
     57  // Called by AccessibleCaretEventHub to inform us that PresShell is destroyed.
     58  void Terminate();
     59 
     60  // The aPoint in the following public methods should be relative to root
     61  // frame.
     62 
     63  // Press caret on the given point. Return NS_OK if the point is actually on
     64  // one of the carets.
     65  MOZ_CAN_RUN_SCRIPT
     66  virtual nsresult PressCaret(const nsPoint& aPoint, EventClassID aEventClass);
     67 
     68  // Drag caret to the given point. It's required to call PressCaret()
     69  // beforehand.
     70  MOZ_CAN_RUN_SCRIPT
     71  virtual nsresult DragCaret(const nsPoint& aPoint);
     72 
     73  // Release caret from he previous press action. It's required to call
     74  // PressCaret() beforehand.
     75  MOZ_CAN_RUN_SCRIPT
     76  virtual nsresult ReleaseCaret();
     77 
     78  // A quick single tap on caret on given point without dragging.
     79  MOZ_CAN_RUN_SCRIPT
     80  virtual nsresult TapCaret(const nsPoint& aPoint);
     81 
     82  // Select a word or bring up paste shortcut (if Gaia is listening) under the
     83  // given point.
     84  MOZ_CAN_RUN_SCRIPT
     85  virtual nsresult SelectWordOrShortcut(const nsPoint& aPoint);
     86 
     87  // Handle scroll-start event.
     88  MOZ_CAN_RUN_SCRIPT
     89  virtual void OnScrollStart();
     90 
     91  // Handle scroll-end event.
     92  MOZ_CAN_RUN_SCRIPT
     93  virtual void OnScrollEnd();
     94 
     95  // Handle ScrollPositionChanged from nsIScrollObserver. This might be called
     96  // at anytime, not necessary between OnScrollStart and OnScrollEnd.
     97  MOZ_CAN_RUN_SCRIPT
     98  virtual void OnScrollPositionChanged();
     99 
    100  // Handle reflow event from nsIReflowObserver.
    101  MOZ_CAN_RUN_SCRIPT
    102  virtual void OnReflow();
    103 
    104  // Handle blur event from nsFocusManager.
    105  MOZ_CAN_RUN_SCRIPT
    106  virtual void OnBlur();
    107 
    108  // Handle NotifySelectionChanged event from nsISelectionListener.
    109  // @param aReason potentially multiple of the reasons defined in
    110  //                nsISelectionListener.idl.
    111  MOZ_CAN_RUN_SCRIPT
    112  virtual nsresult OnSelectionChanged(dom::Document* aDoc, dom::Selection* aSel,
    113                                      int16_t aReason);
    114  // Handle key event.
    115  MOZ_CAN_RUN_SCRIPT
    116  virtual void OnKeyboardEvent();
    117 
    118  // Update the manager with the last input source that was observed. This
    119  // is used in part to determine if the carets should be shown or hidden.
    120  void SetLastInputSource(uint16_t aInputSource);
    121 
    122  // Returns True indicating that we should disable APZ to avoid jumpy carets.
    123  bool ShouldDisableApz() const;
    124 
    125 protected:
    126  class Carets;
    127 
    128  // @param aPresShell may be nullptr for testing.
    129  AccessibleCaretManager(PresShell* aPresShell, Carets aCarets);
    130 
    131  // This enum representing the number of AccessibleCarets on the screen.
    132  enum class CaretMode : uint8_t {
    133    // No caret on the screen.
    134    None,
    135 
    136    // One caret, i.e. the selection is collapsed.
    137    Cursor,
    138 
    139    // Two carets, i.e. the selection is not collapsed.
    140    Selection
    141  };
    142 
    143  friend std::ostream& operator<<(std::ostream& aStream,
    144                                  const CaretMode& aCaretMode);
    145 
    146  enum class UpdateCaretsHint : uint8_t {
    147    // Update everything including appearance and position.
    148    Default,
    149 
    150    // Update everything while respecting the old appearance. For example, if
    151    // the caret in cursor mode is hidden due to blur, do not change its
    152    // appearance to Normal.
    153    RespectOldAppearance,
    154 
    155    // No CaretStateChangedEvent will be dispatched in the end of
    156    // UpdateCarets().
    157    DispatchNoEvent,
    158  };
    159 
    160  using UpdateCaretsHintSet = mozilla::EnumSet<UpdateCaretsHint>;
    161 
    162  friend std::ostream& operator<<(std::ostream& aStream,
    163                                  const UpdateCaretsHint& aResult);
    164 
    165  enum class Terminated : bool { No, Yes };
    166 
    167  // This method could kill the shell, so callers to methods that call
    168  // MaybeFlushLayout should ensure the event hub that owns us is still alive.
    169  //
    170  // See the mRefCnt assertions in AccessibleCaretEventHub.
    171  //
    172  [[nodiscard]] MOZ_CAN_RUN_SCRIPT virtual Terminated MaybeFlushLayout();
    173 
    174  // Update carets based on current selection status. This function will flush
    175  // layout, so caller must ensure the PresShell is still valid after calling
    176  // this method.
    177  MOZ_CAN_RUN_SCRIPT
    178  void UpdateCarets(
    179      const UpdateCaretsHintSet& aHints = UpdateCaretsHint::Default);
    180 
    181  // Force hiding all carets regardless of the current selection status, and
    182  // dispatch CaretStateChangedEvent if one of the carets is logically-visible.
    183  MOZ_CAN_RUN_SCRIPT
    184  void HideCaretsAndDispatchCaretStateChangedEvent();
    185 
    186  MOZ_CAN_RUN_SCRIPT
    187  void UpdateCaretsForCursorMode(const UpdateCaretsHintSet& aHints);
    188 
    189  MOZ_CAN_RUN_SCRIPT
    190  void UpdateCaretsForSelectionMode(const UpdateCaretsHintSet& aHints);
    191 
    192  // A helper function to update mShouldDisableApz.
    193  void UpdateShouldDisableApz();
    194 
    195  // Provide haptic / touch feedback, primarily for select on longpress.
    196  void ProvideHapticFeedback();
    197 
    198  // Get the nearest enclosing focusable frame of aFrame.
    199  // @return focusable frame if there is any; nullptr otherwise.
    200  nsIFrame* GetFocusableFrame(nsIFrame* aFrame) const;
    201 
    202  // Change focus to aFrame if it isn't nullptr. Otherwise, clear the old focus
    203  // then re-focus the window.
    204  MOZ_CAN_RUN_SCRIPT_BOUNDARY void ChangeFocusToOrClearOldFocus(
    205      nsIFrame* aFrame) const;
    206 
    207  MOZ_CAN_RUN_SCRIPT
    208  nsresult SelectWord(nsIFrame* aFrame, const nsPoint& aPoint) const;
    209  MOZ_CAN_RUN_SCRIPT void SetSelectionDragState(bool aState) const;
    210 
    211  // Return true if the candidate string is a phone number.
    212  bool IsPhoneNumber(const nsAString& aCandidate) const;
    213 
    214  // Extend the current selection forwards and backwards if it's already a
    215  // phone number.
    216  MOZ_CAN_RUN_SCRIPT
    217  void SelectMoreIfPhoneNumber() const;
    218 
    219  // Extend the current phone number selection in the requested direction.
    220  MOZ_CAN_RUN_SCRIPT
    221  void ExtendPhoneNumberSelection(const nsAString& aDirection) const;
    222 
    223  void SetSelectionDirection(nsDirection aDir) const;
    224 
    225  /**
    226   * Return a frame and offset where to put the first accessible caret in the
    227   * selection mode. The result may be a non-selectable frame which is for a
    228   * child of a selectable content.  If aOutContent is set and its pointee is
    229   * different from the frame content of the result, it means that the result is
    230   * the child at aOutContent and aOutOffsetInContent.
    231   *
    232   * @param aRange      The range, typically the first range of `Selection`.
    233   * @param aOutContent [optional] If set, this will be set to the first
    234   *                    selectable container in aRange. It's typically a
    235   *                    container element.
    236   * @param aOutOffsetInContent
    237   *                    [optional] If set, this will be set to the offset in
    238   *                    the first selectable content (i.e., aOutContent).
    239   *                    NOTE: {*aOutContent, *aOutOffsetInContent} means that
    240   *                    it's the start boundary of actual selectable range at a
    241   *                    container element.
    242   * @return            mFrame is the first meaningful frame whose content is
    243   *                    selected by aRange. Typically, a text frame or a image
    244   *                    frame. Or a container frame which is not selectable but
    245   *                    its parent is selectable.
    246   *                    mOffsetInFrameContent is the offset in
    247   *                    mFrame->GetContent().
    248   *                    I.e, if mFrame->GetContent() is not a void element,
    249   *                    {mFrame->GetContent(), mOffsetInFrameContent} means that
    250   *                    it's the start boundary of visible/meaningful selection
    251   *                    start boundary at a leaf node like a `Text` or position
    252   *                    at the first non-selectable element in selectable node.
    253   */
    254  FrameAndOffset GetFirstVisibleLeafFrameOrUnselectableChildFrame(
    255      nsRange& aRange, nsIContent** aOutContent = nullptr,
    256      int32_t* aOutOffsetInContent = nullptr) const;
    257 
    258  /**
    259   * Return a frame and offset where to put the last accessible caret in the
    260   * selection mode. The result may be a non-selectable frame which is for a
    261   * child of a selectable content.  If aOutContent is set and its pointee is
    262   * different from the frame content of the result, it means that the result is
    263   * the previous sibling of a child at aOutContent and aOutOffsetInContent.
    264   *
    265   * @param aRange      The range, typically the last range of `Selection`.
    266   * @param aOutContent [optional] If set, this will be set to the last
    267   *                    selectable container in aRange. It's typically a
    268   *                    container element.
    269   * @param aOutOffsetInContent
    270   *                    [optional] If set, this will be set to the offset in
    271   *                    the last selectable container (i.e., aOutContent).
    272   *                    NOTE: {*aOutContent, *aOutOffsetInContent} means that
    273   *                    it's the end boundary of actual selectable range at a
    274   *                    container element.
    275   * @return            mFrame is the last meaningful frame whose content is
    276   *                    selected by aRange. Typically, a text frame or a image
    277   *                    frame. Or a container frame which is not selectable but
    278   *                    its parent is selectable.
    279   *                    mOffsetInFrameContent is the offset in
    280   *                    mFrame->GetContent().
    281   *                    I.e, if mFrame->GetContent() is not a void element,
    282   *                    {mFrame->GetContent(), mOffsetInFrameContent} means that
    283   *                    it's the end boundary of visible/meaningful selection
    284   *                    end boundary at a leaf node like a `Text` or position at
    285   *                    the last non-selectable element in selectable node.
    286   */
    287  FrameAndOffset GetLastVisibleLeafFrameOrUnselectableChildFrame(
    288      nsRange& aRange, nsIContent** aOutContent = nullptr,
    289      int32_t* aOutOffsetInContent = nullptr) const;
    290 
    291  MOZ_CAN_RUN_SCRIPT nsresult DragCaretInternal(const nsPoint& aPoint);
    292  nsPoint AdjustDragBoundary(const nsPoint& aPoint) const;
    293 
    294  // Start the selection scroll timer if the caret is being dragged out of
    295  // the scroll port.
    296  MOZ_CAN_RUN_SCRIPT
    297  void StartSelectionAutoScrollTimer(const nsPoint& aPoint) const;
    298  void StopSelectionAutoScrollTimer() const;
    299 
    300  void ClearMaintainedSelection() const;
    301 
    302  static dom::Element* GetEditingHostForFrame(const nsIFrame* aFrame);
    303  dom::Selection* GetSelection() const;
    304  already_AddRefed<nsFrameSelection> GetFrameSelection() const;
    305 
    306  MOZ_CAN_RUN_SCRIPT
    307  nsAutoString StringifiedSelection() const;
    308 
    309  // Get the union of all the child frame scrollable overflow rects for aFrame,
    310  // which is used as a helper function to restrict the area where the caret can
    311  // be dragged. Returns the rect relative to aFrame.
    312  static nsRect GetAllChildFrameRectsUnion(nsIFrame* aFrame);
    313 
    314  // Restrict the active caret's dragging position based on
    315  // sCaretsAllowDraggingAcrossOtherCaret. If the active caret is the first
    316  // caret, the `limit` will be the previous character of the second caret.
    317  // Otherwise, the `limit` will be the next character of the first caret.
    318  //
    319  // @param aOffsets is the new position of the active caret, and it will be set
    320  // to the `limit` when 1) sCaretsAllowDraggingAcrossOtherCaret is false and
    321  // it's being dragged past the limit. 2) sCaretsAllowDraggingAcrossOtherCaret
    322  // is true and the active caret's position is the same as the inactive's
    323  // position.
    324  // @return true if the aOffsets is suitable for changing the selection.
    325  bool RestrictCaretDraggingOffsets(nsIFrame::ContentOffsets& aOffsets);
    326 
    327  // ---------------------------------------------------------------------------
    328  // The following functions are made virtual for stubbing or mocking in gtest.
    329  //
    330  // @return Yes if Terminate() had been called.
    331  virtual Terminated IsTerminated() const {
    332    return mPresShell ? Terminated::No : Terminated::Yes;
    333  }
    334 
    335  // Get caret mode based on current selection.
    336  virtual CaretMode GetCaretMode() const;
    337 
    338  // @return true if aStartFrame comes before aEndFrame.
    339  virtual bool CompareTreePosition(const nsIFrame* aStartFrame,
    340                                   int32_t aStartOffset,
    341                                   const nsIFrame* aEndFrame,
    342                                   int32_t aEndOffset) const;
    343 
    344  // Check if the two carets is overlapping to become tilt.
    345  // @return true if the two carets become tilt; false, otherwise.
    346  virtual bool UpdateCaretsForOverlappingTilt();
    347 
    348  // Make the two carets always tilt.
    349  virtual void UpdateCaretsForAlwaysTilt(const nsIFrame* aStartFrame,
    350                                         const nsIFrame* aEndFrame);
    351 
    352  // Check whether AccessibleCaret is displayable in cursor mode or not.
    353  // @param aOutFrame returns frame of the cursor if it's displayable.
    354  // @param aOutOffset returns frame offset as well.
    355  virtual bool IsCaretDisplayableInCursorMode(
    356      nsIFrame** aOutFrame = nullptr, int32_t* aOutOffset = nullptr) const;
    357 
    358  virtual bool HasNonEmptyTextContent(nsINode* aNode) const;
    359 
    360  // This function will flush layout, so caller must ensure the PresShell is
    361  // still valid after calling this method.
    362  // @param aPoint The event point when the user is pressing or dragging a
    363  //               caret, which is relative to the root frame.
    364  MOZ_CAN_RUN_SCRIPT
    365  virtual void DispatchCaretStateChangedEvent(dom::CaretChangedReason aReason,
    366                                              const nsPoint* aPoint = nullptr);
    367 
    368  // ---------------------------------------------------------------------------
    369  // Member variables
    370  //
    371  nscoord mOffsetYToCaretLogicalPosition = NS_UNCONSTRAINEDSIZE;
    372 
    373  // AccessibleCaretEventHub owns us by a UniquePtr. When it's destroyed, we'll
    374  // also be destroyed. No need to worry if we outlive mPresShell.
    375  //
    376  // mPresShell will be set to nullptr in Terminate(). Therefore mPresShell is
    377  // nullptr either we are in gtest or PresShell::IsDestroying() is true.
    378  PresShell* MOZ_NON_OWNING_REF mPresShell = nullptr;
    379 
    380  class Carets {
    381   public:
    382    Carets(UniquePtr<AccessibleCaret> aFirst,
    383           UniquePtr<AccessibleCaret> aSecond);
    384 
    385    Carets(Carets&&) = default;
    386    Carets(const Carets&) = delete;
    387    Carets& operator=(const Carets&) = delete;
    388 
    389    AccessibleCaret* GetFirst() const { return mFirst.get(); }
    390 
    391    AccessibleCaret* GetSecond() const { return mSecond.get(); }
    392 
    393    bool HasLogicallyVisibleCaret() const {
    394      return mFirst->IsLogicallyVisible() || mSecond->IsLogicallyVisible();
    395    }
    396 
    397    bool HasVisuallyVisibleCaret() const {
    398      return mFirst->IsVisuallyVisible() || mSecond->IsVisuallyVisible();
    399    }
    400 
    401    void Terminate() {
    402      mFirst = nullptr;
    403      mSecond = nullptr;
    404    }
    405 
    406   private:
    407    // First caret is attached to nsCaret in cursor mode, and is attached to
    408    // selection highlight as the left caret in selection mode.
    409    UniquePtr<AccessibleCaret> mFirst;
    410 
    411    // Second caret is used solely in selection mode, and is attached to
    412    // selection highlight as the right caret.
    413    UniquePtr<AccessibleCaret> mSecond;
    414  };
    415 
    416  Carets mCarets;
    417 
    418  // The caret being pressed or dragged.
    419  AccessibleCaret* mActiveCaret = nullptr;
    420 
    421  // The caret mode since last update carets.
    422  CaretMode mLastUpdateCaretMode = CaretMode::None;
    423 
    424  // The last input source that the event hub saw. We use this to decide whether
    425  // or not show the carets when the selection is updated, as we want to hide
    426  // the carets for mouse-triggered selection changes but show them for other
    427  // input types such as touch.
    428  uint16_t mLastInputSource = dom::MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
    429 
    430  // Set to true in OnScrollStart() and set to false in OnScrollEnd().
    431  bool mIsScrollStarted = false;
    432 
    433  class LayoutFlusher final {
    434   public:
    435    LayoutFlusher() = default;
    436 
    437    ~LayoutFlusher();
    438 
    439    LayoutFlusher(const LayoutFlusher&) = delete;
    440    LayoutFlusher& operator=(const LayoutFlusher&) = delete;
    441 
    442    MOZ_CAN_RUN_SCRIPT void MaybeFlush(const PresShell& aPresShell);
    443 
    444    // Set to false to disallow flushing layout in some callbacks such as
    445    // OnReflow(), OnScrollStart(), OnScrollStart(), or
    446    // OnScrollPositionChanged().
    447    bool mAllowFlushing = true;
    448 
    449   private:
    450    // Whether we're flushing layout, used for sanity-checking.
    451    bool mFlushing = false;
    452  };
    453 
    454  LayoutFlusher mLayoutFlusher;
    455 
    456  // Set to True if one of the caret's position is changed in last update.
    457  bool mIsCaretPositionChanged = false;
    458 
    459  class DesiredAsyncPanZoomState final {
    460   public:
    461    void Update(const AccessibleCaretManager& aAccessibleCaretManager);
    462 
    463    bool ShouldDisable() const { return mValue == Value::Disabled; }
    464 
    465   private:
    466    enum class Value : bool { Disabled, Enabled };
    467 
    468    Value mValue = Value::Enabled;
    469  };
    470 
    471  DesiredAsyncPanZoomState mDesiredAsyncPanZoomState;
    472 
    473  static const int32_t kAutoScrollTimerDelay = 30;
    474 
    475  // Clicking on the boundary of input or textarea will move the caret to the
    476  // front or end of the content. To avoid this, we need to deflate the content
    477  // boundary by 61 app units, which is 1 pixel + 1 app unit as defined in
    478  // AppUnit.h.
    479  static const int32_t kBoundaryAppUnits = 61;
    480 
    481  enum ScriptUpdateMode : int32_t {
    482    // By default, always hide carets for selection changes due to JS calls.
    483    kScriptAlwaysHide,
    484    // Update any visible carets for selection changes due to JS calls,
    485    // but don't show carets if carets are hidden.
    486    kScriptUpdateVisible,
    487    // Always show carets for selection changes due to JS calls.
    488    kScriptAlwaysShow
    489  };
    490 };
    491 
    492 std::ostream& operator<<(std::ostream& aStream,
    493                         const AccessibleCaretManager::CaretMode& aCaretMode);
    494 
    495 std::ostream& operator<<(
    496    std::ostream& aStream,
    497    const AccessibleCaretManager::UpdateCaretsHint& aResult);
    498 
    499 }  // namespace mozilla
    500 
    501 #endif  // AccessibleCaretManager_h