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 "nsPluginFrame.h"
29 #include "nsPresContext.h"
32 #include "ScrollAnimationPhysics.h"
36 /******************************************************************/
37 /* mozilla::DeltaValues */
38 /******************************************************************/
40 DeltaValues::DeltaValues(WidgetWheelEvent
* aEvent
)
41 : deltaX(aEvent
->mDeltaX
), deltaY(aEvent
->mDeltaY
) {}
43 /******************************************************************/
44 /* mozilla::WheelHandlingUtils */
45 /******************************************************************/
48 bool WheelHandlingUtils::CanScrollInRange(nscoord aMin
, nscoord aValue
,
49 nscoord aMax
, double aDirection
) {
50 return aDirection
> 0.0 ? aValue
< static_cast<double>(aMax
)
51 : static_cast<double>(aMin
) < aValue
;
55 bool WheelHandlingUtils::CanScrollOn(nsIFrame
* aFrame
, double aDirectionX
,
57 nsIScrollableFrame
* scrollableFrame
= do_QueryFrame(aFrame
);
58 if (scrollableFrame
) {
59 return CanScrollOn(scrollableFrame
, aDirectionX
, aDirectionY
);
61 nsPluginFrame
* pluginFrame
= do_QueryFrame(aFrame
);
62 return pluginFrame
&& pluginFrame
->WantsToHandleWheelEventAsDefaultAction();
66 bool WheelHandlingUtils::CanScrollOn(nsIScrollableFrame
* aScrollFrame
,
67 double aDirectionX
, double aDirectionY
) {
68 MOZ_ASSERT(aScrollFrame
);
69 NS_ASSERTION(aDirectionX
|| aDirectionY
,
70 "One of the delta values must be non-zero at least");
72 nsPoint scrollPt
= aScrollFrame
->GetVisualViewportOffset();
73 nsRect scrollRange
= aScrollFrame
->GetScrollRangeForUserInputEvents();
74 layers::ScrollDirections directions
=
75 aScrollFrame
->GetAvailableScrollingDirectionsForUserInputEvents();
77 return ((aDirectionX
!= 0.0) &&
78 (directions
.contains(layers::ScrollDirection::eHorizontal
)) &&
79 CanScrollInRange(scrollRange
.x
, scrollPt
.x
, scrollRange
.XMost(),
81 ((aDirectionY
!= 0.0) &&
82 (directions
.contains(layers::ScrollDirection::eVertical
)) &&
83 CanScrollInRange(scrollRange
.y
, scrollPt
.y
, scrollRange
.YMost(),
87 /*static*/ Maybe
<layers::ScrollDirection
>
88 WheelHandlingUtils::GetDisregardedWheelScrollDirection(const nsIFrame
* aFrame
) {
89 nsIContent
* content
= aFrame
->GetContent();
93 TextControlElement
* textControlElement
= TextControlElement::FromNodeOrNull(
94 content
->IsInNativeAnonymousSubtree()
95 ? content
->GetClosestNativeAnonymousSubtreeRootParent()
97 if (!textControlElement
|| !textControlElement
->IsSingleLineTextControl()) {
100 // Disregard scroll in the block-flow direction by mouse wheel on a
101 // single-line text control. For instance, in tranditional Chinese writing
102 // system, a single-line text control cannot be scrolled horizontally with
103 // mouse wheel even if they overflow at the right and left edges; Whereas in
104 // latin-based writing system, a single-line text control cannot be scrolled
105 // vertically with mouse wheel even if they overflow at the top and bottom
107 return Some(aFrame
->GetWritingMode().IsVertical()
108 ? layers::ScrollDirection::eHorizontal
109 : layers::ScrollDirection::eVertical
);
112 /******************************************************************/
113 /* mozilla::WheelTransaction */
114 /******************************************************************/
116 AutoWeakFrame
WheelTransaction::sTargetFrame(nullptr);
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
* aTargetFrame
,
134 const WidgetWheelEvent
* aEvent
) {
135 NS_ASSERTION(!sTargetFrame
, "previous transaction is not finished!");
136 MOZ_ASSERT(aEvent
->mMessage
== eWheel
,
137 "Transaction must be started with a wheel event");
138 ScrollbarsForWheel::OwnWheelTransaction(false);
139 sTargetFrame
= aTargetFrame
;
140 sScrollSeriesCounter
= 0;
141 if (!UpdateTransaction(aEvent
)) {
142 NS_ERROR("BeginTransaction is called even cannot scroll the frame");
148 bool WheelTransaction::UpdateTransaction(const WidgetWheelEvent
* aEvent
) {
149 nsIFrame
* scrollToFrame
= GetTargetFrame();
150 nsIScrollableFrame
* scrollableFrame
= scrollToFrame
->GetScrollTargetFrame();
151 if (scrollableFrame
) {
152 scrollToFrame
= do_QueryFrame(scrollableFrame
);
155 if (!WheelHandlingUtils::CanScrollOn(scrollToFrame
, aEvent
->mDeltaX
,
157 OnFailToScrollTarget();
158 // We should not modify the transaction state when the view will not be
159 // scrolled actually.
165 if (sScrollSeriesCounter
!= 0 && OutOfTime(sTime
, kScrollSeriesTimeoutMs
)) {
166 sScrollSeriesCounter
= 0;
168 sScrollSeriesCounter
++;
170 // We should use current time instead of WidgetEvent.time.
171 // 1. Some events doesn't have the correct creation time.
172 // 2. If the computer runs slowly by other processes eating the CPU resource,
173 // the event creation time doesn't keep real time.
174 sTime
= PR_IntervalToMilliseconds(PR_IntervalNow());
180 void WheelTransaction::MayEndTransaction() {
181 if (!sOwnScrollbars
&& ScrollbarsForWheel::IsActive()) {
182 ScrollbarsForWheel::OwnWheelTransaction(true);
189 void WheelTransaction::EndTransaction() {
193 sTargetFrame
= nullptr;
194 sScrollSeriesCounter
= 0;
195 if (sOwnScrollbars
) {
196 sOwnScrollbars
= false;
197 ScrollbarsForWheel::OwnWheelTransaction(false);
198 ScrollbarsForWheel::Inactivate();
203 bool WheelTransaction::WillHandleDefaultAction(
204 WidgetWheelEvent
* aWheelEvent
, AutoWeakFrame
& aTargetWeakFrame
) {
205 nsIFrame
* lastTargetFrame
= GetTargetFrame();
206 if (!lastTargetFrame
) {
207 BeginTransaction(aTargetWeakFrame
.GetFrame(), aWheelEvent
);
208 } else if (lastTargetFrame
!= aTargetWeakFrame
.GetFrame()) {
210 BeginTransaction(aTargetWeakFrame
.GetFrame(), aWheelEvent
);
212 UpdateTransaction(aWheelEvent
);
215 // When the wheel event will not be handled with any frames,
216 // UpdateTransaction() fires MozMouseScrollFailed event which is for
217 // automated testing. In the event handler, the target frame might be
218 // destroyed. Then, the caller shouldn't try to handle the default action.
219 if (!aTargetWeakFrame
.IsAlive()) {
228 void WheelTransaction::OnEvent(WidgetEvent
* aEvent
) {
233 if (OutOfTime(sTime
, StaticPrefs::mousewheel_transaction_timeout())) {
234 // Even if the scroll event which is handled after timeout, but onTimeout
235 // was not fired by timer, then the scroll event will scroll old frame,
236 // therefore, we should call OnTimeout here and ensure to finish the old
238 OnTimeout(nullptr, nullptr);
242 switch (aEvent
->mMessage
) {
244 if (sMouseMoved
!= 0 &&
245 OutOfTime(sMouseMoved
,
246 StaticPrefs::mousewheel_transaction_ignoremovedelay())) {
247 // Terminate the current mousewheel transaction if the mouse moved more
248 // than ignoremovedelay milliseconds ago
254 WidgetMouseEvent
* mouseEvent
= aEvent
->AsMouseEvent();
255 if (mouseEvent
->IsReal()) {
256 // If the cursor is moving to be outside the frame,
257 // terminate the scrollwheel transaction.
258 LayoutDeviceIntPoint pt
= GetScreenPoint(mouseEvent
);
259 auto r
= LayoutDeviceIntRect::FromAppUnitsToNearest(
260 sTargetFrame
->GetScreenRectInAppUnits(),
261 sTargetFrame
->PresContext()->AppUnitsPerDevPixel());
262 if (!r
.Contains(pt
)) {
267 // If the cursor is moving inside the frame, and it is less than
268 // ignoremovedelay milliseconds since the last scroll operation, ignore
269 // the mouse move; otherwise, record the current mouse move time to be
273 StaticPrefs::mousewheel_transaction_ignoremovedelay())) {
274 sMouseMoved
= PR_IntervalToMilliseconds(PR_IntervalNow());
284 case eMouseDoubleClick
:
297 void WheelTransaction::Shutdown() { NS_IF_RELEASE(sTimer
); }
300 void WheelTransaction::OnFailToScrollTarget() {
301 MOZ_ASSERT(sTargetFrame
, "We don't have mouse scrolling transaction");
303 if (StaticPrefs::test_mousescroll()) {
304 // This event is used for automated tests, see bug 442774.
305 nsContentUtils::DispatchEventOnlyToChrome(
306 sTargetFrame
->GetContent()->OwnerDoc(), sTargetFrame
->GetContent(),
307 u
"MozMouseScrollFailed"_ns
, CanBubble::eYes
, Cancelable::eYes
);
309 // The target frame might be destroyed in the event handler, at that time,
310 // we need to finish the current transaction
317 void WheelTransaction::OnTimeout(nsITimer
* aTimer
, void* aClosure
) {
319 // The transaction target was destroyed already
323 // Store the sTargetFrame, the variable becomes null in EndTransaction.
324 nsIFrame
* frame
= sTargetFrame
;
325 // We need to finish current transaction before DOM event firing. Because
326 // the next DOM event might create strange situation for us.
329 if (StaticPrefs::test_mousescroll()) {
330 // This event is used for automated tests, see bug 442774.
331 nsContentUtils::DispatchEventOnlyToChrome(
332 frame
->GetContent()->OwnerDoc(), frame
->GetContent(),
333 u
"MozMouseScrollTransactionTimeout"_ns
, CanBubble::eYes
,
339 void WheelTransaction::SetTimeout() {
341 sTimer
= NS_NewTimer().take();
347 DebugOnly
<nsresult
> rv
= sTimer
->InitWithNamedFuncCallback(
348 OnTimeout
, nullptr, StaticPrefs::mousewheel_transaction_timeout(),
349 nsITimer::TYPE_ONE_SHOT
, "WheelTransaction::SetTimeout");
350 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
351 "nsITimer::InitWithFuncCallback failed");
355 LayoutDeviceIntPoint
WheelTransaction::GetScreenPoint(WidgetGUIEvent
* aEvent
) {
356 NS_ASSERTION(aEvent
, "aEvent is null");
357 NS_ASSERTION(aEvent
->mWidget
, "aEvent-mWidget is null");
358 return aEvent
->mRefPoint
+ aEvent
->mWidget
->WidgetToScreenOffset();
362 DeltaValues
WheelTransaction::AccelerateWheelDelta(
363 WidgetWheelEvent
* aEvent
, bool aAllowScrollSpeedOverride
) {
364 DeltaValues
result(aEvent
);
366 // Don't accelerate the delta values if the event isn't line scrolling.
367 if (aEvent
->mDeltaMode
!= dom::WheelEvent_Binding::DOM_DELTA_LINE
) {
371 if (aAllowScrollSpeedOverride
) {
372 result
= OverrideSystemScrollSpeed(aEvent
);
375 // Accelerate by the sScrollSeriesCounter
376 int32_t start
= StaticPrefs::mousewheel_acceleration_start();
377 if (start
>= 0 && sScrollSeriesCounter
>= start
) {
378 int32_t factor
= StaticPrefs::mousewheel_acceleration_factor();
380 result
.deltaX
= ComputeAcceleratedWheelDelta(result
.deltaX
, factor
);
381 result
.deltaY
= ComputeAcceleratedWheelDelta(result
.deltaY
, factor
);
389 double WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta
,
391 return mozilla::ComputeAcceleratedWheelDelta(aDelta
, sScrollSeriesCounter
,
396 DeltaValues
WheelTransaction::OverrideSystemScrollSpeed(
397 WidgetWheelEvent
* aEvent
) {
398 MOZ_ASSERT(sTargetFrame
, "We don't have mouse scrolling transaction");
399 MOZ_ASSERT(aEvent
->mDeltaMode
== dom::WheelEvent_Binding::DOM_DELTA_LINE
);
401 // If the event doesn't scroll to both X and Y, we don't need to do anything
403 if (!aEvent
->mDeltaX
&& !aEvent
->mDeltaY
) {
404 return DeltaValues(aEvent
);
407 return DeltaValues(aEvent
->OverriddenDeltaX(), aEvent
->OverriddenDeltaY());
410 /******************************************************************/
411 /* mozilla::ScrollbarsForWheel */
412 /******************************************************************/
414 const DeltaValues
ScrollbarsForWheel::directions
[kNumberOfTargets
] = {
415 DeltaValues(-1, 0), DeltaValues(+1, 0), DeltaValues(0, -1),
418 AutoWeakFrame
ScrollbarsForWheel::sActiveOwner
= nullptr;
419 AutoWeakFrame
ScrollbarsForWheel::sActivatedScrollTargets
[kNumberOfTargets
] = {
420 nullptr, nullptr, nullptr, nullptr};
422 bool ScrollbarsForWheel::sHadWheelStart
= false;
423 bool ScrollbarsForWheel::sOwnWheelTransaction
= false;
426 void ScrollbarsForWheel::PrepareToScrollText(EventStateManager
* aESM
,
427 nsIFrame
* aTargetFrame
,
428 WidgetWheelEvent
* aEvent
) {
429 if (aEvent
->mMessage
== eWheelOperationStart
) {
430 WheelTransaction::OwnScrollbars(false);
432 TemporarilyActivateAllPossibleScrollTargets(aESM
, aTargetFrame
, aEvent
);
433 sHadWheelStart
= true;
436 DeactivateAllTemporarilyActivatedScrollTargets();
441 void ScrollbarsForWheel::SetActiveScrollTarget(
442 nsIScrollableFrame
* aScrollTarget
) {
443 if (!sHadWheelStart
) {
446 nsIScrollbarMediator
* scrollbarMediator
= do_QueryFrame(aScrollTarget
);
447 if (!scrollbarMediator
) {
450 sHadWheelStart
= false;
451 sActiveOwner
= do_QueryFrame(aScrollTarget
);
452 scrollbarMediator
->ScrollbarActivityStarted();
456 void ScrollbarsForWheel::MayInactivate() {
457 if (!sOwnWheelTransaction
&& WheelTransaction::GetTargetFrame()) {
458 WheelTransaction::OwnScrollbars(true);
465 void ScrollbarsForWheel::Inactivate() {
466 nsIScrollbarMediator
* scrollbarMediator
= do_QueryFrame(sActiveOwner
);
467 if (scrollbarMediator
) {
468 scrollbarMediator
->ScrollbarActivityStopped();
470 sActiveOwner
= nullptr;
471 DeactivateAllTemporarilyActivatedScrollTargets();
472 if (sOwnWheelTransaction
) {
473 sOwnWheelTransaction
= false;
474 WheelTransaction::OwnScrollbars(false);
475 WheelTransaction::EndTransaction();
480 bool ScrollbarsForWheel::IsActive() {
484 for (size_t i
= 0; i
< kNumberOfTargets
; ++i
) {
485 if (sActivatedScrollTargets
[i
]) {
493 void ScrollbarsForWheel::OwnWheelTransaction(bool aOwn
) {
494 sOwnWheelTransaction
= aOwn
;
498 void ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets(
499 EventStateManager
* aESM
, nsIFrame
* aTargetFrame
, WidgetWheelEvent
* aEvent
) {
500 for (size_t i
= 0; i
< kNumberOfTargets
; i
++) {
501 const DeltaValues
* dir
= &directions
[i
];
502 AutoWeakFrame
* scrollTarget
= &sActivatedScrollTargets
[i
];
503 MOZ_ASSERT(!*scrollTarget
, "scroll target still temporarily activated!");
504 nsIScrollableFrame
* target
= do_QueryFrame(aESM
->ComputeScrollTarget(
505 aTargetFrame
, dir
->deltaX
, dir
->deltaY
, aEvent
,
506 EventStateManager::COMPUTE_DEFAULT_ACTION_TARGET
));
507 nsIScrollbarMediator
* scrollbarMediator
= do_QueryFrame(target
);
508 if (scrollbarMediator
) {
509 nsIFrame
* targetFrame
= do_QueryFrame(target
);
510 *scrollTarget
= targetFrame
;
511 scrollbarMediator
->ScrollbarActivityStarted();
517 void ScrollbarsForWheel::DeactivateAllTemporarilyActivatedScrollTargets() {
518 for (size_t i
= 0; i
< kNumberOfTargets
; i
++) {
519 AutoWeakFrame
* scrollTarget
= &sActivatedScrollTargets
[i
];
521 nsIScrollbarMediator
* scrollbarMediator
= do_QueryFrame(*scrollTarget
);
522 if (scrollbarMediator
) {
523 scrollbarMediator
->ScrollbarActivityStopped();
525 *scrollTarget
= nullptr;
530 /******************************************************************/
531 /* mozilla::WheelDeltaHorizontalizer */
532 /******************************************************************/
534 void WheelDeltaHorizontalizer::Horizontalize() {
535 MOZ_ASSERT(!mWheelEvent
.mDeltaValuesHorizontalizedForDefaultHandler
,
536 "Wheel delta values in one wheel scroll event are being adjusted "
539 // Log the old values.
540 mOldDeltaX
= mWheelEvent
.mDeltaX
;
541 mOldDeltaZ
= mWheelEvent
.mDeltaZ
;
542 mOldOverflowDeltaX
= mWheelEvent
.mOverflowDeltaX
;
543 mOldLineOrPageDeltaX
= mWheelEvent
.mLineOrPageDeltaX
;
545 // Move deltaY values to deltaX and set both deltaY and deltaZ to 0.
546 mWheelEvent
.mDeltaX
= mWheelEvent
.mDeltaY
;
547 mWheelEvent
.mDeltaY
= 0.0;
548 mWheelEvent
.mDeltaZ
= 0.0;
549 mWheelEvent
.mOverflowDeltaX
= mWheelEvent
.mOverflowDeltaY
;
550 mWheelEvent
.mOverflowDeltaY
= 0.0;
551 mWheelEvent
.mLineOrPageDeltaX
= mWheelEvent
.mLineOrPageDeltaY
;
552 mWheelEvent
.mLineOrPageDeltaY
= 0;
554 // Mark it horizontalized in order to restore the delta values when this
555 // instance is being destroyed.
556 mWheelEvent
.mDeltaValuesHorizontalizedForDefaultHandler
= true;
557 mHorizontalized
= true;
560 void WheelDeltaHorizontalizer::CancelHorizontalization() {
561 // Restore the horizontalized delta.
562 if (mHorizontalized
&&
563 mWheelEvent
.mDeltaValuesHorizontalizedForDefaultHandler
) {
564 mWheelEvent
.mDeltaY
= mWheelEvent
.mDeltaX
;
565 mWheelEvent
.mDeltaX
= mOldDeltaX
;
566 mWheelEvent
.mDeltaZ
= mOldDeltaZ
;
567 mWheelEvent
.mOverflowDeltaY
= mWheelEvent
.mOverflowDeltaX
;
568 mWheelEvent
.mOverflowDeltaX
= mOldOverflowDeltaX
;
569 mWheelEvent
.mLineOrPageDeltaY
= mWheelEvent
.mLineOrPageDeltaX
;
570 mWheelEvent
.mLineOrPageDeltaX
= mOldLineOrPageDeltaX
;
571 mWheelEvent
.mDeltaValuesHorizontalizedForDefaultHandler
= false;
572 mHorizontalized
= false;
576 WheelDeltaHorizontalizer::~WheelDeltaHorizontalizer() {
577 CancelHorizontalization();
580 /******************************************************************/
581 /* mozilla::AutoDirWheelDeltaAdjuster */
582 /******************************************************************/
584 bool AutoDirWheelDeltaAdjuster::ShouldBeAdjusted() {
585 // Sometimes, this function can be called more than one time. If we have
586 // already checked if the scroll should be adjusted, there's no need to check
588 if (mCheckedIfShouldBeAdjusted
) {
589 return mShouldBeAdjusted
;
591 mCheckedIfShouldBeAdjusted
= true;
593 // For an auto-dir wheel scroll, if all the following conditions are met, we
594 // should adjust X and Y values:
595 // 1. There is only one non-zero value between DeltaX and DeltaY.
596 // 2. There is only one direction for the target that overflows and is
597 // scrollable with wheel.
598 // 3. The direction described in Condition 1 is orthogonal to the one
599 // described in Condition 2.
600 if ((mDeltaX
&& mDeltaY
) || (!mDeltaX
&& !mDeltaY
)) {
604 if (CanScrollAlongXAxis()) {
607 if (IsHorizontalContentRightToLeft()) {
609 mDeltaX
> 0 ? CanScrollUpwards() : CanScrollDownwards();
612 mDeltaX
< 0 ? CanScrollUpwards() : CanScrollDownwards();
614 return mShouldBeAdjusted
;
616 MOZ_ASSERT(0 != mDeltaY
);
617 if (CanScrollAlongYAxis()) {
620 if (IsHorizontalContentRightToLeft()) {
622 mDeltaY
> 0 ? CanScrollLeftwards() : CanScrollRightwards();
625 mDeltaY
< 0 ? CanScrollLeftwards() : CanScrollRightwards();
627 return mShouldBeAdjusted
;
630 void AutoDirWheelDeltaAdjuster::Adjust() {
631 if (!ShouldBeAdjusted()) {
634 std::swap(mDeltaX
, mDeltaY
);
635 if (IsHorizontalContentRightToLeft()) {
639 mShouldBeAdjusted
= false;
643 /******************************************************************/
644 /* mozilla::ESMAutoDirWheelDeltaAdjuster */
645 /******************************************************************/
647 ESMAutoDirWheelDeltaAdjuster::ESMAutoDirWheelDeltaAdjuster(
648 WidgetWheelEvent
& aEvent
, nsIFrame
& aScrollFrame
, bool aHonoursRoot
)
649 : AutoDirWheelDeltaAdjuster(aEvent
.mDeltaX
, aEvent
.mDeltaY
),
650 mLineOrPageDeltaX(aEvent
.mLineOrPageDeltaX
),
651 mLineOrPageDeltaY(aEvent
.mLineOrPageDeltaY
),
652 mOverflowDeltaX(aEvent
.mOverflowDeltaX
),
653 mOverflowDeltaY(aEvent
.mOverflowDeltaY
) {
654 mScrollTargetFrame
= aScrollFrame
.GetScrollTargetFrame();
655 MOZ_ASSERT(mScrollTargetFrame
);
657 nsIFrame
* honouredFrame
= nullptr;
659 // If we are going to honour root, first try to get the frame for <body> as
660 // the honoured root, because <body> is in preference to <html> if the
661 // current document is an HTML document.
662 dom::Document
* document
= aScrollFrame
.PresShell()->GetDocument();
664 dom::Element
* bodyElement
= document
->GetBodyElement();
666 honouredFrame
= bodyElement
->GetPrimaryFrame();
670 if (!honouredFrame
) {
671 // If there is no <body> frame, fall back to the real root frame.
672 honouredFrame
= aScrollFrame
.PresShell()->GetRootScrollFrame();
675 if (!honouredFrame
) {
676 // If there is no root scroll frame, fall back to the current scrolling
678 honouredFrame
= &aScrollFrame
;
681 honouredFrame
= &aScrollFrame
;
684 WritingMode writingMode
= honouredFrame
->GetWritingMode();
685 WritingMode::BlockDir blockDir
= writingMode
.GetBlockDir();
686 WritingMode::InlineDir inlineDir
= writingMode
.GetInlineDir();
687 // Get whether the honoured frame's content in the horizontal direction starts
688 // from right to left(E.g. it's true either if "writing-mode: vertical-rl", or
689 // if "writing-mode: horizontal-tb; direction: rtl;" in CSS).
690 mIsHorizontalContentRightToLeft
=
691 (blockDir
== WritingMode::BlockDir::eBlockRL
||
692 (blockDir
== WritingMode::BlockDir::eBlockTB
&&
693 inlineDir
== WritingMode::InlineDir::eInlineRTL
));
696 void ESMAutoDirWheelDeltaAdjuster::OnAdjusted() {
697 // Adjust() only adjusted basic deltaX and deltaY, which are not enough for
698 // ESM, we should continue to adjust line-or-page and overflow values.
700 // A vertical scroll was adjusted to be horizontal.
701 MOZ_ASSERT(0 == mDeltaY
);
703 mLineOrPageDeltaX
= mLineOrPageDeltaY
;
704 mLineOrPageDeltaY
= 0;
705 mOverflowDeltaX
= mOverflowDeltaY
;
708 // A horizontal scroll was adjusted to be vertical.
709 MOZ_ASSERT(0 != mDeltaY
);
711 mLineOrPageDeltaY
= mLineOrPageDeltaX
;
712 mLineOrPageDeltaX
= 0;
713 mOverflowDeltaY
= mOverflowDeltaX
;
716 if (mIsHorizontalContentRightToLeft
) {
717 // If in RTL writing mode, reverse the side the scroll will go towards.
718 mLineOrPageDeltaX
*= -1;
719 mLineOrPageDeltaY
*= -1;
720 mOverflowDeltaX
*= -1;
721 mOverflowDeltaY
*= -1;
725 bool ESMAutoDirWheelDeltaAdjuster::CanScrollAlongXAxis() const {
726 return mScrollTargetFrame
->GetAvailableScrollingDirections().contains(
727 layers::ScrollDirection::eHorizontal
);
730 bool ESMAutoDirWheelDeltaAdjuster::CanScrollAlongYAxis() const {
731 return mScrollTargetFrame
->GetAvailableScrollingDirections().contains(
732 layers::ScrollDirection::eVertical
);
735 bool ESMAutoDirWheelDeltaAdjuster::CanScrollUpwards() const {
736 nsPoint scrollPt
= mScrollTargetFrame
->GetScrollPosition();
737 nsRect scrollRange
= mScrollTargetFrame
->GetScrollRange();
738 return static_cast<double>(scrollRange
.y
) < scrollPt
.y
;
741 bool ESMAutoDirWheelDeltaAdjuster::CanScrollDownwards() const {
742 nsPoint scrollPt
= mScrollTargetFrame
->GetScrollPosition();
743 nsRect scrollRange
= mScrollTargetFrame
->GetScrollRange();
744 return static_cast<double>(scrollRange
.YMost()) > scrollPt
.y
;
747 bool ESMAutoDirWheelDeltaAdjuster::CanScrollLeftwards() const {
748 nsPoint scrollPt
= mScrollTargetFrame
->GetScrollPosition();
749 nsRect scrollRange
= mScrollTargetFrame
->GetScrollRange();
750 return static_cast<double>(scrollRange
.x
) < scrollPt
.x
;
753 bool ESMAutoDirWheelDeltaAdjuster::CanScrollRightwards() const {
754 nsPoint scrollPt
= mScrollTargetFrame
->GetScrollPosition();
755 nsRect scrollRange
= mScrollTargetFrame
->GetScrollRange();
756 return static_cast<double>(scrollRange
.XMost()) > scrollPt
.x
;
759 bool ESMAutoDirWheelDeltaAdjuster::IsHorizontalContentRightToLeft() const {
760 return mIsHorizontalContentRightToLeft
;
763 /******************************************************************/
764 /* mozilla::ESMAutoDirWheelDeltaRestorer */
765 /******************************************************************/
768 ESMAutoDirWheelDeltaRestorer::ESMAutoDirWheelDeltaRestorer(
769 WidgetWheelEvent
& aEvent
)
771 mOldDeltaX(aEvent
.mDeltaX
),
772 mOldDeltaY(aEvent
.mDeltaY
),
773 mOldLineOrPageDeltaX(aEvent
.mLineOrPageDeltaX
),
774 mOldLineOrPageDeltaY(aEvent
.mLineOrPageDeltaY
),
775 mOldOverflowDeltaX(aEvent
.mOverflowDeltaX
),
776 mOldOverflowDeltaY(aEvent
.mOverflowDeltaY
) {}
778 ESMAutoDirWheelDeltaRestorer::~ESMAutoDirWheelDeltaRestorer() {
779 if (mOldDeltaX
== mEvent
.mDeltaX
|| mOldDeltaY
== mEvent
.mDeltaY
) {
780 // The delta of the event wasn't adjusted during the lifetime of this
781 // |ESMAutoDirWheelDeltaRestorer| instance. No need to restore it.
787 // First, restore the basic deltaX and deltaY.
788 std::swap(mEvent
.mDeltaX
, mEvent
.mDeltaY
);
789 if (mOldDeltaX
!= mEvent
.mDeltaX
|| mOldDeltaY
!= mEvent
.mDeltaY
) {
790 // If X and Y still don't equal to their original values after being
791 // swapped, then it must be because they were adjusted for RTL.
793 mEvent
.mDeltaX
*= -1;
794 mEvent
.mDeltaY
*= -1;
795 MOZ_ASSERT(mOldDeltaX
== mEvent
.mDeltaX
&& mOldDeltaY
== mEvent
.mDeltaY
);
798 if (mEvent
.mDeltaX
) {
799 // A horizontal scroll was adjusted to be vertical during the lifetime of
801 MOZ_ASSERT(0 == mEvent
.mDeltaY
);
803 // Restore the line-or-page and overflow values to be horizontal.
804 mEvent
.mOverflowDeltaX
= mEvent
.mOverflowDeltaY
;
805 mEvent
.mLineOrPageDeltaX
= mEvent
.mLineOrPageDeltaY
;
807 mEvent
.mOverflowDeltaX
*= -1;
808 mEvent
.mLineOrPageDeltaX
*= -1;
810 mEvent
.mOverflowDeltaY
= mOldOverflowDeltaY
;
811 mEvent
.mLineOrPageDeltaY
= mOldLineOrPageDeltaY
;
813 // A vertical scroll was adjusted to be horizontal during the lifetime of
815 MOZ_ASSERT(0 != mEvent
.mDeltaY
);
817 // Restore the line-or-page and overflow values to be vertical.
818 mEvent
.mOverflowDeltaY
= mEvent
.mOverflowDeltaX
;
819 mEvent
.mLineOrPageDeltaY
= mEvent
.mLineOrPageDeltaX
;
821 mEvent
.mOverflowDeltaY
*= -1;
822 mEvent
.mLineOrPageDeltaY
*= -1;
824 mEvent
.mOverflowDeltaX
= mOldOverflowDeltaX
;
825 mEvent
.mLineOrPageDeltaX
= mOldLineOrPageDeltaX
;
829 } // namespace mozilla