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 "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 "nsPluginFrame.h"
21 #include "nsPresContext.h"
22 #include "prtime.h"
23 #include "Units.h"
24 #include "AsyncScrollBase.h"
25 
26 namespace mozilla {
27 
28 /******************************************************************/
29 /* mozilla::DeltaValues                                           */
30 /******************************************************************/
31 
DeltaValues(WidgetWheelEvent * aEvent)32 DeltaValues::DeltaValues(WidgetWheelEvent* aEvent)
33   : deltaX(aEvent->mDeltaX)
34   , deltaY(aEvent->mDeltaY)
35 {
36 }
37 
38 /******************************************************************/
39 /* mozilla::WheelHandlingUtils                                    */
40 /******************************************************************/
41 
42 /* static */ bool
CanScrollInRange(nscoord aMin,nscoord aValue,nscoord aMax,double aDirection)43 WheelHandlingUtils::CanScrollInRange(nscoord aMin, nscoord aValue, nscoord aMax,
44                                      double aDirection)
45 {
46   return aDirection > 0.0 ? aValue < static_cast<double>(aMax) :
47                             static_cast<double>(aMin) < aValue;
48 }
49 
50 /* static */ bool
CanScrollOn(nsIFrame * aFrame,double aDirectionX,double aDirectionY)51 WheelHandlingUtils::CanScrollOn(nsIFrame* aFrame,
52                                 double aDirectionX, double aDirectionY)
53 {
54   nsIScrollableFrame* scrollableFrame = do_QueryFrame(aFrame);
55   if (scrollableFrame) {
56     return CanScrollOn(scrollableFrame, aDirectionX, aDirectionY);
57   }
58   nsPluginFrame* pluginFrame = do_QueryFrame(aFrame);
59   return pluginFrame && pluginFrame->WantsToHandleWheelEventAsDefaultAction();
60 }
61 
62 /* static */ bool
CanScrollOn(nsIScrollableFrame * aScrollFrame,double aDirectionX,double aDirectionY)63 WheelHandlingUtils::CanScrollOn(nsIScrollableFrame* aScrollFrame,
64                                 double aDirectionX, double aDirectionY)
65 {
66   MOZ_ASSERT(aScrollFrame);
67   NS_ASSERTION(aDirectionX || aDirectionY,
68                "One of the delta values must be non-zero at least");
69 
70   nsPoint scrollPt = aScrollFrame->GetScrollPosition();
71   nsRect scrollRange = aScrollFrame->GetScrollRange();
72   uint32_t directions = aScrollFrame->GetPerceivedScrollingDirections();
73 
74   return (aDirectionX && (directions & nsIScrollableFrame::HORIZONTAL) &&
75           CanScrollInRange(scrollRange.x, scrollPt.x,
76                            scrollRange.XMost(), aDirectionX)) ||
77          (aDirectionY && (directions & nsIScrollableFrame::VERTICAL) &&
78           CanScrollInRange(scrollRange.y, scrollPt.y,
79                            scrollRange.YMost(), aDirectionY));
80 }
81 
82 /******************************************************************/
83 /* mozilla::WheelTransaction                                      */
84 /******************************************************************/
85 
86 nsWeakFrame WheelTransaction::sTargetFrame(nullptr);
87 uint32_t WheelTransaction::sTime = 0;
88 uint32_t WheelTransaction::sMouseMoved = 0;
89 nsITimer* WheelTransaction::sTimer = nullptr;
90 int32_t WheelTransaction::sScrollSeriesCounter = 0;
91 bool WheelTransaction::sOwnScrollbars = false;
92 
93 /* static */ bool
OutOfTime(uint32_t aBaseTime,uint32_t aThreshold)94 WheelTransaction::OutOfTime(uint32_t aBaseTime, uint32_t aThreshold)
95 {
96   uint32_t now = PR_IntervalToMilliseconds(PR_IntervalNow());
97   return (now - aBaseTime > aThreshold);
98 }
99 
100 /* static */ void
OwnScrollbars(bool aOwn)101 WheelTransaction::OwnScrollbars(bool aOwn)
102 {
103   sOwnScrollbars = aOwn;
104 }
105 
106 /* static */ void
BeginTransaction(nsIFrame * aTargetFrame,WidgetWheelEvent * aEvent)107 WheelTransaction::BeginTransaction(nsIFrame* aTargetFrame,
108                                    WidgetWheelEvent* aEvent)
109 {
110   NS_ASSERTION(!sTargetFrame, "previous transaction is not finished!");
111   MOZ_ASSERT(aEvent->mMessage == eWheel,
112              "Transaction must be started with a wheel event");
113   ScrollbarsForWheel::OwnWheelTransaction(false);
114   sTargetFrame = aTargetFrame;
115   sScrollSeriesCounter = 0;
116   if (!UpdateTransaction(aEvent)) {
117     NS_ERROR("BeginTransaction is called even cannot scroll the frame");
118     EndTransaction();
119   }
120 }
121 
122 /* static */ bool
UpdateTransaction(WidgetWheelEvent * aEvent)123 WheelTransaction::UpdateTransaction(WidgetWheelEvent* aEvent)
124 {
125   nsIFrame* scrollToFrame = GetTargetFrame();
126   nsIScrollableFrame* scrollableFrame = scrollToFrame->GetScrollTargetFrame();
127   if (scrollableFrame) {
128     scrollToFrame = do_QueryFrame(scrollableFrame);
129   }
130 
131   if (!WheelHandlingUtils::CanScrollOn(scrollToFrame,
132                                        aEvent->mDeltaX, aEvent->mDeltaY)) {
133     OnFailToScrollTarget();
134     // We should not modify the transaction state when the view will not be
135     // scrolled actually.
136     return false;
137   }
138 
139   SetTimeout();
140 
141   if (sScrollSeriesCounter != 0 && OutOfTime(sTime, kScrollSeriesTimeoutMs)) {
142     sScrollSeriesCounter = 0;
143   }
144   sScrollSeriesCounter++;
145 
146   // We should use current time instead of WidgetEvent.time.
147   // 1. Some events doesn't have the correct creation time.
148   // 2. If the computer runs slowly by other processes eating the CPU resource,
149   //    the event creation time doesn't keep real time.
150   sTime = PR_IntervalToMilliseconds(PR_IntervalNow());
151   sMouseMoved = 0;
152   return true;
153 }
154 
155 /* static */ void
MayEndTransaction()156 WheelTransaction::MayEndTransaction()
157 {
158   if (!sOwnScrollbars && ScrollbarsForWheel::IsActive()) {
159     ScrollbarsForWheel::OwnWheelTransaction(true);
160   } else {
161     EndTransaction();
162   }
163 }
164 
165 /* static */ void
EndTransaction()166 WheelTransaction::EndTransaction()
167 {
168   if (sTimer) {
169     sTimer->Cancel();
170   }
171   sTargetFrame = nullptr;
172   sScrollSeriesCounter = 0;
173   if (sOwnScrollbars) {
174     sOwnScrollbars = false;
175     ScrollbarsForWheel::OwnWheelTransaction(false);
176     ScrollbarsForWheel::Inactivate();
177   }
178 }
179 
180 /* static */ bool
WillHandleDefaultAction(WidgetWheelEvent * aWheelEvent,nsWeakFrame & aTargetWeakFrame)181 WheelTransaction::WillHandleDefaultAction(WidgetWheelEvent* aWheelEvent,
182                                           nsWeakFrame& aTargetWeakFrame)
183 {
184   nsIFrame* lastTargetFrame = GetTargetFrame();
185   if (!lastTargetFrame) {
186     BeginTransaction(aTargetWeakFrame.GetFrame(), aWheelEvent);
187   } else if (lastTargetFrame != aTargetWeakFrame.GetFrame()) {
188     EndTransaction();
189     BeginTransaction(aTargetWeakFrame.GetFrame(), aWheelEvent);
190   } else {
191     UpdateTransaction(aWheelEvent);
192   }
193 
194   // When the wheel event will not be handled with any frames,
195   // UpdateTransaction() fires MozMouseScrollFailed event which is for
196   // automated testing.  In the event handler, the target frame might be
197   // destroyed.  Then, the caller shouldn't try to handle the default action.
198   if (!aTargetWeakFrame.IsAlive()) {
199     EndTransaction();
200     return false;
201   }
202 
203   return true;
204 }
205 
206 /* static */ void
OnEvent(WidgetEvent * aEvent)207 WheelTransaction::OnEvent(WidgetEvent* aEvent)
208 {
209   if (!sTargetFrame) {
210     return;
211   }
212 
213   if (OutOfTime(sTime, GetTimeoutTime())) {
214     // Even if the scroll event which is handled after timeout, but onTimeout
215     // was not fired by timer, then the scroll event will scroll old frame,
216     // therefore, we should call OnTimeout here and ensure to finish the old
217     // transaction.
218     OnTimeout(nullptr, nullptr);
219     return;
220   }
221 
222   switch (aEvent->mMessage) {
223     case eWheel:
224       if (sMouseMoved != 0 &&
225           OutOfTime(sMouseMoved, GetIgnoreMoveDelayTime())) {
226         // Terminate the current mousewheel transaction if the mouse moved more
227         // than ignoremovedelay milliseconds ago
228         EndTransaction();
229       }
230       return;
231     case eMouseMove:
232     case eDragOver: {
233       WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
234       if (mouseEvent->IsReal()) {
235         // If the cursor is moving to be outside the frame,
236         // terminate the scrollwheel transaction.
237         nsIntPoint pt = GetScreenPoint(mouseEvent);
238         nsIntRect r = sTargetFrame->GetScreenRect();
239         if (!r.Contains(pt)) {
240           EndTransaction();
241           return;
242         }
243 
244         // If the cursor is moving inside the frame, and it is less than
245         // ignoremovedelay milliseconds since the last scroll operation, ignore
246         // the mouse move; otherwise, record the current mouse move time to be
247         // checked later
248         if (!sMouseMoved && OutOfTime(sTime, GetIgnoreMoveDelayTime())) {
249           sMouseMoved = PR_IntervalToMilliseconds(PR_IntervalNow());
250         }
251       }
252       return;
253     }
254     case eKeyPress:
255     case eKeyUp:
256     case eKeyDown:
257     case eMouseUp:
258     case eMouseDown:
259     case eMouseDoubleClick:
260     case eMouseClick:
261     case eContextMenu:
262     case eDrop:
263       EndTransaction();
264       return;
265     default:
266       break;
267   }
268 }
269 
270 /* static */ void
Shutdown()271 WheelTransaction::Shutdown()
272 {
273   NS_IF_RELEASE(sTimer);
274 }
275 
276 /* static */ void
OnFailToScrollTarget()277 WheelTransaction::OnFailToScrollTarget()
278 {
279   NS_PRECONDITION(sTargetFrame, "We don't have mouse scrolling transaction");
280 
281   if (Preferences::GetBool("test.mousescroll", false)) {
282     // This event is used for automated tests, see bug 442774.
283     nsContentUtils::DispatchTrustedEvent(
284                       sTargetFrame->GetContent()->OwnerDoc(),
285                       sTargetFrame->GetContent(),
286                       NS_LITERAL_STRING("MozMouseScrollFailed"),
287                       true, true);
288   }
289   // The target frame might be destroyed in the event handler, at that time,
290   // we need to finish the current transaction
291   if (!sTargetFrame) {
292     EndTransaction();
293   }
294 }
295 
296 /* static */ void
OnTimeout(nsITimer * aTimer,void * aClosure)297 WheelTransaction::OnTimeout(nsITimer* aTimer, void* aClosure)
298 {
299   if (!sTargetFrame) {
300     // The transaction target was destroyed already
301     EndTransaction();
302     return;
303   }
304   // Store the sTargetFrame, the variable becomes null in EndTransaction.
305   nsIFrame* frame = sTargetFrame;
306   // We need to finish current transaction before DOM event firing. Because
307   // the next DOM event might create strange situation for us.
308   MayEndTransaction();
309 
310   if (Preferences::GetBool("test.mousescroll", false)) {
311     // This event is used for automated tests, see bug 442774.
312     nsContentUtils::DispatchTrustedEvent(
313                       frame->GetContent()->OwnerDoc(),
314                       frame->GetContent(),
315                       NS_LITERAL_STRING("MozMouseScrollTransactionTimeout"),
316                       true, true);
317   }
318 }
319 
320 /* static */ void
SetTimeout()321 WheelTransaction::SetTimeout()
322 {
323   if (!sTimer) {
324     nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
325     if (!timer) {
326       return;
327     }
328     timer.swap(sTimer);
329   }
330   sTimer->Cancel();
331   DebugOnly<nsresult> rv =
332     sTimer->InitWithFuncCallback(OnTimeout, nullptr, GetTimeoutTime(),
333                                  nsITimer::TYPE_ONE_SHOT);
334   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
335                        "nsITimer::InitWithFuncCallback failed");
336 }
337 
338 /* static */ nsIntPoint
GetScreenPoint(WidgetGUIEvent * aEvent)339 WheelTransaction::GetScreenPoint(WidgetGUIEvent* aEvent)
340 {
341   NS_ASSERTION(aEvent, "aEvent is null");
342   NS_ASSERTION(aEvent->mWidget, "aEvent-mWidget is null");
343   return (aEvent->mRefPoint + aEvent->mWidget->WidgetToScreenOffset())
344       .ToUnknownPoint();
345 }
346 
347 /* static */ uint32_t
GetTimeoutTime()348 WheelTransaction::GetTimeoutTime()
349 {
350   return Preferences::GetUint("mousewheel.transaction.timeout", 1500);
351 }
352 
353 /* static */ uint32_t
GetIgnoreMoveDelayTime()354 WheelTransaction::GetIgnoreMoveDelayTime()
355 {
356   return Preferences::GetUint("mousewheel.transaction.ignoremovedelay", 100);
357 }
358 
359 /* static */ DeltaValues
AccelerateWheelDelta(WidgetWheelEvent * aEvent,bool aAllowScrollSpeedOverride)360 WheelTransaction::AccelerateWheelDelta(WidgetWheelEvent* aEvent,
361                                        bool aAllowScrollSpeedOverride)
362 {
363   DeltaValues result(aEvent);
364 
365   // Don't accelerate the delta values if the event isn't line scrolling.
366   if (aEvent->mDeltaMode != nsIDOMWheelEvent::DOM_DELTA_LINE) {
367     return result;
368   }
369 
370   if (aAllowScrollSpeedOverride) {
371     result = OverrideSystemScrollSpeed(aEvent);
372   }
373 
374   // Accelerate by the sScrollSeriesCounter
375   int32_t start = GetAccelerationStart();
376   if (start >= 0 && sScrollSeriesCounter >= start) {
377     int32_t factor = GetAccelerationFactor();
378     if (factor > 0) {
379       result.deltaX = ComputeAcceleratedWheelDelta(result.deltaX, factor);
380       result.deltaY = ComputeAcceleratedWheelDelta(result.deltaY, factor);
381     }
382   }
383 
384   return result;
385 }
386 
387 /* static */ double
ComputeAcceleratedWheelDelta(double aDelta,int32_t aFactor)388 WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta, int32_t aFactor)
389 {
390   return mozilla::ComputeAcceleratedWheelDelta(aDelta, sScrollSeriesCounter, aFactor);
391 }
392 
393 /* static */ int32_t
GetAccelerationStart()394 WheelTransaction::GetAccelerationStart()
395 {
396   return Preferences::GetInt("mousewheel.acceleration.start", -1);
397 }
398 
399 /* static */ int32_t
GetAccelerationFactor()400 WheelTransaction::GetAccelerationFactor()
401 {
402   return Preferences::GetInt("mousewheel.acceleration.factor", -1);
403 }
404 
405 /* static */ DeltaValues
OverrideSystemScrollSpeed(WidgetWheelEvent * aEvent)406 WheelTransaction::OverrideSystemScrollSpeed(WidgetWheelEvent* aEvent)
407 {
408   MOZ_ASSERT(sTargetFrame, "We don't have mouse scrolling transaction");
409   MOZ_ASSERT(aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE);
410 
411   // If the event doesn't scroll to both X and Y, we don't need to do anything
412   // here.
413   if (!aEvent->mDeltaX && !aEvent->mDeltaY) {
414     return DeltaValues(aEvent);
415   }
416 
417   return DeltaValues(aEvent->OverriddenDeltaX(),
418                      aEvent->OverriddenDeltaY());
419 }
420 
421 /******************************************************************/
422 /* mozilla::ScrollbarsForWheel                                    */
423 /******************************************************************/
424 
425 const DeltaValues ScrollbarsForWheel::directions[kNumberOfTargets] = {
426   DeltaValues(-1, 0), DeltaValues(+1, 0), DeltaValues(0, -1), DeltaValues(0, +1)
427 };
428 
429 nsWeakFrame ScrollbarsForWheel::sActiveOwner = nullptr;
430 nsWeakFrame ScrollbarsForWheel::sActivatedScrollTargets[kNumberOfTargets] = {
431   nullptr, nullptr, nullptr, nullptr
432 };
433 
434 bool ScrollbarsForWheel::sHadWheelStart = false;
435 bool ScrollbarsForWheel::sOwnWheelTransaction = false;
436 
437 /* static */ void
PrepareToScrollText(EventStateManager * aESM,nsIFrame * aTargetFrame,WidgetWheelEvent * aEvent)438 ScrollbarsForWheel::PrepareToScrollText(EventStateManager* aESM,
439                                         nsIFrame* aTargetFrame,
440                                         WidgetWheelEvent* aEvent)
441 {
442   if (aEvent->mMessage == eWheelOperationStart) {
443     WheelTransaction::OwnScrollbars(false);
444     if (!IsActive()) {
445       TemporarilyActivateAllPossibleScrollTargets(aESM, aTargetFrame, aEvent);
446       sHadWheelStart = true;
447     }
448   } else {
449     DeactivateAllTemporarilyActivatedScrollTargets();
450   }
451 }
452 
453 /* static */ void
SetActiveScrollTarget(nsIScrollableFrame * aScrollTarget)454 ScrollbarsForWheel::SetActiveScrollTarget(nsIScrollableFrame* aScrollTarget)
455 {
456   if (!sHadWheelStart) {
457     return;
458   }
459   nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(aScrollTarget);
460   if (!scrollbarMediator) {
461     return;
462   }
463   sHadWheelStart = false;
464   sActiveOwner = do_QueryFrame(aScrollTarget);
465   scrollbarMediator->ScrollbarActivityStarted();
466 }
467 
468 /* static */ void
MayInactivate()469 ScrollbarsForWheel::MayInactivate()
470 {
471   if (!sOwnWheelTransaction && WheelTransaction::GetTargetFrame()) {
472     WheelTransaction::OwnScrollbars(true);
473   } else {
474     Inactivate();
475   }
476 }
477 
478 /* static */ void
Inactivate()479 ScrollbarsForWheel::Inactivate()
480 {
481   nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sActiveOwner);
482   if (scrollbarMediator) {
483     scrollbarMediator->ScrollbarActivityStopped();
484   }
485   sActiveOwner = nullptr;
486   DeactivateAllTemporarilyActivatedScrollTargets();
487   if (sOwnWheelTransaction) {
488     sOwnWheelTransaction = false;
489     WheelTransaction::OwnScrollbars(false);
490     WheelTransaction::EndTransaction();
491   }
492 }
493 
494 /* static */ bool
IsActive()495 ScrollbarsForWheel::IsActive()
496 {
497   if (sActiveOwner) {
498     return true;
499   }
500   for (size_t i = 0; i < kNumberOfTargets; ++i) {
501     if (sActivatedScrollTargets[i]) {
502       return true;
503     }
504   }
505   return false;
506 }
507 
508 /* static */ void
OwnWheelTransaction(bool aOwn)509 ScrollbarsForWheel::OwnWheelTransaction(bool aOwn)
510 {
511   sOwnWheelTransaction = aOwn;
512 }
513 
514 /* static */ void
TemporarilyActivateAllPossibleScrollTargets(EventStateManager * aESM,nsIFrame * aTargetFrame,WidgetWheelEvent * aEvent)515 ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets(
516                       EventStateManager* aESM,
517                       nsIFrame* aTargetFrame,
518                       WidgetWheelEvent* aEvent)
519 {
520   for (size_t i = 0; i < kNumberOfTargets; i++) {
521     const DeltaValues *dir = &directions[i];
522     nsWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
523     MOZ_ASSERT(!*scrollTarget, "scroll target still temporarily activated!");
524     nsIScrollableFrame* target = do_QueryFrame(
525       aESM->ComputeScrollTarget(aTargetFrame, dir->deltaX, dir->deltaY, aEvent,
526               EventStateManager::COMPUTE_DEFAULT_ACTION_TARGET));
527     nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(target);
528     if (scrollbarMediator) {
529       nsIFrame* targetFrame = do_QueryFrame(target);
530       *scrollTarget = targetFrame;
531       scrollbarMediator->ScrollbarActivityStarted();
532     }
533   }
534 }
535 
536 /* static */ void
DeactivateAllTemporarilyActivatedScrollTargets()537 ScrollbarsForWheel::DeactivateAllTemporarilyActivatedScrollTargets()
538 {
539   for (size_t i = 0; i < kNumberOfTargets; i++) {
540     nsWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
541     if (*scrollTarget) {
542       nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(*scrollTarget);
543       if (scrollbarMediator) {
544         scrollbarMediator->ScrollbarActivityStopped();
545       }
546       *scrollTarget = nullptr;
547     }
548   }
549 }
550 
551 } // namespace mozilla
552