WheelHandlingHelper.h (17834B)
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 mozilla_WheelHandlingHelper_h_ 8 #define mozilla_WheelHandlingHelper_h_ 9 10 #include "mozilla/Attributes.h" 11 #include "mozilla/EventForwards.h" 12 #include "nsCoord.h" 13 #include "nsIFrame.h" // for AutoWeakFrame only 14 #include "nsPoint.h" 15 16 class nsIFrame; 17 class nsITimer; 18 19 namespace mozilla { 20 21 class EventStateManager; 22 class ScrollContainerFrame; 23 24 /** 25 * DeltaValues stores two delta values which are along X and Y axis. This is 26 * useful for arguments and results of some methods. 27 */ 28 29 struct DeltaValues { 30 constexpr DeltaValues() : deltaX(0.0), deltaY(0.0) {} 31 32 constexpr DeltaValues(double aDeltaX, double aDeltaY) 33 : deltaX(aDeltaX), deltaY(aDeltaY) {} 34 35 explicit DeltaValues(WidgetWheelEvent* aEvent); 36 37 double deltaX; 38 double deltaY; 39 }; 40 41 /** 42 * WheelHandlingUtils provides some static methods which are useful at handling 43 * wheel events. 44 */ 45 46 class WheelHandlingUtils { 47 public: 48 /** 49 * Returns true if aFrame is a scrollable frame and it can be scrolled to 50 * either aDirectionX or aDirectionY along each axis. Or if aFrame is a 51 * plugin frame (in this case, aDirectionX and aDirectionY are ignored). 52 * Otherwise, false. 53 */ 54 static bool CanScrollOn(nsIFrame* aFrame, double aDirectionX, 55 double aDirectionY); 56 /** 57 * Returns true if the scroll container frame can be scrolled to either 58 * aDirectionX or aDirectionY along each axis. Otherwise, false. 59 */ 60 static bool CanScrollOn(ScrollContainerFrame* aScrollContainerFrame, 61 double aDirectionX, double aDirectionY); 62 63 // For more details about the concept of a disregarded direction, refer to the 64 // code in struct mozilla::layers::ScrollMetadata which defines 65 // mDisregardedDirection. 66 static Maybe<layers::ScrollDirection> GetDisregardedWheelScrollDirection( 67 const nsIFrame* aFrame); 68 69 private: 70 static bool CanScrollInRange(nscoord aMin, nscoord aValue, nscoord aMax, 71 double aDirection); 72 }; 73 74 /** 75 * ScrollbarsForWheel manages scrollbars state during wheel operation. 76 * E.g., on some platforms, scrollbars should show only while user attempts to 77 * scroll. At that time, scrollbars which may be possible to scroll by 78 * operation of wheel at the point should show temporarily. 79 */ 80 81 class ScrollbarsForWheel { 82 public: 83 static void PrepareToScrollText(EventStateManager* aESM, 84 nsIFrame* aTargetFrame, 85 WidgetWheelEvent* aEvent); 86 static void SetActiveScrollTarget(ScrollContainerFrame* aScrollTarget); 87 // Hide all scrollbars (both mActiveOwner's and mActivatedScrollTargets') 88 static void MayInactivate(); 89 static void Inactivate(); 90 static bool IsActive(); 91 static void OwnWheelTransaction(bool aOwn); 92 93 protected: 94 static const size_t kNumberOfTargets = 4; 95 static constexpr DeltaValues directions[kNumberOfTargets] = { 96 DeltaValues(-1, 0), DeltaValues(+1, 0), DeltaValues(0, -1), 97 DeltaValues(0, +1)}; 98 static AutoWeakFrame sActiveOwner; 99 static AutoWeakFrame sActivatedScrollTargets[kNumberOfTargets]; 100 static bool sHadWheelStart; 101 static bool sOwnWheelTransaction; 102 103 /** 104 * These two methods are called upon eWheelOperationStart/eWheelOperationEnd 105 * events to show/hide the right scrollbars. 106 */ 107 static void TemporarilyActivateAllPossibleScrollTargets( 108 EventStateManager* aESM, nsIFrame* aTargetFrame, 109 WidgetWheelEvent* aEvent); 110 static void DeactivateAllTemporarilyActivatedScrollTargets(); 111 }; 112 113 /** 114 * WheelTransaction manages a series of wheel events as a transaction. 115 * While in a transaction, every wheel event should scroll the same scrollable 116 * element even if a different scrollable element is under the mouse cursor. 117 * 118 * Additionally, this class also manages wheel scroll speed acceleration. 119 */ 120 121 class WheelTransaction { 122 public: 123 /** 124 * Get the target scroll frame for this wheel transaction. This should 125 * the the scrollable fame that will scroll for all wheel events in 126 * this wheel transaction. 127 */ 128 static nsIFrame* GetScrollTargetFrame() { return sScrollTargetFrame; } 129 /* 130 * The event target to use for all wheel events in this wheel transaction. 131 * This should be the event target for all wheel events in this wheel 132 * transaction. Note that this frame will likely be a child of the 133 * scrollable frame. 134 */ 135 static nsIFrame* GetEventTargetFrame() { return sEventTargetFrame; } 136 static bool HandledByApz() { return sHandledByApz; } 137 static void EndTransaction(); 138 /** 139 * WillHandleDefaultAction() is called before handling aWheelEvent on 140 * aScrollTargetWeakFrame given the event target aEventTargetWeakFrame. 141 * 142 * @return false if the caller cannot continue to handle the default 143 * action. Otherwise, true. 144 */ 145 static bool WillHandleDefaultAction(WidgetWheelEvent* aWheelEvent, 146 AutoWeakFrame& aScrollTargetWeakFrame, 147 AutoWeakFrame& aEventTargetWeakFrame); 148 static bool WillHandleDefaultAction(WidgetWheelEvent* aWheelEvent, 149 nsIFrame* aScrollTargetFrame, 150 nsIFrame* aEventTargetFrame) { 151 AutoWeakFrame scrollTargetWeakFrame(aScrollTargetFrame); 152 AutoWeakFrame eventTargetWeakFrame(aEventTargetFrame); 153 return WillHandleDefaultAction(aWheelEvent, scrollTargetWeakFrame, 154 eventTargetWeakFrame); 155 } 156 static void OnEvent(WidgetEvent* aEvent); 157 static void OnRemoveElement(nsIContent* aContent); 158 static void Shutdown(); 159 160 static void OwnScrollbars(bool aOwn); 161 162 static DeltaValues AccelerateWheelDelta(WidgetWheelEvent* aEvent); 163 164 protected: 165 static void BeginTransaction(nsIFrame* aScrollTargetFrame, 166 nsIFrame* aEventTargetFrame, 167 const WidgetWheelEvent* aEvent); 168 // Be careful, UpdateTransaction may fire a DOM event, therefore, the target 169 // frame might be destroyed in the event handler. 170 static bool UpdateTransaction(const WidgetWheelEvent* aEvent); 171 static void MayEndTransaction(); 172 173 static LayoutDeviceIntPoint GetScreenPoint(WidgetGUIEvent* aEvent); 174 static void OnFailToScrollTarget(); 175 static void OnTimeout(nsITimer* aTimer, void* aClosure); 176 static void SetTimeout(); 177 static DeltaValues OverrideSystemScrollSpeed(WidgetWheelEvent* aEvent); 178 static double ComputeAcceleratedWheelDelta(double aDelta, int32_t aFactor); 179 static bool OutOfTime(uint32_t aBaseTime, uint32_t aThreshold); 180 181 /** 182 * The scrollable element the current wheel event group is bound to. 183 */ 184 static AutoWeakFrame sScrollTargetFrame; 185 /** 186 * The initial target of the first wheel event in the wheel event group. 187 * This frame is typically a child of the scrollable element. The wheel 188 * event should target the topmost-event-target. For a wheel event 189 * group, we'll use this target for the entire group. 190 * 191 * See https://w3c.github.io/uievents/#topmost-event-target and 192 * https://w3c.github.io/uievents/#event-type-wheel for details. 193 * 194 * Note: this is only populated if dom.event.wheel-event-groups.enabled is 195 * set. 196 */ 197 static AutoWeakFrame sEventTargetFrame; 198 /** 199 * The wheel events for this transaction are handled by APZ. 200 */ 201 static bool sHandledByApz; 202 static uint32_t sTime; // in milliseconds 203 static uint32_t sMouseMoved; // in milliseconds 204 static nsITimer* sTimer; 205 static int32_t sScrollSeriesCounter; 206 static bool sOwnScrollbars; 207 }; 208 209 // For some kinds of scrollings, the delta values of WidgetWheelEvent are 210 // possbile to be adjusted. For example, the user has configured the pref to let 211 // [vertical wheel + Shift key] to perform horizontal scrolling instead of 212 // vertical scrolling. 213 // The values in this enumeration list all kinds of scrollings whose delta 214 // values are possible to be adjusted. 215 enum class WheelDeltaAdjustmentStrategy : uint8_t { 216 // There is no strategy, don't adjust delta values in any cases. 217 eNone, 218 // This strategy means we're receiving a horizontalized scroll, so we should 219 // apply horizontalization strategy for its delta values. 220 // Horizontalized scrolling means treating vertical wheel scrolling as 221 // horizontal scrolling by adjusting delta values. 222 // It's important to keep in mind with the percise concept of horizontalized 223 // scrolling: Delta values are *ONLY* going to be adjusted during the process 224 // of its default action handling; in views of any programmes other than the 225 // default action handler, such as a DOM event listener or a plugin, delta 226 // values are never going to be adjusted, they will still retrive original 227 // delta values when horizontalization occured for default actions. 228 eHorizontalize, 229 // The following two strategies mean we're receving an auto-dir scroll, so we 230 // should apply auto-dir adjustment to the delta of the wheel event if needed. 231 // Auto-dir is a feature which treats any single-wheel scroll as a scroll in 232 // the only one scrollable direction if the target has only one scrollable 233 // direction. For example, if the user scrolls a vertical wheel inside a 234 // target which is horizontally scrollable but vertical unscrollable, then the 235 // vertical scroll is converted to a horizontal scroll for that target. 236 // So why do we need two different strategies for auto-dir scrolling? That's 237 // because when a wheel scroll is converted due to auto-dir, there is one 238 // thing called "honoured target" which decides which side the converted 239 // scroll goes towards. If the content of the honoured target horizontally 240 // is RTL content, then an upward scroll maps to a rightward scroll and a 241 // downward scroll maps to a leftward scroll; otherwise, an upward scroll maps 242 // to a leftward scroll and a downward scroll maps to a rightward scroll. 243 // |eAutoDir| considers the scrolling target as the honoured target. 244 // |eAutoDirWithRootHonour| takes the root element of the document with the 245 // scrolling element, and considers that as the honoured target. But note that 246 // there's one exception: for targets in an HTML document, the real root 247 // element(I.e. the <html> element) is typically not considered as a root 248 // element, but the <body> element is typically considered as a root element. 249 // If there is no <body> element, then consider the <html> element instead. 250 // And also note that like |eHorizontalize|, delta values are *ONLY* going to 251 // be adjusted during the process of its default action handling; in views of 252 // any programmes other than the default action handler, such as a DOM event 253 // listener or a plugin, delta values are never going to be adjusted. 254 eAutoDir, 255 eAutoDirWithRootHonour, 256 // Not an actual strategy. This is just used as an upper bound for 257 // ContiguousEnumSerializer. 258 eSentinel, 259 }; 260 261 /** 262 * When a *pure* vertical wheel event should be treated as if it was a 263 * horizontal scroll because the user wants to horizontalize the wheel scroll, 264 * an instance of this class will adjust the delta values upon calling 265 * Horizontalize(). And the horizontalized delta values will be restored 266 * automatically when the instance of this class is being destructed. Or you can 267 * restore them in advance by calling CancelHorizontalization(). 268 */ 269 class MOZ_STACK_CLASS WheelDeltaHorizontalizer final { 270 public: 271 /** 272 * @param aWheelEvent A wheel event whose delta values will be adjusted 273 * upon calling Horizontalize(). 274 */ 275 explicit WheelDeltaHorizontalizer(WidgetWheelEvent& aWheelEvent) 276 : mWheelEvent(aWheelEvent), 277 mOldDeltaX(0.0), 278 mOldDeltaZ(0.0), 279 mOldOverflowDeltaX(0.0), 280 mOldLineOrPageDeltaX(0), 281 mHorizontalized(false) {} 282 /** 283 * Converts vertical scrolling into horizontal scrolling by adjusting the 284 * its delta values. 285 */ 286 void Horizontalize(); 287 ~WheelDeltaHorizontalizer(); 288 void CancelHorizontalization(); 289 290 private: 291 WidgetWheelEvent& mWheelEvent; 292 double mOldDeltaX; 293 double mOldDeltaZ; 294 double mOldOverflowDeltaX; 295 int32_t mOldLineOrPageDeltaX; 296 bool mHorizontalized; 297 }; 298 299 /** 300 * This class is used to adjust the delta values for wheel scrolling with the 301 * auto-dir functionality. 302 * A traditional wheel scroll only allows the user use the wheel in the same 303 * scrollable direction as that of the scrolling target to scroll the target, 304 * whereas an auto-dir scroll lets the user use any wheel(either a vertical 305 * wheel or a horizontal tilt wheel) to scroll a frame which is scrollable in 306 * only one direction. For detailed information on auto-dir scrolling, 307 * @see mozilla::WheelDeltaAdjustmentStrategy. 308 */ 309 class MOZ_STACK_CLASS AutoDirWheelDeltaAdjuster { 310 protected: 311 /** 312 * @param aDeltaX DeltaX for a wheel event whose delta values will 313 * be adjusted upon calling Adjust() when 314 * ShouldBeAdjusted() returns true. 315 * @param aDeltaY DeltaY for a wheel event, like DeltaX. 316 */ 317 AutoDirWheelDeltaAdjuster(double& aDeltaX, double& aDeltaY) 318 : mDeltaX(aDeltaX), 319 mDeltaY(aDeltaY), 320 mCheckedIfShouldBeAdjusted(false), 321 mShouldBeAdjusted(false) {} 322 323 public: 324 /** 325 * Gets whether the values of the delta should be adjusted for auto-dir 326 * scrolling. Note that if Adjust() has been called, this function simply 327 * returns false. 328 * 329 * @return true if the delta should be adjusted; otherwise false. 330 */ 331 bool ShouldBeAdjusted(); 332 /** 333 * Adjusts the values of the delta values for auto-dir scrolling when 334 * ShouldBeAdjusted() returns true. If you call it when ShouldBeAdjusted() 335 * returns false, this function will simply do nothing. 336 */ 337 void Adjust(); 338 339 private: 340 /** 341 * Called by Adjust() if Adjust() successfully adjusted the delta values. 342 */ 343 virtual void OnAdjusted() {} 344 345 virtual bool CanScrollAlongXAxis() const = 0; 346 virtual bool CanScrollAlongYAxis() const = 0; 347 virtual bool CanScrollUpwards() const = 0; 348 virtual bool CanScrollDownwards() const = 0; 349 virtual bool CanScrollLeftwards() const = 0; 350 virtual bool CanScrollRightwards() const = 0; 351 352 /** 353 * Gets whether the horizontal content starts at rightside. 354 * 355 * @return If the content is in vertical-RTL writing mode(E.g. "writing-mode: 356 * vertical-rl" in CSS), or if it's in horizontal-RTL writing-mode 357 * (E.g. "writing-mode: horizontal-tb; direction: rtl;" in CSS), then 358 * this function returns true. From the representation perspective, 359 * frames whose horizontal contents start at rightside also cause 360 * their horizontal scrollbars, if any, initially start at rightside. 361 * So we can also learn about the initial side of the horizontal 362 * scrollbar for the frame by calling this function. 363 */ 364 virtual bool IsHorizontalContentRightToLeft() const = 0; 365 366 protected: 367 double& mDeltaX; 368 double& mDeltaY; 369 370 private: 371 bool mCheckedIfShouldBeAdjusted; 372 bool mShouldBeAdjusted; 373 }; 374 375 /** 376 * This is the implementation of AutoDirWheelDeltaAdjuster for EventStateManager 377 * 378 * Detailed comments about some member functions are given in the base class 379 * AutoDirWheelDeltaAdjuster. 380 */ 381 class MOZ_STACK_CLASS ESMAutoDirWheelDeltaAdjuster final 382 : public AutoDirWheelDeltaAdjuster { 383 public: 384 /** 385 * @param aEvent The auto-dir wheel scroll event. 386 * @param aScrollFrame The scroll target for the event. 387 * @param aHonoursRoot If set to true, the honoured frame is the root 388 * frame in the same document where the target is; 389 * If false, the honoured frame is the scroll 390 * target. For the concept of an honoured target, 391 * @see mozilla::WheelDeltaAdjustmentStrategy 392 */ 393 ESMAutoDirWheelDeltaAdjuster(WidgetWheelEvent& aEvent, nsIFrame& aScrollFrame, 394 bool aHonoursRoot); 395 396 private: 397 virtual void OnAdjusted() override; 398 virtual bool CanScrollAlongXAxis() const override; 399 virtual bool CanScrollAlongYAxis() const override; 400 virtual bool CanScrollUpwards() const override; 401 virtual bool CanScrollDownwards() const override; 402 virtual bool CanScrollLeftwards() const override; 403 virtual bool CanScrollRightwards() const override; 404 virtual bool IsHorizontalContentRightToLeft() const override; 405 406 ScrollContainerFrame* mScrollTargetFrame; 407 bool mIsHorizontalContentRightToLeft; 408 409 int32_t& mLineOrPageDeltaX; 410 int32_t& mLineOrPageDeltaY; 411 double& mOverflowDeltaX; 412 double& mOverflowDeltaY; 413 }; 414 415 /** 416 * This class is used for restoring the delta in an auto-dir wheel. 417 * 418 * An instance of this calss monitors auto-dir adjustment which may happen 419 * during its lifetime. If the delta values is adjusted during its lifetime, the 420 * instance will restore the adjusted delta when it's being destrcuted. 421 */ 422 class MOZ_STACK_CLASS ESMAutoDirWheelDeltaRestorer final { 423 public: 424 /** 425 * @param aEvent The wheel scroll event to be monitored. 426 */ 427 explicit ESMAutoDirWheelDeltaRestorer(WidgetWheelEvent& aEvent); 428 ~ESMAutoDirWheelDeltaRestorer(); 429 430 private: 431 WidgetWheelEvent& mEvent; 432 double mOldDeltaX; 433 double mOldDeltaY; 434 int32_t mOldLineOrPageDeltaX; 435 int32_t mOldLineOrPageDeltaY; 436 double mOldOverflowDeltaX; 437 double mOldOverflowDeltaY; 438 }; 439 440 } // namespace mozilla 441 442 #endif // mozilla_WheelHandlingHelper_h_