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 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
|
/* -*- 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 "mozilla/Attributes.h"
#include "mozilla/dom/CaretStateChangedEvent.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "mozilla/EnumSet.h"
#include "mozilla/EventForwards.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h"
#include "nsCOMPtr.h"
#include "nsCoord.h"
#include "nsIFrame.h"
#include "nsISelectionListener.h"
class nsFrameSelection;
class nsIContent;
struct nsPoint;
namespace mozilla {
class PresShell;
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.
// TODO: it's unclear, whether that's true. `OnSelectionChanged` calls
// `UpdateCarets`, which may flush layout.
//
// Please see the wiki page for more information.
// https://wiki.mozilla.org/AccessibleCaret
//
class AccessibleCaretManager {
public:
// @param aPresShell may be nullptr for testing.
explicit AccessibleCaretManager(PresShell* aPresShell);
virtual ~AccessibleCaretManager() = default;
// 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.
MOZ_CAN_RUN_SCRIPT
virtual nsresult PressCaret(const nsPoint& aPoint, EventClassID aEventClass);
// Drag caret to the given point. It's required to call PressCaret()
// beforehand.
MOZ_CAN_RUN_SCRIPT
virtual nsresult DragCaret(const nsPoint& aPoint);
// Release caret from he previous press action. It's required to call
// PressCaret() beforehand.
MOZ_CAN_RUN_SCRIPT
virtual nsresult ReleaseCaret();
// A quick single tap on caret on given point without dragging.
MOZ_CAN_RUN_SCRIPT
virtual nsresult TapCaret(const nsPoint& aPoint);
// Select a word or bring up paste shortcut (if Gaia is listening) under the
// given point.
MOZ_CAN_RUN_SCRIPT
virtual nsresult SelectWordOrShortcut(const nsPoint& aPoint);
// Handle scroll-start event.
MOZ_CAN_RUN_SCRIPT
virtual void OnScrollStart();
// Handle scroll-end event.
MOZ_CAN_RUN_SCRIPT
virtual void OnScrollEnd();
// Handle ScrollPositionChanged from nsIScrollObserver. This might be called
// at anytime, not necessary between OnScrollStart and OnScrollEnd.
MOZ_CAN_RUN_SCRIPT
virtual void OnScrollPositionChanged();
// Handle reflow event from nsIReflowObserver.
MOZ_CAN_RUN_SCRIPT
virtual void OnReflow();
// Handle blur event from nsFocusManager.
MOZ_CAN_RUN_SCRIPT
virtual void OnBlur();
// Handle NotifySelectionChanged event from nsISelectionListener.
// @param aReason potentially multiple of the reasons defined in
// nsISelectionListener.idl.
MOZ_CAN_RUN_SCRIPT
virtual nsresult OnSelectionChanged(dom::Document* aDoc, dom::Selection* aSel,
int16_t aReason);
// Handle key event.
MOZ_CAN_RUN_SCRIPT
virtual void OnKeyboardEvent();
// 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);
// Returns True indicating that we should disable APZ to avoid jumpy carets.
bool ShouldDisableApz() const;
protected:
class Carets;
// @param aPresShell may be nullptr for testing.
AccessibleCaretManager(PresShell* aPresShell, Carets aCarets);
// 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 blur, do not change its
// appearance to Normal.
RespectOldAppearance,
// No CaretStateChangedEvent will be dispatched in the end of
// UpdateCarets().
DispatchNoEvent,
};
using UpdateCaretsHintSet = mozilla::EnumSet<UpdateCaretsHint>;
friend std::ostream& operator<<(std::ostream& aStream,
const UpdateCaretsHint& aResult);
enum class Terminated : bool { No, Yes };
// This method could kill the shell, so callers to methods that call
// MaybeFlushLayout should ensure the event hub that owns us is still alive.
//
// See the mRefCnt assertions in AccessibleCaretEventHub.
//
[[nodiscard]] MOZ_CAN_RUN_SCRIPT virtual Terminated MaybeFlushLayout();
// 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.
MOZ_CAN_RUN_SCRIPT
void UpdateCarets(
const UpdateCaretsHintSet& aHints = UpdateCaretsHint::Default);
// Force hiding all carets regardless of the current selection status, and
// dispatch CaretStateChangedEvent if one of the carets is logically-visible.
MOZ_CAN_RUN_SCRIPT
void HideCaretsAndDispatchCaretStateChangedEvent();
MOZ_CAN_RUN_SCRIPT
void UpdateCaretsForCursorMode(const UpdateCaretsHintSet& aHints);
MOZ_CAN_RUN_SCRIPT
void UpdateCaretsForSelectionMode(const UpdateCaretsHintSet& aHints);
// A helper function to update mShouldDisableApz.
void UpdateShouldDisableApz();
// 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.
MOZ_CAN_RUN_SCRIPT_BOUNDARY void ChangeFocusToOrClearOldFocus(
nsIFrame* aFrame) const;
MOZ_CAN_RUN_SCRIPT
nsresult SelectWord(nsIFrame* aFrame, const nsPoint& aPoint) const;
MOZ_CAN_RUN_SCRIPT void SetSelectionDragState(bool aState) const;
// Return true if the candidate string is a phone number.
bool IsPhoneNumber(const nsAString& aCandidate) const;
// Extend the current selection forwards and backwards if it's already a
// phone number.
MOZ_CAN_RUN_SCRIPT
void SelectMoreIfPhoneNumber() const;
// Extend the current phone number selection in the requested direction.
MOZ_CAN_RUN_SCRIPT
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 content and the content offset. Otherwise, get the
// frame and the offset for the range end in the last range instead.
nsIFrame* GetFrameForFirstRangeStartOrLastRangeEnd(
nsDirection aDirection, int32_t* aOutOffset,
nsIContent** aOutContent = nullptr,
int32_t* aOutContentOffset = nullptr) const;
MOZ_CAN_RUN_SCRIPT nsresult DragCaretInternal(const nsPoint& aPoint);
nsPoint AdjustDragBoundary(const nsPoint& aPoint) const;
// Start the selection scroll timer if the caret is being dragged out of
// the scroll port.
MOZ_CAN_RUN_SCRIPT
void StartSelectionAutoScrollTimer(const nsPoint& aPoint) const;
void StopSelectionAutoScrollTimer() const;
void ClearMaintainedSelection() const;
static dom::Element* GetEditingHostForFrame(const nsIFrame* aFrame);
dom::Selection* GetSelection() const;
already_AddRefed<nsFrameSelection> GetFrameSelection() const;
MOZ_CAN_RUN_SCRIPT
nsAutoString StringifiedSelection() 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.
static nsRect GetAllChildFrameRectsUnion(nsIFrame* aFrame);
// 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);
// ---------------------------------------------------------------------------
// The following functions are made virtual for stubbing or mocking in gtest.
//
// @return Yes if Terminate() had been called.
virtual Terminated IsTerminated() const {
return mPresShell ? Terminated::No : Terminated::Yes;
}
// 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.
// @return true if the two carets become tilt; false, otherwise.
virtual bool UpdateCaretsForOverlappingTilt();
// Make the two carets always tilt.
virtual void UpdateCaretsForAlwaysTilt(const nsIFrame* aStartFrame,
const 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.
// @param aPoint The event point when the user is pressing or dragging a
// caret, which is relative to the root frame.
MOZ_CAN_RUN_SCRIPT
virtual void DispatchCaretStateChangedEvent(dom::CaretChangedReason aReason,
const nsPoint* aPoint = nullptr);
// ---------------------------------------------------------------------------
// 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.
PresShell* MOZ_NON_OWNING_REF mPresShell = nullptr;
class Carets {
public:
Carets(UniquePtr<AccessibleCaret> aFirst,
UniquePtr<AccessibleCaret> aSecond);
Carets(Carets&&) = default;
Carets(const Carets&) = delete;
Carets& operator=(const Carets&) = delete;
AccessibleCaret* GetFirst() const { return mFirst.get(); }
AccessibleCaret* GetSecond() const { return mSecond.get(); }
bool HasLogicallyVisibleCaret() const {
return mFirst->IsLogicallyVisible() || mSecond->IsLogicallyVisible();
}
bool HasVisuallyVisibleCaret() const {
return mFirst->IsVisuallyVisible() || mSecond->IsVisuallyVisible();
}
void Terminate() {
mFirst = nullptr;
mSecond = nullptr;
}
private:
// First caret is attached to nsCaret in cursor mode, and is attached to
// selection highlight as the left caret in selection mode.
UniquePtr<AccessibleCaret> mFirst;
// Second caret is used solely in selection mode, and is attached to
// selection highlight as the right caret.
UniquePtr<AccessibleCaret> mSecond;
};
Carets mCarets;
// The caret being pressed or dragged.
AccessibleCaret* mActiveCaret = nullptr;
// The caret mode since last update carets.
CaretMode mLastUpdateCaretMode = CaretMode::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 = dom::MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
// Set to true in OnScrollStart() and set to false in OnScrollEnd().
bool mIsScrollStarted = false;
class LayoutFlusher final {
public:
LayoutFlusher() = default;
~LayoutFlusher();
LayoutFlusher(const LayoutFlusher&) = delete;
LayoutFlusher& operator=(const LayoutFlusher&) = delete;
MOZ_CAN_RUN_SCRIPT void MaybeFlush(const PresShell& aPresShell);
// Set to false to disallow flushing layout in some callbacks such as
// OnReflow(), OnScrollStart(), OnScrollStart(), or
// OnScrollPositionChanged().
bool mAllowFlushing = true;
private:
// Whether we're flushing layout, used for sanity-checking.
bool mFlushing = false;
};
LayoutFlusher mLayoutFlusher;
// Set to True if one of the caret's position is changed in last update.
bool mIsCaretPositionChanged = false;
class DesiredAsyncPanZoomState final {
public:
void Update(const AccessibleCaretManager& aAccessibleCaretManager);
enum class Value : bool { Disabled, Enabled };
Value Get() const { return mValue; }
private:
Value mValue = Value::Enabled;
};
DesiredAsyncPanZoomState mDesiredAsyncPanZoomState;
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;
enum ScriptUpdateMode : int32_t {
// By default, always hide carets for selection changes due to JS calls.
kScriptAlwaysHide,
// Update any visible carets for selection changes due to JS calls,
// but don't show carets if carets are hidden.
kScriptUpdateVisible,
// Always show carets for selection changes due to JS calls.
kScriptAlwaysShow
};
};
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
|