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"
14 #include "nsContentUtils.h"
15 #include "nsIContent.h"
16 #include "nsIDocument.h"
17 #include "nsIPresShell.h"
18 #include "nsIScrollableFrame.h"
20 #include "nsPresContext.h"
26 /******************************************************************/
27 /* mozilla::DeltaValues */
28 /******************************************************************/
30 DeltaValues::DeltaValues(WidgetWheelEvent
* aEvent
)
31 : deltaX(aEvent
->deltaX
)
32 , deltaY(aEvent
->deltaY
)
36 /******************************************************************/
37 /* mozilla::WheelHandlingUtils */
38 /******************************************************************/
41 WheelHandlingUtils::CanScrollInRange(nscoord aMin
, nscoord aValue
, nscoord aMax
,
44 return aDirection
> 0.0 ? aValue
< static_cast<double>(aMax
) :
45 static_cast<double>(aMin
) < aValue
;
49 WheelHandlingUtils::CanScrollOn(nsIScrollableFrame
* aScrollFrame
,
50 double aDirectionX
, double aDirectionY
)
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
));
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;
80 WheelTransaction::OutOfTime(uint32_t aBaseTime
, uint32_t aThreshold
)
82 uint32_t now
= PR_IntervalToMilliseconds(PR_IntervalNow());
83 return (now
- aBaseTime
> aThreshold
);
87 WheelTransaction::OwnScrollbars(bool aOwn
)
89 sOwnScrollbars
= aOwn
;
93 WheelTransaction::BeginTransaction(nsIFrame
* aTargetFrame
,
94 WidgetWheelEvent
* aEvent
)
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");
109 WheelTransaction::UpdateTransaction(WidgetWheelEvent
* aEvent
)
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.
123 if (sScrollSeriesCounter
!= 0 && OutOfTime(sTime
, kScrollSeriesTimeout
)) {
124 sScrollSeriesCounter
= 0;
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());
138 WheelTransaction::MayEndTransaction()
140 if (!sOwnScrollbars
&& ScrollbarsForWheel::IsActive()) {
141 ScrollbarsForWheel::OwnWheelTransaction(true);
148 WheelTransaction::EndTransaction()
153 sTargetFrame
= nullptr;
154 sScrollSeriesCounter
= 0;
155 if (sOwnScrollbars
) {
156 sOwnScrollbars
= false;
157 ScrollbarsForWheel::OwnWheelTransaction(false);
158 ScrollbarsForWheel::Inactivate();
163 WheelTransaction::OnEvent(WidgetEvent
* aEvent
)
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
174 OnTimeout(nullptr, nullptr);
178 switch (aEvent
->message
) {
180 if (sMouseMoved
!= 0 &&
181 OutOfTime(sMouseMoved
, GetIgnoreMoveDelayTime())) {
182 // Terminate the current mousewheel transaction if the mouse moved more
183 // than ignoremovedelay milliseconds ago
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
)) {
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
204 if (!sMouseMoved
&& OutOfTime(sTime
, GetIgnoreMoveDelayTime())) {
205 sMouseMoved
= PR_IntervalToMilliseconds(PR_IntervalNow());
213 case NS_MOUSE_BUTTON_UP
:
214 case NS_MOUSE_BUTTON_DOWN
:
215 case NS_MOUSE_DOUBLECLICK
:
218 case NS_DRAGDROP_DROP
:
225 WheelTransaction::Shutdown()
227 NS_IF_RELEASE(sTimer
);
231 WheelTransaction::OnFailToScrollTarget()
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"),
243 // The target frame might be destroyed in the event handler, at that time,
244 // we need to finish the current transaction
251 WheelTransaction::OnTimeout(nsITimer
* aTimer
, void* aClosure
)
254 // The transaction target was destroyed already
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.
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(),
269 NS_LITERAL_STRING("MozMouseScrollTransactionTimeout"),
275 WheelTransaction::SetTimeout()
278 nsCOMPtr
<nsITimer
> timer
= do_CreateInstance(NS_TIMER_CONTRACTID
);
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");
291 /* static */ nsIntPoint
292 WheelTransaction::GetScreenPoint(WidgetGUIEvent
* aEvent
)
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();
300 /* static */ uint32_t
301 WheelTransaction::GetTimeoutTime()
303 return Preferences::GetUint("mousewheel.transaction.timeout", 1500);
306 /* static */ uint32_t
307 WheelTransaction::GetIgnoreMoveDelayTime()
309 return Preferences::GetUint("mousewheel.transaction.ignoremovedelay", 100);
312 /* static */ DeltaValues
313 WheelTransaction::AccelerateWheelDelta(WidgetWheelEvent
* aEvent
,
314 bool aAllowScrollSpeedOverride
)
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
) {
323 if (aAllowScrollSpeedOverride
) {
324 result
= OverrideSystemScrollSpeed(aEvent
);
327 // Accelerate by the sScrollSeriesCounter
328 int32_t start
= GetAccelerationStart();
329 if (start
>= 0 && sScrollSeriesCounter
>= start
) {
330 int32_t factor
= GetAccelerationFactor();
332 result
.deltaX
= ComputeAcceleratedWheelDelta(result
.deltaX
, factor
);
333 result
.deltaY
= ComputeAcceleratedWheelDelta(result
.deltaY
, factor
);
341 WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta
,
348 return (aDelta
* sScrollSeriesCounter
* (double)aFactor
/ 10);
352 WheelTransaction::GetAccelerationStart()
354 return Preferences::GetInt("mousewheel.acceleration.start", -1);
358 WheelTransaction::GetAccelerationFactor()
360 return Preferences::GetInt("mousewheel.acceleration.factor", -1);
363 /* static */ DeltaValues
364 WheelTransaction::OverrideSystemScrollSpeed(WidgetWheelEvent
* aEvent
)
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
371 if (!aEvent
->deltaX
&& !aEvent
->deltaY
) {
372 return DeltaValues(aEvent
);
375 // We shouldn't override the scrolling speed on non root scroll frame.
377 sTargetFrame
->PresContext()->PresShell()->GetRootScrollFrame()) {
378 return DeltaValues(aEvent
);
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);
389 widget
->OverrideSystemMouseScrollSpeed(aEvent
->deltaX
, aEvent
->deltaY
,
390 overriddenDeltaValues
.deltaX
,
391 overriddenDeltaValues
.deltaY
);
392 return NS_FAILED(rv
) ? DeltaValues(aEvent
) : overriddenDeltaValues
;
395 /******************************************************************/
396 /* mozilla::ScrollbarsForWheel */
397 /******************************************************************/
399 const DeltaValues
ScrollbarsForWheel::directions
[kNumberOfTargets
] = {
400 DeltaValues(-1, 0), DeltaValues(+1, 0), DeltaValues(0, -1), DeltaValues(0, +1)
403 nsWeakFrame
ScrollbarsForWheel::sActiveOwner
= nullptr;
404 nsWeakFrame
ScrollbarsForWheel::sActivatedScrollTargets
[kNumberOfTargets
] = {
405 nullptr, nullptr, nullptr, nullptr
408 bool ScrollbarsForWheel::sHadWheelStart
= false;
409 bool ScrollbarsForWheel::sOwnWheelTransaction
= false;
412 ScrollbarsForWheel::PrepareToScrollText(EventStateManager
* aESM
,
413 nsIFrame
* aTargetFrame
,
414 WidgetWheelEvent
* aEvent
)
416 if (aEvent
->message
== NS_WHEEL_START
) {
417 WheelTransaction::OwnScrollbars(false);
419 TemporarilyActivateAllPossibleScrollTargets(aESM
, aTargetFrame
, aEvent
);
420 sHadWheelStart
= true;
423 DeactivateAllTemporarilyActivatedScrollTargets();
428 ScrollbarsForWheel::SetActiveScrollTarget(nsIScrollableFrame
* aScrollTarget
)
430 if (!sHadWheelStart
) {
433 nsIScrollbarMediator
* scrollbarMediator
= do_QueryFrame(aScrollTarget
);
434 if (!scrollbarMediator
) {
437 sHadWheelStart
= false;
438 sActiveOwner
= do_QueryFrame(aScrollTarget
);
439 scrollbarMediator
->ScrollbarActivityStarted();
443 ScrollbarsForWheel::MayInactivate()
445 if (!sOwnWheelTransaction
&& WheelTransaction::GetTargetFrame()) {
446 WheelTransaction::OwnScrollbars(true);
453 ScrollbarsForWheel::Inactivate()
455 nsIScrollbarMediator
* scrollbarMediator
= do_QueryFrame(sActiveOwner
);
456 if (scrollbarMediator
) {
457 scrollbarMediator
->ScrollbarActivityStopped();
459 sActiveOwner
= nullptr;
460 DeactivateAllTemporarilyActivatedScrollTargets();
461 if (sOwnWheelTransaction
) {
462 sOwnWheelTransaction
= false;
463 WheelTransaction::OwnScrollbars(false);
464 WheelTransaction::EndTransaction();
469 ScrollbarsForWheel::IsActive()
474 for (size_t i
= 0; i
< kNumberOfTargets
; ++i
) {
475 if (sActivatedScrollTargets
[i
]) {
483 ScrollbarsForWheel::OwnWheelTransaction(bool aOwn
)
485 sOwnWheelTransaction
= aOwn
;
489 ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets(
490 EventStateManager
* aESM
,
491 nsIFrame
* aTargetFrame
,
492 WidgetWheelEvent
* aEvent
)
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 nsIScrollbarMediator
* scrollbarMediator
= do_QueryFrame(target
);
502 if (scrollbarMediator
) {
503 nsIFrame
* targetFrame
= do_QueryFrame(target
);
504 *scrollTarget
= targetFrame
;
505 scrollbarMediator
->ScrollbarActivityStarted();
511 ScrollbarsForWheel::DeactivateAllTemporarilyActivatedScrollTargets()
513 for (size_t i
= 0; i
< kNumberOfTargets
; i
++) {
514 nsWeakFrame
* scrollTarget
= &sActivatedScrollTargets
[i
];
516 nsIScrollbarMediator
* scrollbarMediator
= do_QueryFrame(*scrollTarget
);
517 if (scrollbarMediator
) {
518 scrollbarMediator
->ScrollbarActivityStopped();
520 *scrollTarget
= nullptr;
525 } // namespace mozilla