summaryrefslogtreecommitdiffstats
path: root/dom/events/WheelHandlingHelper.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/events/WheelHandlingHelper.cpp872
1 files changed, 872 insertions, 0 deletions
diff --git a/dom/events/WheelHandlingHelper.cpp b/dom/events/WheelHandlingHelper.cpp
new file mode 100644
index 0000000000..3b5653c50e
--- /dev/null
+++ b/dom/events/WheelHandlingHelper.cpp
@@ -0,0 +1,872 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WheelHandlingHelper.h"
+
+#include <utility> // for std::swap
+
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_mousewheel.h"
+#include "mozilla/StaticPrefs_test.h"
+#include "mozilla/TextControlElement.h"
+#include "mozilla/dom/WheelEventBinding.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "mozilla/dom/Document.h"
+#include "DocumentInlines.h" // for Document and HTMLBodyElement
+#include "nsIScrollableFrame.h"
+#include "nsITimer.h"
+#include "nsPresContext.h"
+#include "prtime.h"
+#include "Units.h"
+#include "ScrollAnimationPhysics.h"
+
+namespace mozilla {
+
+/******************************************************************/
+/* mozilla::DeltaValues */
+/******************************************************************/
+
+DeltaValues::DeltaValues(WidgetWheelEvent* aEvent)
+ : deltaX(aEvent->mDeltaX), deltaY(aEvent->mDeltaY) {}
+
+/******************************************************************/
+/* mozilla::WheelHandlingUtils */
+/******************************************************************/
+
+/* static */
+bool WheelHandlingUtils::CanScrollInRange(nscoord aMin, nscoord aValue,
+ nscoord aMax, double aDirection) {
+ return aDirection > 0.0 ? aValue < static_cast<double>(aMax)
+ : static_cast<double>(aMin) < aValue;
+}
+
+/* static */
+bool WheelHandlingUtils::CanScrollOn(nsIFrame* aFrame, double aDirectionX,
+ double aDirectionY) {
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(aFrame);
+ if (!scrollableFrame) {
+ return false;
+ }
+ return CanScrollOn(scrollableFrame, aDirectionX, aDirectionY);
+}
+
+/* static */
+bool WheelHandlingUtils::CanScrollOn(nsIScrollableFrame* aScrollFrame,
+ double aDirectionX, double aDirectionY) {
+ MOZ_ASSERT(aScrollFrame);
+ NS_ASSERTION(aDirectionX || aDirectionY,
+ "One of the delta values must be non-zero at least");
+
+ nsPoint scrollPt = aScrollFrame->GetVisualViewportOffset();
+ nsRect scrollRange = aScrollFrame->GetScrollRangeForUserInputEvents();
+ layers::ScrollDirections directions =
+ aScrollFrame->GetAvailableScrollingDirectionsForUserInputEvents();
+
+ return ((aDirectionX != 0.0) &&
+ (directions.contains(layers::ScrollDirection::eHorizontal)) &&
+ CanScrollInRange(scrollRange.x, scrollPt.x, scrollRange.XMost(),
+ aDirectionX)) ||
+ ((aDirectionY != 0.0) &&
+ (directions.contains(layers::ScrollDirection::eVertical)) &&
+ CanScrollInRange(scrollRange.y, scrollPt.y, scrollRange.YMost(),
+ aDirectionY));
+}
+
+/*static*/ Maybe<layers::ScrollDirection>
+WheelHandlingUtils::GetDisregardedWheelScrollDirection(const nsIFrame* aFrame) {
+ nsIContent* content = aFrame->GetContent();
+ if (!content) {
+ return Nothing();
+ }
+ TextControlElement* textControlElement = TextControlElement::FromNodeOrNull(
+ content->IsInNativeAnonymousSubtree()
+ ? content->GetClosestNativeAnonymousSubtreeRootParentOrHost()
+ : content);
+ if (!textControlElement || !textControlElement->IsSingleLineTextControl()) {
+ return Nothing();
+ }
+ // Disregard scroll in the block-flow direction by mouse wheel on a
+ // single-line text control. For instance, in tranditional Chinese writing
+ // system, a single-line text control cannot be scrolled horizontally with
+ // mouse wheel even if they overflow at the right and left edges; Whereas in
+ // latin-based writing system, a single-line text control cannot be scrolled
+ // vertically with mouse wheel even if they overflow at the top and bottom
+ // edges
+ return Some(aFrame->GetWritingMode().IsVertical()
+ ? layers::ScrollDirection::eHorizontal
+ : layers::ScrollDirection::eVertical);
+}
+
+/******************************************************************/
+/* mozilla::WheelTransaction */
+/******************************************************************/
+
+AutoWeakFrame WheelTransaction::sScrollTargetFrame(nullptr);
+AutoWeakFrame WheelTransaction::sEventTargetFrame(nullptr);
+bool WheelTransaction::sHandledByApz(false);
+uint32_t WheelTransaction::sTime = 0;
+uint32_t WheelTransaction::sMouseMoved = 0;
+nsITimer* WheelTransaction::sTimer = nullptr;
+int32_t WheelTransaction::sScrollSeriesCounter = 0;
+bool WheelTransaction::sOwnScrollbars = false;
+
+/* static */
+bool WheelTransaction::OutOfTime(uint32_t aBaseTime, uint32_t aThreshold) {
+ uint32_t now = PR_IntervalToMilliseconds(PR_IntervalNow());
+ return (now - aBaseTime > aThreshold);
+}
+
+/* static */
+void WheelTransaction::OwnScrollbars(bool aOwn) { sOwnScrollbars = aOwn; }
+
+/* static */
+void WheelTransaction::BeginTransaction(nsIFrame* aScrollTargetFrame,
+ nsIFrame* aEventTargetFrame,
+ const WidgetWheelEvent* aEvent) {
+ NS_ASSERTION(!sScrollTargetFrame && !sEventTargetFrame,
+ "previous transaction is not finished!");
+ MOZ_ASSERT(aEvent->mMessage == eWheel,
+ "Transaction must be started with a wheel event");
+
+ ScrollbarsForWheel::OwnWheelTransaction(false);
+ sScrollTargetFrame = aScrollTargetFrame;
+
+ // Only set the static event target if wheel event groups are enabled.
+ if (StaticPrefs::dom_event_wheel_event_groups_enabled()) {
+ // Set a static event target for the wheel transaction. This will be used
+ // to override the event target frame when computing the event target from
+ // input coordinates. When this preference is not set or there is no stored
+ // event target for the current wheel transaction, the event target will
+ // not be overridden by the current wheel transaction, but will be computed
+ // from the input coordinates.
+ sEventTargetFrame = aEventTargetFrame;
+ // If the wheel events will be handled by APZ, set a flag here. We can use
+ // this later to determine if we need to scroll snap at the end of the
+ // wheel operation.
+ sHandledByApz = aEvent->mFlags.mHandledByAPZ;
+ }
+
+ sScrollSeriesCounter = 0;
+ if (!UpdateTransaction(aEvent)) {
+ NS_ERROR("BeginTransaction is called even cannot scroll the frame");
+ EndTransaction();
+ }
+}
+
+/* static */
+bool WheelTransaction::UpdateTransaction(const WidgetWheelEvent* aEvent) {
+ nsIFrame* scrollToFrame = GetScrollTargetFrame();
+ nsIScrollableFrame* scrollableFrame = scrollToFrame->GetScrollTargetFrame();
+ if (scrollableFrame) {
+ scrollToFrame = do_QueryFrame(scrollableFrame);
+ }
+
+ if (!WheelHandlingUtils::CanScrollOn(scrollToFrame, aEvent->mDeltaX,
+ aEvent->mDeltaY)) {
+ OnFailToScrollTarget();
+ // We should not modify the transaction state when the view will not be
+ // scrolled actually.
+ return false;
+ }
+
+ SetTimeout();
+
+ if (sScrollSeriesCounter != 0 &&
+ OutOfTime(sTime, StaticPrefs::mousewheel_scroll_series_timeout())) {
+ sScrollSeriesCounter = 0;
+ }
+ sScrollSeriesCounter++;
+
+ // We should use current time instead of WidgetEvent.time.
+ // 1. Some events doesn't have the correct creation time.
+ // 2. If the computer runs slowly by other processes eating the CPU resource,
+ // the event creation time doesn't keep real time.
+ sTime = PR_IntervalToMilliseconds(PR_IntervalNow());
+ sMouseMoved = 0;
+ return true;
+}
+
+/* static */
+void WheelTransaction::MayEndTransaction() {
+ if (!sOwnScrollbars && ScrollbarsForWheel::IsActive()) {
+ ScrollbarsForWheel::OwnWheelTransaction(true);
+ } else {
+ EndTransaction();
+ }
+}
+
+/* static */
+void WheelTransaction::EndTransaction() {
+ if (sTimer) {
+ sTimer->Cancel();
+ }
+ sScrollTargetFrame = nullptr;
+ sEventTargetFrame = nullptr;
+ sScrollSeriesCounter = 0;
+ sHandledByApz = false;
+ if (sOwnScrollbars) {
+ sOwnScrollbars = false;
+ ScrollbarsForWheel::OwnWheelTransaction(false);
+ ScrollbarsForWheel::Inactivate();
+ }
+}
+
+/* static */
+bool WheelTransaction::WillHandleDefaultAction(
+ WidgetWheelEvent* aWheelEvent, AutoWeakFrame& aScrollTargetWeakFrame,
+ AutoWeakFrame& aEventTargetWeakFrame) {
+ nsIFrame* lastTargetFrame = GetScrollTargetFrame();
+ if (!lastTargetFrame) {
+ BeginTransaction(aScrollTargetWeakFrame.GetFrame(),
+ aEventTargetWeakFrame.GetFrame(), aWheelEvent);
+ } else if (lastTargetFrame != aScrollTargetWeakFrame.GetFrame()) {
+ EndTransaction();
+ BeginTransaction(aScrollTargetWeakFrame.GetFrame(),
+ aEventTargetWeakFrame.GetFrame(), aWheelEvent);
+ } else {
+ UpdateTransaction(aWheelEvent);
+ }
+
+ // When the wheel event will not be handled with any frames,
+ // UpdateTransaction() fires MozMouseScrollFailed event which is for
+ // automated testing. In the event handler, the target frame might be
+ // destroyed. Then, the caller shouldn't try to handle the default action.
+ if (!aScrollTargetWeakFrame.IsAlive()) {
+ EndTransaction();
+ return false;
+ }
+
+ return true;
+}
+
+/* static */
+void WheelTransaction::OnEvent(WidgetEvent* aEvent) {
+ if (!sScrollTargetFrame) {
+ return;
+ }
+
+ if (OutOfTime(sTime, StaticPrefs::mousewheel_transaction_timeout())) {
+ // Even if the scroll event which is handled after timeout, but onTimeout
+ // was not fired by timer, then the scroll event will scroll old frame,
+ // therefore, we should call OnTimeout here and ensure to finish the old
+ // transaction.
+ OnTimeout(nullptr, nullptr);
+ return;
+ }
+
+ switch (aEvent->mMessage) {
+ case eWheel:
+ if (sMouseMoved != 0 &&
+ OutOfTime(sMouseMoved,
+ StaticPrefs::mousewheel_transaction_ignoremovedelay())) {
+ // Terminate the current mousewheel transaction if the mouse moved more
+ // than ignoremovedelay milliseconds ago
+ EndTransaction();
+ }
+ return;
+ case eMouseMove:
+ case eDragOver: {
+ WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+ if (mouseEvent->IsReal()) {
+ // If the cursor is moving to be outside the frame,
+ // terminate the scrollwheel transaction.
+ LayoutDeviceIntPoint pt = GetScreenPoint(mouseEvent);
+ auto r = LayoutDeviceIntRect::FromAppUnitsToNearest(
+ sScrollTargetFrame->GetScreenRectInAppUnits(),
+ sScrollTargetFrame->PresContext()->AppUnitsPerDevPixel());
+ if (!r.Contains(pt)) {
+ EndTransaction();
+ return;
+ }
+
+ // For mouse move events where the wheel transaction is still valid, the
+ // stored event target should be reset.
+ sEventTargetFrame = nullptr;
+
+ // If the cursor is moving inside the frame, and it is less than
+ // ignoremovedelay milliseconds since the last scroll operation, ignore
+ // the mouse move; otherwise, record the current mouse move time to be
+ // checked later
+ if (!sMouseMoved &&
+ OutOfTime(sTime,
+ StaticPrefs::mousewheel_transaction_ignoremovedelay())) {
+ sMouseMoved = PR_IntervalToMilliseconds(PR_IntervalNow());
+ }
+ }
+ return;
+ }
+ case eKeyPress:
+ case eKeyUp:
+ case eKeyDown:
+ case eMouseUp:
+ case eMouseDown:
+ case eMouseDoubleClick:
+ case eMouseAuxClick:
+ case eMouseClick:
+ case eContextMenu:
+ case eDrop:
+ EndTransaction();
+ return;
+ default:
+ break;
+ }
+}
+
+/* static */
+void WheelTransaction::OnRemoveElement(nsIContent* aContent) {
+ // If dom.event.wheel-event-groups.enabled is not set or we have no current
+ // wheel event transaction there is no internal state to be updated.
+ if (!sEventTargetFrame) {
+ return;
+ }
+
+ if (sEventTargetFrame->GetContent() == aContent) {
+ // Only invalidate the wheel transaction event target frame when the
+ // remove target is the event target of the wheel event group. The
+ // scroll target frame of the wheel event group may still be valid.
+ //
+ // With the stored event target unset, the target for any following
+ // events will be the frame found using the input coordinates.
+ sEventTargetFrame = nullptr;
+ }
+}
+
+/* static */
+void WheelTransaction::Shutdown() { NS_IF_RELEASE(sTimer); }
+
+/* static */
+void WheelTransaction::OnFailToScrollTarget() {
+ MOZ_ASSERT(sScrollTargetFrame, "We don't have mouse scrolling transaction");
+
+ if (StaticPrefs::test_mousescroll()) {
+ // This event is used for automated tests, see bug 442774.
+ nsContentUtils::DispatchEventOnlyToChrome(
+ sScrollTargetFrame->GetContent()->OwnerDoc(),
+ sScrollTargetFrame->GetContent(), u"MozMouseScrollFailed"_ns,
+ CanBubble::eYes, Cancelable::eYes);
+ }
+ // The target frame might be destroyed in the event handler, at that time,
+ // we need to finish the current transaction
+ if (!sScrollTargetFrame) {
+ EndTransaction();
+ }
+}
+
+/* static */
+void WheelTransaction::OnTimeout(nsITimer* aTimer, void* aClosure) {
+ if (!sScrollTargetFrame) {
+ // The transaction target was destroyed already
+ EndTransaction();
+ return;
+ }
+ // Store the sScrollTargetFrame, the variable becomes null in EndTransaction.
+ nsIFrame* frame = sScrollTargetFrame;
+ // We need to finish current transaction before DOM event firing. Because
+ // the next DOM event might create strange situation for us.
+ MayEndTransaction();
+
+ if (StaticPrefs::test_mousescroll()) {
+ // This event is used for automated tests, see bug 442774.
+ nsContentUtils::DispatchEventOnlyToChrome(
+ frame->GetContent()->OwnerDoc(), frame->GetContent(),
+ u"MozMouseScrollTransactionTimeout"_ns, CanBubble::eYes,
+ Cancelable::eYes);
+ }
+}
+
+/* static */
+void WheelTransaction::SetTimeout() {
+ if (!sTimer) {
+ sTimer = NS_NewTimer().take();
+ if (!sTimer) {
+ return;
+ }
+ }
+ sTimer->Cancel();
+ DebugOnly<nsresult> rv = sTimer->InitWithNamedFuncCallback(
+ OnTimeout, nullptr, StaticPrefs::mousewheel_transaction_timeout(),
+ nsITimer::TYPE_ONE_SHOT, "WheelTransaction::SetTimeout");
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "nsITimer::InitWithFuncCallback failed");
+}
+
+/* static */
+LayoutDeviceIntPoint WheelTransaction::GetScreenPoint(WidgetGUIEvent* aEvent) {
+ NS_ASSERTION(aEvent, "aEvent is null");
+ NS_ASSERTION(aEvent->mWidget, "aEvent-mWidget is null");
+ return aEvent->mRefPoint + aEvent->mWidget->WidgetToScreenOffset();
+}
+
+/* static */
+DeltaValues WheelTransaction::AccelerateWheelDelta(WidgetWheelEvent* aEvent) {
+ DeltaValues result = OverrideSystemScrollSpeed(aEvent);
+
+ // Don't accelerate the delta values if the event isn't line scrolling.
+ if (aEvent->mDeltaMode != dom::WheelEvent_Binding::DOM_DELTA_LINE) {
+ return result;
+ }
+
+ // Accelerate by the sScrollSeriesCounter
+ int32_t start = StaticPrefs::mousewheel_acceleration_start();
+ if (start >= 0 && sScrollSeriesCounter >= start) {
+ int32_t factor = StaticPrefs::mousewheel_acceleration_factor();
+ if (factor > 0) {
+ result.deltaX = ComputeAcceleratedWheelDelta(result.deltaX, factor);
+ result.deltaY = ComputeAcceleratedWheelDelta(result.deltaY, factor);
+ }
+ }
+
+ return result;
+}
+
+/* static */
+double WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta,
+ int32_t aFactor) {
+ return mozilla::ComputeAcceleratedWheelDelta(aDelta, sScrollSeriesCounter,
+ aFactor);
+}
+
+/* static */
+DeltaValues WheelTransaction::OverrideSystemScrollSpeed(
+ WidgetWheelEvent* aEvent) {
+ MOZ_ASSERT(sScrollTargetFrame, "We don't have mouse scrolling transaction");
+
+ // If the event doesn't scroll to both X and Y, we don't need to do anything
+ // here.
+ if (!aEvent->mDeltaX && !aEvent->mDeltaY) {
+ return DeltaValues(aEvent);
+ }
+
+ return DeltaValues(aEvent->OverriddenDeltaX(), aEvent->OverriddenDeltaY());
+}
+
+/******************************************************************/
+/* mozilla::ScrollbarsForWheel */
+/******************************************************************/
+
+const DeltaValues ScrollbarsForWheel::directions[kNumberOfTargets] = {
+ DeltaValues(-1, 0), DeltaValues(+1, 0), DeltaValues(0, -1),
+ DeltaValues(0, +1)};
+
+AutoWeakFrame ScrollbarsForWheel::sActiveOwner = nullptr;
+AutoWeakFrame ScrollbarsForWheel::sActivatedScrollTargets[kNumberOfTargets] = {
+ nullptr, nullptr, nullptr, nullptr};
+
+bool ScrollbarsForWheel::sHadWheelStart = false;
+bool ScrollbarsForWheel::sOwnWheelTransaction = false;
+
+/* static */
+void ScrollbarsForWheel::PrepareToScrollText(EventStateManager* aESM,
+ nsIFrame* aTargetFrame,
+ WidgetWheelEvent* aEvent) {
+ if (aEvent->mMessage == eWheelOperationStart) {
+ WheelTransaction::OwnScrollbars(false);
+ if (!IsActive()) {
+ TemporarilyActivateAllPossibleScrollTargets(aESM, aTargetFrame, aEvent);
+ sHadWheelStart = true;
+ }
+ } else {
+ DeactivateAllTemporarilyActivatedScrollTargets();
+ }
+}
+
+/* static */
+void ScrollbarsForWheel::SetActiveScrollTarget(
+ nsIScrollableFrame* aScrollTarget) {
+ if (!sHadWheelStart) {
+ return;
+ }
+ nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(aScrollTarget);
+ if (!scrollbarMediator) {
+ return;
+ }
+ sHadWheelStart = false;
+ sActiveOwner = do_QueryFrame(aScrollTarget);
+ scrollbarMediator->ScrollbarActivityStarted();
+}
+
+/* static */
+void ScrollbarsForWheel::MayInactivate() {
+ if (!sOwnWheelTransaction && WheelTransaction::GetScrollTargetFrame()) {
+ WheelTransaction::OwnScrollbars(true);
+ } else {
+ Inactivate();
+ }
+}
+
+/* static */
+void ScrollbarsForWheel::Inactivate() {
+ nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sActiveOwner);
+ if (scrollbarMediator) {
+ scrollbarMediator->ScrollbarActivityStopped();
+ }
+ sActiveOwner = nullptr;
+ DeactivateAllTemporarilyActivatedScrollTargets();
+ if (sOwnWheelTransaction) {
+ sOwnWheelTransaction = false;
+ WheelTransaction::OwnScrollbars(false);
+ WheelTransaction::EndTransaction();
+ }
+}
+
+/* static */
+bool ScrollbarsForWheel::IsActive() {
+ if (sActiveOwner) {
+ return true;
+ }
+ for (size_t i = 0; i < kNumberOfTargets; ++i) {
+ if (sActivatedScrollTargets[i]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */
+void ScrollbarsForWheel::OwnWheelTransaction(bool aOwn) {
+ sOwnWheelTransaction = aOwn;
+}
+
+/* static */
+void ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets(
+ EventStateManager* aESM, nsIFrame* aTargetFrame, WidgetWheelEvent* aEvent) {
+ for (size_t i = 0; i < kNumberOfTargets; i++) {
+ const DeltaValues* dir = &directions[i];
+ AutoWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
+ MOZ_ASSERT(!*scrollTarget, "scroll target still temporarily activated!");
+ nsIScrollableFrame* target = do_QueryFrame(aESM->ComputeScrollTarget(
+ aTargetFrame, dir->deltaX, dir->deltaY, aEvent,
+ EventStateManager::COMPUTE_DEFAULT_ACTION_TARGET));
+ nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(target);
+ if (scrollbarMediator) {
+ nsIFrame* targetFrame = do_QueryFrame(target);
+ *scrollTarget = targetFrame;
+ scrollbarMediator->ScrollbarActivityStarted();
+ }
+ }
+}
+
+/* static */
+void ScrollbarsForWheel::DeactivateAllTemporarilyActivatedScrollTargets() {
+ for (size_t i = 0; i < kNumberOfTargets; i++) {
+ AutoWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
+ if (*scrollTarget) {
+ nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(*scrollTarget);
+ if (scrollbarMediator) {
+ scrollbarMediator->ScrollbarActivityStopped();
+ }
+ *scrollTarget = nullptr;
+ }
+ }
+}
+
+/******************************************************************/
+/* mozilla::WheelDeltaHorizontalizer */
+/******************************************************************/
+
+void WheelDeltaHorizontalizer::Horizontalize() {
+ MOZ_ASSERT(!mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler,
+ "Wheel delta values in one wheel scroll event are being adjusted "
+ "a second time");
+
+ // Log the old values.
+ mOldDeltaX = mWheelEvent.mDeltaX;
+ mOldDeltaZ = mWheelEvent.mDeltaZ;
+ mOldOverflowDeltaX = mWheelEvent.mOverflowDeltaX;
+ mOldLineOrPageDeltaX = mWheelEvent.mLineOrPageDeltaX;
+
+ // Move deltaY values to deltaX and set both deltaY and deltaZ to 0.
+ mWheelEvent.mDeltaX = mWheelEvent.mDeltaY;
+ mWheelEvent.mDeltaY = 0.0;
+ mWheelEvent.mDeltaZ = 0.0;
+ mWheelEvent.mOverflowDeltaX = mWheelEvent.mOverflowDeltaY;
+ mWheelEvent.mOverflowDeltaY = 0.0;
+ mWheelEvent.mLineOrPageDeltaX = mWheelEvent.mLineOrPageDeltaY;
+ mWheelEvent.mLineOrPageDeltaY = 0;
+
+ // Mark it horizontalized in order to restore the delta values when this
+ // instance is being destroyed.
+ mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler = true;
+ mHorizontalized = true;
+}
+
+void WheelDeltaHorizontalizer::CancelHorizontalization() {
+ // Restore the horizontalized delta.
+ if (mHorizontalized &&
+ mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler) {
+ mWheelEvent.mDeltaY = mWheelEvent.mDeltaX;
+ mWheelEvent.mDeltaX = mOldDeltaX;
+ mWheelEvent.mDeltaZ = mOldDeltaZ;
+ mWheelEvent.mOverflowDeltaY = mWheelEvent.mOverflowDeltaX;
+ mWheelEvent.mOverflowDeltaX = mOldOverflowDeltaX;
+ mWheelEvent.mLineOrPageDeltaY = mWheelEvent.mLineOrPageDeltaX;
+ mWheelEvent.mLineOrPageDeltaX = mOldLineOrPageDeltaX;
+ mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler = false;
+ mHorizontalized = false;
+ }
+}
+
+WheelDeltaHorizontalizer::~WheelDeltaHorizontalizer() {
+ CancelHorizontalization();
+}
+
+/******************************************************************/
+/* mozilla::AutoDirWheelDeltaAdjuster */
+/******************************************************************/
+
+bool AutoDirWheelDeltaAdjuster::ShouldBeAdjusted() {
+ // Sometimes, this function can be called more than one time. If we have
+ // already checked if the scroll should be adjusted, there's no need to check
+ // it again.
+ if (mCheckedIfShouldBeAdjusted) {
+ return mShouldBeAdjusted;
+ }
+ mCheckedIfShouldBeAdjusted = true;
+
+ // For an auto-dir wheel scroll, if all the following conditions are met, we
+ // should adjust X and Y values:
+ // 1. There is only one non-zero value between DeltaX and DeltaY.
+ // 2. There is only one direction for the target that overflows and is
+ // scrollable with wheel.
+ // 3. The direction described in Condition 1 is orthogonal to the one
+ // described in Condition 2.
+ if ((mDeltaX && mDeltaY) || (!mDeltaX && !mDeltaY)) {
+ return false;
+ }
+ if (mDeltaX) {
+ if (CanScrollAlongXAxis()) {
+ return false;
+ }
+ if (IsHorizontalContentRightToLeft()) {
+ mShouldBeAdjusted =
+ mDeltaX > 0 ? CanScrollUpwards() : CanScrollDownwards();
+ } else {
+ mShouldBeAdjusted =
+ mDeltaX < 0 ? CanScrollUpwards() : CanScrollDownwards();
+ }
+ return mShouldBeAdjusted;
+ }
+ MOZ_ASSERT(0 != mDeltaY);
+ if (CanScrollAlongYAxis()) {
+ return false;
+ }
+ if (IsHorizontalContentRightToLeft()) {
+ mShouldBeAdjusted =
+ mDeltaY > 0 ? CanScrollLeftwards() : CanScrollRightwards();
+ } else {
+ mShouldBeAdjusted =
+ mDeltaY < 0 ? CanScrollLeftwards() : CanScrollRightwards();
+ }
+ return mShouldBeAdjusted;
+}
+
+void AutoDirWheelDeltaAdjuster::Adjust() {
+ if (!ShouldBeAdjusted()) {
+ return;
+ }
+ std::swap(mDeltaX, mDeltaY);
+ if (IsHorizontalContentRightToLeft()) {
+ mDeltaX *= -1;
+ mDeltaY *= -1;
+ }
+ mShouldBeAdjusted = false;
+ OnAdjusted();
+}
+
+/******************************************************************/
+/* mozilla::ESMAutoDirWheelDeltaAdjuster */
+/******************************************************************/
+
+ESMAutoDirWheelDeltaAdjuster::ESMAutoDirWheelDeltaAdjuster(
+ WidgetWheelEvent& aEvent, nsIFrame& aScrollFrame, bool aHonoursRoot)
+ : AutoDirWheelDeltaAdjuster(aEvent.mDeltaX, aEvent.mDeltaY),
+ mLineOrPageDeltaX(aEvent.mLineOrPageDeltaX),
+ mLineOrPageDeltaY(aEvent.mLineOrPageDeltaY),
+ mOverflowDeltaX(aEvent.mOverflowDeltaX),
+ mOverflowDeltaY(aEvent.mOverflowDeltaY) {
+ mScrollTargetFrame = aScrollFrame.GetScrollTargetFrame();
+ MOZ_ASSERT(mScrollTargetFrame);
+
+ nsIFrame* honouredFrame = nullptr;
+ if (aHonoursRoot) {
+ // If we are going to honour root, first try to get the frame for <body> as
+ // the honoured root, because <body> is in preference to <html> if the
+ // current document is an HTML document.
+ dom::Document* document = aScrollFrame.PresShell()->GetDocument();
+ if (document) {
+ dom::Element* bodyElement = document->GetBodyElement();
+ if (bodyElement) {
+ honouredFrame = bodyElement->GetPrimaryFrame();
+ }
+ }
+
+ if (!honouredFrame) {
+ // If there is no <body> frame, fall back to the real root frame.
+ honouredFrame = aScrollFrame.PresShell()->GetRootScrollFrame();
+ }
+
+ if (!honouredFrame) {
+ // If there is no root scroll frame, fall back to the current scrolling
+ // frame.
+ honouredFrame = &aScrollFrame;
+ }
+ } else {
+ honouredFrame = &aScrollFrame;
+ }
+
+ WritingMode writingMode = honouredFrame->GetWritingMode();
+ WritingMode::BlockDir blockDir = writingMode.GetBlockDir();
+ WritingMode::InlineDir inlineDir = writingMode.GetInlineDir();
+ // Get whether the honoured frame's content in the horizontal direction starts
+ // from right to left(E.g. it's true either if "writing-mode: vertical-rl", or
+ // if "writing-mode: horizontal-tb; direction: rtl;" in CSS).
+ mIsHorizontalContentRightToLeft =
+ (blockDir == WritingMode::BlockDir::eBlockRL ||
+ (blockDir == WritingMode::BlockDir::eBlockTB &&
+ inlineDir == WritingMode::InlineDir::eInlineRTL));
+}
+
+void ESMAutoDirWheelDeltaAdjuster::OnAdjusted() {
+ // Adjust() only adjusted basic deltaX and deltaY, which are not enough for
+ // ESM, we should continue to adjust line-or-page and overflow values.
+ if (mDeltaX) {
+ // A vertical scroll was adjusted to be horizontal.
+ MOZ_ASSERT(0 == mDeltaY);
+
+ mLineOrPageDeltaX = mLineOrPageDeltaY;
+ mLineOrPageDeltaY = 0;
+ mOverflowDeltaX = mOverflowDeltaY;
+ mOverflowDeltaY = 0;
+ } else {
+ // A horizontal scroll was adjusted to be vertical.
+ MOZ_ASSERT(0 != mDeltaY);
+
+ mLineOrPageDeltaY = mLineOrPageDeltaX;
+ mLineOrPageDeltaX = 0;
+ mOverflowDeltaY = mOverflowDeltaX;
+ mOverflowDeltaX = 0;
+ }
+ if (mIsHorizontalContentRightToLeft) {
+ // If in RTL writing mode, reverse the side the scroll will go towards.
+ mLineOrPageDeltaX *= -1;
+ mLineOrPageDeltaY *= -1;
+ mOverflowDeltaX *= -1;
+ mOverflowDeltaY *= -1;
+ }
+}
+
+bool ESMAutoDirWheelDeltaAdjuster::CanScrollAlongXAxis() const {
+ return mScrollTargetFrame->GetAvailableScrollingDirections().contains(
+ layers::ScrollDirection::eHorizontal);
+}
+
+bool ESMAutoDirWheelDeltaAdjuster::CanScrollAlongYAxis() const {
+ return mScrollTargetFrame->GetAvailableScrollingDirections().contains(
+ layers::ScrollDirection::eVertical);
+}
+
+bool ESMAutoDirWheelDeltaAdjuster::CanScrollUpwards() const {
+ nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
+ nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
+ return static_cast<double>(scrollRange.y) < scrollPt.y;
+}
+
+bool ESMAutoDirWheelDeltaAdjuster::CanScrollDownwards() const {
+ nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
+ nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
+ return static_cast<double>(scrollRange.YMost()) > scrollPt.y;
+}
+
+bool ESMAutoDirWheelDeltaAdjuster::CanScrollLeftwards() const {
+ nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
+ nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
+ return static_cast<double>(scrollRange.x) < scrollPt.x;
+}
+
+bool ESMAutoDirWheelDeltaAdjuster::CanScrollRightwards() const {
+ nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
+ nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
+ return static_cast<double>(scrollRange.XMost()) > scrollPt.x;
+}
+
+bool ESMAutoDirWheelDeltaAdjuster::IsHorizontalContentRightToLeft() const {
+ return mIsHorizontalContentRightToLeft;
+}
+
+/******************************************************************/
+/* mozilla::ESMAutoDirWheelDeltaRestorer */
+/******************************************************************/
+
+/*explicit*/
+ESMAutoDirWheelDeltaRestorer::ESMAutoDirWheelDeltaRestorer(
+ WidgetWheelEvent& aEvent)
+ : mEvent(aEvent),
+ mOldDeltaX(aEvent.mDeltaX),
+ mOldDeltaY(aEvent.mDeltaY),
+ mOldLineOrPageDeltaX(aEvent.mLineOrPageDeltaX),
+ mOldLineOrPageDeltaY(aEvent.mLineOrPageDeltaY),
+ mOldOverflowDeltaX(aEvent.mOverflowDeltaX),
+ mOldOverflowDeltaY(aEvent.mOverflowDeltaY) {}
+
+ESMAutoDirWheelDeltaRestorer::~ESMAutoDirWheelDeltaRestorer() {
+ if (mOldDeltaX == mEvent.mDeltaX || mOldDeltaY == mEvent.mDeltaY) {
+ // The delta of the event wasn't adjusted during the lifetime of this
+ // |ESMAutoDirWheelDeltaRestorer| instance. No need to restore it.
+ return;
+ }
+
+ bool forRTL = false;
+
+ // First, restore the basic deltaX and deltaY.
+ std::swap(mEvent.mDeltaX, mEvent.mDeltaY);
+ if (mOldDeltaX != mEvent.mDeltaX || mOldDeltaY != mEvent.mDeltaY) {
+ // If X and Y still don't equal to their original values after being
+ // swapped, then it must be because they were adjusted for RTL.
+ forRTL = true;
+ mEvent.mDeltaX *= -1;
+ mEvent.mDeltaY *= -1;
+ MOZ_ASSERT(mOldDeltaX == mEvent.mDeltaX && mOldDeltaY == mEvent.mDeltaY);
+ }
+
+ if (mEvent.mDeltaX) {
+ // A horizontal scroll was adjusted to be vertical during the lifetime of
+ // this instance.
+ MOZ_ASSERT(0 == mEvent.mDeltaY);
+
+ // Restore the line-or-page and overflow values to be horizontal.
+ mEvent.mOverflowDeltaX = mEvent.mOverflowDeltaY;
+ mEvent.mLineOrPageDeltaX = mEvent.mLineOrPageDeltaY;
+ if (forRTL) {
+ mEvent.mOverflowDeltaX *= -1;
+ mEvent.mLineOrPageDeltaX *= -1;
+ }
+ mEvent.mOverflowDeltaY = mOldOverflowDeltaY;
+ mEvent.mLineOrPageDeltaY = mOldLineOrPageDeltaY;
+ } else {
+ // A vertical scroll was adjusted to be horizontal during the lifetime of
+ // this instance.
+ MOZ_ASSERT(0 != mEvent.mDeltaY);
+
+ // Restore the line-or-page and overflow values to be vertical.
+ mEvent.mOverflowDeltaY = mEvent.mOverflowDeltaX;
+ mEvent.mLineOrPageDeltaY = mEvent.mLineOrPageDeltaX;
+ if (forRTL) {
+ mEvent.mOverflowDeltaY *= -1;
+ mEvent.mLineOrPageDeltaY *= -1;
+ }
+ mEvent.mOverflowDeltaX = mOldOverflowDeltaX;
+ mEvent.mLineOrPageDeltaX = mOldLineOrPageDeltaX;
+ }
+}
+
+} // namespace mozilla