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/Attributes.h" 13 #include "mozilla/dom/CaretStateChangedEvent.h" 14 #include "mozilla/dom/MouseEventBinding.h" 15 #include "mozilla/EnumSet.h" 16 #include "mozilla/EventForwards.h" 17 #include "mozilla/RefPtr.h" 18 #include "mozilla/UniquePtr.h" 19 #include "nsCOMPtr.h" 20 #include "nsCoord.h" 21 #include "nsIFrame.h" 22 #include "nsISelectionListener.h" 23 24 class nsFrameSelection; 25 class nsIContent; 26 27 struct nsPoint; 28 29 namespace mozilla { 30 class PresShell; 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 // The canvas frame holding the accessible caret anonymous content elements 119 // was reconstructed, resulting in the content elements getting cloned. 120 virtual void OnFrameReconstruction(); 121 122 // Update the manager with the last input source that was observed. This 123 // is used in part to determine if the carets should be shown or hidden. 124 void SetLastInputSource(uint16_t aInputSource); 125 126 // Returns True indicating that we should disable APZ to avoid jumpy carets. 127 bool ShouldDisableApz() const; 128 129 protected: 130 class Carets; 131 132 // @param aPresShell may be nullptr for testing. 133 AccessibleCaretManager(PresShell* aPresShell, Carets aCarets); 134 135 // This enum representing the number of AccessibleCarets on the screen. 136 enum class CaretMode : uint8_t { 137 // No caret on the screen. 138 None, 139 140 // One caret, i.e. the selection is collapsed. 141 Cursor, 142 143 // Two carets, i.e. the selection is not collapsed. 144 Selection 145 }; 146 147 friend std::ostream& operator<<(std::ostream& aStream, 148 const CaretMode& aCaretMode); 149 150 enum class UpdateCaretsHint : uint8_t { 151 // Update everything including appearance and position. 152 Default, 153 154 // Update everything while respecting the old appearance. For example, if 155 // the caret in cursor mode is hidden due to blur, do not change its 156 // appearance to Normal. 157 RespectOldAppearance, 158 159 // No CaretStateChangedEvent will be dispatched in the end of 160 // UpdateCarets(). 161 DispatchNoEvent, 162 }; 163 164 using UpdateCaretsHintSet = mozilla::EnumSet<UpdateCaretsHint>; 165 166 friend std::ostream& operator<<(std::ostream& aStream, 167 const UpdateCaretsHint& aResult); 168 169 enum class Terminated : bool { No, Yes }; 170 171 // This method could kill the shell, so callers to methods that call 172 // MaybeFlushLayout should ensure the event hub that owns us is still alive. 173 // 174 // See the mRefCnt assertions in AccessibleCaretEventHub. 175 // 176 [[nodiscard]] MOZ_CAN_RUN_SCRIPT virtual Terminated MaybeFlushLayout(); 177 178 // Update carets based on current selection status. This function will flush 179 // layout, so caller must ensure the PresShell is still valid after calling 180 // this method. 181 MOZ_CAN_RUN_SCRIPT 182 void UpdateCarets( 183 const UpdateCaretsHintSet& aHints = UpdateCaretsHint::Default); 184 185 // Force hiding all carets regardless of the current selection status, and 186 // dispatch CaretStateChangedEvent if one of the carets is logically-visible. 187 MOZ_CAN_RUN_SCRIPT 188 void HideCaretsAndDispatchCaretStateChangedEvent(); 189 190 MOZ_CAN_RUN_SCRIPT 191 void UpdateCaretsForCursorMode(const UpdateCaretsHintSet& aHints); 192 193 MOZ_CAN_RUN_SCRIPT 194 void UpdateCaretsForSelectionMode(const UpdateCaretsHintSet& aHints); 195 196 // A helper function to update mShouldDisableApz. 197 void UpdateShouldDisableApz(); 198 199 // Provide haptic / touch feedback, primarily for select on longpress. 200 void ProvideHapticFeedback(); 201 202 // Get the nearest enclosing focusable frame of aFrame. 203 // @return focusable frame if there is any; nullptr otherwise. 204 nsIFrame* GetFocusableFrame(nsIFrame* aFrame) const; 205 206 // Change focus to aFrame if it isn't nullptr. Otherwise, clear the old focus 207 // then re-focus the window. 208 MOZ_CAN_RUN_SCRIPT_BOUNDARY void ChangeFocusToOrClearOldFocus( 209 nsIFrame* aFrame) const; 210 211 MOZ_CAN_RUN_SCRIPT 212 nsresult SelectWord(nsIFrame* aFrame, const nsPoint& aPoint) const; 213 MOZ_CAN_RUN_SCRIPT void SetSelectionDragState(bool aState) const; 214 215 // Return true if the candidate string is a phone number. 216 bool IsPhoneNumber(nsAString& aCandidate) const; 217 218 // Extend the current selection forwards and backwards if it's already a 219 // phone number. 220 MOZ_CAN_RUN_SCRIPT 221 void SelectMoreIfPhoneNumber() const; 222 223 // Extend the current phone number selection in the requested direction. 224 MOZ_CAN_RUN_SCRIPT 225 void ExtendPhoneNumberSelection(const nsAString& aDirection) const; 226 227 void SetSelectionDirection(nsDirection aDir) const; 228 229 // If aDirection is eDirNext, get the frame for the range start in the first 230 // range from the current selection, and return the offset into that frame as 231 // well as the range start content and the content offset. Otherwise, get the 232 // frame and the offset for the range end in the last range instead. 233 nsIFrame* GetFrameForFirstRangeStartOrLastRangeEnd( 234 nsDirection aDirection, int32_t* aOutOffset, 235 nsIContent** aOutContent = nullptr, 236 int32_t* aOutContentOffset = nullptr) const; 237 238 MOZ_CAN_RUN_SCRIPT nsresult DragCaretInternal(const nsPoint& aPoint); 239 nsPoint AdjustDragBoundary(const nsPoint& aPoint) const; 240 241 // Start the selection scroll timer if the caret is being dragged out of 242 // the scroll port. 243 MOZ_CAN_RUN_SCRIPT 244 void StartSelectionAutoScrollTimer(const nsPoint& aPoint) const; 245 void StopSelectionAutoScrollTimer() const; 246 247 void ClearMaintainedSelection() const; 248 249 static dom::Element* GetEditingHostForFrame(const nsIFrame* aFrame); 250 dom::Selection* GetSelection() const; 251 already_AddRefed<nsFrameSelection> GetFrameSelection() const; 252 253 MOZ_CAN_RUN_SCRIPT 254 nsAutoString StringifiedSelection() const; 255 256 // Get the union of all the child frame scrollable overflow rects for aFrame, 257 // which is used as a helper function to restrict the area where the caret can 258 // be dragged. Returns the rect relative to aFrame. 259 static nsRect GetAllChildFrameRectsUnion(nsIFrame* aFrame); 260 261 // Restrict the active caret's dragging position based on 262 // sCaretsAllowDraggingAcrossOtherCaret. If the active caret is the first 263 // caret, the `limit` will be the previous character of the second caret. 264 // Otherwise, the `limit` will be the next character of the first caret. 265 // 266 // @param aOffsets is the new position of the active caret, and it will be set 267 // to the `limit` when 1) sCaretsAllowDraggingAcrossOtherCaret is false and 268 // it's being dragged past the limit. 2) sCaretsAllowDraggingAcrossOtherCaret 269 // is true and the active caret's position is the same as the inactive's 270 // position. 271 // @return true if the aOffsets is suitable for changing the selection. 272 bool RestrictCaretDraggingOffsets(nsIFrame::ContentOffsets& aOffsets); 273 274 // --------------------------------------------------------------------------- 275 // The following functions are made virtual for stubbing or mocking in gtest. 276 // 277 // @return Yes if Terminate() had been called. IsTerminated()278 virtual Terminated IsTerminated() const { 279 return mPresShell ? Terminated::No : Terminated::Yes; 280 } 281 282 // Get caret mode based on current selection. 283 virtual CaretMode GetCaretMode() const; 284 285 // @return true if aStartFrame comes before aEndFrame. 286 virtual bool CompareTreePosition(nsIFrame* aStartFrame, 287 nsIFrame* aEndFrame) const; 288 289 // Check if the two carets is overlapping to become tilt. 290 // @return true if the two carets become tilt; false, otherwise. 291 virtual bool UpdateCaretsForOverlappingTilt(); 292 293 // Make the two carets always tilt. 294 virtual void UpdateCaretsForAlwaysTilt(const nsIFrame* aStartFrame, 295 const nsIFrame* aEndFrame); 296 297 // Check whether AccessibleCaret is displayable in cursor mode or not. 298 // @param aOutFrame returns frame of the cursor if it's displayable. 299 // @param aOutOffset returns frame offset as well. 300 virtual bool IsCaretDisplayableInCursorMode( 301 nsIFrame** aOutFrame = nullptr, int32_t* aOutOffset = nullptr) const; 302 303 virtual bool HasNonEmptyTextContent(nsINode* aNode) const; 304 305 // This function will flush layout, so caller must ensure the PresShell is 306 // still valid after calling this method. 307 MOZ_CAN_RUN_SCRIPT 308 virtual void DispatchCaretStateChangedEvent(dom::CaretChangedReason aReason); 309 310 // --------------------------------------------------------------------------- 311 // Member variables 312 // 313 nscoord mOffsetYToCaretLogicalPosition = NS_UNCONSTRAINEDSIZE; 314 315 // AccessibleCaretEventHub owns us by a UniquePtr. When it's destroyed, we'll 316 // also be destroyed. No need to worry if we outlive mPresShell. 317 // 318 // mPresShell will be set to nullptr in Terminate(). Therefore mPresShell is 319 // nullptr either we are in gtest or PresShell::IsDestroying() is true. 320 PresShell* MOZ_NON_OWNING_REF mPresShell = nullptr; 321 322 class Carets { 323 public: 324 Carets(UniquePtr<AccessibleCaret> aFirst, 325 UniquePtr<AccessibleCaret> aSecond); 326 327 Carets(Carets&&) = default; 328 Carets(const Carets&) = delete; 329 Carets& operator=(const Carets&) = delete; 330 GetFirst()331 AccessibleCaret* GetFirst() const { return mFirst.get(); } 332 GetSecond()333 AccessibleCaret* GetSecond() const { return mSecond.get(); } 334 HasLogicallyVisibleCaret()335 bool HasLogicallyVisibleCaret() const { 336 return mFirst->IsLogicallyVisible() || mSecond->IsLogicallyVisible(); 337 } 338 HasVisuallyVisibleCaret()339 bool HasVisuallyVisibleCaret() const { 340 return mFirst->IsVisuallyVisible() || mSecond->IsVisuallyVisible(); 341 } 342 Terminate()343 void Terminate() { 344 mFirst = nullptr; 345 mSecond = nullptr; 346 } 347 348 private: 349 // First caret is attached to nsCaret in cursor mode, and is attached to 350 // selection highlight as the left caret in selection mode. 351 UniquePtr<AccessibleCaret> mFirst; 352 353 // Second caret is used solely in selection mode, and is attached to 354 // selection highlight as the right caret. 355 UniquePtr<AccessibleCaret> mSecond; 356 }; 357 358 Carets mCarets; 359 360 // The caret being pressed or dragged. 361 AccessibleCaret* mActiveCaret = nullptr; 362 363 // The caret mode since last update carets. 364 CaretMode mLastUpdateCaretMode = CaretMode::None; 365 366 // The last input source that the event hub saw. We use this to decide whether 367 // or not show the carets when the selection is updated, as we want to hide 368 // the carets for mouse-triggered selection changes but show them for other 369 // input types such as touch. 370 uint16_t mLastInputSource = dom::MouseEvent_Binding::MOZ_SOURCE_UNKNOWN; 371 372 // Set to true in OnScrollStart() and set to false in OnScrollEnd(). 373 bool mIsScrollStarted = false; 374 375 class LayoutFlusher final { 376 public: 377 LayoutFlusher() = default; 378 379 ~LayoutFlusher(); 380 381 LayoutFlusher(const LayoutFlusher&) = delete; 382 LayoutFlusher& operator=(const LayoutFlusher&) = delete; 383 384 MOZ_CAN_RUN_SCRIPT void MaybeFlush(const PresShell& aPresShell); 385 386 // Set to false to disallow flushing layout in some callbacks such as 387 // OnReflow(), OnScrollStart(), OnScrollStart(), or 388 // OnScrollPositionChanged(). 389 bool mAllowFlushing = true; 390 391 private: 392 // Whether we're flushing layout, used for sanity-checking. 393 bool mFlushing = false; 394 }; 395 396 LayoutFlusher mLayoutFlusher; 397 398 // Set to True if one of the caret's position is changed in last update. 399 bool mIsCaretPositionChanged = false; 400 401 class DesiredAsyncPanZoomState final { 402 public: 403 void Update(const AccessibleCaretManager& aAccessibleCaretManager); 404 405 enum class Value : bool { Disabled, Enabled }; 406 Get()407 Value Get() const { return mValue; } 408 409 private: 410 Value mValue = Value::Enabled; 411 }; 412 413 DesiredAsyncPanZoomState mDesiredAsyncPanZoomState; 414 415 static const int32_t kAutoScrollTimerDelay = 30; 416 417 // Clicking on the boundary of input or textarea will move the caret to the 418 // front or end of the content. To avoid this, we need to deflate the content 419 // boundary by 61 app units, which is 1 pixel + 1 app unit as defined in 420 // AppUnit.h. 421 static const int32_t kBoundaryAppUnits = 61; 422 423 enum ScriptUpdateMode : int32_t { 424 // By default, always hide carets for selection changes due to JS calls. 425 kScriptAlwaysHide, 426 // Update any visible carets for selection changes due to JS calls, 427 // but don't show carets if carets are hidden. 428 kScriptUpdateVisible, 429 // Always show carets for selection changes due to JS calls. 430 kScriptAlwaysShow 431 }; 432 }; 433 434 std::ostream& operator<<(std::ostream& aStream, 435 const AccessibleCaretManager::CaretMode& aCaretMode); 436 437 std::ostream& operator<<( 438 std::ostream& aStream, 439 const AccessibleCaretManager::UpdateCaretsHint& aResult); 440 441 } // namespace mozilla 442 443 #endif // AccessibleCaretManager_h 444