Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / events / WheelHandlingHelper.cpp
blob3b5653c50eefe7d3474e82a77a4e9ad908f2b3cd
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 "nsPresContext.h"
29 #include "prtime.h"
30 #include "Units.h"
31 #include "ScrollAnimationPhysics.h"
33 namespace mozilla {
35 /******************************************************************/
36 /* mozilla::DeltaValues */
37 /******************************************************************/
39 DeltaValues::DeltaValues(WidgetWheelEvent* aEvent)
40 : deltaX(aEvent->mDeltaX), deltaY(aEvent->mDeltaY) {}
42 /******************************************************************/
43 /* mozilla::WheelHandlingUtils */
44 /******************************************************************/
46 /* static */
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;
53 /* static */
54 bool WheelHandlingUtils::CanScrollOn(nsIFrame* aFrame, double aDirectionX,
55 double aDirectionY) {
56 nsIScrollableFrame* scrollableFrame = do_QueryFrame(aFrame);
57 if (!scrollableFrame) {
58 return false;
60 return CanScrollOn(scrollableFrame, aDirectionX, aDirectionY);
63 /* static */
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(),
78 aDirectionX)) ||
79 ((aDirectionY != 0.0) &&
80 (directions.contains(layers::ScrollDirection::eVertical)) &&
81 CanScrollInRange(scrollRange.y, scrollPt.y, scrollRange.YMost(),
82 aDirectionY));
85 /*static*/ Maybe<layers::ScrollDirection>
86 WheelHandlingUtils::GetDisregardedWheelScrollDirection(const nsIFrame* aFrame) {
87 nsIContent* content = aFrame->GetContent();
88 if (!content) {
89 return Nothing();
91 TextControlElement* textControlElement = TextControlElement::FromNodeOrNull(
92 content->IsInNativeAnonymousSubtree()
93 ? content->GetClosestNativeAnonymousSubtreeRootParentOrHost()
94 : content);
95 if (!textControlElement || !textControlElement->IsSingleLineTextControl()) {
96 return Nothing();
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);
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;
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* 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
155 // wheel operation.
156 sHandledByApz = aEvent->mFlags.mHandledByAPZ;
159 sScrollSeriesCounter = 0;
160 if (!UpdateTransaction(aEvent)) {
161 NS_ERROR("BeginTransaction is called even cannot scroll the frame");
162 EndTransaction();
166 /* static */
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,
175 aEvent->mDeltaY)) {
176 OnFailToScrollTarget();
177 // We should not modify the transaction state when the view will not be
178 // scrolled actually.
179 return false;
182 SetTimeout();
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());
195 sMouseMoved = 0;
196 return true;
199 /* static */
200 void WheelTransaction::MayEndTransaction() {
201 if (!sOwnScrollbars && ScrollbarsForWheel::IsActive()) {
202 ScrollbarsForWheel::OwnWheelTransaction(true);
203 } else {
204 EndTransaction();
208 /* static */
209 void WheelTransaction::EndTransaction() {
210 if (sTimer) {
211 sTimer->Cancel();
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();
224 /* static */
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()) {
233 EndTransaction();
234 BeginTransaction(aScrollTargetWeakFrame.GetFrame(),
235 aEventTargetWeakFrame.GetFrame(), aWheelEvent);
236 } else {
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()) {
245 EndTransaction();
246 return false;
249 return true;
252 /* static */
253 void WheelTransaction::OnEvent(WidgetEvent* aEvent) {
254 if (!sScrollTargetFrame) {
255 return;
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
262 // transaction.
263 OnTimeout(nullptr, nullptr);
264 return;
267 switch (aEvent->mMessage) {
268 case eWheel:
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
274 EndTransaction();
276 return;
277 case eMouseMove:
278 case eDragOver: {
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)) {
288 EndTransaction();
289 return;
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
299 // checked later
300 if (!sMouseMoved &&
301 OutOfTime(sTime,
302 StaticPrefs::mousewheel_transaction_ignoremovedelay())) {
303 sMouseMoved = PR_IntervalToMilliseconds(PR_IntervalNow());
306 return;
308 case eKeyPress:
309 case eKeyUp:
310 case eKeyDown:
311 case eMouseUp:
312 case eMouseDown:
313 case eMouseDoubleClick:
314 case eMouseAuxClick:
315 case eMouseClick:
316 case eContextMenu:
317 case eDrop:
318 EndTransaction();
319 return;
320 default:
321 break;
325 /* static */
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) {
330 return;
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;
344 /* static */
345 void WheelTransaction::Shutdown() { NS_IF_RELEASE(sTimer); }
347 /* static */
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) {
361 EndTransaction();
365 /* static */
366 void WheelTransaction::OnTimeout(nsITimer* aTimer, void* aClosure) {
367 if (!sScrollTargetFrame) {
368 // The transaction target was destroyed already
369 EndTransaction();
370 return;
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.
376 MayEndTransaction();
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,
383 Cancelable::eYes);
387 /* static */
388 void WheelTransaction::SetTimeout() {
389 if (!sTimer) {
390 sTimer = NS_NewTimer().take();
391 if (!sTimer) {
392 return;
395 sTimer->Cancel();
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");
403 /* static */
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();
410 /* static */
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) {
416 return result;
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();
423 if (factor > 0) {
424 result.deltaX = ComputeAcceleratedWheelDelta(result.deltaX, factor);
425 result.deltaY = ComputeAcceleratedWheelDelta(result.deltaY, factor);
429 return result;
432 /* static */
433 double WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta,
434 int32_t aFactor) {
435 return mozilla::ComputeAcceleratedWheelDelta(aDelta, sScrollSeriesCounter,
436 aFactor);
439 /* static */
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
445 // here.
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),
459 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;
468 /* static */
469 void ScrollbarsForWheel::PrepareToScrollText(EventStateManager* aESM,
470 nsIFrame* aTargetFrame,
471 WidgetWheelEvent* aEvent) {
472 if (aEvent->mMessage == eWheelOperationStart) {
473 WheelTransaction::OwnScrollbars(false);
474 if (!IsActive()) {
475 TemporarilyActivateAllPossibleScrollTargets(aESM, aTargetFrame, aEvent);
476 sHadWheelStart = true;
478 } else {
479 DeactivateAllTemporarilyActivatedScrollTargets();
483 /* static */
484 void ScrollbarsForWheel::SetActiveScrollTarget(
485 nsIScrollableFrame* aScrollTarget) {
486 if (!sHadWheelStart) {
487 return;
489 nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(aScrollTarget);
490 if (!scrollbarMediator) {
491 return;
493 sHadWheelStart = false;
494 sActiveOwner = do_QueryFrame(aScrollTarget);
495 scrollbarMediator->ScrollbarActivityStarted();
498 /* static */
499 void ScrollbarsForWheel::MayInactivate() {
500 if (!sOwnWheelTransaction && WheelTransaction::GetScrollTargetFrame()) {
501 WheelTransaction::OwnScrollbars(true);
502 } else {
503 Inactivate();
507 /* static */
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();
522 /* static */
523 bool ScrollbarsForWheel::IsActive() {
524 if (sActiveOwner) {
525 return true;
527 for (size_t i = 0; i < kNumberOfTargets; ++i) {
528 if (sActivatedScrollTargets[i]) {
529 return true;
532 return false;
535 /* static */
536 void ScrollbarsForWheel::OwnWheelTransaction(bool aOwn) {
537 sOwnWheelTransaction = aOwn;
540 /* static */
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();
559 /* static */
560 void ScrollbarsForWheel::DeactivateAllTemporarilyActivatedScrollTargets() {
561 for (size_t i = 0; i < kNumberOfTargets; i++) {
562 AutoWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
563 if (*scrollTarget) {
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 "
580 "a second time");
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
630 // it again.
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)) {
644 return false;
646 if (mDeltaX) {
647 if (CanScrollAlongXAxis()) {
648 return false;
650 if (IsHorizontalContentRightToLeft()) {
651 mShouldBeAdjusted =
652 mDeltaX > 0 ? CanScrollUpwards() : CanScrollDownwards();
653 } else {
654 mShouldBeAdjusted =
655 mDeltaX < 0 ? CanScrollUpwards() : CanScrollDownwards();
657 return mShouldBeAdjusted;
659 MOZ_ASSERT(0 != mDeltaY);
660 if (CanScrollAlongYAxis()) {
661 return false;
663 if (IsHorizontalContentRightToLeft()) {
664 mShouldBeAdjusted =
665 mDeltaY > 0 ? CanScrollLeftwards() : CanScrollRightwards();
666 } else {
667 mShouldBeAdjusted =
668 mDeltaY < 0 ? CanScrollLeftwards() : CanScrollRightwards();
670 return mShouldBeAdjusted;
673 void AutoDirWheelDeltaAdjuster::Adjust() {
674 if (!ShouldBeAdjusted()) {
675 return;
677 std::swap(mDeltaX, mDeltaY);
678 if (IsHorizontalContentRightToLeft()) {
679 mDeltaX *= -1;
680 mDeltaY *= -1;
682 mShouldBeAdjusted = false;
683 OnAdjusted();
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;
701 if (aHonoursRoot) {
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();
706 if (document) {
707 dom::Element* bodyElement = document->GetBodyElement();
708 if (bodyElement) {
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
720 // frame.
721 honouredFrame = &aScrollFrame;
723 } else {
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.
742 if (mDeltaX) {
743 // A vertical scroll was adjusted to be horizontal.
744 MOZ_ASSERT(0 == mDeltaY);
746 mLineOrPageDeltaX = mLineOrPageDeltaY;
747 mLineOrPageDeltaY = 0;
748 mOverflowDeltaX = mOverflowDeltaY;
749 mOverflowDeltaY = 0;
750 } else {
751 // A horizontal scroll was adjusted to be vertical.
752 MOZ_ASSERT(0 != mDeltaY);
754 mLineOrPageDeltaY = mLineOrPageDeltaX;
755 mLineOrPageDeltaX = 0;
756 mOverflowDeltaY = mOverflowDeltaX;
757 mOverflowDeltaX = 0;
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 /******************************************************************/
810 /*explicit*/
811 ESMAutoDirWheelDeltaRestorer::ESMAutoDirWheelDeltaRestorer(
812 WidgetWheelEvent& aEvent)
813 : mEvent(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.
825 return;
828 bool forRTL = false;
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.
835 forRTL = true;
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
843 // this instance.
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;
849 if (forRTL) {
850 mEvent.mOverflowDeltaX *= -1;
851 mEvent.mLineOrPageDeltaX *= -1;
853 mEvent.mOverflowDeltaY = mOldOverflowDeltaY;
854 mEvent.mLineOrPageDeltaY = mOldLineOrPageDeltaY;
855 } else {
856 // A vertical scroll was adjusted to be horizontal during the lifetime of
857 // this instance.
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;
863 if (forRTL) {
864 mEvent.mOverflowDeltaY *= -1;
865 mEvent.mLineOrPageDeltaY *= -1;
867 mEvent.mOverflowDeltaX = mOldOverflowDeltaX;
868 mEvent.mLineOrPageDeltaX = mOldLineOrPageDeltaX;
872 } // namespace mozilla