Bug 1686668 [wpt PR 27185] - Update wpt metadata, a=testonly
[gecko.git] / dom / events / WheelHandlingHelper.cpp
blob6f7a24fb551a98689f7fe8b2ff9a9cf95af8acea
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"
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 "nsPluginFrame.h"
29 #include "nsPresContext.h"
30 #include "prtime.h"
31 #include "Units.h"
32 #include "ScrollAnimationPhysics.h"
34 namespace mozilla {
36 /******************************************************************/
37 /* mozilla::DeltaValues */
38 /******************************************************************/
40 DeltaValues::DeltaValues(WidgetWheelEvent* aEvent)
41 : deltaX(aEvent->mDeltaX), deltaY(aEvent->mDeltaY) {}
43 /******************************************************************/
44 /* mozilla::WheelHandlingUtils */
45 /******************************************************************/
47 /* static */
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;
54 /* static */
55 bool WheelHandlingUtils::CanScrollOn(nsIFrame* aFrame, double aDirectionX,
56 double aDirectionY) {
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();
65 /* static */
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(),
80 aDirectionX)) ||
81 ((aDirectionY != 0.0) &&
82 (directions.contains(layers::ScrollDirection::eVertical)) &&
83 CanScrollInRange(scrollRange.y, scrollPt.y, scrollRange.YMost(),
84 aDirectionY));
87 /*static*/ Maybe<layers::ScrollDirection>
88 WheelHandlingUtils::GetDisregardedWheelScrollDirection(const nsIFrame* aFrame) {
89 nsIContent* content = aFrame->GetContent();
90 if (!content) {
91 return Nothing();
93 TextControlElement* textControlElement = TextControlElement::FromNodeOrNull(
94 content->IsInNativeAnonymousSubtree()
95 ? content->GetClosestNativeAnonymousSubtreeRootParent()
96 : content);
97 if (!textControlElement || !textControlElement->IsSingleLineTextControl()) {
98 return Nothing();
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
106 // edges
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;
123 /* static */
124 bool WheelTransaction::OutOfTime(uint32_t aBaseTime, uint32_t aThreshold) {
125 uint32_t now = PR_IntervalToMilliseconds(PR_IntervalNow());
126 return (now - aBaseTime > aThreshold);
129 /* static */
130 void WheelTransaction::OwnScrollbars(bool aOwn) { sOwnScrollbars = aOwn; }
132 /* static */
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");
143 EndTransaction();
147 /* static */
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,
156 aEvent->mDeltaY)) {
157 OnFailToScrollTarget();
158 // We should not modify the transaction state when the view will not be
159 // scrolled actually.
160 return false;
163 SetTimeout();
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());
175 sMouseMoved = 0;
176 return true;
179 /* static */
180 void WheelTransaction::MayEndTransaction() {
181 if (!sOwnScrollbars && ScrollbarsForWheel::IsActive()) {
182 ScrollbarsForWheel::OwnWheelTransaction(true);
183 } else {
184 EndTransaction();
188 /* static */
189 void WheelTransaction::EndTransaction() {
190 if (sTimer) {
191 sTimer->Cancel();
193 sTargetFrame = nullptr;
194 sScrollSeriesCounter = 0;
195 if (sOwnScrollbars) {
196 sOwnScrollbars = false;
197 ScrollbarsForWheel::OwnWheelTransaction(false);
198 ScrollbarsForWheel::Inactivate();
202 /* static */
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()) {
209 EndTransaction();
210 BeginTransaction(aTargetWeakFrame.GetFrame(), aWheelEvent);
211 } else {
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()) {
220 EndTransaction();
221 return false;
224 return true;
227 /* static */
228 void WheelTransaction::OnEvent(WidgetEvent* aEvent) {
229 if (!sTargetFrame) {
230 return;
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
237 // transaction.
238 OnTimeout(nullptr, nullptr);
239 return;
242 switch (aEvent->mMessage) {
243 case eWheel:
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
249 EndTransaction();
251 return;
252 case eMouseMove:
253 case eDragOver: {
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)) {
263 EndTransaction();
264 return;
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
270 // checked later
271 if (!sMouseMoved &&
272 OutOfTime(sTime,
273 StaticPrefs::mousewheel_transaction_ignoremovedelay())) {
274 sMouseMoved = PR_IntervalToMilliseconds(PR_IntervalNow());
277 return;
279 case eKeyPress:
280 case eKeyUp:
281 case eKeyDown:
282 case eMouseUp:
283 case eMouseDown:
284 case eMouseDoubleClick:
285 case eMouseAuxClick:
286 case eMouseClick:
287 case eContextMenu:
288 case eDrop:
289 EndTransaction();
290 return;
291 default:
292 break;
296 /* static */
297 void WheelTransaction::Shutdown() { NS_IF_RELEASE(sTimer); }
299 /* static */
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
311 if (!sTargetFrame) {
312 EndTransaction();
316 /* static */
317 void WheelTransaction::OnTimeout(nsITimer* aTimer, void* aClosure) {
318 if (!sTargetFrame) {
319 // The transaction target was destroyed already
320 EndTransaction();
321 return;
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.
327 MayEndTransaction();
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,
334 Cancelable::eYes);
338 /* static */
339 void WheelTransaction::SetTimeout() {
340 if (!sTimer) {
341 sTimer = NS_NewTimer().take();
342 if (!sTimer) {
343 return;
346 sTimer->Cancel();
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");
354 /* static */
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();
361 /* static */
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) {
368 return result;
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();
379 if (factor > 0) {
380 result.deltaX = ComputeAcceleratedWheelDelta(result.deltaX, factor);
381 result.deltaY = ComputeAcceleratedWheelDelta(result.deltaY, factor);
385 return result;
388 /* static */
389 double WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta,
390 int32_t aFactor) {
391 return mozilla::ComputeAcceleratedWheelDelta(aDelta, sScrollSeriesCounter,
392 aFactor);
395 /* static */
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
402 // here.
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),
416 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;
425 /* static */
426 void ScrollbarsForWheel::PrepareToScrollText(EventStateManager* aESM,
427 nsIFrame* aTargetFrame,
428 WidgetWheelEvent* aEvent) {
429 if (aEvent->mMessage == eWheelOperationStart) {
430 WheelTransaction::OwnScrollbars(false);
431 if (!IsActive()) {
432 TemporarilyActivateAllPossibleScrollTargets(aESM, aTargetFrame, aEvent);
433 sHadWheelStart = true;
435 } else {
436 DeactivateAllTemporarilyActivatedScrollTargets();
440 /* static */
441 void ScrollbarsForWheel::SetActiveScrollTarget(
442 nsIScrollableFrame* aScrollTarget) {
443 if (!sHadWheelStart) {
444 return;
446 nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(aScrollTarget);
447 if (!scrollbarMediator) {
448 return;
450 sHadWheelStart = false;
451 sActiveOwner = do_QueryFrame(aScrollTarget);
452 scrollbarMediator->ScrollbarActivityStarted();
455 /* static */
456 void ScrollbarsForWheel::MayInactivate() {
457 if (!sOwnWheelTransaction && WheelTransaction::GetTargetFrame()) {
458 WheelTransaction::OwnScrollbars(true);
459 } else {
460 Inactivate();
464 /* static */
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();
479 /* static */
480 bool ScrollbarsForWheel::IsActive() {
481 if (sActiveOwner) {
482 return true;
484 for (size_t i = 0; i < kNumberOfTargets; ++i) {
485 if (sActivatedScrollTargets[i]) {
486 return true;
489 return false;
492 /* static */
493 void ScrollbarsForWheel::OwnWheelTransaction(bool aOwn) {
494 sOwnWheelTransaction = aOwn;
497 /* static */
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();
516 /* static */
517 void ScrollbarsForWheel::DeactivateAllTemporarilyActivatedScrollTargets() {
518 for (size_t i = 0; i < kNumberOfTargets; i++) {
519 AutoWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
520 if (*scrollTarget) {
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 "
537 "a second time");
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
587 // it again.
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)) {
601 return false;
603 if (mDeltaX) {
604 if (CanScrollAlongXAxis()) {
605 return false;
607 if (IsHorizontalContentRightToLeft()) {
608 mShouldBeAdjusted =
609 mDeltaX > 0 ? CanScrollUpwards() : CanScrollDownwards();
610 } else {
611 mShouldBeAdjusted =
612 mDeltaX < 0 ? CanScrollUpwards() : CanScrollDownwards();
614 return mShouldBeAdjusted;
616 MOZ_ASSERT(0 != mDeltaY);
617 if (CanScrollAlongYAxis()) {
618 return false;
620 if (IsHorizontalContentRightToLeft()) {
621 mShouldBeAdjusted =
622 mDeltaY > 0 ? CanScrollLeftwards() : CanScrollRightwards();
623 } else {
624 mShouldBeAdjusted =
625 mDeltaY < 0 ? CanScrollLeftwards() : CanScrollRightwards();
627 return mShouldBeAdjusted;
630 void AutoDirWheelDeltaAdjuster::Adjust() {
631 if (!ShouldBeAdjusted()) {
632 return;
634 std::swap(mDeltaX, mDeltaY);
635 if (IsHorizontalContentRightToLeft()) {
636 mDeltaX *= -1;
637 mDeltaY *= -1;
639 mShouldBeAdjusted = false;
640 OnAdjusted();
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;
658 if (aHonoursRoot) {
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();
663 if (document) {
664 dom::Element* bodyElement = document->GetBodyElement();
665 if (bodyElement) {
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
677 // frame.
678 honouredFrame = &aScrollFrame;
680 } else {
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.
699 if (mDeltaX) {
700 // A vertical scroll was adjusted to be horizontal.
701 MOZ_ASSERT(0 == mDeltaY);
703 mLineOrPageDeltaX = mLineOrPageDeltaY;
704 mLineOrPageDeltaY = 0;
705 mOverflowDeltaX = mOverflowDeltaY;
706 mOverflowDeltaY = 0;
707 } else {
708 // A horizontal scroll was adjusted to be vertical.
709 MOZ_ASSERT(0 != mDeltaY);
711 mLineOrPageDeltaY = mLineOrPageDeltaX;
712 mLineOrPageDeltaX = 0;
713 mOverflowDeltaY = mOverflowDeltaX;
714 mOverflowDeltaX = 0;
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 /******************************************************************/
767 /*explicit*/
768 ESMAutoDirWheelDeltaRestorer::ESMAutoDirWheelDeltaRestorer(
769 WidgetWheelEvent& aEvent)
770 : mEvent(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.
782 return;
785 bool forRTL = false;
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.
792 forRTL = true;
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
800 // this instance.
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;
806 if (forRTL) {
807 mEvent.mOverflowDeltaX *= -1;
808 mEvent.mLineOrPageDeltaX *= -1;
810 mEvent.mOverflowDeltaY = mOldOverflowDeltaY;
811 mEvent.mLineOrPageDeltaY = mOldLineOrPageDeltaY;
812 } else {
813 // A vertical scroll was adjusted to be horizontal during the lifetime of
814 // this instance.
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;
820 if (forRTL) {
821 mEvent.mOverflowDeltaY *= -1;
822 mEvent.mLineOrPageDeltaY *= -1;
824 mEvent.mOverflowDeltaX = mOldOverflowDeltaX;
825 mEvent.mLineOrPageDeltaX = mOldLineOrPageDeltaX;
829 } // namespace mozilla