/src/mozilla-central/layout/base/AccessibleCaretManager.h
Line | Count | Source (jump to first uncovered line) |
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 | | |
12 | | #include "mozilla/dom/CaretStateChangedEvent.h" |
13 | | #include "mozilla/dom/MouseEventBinding.h" |
14 | | #include "mozilla/EnumSet.h" |
15 | | #include "mozilla/EventForwards.h" |
16 | | #include "mozilla/RefPtr.h" |
17 | | #include "mozilla/UniquePtr.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 | | class nsIDocument; |
26 | | class nsIPresShell; |
27 | | struct nsPoint; |
28 | | |
29 | | namespace mozilla { |
30 | | |
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 | | // |
46 | | // Please see the wiki page for more information. |
47 | | // https://wiki.mozilla.org/AccessibleCaret |
48 | | // |
49 | | class AccessibleCaretManager |
50 | | { |
51 | | public: |
52 | | explicit AccessibleCaretManager(nsIPresShell* aPresShell); |
53 | | virtual ~AccessibleCaretManager(); |
54 | | |
55 | | // Called by AccessibleCaretEventHub to inform us that PresShell is destroyed. |
56 | | void Terminate(); |
57 | | |
58 | | // The aPoint in the following public methods should be relative to root |
59 | | // frame. |
60 | | |
61 | | // Press caret on the given point. Return NS_OK if the point is actually on |
62 | | // one of the carets. |
63 | | MOZ_CAN_RUN_SCRIPT |
64 | | virtual nsresult PressCaret(const nsPoint& aPoint, |
65 | | EventClassID aEventClass); |
66 | | |
67 | | // Drag caret to the given point. It's required to call PressCaret() |
68 | | // beforehand. |
69 | | MOZ_CAN_RUN_SCRIPT |
70 | | virtual nsresult DragCaret(const nsPoint& aPoint); |
71 | | |
72 | | // Release caret from he previous press action. It's required to call |
73 | | // PressCaret() beforehand. |
74 | | MOZ_CAN_RUN_SCRIPT |
75 | | virtual nsresult ReleaseCaret(); |
76 | | |
77 | | // A quick single tap on caret on given point without dragging. |
78 | | MOZ_CAN_RUN_SCRIPT |
79 | | virtual nsresult TapCaret(const nsPoint& aPoint); |
80 | | |
81 | | // Select a word or bring up paste shortcut (if Gaia is listening) under the |
82 | | // given point. |
83 | | MOZ_CAN_RUN_SCRIPT |
84 | | virtual nsresult SelectWordOrShortcut(const nsPoint& aPoint); |
85 | | |
86 | | // Handle scroll-start event. |
87 | | MOZ_CAN_RUN_SCRIPT |
88 | | virtual void OnScrollStart(); |
89 | | |
90 | | // Handle scroll-end event. |
91 | | MOZ_CAN_RUN_SCRIPT |
92 | | virtual void OnScrollEnd(); |
93 | | |
94 | | // Handle ScrollPositionChanged from nsIScrollObserver. This might be called |
95 | | // at anytime, not necessary between OnScrollStart and OnScrollEnd. |
96 | | MOZ_CAN_RUN_SCRIPT |
97 | | virtual void OnScrollPositionChanged(); |
98 | | |
99 | | // Handle reflow event from nsIReflowObserver. |
100 | | MOZ_CAN_RUN_SCRIPT |
101 | | virtual void OnReflow(); |
102 | | |
103 | | // Handle blur event from nsFocusManager. |
104 | | MOZ_CAN_RUN_SCRIPT |
105 | | virtual void OnBlur(); |
106 | | |
107 | | // Handle NotifySelectionChanged event from nsISelectionListener. |
108 | | MOZ_CAN_RUN_SCRIPT |
109 | | virtual nsresult OnSelectionChanged(nsIDocument* aDoc, |
110 | | dom::Selection* aSel, |
111 | | int16_t aReason); |
112 | | // Handle key event. |
113 | | MOZ_CAN_RUN_SCRIPT |
114 | | virtual void OnKeyboardEvent(); |
115 | | |
116 | | // The canvas frame holding the accessible caret anonymous content elements |
117 | | // was reconstructed, resulting in the content elements getting cloned. |
118 | | virtual void OnFrameReconstruction(); |
119 | | |
120 | | // Update the manager with the last input source that was observed. This |
121 | | // is used in part to determine if the carets should be shown or hidden. |
122 | | void SetLastInputSource(uint16_t aInputSource); |
123 | | |
124 | | protected: |
125 | | // This enum representing the number of AccessibleCarets on the screen. |
126 | | enum class CaretMode : uint8_t { |
127 | | // No caret on the screen. |
128 | | None, |
129 | | |
130 | | // One caret, i.e. the selection is collapsed. |
131 | | Cursor, |
132 | | |
133 | | // Two carets, i.e. the selection is not collapsed. |
134 | | Selection |
135 | | }; |
136 | | |
137 | | friend std::ostream& operator<<(std::ostream& aStream, |
138 | | const CaretMode& aCaretMode); |
139 | | |
140 | | enum class UpdateCaretsHint : uint8_t { |
141 | | // Update everything including appearance and position. |
142 | | Default, |
143 | | |
144 | | // Update everything while respecting the old appearance. For example, if |
145 | | // the caret in cursor mode is hidden due to blur, do not change its |
146 | | // appearance to Normal. |
147 | | RespectOldAppearance, |
148 | | |
149 | | // No CaretStateChangedEvent will be dispatched in the end of |
150 | | // UpdateCarets(). |
151 | | DispatchNoEvent, |
152 | | }; |
153 | | |
154 | | using UpdateCaretsHintSet = mozilla::EnumSet<UpdateCaretsHint>; |
155 | | |
156 | | friend std::ostream& operator<<(std::ostream& aStream, |
157 | | const UpdateCaretsHint& aResult); |
158 | | |
159 | | // Update carets based on current selection status. This function will flush |
160 | | // layout, so caller must ensure the PresShell is still valid after calling |
161 | | // this method. |
162 | | MOZ_CAN_RUN_SCRIPT |
163 | | void UpdateCarets( |
164 | | const UpdateCaretsHintSet& aHints = UpdateCaretsHint::Default); |
165 | | |
166 | | // Force hiding all carets regardless of the current selection status. |
167 | | MOZ_CAN_RUN_SCRIPT |
168 | | void HideCarets(); |
169 | | |
170 | | MOZ_CAN_RUN_SCRIPT |
171 | | void UpdateCaretsForCursorMode(const UpdateCaretsHintSet& aHints); |
172 | | |
173 | | MOZ_CAN_RUN_SCRIPT |
174 | | void UpdateCaretsForSelectionMode(const UpdateCaretsHintSet& aHints); |
175 | | |
176 | | // Provide haptic / touch feedback, primarily for select on longpress. |
177 | | void ProvideHapticFeedback(); |
178 | | |
179 | | // Get the nearest enclosing focusable frame of aFrame. |
180 | | // @return focusable frame if there is any; nullptr otherwise. |
181 | | nsIFrame* GetFocusableFrame(nsIFrame* aFrame) const; |
182 | | |
183 | | // Change focus to aFrame if it isn't nullptr. Otherwise, clear the old focus |
184 | | // then re-focus the window. |
185 | | void ChangeFocusToOrClearOldFocus(nsIFrame* aFrame) const; |
186 | | |
187 | | nsresult SelectWord(nsIFrame* aFrame, const nsPoint& aPoint) const; |
188 | | void SetSelectionDragState(bool aState) const; |
189 | | |
190 | | // Return true if the candidate string is a phone number. |
191 | | bool IsPhoneNumber(nsAString& aCandidate) const; |
192 | | |
193 | | // Extend the current selection forwards and backwards if it's already a |
194 | | // phone number. |
195 | | void SelectMoreIfPhoneNumber() const; |
196 | | |
197 | | // Extend the current phone number selection in the requested direction. |
198 | | void ExtendPhoneNumberSelection(const nsAString& aDirection) const; |
199 | | |
200 | | void SetSelectionDirection(nsDirection aDir) const; |
201 | | |
202 | | // If aDirection is eDirNext, get the frame for the range start in the first |
203 | | // range from the current selection, and return the offset into that frame as |
204 | | // well as the range start content and the content offset. Otherwise, get the |
205 | | // frame and the offset for the range end in the last range instead. |
206 | | nsIFrame* GetFrameForFirstRangeStartOrLastRangeEnd( |
207 | | nsDirection aDirection, |
208 | | int32_t* aOutOffset, |
209 | | nsIContent** aOutContent = nullptr, |
210 | | int32_t* aOutContentOffset = nullptr) const; |
211 | | |
212 | | nsresult DragCaretInternal(const nsPoint& aPoint); |
213 | | nsPoint AdjustDragBoundary(const nsPoint& aPoint) const; |
214 | | |
215 | | // Start the selection scroll timer if the caret is being dragged out of |
216 | | // the scroll port. |
217 | | void StartSelectionAutoScrollTimer(const nsPoint& aPoint) const; |
218 | | void StopSelectionAutoScrollTimer() const; |
219 | | |
220 | | void ClearMaintainedSelection() const; |
221 | | |
222 | | // This method could kill the shell, so callers to methods that call |
223 | | // FlushLayout should ensure the event hub that owns us is still alive. |
224 | | // |
225 | | // See the mRefCnt assertions in AccessibleCaretEventHub. |
226 | | // |
227 | | // Returns whether mPresShell we're holding is still valid. |
228 | | MOZ_MUST_USE MOZ_CAN_RUN_SCRIPT |
229 | | bool FlushLayout(); |
230 | | |
231 | | dom::Element* GetEditingHostForFrame(nsIFrame* aFrame) const; |
232 | | dom::Selection* GetSelection() const; |
233 | | already_AddRefed<nsFrameSelection> GetFrameSelection() const; |
234 | | nsAutoString StringifiedSelection() const; |
235 | | |
236 | | // Get the union of all the child frame scrollable overflow rects for aFrame, |
237 | | // which is used as a helper function to restrict the area where the caret can |
238 | | // be dragged. Returns the rect relative to aFrame. |
239 | | nsRect GetAllChildFrameRectsUnion(nsIFrame* aFrame) const; |
240 | | |
241 | | // Restrict the active caret's dragging position based on |
242 | | // sCaretsAllowDraggingAcrossOtherCaret. If the active caret is the first |
243 | | // caret, the `limit` will be the previous character of the second caret. |
244 | | // Otherwise, the `limit` will be the next character of the first caret. |
245 | | // |
246 | | // @param aOffsets is the new position of the active caret, and it will be set |
247 | | // to the `limit` when 1) sCaretsAllowDraggingAcrossOtherCaret is false and |
248 | | // it's being dragged past the limit. 2) sCaretsAllowDraggingAcrossOtherCaret |
249 | | // is true and the active caret's position is the same as the inactive's |
250 | | // position. |
251 | | // @return true if the aOffsets is suitable for changing the selection. |
252 | | bool RestrictCaretDraggingOffsets(nsIFrame::ContentOffsets& aOffsets); |
253 | | |
254 | | // --------------------------------------------------------------------------- |
255 | | // The following functions are made virtual for stubbing or mocking in gtest. |
256 | | // |
257 | | // @return true if Terminate() had been called. |
258 | 0 | virtual bool IsTerminated() const { return !mPresShell; } |
259 | | |
260 | | // Get caret mode based on current selection. |
261 | | virtual CaretMode GetCaretMode() const; |
262 | | |
263 | | // @return true if aStartFrame comes before aEndFrame. |
264 | | virtual bool CompareTreePosition(nsIFrame* aStartFrame, |
265 | | nsIFrame* aEndFrame) const; |
266 | | |
267 | | // Check if the two carets is overlapping to become tilt. |
268 | | // @return true if the two carets become tilt; false, otherwise. |
269 | | virtual bool UpdateCaretsForOverlappingTilt(); |
270 | | |
271 | | // Make the two carets always tilt. |
272 | | virtual void UpdateCaretsForAlwaysTilt(nsIFrame* aStartFrame, |
273 | | nsIFrame* aEndFrame); |
274 | | |
275 | | // Check whether AccessibleCaret is displayable in cursor mode or not. |
276 | | // @param aOutFrame returns frame of the cursor if it's displayable. |
277 | | // @param aOutOffset returns frame offset as well. |
278 | | virtual bool IsCaretDisplayableInCursorMode(nsIFrame** aOutFrame = nullptr, |
279 | | int32_t* aOutOffset = nullptr) const; |
280 | | |
281 | | virtual bool HasNonEmptyTextContent(nsINode* aNode) const; |
282 | | |
283 | | // This function will flush layout, so caller must ensure the PresShell is |
284 | | // still valid after calling this method. |
285 | | MOZ_CAN_RUN_SCRIPT |
286 | | virtual void DispatchCaretStateChangedEvent(dom::CaretChangedReason aReason); |
287 | | |
288 | | // --------------------------------------------------------------------------- |
289 | | // Member variables |
290 | | // |
291 | | nscoord mOffsetYToCaretLogicalPosition = NS_UNCONSTRAINEDSIZE; |
292 | | |
293 | | // AccessibleCaretEventHub owns us by a UniquePtr. When it's destroyed, we'll |
294 | | // also be destroyed. No need to worry if we outlive mPresShell. |
295 | | // |
296 | | // mPresShell will be set to nullptr in Terminate(). Therefore mPresShell is |
297 | | // nullptr either we are in gtest or PresShell::IsDestroying() is true. |
298 | | nsIPresShell* MOZ_NON_OWNING_REF mPresShell = nullptr; |
299 | | |
300 | | // First caret is attached to nsCaret in cursor mode, and is attached to |
301 | | // selection highlight as the left caret in selection mode. |
302 | | UniquePtr<AccessibleCaret> mFirstCaret; |
303 | | |
304 | | // Second caret is used solely in selection mode, and is attached to selection |
305 | | // highlight as the right caret. |
306 | | UniquePtr<AccessibleCaret> mSecondCaret; |
307 | | |
308 | | // The caret being pressed or dragged. |
309 | | AccessibleCaret* mActiveCaret = nullptr; |
310 | | |
311 | | // The caret mode since last update carets. |
312 | | CaretMode mLastUpdateCaretMode = CaretMode::None; |
313 | | |
314 | | // The last input source that the event hub saw. We use this to decide whether |
315 | | // or not show the carets when the selection is updated, as we want to hide |
316 | | // the carets for mouse-triggered selection changes but show them for other |
317 | | // input types such as touch. |
318 | | uint16_t mLastInputSource = dom::MouseEvent_Binding::MOZ_SOURCE_UNKNOWN; |
319 | | |
320 | | // Set to true in OnScrollStart() and set to false in OnScrollEnd(). |
321 | | bool mIsScrollStarted = false; |
322 | | |
323 | | // Whether we're flushing layout, used for sanity-checking. |
324 | | bool mFlushingLayout = false; |
325 | | |
326 | | static const int32_t kAutoScrollTimerDelay = 30; |
327 | | |
328 | | // Clicking on the boundary of input or textarea will move the caret to the |
329 | | // front or end of the content. To avoid this, we need to deflate the content |
330 | | // boundary by 61 app units, which is 1 pixel + 1 app unit as defined in |
331 | | // AppUnit.h. |
332 | | static const int32_t kBoundaryAppUnits = 61; |
333 | | |
334 | | enum ScriptUpdateMode : int32_t { |
335 | | // By default, always hide carets for selection changes due to JS calls. |
336 | | kScriptAlwaysHide, |
337 | | // Update any visible carets for selection changes due to JS calls, |
338 | | // but don't show carets if carets are hidden. |
339 | | kScriptUpdateVisible, |
340 | | // Always show carets for selection changes due to JS calls. |
341 | | kScriptAlwaysShow |
342 | | }; |
343 | | }; |
344 | | |
345 | | std::ostream& operator<<(std::ostream& aStream, |
346 | | const AccessibleCaretManager::CaretMode& aCaretMode); |
347 | | |
348 | | std::ostream& operator<<(std::ostream& aStream, |
349 | | const AccessibleCaretManager::UpdateCaretsHint& aResult); |
350 | | |
351 | | } // namespace mozilla |
352 | | |
353 | | #endif // AccessibleCaretManager_h |