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