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 #include "WheelHandlingHelper.h"
8 
9 #include <utility>  // for std::swap
10 
11 #include "mozilla/EventDispatcher.h"
12 #include "mozilla/EventStateManager.h"
13 #include "mozilla/MouseEvents.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/PresShell.h"
16 #include "mozilla/StaticPrefs_mousewheel.h"
17 #include "mozilla/StaticPrefs_test.h"
18 #include "mozilla/TextControlElement.h"
19 #include "mozilla/dom/WheelEventBinding.h"
20 #include "nsCOMPtr.h"
21 #include "nsContentUtils.h"
22 #include "nsIContent.h"
23 #include "nsIContentInlines.h"
24 #include "mozilla/dom/Document.h"
25 #include "DocumentInlines.h"  // for Document and HTMLBodyElement
26 #include "nsIScrollableFrame.h"
27 #include "nsITimer.h"
28 #include "nsPresContext.h"
29 #include "prtime.h"
30 #include "Units.h"
31 #include "ScrollAnimationPhysics.h"
32 
33 namespace mozilla {
34 
35 /******************************************************************/
36 /* mozilla::DeltaValues                                           */
37 /******************************************************************/
38 
DeltaValues(WidgetWheelEvent * aEvent)39 DeltaValues::DeltaValues(WidgetWheelEvent* aEvent)
40     : deltaX(aEvent->mDeltaX), deltaY(aEvent->mDeltaY) {}
41 
42 /******************************************************************/
43 /* mozilla::WheelHandlingUtils                                    */
44 /******************************************************************/
45 
46 /* static */
CanScrollInRange(nscoord aMin,nscoord aValue,nscoord aMax,double aDirection)47 bool WheelHandlingUtils::CanScrollInRange(nscoord aMin, nscoord aValue,
48                                           nscoord aMax, double aDirection) {
49   return aDirection > 0.0 ? aValue < static_cast<double>(aMax)
50                           : static_cast<double>(aMin) < aValue;
51 }
52 
53 /* static */
CanScrollOn(nsIFrame * aFrame,double aDirectionX,double aDirectionY)54 bool WheelHandlingUtils::CanScrollOn(nsIFrame* aFrame, double aDirectionX,
55                                      double aDirectionY) {
56   nsIScrollableFrame* scrollableFrame = do_QueryFrame(aFrame);
57   if (!scrollableFrame) {
58     return false;
59   }
60   return CanScrollOn(scrollableFrame, aDirectionX, aDirectionY);
61 }
62 
63 /* static */
CanScrollOn(nsIScrollableFrame * aScrollFrame,double aDirectionX,double aDirectionY)64 bool WheelHandlingUtils::CanScrollOn(nsIScrollableFrame* aScrollFrame,
65                                      double aDirectionX, double aDirectionY) {
66   MOZ_ASSERT(aScrollFrame);
67   NS_ASSERTION(aDirectionX || aDirectionY,
68                "One of the delta values must be non-zero at least");
69 
70   nsPoint scrollPt = aScrollFrame->GetVisualViewportOffset();
71   nsRect scrollRange = aScrollFrame->GetScrollRangeForUserInputEvents();
72   layers::ScrollDirections directions =
73       aScrollFrame->GetAvailableScrollingDirectionsForUserInputEvents();
74 
75   return ((aDirectionX != 0.0) &&
76           (directions.contains(layers::ScrollDirection::eHorizontal)) &&
77           CanScrollInRange(scrollRange.x, scrollPt.x, scrollRange.XMost(),
78                            aDirectionX)) ||
79          ((aDirectionY != 0.0) &&
80           (directions.contains(layers::ScrollDirection::eVertical)) &&
81           CanScrollInRange(scrollRange.y, scrollPt.y, scrollRange.YMost(),
82                            aDirectionY));
83 }
84 
85 /*static*/ Maybe<layers::ScrollDirection>
GetDisregardedWheelScrollDirection(const nsIFrame * aFrame)86 WheelHandlingUtils::GetDisregardedWheelScrollDirection(const nsIFrame* aFrame) {
87   nsIContent* content = aFrame->GetContent();
88   if (!content) {
89     return Nothing();
90   }
91   TextControlElement* textControlElement = TextControlElement::FromNodeOrNull(
92       content->IsInNativeAnonymousSubtree()
93           ? content->GetClosestNativeAnonymousSubtreeRootParent()
94           : content);
95   if (!textControlElement || !textControlElement->IsSingleLineTextControl()) {
96     return Nothing();
97   }
98   // Disregard scroll in the block-flow direction by mouse wheel on a
99   // single-line text control. For instance, in tranditional Chinese writing
100   // system, a single-line text control cannot be scrolled horizontally with
101   // mouse wheel even if they overflow at the right and left edges; Whereas in
102   // latin-based writing system, a single-line text control cannot be scrolled
103   // vertically with mouse wheel even if they overflow at the top and bottom
104   // edges
105   return Some(aFrame->GetWritingMode().IsVertical()
106                   ? layers::ScrollDirection::eHorizontal
107                   : layers::ScrollDirection::eVertical);
108 }
109 
110 /******************************************************************/
111 /* mozilla::WheelTransaction                                      */
112 /******************************************************************/
113 
114 AutoWeakFrame WheelTransaction::sTargetFrame(nullptr);
115 uint32_t WheelTransaction::sTime = 0;
116 uint32_t WheelTransaction::sMouseMoved = 0;
117 nsITimer* WheelTransaction::sTimer = nullptr;
118 int32_t WheelTransaction::sScrollSeriesCounter = 0;
119 bool WheelTransaction::sOwnScrollbars = false;
120 
121 /* static */
OutOfTime(uint32_t aBaseTime,uint32_t aThreshold)122 bool WheelTransaction::OutOfTime(uint32_t aBaseTime, uint32_t aThreshold) {
123   uint32_t now = PR_IntervalToMilliseconds(PR_IntervalNow());
124   return (now - aBaseTime > aThreshold);
125 }
126 
127 /* static */
OwnScrollbars(bool aOwn)128 void WheelTransaction::OwnScrollbars(bool aOwn) { sOwnScrollbars = aOwn; }
129 
130 /* static */
BeginTransaction(nsIFrame * aTargetFrame,const WidgetWheelEvent * aEvent)131 void WheelTransaction::BeginTransaction(nsIFrame* aTargetFrame,
132                                         const WidgetWheelEvent* aEvent) {
133   NS_ASSERTION(!sTargetFrame, "previous transaction is not finished!");
134   MOZ_ASSERT(aEvent->mMessage == eWheel,
135              "Transaction must be started with a wheel event");
136   ScrollbarsForWheel::OwnWheelTransaction(false);
137   sTargetFrame = aTargetFrame;
138   sScrollSeriesCounter = 0;
139   if (!UpdateTransaction(aEvent)) {
140     NS_ERROR("BeginTransaction is called even cannot scroll the frame");
141     EndTransaction();
142   }
143 }
144 
145 /* static */
UpdateTransaction(const WidgetWheelEvent * aEvent)146 bool WheelTransaction::UpdateTransaction(const WidgetWheelEvent* aEvent) {
147   nsIFrame* scrollToFrame = GetTargetFrame();
148   nsIScrollableFrame* scrollableFrame = scrollToFrame->GetScrollTargetFrame();
149   if (scrollableFrame) {
150     scrollToFrame = do_QueryFrame(scrollableFrame);
151   }
152 
153   if (!WheelHandlingUtils::CanScrollOn(scrollToFrame, aEvent->mDeltaX,
154                                        aEvent->mDeltaY)) {
155     OnFailToScrollTarget();
156     // We should not modify the transaction state when the view will not be
157     // scrolled actually.
158     return false;
159   }
160 
161   SetTimeout();
162 
163   if (sScrollSeriesCounter != 0 && OutOfTime(sTime, kScrollSeriesTimeoutMs)) {
164     sScrollSeriesCounter = 0;
165   }
166   sScrollSeriesCounter++;
167 
168   // We should use current time instead of WidgetEvent.time.
169   // 1. Some events doesn't have the correct creation time.
170   // 2. If the computer runs slowly by other processes eating the CPU resource,
171   //    the event creation time doesn't keep real time.
172   sTime = PR_IntervalToMilliseconds(PR_IntervalNow());
173   sMouseMoved = 0;
174   return true;
175 }
176 
177 /* static */
MayEndTransaction()178 void WheelTransaction::MayEndTransaction() {
179   if (!sOwnScrollbars && ScrollbarsForWheel::IsActive()) {
180     ScrollbarsForWheel::OwnWheelTransaction(true);
181   } else {
182     EndTransaction();
183   }
184 }
185 
186 /* static */
EndTransaction()187 void WheelTransaction::EndTransaction() {
188   if (sTimer) {
189     sTimer->Cancel();
190   }
191   sTargetFrame = nullptr;
192   sScrollSeriesCounter = 0;
193   if (sOwnScrollbars) {
194     sOwnScrollbars = false;
195     ScrollbarsForWheel::OwnWheelTransaction(false);
196     ScrollbarsForWheel::Inactivate();
197   }
198 }
199 
200 /* static */
WillHandleDefaultAction(WidgetWheelEvent * aWheelEvent,AutoWeakFrame & aTargetWeakFrame)201 bool WheelTransaction::WillHandleDefaultAction(
202     WidgetWheelEvent* aWheelEvent, AutoWeakFrame& aTargetWeakFrame) {
203   nsIFrame* lastTargetFrame = GetTargetFrame();
204   if (!lastTargetFrame) {
205     BeginTransaction(aTargetWeakFrame.GetFrame(), aWheelEvent);
206   } else if (lastTargetFrame != aTargetWeakFrame.GetFrame()) {
207     EndTransaction();
208     BeginTransaction(aTargetWeakFrame.GetFrame(), aWheelEvent);
209   } else {
210     UpdateTransaction(aWheelEvent);
211   }
212 
213   // When the wheel event will not be handled with any frames,
214   // UpdateTransaction() fires MozMouseScrollFailed event which is for
215   // automated testing.  In the event handler, the target frame might be
216   // destroyed.  Then, the caller shouldn't try to handle the default action.
217   if (!aTargetWeakFrame.IsAlive()) {
218     EndTransaction();
219     return false;
220   }
221 
222   return true;
223 }
224 
225 /* static */
OnEvent(WidgetEvent * aEvent)226 void WheelTransaction::OnEvent(WidgetEvent* aEvent) {
227   if (!sTargetFrame) {
228     return;
229   }
230 
231   if (OutOfTime(sTime, StaticPrefs::mousewheel_transaction_timeout())) {
232     // Even if the scroll event which is handled after timeout, but onTimeout
233     // was not fired by timer, then the scroll event will scroll old frame,
234     // therefore, we should call OnTimeout here and ensure to finish the old
235     // transaction.
236     OnTimeout(nullptr, nullptr);
237     return;
238   }
239 
240   switch (aEvent->mMessage) {
241     case eWheel:
242       if (sMouseMoved != 0 &&
243           OutOfTime(sMouseMoved,
244                     StaticPrefs::mousewheel_transaction_ignoremovedelay())) {
245         // Terminate the current mousewheel transaction if the mouse moved more
246         // than ignoremovedelay milliseconds ago
247         EndTransaction();
248       }
249       return;
250     case eMouseMove:
251     case eDragOver: {
252       WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
253       if (mouseEvent->IsReal()) {
254         // If the cursor is moving to be outside the frame,
255         // terminate the scrollwheel transaction.
256         LayoutDeviceIntPoint pt = GetScreenPoint(mouseEvent);
257         auto r = LayoutDeviceIntRect::FromAppUnitsToNearest(
258             sTargetFrame->GetScreenRectInAppUnits(),
259             sTargetFrame->PresContext()->AppUnitsPerDevPixel());
260         if (!r.Contains(pt)) {
261           EndTransaction();
262           return;
263         }
264 
265         // If the cursor is moving inside the frame, and it is less than
266         // ignoremovedelay milliseconds since the last scroll operation, ignore
267         // the mouse move; otherwise, record the current mouse move time to be
268         // checked later
269         if (!sMouseMoved &&
270             OutOfTime(sTime,
271                       StaticPrefs::mousewheel_transaction_ignoremovedelay())) {
272           sMouseMoved = PR_IntervalToMilliseconds(PR_IntervalNow());
273         }
274       }
275       return;
276     }
277     case eKeyPress:
278     case eKeyUp:
279     case eKeyDown:
280     case eMouseUp:
281     case eMouseDown:
282     case eMouseDoubleClick:
283     case eMouseAuxClick:
284     case eMouseClick:
285     case eContextMenu:
286     case eDrop:
287       EndTransaction();
288       return;
289     default:
290       break;
291   }
292 }
293 
294 /* static */
Shutdown()295 void WheelTransaction::Shutdown() { NS_IF_RELEASE(sTimer); }
296 
297 /* static */
OnFailToScrollTarget()298 void WheelTransaction::OnFailToScrollTarget() {
299   MOZ_ASSERT(sTargetFrame, "We don't have mouse scrolling transaction");
300 
301   if (StaticPrefs::test_mousescroll()) {
302     // This event is used for automated tests, see bug 442774.
303     nsContentUtils::DispatchEventOnlyToChrome(
304         sTargetFrame->GetContent()->OwnerDoc(), sTargetFrame->GetContent(),
305         u"MozMouseScrollFailed"_ns, CanBubble::eYes, Cancelable::eYes);
306   }
307   // The target frame might be destroyed in the event handler, at that time,
308   // we need to finish the current transaction
309   if (!sTargetFrame) {
310     EndTransaction();
311   }
312 }
313 
314 /* static */
OnTimeout(nsITimer * aTimer,void * aClosure)315 void WheelTransaction::OnTimeout(nsITimer* aTimer, void* aClosure) {
316   if (!sTargetFrame) {
317     // The transaction target was destroyed already
318     EndTransaction();
319     return;
320   }
321   // Store the sTargetFrame, the variable becomes null in EndTransaction.
322   nsIFrame* frame = sTargetFrame;
323   // We need to finish current transaction before DOM event firing. Because
324   // the next DOM event might create strange situation for us.
325   MayEndTransaction();
326 
327   if (StaticPrefs::test_mousescroll()) {
328     // This event is used for automated tests, see bug 442774.
329     nsContentUtils::DispatchEventOnlyToChrome(
330         frame->GetContent()->OwnerDoc(), frame->GetContent(),
331         u"MozMouseScrollTransactionTimeout"_ns, CanBubble::eYes,
332         Cancelable::eYes);
333   }
334 }
335 
336 /* static */
SetTimeout()337 void WheelTransaction::SetTimeout() {
338   if (!sTimer) {
339     sTimer = NS_NewTimer().take();
340     if (!sTimer) {
341       return;
342     }
343   }
344   sTimer->Cancel();
345   DebugOnly<nsresult> rv = sTimer->InitWithNamedFuncCallback(
346       OnTimeout, nullptr, StaticPrefs::mousewheel_transaction_timeout(),
347       nsITimer::TYPE_ONE_SHOT, "WheelTransaction::SetTimeout");
348   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
349                        "nsITimer::InitWithFuncCallback failed");
350 }
351 
352 /* static */
GetScreenPoint(WidgetGUIEvent * aEvent)353 LayoutDeviceIntPoint WheelTransaction::GetScreenPoint(WidgetGUIEvent* aEvent) {
354   NS_ASSERTION(aEvent, "aEvent is null");
355   NS_ASSERTION(aEvent->mWidget, "aEvent-mWidget is null");
356   return aEvent->mRefPoint + aEvent->mWidget->WidgetToScreenOffset();
357 }
358 
359 /* static */
AccelerateWheelDelta(WidgetWheelEvent * aEvent)360 DeltaValues WheelTransaction::AccelerateWheelDelta(WidgetWheelEvent* aEvent) {
361   DeltaValues result = OverrideSystemScrollSpeed(aEvent);
362 
363   // Don't accelerate the delta values if the event isn't line scrolling.
364   if (aEvent->mDeltaMode != dom::WheelEvent_Binding::DOM_DELTA_LINE) {
365     return result;
366   }
367 
368   // Accelerate by the sScrollSeriesCounter
369   int32_t start = StaticPrefs::mousewheel_acceleration_start();
370   if (start >= 0 && sScrollSeriesCounter >= start) {
371     int32_t factor = StaticPrefs::mousewheel_acceleration_factor();
372     if (factor > 0) {
373       result.deltaX = ComputeAcceleratedWheelDelta(result.deltaX, factor);
374       result.deltaY = ComputeAcceleratedWheelDelta(result.deltaY, factor);
375     }
376   }
377 
378   return result;
379 }
380 
381 /* static */
ComputeAcceleratedWheelDelta(double aDelta,int32_t aFactor)382 double WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta,
383                                                       int32_t aFactor) {
384   return mozilla::ComputeAcceleratedWheelDelta(aDelta, sScrollSeriesCounter,
385                                                aFactor);
386 }
387 
388 /* static */
OverrideSystemScrollSpeed(WidgetWheelEvent * aEvent)389 DeltaValues WheelTransaction::OverrideSystemScrollSpeed(
390     WidgetWheelEvent* aEvent) {
391   MOZ_ASSERT(sTargetFrame, "We don't have mouse scrolling transaction");
392 
393   // If the event doesn't scroll to both X and Y, we don't need to do anything
394   // here.
395   if (!aEvent->mDeltaX && !aEvent->mDeltaY) {
396     return DeltaValues(aEvent);
397   }
398 
399   return DeltaValues(aEvent->OverriddenDeltaX(), aEvent->OverriddenDeltaY());
400 }
401 
402 /******************************************************************/
403 /* mozilla::ScrollbarsForWheel                                    */
404 /******************************************************************/
405 
406 const DeltaValues ScrollbarsForWheel::directions[kNumberOfTargets] = {
407     DeltaValues(-1, 0), DeltaValues(+1, 0), DeltaValues(0, -1),
408     DeltaValues(0, +1)};
409 
410 AutoWeakFrame ScrollbarsForWheel::sActiveOwner = nullptr;
411 AutoWeakFrame ScrollbarsForWheel::sActivatedScrollTargets[kNumberOfTargets] = {
412     nullptr, nullptr, nullptr, nullptr};
413 
414 bool ScrollbarsForWheel::sHadWheelStart = false;
415 bool ScrollbarsForWheel::sOwnWheelTransaction = false;
416 
417 /* static */
PrepareToScrollText(EventStateManager * aESM,nsIFrame * aTargetFrame,WidgetWheelEvent * aEvent)418 void ScrollbarsForWheel::PrepareToScrollText(EventStateManager* aESM,
419                                              nsIFrame* aTargetFrame,
420                                              WidgetWheelEvent* aEvent) {
421   if (aEvent->mMessage == eWheelOperationStart) {
422     WheelTransaction::OwnScrollbars(false);
423     if (!IsActive()) {
424       TemporarilyActivateAllPossibleScrollTargets(aESM, aTargetFrame, aEvent);
425       sHadWheelStart = true;
426     }
427   } else {
428     DeactivateAllTemporarilyActivatedScrollTargets();
429   }
430 }
431 
432 /* static */
SetActiveScrollTarget(nsIScrollableFrame * aScrollTarget)433 void ScrollbarsForWheel::SetActiveScrollTarget(
434     nsIScrollableFrame* aScrollTarget) {
435   if (!sHadWheelStart) {
436     return;
437   }
438   nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(aScrollTarget);
439   if (!scrollbarMediator) {
440     return;
441   }
442   sHadWheelStart = false;
443   sActiveOwner = do_QueryFrame(aScrollTarget);
444   scrollbarMediator->ScrollbarActivityStarted();
445 }
446 
447 /* static */
MayInactivate()448 void ScrollbarsForWheel::MayInactivate() {
449   if (!sOwnWheelTransaction && WheelTransaction::GetTargetFrame()) {
450     WheelTransaction::OwnScrollbars(true);
451   } else {
452     Inactivate();
453   }
454 }
455 
456 /* static */
Inactivate()457 void ScrollbarsForWheel::Inactivate() {
458   nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sActiveOwner);
459   if (scrollbarMediator) {
460     scrollbarMediator->ScrollbarActivityStopped();
461   }
462   sActiveOwner = nullptr;
463   DeactivateAllTemporarilyActivatedScrollTargets();
464   if (sOwnWheelTransaction) {
465     sOwnWheelTransaction = false;
466     WheelTransaction::OwnScrollbars(false);
467     WheelTransaction::EndTransaction();
468   }
469 }
470 
471 /* static */
IsActive()472 bool ScrollbarsForWheel::IsActive() {
473   if (sActiveOwner) {
474     return true;
475   }
476   for (size_t i = 0; i < kNumberOfTargets; ++i) {
477     if (sActivatedScrollTargets[i]) {
478       return true;
479     }
480   }
481   return false;
482 }
483 
484 /* static */
OwnWheelTransaction(bool aOwn)485 void ScrollbarsForWheel::OwnWheelTransaction(bool aOwn) {
486   sOwnWheelTransaction = aOwn;
487 }
488 
489 /* static */
TemporarilyActivateAllPossibleScrollTargets(EventStateManager * aESM,nsIFrame * aTargetFrame,WidgetWheelEvent * aEvent)490 void ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets(
491     EventStateManager* aESM, nsIFrame* aTargetFrame, WidgetWheelEvent* aEvent) {
492   for (size_t i = 0; i < kNumberOfTargets; i++) {
493     const DeltaValues* dir = &directions[i];
494     AutoWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
495     MOZ_ASSERT(!*scrollTarget, "scroll target still temporarily activated!");
496     nsIScrollableFrame* target = do_QueryFrame(aESM->ComputeScrollTarget(
497         aTargetFrame, dir->deltaX, dir->deltaY, aEvent,
498         EventStateManager::COMPUTE_DEFAULT_ACTION_TARGET));
499     nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(target);
500     if (scrollbarMediator) {
501       nsIFrame* targetFrame = do_QueryFrame(target);
502       *scrollTarget = targetFrame;
503       scrollbarMediator->ScrollbarActivityStarted();
504     }
505   }
506 }
507 
508 /* static */
DeactivateAllTemporarilyActivatedScrollTargets()509 void ScrollbarsForWheel::DeactivateAllTemporarilyActivatedScrollTargets() {
510   for (size_t i = 0; i < kNumberOfTargets; i++) {
511     AutoWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
512     if (*scrollTarget) {
513       nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(*scrollTarget);
514       if (scrollbarMediator) {
515         scrollbarMediator->ScrollbarActivityStopped();
516       }
517       *scrollTarget = nullptr;
518     }
519   }
520 }
521 
522 /******************************************************************/
523 /* mozilla::WheelDeltaHorizontalizer                              */
524 /******************************************************************/
525 
Horizontalize()526 void WheelDeltaHorizontalizer::Horizontalize() {
527   MOZ_ASSERT(!mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler,
528              "Wheel delta values in one wheel scroll event are being adjusted "
529              "a second time");
530 
531   // Log the old values.
532   mOldDeltaX = mWheelEvent.mDeltaX;
533   mOldDeltaZ = mWheelEvent.mDeltaZ;
534   mOldOverflowDeltaX = mWheelEvent.mOverflowDeltaX;
535   mOldLineOrPageDeltaX = mWheelEvent.mLineOrPageDeltaX;
536 
537   // Move deltaY values to deltaX and set both deltaY and deltaZ to 0.
538   mWheelEvent.mDeltaX = mWheelEvent.mDeltaY;
539   mWheelEvent.mDeltaY = 0.0;
540   mWheelEvent.mDeltaZ = 0.0;
541   mWheelEvent.mOverflowDeltaX = mWheelEvent.mOverflowDeltaY;
542   mWheelEvent.mOverflowDeltaY = 0.0;
543   mWheelEvent.mLineOrPageDeltaX = mWheelEvent.mLineOrPageDeltaY;
544   mWheelEvent.mLineOrPageDeltaY = 0;
545 
546   // Mark it horizontalized in order to restore the delta values when this
547   // instance is being destroyed.
548   mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler = true;
549   mHorizontalized = true;
550 }
551 
CancelHorizontalization()552 void WheelDeltaHorizontalizer::CancelHorizontalization() {
553   // Restore the horizontalized delta.
554   if (mHorizontalized &&
555       mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler) {
556     mWheelEvent.mDeltaY = mWheelEvent.mDeltaX;
557     mWheelEvent.mDeltaX = mOldDeltaX;
558     mWheelEvent.mDeltaZ = mOldDeltaZ;
559     mWheelEvent.mOverflowDeltaY = mWheelEvent.mOverflowDeltaX;
560     mWheelEvent.mOverflowDeltaX = mOldOverflowDeltaX;
561     mWheelEvent.mLineOrPageDeltaY = mWheelEvent.mLineOrPageDeltaX;
562     mWheelEvent.mLineOrPageDeltaX = mOldLineOrPageDeltaX;
563     mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler = false;
564     mHorizontalized = false;
565   }
566 }
567 
~WheelDeltaHorizontalizer()568 WheelDeltaHorizontalizer::~WheelDeltaHorizontalizer() {
569   CancelHorizontalization();
570 }
571 
572 /******************************************************************/
573 /* mozilla::AutoDirWheelDeltaAdjuster                             */
574 /******************************************************************/
575 
ShouldBeAdjusted()576 bool AutoDirWheelDeltaAdjuster::ShouldBeAdjusted() {
577   // Sometimes, this function can be called more than one time. If we have
578   // already checked if the scroll should be adjusted, there's no need to check
579   // it again.
580   if (mCheckedIfShouldBeAdjusted) {
581     return mShouldBeAdjusted;
582   }
583   mCheckedIfShouldBeAdjusted = true;
584 
585   // For an auto-dir wheel scroll, if all the following conditions are met, we
586   // should adjust X and Y values:
587   // 1. There is only one non-zero value between DeltaX and DeltaY.
588   // 2. There is only one direction for the target that overflows and is
589   //    scrollable with wheel.
590   // 3. The direction described in Condition 1 is orthogonal to the one
591   // described in Condition 2.
592   if ((mDeltaX && mDeltaY) || (!mDeltaX && !mDeltaY)) {
593     return false;
594   }
595   if (mDeltaX) {
596     if (CanScrollAlongXAxis()) {
597       return false;
598     }
599     if (IsHorizontalContentRightToLeft()) {
600       mShouldBeAdjusted =
601           mDeltaX > 0 ? CanScrollUpwards() : CanScrollDownwards();
602     } else {
603       mShouldBeAdjusted =
604           mDeltaX < 0 ? CanScrollUpwards() : CanScrollDownwards();
605     }
606     return mShouldBeAdjusted;
607   }
608   MOZ_ASSERT(0 != mDeltaY);
609   if (CanScrollAlongYAxis()) {
610     return false;
611   }
612   if (IsHorizontalContentRightToLeft()) {
613     mShouldBeAdjusted =
614         mDeltaY > 0 ? CanScrollLeftwards() : CanScrollRightwards();
615   } else {
616     mShouldBeAdjusted =
617         mDeltaY < 0 ? CanScrollLeftwards() : CanScrollRightwards();
618   }
619   return mShouldBeAdjusted;
620 }
621 
Adjust()622 void AutoDirWheelDeltaAdjuster::Adjust() {
623   if (!ShouldBeAdjusted()) {
624     return;
625   }
626   std::swap(mDeltaX, mDeltaY);
627   if (IsHorizontalContentRightToLeft()) {
628     mDeltaX *= -1;
629     mDeltaY *= -1;
630   }
631   mShouldBeAdjusted = false;
632   OnAdjusted();
633 }
634 
635 /******************************************************************/
636 /* mozilla::ESMAutoDirWheelDeltaAdjuster                          */
637 /******************************************************************/
638 
ESMAutoDirWheelDeltaAdjuster(WidgetWheelEvent & aEvent,nsIFrame & aScrollFrame,bool aHonoursRoot)639 ESMAutoDirWheelDeltaAdjuster::ESMAutoDirWheelDeltaAdjuster(
640     WidgetWheelEvent& aEvent, nsIFrame& aScrollFrame, bool aHonoursRoot)
641     : AutoDirWheelDeltaAdjuster(aEvent.mDeltaX, aEvent.mDeltaY),
642       mLineOrPageDeltaX(aEvent.mLineOrPageDeltaX),
643       mLineOrPageDeltaY(aEvent.mLineOrPageDeltaY),
644       mOverflowDeltaX(aEvent.mOverflowDeltaX),
645       mOverflowDeltaY(aEvent.mOverflowDeltaY) {
646   mScrollTargetFrame = aScrollFrame.GetScrollTargetFrame();
647   MOZ_ASSERT(mScrollTargetFrame);
648 
649   nsIFrame* honouredFrame = nullptr;
650   if (aHonoursRoot) {
651     // If we are going to honour root, first try to get the frame for <body> as
652     // the honoured root, because <body> is in preference to <html> if the
653     // current document is an HTML document.
654     dom::Document* document = aScrollFrame.PresShell()->GetDocument();
655     if (document) {
656       dom::Element* bodyElement = document->GetBodyElement();
657       if (bodyElement) {
658         honouredFrame = bodyElement->GetPrimaryFrame();
659       }
660     }
661 
662     if (!honouredFrame) {
663       // If there is no <body> frame, fall back to the real root frame.
664       honouredFrame = aScrollFrame.PresShell()->GetRootScrollFrame();
665     }
666 
667     if (!honouredFrame) {
668       // If there is no root scroll frame, fall back to the current scrolling
669       // frame.
670       honouredFrame = &aScrollFrame;
671     }
672   } else {
673     honouredFrame = &aScrollFrame;
674   }
675 
676   WritingMode writingMode = honouredFrame->GetWritingMode();
677   WritingMode::BlockDir blockDir = writingMode.GetBlockDir();
678   WritingMode::InlineDir inlineDir = writingMode.GetInlineDir();
679   // Get whether the honoured frame's content in the horizontal direction starts
680   // from right to left(E.g. it's true either if "writing-mode: vertical-rl", or
681   // if "writing-mode: horizontal-tb; direction: rtl;" in CSS).
682   mIsHorizontalContentRightToLeft =
683       (blockDir == WritingMode::BlockDir::eBlockRL ||
684        (blockDir == WritingMode::BlockDir::eBlockTB &&
685         inlineDir == WritingMode::InlineDir::eInlineRTL));
686 }
687 
OnAdjusted()688 void ESMAutoDirWheelDeltaAdjuster::OnAdjusted() {
689   // Adjust() only adjusted basic deltaX and deltaY, which are not enough for
690   // ESM, we should continue to adjust line-or-page and overflow values.
691   if (mDeltaX) {
692     // A vertical scroll was adjusted to be horizontal.
693     MOZ_ASSERT(0 == mDeltaY);
694 
695     mLineOrPageDeltaX = mLineOrPageDeltaY;
696     mLineOrPageDeltaY = 0;
697     mOverflowDeltaX = mOverflowDeltaY;
698     mOverflowDeltaY = 0;
699   } else {
700     // A horizontal scroll was adjusted to be vertical.
701     MOZ_ASSERT(0 != mDeltaY);
702 
703     mLineOrPageDeltaY = mLineOrPageDeltaX;
704     mLineOrPageDeltaX = 0;
705     mOverflowDeltaY = mOverflowDeltaX;
706     mOverflowDeltaX = 0;
707   }
708   if (mIsHorizontalContentRightToLeft) {
709     // If in RTL writing mode, reverse the side the scroll will go towards.
710     mLineOrPageDeltaX *= -1;
711     mLineOrPageDeltaY *= -1;
712     mOverflowDeltaX *= -1;
713     mOverflowDeltaY *= -1;
714   }
715 }
716 
CanScrollAlongXAxis() const717 bool ESMAutoDirWheelDeltaAdjuster::CanScrollAlongXAxis() const {
718   return mScrollTargetFrame->GetAvailableScrollingDirections().contains(
719       layers::ScrollDirection::eHorizontal);
720 }
721 
CanScrollAlongYAxis() const722 bool ESMAutoDirWheelDeltaAdjuster::CanScrollAlongYAxis() const {
723   return mScrollTargetFrame->GetAvailableScrollingDirections().contains(
724       layers::ScrollDirection::eVertical);
725 }
726 
CanScrollUpwards() const727 bool ESMAutoDirWheelDeltaAdjuster::CanScrollUpwards() const {
728   nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
729   nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
730   return static_cast<double>(scrollRange.y) < scrollPt.y;
731 }
732 
CanScrollDownwards() const733 bool ESMAutoDirWheelDeltaAdjuster::CanScrollDownwards() const {
734   nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
735   nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
736   return static_cast<double>(scrollRange.YMost()) > scrollPt.y;
737 }
738 
CanScrollLeftwards() const739 bool ESMAutoDirWheelDeltaAdjuster::CanScrollLeftwards() const {
740   nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
741   nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
742   return static_cast<double>(scrollRange.x) < scrollPt.x;
743 }
744 
CanScrollRightwards() const745 bool ESMAutoDirWheelDeltaAdjuster::CanScrollRightwards() const {
746   nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
747   nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
748   return static_cast<double>(scrollRange.XMost()) > scrollPt.x;
749 }
750 
IsHorizontalContentRightToLeft() const751 bool ESMAutoDirWheelDeltaAdjuster::IsHorizontalContentRightToLeft() const {
752   return mIsHorizontalContentRightToLeft;
753 }
754 
755 /******************************************************************/
756 /* mozilla::ESMAutoDirWheelDeltaRestorer                          */
757 /******************************************************************/
758 
759 /*explicit*/
ESMAutoDirWheelDeltaRestorer(WidgetWheelEvent & aEvent)760 ESMAutoDirWheelDeltaRestorer::ESMAutoDirWheelDeltaRestorer(
761     WidgetWheelEvent& aEvent)
762     : mEvent(aEvent),
763       mOldDeltaX(aEvent.mDeltaX),
764       mOldDeltaY(aEvent.mDeltaY),
765       mOldLineOrPageDeltaX(aEvent.mLineOrPageDeltaX),
766       mOldLineOrPageDeltaY(aEvent.mLineOrPageDeltaY),
767       mOldOverflowDeltaX(aEvent.mOverflowDeltaX),
768       mOldOverflowDeltaY(aEvent.mOverflowDeltaY) {}
769 
~ESMAutoDirWheelDeltaRestorer()770 ESMAutoDirWheelDeltaRestorer::~ESMAutoDirWheelDeltaRestorer() {
771   if (mOldDeltaX == mEvent.mDeltaX || mOldDeltaY == mEvent.mDeltaY) {
772     // The delta of the event wasn't adjusted during the lifetime of this
773     // |ESMAutoDirWheelDeltaRestorer| instance. No need to restore it.
774     return;
775   }
776 
777   bool forRTL = false;
778 
779   // First, restore the basic deltaX and deltaY.
780   std::swap(mEvent.mDeltaX, mEvent.mDeltaY);
781   if (mOldDeltaX != mEvent.mDeltaX || mOldDeltaY != mEvent.mDeltaY) {
782     // If X and Y still don't equal to their original values after being
783     // swapped, then it must be because they were adjusted for RTL.
784     forRTL = true;
785     mEvent.mDeltaX *= -1;
786     mEvent.mDeltaY *= -1;
787     MOZ_ASSERT(mOldDeltaX == mEvent.mDeltaX && mOldDeltaY == mEvent.mDeltaY);
788   }
789 
790   if (mEvent.mDeltaX) {
791     // A horizontal scroll was adjusted to be vertical during the lifetime of
792     // this instance.
793     MOZ_ASSERT(0 == mEvent.mDeltaY);
794 
795     // Restore the line-or-page and overflow values to be horizontal.
796     mEvent.mOverflowDeltaX = mEvent.mOverflowDeltaY;
797     mEvent.mLineOrPageDeltaX = mEvent.mLineOrPageDeltaY;
798     if (forRTL) {
799       mEvent.mOverflowDeltaX *= -1;
800       mEvent.mLineOrPageDeltaX *= -1;
801     }
802     mEvent.mOverflowDeltaY = mOldOverflowDeltaY;
803     mEvent.mLineOrPageDeltaY = mOldLineOrPageDeltaY;
804   } else {
805     // A vertical scroll was adjusted to be horizontal during the lifetime of
806     // this instance.
807     MOZ_ASSERT(0 != mEvent.mDeltaY);
808 
809     // Restore the line-or-page and overflow values to be vertical.
810     mEvent.mOverflowDeltaY = mEvent.mOverflowDeltaX;
811     mEvent.mLineOrPageDeltaY = mEvent.mLineOrPageDeltaX;
812     if (forRTL) {
813       mEvent.mOverflowDeltaY *= -1;
814       mEvent.mLineOrPageDeltaY *= -1;
815     }
816     mEvent.mOverflowDeltaX = mOldOverflowDeltaX;
817     mEvent.mLineOrPageDeltaX = mOldLineOrPageDeltaX;
818   }
819 }
820 
821 }  // namespace mozilla
822