summaryrefslogtreecommitdiff
path: root/layout/base/AccessibleCaretManager.h
blob: 1cfe85d01b20eec9fd3caa1a50acc5d94b86476a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef AccessibleCaretManager_h
#define AccessibleCaretManager_h

#include "AccessibleCaret.h"
#include "nsCOMPtr.h"
#include "nsCoord.h"
#include "nsIDOMMouseEvent.h"
#include "nsIFrame.h"
#include "nsISelectionListener.h"
#include "mozilla/RefPtr.h"
#include "nsWeakReference.h"
#include "mozilla/dom/CaretStateChangedEvent.h"
#include "mozilla/EventForwards.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/WeakPtr.h"

class nsFrameSelection;
class nsIContent;
class nsIPresShell;
struct nsPoint;

namespace mozilla {

namespace dom {
class Element;
class Selection;
} // namespace dom

// -----------------------------------------------------------------------------
// AccessibleCaretManager does not deal with events or callbacks directly. It
// relies on AccessibleCaretEventHub to call its public methods to do the work.
// All codes needed to interact with PresShell, Selection, and AccessibleCaret
// should be written in AccessibleCaretManager.
//
// None the public methods in AccessibleCaretManager will flush layout or style
// prior to performing its task. The caller must ensure the layout is up to
// date.
//
// Please see the wiki page for more information.
// https://wiki.mozilla.org/AccessibleCaret
//
class AccessibleCaretManager
{
public:
  explicit AccessibleCaretManager(nsIPresShell* aPresShell);
  virtual ~AccessibleCaretManager();

  // Called by AccessibleCaretEventHub to inform us that PresShell is destroyed.
  void Terminate();

  // The aPoint in the following public methods should be relative to root
  // frame.

  // Press caret on the given point. Return NS_OK if the point is actually on
  // one of the carets.
  virtual nsresult PressCaret(const nsPoint& aPoint, EventClassID aEventClass);

  // Drag caret to the given point. It's required to call PressCaret()
  // beforehand.
  virtual nsresult DragCaret(const nsPoint& aPoint);

  // Release caret from he previous press action. It's required to call
  // PressCaret() beforehand.
  virtual nsresult ReleaseCaret();

  // A quick single tap on caret on given point without dragging.
  virtual nsresult TapCaret(const nsPoint& aPoint);

  // Select a word or bring up paste shortcut (if Gaia is listening) under the
  // given point.
  virtual nsresult SelectWordOrShortcut(const nsPoint& aPoint);

  // Handle scroll-start event.
  virtual void OnScrollStart();

  // Handle scroll-end event.
  virtual void OnScrollEnd();

  // Handle ScrollPositionChanged from nsIScrollObserver. This might be called
  // at anytime, not necessary between OnScrollStart and OnScrollEnd.
  virtual void OnScrollPositionChanged();

  // Handle reflow event from nsIReflowObserver.
  virtual void OnReflow();

  // Handle blur event from nsFocusManager.
  virtual void OnBlur();

  // Handle NotifySelectionChanged event from nsISelectionListener.
  virtual nsresult OnSelectionChanged(nsIDOMDocument* aDoc,
                                      nsISelection* aSel,
                                      int16_t aReason);
  // Handle key event.
  virtual void OnKeyboardEvent();

  // The canvas frame holding the accessible caret anonymous content elements
  // was reconstructed, resulting in the content elements getting cloned.
  virtual void OnFrameReconstruction();

  // Update the manager with the last input source that was observed. This
  // is used in part to determine if the carets should be shown or hidden.
  void SetLastInputSource(uint16_t aInputSource);

protected:
  // This enum representing the number of AccessibleCarets on the screen.
  enum class CaretMode : uint8_t {
    // No caret on the screen.
    None,

    // One caret, i.e. the selection is collapsed.
    Cursor,

    // Two carets, i.e. the selection is not collapsed.
    Selection
  };

  friend std::ostream& operator<<(std::ostream& aStream,
                                  const CaretMode& aCaretMode);

  enum class UpdateCaretsHint : uint8_t {
    // Update everything including appearance and position.
    Default,

    // Update everything while respecting the old appearance. For example, if
    // the caret in cursor mode is hidden due to timeout, do not change its
    // appearance to Normal.
    RespectOldAppearance
  };

  friend std::ostream& operator<<(std::ostream& aStream,
                                  const UpdateCaretsHint& aResult);

  // Update carets based on current selection status. This function will flush
  // layout, so caller must ensure the PresShell is still valid after calling
  // this method.
  void UpdateCarets(UpdateCaretsHint aHint = UpdateCaretsHint::Default);

  // Force hiding all carets regardless of the current selection status.
  void HideCarets();

  void UpdateCaretsForCursorMode(UpdateCaretsHint aHint);
  void UpdateCaretsForSelectionMode(UpdateCaretsHint aHint);

  // Provide haptic / touch feedback, primarily for select on longpress.
  void ProvideHapticFeedback();

  // Get the nearest enclosing focusable frame of aFrame.
  // @return focusable frame if there is any; nullptr otherwise.
  nsIFrame* GetFocusableFrame(nsIFrame* aFrame) const;

  // Change focus to aFrame if it isn't nullptr. Otherwise, clear the old focus
  // then re-focus the window.
  void ChangeFocusToOrClearOldFocus(nsIFrame* aFrame) const;

  nsresult SelectWord(nsIFrame* aFrame, const nsPoint& aPoint) const;
  void SetSelectionDragState(bool aState) const;

  // Called to extend a selection if possible that it's a phone number.
  void SelectMoreIfPhoneNumber() const;
  // Extend the current phone number selection in the requested direction.
  void ExtendPhoneNumberSelection(const nsAString& aDirection) const;

  void SetSelectionDirection(nsDirection aDir) const;

  // If aDirection is eDirNext, get the frame for the range start in the first
  // range from the current selection, and return the offset into that frame as
  // well as the range start node and the node offset. Otherwise, get the frame
  // and offset for the range end in the last range instead.
  nsIFrame* GetFrameForFirstRangeStartOrLastRangeEnd(
    nsDirection aDirection, int32_t* aOutOffset, nsINode** aOutNode = nullptr,
    int32_t* aOutNodeOffset = nullptr) const;

  nsresult DragCaretInternal(const nsPoint& aPoint);
  nsPoint AdjustDragBoundary(const nsPoint& aPoint) const;
  void ClearMaintainedSelection() const;

  // Caller is responsible to use IsTerminated() to check whether PresShell is
  // still valid.
  void FlushLayout() const;

  dom::Element* GetEditingHostForFrame(nsIFrame* aFrame) const;
  dom::Selection* GetSelection() const;
  already_AddRefed<nsFrameSelection> GetFrameSelection() const;

  // Get the union of all the child frame scrollable overflow rects for aFrame,
  // which is used as a helper function to restrict the area where the caret can
  // be dragged. Returns the rect relative to aFrame.
  nsRect GetAllChildFrameRectsUnion(nsIFrame* aFrame) const;

  // Restrict the active caret's dragging position based on
  // sCaretsAllowDraggingAcrossOtherCaret. If the active caret is the first
  // caret, the `limit` will be the previous character of the second caret.
  // Otherwise, the `limit` will be the next character of the first caret.
  //
  // @param aOffsets is the new position of the active caret, and it will be set
  // to the `limit` when 1) sCaretsAllowDraggingAcrossOtherCaret is false and
  // it's being dragged past the limit. 2) sCaretsAllowDraggingAcrossOtherCaret
  // is true and the active caret's position is the same as the inactive's
  // position.
  // @return true if the aOffsets is suitable for changing the selection.
  bool RestrictCaretDraggingOffsets(nsIFrame::ContentOffsets& aOffsets);

  // Timeout in milliseconds to hide the AccessibleCaret under cursor mode while
  // no one touches it.
  uint32_t CaretTimeoutMs() const;
  void LaunchCaretTimeoutTimer();
  void CancelCaretTimeoutTimer();

  // ---------------------------------------------------------------------------
  // The following functions are made virtual for stubbing or mocking in gtest.
  //
  // @return true if Terminate() had been called.
  virtual bool IsTerminated() const { return !mPresShell; }

  // Get caret mode based on current selection.
  virtual CaretMode GetCaretMode() const;

  // @return true if aStartFrame comes before aEndFrame.
  virtual bool CompareTreePosition(nsIFrame* aStartFrame,
                                   nsIFrame* aEndFrame) const;

  // Check if the two carets is overlapping to become tilt.
  virtual void UpdateCaretsForOverlappingTilt();

  // Make the two carets always tilt.
  virtual void UpdateCaretsForAlwaysTilt(nsIFrame* aStartFrame,
                                         nsIFrame* aEndFrame);

  // Check whether AccessibleCaret is displayable in cursor mode or not.
  // @param aOutFrame returns frame of the cursor if it's displayable.
  // @param aOutOffset returns frame offset as well.
  virtual bool IsCaretDisplayableInCursorMode(nsIFrame** aOutFrame = nullptr,
                                              int32_t* aOutOffset = nullptr) const;

  virtual bool HasNonEmptyTextContent(nsINode* aNode) const;

  // This function will flush layout, so caller must ensure the PresShell is
  // still valid after calling this method.
  virtual void DispatchCaretStateChangedEvent(dom::CaretChangedReason aReason) const;

  // ---------------------------------------------------------------------------
  // Member variables
  //
  nscoord mOffsetYToCaretLogicalPosition = NS_UNCONSTRAINEDSIZE;

  // AccessibleCaretEventHub owns us by a UniquePtr. When it's destroyed, we'll
  // also be destroyed. No need to worry if we outlive mPresShell.
  //
  // mPresShell will be set to nullptr in Terminate(). Therefore mPresShell is
  // nullptr either we are in gtest or PresShell::IsDestroying() is true.
  nsIPresShell* MOZ_NON_OWNING_REF mPresShell = nullptr;

  // First caret is attached to nsCaret in cursor mode, and is attached to
  // selection highlight as the left caret in selection mode.
  UniquePtr<AccessibleCaret> mFirstCaret;

  // Second caret is used solely in selection mode, and is attached to selection
  // highlight as the right caret.
  UniquePtr<AccessibleCaret> mSecondCaret;

  // The caret being pressed or dragged.
  AccessibleCaret* mActiveCaret = nullptr;

  // The timer for hiding the caret in cursor mode after timeout behind the
  // preference "layout.accessiblecaret.timeout_ms".
  nsCOMPtr<nsITimer> mCaretTimeoutTimer;

  // The caret mode since last update carets.
  CaretMode mLastUpdateCaretMode = CaretMode::None;

  // Store the appearance of the carets when calling OnScrollStart() so that it
  // can be restored in OnScrollEnd().
  AccessibleCaret::Appearance mFirstCaretAppearanceOnScrollStart =
                                 AccessibleCaret::Appearance::None;
  AccessibleCaret::Appearance mSecondCaretAppearanceOnScrollStart =
                                 AccessibleCaret::Appearance::None;

  // The last input source that the event hub saw. We use this to decide whether
  // or not show the carets when the selection is updated, as we want to hide
  // the carets for mouse-triggered selection changes but show them for other
  // input types such as touch.
  uint16_t mLastInputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;

  static const int32_t kAutoScrollTimerDelay = 30;

  // Clicking on the boundary of input or textarea will move the caret to the
  // front or end of the content. To avoid this, we need to deflate the content
  // boundary by 61 app units, which is 1 pixel + 1 app unit as defined in
  // AppUnit.h.
  static const int32_t kBoundaryAppUnits = 61;

  // Preference to show selection bars at the two ends in selection mode. The
  // selection bar is always disabled in cursor mode.
  static bool sSelectionBarEnabled;

  // Preference to allow smarter selection of phone numbers,
  // when user long presses text to start.
  static bool sExtendSelectionForPhoneNumber;

  // Preference to show caret in cursor mode when long tapping on an empty
  // content. This also changes the default update behavior in cursor mode,
  // which is based on the emptiness of the content, into something more
  // heuristic. See UpdateCaretsForCursorMode() for the details.
  static bool sCaretShownWhenLongTappingOnEmptyContent;

  // Preference to make carets always tilt in selection mode. By default, the
  // carets become tilt only when they are overlapping.
  static bool sCaretsAlwaysTilt;

  // Preference to allow carets always show when scrolling (either panning or
  // zooming) the page. When set to false, carets will hide during scrolling,
  // and show again after the user lifts the finger off the screen.
  static bool sCaretsAlwaysShowWhenScrolling;

  // By default, javascript content selection changes closes AccessibleCarets and
  // UI interactions. Optionally, we can try to maintain the active UI, keeping
  // carets and ActionBar available.
  static bool sCaretsScriptUpdates;

  // Preference to allow one caret to be dragged across the other caret without
  // any limitation. When set to false, one caret cannot be dragged across the
  // other one.
  static bool sCaretsAllowDraggingAcrossOtherCaret;

  // AccessibleCaret pref for haptic feedback behaviour on longPress.
  static bool sHapticFeedback;

  // Preference to keep carets hidden when the selection is being manipulated
  // by mouse input (as opposed to touch/pen/etc.).
  static bool sHideCaretsForMouseInput;
};

std::ostream& operator<<(std::ostream& aStream,
                         const AccessibleCaretManager::CaretMode& aCaretMode);

std::ostream& operator<<(std::ostream& aStream,
                         const AccessibleCaretManager::UpdateCaretsHint& aResult);

} // namespace mozilla

#endif // AccessibleCaretManager_h