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/. */
7 #include "WheelHandlingHelper.h"
9 #include <utility> // for std::swap
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"
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"
28 #include "nsPresContext.h"
31 #include "ScrollAnimationPhysics.h"
35 /******************************************************************/
36 /* mozilla::DeltaValues */
37 /******************************************************************/
39 DeltaValues::DeltaValues(WidgetWheelEvent
* aEvent
)
40 : deltaX(aEvent
->mDeltaX
), deltaY(aEvent
->mDeltaY
) {}
42 /******************************************************************/
43 /* mozilla::WheelHandlingUtils */
44 /******************************************************************/
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
;
54 bool WheelHandlingUtils::CanScrollOn(nsIFrame
* aFrame
, double aDirectionX
,
56 nsIScrollableFrame
* scrollableFrame
= do_QueryFrame(aFrame
);
57 if (!scrollableFrame
) {
60 return CanScrollOn(scrollableFrame
, aDirectionX
, 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");
70 nsPoint scrollPt
= aScrollFrame
->GetVisualViewportOffset();
71 nsRect scrollRange
= aScrollFrame
->GetScrollRangeForUserInputEvents();
72 layers::ScrollDirections directions
=
73 aScrollFrame
->GetAvailableScrollingDirectionsForUserInputEvents();
75 return ((aDirectionX
!= 0.0) &&
76 (directions
.contains(layers::ScrollDirection::eHorizontal
)) &&
77 CanScrollInRange(scrollRange
.x
, scrollPt
.x
, scrollRange
.XMost(),
79 ((aDirectionY
!= 0.0) &&
80 (directions
.contains(layers::ScrollDirection::eVertical
)) &&
81 CanScrollInRange(scrollRange
.y
, scrollPt
.y
, scrollRange
.YMost(),
85 /*static*/ Maybe
<layers::ScrollDirection
>
86 WheelHandlingUtils::GetDisregardedWheelScrollDirection(const nsIFrame
* aFrame
) {
87 nsIContent
* content
= aFrame
->GetContent();
91 TextControlElement
* textControlElement
= TextControlElement::FromNodeOrNull(
92 content
->IsInNativeAnonymousSubtree()
93 ? content
->GetClosestNativeAnonymousSubtreeRootParentOrHost()
95 if (!textControlElement
|| !textControlElement
->IsSingleLineTextControl()) {
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
105 return Some(aFrame
->GetWritingMode().IsVertical()
106 ? layers::ScrollDirection::eHorizontal
107 : layers::ScrollDirection::eVertical
);
110 /******************************************************************/
111 /* mozilla::WheelTransaction */
112 /******************************************************************/
114 AutoWeakFrame
WheelTransaction::sScrollTargetFrame(nullptr);
115 AutoWeakFrame
WheelTransaction::sEventTargetFrame(nullptr);
116 bool WheelTransaction::sHandledByApz(false);
117 uint32_t WheelTransaction::sTime
= 0;
118 uint32_t WheelTransaction::sMouseMoved
= 0;
119 nsITimer
* WheelTransaction::sTimer
= nullptr;
120 int32_t WheelTransaction::sScrollSeriesCounter
= 0;
121 bool WheelTransaction::sOwnScrollbars
= false;
124 bool WheelTransaction::OutOfTime(uint32_t aBaseTime
, uint32_t aThreshold
) {
125 uint32_t now
= PR_IntervalToMilliseconds(PR_IntervalNow());
126 return (now
- aBaseTime
> aThreshold
);
130 void WheelTransaction::OwnScrollbars(bool aOwn
) { sOwnScrollbars
= aOwn
; }
133 void WheelTransaction::BeginTransaction(nsIFrame
* aScrollTargetFrame
,
134 nsIFrame
* aEventTargetFrame
,
135 const WidgetWheelEvent
* aEvent
) {
136 NS_ASSERTION(!sScrollTargetFrame
&& !sEventTargetFrame
,
137 "previous transaction is not finished!");
138 MOZ_ASSERT(aEvent
->mMessage
== eWheel
,
139 "Transaction must be started with a wheel event");
141 ScrollbarsForWheel::OwnWheelTransaction(false);
142 sScrollTargetFrame
= aScrollTargetFrame
;
144 // Only set the static event target if wheel event groups are enabled.
145 if (StaticPrefs::dom_event_wheel_event_groups_enabled()) {
146 // Set a static event target for the wheel transaction. This will be used
147 // to override the event target frame when computing the event target from
148 // input coordinates. When this preference is not set or there is no stored
149 // event target for the current wheel transaction, the event target will
150 // not be overridden by the current wheel transaction, but will be computed
151 // from the input coordinates.
152 sEventTargetFrame
= aEventTargetFrame
;
153 // If the wheel events will be handled by APZ, set a flag here. We can use
154 // this later to determine if we need to scroll snap at the end of the
156 sHandledByApz
= aEvent
->mFlags
.mHandledByAPZ
;
159 sScrollSeriesCounter
= 0;
160 if (!UpdateTransaction(aEvent
)) {
161 NS_ERROR("BeginTransaction is called even cannot scroll the frame");
167 bool WheelTransaction::UpdateTransaction(const WidgetWheelEvent
* aEvent
) {
168 nsIFrame
* scrollToFrame
= GetScrollTargetFrame();
169 nsIScrollableFrame
* scrollableFrame
= scrollToFrame
->GetScrollTargetFrame();
170 if (scrollableFrame
) {
171 scrollToFrame
= do_QueryFrame(scrollableFrame
);
174 if (!WheelHandlingUtils::CanScrollOn(scrollToFrame
, aEvent
->mDeltaX
,
176 OnFailToScrollTarget();
177 // We should not modify the transaction state when the view will not be
178 // scrolled actually.
184 if (sScrollSeriesCounter
!= 0 &&
185 OutOfTime(sTime
, StaticPrefs::mousewheel_scroll_series_timeout())) {
186 sScrollSeriesCounter
= 0;
188 sScrollSeriesCounter
++;
190 // We should use current time instead of WidgetEvent.time.
191 // 1. Some events doesn't have the correct creation time.
192 // 2. If the computer runs slowly by other processes eating the CPU resource,
193 // the event creation time doesn't keep real time.
194 sTime
= PR_IntervalToMilliseconds(PR_IntervalNow());
200 void WheelTransaction::MayEndTransaction() {
201 if (!sOwnScrollbars
&& ScrollbarsForWheel::IsActive()) {
202 ScrollbarsForWheel::OwnWheelTransaction(true);
209 void WheelTransaction::EndTransaction() {
213 sScrollTargetFrame
= nullptr;
214 sEventTargetFrame
= nullptr;
215 sScrollSeriesCounter
= 0;
216 sHandledByApz
= false;
217 if (sOwnScrollbars
) {
218 sOwnScrollbars
= false;
219 ScrollbarsForWheel::OwnWheelTransaction(false);
220 ScrollbarsForWheel::Inactivate();
225 bool WheelTransaction::WillHandleDefaultAction(
226 WidgetWheelEvent
* aWheelEvent
, AutoWeakFrame
& aScrollTargetWeakFrame
,
227 AutoWeakFrame
& aEventTargetWeakFrame
) {
228 nsIFrame
* lastTargetFrame
= GetScrollTargetFrame();
229 if (!lastTargetFrame
) {
230 BeginTransaction(aScrollTargetWeakFrame
.GetFrame(),
231 aEventTargetWeakFrame
.GetFrame(), aWheelEvent
);
232 } else if (lastTargetFrame
!= aScrollTargetWeakFrame
.GetFrame()) {
234 BeginTransaction(aScrollTargetWeakFrame
.GetFrame(),
235 aEventTargetWeakFrame
.GetFrame(), aWheelEvent
);
237 UpdateTransaction(aWheelEvent
);
240 // When the wheel event will not be handled with any frames,
241 // UpdateTransaction() fires MozMouseScrollFailed event which is for
242 // automated testing. In the event handler, the target frame might be
243 // destroyed. Then, the caller shouldn't try to handle the default action.
244 if (!aScrollTargetWeakFrame
.IsAlive()) {
253 void WheelTransaction::OnEvent(WidgetEvent
* aEvent
) {
254 if (!sScrollTargetFrame
) {
258 if (OutOfTime(sTime
, StaticPrefs::mousewheel_transaction_timeout())) {
259 // Even if the scroll event which is handled after timeout, but onTimeout
260 // was not fired by timer, then the scroll event will scroll old frame,
261 // therefore, we should call OnTimeout here and ensure to finish the old
263 OnTimeout(nullptr, nullptr);
267 switch (aEvent
->mMessage
) {
269 if (sMouseMoved
!= 0 &&
270 OutOfTime(sMouseMoved
,
271 StaticPrefs::mousewheel_transaction_ignoremovedelay())) {
272 // Terminate the current mousewheel transaction if the mouse moved more
273 // than ignoremovedelay milliseconds ago
279 WidgetMouseEvent
* mouseEvent
= aEvent
->AsMouseEvent();
280 if (mouseEvent
->IsReal()) {
281 // If the cursor is moving to be outside the frame,
282 // terminate the scrollwheel transaction.
283 LayoutDeviceIntPoint pt
= GetScreenPoint(mouseEvent
);
284 auto r
= LayoutDeviceIntRect::FromAppUnitsToNearest(
285 sScrollTargetFrame
->GetScreenRectInAppUnits(),
286 sScrollTargetFrame
->PresContext()->AppUnitsPerDevPixel());
287 if (!r
.Contains(pt
)) {
292 // For mouse move events where the wheel transaction is still valid, the
293 // stored event target should be reset.
294 sEventTargetFrame
= nullptr;
296 // If the cursor is moving inside the frame, and it is less than
297 // ignoremovedelay milliseconds since the last scroll operation, ignore
298 // the mouse move; otherwise, record the current mouse move time to be
302 StaticPrefs::mousewheel_transaction_ignoremovedelay())) {
303 sMouseMoved
= PR_IntervalToMilliseconds(PR_IntervalNow());
313 case eMouseDoubleClick
:
326 void WheelTransaction::OnRemoveElement(nsIContent
* aContent
) {
327 // If dom.event.wheel-event-groups.enabled is not set or we have no current
328 // wheel event transaction there is no internal state to be updated.
329 if (!sEventTargetFrame
) {
333 if (sEventTargetFrame
->GetContent() == aContent
) {
334 // Only invalidate the wheel transaction event target frame when the
335 // remove target is the event target of the wheel event group. The
336 // scroll target frame of the wheel event group may still be valid.
338 // With the stored event target unset, the target for any following
339 // events will be the frame found using the input coordinates.
340 sEventTargetFrame
= nullptr;
345 void WheelTransaction::Shutdown() { NS_IF_RELEASE(sTimer
); }
348 void WheelTransaction::OnFailToScrollTarget() {
349 MOZ_ASSERT(sScrollTargetFrame
, "We don't have mouse scrolling transaction");
351 if (StaticPrefs::test_mousescroll()) {
352 // This event is used for automated tests, see bug 442774.
353 nsContentUtils::DispatchEventOnlyToChrome(
354 sScrollTargetFrame
->GetContent()->OwnerDoc(),
355 sScrollTargetFrame
->GetContent(), u
"MozMouseScrollFailed"_ns
,
356 CanBubble::eYes
, Cancelable::eYes
);
358 // The target frame might be destroyed in the event handler, at that time,
359 // we need to finish the current transaction
360 if (!sScrollTargetFrame
) {
366 void WheelTransaction::OnTimeout(nsITimer
* aTimer
, void* aClosure
) {
367 if (!sScrollTargetFrame
) {
368 // The transaction target was destroyed already
372 // Store the sScrollTargetFrame, the variable becomes null in EndTransaction.
373 nsIFrame
* frame
= sScrollTargetFrame
;
374 // We need to finish current transaction before DOM event firing. Because
375 // the next DOM event might create strange situation for us.
378 if (StaticPrefs::test_mousescroll()) {
379 // This event is used for automated tests, see bug 442774.
380 nsContentUtils::DispatchEventOnlyToChrome(
381 frame
->GetContent()->OwnerDoc(), frame
->GetContent(),
382 u
"MozMouseScrollTransactionTimeout"_ns
, CanBubble::eYes
,
388 void WheelTransaction::SetTimeout() {
390 sTimer
= NS_NewTimer().take();
396 DebugOnly
<nsresult
> rv
= sTimer
->InitWithNamedFuncCallback(
397 OnTimeout
, nullptr, StaticPrefs::mousewheel_transaction_timeout(),
398 nsITimer::TYPE_ONE_SHOT
, "WheelTransaction::SetTimeout");
399 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
400 "nsITimer::InitWithFuncCallback failed");
404 LayoutDeviceIntPoint
WheelTransaction::GetScreenPoint(WidgetGUIEvent
* aEvent
) {
405 NS_ASSERTION(aEvent
, "aEvent is null");
406 NS_ASSERTION(aEvent
->mWidget
, "aEvent-mWidget is null");
407 return aEvent
->mRefPoint
+ aEvent
->mWidget
->WidgetToScreenOffset();
411 DeltaValues
WheelTransaction::AccelerateWheelDelta(WidgetWheelEvent
* aEvent
) {
412 DeltaValues result
= OverrideSystemScrollSpeed(aEvent
);
414 // Don't accelerate the delta values if the event isn't line scrolling.
415 if (aEvent
->mDeltaMode
!= dom::WheelEvent_Binding::DOM_DELTA_LINE
) {
419 // Accelerate by the sScrollSeriesCounter
420 int32_t start
= StaticPrefs::mousewheel_acceleration_start();
421 if (start
>= 0 && sScrollSeriesCounter
>= start
) {
422 int32_t factor
= StaticPrefs::mousewheel_acceleration_factor();
424 result
.deltaX
= ComputeAcceleratedWheelDelta(result
.deltaX
, factor
);
425 result
.deltaY
= ComputeAcceleratedWheelDelta(result
.deltaY
, factor
);
433 double WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta
,
435 return mozilla::ComputeAcceleratedWheelDelta(aDelta
, sScrollSeriesCounter
,
440 DeltaValues
WheelTransaction::OverrideSystemScrollSpeed(
441 WidgetWheelEvent
* aEvent
) {
442 MOZ_ASSERT(sScrollTargetFrame
, "We don't have mouse scrolling transaction");
444 // If the event doesn't scroll to both X and Y, we don't need to do anything
446 if (!aEvent
->mDeltaX
&& !aEvent
->mDeltaY
) {
447 return DeltaValues(aEvent
);
450 return DeltaValues(aEvent
->OverriddenDeltaX(), aEvent
->OverriddenDeltaY());
453 /******************************************************************/
454 /* mozilla::ScrollbarsForWheel */
455 /******************************************************************/
457 const DeltaValues
ScrollbarsForWheel::directions
[kNumberOfTargets
] = {
458 DeltaValues(-1, 0), DeltaValues(+1, 0), DeltaValues(0, -1),
461 AutoWeakFrame
ScrollbarsForWheel::sActiveOwner
= nullptr;
462 AutoWeakFrame
ScrollbarsForWheel::sActivatedScrollTargets
[kNumberOfTargets
] = {
463 nullptr, nullptr, nullptr, nullptr};
465 bool ScrollbarsForWheel::sHadWheelStart
= false;
466 bool ScrollbarsForWheel::sOwnWheelTransaction
= false;
469 void ScrollbarsForWheel::PrepareToScrollText(EventStateManager
* aESM
,
470 nsIFrame
* aTargetFrame
,
471 WidgetWheelEvent
* aEvent
) {
472 if (aEvent
->mMessage
== eWheelOperationStart
) {
473 WheelTransaction::OwnScrollbars(false);
475 TemporarilyActivateAllPossibleScrollTargets(aESM
, aTargetFrame
, aEvent
);
476 sHadWheelStart
= true;
479 DeactivateAllTemporarilyActivatedScrollTargets();
484 void ScrollbarsForWheel::SetActiveScrollTarget(
485 nsIScrollableFrame
* aScrollTarget
) {
486 if (!sHadWheelStart
) {
489 nsIScrollbarMediator
* scrollbarMediator
= do_QueryFrame(aScrollTarget
);
490 if (!scrollbarMediator
) {
493 sHadWheelStart
= false;
494 sActiveOwner
= do_QueryFrame(aScrollTarget
);
495 scrollbarMediator
->ScrollbarActivityStarted();
499 void ScrollbarsForWheel::MayInactivate() {
500 if (!sOwnWheelTransaction
&& WheelTransaction::GetScrollTargetFrame()) {
501 WheelTransaction::OwnScrollbars(true);
508 void ScrollbarsForWheel::Inactivate() {
509 nsIScrollbarMediator
* scrollbarMediator
= do_QueryFrame(sActiveOwner
);
510 if (scrollbarMediator
) {
511 scrollbarMediator
->ScrollbarActivityStopped();
513 sActiveOwner
= nullptr;
514 DeactivateAllTemporarilyActivatedScrollTargets();
515 if (sOwnWheelTransaction
) {
516 sOwnWheelTransaction
= false;
517 WheelTransaction::OwnScrollbars(false);
518 WheelTransaction::EndTransaction();
523 bool ScrollbarsForWheel::IsActive() {
527 for (size_t i
= 0; i
< kNumberOfTargets
; ++i
) {
528 if (sActivatedScrollTargets
[i
]) {
536 void ScrollbarsForWheel::OwnWheelTransaction(bool aOwn
) {
537 sOwnWheelTransaction
= aOwn
;
541 void ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets(
542 EventStateManager
* aESM
, nsIFrame
* aTargetFrame
, WidgetWheelEvent
* aEvent
) {
543 for (size_t i
= 0; i
< kNumberOfTargets
; i
++) {
544 const DeltaValues
* dir
= &directions
[i
];
545 AutoWeakFrame
* scrollTarget
= &sActivatedScrollTargets
[i
];
546 MOZ_ASSERT(!*scrollTarget
, "scroll target still temporarily activated!");
547 nsIScrollableFrame
* target
= do_QueryFrame(aESM
->ComputeScrollTarget(
548 aTargetFrame
, dir
->deltaX
, dir
->deltaY
, aEvent
,
549 EventStateManager::COMPUTE_DEFAULT_ACTION_TARGET
));
550 nsIScrollbarMediator
* scrollbarMediator
= do_QueryFrame(target
);
551 if (scrollbarMediator
) {
552 nsIFrame
* targetFrame
= do_QueryFrame(target
);
553 *scrollTarget
= targetFrame
;
554 scrollbarMediator
->ScrollbarActivityStarted();
560 void ScrollbarsForWheel::DeactivateAllTemporarilyActivatedScrollTargets() {
561 for (size_t i
= 0; i
< kNumberOfTargets
; i
++) {
562 AutoWeakFrame
* scrollTarget
= &sActivatedScrollTargets
[i
];
564 nsIScrollbarMediator
* scrollbarMediator
= do_QueryFrame(*scrollTarget
);
565 if (scrollbarMediator
) {
566 scrollbarMediator
->ScrollbarActivityStopped();
568 *scrollTarget
= nullptr;
573 /******************************************************************/
574 /* mozilla::WheelDeltaHorizontalizer */
575 /******************************************************************/
577 void WheelDeltaHorizontalizer::Horizontalize() {
578 MOZ_ASSERT(!mWheelEvent
.mDeltaValuesHorizontalizedForDefaultHandler
,
579 "Wheel delta values in one wheel scroll event are being adjusted "
582 // Log the old values.
583 mOldDeltaX
= mWheelEvent
.mDeltaX
;
584 mOldDeltaZ
= mWheelEvent
.mDeltaZ
;
585 mOldOverflowDeltaX
= mWheelEvent
.mOverflowDeltaX
;
586 mOldLineOrPageDeltaX
= mWheelEvent
.mLineOrPageDeltaX
;
588 // Move deltaY values to deltaX and set both deltaY and deltaZ to 0.
589 mWheelEvent
.mDeltaX
= mWheelEvent
.mDeltaY
;
590 mWheelEvent
.mDeltaY
= 0.0;
591 mWheelEvent
.mDeltaZ
= 0.0;
592 mWheelEvent
.mOverflowDeltaX
= mWheelEvent
.mOverflowDeltaY
;
593 mWheelEvent
.mOverflowDeltaY
= 0.0;
594 mWheelEvent
.mLineOrPageDeltaX
= mWheelEvent
.mLineOrPageDeltaY
;
595 mWheelEvent
.mLineOrPageDeltaY
= 0;
597 // Mark it horizontalized in order to restore the delta values when this
598 // instance is being destroyed.
599 mWheelEvent
.mDeltaValuesHorizontalizedForDefaultHandler
= true;
600 mHorizontalized
= true;
603 void WheelDeltaHorizontalizer::CancelHorizontalization() {
604 // Restore the horizontalized delta.
605 if (mHorizontalized
&&
606 mWheelEvent
.mDeltaValuesHorizontalizedForDefaultHandler
) {
607 mWheelEvent
.mDeltaY
= mWheelEvent
.mDeltaX
;
608 mWheelEvent
.mDeltaX
= mOldDeltaX
;
609 mWheelEvent
.mDeltaZ
= mOldDeltaZ
;
610 mWheelEvent
.mOverflowDeltaY
= mWheelEvent
.mOverflowDeltaX
;
611 mWheelEvent
.mOverflowDeltaX
= mOldOverflowDeltaX
;
612 mWheelEvent
.mLineOrPageDeltaY
= mWheelEvent
.mLineOrPageDeltaX
;
613 mWheelEvent
.mLineOrPageDeltaX
= mOldLineOrPageDeltaX
;
614 mWheelEvent
.mDeltaValuesHorizontalizedForDefaultHandler
= false;
615 mHorizontalized
= false;
619 WheelDeltaHorizontalizer::~WheelDeltaHorizontalizer() {
620 CancelHorizontalization();
623 /******************************************************************/
624 /* mozilla::AutoDirWheelDeltaAdjuster */
625 /******************************************************************/
627 bool AutoDirWheelDeltaAdjuster::ShouldBeAdjusted() {
628 // Sometimes, this function can be called more than one time. If we have
629 // already checked if the scroll should be adjusted, there's no need to check
631 if (mCheckedIfShouldBeAdjusted
) {
632 return mShouldBeAdjusted
;
634 mCheckedIfShouldBeAdjusted
= true;
636 // For an auto-dir wheel scroll, if all the following conditions are met, we
637 // should adjust X and Y values:
638 // 1. There is only one non-zero value between DeltaX and DeltaY.
639 // 2. There is only one direction for the target that overflows and is
640 // scrollable with wheel.
641 // 3. The direction described in Condition 1 is orthogonal to the one
642 // described in Condition 2.
643 if ((mDeltaX
&& mDeltaY
) || (!mDeltaX
&& !mDeltaY
)) {
647 if (CanScrollAlongXAxis()) {
650 if (IsHorizontalContentRightToLeft()) {
652 mDeltaX
> 0 ? CanScrollUpwards() : CanScrollDownwards();
655 mDeltaX
< 0 ? CanScrollUpwards() : CanScrollDownwards();
657 return mShouldBeAdjusted
;
659 MOZ_ASSERT(0 != mDeltaY
);
660 if (CanScrollAlongYAxis()) {
663 if (IsHorizontalContentRightToLeft()) {
665 mDeltaY
> 0 ? CanScrollLeftwards() : CanScrollRightwards();
668 mDeltaY
< 0 ? CanScrollLeftwards() : CanScrollRightwards();
670 return mShouldBeAdjusted
;
673 void AutoDirWheelDeltaAdjuster::Adjust() {
674 if (!ShouldBeAdjusted()) {
677 std::swap(mDeltaX
, mDeltaY
);
678 if (IsHorizontalContentRightToLeft()) {
682 mShouldBeAdjusted
= false;
686 /******************************************************************/
687 /* mozilla::ESMAutoDirWheelDeltaAdjuster */
688 /******************************************************************/
690 ESMAutoDirWheelDeltaAdjuster::ESMAutoDirWheelDeltaAdjuster(
691 WidgetWheelEvent
& aEvent
, nsIFrame
& aScrollFrame
, bool aHonoursRoot
)
692 : AutoDirWheelDeltaAdjuster(aEvent
.mDeltaX
, aEvent
.mDeltaY
),
693 mLineOrPageDeltaX(aEvent
.mLineOrPageDeltaX
),
694 mLineOrPageDeltaY(aEvent
.mLineOrPageDeltaY
),
695 mOverflowDeltaX(aEvent
.mOverflowDeltaX
),
696 mOverflowDeltaY(aEvent
.mOverflowDeltaY
) {
697 mScrollTargetFrame
= aScrollFrame
.GetScrollTargetFrame();
698 MOZ_ASSERT(mScrollTargetFrame
);
700 nsIFrame
* honouredFrame
= nullptr;
702 // If we are going to honour root, first try to get the frame for <body> as
703 // the honoured root, because <body> is in preference to <html> if the
704 // current document is an HTML document.
705 dom::Document
* document
= aScrollFrame
.PresShell()->GetDocument();
707 dom::Element
* bodyElement
= document
->GetBodyElement();
709 honouredFrame
= bodyElement
->GetPrimaryFrame();
713 if (!honouredFrame
) {
714 // If there is no <body> frame, fall back to the real root frame.
715 honouredFrame
= aScrollFrame
.PresShell()->GetRootScrollFrame();
718 if (!honouredFrame
) {
719 // If there is no root scroll frame, fall back to the current scrolling
721 honouredFrame
= &aScrollFrame
;
724 honouredFrame
= &aScrollFrame
;
727 WritingMode writingMode
= honouredFrame
->GetWritingMode();
728 WritingMode::BlockDir blockDir
= writingMode
.GetBlockDir();
729 WritingMode::InlineDir inlineDir
= writingMode
.GetInlineDir();
730 // Get whether the honoured frame's content in the horizontal direction starts
731 // from right to left(E.g. it's true either if "writing-mode: vertical-rl", or
732 // if "writing-mode: horizontal-tb; direction: rtl;" in CSS).
733 mIsHorizontalContentRightToLeft
=
734 (blockDir
== WritingMode::BlockDir::eBlockRL
||
735 (blockDir
== WritingMode::BlockDir::eBlockTB
&&
736 inlineDir
== WritingMode::InlineDir::eInlineRTL
));
739 void ESMAutoDirWheelDeltaAdjuster::OnAdjusted() {
740 // Adjust() only adjusted basic deltaX and deltaY, which are not enough for
741 // ESM, we should continue to adjust line-or-page and overflow values.
743 // A vertical scroll was adjusted to be horizontal.
744 MOZ_ASSERT(0 == mDeltaY
);
746 mLineOrPageDeltaX
= mLineOrPageDeltaY
;
747 mLineOrPageDeltaY
= 0;
748 mOverflowDeltaX
= mOverflowDeltaY
;
751 // A horizontal scroll was adjusted to be vertical.
752 MOZ_ASSERT(0 != mDeltaY
);
754 mLineOrPageDeltaY
= mLineOrPageDeltaX
;
755 mLineOrPageDeltaX
= 0;
756 mOverflowDeltaY
= mOverflowDeltaX
;
759 if (mIsHorizontalContentRightToLeft
) {
760 // If in RTL writing mode, reverse the side the scroll will go towards.
761 mLineOrPageDeltaX
*= -1;
762 mLineOrPageDeltaY
*= -1;
763 mOverflowDeltaX
*= -1;
764 mOverflowDeltaY
*= -1;
768 bool ESMAutoDirWheelDeltaAdjuster::CanScrollAlongXAxis() const {
769 return mScrollTargetFrame
->GetAvailableScrollingDirections().contains(
770 layers::ScrollDirection::eHorizontal
);
773 bool ESMAutoDirWheelDeltaAdjuster::CanScrollAlongYAxis() const {
774 return mScrollTargetFrame
->GetAvailableScrollingDirections().contains(
775 layers::ScrollDirection::eVertical
);
778 bool ESMAutoDirWheelDeltaAdjuster::CanScrollUpwards() const {
779 nsPoint scrollPt
= mScrollTargetFrame
->GetScrollPosition();
780 nsRect scrollRange
= mScrollTargetFrame
->GetScrollRange();
781 return static_cast<double>(scrollRange
.y
) < scrollPt
.y
;
784 bool ESMAutoDirWheelDeltaAdjuster::CanScrollDownwards() const {
785 nsPoint scrollPt
= mScrollTargetFrame
->GetScrollPosition();
786 nsRect scrollRange
= mScrollTargetFrame
->GetScrollRange();
787 return static_cast<double>(scrollRange
.YMost()) > scrollPt
.y
;
790 bool ESMAutoDirWheelDeltaAdjuster::CanScrollLeftwards() const {
791 nsPoint scrollPt
= mScrollTargetFrame
->GetScrollPosition();
792 nsRect scrollRange
= mScrollTargetFrame
->GetScrollRange();
793 return static_cast<double>(scrollRange
.x
) < scrollPt
.x
;
796 bool ESMAutoDirWheelDeltaAdjuster::CanScrollRightwards() const {
797 nsPoint scrollPt
= mScrollTargetFrame
->GetScrollPosition();
798 nsRect scrollRange
= mScrollTargetFrame
->GetScrollRange();
799 return static_cast<double>(scrollRange
.XMost()) > scrollPt
.x
;
802 bool ESMAutoDirWheelDeltaAdjuster::IsHorizontalContentRightToLeft() const {
803 return mIsHorizontalContentRightToLeft
;
806 /******************************************************************/
807 /* mozilla::ESMAutoDirWheelDeltaRestorer */
808 /******************************************************************/
811 ESMAutoDirWheelDeltaRestorer::ESMAutoDirWheelDeltaRestorer(
812 WidgetWheelEvent
& aEvent
)
814 mOldDeltaX(aEvent
.mDeltaX
),
815 mOldDeltaY(aEvent
.mDeltaY
),
816 mOldLineOrPageDeltaX(aEvent
.mLineOrPageDeltaX
),
817 mOldLineOrPageDeltaY(aEvent
.mLineOrPageDeltaY
),
818 mOldOverflowDeltaX(aEvent
.mOverflowDeltaX
),
819 mOldOverflowDeltaY(aEvent
.mOverflowDeltaY
) {}
821 ESMAutoDirWheelDeltaRestorer::~ESMAutoDirWheelDeltaRestorer() {
822 if (mOldDeltaX
== mEvent
.mDeltaX
|| mOldDeltaY
== mEvent
.mDeltaY
) {
823 // The delta of the event wasn't adjusted during the lifetime of this
824 // |ESMAutoDirWheelDeltaRestorer| instance. No need to restore it.
830 // First, restore the basic deltaX and deltaY.
831 std::swap(mEvent
.mDeltaX
, mEvent
.mDeltaY
);
832 if (mOldDeltaX
!= mEvent
.mDeltaX
|| mOldDeltaY
!= mEvent
.mDeltaY
) {
833 // If X and Y still don't equal to their original values after being
834 // swapped, then it must be because they were adjusted for RTL.
836 mEvent
.mDeltaX
*= -1;
837 mEvent
.mDeltaY
*= -1;
838 MOZ_ASSERT(mOldDeltaX
== mEvent
.mDeltaX
&& mOldDeltaY
== mEvent
.mDeltaY
);
841 if (mEvent
.mDeltaX
) {
842 // A horizontal scroll was adjusted to be vertical during the lifetime of
844 MOZ_ASSERT(0 == mEvent
.mDeltaY
);
846 // Restore the line-or-page and overflow values to be horizontal.
847 mEvent
.mOverflowDeltaX
= mEvent
.mOverflowDeltaY
;
848 mEvent
.mLineOrPageDeltaX
= mEvent
.mLineOrPageDeltaY
;
850 mEvent
.mOverflowDeltaX
*= -1;
851 mEvent
.mLineOrPageDeltaX
*= -1;
853 mEvent
.mOverflowDeltaY
= mOldOverflowDeltaY
;
854 mEvent
.mLineOrPageDeltaY
= mOldLineOrPageDeltaY
;
856 // A vertical scroll was adjusted to be horizontal during the lifetime of
858 MOZ_ASSERT(0 != mEvent
.mDeltaY
);
860 // Restore the line-or-page and overflow values to be vertical.
861 mEvent
.mOverflowDeltaY
= mEvent
.mOverflowDeltaX
;
862 mEvent
.mLineOrPageDeltaY
= mEvent
.mLineOrPageDeltaX
;
864 mEvent
.mOverflowDeltaY
*= -1;
865 mEvent
.mLineOrPageDeltaY
*= -1;
867 mEvent
.mOverflowDeltaX
= mOldOverflowDeltaX
;
868 mEvent
.mLineOrPageDeltaX
= mOldLineOrPageDeltaX
;
872 } // namespace mozilla