tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

WheelHandlingHelper.cpp (31656B)


      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/. */
      6 
      7 #include "WheelHandlingHelper.h"
      8 
      9 #include <utility>  // for std::swap
     10 
     11 #include "DocumentInlines.h"  // for Document and HTMLBodyElement
     12 #include "ScrollAnimationPhysics.h"
     13 #include "Units.h"
     14 #include "mozilla/EventDispatcher.h"
     15 #include "mozilla/EventStateManager.h"
     16 #include "mozilla/MouseEvents.h"
     17 #include "mozilla/Preferences.h"
     18 #include "mozilla/PresShell.h"
     19 #include "mozilla/ScrollContainerFrame.h"
     20 #include "mozilla/StaticPrefs_mousewheel.h"
     21 #include "mozilla/StaticPrefs_test.h"
     22 #include "mozilla/TextControlElement.h"
     23 #include "mozilla/dom/Document.h"
     24 #include "mozilla/dom/WheelEventBinding.h"
     25 #include "nsCOMPtr.h"
     26 #include "nsContentUtils.h"
     27 #include "nsIContent.h"
     28 #include "nsIContentInlines.h"
     29 #include "nsITimer.h"
     30 #include "nsPresContext.h"
     31 #include "prtime.h"
     32 
     33 static mozilla::LazyLogModule sWheelTransactionLog("dom.wheeltransaction");
     34 #define WTXN_LOG(...) \
     35  MOZ_LOG(sWheelTransactionLog, LogLevel::Debug, (__VA_ARGS__))
     36 
     37 namespace mozilla {
     38 
     39 /******************************************************************/
     40 /* mozilla::DeltaValues                                           */
     41 /******************************************************************/
     42 
     43 DeltaValues::DeltaValues(WidgetWheelEvent* aEvent)
     44    : deltaX(aEvent->mDeltaX), deltaY(aEvent->mDeltaY) {}
     45 
     46 /******************************************************************/
     47 /* mozilla::WheelHandlingUtils                                    */
     48 /******************************************************************/
     49 
     50 /* static */
     51 bool WheelHandlingUtils::CanScrollInRange(nscoord aMin, nscoord aValue,
     52                                          nscoord aMax, double aDirection) {
     53  return aDirection > 0.0 ? aValue < static_cast<double>(aMax)
     54                          : static_cast<double>(aMin) < aValue;
     55 }
     56 
     57 /* static */
     58 bool WheelHandlingUtils::CanScrollOn(nsIFrame* aFrame, double aDirectionX,
     59                                     double aDirectionY) {
     60  ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(aFrame);
     61  if (!scrollContainerFrame) {
     62    return false;
     63  }
     64  return CanScrollOn(scrollContainerFrame, aDirectionX, aDirectionY);
     65 }
     66 
     67 /* static */
     68 bool WheelHandlingUtils::CanScrollOn(
     69    ScrollContainerFrame* aScrollContainerFrame, double aDirectionX,
     70    double aDirectionY) {
     71  MOZ_ASSERT(aScrollContainerFrame);
     72  NS_ASSERTION(aDirectionX || aDirectionY,
     73               "One of the delta values must be non-zero at least");
     74 
     75  nsPoint scrollPt = aScrollContainerFrame->GetVisualViewportOffset();
     76  nsRect scrollRange =
     77      aScrollContainerFrame->GetScrollRangeForUserInputEvents();
     78  layers::ScrollDirections directions =
     79      aScrollContainerFrame
     80          ->GetAvailableScrollingDirectionsForUserInputEvents();
     81 
     82  return ((aDirectionX != 0.0) &&
     83          (directions.contains(layers::ScrollDirection::eHorizontal)) &&
     84          CanScrollInRange(scrollRange.x, scrollPt.x, scrollRange.XMost(),
     85                           aDirectionX)) ||
     86         ((aDirectionY != 0.0) &&
     87          (directions.contains(layers::ScrollDirection::eVertical)) &&
     88          CanScrollInRange(scrollRange.y, scrollPt.y, scrollRange.YMost(),
     89                           aDirectionY));
     90 }
     91 
     92 /*static*/ Maybe<layers::ScrollDirection>
     93 WheelHandlingUtils::GetDisregardedWheelScrollDirection(const nsIFrame* aFrame) {
     94  nsIContent* content = aFrame->GetContent();
     95  if (!content) {
     96    return Nothing();
     97  }
     98  TextControlElement* textControlElement = TextControlElement::FromNodeOrNull(
     99      content->IsInNativeAnonymousSubtree()
    100          ? content->GetClosestNativeAnonymousSubtreeRootParentOrHost()
    101          : content);
    102  if (!textControlElement || !textControlElement->IsSingleLineTextControl()) {
    103    return Nothing();
    104  }
    105  // Disregard scroll in the block-flow direction by mouse wheel on a
    106  // single-line text control. For instance, in tranditional Chinese writing
    107  // system, a single-line text control cannot be scrolled horizontally with
    108  // mouse wheel even if they overflow at the right and left edges; Whereas in
    109  // latin-based writing system, a single-line text control cannot be scrolled
    110  // vertically with mouse wheel even if they overflow at the top and bottom
    111  // edges
    112  return Some(aFrame->GetWritingMode().IsVertical()
    113                  ? layers::ScrollDirection::eHorizontal
    114                  : layers::ScrollDirection::eVertical);
    115 }
    116 
    117 /******************************************************************/
    118 /* mozilla::WheelTransaction                                      */
    119 /******************************************************************/
    120 
    121 constinit AutoWeakFrame WheelTransaction::sScrollTargetFrame;
    122 constinit AutoWeakFrame WheelTransaction::sEventTargetFrame;
    123 
    124 bool WheelTransaction::sHandledByApz(false);
    125 uint32_t WheelTransaction::sTime = 0;
    126 uint32_t WheelTransaction::sMouseMoved = 0;
    127 nsITimer* WheelTransaction::sTimer = nullptr;
    128 int32_t WheelTransaction::sScrollSeriesCounter = 0;
    129 bool WheelTransaction::sOwnScrollbars = false;
    130 
    131 /* static */
    132 bool WheelTransaction::OutOfTime(uint32_t aBaseTime, uint32_t aThreshold) {
    133  uint32_t now = PR_IntervalToMilliseconds(PR_IntervalNow());
    134  return (now - aBaseTime > aThreshold);
    135 }
    136 
    137 /* static */
    138 void WheelTransaction::OwnScrollbars(bool aOwn) { sOwnScrollbars = aOwn; }
    139 
    140 /* static */
    141 void WheelTransaction::BeginTransaction(nsIFrame* aScrollTargetFrame,
    142                                        nsIFrame* aEventTargetFrame,
    143                                        const WidgetWheelEvent* aEvent) {
    144  NS_ASSERTION(!sScrollTargetFrame && !sEventTargetFrame,
    145               "previous transaction is not finished!");
    146  MOZ_ASSERT(aEvent->mMessage == eWheel,
    147             "Transaction must be started with a wheel event");
    148 
    149  ScrollbarsForWheel::OwnWheelTransaction(false);
    150  sScrollTargetFrame = aScrollTargetFrame;
    151 
    152  // Only set the static event target if wheel event groups are enabled.
    153  if (StaticPrefs::dom_event_wheel_event_groups_enabled()) {
    154    WTXN_LOG("WheelTransaction start for frame=0x%p handled-by-apz=%s",
    155             aEventTargetFrame,
    156             aEvent->mFlags.mHandledByAPZ ? "true" : "false");
    157    // Set a static event target for the wheel transaction. This will be used
    158    // to override the event target frame when computing the event target from
    159    // input coordinates. When this preference is not set or there is no stored
    160    // event target for the current wheel transaction, the event target will
    161    // not be overridden by the current wheel transaction, but will be computed
    162    // from the input coordinates.
    163    sEventTargetFrame = aEventTargetFrame;
    164    // If the wheel events will be handled by APZ, set a flag here. We can use
    165    // this later to determine if we need to scroll snap at the end of the
    166    // wheel operation.
    167    sHandledByApz = aEvent->mFlags.mHandledByAPZ;
    168  }
    169 
    170  sScrollSeriesCounter = 0;
    171  if (!UpdateTransaction(aEvent)) {
    172    NS_ERROR("BeginTransaction is called even cannot scroll the frame");
    173    EndTransaction();
    174  }
    175 }
    176 
    177 /* static */
    178 bool WheelTransaction::UpdateTransaction(const WidgetWheelEvent* aEvent) {
    179  nsIFrame* scrollToFrame = GetScrollTargetFrame();
    180  ScrollContainerFrame* scrollContainerFrame =
    181      scrollToFrame->GetScrollTargetFrame();
    182  if (scrollContainerFrame) {
    183    scrollToFrame = scrollContainerFrame;
    184  }
    185 
    186  if (!WheelHandlingUtils::CanScrollOn(scrollToFrame, aEvent->mDeltaX,
    187                                       aEvent->mDeltaY)) {
    188    OnFailToScrollTarget();
    189    // We should not modify the transaction state when the view will not be
    190    // scrolled actually.
    191    return false;
    192  }
    193 
    194  SetTimeout();
    195 
    196  if (sScrollSeriesCounter != 0 &&
    197      OutOfTime(sTime, StaticPrefs::mousewheel_scroll_series_timeout())) {
    198    sScrollSeriesCounter = 0;
    199  }
    200  sScrollSeriesCounter++;
    201 
    202  // We should use current time instead of WidgetEvent.time.
    203  // 1. Some events doesn't have the correct creation time.
    204  // 2. If the computer runs slowly by other processes eating the CPU resource,
    205  //    the event creation time doesn't keep real time.
    206  sTime = PR_IntervalToMilliseconds(PR_IntervalNow());
    207  sMouseMoved = 0;
    208  return true;
    209 }
    210 
    211 /* static */
    212 void WheelTransaction::MayEndTransaction() {
    213  if (!sOwnScrollbars && ScrollbarsForWheel::IsActive()) {
    214    ScrollbarsForWheel::OwnWheelTransaction(true);
    215  } else {
    216    EndTransaction();
    217  }
    218 }
    219 
    220 /* static */
    221 void WheelTransaction::EndTransaction() {
    222  if (sTimer) {
    223    sTimer->Cancel();
    224  }
    225  sScrollTargetFrame = nullptr;
    226  sEventTargetFrame = nullptr;
    227  sScrollSeriesCounter = 0;
    228  sHandledByApz = false;
    229  if (sOwnScrollbars) {
    230    sOwnScrollbars = false;
    231    ScrollbarsForWheel::OwnWheelTransaction(false);
    232    ScrollbarsForWheel::Inactivate();
    233  }
    234 }
    235 
    236 /* static */
    237 bool WheelTransaction::WillHandleDefaultAction(
    238    WidgetWheelEvent* aWheelEvent, AutoWeakFrame& aScrollTargetWeakFrame,
    239    AutoWeakFrame& aEventTargetWeakFrame) {
    240  nsIFrame* lastTargetFrame = GetScrollTargetFrame();
    241  if (!lastTargetFrame) {
    242    BeginTransaction(aScrollTargetWeakFrame.GetFrame(),
    243                     aEventTargetWeakFrame.GetFrame(), aWheelEvent);
    244  } else if (lastTargetFrame != aScrollTargetWeakFrame.GetFrame()) {
    245    WTXN_LOG("Wheel transaction ending due to new target frame");
    246    EndTransaction();
    247    BeginTransaction(aScrollTargetWeakFrame.GetFrame(),
    248                     aEventTargetWeakFrame.GetFrame(), aWheelEvent);
    249  } else {
    250    UpdateTransaction(aWheelEvent);
    251  }
    252 
    253  // When the wheel event will not be handled with any frames,
    254  // UpdateTransaction() fires MozMouseScrollFailed event which is for
    255  // automated testing.  In the event handler, the target frame might be
    256  // destroyed.  Then, the caller shouldn't try to handle the default action.
    257  if (!aScrollTargetWeakFrame.IsAlive()) {
    258    WTXN_LOG("Wheel transaction ending due to target frame removal");
    259    EndTransaction();
    260    return false;
    261  }
    262 
    263  return true;
    264 }
    265 
    266 /* static */
    267 void WheelTransaction::OnEvent(WidgetEvent* aEvent) {
    268  if (!sScrollTargetFrame) {
    269    return;
    270  }
    271 
    272  if (OutOfTime(sTime, StaticPrefs::mousewheel_transaction_timeout())) {
    273    // Even if the scroll event which is handled after timeout, but onTimeout
    274    // was not fired by timer, then the scroll event will scroll old frame,
    275    // therefore, we should call OnTimeout here and ensure to finish the old
    276    // transaction.
    277    OnTimeout(nullptr, nullptr);
    278    return;
    279  }
    280 
    281  switch (aEvent->mMessage) {
    282    case eWheel:
    283      if (sMouseMoved != 0 &&
    284          OutOfTime(sMouseMoved,
    285                    StaticPrefs::mousewheel_transaction_ignoremovedelay())) {
    286        // Terminate the current mousewheel transaction if the mouse moved more
    287        // than ignoremovedelay milliseconds ago
    288        WTXN_LOG("Wheel transaction ending due to transaction timeout");
    289        EndTransaction();
    290      }
    291      return;
    292    case eMouseMove:
    293    case eDragOver: {
    294      WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
    295      if (mouseEvent->IsReal()) {
    296        // If the cursor is moving to be outside the frame,
    297        // terminate the scrollwheel transaction.
    298        LayoutDeviceIntPoint pt = GetScreenPoint(mouseEvent);
    299        auto r = LayoutDeviceIntRect::FromAppUnitsToNearest(
    300            sScrollTargetFrame->GetScreenRectInAppUnits(),
    301            sScrollTargetFrame->PresContext()->AppUnitsPerDevPixel());
    302        if (!r.Contains(pt)) {
    303          WTXN_LOG("Wheel transaction ending due to mousemove");
    304          EndTransaction();
    305          return;
    306        }
    307 
    308        // For mouse move events where the wheel transaction is still valid, the
    309        // stored event target should be reset.
    310        sEventTargetFrame = nullptr;
    311 
    312        // If the cursor is moving inside the frame, and it is less than
    313        // ignoremovedelay milliseconds since the last scroll operation, ignore
    314        // the mouse move; otherwise, record the current mouse move time to be
    315        // checked later
    316        if (!sMouseMoved &&
    317            OutOfTime(sTime,
    318                      StaticPrefs::mousewheel_transaction_ignoremovedelay())) {
    319          sMouseMoved = PR_IntervalToMilliseconds(PR_IntervalNow());
    320        }
    321      }
    322      return;
    323    }
    324    case eKeyPress:
    325    case eKeyUp:
    326    case eKeyDown:
    327    case eMouseUp:
    328    case eMouseDown:
    329    case eMouseDoubleClick:
    330    case ePointerAuxClick:
    331    case ePointerClick:
    332    case eContextMenu:
    333    case eDrop:
    334      WTXN_LOG("Wheel transaction ending due to keyboard event");
    335      EndTransaction();
    336      return;
    337    default:
    338      break;
    339  }
    340 }
    341 
    342 /* static */
    343 void WheelTransaction::OnRemoveElement(nsIContent* aContent) {
    344  // If dom.event.wheel-event-groups.enabled is not set or we have no current
    345  // wheel event transaction there is no internal state to be updated.
    346  if (!sEventTargetFrame) {
    347    return;
    348  }
    349 
    350  if (sEventTargetFrame->GetContent() == aContent) {
    351    // Only invalidate the wheel transaction event target frame when the
    352    // remove target is the event target of the wheel event group. The
    353    // scroll target frame of the wheel event group may still be valid.
    354    //
    355    // With the stored event target unset, the target for any following
    356    // events will be the frame found using the input coordinates.
    357    sEventTargetFrame = nullptr;
    358  }
    359 }
    360 
    361 /* static */
    362 void WheelTransaction::Shutdown() { NS_IF_RELEASE(sTimer); }
    363 
    364 /* static */
    365 void WheelTransaction::OnFailToScrollTarget() {
    366  MOZ_ASSERT(sScrollTargetFrame, "We don't have mouse scrolling transaction");
    367 
    368  if (StaticPrefs::test_mousescroll()) {
    369    // This event is used for automated tests, see bug 442774.
    370    nsContentUtils::DispatchEventOnlyToChrome(
    371        sScrollTargetFrame->GetContent()->OwnerDoc(),
    372        sScrollTargetFrame->GetContent(), u"MozMouseScrollFailed"_ns,
    373        CanBubble::eYes, Cancelable::eYes);
    374  }
    375  // The target frame might be destroyed in the event handler, at that time,
    376  // we need to finish the current transaction
    377  if (!sScrollTargetFrame) {
    378    WTXN_LOG("Wheel transaction ending due to failed scroll");
    379    EndTransaction();
    380  }
    381 }
    382 
    383 /* static */
    384 void WheelTransaction::OnTimeout(nsITimer* aTimer, void* aClosure) {
    385  if (!sScrollTargetFrame) {
    386    // The transaction target was destroyed already
    387    WTXN_LOG("Wheel transaction ending due to target removal");
    388    EndTransaction();
    389    return;
    390  }
    391  WTXN_LOG("Wheel transaction may end due to timeout");
    392  // Store the sScrollTargetFrame, the variable becomes null in EndTransaction.
    393  nsIFrame* frame = sScrollTargetFrame;
    394  // We need to finish current transaction before DOM event firing. Because
    395  // the next DOM event might create strange situation for us.
    396  MayEndTransaction();
    397 
    398  if (StaticPrefs::test_mousescroll()) {
    399    // This event is used for automated tests, see bug 442774.
    400    nsContentUtils::DispatchEventOnlyToChrome(
    401        frame->GetContent()->OwnerDoc(), frame->GetContent(),
    402        u"MozMouseScrollTransactionTimeout"_ns, CanBubble::eYes,
    403        Cancelable::eYes);
    404  }
    405 }
    406 
    407 /* static */
    408 void WheelTransaction::SetTimeout() {
    409  if (!sTimer) {
    410    sTimer = NS_NewTimer().take();
    411    if (!sTimer) {
    412      return;
    413    }
    414  }
    415  sTimer->Cancel();
    416  DebugOnly<nsresult> rv = sTimer->InitWithNamedFuncCallback(
    417      OnTimeout, nullptr, StaticPrefs::mousewheel_transaction_timeout(),
    418      nsITimer::TYPE_ONE_SHOT, "WheelTransaction::SetTimeout"_ns);
    419  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    420                       "nsITimer::InitWithNamedFuncCallback failed");
    421 }
    422 
    423 /* static */
    424 LayoutDeviceIntPoint WheelTransaction::GetScreenPoint(WidgetGUIEvent* aEvent) {
    425  NS_ASSERTION(aEvent, "aEvent is null");
    426  NS_ASSERTION(aEvent->mWidget, "aEvent-mWidget is null");
    427  return aEvent->mRefPoint + aEvent->mWidget->WidgetToScreenOffset();
    428 }
    429 
    430 /* static */
    431 DeltaValues WheelTransaction::AccelerateWheelDelta(WidgetWheelEvent* aEvent) {
    432  DeltaValues result = OverrideSystemScrollSpeed(aEvent);
    433 
    434  // Don't accelerate the delta values if the event isn't line scrolling.
    435  if (aEvent->mDeltaMode != dom::WheelEvent_Binding::DOM_DELTA_LINE) {
    436    return result;
    437  }
    438 
    439  // Accelerate by the sScrollSeriesCounter
    440  int32_t start = StaticPrefs::mousewheel_acceleration_start();
    441  if (start >= 0 && sScrollSeriesCounter >= start) {
    442    int32_t factor = StaticPrefs::mousewheel_acceleration_factor();
    443    if (factor > 0) {
    444      result.deltaX = ComputeAcceleratedWheelDelta(result.deltaX, factor);
    445      result.deltaY = ComputeAcceleratedWheelDelta(result.deltaY, factor);
    446    }
    447  }
    448 
    449  return result;
    450 }
    451 
    452 /* static */
    453 double WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta,
    454                                                      int32_t aFactor) {
    455  return mozilla::ComputeAcceleratedWheelDelta(aDelta, sScrollSeriesCounter,
    456                                               aFactor);
    457 }
    458 
    459 /* static */
    460 DeltaValues WheelTransaction::OverrideSystemScrollSpeed(
    461    WidgetWheelEvent* aEvent) {
    462  MOZ_ASSERT(sScrollTargetFrame, "We don't have mouse scrolling transaction");
    463 
    464  // If the event doesn't scroll to both X and Y, we don't need to do anything
    465  // here.
    466  if (!aEvent->mDeltaX && !aEvent->mDeltaY) {
    467    return DeltaValues(aEvent);
    468  }
    469 
    470  return DeltaValues(aEvent->OverriddenDeltaX(), aEvent->OverriddenDeltaY());
    471 }
    472 
    473 /******************************************************************/
    474 /* mozilla::ScrollbarsForWheel                                    */
    475 /******************************************************************/
    476 
    477 constinit AutoWeakFrame ScrollbarsForWheel::sActiveOwner;
    478 constinit AutoWeakFrame
    479    ScrollbarsForWheel::sActivatedScrollTargets[kNumberOfTargets];
    480 
    481 bool ScrollbarsForWheel::sHadWheelStart = false;
    482 bool ScrollbarsForWheel::sOwnWheelTransaction = false;
    483 
    484 /* static */
    485 void ScrollbarsForWheel::PrepareToScrollText(EventStateManager* aESM,
    486                                             nsIFrame* aTargetFrame,
    487                                             WidgetWheelEvent* aEvent) {
    488  if (aEvent->mMessage == eWheelOperationStart) {
    489    WheelTransaction::OwnScrollbars(false);
    490    if (!IsActive()) {
    491      TemporarilyActivateAllPossibleScrollTargets(aESM, aTargetFrame, aEvent);
    492      sHadWheelStart = true;
    493    }
    494  } else {
    495    DeactivateAllTemporarilyActivatedScrollTargets();
    496  }
    497 }
    498 
    499 /* static */
    500 void ScrollbarsForWheel::SetActiveScrollTarget(
    501    ScrollContainerFrame* aScrollTarget) {
    502  if (!sHadWheelStart) {
    503    return;
    504  }
    505  if (!aScrollTarget) {
    506    return;
    507  }
    508  sHadWheelStart = false;
    509  sActiveOwner = aScrollTarget;
    510  aScrollTarget->ScrollbarActivityStarted();
    511 }
    512 
    513 /* static */
    514 void ScrollbarsForWheel::MayInactivate() {
    515  if (!sOwnWheelTransaction && WheelTransaction::GetScrollTargetFrame()) {
    516    WheelTransaction::OwnScrollbars(true);
    517  } else {
    518    Inactivate();
    519  }
    520 }
    521 
    522 /* static */
    523 void ScrollbarsForWheel::Inactivate() {
    524  nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sActiveOwner);
    525  if (scrollbarMediator) {
    526    scrollbarMediator->ScrollbarActivityStopped();
    527  }
    528  sActiveOwner = nullptr;
    529  DeactivateAllTemporarilyActivatedScrollTargets();
    530  if (sOwnWheelTransaction) {
    531    WTXN_LOG("Wheel transaction ending due to inactive scrollbar");
    532    sOwnWheelTransaction = false;
    533    WheelTransaction::OwnScrollbars(false);
    534    WheelTransaction::EndTransaction();
    535  }
    536 }
    537 
    538 /* static */
    539 bool ScrollbarsForWheel::IsActive() {
    540  if (sActiveOwner) {
    541    return true;
    542  }
    543  for (size_t i = 0; i < kNumberOfTargets; ++i) {
    544    if (sActivatedScrollTargets[i]) {
    545      return true;
    546    }
    547  }
    548  return false;
    549 }
    550 
    551 /* static */
    552 void ScrollbarsForWheel::OwnWheelTransaction(bool aOwn) {
    553  sOwnWheelTransaction = aOwn;
    554 }
    555 
    556 /* static */
    557 void ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets(
    558    EventStateManager* aESM, nsIFrame* aTargetFrame, WidgetWheelEvent* aEvent) {
    559  for (size_t i = 0; i < kNumberOfTargets; i++) {
    560    const DeltaValues* dir = &directions[i];
    561    AutoWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
    562    MOZ_ASSERT(!*scrollTarget, "scroll target still temporarily activated!");
    563    ScrollContainerFrame* target = aESM->ComputeScrollTarget(
    564        aTargetFrame, dir->deltaX, dir->deltaY, aEvent,
    565        EventStateManager::COMPUTE_DEFAULT_ACTION_TARGET);
    566    if (target) {
    567      *scrollTarget = target;
    568      target->ScrollbarActivityStarted();
    569    }
    570  }
    571 }
    572 
    573 /* static */
    574 void ScrollbarsForWheel::DeactivateAllTemporarilyActivatedScrollTargets() {
    575  for (size_t i = 0; i < kNumberOfTargets; i++) {
    576    AutoWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
    577    if (*scrollTarget) {
    578      nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(*scrollTarget);
    579      if (scrollbarMediator) {
    580        scrollbarMediator->ScrollbarActivityStopped();
    581      }
    582      *scrollTarget = nullptr;
    583    }
    584  }
    585 }
    586 
    587 /******************************************************************/
    588 /* mozilla::WheelDeltaHorizontalizer                              */
    589 /******************************************************************/
    590 
    591 void WheelDeltaHorizontalizer::Horizontalize() {
    592  MOZ_ASSERT(!mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler,
    593             "Wheel delta values in one wheel scroll event are being adjusted "
    594             "a second time");
    595 
    596  // Log the old values.
    597  mOldDeltaX = mWheelEvent.mDeltaX;
    598  mOldDeltaZ = mWheelEvent.mDeltaZ;
    599  mOldOverflowDeltaX = mWheelEvent.mOverflowDeltaX;
    600  mOldLineOrPageDeltaX = mWheelEvent.mLineOrPageDeltaX;
    601 
    602  // Move deltaY values to deltaX and set both deltaY and deltaZ to 0.
    603  mWheelEvent.mDeltaX = mWheelEvent.mDeltaY;
    604  mWheelEvent.mDeltaY = 0.0;
    605  mWheelEvent.mDeltaZ = 0.0;
    606  mWheelEvent.mOverflowDeltaX = mWheelEvent.mOverflowDeltaY;
    607  mWheelEvent.mOverflowDeltaY = 0.0;
    608  mWheelEvent.mLineOrPageDeltaX = mWheelEvent.mLineOrPageDeltaY;
    609  mWheelEvent.mLineOrPageDeltaY = 0;
    610 
    611  // Mark it horizontalized in order to restore the delta values when this
    612  // instance is being destroyed.
    613  mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler = true;
    614  mHorizontalized = true;
    615 }
    616 
    617 void WheelDeltaHorizontalizer::CancelHorizontalization() {
    618  // Restore the horizontalized delta.
    619  if (mHorizontalized &&
    620      mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler) {
    621    mWheelEvent.mDeltaY = mWheelEvent.mDeltaX;
    622    mWheelEvent.mDeltaX = mOldDeltaX;
    623    mWheelEvent.mDeltaZ = mOldDeltaZ;
    624    mWheelEvent.mOverflowDeltaY = mWheelEvent.mOverflowDeltaX;
    625    mWheelEvent.mOverflowDeltaX = mOldOverflowDeltaX;
    626    mWheelEvent.mLineOrPageDeltaY = mWheelEvent.mLineOrPageDeltaX;
    627    mWheelEvent.mLineOrPageDeltaX = mOldLineOrPageDeltaX;
    628    mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler = false;
    629    mHorizontalized = false;
    630  }
    631 }
    632 
    633 WheelDeltaHorizontalizer::~WheelDeltaHorizontalizer() {
    634  CancelHorizontalization();
    635 }
    636 
    637 /******************************************************************/
    638 /* mozilla::AutoDirWheelDeltaAdjuster                             */
    639 /******************************************************************/
    640 
    641 bool AutoDirWheelDeltaAdjuster::ShouldBeAdjusted() {
    642  // Sometimes, this function can be called more than one time. If we have
    643  // already checked if the scroll should be adjusted, there's no need to check
    644  // it again.
    645  if (mCheckedIfShouldBeAdjusted) {
    646    return mShouldBeAdjusted;
    647  }
    648  mCheckedIfShouldBeAdjusted = true;
    649 
    650  // For an auto-dir wheel scroll, if all the following conditions are met, we
    651  // should adjust X and Y values:
    652  // 1. There is only one non-zero value between DeltaX and DeltaY.
    653  // 2. There is only one direction for the target that overflows and is
    654  //    scrollable with wheel.
    655  // 3. The direction described in Condition 1 is orthogonal to the one
    656  // described in Condition 2.
    657  if ((mDeltaX && mDeltaY) || (!mDeltaX && !mDeltaY)) {
    658    return false;
    659  }
    660  if (mDeltaX) {
    661    if (CanScrollAlongXAxis()) {
    662      return false;
    663    }
    664    if (IsHorizontalContentRightToLeft()) {
    665      mShouldBeAdjusted =
    666          mDeltaX > 0 ? CanScrollUpwards() : CanScrollDownwards();
    667    } else {
    668      mShouldBeAdjusted =
    669          mDeltaX < 0 ? CanScrollUpwards() : CanScrollDownwards();
    670    }
    671    return mShouldBeAdjusted;
    672  }
    673  MOZ_ASSERT(0 != mDeltaY);
    674  if (CanScrollAlongYAxis()) {
    675    return false;
    676  }
    677  if (IsHorizontalContentRightToLeft()) {
    678    mShouldBeAdjusted =
    679        mDeltaY > 0 ? CanScrollLeftwards() : CanScrollRightwards();
    680  } else {
    681    mShouldBeAdjusted =
    682        mDeltaY < 0 ? CanScrollLeftwards() : CanScrollRightwards();
    683  }
    684  return mShouldBeAdjusted;
    685 }
    686 
    687 void AutoDirWheelDeltaAdjuster::Adjust() {
    688  if (!ShouldBeAdjusted()) {
    689    return;
    690  }
    691  std::swap(mDeltaX, mDeltaY);
    692  if (IsHorizontalContentRightToLeft()) {
    693    mDeltaX *= -1;
    694    mDeltaY *= -1;
    695  }
    696  mShouldBeAdjusted = false;
    697  OnAdjusted();
    698 }
    699 
    700 /******************************************************************/
    701 /* mozilla::ESMAutoDirWheelDeltaAdjuster                          */
    702 /******************************************************************/
    703 
    704 ESMAutoDirWheelDeltaAdjuster::ESMAutoDirWheelDeltaAdjuster(
    705    WidgetWheelEvent& aEvent, nsIFrame& aScrollFrame, bool aHonoursRoot)
    706    : AutoDirWheelDeltaAdjuster(aEvent.mDeltaX, aEvent.mDeltaY),
    707      mLineOrPageDeltaX(aEvent.mLineOrPageDeltaX),
    708      mLineOrPageDeltaY(aEvent.mLineOrPageDeltaY),
    709      mOverflowDeltaX(aEvent.mOverflowDeltaX),
    710      mOverflowDeltaY(aEvent.mOverflowDeltaY) {
    711  mScrollTargetFrame = aScrollFrame.GetScrollTargetFrame();
    712  MOZ_ASSERT(mScrollTargetFrame);
    713 
    714  nsIFrame* honouredFrame = nullptr;
    715  if (aHonoursRoot) {
    716    // If we are going to honour root, first try to get the frame for <body> as
    717    // the honoured root, because <body> is in preference to <html> if the
    718    // current document is an HTML document.
    719    dom::Document* document = aScrollFrame.PresShell()->GetDocument();
    720    if (document) {
    721      dom::Element* bodyElement = document->GetBodyElement();
    722      if (bodyElement) {
    723        honouredFrame = bodyElement->GetPrimaryFrame();
    724      }
    725    }
    726 
    727    if (!honouredFrame) {
    728      // If there is no <body> frame, fall back to the real root frame.
    729      honouredFrame = aScrollFrame.PresShell()->GetRootScrollContainerFrame();
    730    }
    731 
    732    if (!honouredFrame) {
    733      // If there is no root scroll frame, fall back to the current scrolling
    734      // frame.
    735      honouredFrame = &aScrollFrame;
    736    }
    737  } else {
    738    honouredFrame = &aScrollFrame;
    739  }
    740 
    741  WritingMode writingMode = honouredFrame->GetWritingMode();
    742  // Get whether the honoured frame's content in the horizontal direction starts
    743  // from right to left(E.g. it's true either if "writing-mode: vertical-rl", or
    744  // if "writing-mode: horizontal-tb; direction: rtl;" in CSS).
    745  mIsHorizontalContentRightToLeft = writingMode.IsPhysicalRTL();
    746 }
    747 
    748 void ESMAutoDirWheelDeltaAdjuster::OnAdjusted() {
    749  // Adjust() only adjusted basic deltaX and deltaY, which are not enough for
    750  // ESM, we should continue to adjust line-or-page and overflow values.
    751  if (mDeltaX) {
    752    // A vertical scroll was adjusted to be horizontal.
    753    MOZ_ASSERT(0 == mDeltaY);
    754 
    755    mLineOrPageDeltaX = mLineOrPageDeltaY;
    756    mLineOrPageDeltaY = 0;
    757    mOverflowDeltaX = mOverflowDeltaY;
    758    mOverflowDeltaY = 0;
    759  } else {
    760    // A horizontal scroll was adjusted to be vertical.
    761    MOZ_ASSERT(0 != mDeltaY);
    762 
    763    mLineOrPageDeltaY = mLineOrPageDeltaX;
    764    mLineOrPageDeltaX = 0;
    765    mOverflowDeltaY = mOverflowDeltaX;
    766    mOverflowDeltaX = 0;
    767  }
    768  if (mIsHorizontalContentRightToLeft) {
    769    // If in RTL writing mode, reverse the side the scroll will go towards.
    770    mLineOrPageDeltaX *= -1;
    771    mLineOrPageDeltaY *= -1;
    772    mOverflowDeltaX *= -1;
    773    mOverflowDeltaY *= -1;
    774  }
    775 }
    776 
    777 bool ESMAutoDirWheelDeltaAdjuster::CanScrollAlongXAxis() const {
    778  return mScrollTargetFrame->GetAvailableScrollingDirections().contains(
    779      layers::ScrollDirection::eHorizontal);
    780 }
    781 
    782 bool ESMAutoDirWheelDeltaAdjuster::CanScrollAlongYAxis() const {
    783  return mScrollTargetFrame->GetAvailableScrollingDirections().contains(
    784      layers::ScrollDirection::eVertical);
    785 }
    786 
    787 bool ESMAutoDirWheelDeltaAdjuster::CanScrollUpwards() const {
    788  nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
    789  nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
    790  return static_cast<double>(scrollRange.y) < scrollPt.y;
    791 }
    792 
    793 bool ESMAutoDirWheelDeltaAdjuster::CanScrollDownwards() const {
    794  nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
    795  nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
    796  return static_cast<double>(scrollRange.YMost()) > scrollPt.y;
    797 }
    798 
    799 bool ESMAutoDirWheelDeltaAdjuster::CanScrollLeftwards() const {
    800  nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
    801  nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
    802  return static_cast<double>(scrollRange.x) < scrollPt.x;
    803 }
    804 
    805 bool ESMAutoDirWheelDeltaAdjuster::CanScrollRightwards() const {
    806  nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
    807  nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
    808  return static_cast<double>(scrollRange.XMost()) > scrollPt.x;
    809 }
    810 
    811 bool ESMAutoDirWheelDeltaAdjuster::IsHorizontalContentRightToLeft() const {
    812  return mIsHorizontalContentRightToLeft;
    813 }
    814 
    815 /******************************************************************/
    816 /* mozilla::ESMAutoDirWheelDeltaRestorer                          */
    817 /******************************************************************/
    818 
    819 /*explicit*/
    820 ESMAutoDirWheelDeltaRestorer::ESMAutoDirWheelDeltaRestorer(
    821    WidgetWheelEvent& aEvent)
    822    : mEvent(aEvent),
    823      mOldDeltaX(aEvent.mDeltaX),
    824      mOldDeltaY(aEvent.mDeltaY),
    825      mOldLineOrPageDeltaX(aEvent.mLineOrPageDeltaX),
    826      mOldLineOrPageDeltaY(aEvent.mLineOrPageDeltaY),
    827      mOldOverflowDeltaX(aEvent.mOverflowDeltaX),
    828      mOldOverflowDeltaY(aEvent.mOverflowDeltaY) {}
    829 
    830 ESMAutoDirWheelDeltaRestorer::~ESMAutoDirWheelDeltaRestorer() {
    831  if (mOldDeltaX == mEvent.mDeltaX || mOldDeltaY == mEvent.mDeltaY) {
    832    // The delta of the event wasn't adjusted during the lifetime of this
    833    // |ESMAutoDirWheelDeltaRestorer| instance. No need to restore it.
    834    return;
    835  }
    836 
    837  bool forRTL = false;
    838 
    839  // First, restore the basic deltaX and deltaY.
    840  std::swap(mEvent.mDeltaX, mEvent.mDeltaY);
    841  if (mOldDeltaX != mEvent.mDeltaX || mOldDeltaY != mEvent.mDeltaY) {
    842    // If X and Y still don't equal to their original values after being
    843    // swapped, then it must be because they were adjusted for RTL.
    844    forRTL = true;
    845    mEvent.mDeltaX *= -1;
    846    mEvent.mDeltaY *= -1;
    847    MOZ_ASSERT(mOldDeltaX == mEvent.mDeltaX && mOldDeltaY == mEvent.mDeltaY);
    848  }
    849 
    850  if (mEvent.mDeltaX) {
    851    // A horizontal scroll was adjusted to be vertical during the lifetime of
    852    // this instance.
    853    MOZ_ASSERT(0 == mEvent.mDeltaY);
    854 
    855    // Restore the line-or-page and overflow values to be horizontal.
    856    mEvent.mOverflowDeltaX = mEvent.mOverflowDeltaY;
    857    mEvent.mLineOrPageDeltaX = mEvent.mLineOrPageDeltaY;
    858    if (forRTL) {
    859      mEvent.mOverflowDeltaX *= -1;
    860      mEvent.mLineOrPageDeltaX *= -1;
    861    }
    862    mEvent.mOverflowDeltaY = mOldOverflowDeltaY;
    863    mEvent.mLineOrPageDeltaY = mOldLineOrPageDeltaY;
    864  } else {
    865    // A vertical scroll was adjusted to be horizontal during the lifetime of
    866    // this instance.
    867    MOZ_ASSERT(0 != mEvent.mDeltaY);
    868 
    869    // Restore the line-or-page and overflow values to be vertical.
    870    mEvent.mOverflowDeltaY = mEvent.mOverflowDeltaX;
    871    mEvent.mLineOrPageDeltaY = mEvent.mLineOrPageDeltaX;
    872    if (forRTL) {
    873      mEvent.mOverflowDeltaY *= -1;
    874      mEvent.mLineOrPageDeltaY *= -1;
    875    }
    876    mEvent.mOverflowDeltaX = mOldOverflowDeltaX;
    877    mEvent.mLineOrPageDeltaX = mOldLineOrPageDeltaX;
    878  }
    879 }
    880 
    881 }  // namespace mozilla