dom/events/WheelHandlingHelper.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ts=2 sw=2 et 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 "mozilla/EventDispatcher.h"
    10 #include "mozilla/EventStateManager.h"
    11 #include "mozilla/MouseEvents.h"
    12 #include "mozilla/Preferences.h"
    13 #include "nsCOMPtr.h"
    14 #include "nsContentUtils.h"
    15 #include "nsIContent.h"
    16 #include "nsIDocument.h"
    17 #include "nsIPresShell.h"
    18 #include "nsIScrollableFrame.h"
    19 #include "nsITimer.h"
    20 #include "nsPresContext.h"
    21 #include "prtime.h"
    22 #include "Units.h"
    24 namespace mozilla {
    26 /******************************************************************/
    27 /* mozilla::DeltaValues                                           */
    28 /******************************************************************/
    30 DeltaValues::DeltaValues(WidgetWheelEvent* aEvent)
    31   : deltaX(aEvent->deltaX)
    32   , deltaY(aEvent->deltaY)
    33 {
    34 }
    36 /******************************************************************/
    37 /* mozilla::WheelHandlingUtils                                    */
    38 /******************************************************************/
    40 /* static */ bool
    41 WheelHandlingUtils::CanScrollInRange(nscoord aMin, nscoord aValue, nscoord aMax,
    42                                      double aDirection)
    43 {
    44   return aDirection > 0.0 ? aValue < static_cast<double>(aMax) :
    45                             static_cast<double>(aMin) < aValue;
    46 }
    48 /* static */ bool
    49 WheelHandlingUtils::CanScrollOn(nsIScrollableFrame* aScrollFrame,
    50                                 double aDirectionX, double aDirectionY)
    51 {
    52   MOZ_ASSERT(aScrollFrame);
    53   NS_ASSERTION(aDirectionX || aDirectionY,
    54                "One of the delta values must be non-zero at least");
    56   nsPoint scrollPt = aScrollFrame->GetScrollPosition();
    57   nsRect scrollRange = aScrollFrame->GetScrollRange();
    58   uint32_t directions = aScrollFrame->GetPerceivedScrollingDirections();
    60   return (aDirectionX && (directions & nsIScrollableFrame::HORIZONTAL) &&
    61           CanScrollInRange(scrollRange.x, scrollPt.x,
    62                            scrollRange.XMost(), aDirectionX)) ||
    63          (aDirectionY && (directions & nsIScrollableFrame::VERTICAL) &&
    64           CanScrollInRange(scrollRange.y, scrollPt.y,
    65                            scrollRange.YMost(), aDirectionY));
    66 }
    68 /******************************************************************/
    69 /* mozilla::WheelTransaction                                      */
    70 /******************************************************************/
    72 nsWeakFrame WheelTransaction::sTargetFrame(nullptr);
    73 uint32_t WheelTransaction::sTime = 0;
    74 uint32_t WheelTransaction::sMouseMoved = 0;
    75 nsITimer* WheelTransaction::sTimer = nullptr;
    76 int32_t WheelTransaction::sScrollSeriesCounter = 0;
    77 bool WheelTransaction::sOwnScrollbars = false;
    79 /* static */ bool
    80 WheelTransaction::OutOfTime(uint32_t aBaseTime, uint32_t aThreshold)
    81 {
    82   uint32_t now = PR_IntervalToMilliseconds(PR_IntervalNow());
    83   return (now - aBaseTime > aThreshold);
    84 }
    86 /* static */ void
    87 WheelTransaction::OwnScrollbars(bool aOwn)
    88 {
    89   sOwnScrollbars = aOwn;
    90 }
    92 /* static */ void
    93 WheelTransaction::BeginTransaction(nsIFrame* aTargetFrame,
    94                                    WidgetWheelEvent* aEvent)
    95 {
    96   NS_ASSERTION(!sTargetFrame, "previous transaction is not finished!");
    97   MOZ_ASSERT(aEvent->message == NS_WHEEL_WHEEL,
    98              "Transaction must be started with a wheel event");
    99   ScrollbarsForWheel::OwnWheelTransaction(false);
   100   sTargetFrame = aTargetFrame;
   101   sScrollSeriesCounter = 0;
   102   if (!UpdateTransaction(aEvent)) {
   103     NS_ERROR("BeginTransaction is called even cannot scroll the frame");
   104     EndTransaction();
   105   }
   106 }
   108 /* static */ bool
   109 WheelTransaction::UpdateTransaction(WidgetWheelEvent* aEvent)
   110 {
   111   nsIScrollableFrame* sf = GetTargetFrame()->GetScrollTargetFrame();
   112   NS_ENSURE_TRUE(sf, false);
   114   if (!WheelHandlingUtils::CanScrollOn(sf, aEvent->deltaX, aEvent->deltaY)) {
   115     OnFailToScrollTarget();
   116     // We should not modify the transaction state when the view will not be
   117     // scrolled actually.
   118     return false;
   119   }
   121   SetTimeout();
   123   if (sScrollSeriesCounter != 0 && OutOfTime(sTime, kScrollSeriesTimeout)) {
   124     sScrollSeriesCounter = 0;
   125   }
   126   sScrollSeriesCounter++;
   128   // We should use current time instead of WidgetEvent.time.
   129   // 1. Some events doesn't have the correct creation time.
   130   // 2. If the computer runs slowly by other processes eating the CPU resource,
   131   //    the event creation time doesn't keep real time.
   132   sTime = PR_IntervalToMilliseconds(PR_IntervalNow());
   133   sMouseMoved = 0;
   134   return true;
   135 }
   137 /* static */ void
   138 WheelTransaction::MayEndTransaction()
   139 {
   140   if (!sOwnScrollbars && ScrollbarsForWheel::IsActive()) {
   141     ScrollbarsForWheel::OwnWheelTransaction(true);
   142   } else {
   143     EndTransaction();
   144   }
   145 }
   147 /* static */ void
   148 WheelTransaction::EndTransaction()
   149 {
   150   if (sTimer) {
   151     sTimer->Cancel();
   152   }
   153   sTargetFrame = nullptr;
   154   sScrollSeriesCounter = 0;
   155   if (sOwnScrollbars) {
   156     sOwnScrollbars = false;
   157     ScrollbarsForWheel::OwnWheelTransaction(false);
   158     ScrollbarsForWheel::Inactivate();
   159   }
   160 }
   162 /* static */ void
   163 WheelTransaction::OnEvent(WidgetEvent* aEvent)
   164 {
   165   if (!sTargetFrame) {
   166     return;
   167   }
   169   if (OutOfTime(sTime, GetTimeoutTime())) {
   170     // Even if the scroll event which is handled after timeout, but onTimeout
   171     // was not fired by timer, then the scroll event will scroll old frame,
   172     // therefore, we should call OnTimeout here and ensure to finish the old
   173     // transaction.
   174     OnTimeout(nullptr, nullptr);
   175     return;
   176   }
   178   switch (aEvent->message) {
   179     case NS_WHEEL_WHEEL:
   180       if (sMouseMoved != 0 &&
   181           OutOfTime(sMouseMoved, GetIgnoreMoveDelayTime())) {
   182         // Terminate the current mousewheel transaction if the mouse moved more
   183         // than ignoremovedelay milliseconds ago
   184         EndTransaction();
   185       }
   186       return;
   187     case NS_MOUSE_MOVE:
   188     case NS_DRAGDROP_OVER: {
   189       WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
   190       if (mouseEvent->IsReal()) {
   191         // If the cursor is moving to be outside the frame,
   192         // terminate the scrollwheel transaction.
   193         nsIntPoint pt = GetScreenPoint(mouseEvent);
   194         nsIntRect r = sTargetFrame->GetScreenRectExternal();
   195         if (!r.Contains(pt)) {
   196           EndTransaction();
   197           return;
   198         }
   200         // If the cursor is moving inside the frame, and it is less than
   201         // ignoremovedelay milliseconds since the last scroll operation, ignore
   202         // the mouse move; otherwise, record the current mouse move time to be
   203         // checked later
   204         if (!sMouseMoved && OutOfTime(sTime, GetIgnoreMoveDelayTime())) {
   205           sMouseMoved = PR_IntervalToMilliseconds(PR_IntervalNow());
   206         }
   207       }
   208       return;
   209     }
   210     case NS_KEY_PRESS:
   211     case NS_KEY_UP:
   212     case NS_KEY_DOWN:
   213     case NS_MOUSE_BUTTON_UP:
   214     case NS_MOUSE_BUTTON_DOWN:
   215     case NS_MOUSE_DOUBLECLICK:
   216     case NS_MOUSE_CLICK:
   217     case NS_CONTEXTMENU:
   218     case NS_DRAGDROP_DROP:
   219       EndTransaction();
   220       return;
   221   }
   222 }
   224 /* static */ void
   225 WheelTransaction::Shutdown()
   226 {
   227   NS_IF_RELEASE(sTimer);
   228 }
   230 /* static */ void
   231 WheelTransaction::OnFailToScrollTarget()
   232 {
   233   NS_PRECONDITION(sTargetFrame, "We don't have mouse scrolling transaction");
   235   if (Preferences::GetBool("test.mousescroll", false)) {
   236     // This event is used for automated tests, see bug 442774.
   237     nsContentUtils::DispatchTrustedEvent(
   238                       sTargetFrame->GetContent()->OwnerDoc(),
   239                       sTargetFrame->GetContent(),
   240                       NS_LITERAL_STRING("MozMouseScrollFailed"),
   241                       true, true);
   242   }
   243   // The target frame might be destroyed in the event handler, at that time,
   244   // we need to finish the current transaction
   245   if (!sTargetFrame) {
   246     EndTransaction();
   247   }
   248 }
   250 /* static */ void
   251 WheelTransaction::OnTimeout(nsITimer* aTimer, void* aClosure)
   252 {
   253   if (!sTargetFrame) {
   254     // The transaction target was destroyed already
   255     EndTransaction();
   256     return;
   257   }
   258   // Store the sTargetFrame, the variable becomes null in EndTransaction.
   259   nsIFrame* frame = sTargetFrame;
   260   // We need to finish current transaction before DOM event firing. Because
   261   // the next DOM event might create strange situation for us.
   262   MayEndTransaction();
   264   if (Preferences::GetBool("test.mousescroll", false)) {
   265     // This event is used for automated tests, see bug 442774.
   266     nsContentUtils::DispatchTrustedEvent(
   267                       frame->GetContent()->OwnerDoc(),
   268                       frame->GetContent(),
   269                       NS_LITERAL_STRING("MozMouseScrollTransactionTimeout"),
   270                       true, true);
   271   }
   272 }
   274 /* static */ void
   275 WheelTransaction::SetTimeout()
   276 {
   277   if (!sTimer) {
   278     nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
   279     if (!timer) {
   280       return;
   281     }
   282     timer.swap(sTimer);
   283   }
   284   sTimer->Cancel();
   285   DebugOnly<nsresult> rv =
   286     sTimer->InitWithFuncCallback(OnTimeout, nullptr, GetTimeoutTime(),
   287                                  nsITimer::TYPE_ONE_SHOT);
   288   NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "nsITimer::InitWithFuncCallback failed");
   289 }
   291 /* static */ nsIntPoint
   292 WheelTransaction::GetScreenPoint(WidgetGUIEvent* aEvent)
   293 {
   294   NS_ASSERTION(aEvent, "aEvent is null");
   295   NS_ASSERTION(aEvent->widget, "aEvent-widget is null");
   296   return LayoutDeviceIntPoint::ToUntyped(aEvent->refPoint) +
   297            aEvent->widget->WidgetToScreenOffset();
   298 }
   300 /* static */ uint32_t
   301 WheelTransaction::GetTimeoutTime()
   302 {
   303   return Preferences::GetUint("mousewheel.transaction.timeout", 1500);
   304 }
   306 /* static */ uint32_t
   307 WheelTransaction::GetIgnoreMoveDelayTime()
   308 {
   309   return Preferences::GetUint("mousewheel.transaction.ignoremovedelay", 100);
   310 }
   312 /* static */ DeltaValues
   313 WheelTransaction::AccelerateWheelDelta(WidgetWheelEvent* aEvent,
   314                                        bool aAllowScrollSpeedOverride)
   315 {
   316   DeltaValues result(aEvent);
   318   // Don't accelerate the delta values if the event isn't line scrolling.
   319   if (aEvent->deltaMode != nsIDOMWheelEvent::DOM_DELTA_LINE) {
   320     return result;
   321   }
   323   if (aAllowScrollSpeedOverride) {
   324     result = OverrideSystemScrollSpeed(aEvent);
   325   }
   327   // Accelerate by the sScrollSeriesCounter
   328   int32_t start = GetAccelerationStart();
   329   if (start >= 0 && sScrollSeriesCounter >= start) {
   330     int32_t factor = GetAccelerationFactor();
   331     if (factor > 0) {
   332       result.deltaX = ComputeAcceleratedWheelDelta(result.deltaX, factor);
   333       result.deltaY = ComputeAcceleratedWheelDelta(result.deltaY, factor);
   334     }
   335   }
   337   return result;
   338 }
   340 /* static */ double
   341 WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta,
   342                                                int32_t aFactor)
   343 {
   344   if (aDelta == 0.0) {
   345     return 0;
   346   }
   348   return (aDelta * sScrollSeriesCounter * (double)aFactor / 10);
   349 }
   351 /* static */ int32_t
   352 WheelTransaction::GetAccelerationStart()
   353 {
   354   return Preferences::GetInt("mousewheel.acceleration.start", -1);
   355 }
   357 /* static */ int32_t
   358 WheelTransaction::GetAccelerationFactor()
   359 {
   360   return Preferences::GetInt("mousewheel.acceleration.factor", -1);
   361 }
   363 /* static */ DeltaValues
   364 WheelTransaction::OverrideSystemScrollSpeed(WidgetWheelEvent* aEvent)
   365 {
   366   MOZ_ASSERT(sTargetFrame, "We don't have mouse scrolling transaction");
   367   MOZ_ASSERT(aEvent->deltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE);
   369   // If the event doesn't scroll to both X and Y, we don't need to do anything
   370   // here.
   371   if (!aEvent->deltaX && !aEvent->deltaY) {
   372     return DeltaValues(aEvent);
   373   }
   375   // We shouldn't override the scrolling speed on non root scroll frame.
   376   if (sTargetFrame !=
   377         sTargetFrame->PresContext()->PresShell()->GetRootScrollFrame()) {
   378     return DeltaValues(aEvent);
   379   }
   381   // Compute the overridden speed to nsIWidget.  The widget can check the
   382   // conditions (e.g., checking the prefs, and also whether the user customized
   383   // the system settings of the mouse wheel scrolling or not), and can limit
   384   // the speed for preventing the unexpected high speed scrolling.
   385   nsCOMPtr<nsIWidget> widget(sTargetFrame->GetNearestWidget());
   386   NS_ENSURE_TRUE(widget, DeltaValues(aEvent));
   387   DeltaValues overriddenDeltaValues(0.0, 0.0);
   388   nsresult rv =
   389     widget->OverrideSystemMouseScrollSpeed(aEvent->deltaX, aEvent->deltaY,
   390                                            overriddenDeltaValues.deltaX,
   391                                            overriddenDeltaValues.deltaY);
   392   return NS_FAILED(rv) ? DeltaValues(aEvent) : overriddenDeltaValues;
   393 }
   395 /******************************************************************/
   396 /* mozilla::ScrollbarsForWheel                                    */
   397 /******************************************************************/
   399 const DeltaValues ScrollbarsForWheel::directions[kNumberOfTargets] = {
   400   DeltaValues(-1, 0), DeltaValues(+1, 0), DeltaValues(0, -1), DeltaValues(0, +1)
   401 };
   403 nsWeakFrame ScrollbarsForWheel::sActiveOwner = nullptr;
   404 nsWeakFrame ScrollbarsForWheel::sActivatedScrollTargets[kNumberOfTargets] = {
   405   nullptr, nullptr, nullptr, nullptr
   406 };
   408 bool ScrollbarsForWheel::sHadWheelStart = false;
   409 bool ScrollbarsForWheel::sOwnWheelTransaction = false;
   411 /* static */ void
   412 ScrollbarsForWheel::PrepareToScrollText(EventStateManager* aESM,
   413                                         nsIFrame* aTargetFrame,
   414                                         WidgetWheelEvent* aEvent)
   415 {
   416   if (aEvent->message == NS_WHEEL_START) {
   417     WheelTransaction::OwnScrollbars(false);
   418     if (!IsActive()) {
   419       TemporarilyActivateAllPossibleScrollTargets(aESM, aTargetFrame, aEvent);
   420       sHadWheelStart = true;
   421     }
   422   } else {
   423     DeactivateAllTemporarilyActivatedScrollTargets();
   424   }
   425 }
   427 /* static */ void
   428 ScrollbarsForWheel::SetActiveScrollTarget(nsIScrollableFrame* aScrollTarget)
   429 {
   430   if (!sHadWheelStart) {
   431     return;
   432   }
   433   nsIScrollbarOwner* scrollbarOwner = do_QueryFrame(aScrollTarget);
   434   if (!scrollbarOwner) {
   435     return;
   436   }
   437   sHadWheelStart = false;
   438   sActiveOwner = do_QueryFrame(aScrollTarget);
   439   scrollbarOwner->ScrollbarActivityStarted();
   440 }
   442 /* static */ void
   443 ScrollbarsForWheel::MayInactivate()
   444 {
   445   if (!sOwnWheelTransaction && WheelTransaction::GetTargetFrame()) {
   446     WheelTransaction::OwnScrollbars(true);
   447   } else {
   448     Inactivate();
   449   }
   450 }
   452 /* static */ void
   453 ScrollbarsForWheel::Inactivate()
   454 {
   455   nsIScrollbarOwner* scrollbarOwner = do_QueryFrame(sActiveOwner);
   456   if (scrollbarOwner) {
   457     scrollbarOwner->ScrollbarActivityStopped();
   458   }
   459   sActiveOwner = nullptr;
   460   DeactivateAllTemporarilyActivatedScrollTargets();
   461   if (sOwnWheelTransaction) {
   462     sOwnWheelTransaction = false;
   463     WheelTransaction::OwnScrollbars(false);
   464     WheelTransaction::EndTransaction();
   465   }
   466 }
   468 /* static */ bool
   469 ScrollbarsForWheel::IsActive()
   470 {
   471   if (sActiveOwner) {
   472     return true;
   473   }
   474   for (size_t i = 0; i < kNumberOfTargets; ++i) {
   475     if (sActivatedScrollTargets[i]) {
   476       return true;
   477     }
   478   }
   479   return false;
   480 }
   482 /* static */ void
   483 ScrollbarsForWheel::OwnWheelTransaction(bool aOwn)
   484 {
   485   sOwnWheelTransaction = aOwn;
   486 }
   488 /* static */ void
   489 ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets(
   490                       EventStateManager* aESM,
   491                       nsIFrame* aTargetFrame,
   492                       WidgetWheelEvent* aEvent)
   493 {
   494   for (size_t i = 0; i < kNumberOfTargets; i++) {
   495     const DeltaValues *dir = &directions[i];
   496     nsWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
   497     MOZ_ASSERT(!*scrollTarget, "scroll target still temporarily activated!");
   498     nsIScrollableFrame* target =
   499       aESM->ComputeScrollTarget(aTargetFrame, dir->deltaX, dir->deltaY, aEvent,
   500               EventStateManager::COMPUTE_DEFAULT_ACTION_TARGET);
   501     nsIScrollbarOwner* scrollbarOwner = do_QueryFrame(target);
   502     if (scrollbarOwner) {
   503       nsIFrame* targetFrame = do_QueryFrame(target);
   504       *scrollTarget = targetFrame;
   505       scrollbarOwner->ScrollbarActivityStarted();
   506     }
   507   }
   508 }
   510 /* static */ void
   511 ScrollbarsForWheel::DeactivateAllTemporarilyActivatedScrollTargets()
   512 {
   513   for (size_t i = 0; i < kNumberOfTargets; i++) {
   514     nsWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
   515     if (*scrollTarget) {
   516       nsIScrollbarOwner* scrollbarOwner = do_QueryFrame(*scrollTarget);
   517       if (scrollbarOwner) {
   518         scrollbarOwner->ScrollbarActivityStopped();
   519       }
   520       *scrollTarget = nullptr;
   521     }
   522   }
   523 }
   525 } // namespace mozilla

mercurial