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