Bumping manifests a=b2g-bump
[gecko.git] / dom / events / WheelHandlingHelper.cpp
blobba586cfc368424579d009a03d47eac62911173c0
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)
36 /******************************************************************/
37 /* mozilla::WheelHandlingUtils */
38 /******************************************************************/
40 /* static */ bool
41 WheelHandlingUtils::CanScrollInRange(nscoord aMin, nscoord aValue, nscoord aMax,
42 double aDirection)
44 return aDirection > 0.0 ? aValue < static_cast<double>(aMax) :
45 static_cast<double>(aMin) < aValue;
48 /* static */ bool
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;
79 /* static */ bool
80 WheelTransaction::OutOfTime(uint32_t aBaseTime, uint32_t aThreshold)
82 uint32_t now = PR_IntervalToMilliseconds(PR_IntervalNow());
83 return (now - aBaseTime > aThreshold);
86 /* static */ void
87 WheelTransaction::OwnScrollbars(bool aOwn)
89 sOwnScrollbars = aOwn;
92 /* static */ void
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");
104 EndTransaction();
108 /* static */ bool
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.
118 return false;
121 SetTimeout();
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());
133 sMouseMoved = 0;
134 return true;
137 /* static */ void
138 WheelTransaction::MayEndTransaction()
140 if (!sOwnScrollbars && ScrollbarsForWheel::IsActive()) {
141 ScrollbarsForWheel::OwnWheelTransaction(true);
142 } else {
143 EndTransaction();
147 /* static */ void
148 WheelTransaction::EndTransaction()
150 if (sTimer) {
151 sTimer->Cancel();
153 sTargetFrame = nullptr;
154 sScrollSeriesCounter = 0;
155 if (sOwnScrollbars) {
156 sOwnScrollbars = false;
157 ScrollbarsForWheel::OwnWheelTransaction(false);
158 ScrollbarsForWheel::Inactivate();
162 /* static */ void
163 WheelTransaction::OnEvent(WidgetEvent* aEvent)
165 if (!sTargetFrame) {
166 return;
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;
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();
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;
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());
208 return;
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;
224 /* static */ void
225 WheelTransaction::Shutdown()
227 NS_IF_RELEASE(sTimer);
230 /* static */ void
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"),
241 true, true);
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();
250 /* static */ void
251 WheelTransaction::OnTimeout(nsITimer* aTimer, void* aClosure)
253 if (!sTargetFrame) {
254 // The transaction target was destroyed already
255 EndTransaction();
256 return;
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);
274 /* static */ void
275 WheelTransaction::SetTimeout()
277 if (!sTimer) {
278 nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
279 if (!timer) {
280 return;
282 timer.swap(sTimer);
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");
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) {
320 return result;
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();
331 if (factor > 0) {
332 result.deltaX = ComputeAcceleratedWheelDelta(result.deltaX, factor);
333 result.deltaY = ComputeAcceleratedWheelDelta(result.deltaY, factor);
337 return result;
340 /* static */ double
341 WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta,
342 int32_t aFactor)
344 if (aDelta == 0.0) {
345 return 0;
348 return (aDelta * sScrollSeriesCounter * (double)aFactor / 10);
351 /* static */ int32_t
352 WheelTransaction::GetAccelerationStart()
354 return Preferences::GetInt("mousewheel.acceleration.start", -1);
357 /* static */ int32_t
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
370 // here.
371 if (!aEvent->deltaX && !aEvent->deltaY) {
372 return DeltaValues(aEvent);
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);
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;
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;
411 /* static */ void
412 ScrollbarsForWheel::PrepareToScrollText(EventStateManager* aESM,
413 nsIFrame* aTargetFrame,
414 WidgetWheelEvent* aEvent)
416 if (aEvent->message == NS_WHEEL_START) {
417 WheelTransaction::OwnScrollbars(false);
418 if (!IsActive()) {
419 TemporarilyActivateAllPossibleScrollTargets(aESM, aTargetFrame, aEvent);
420 sHadWheelStart = true;
422 } else {
423 DeactivateAllTemporarilyActivatedScrollTargets();
427 /* static */ void
428 ScrollbarsForWheel::SetActiveScrollTarget(nsIScrollableFrame* aScrollTarget)
430 if (!sHadWheelStart) {
431 return;
433 nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(aScrollTarget);
434 if (!scrollbarMediator) {
435 return;
437 sHadWheelStart = false;
438 sActiveOwner = do_QueryFrame(aScrollTarget);
439 scrollbarMediator->ScrollbarActivityStarted();
442 /* static */ void
443 ScrollbarsForWheel::MayInactivate()
445 if (!sOwnWheelTransaction && WheelTransaction::GetTargetFrame()) {
446 WheelTransaction::OwnScrollbars(true);
447 } else {
448 Inactivate();
452 /* static */ void
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();
468 /* static */ bool
469 ScrollbarsForWheel::IsActive()
471 if (sActiveOwner) {
472 return true;
474 for (size_t i = 0; i < kNumberOfTargets; ++i) {
475 if (sActivatedScrollTargets[i]) {
476 return true;
479 return false;
482 /* static */ void
483 ScrollbarsForWheel::OwnWheelTransaction(bool aOwn)
485 sOwnWheelTransaction = aOwn;
488 /* static */ void
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();
510 /* static */ void
511 ScrollbarsForWheel::DeactivateAllTemporarilyActivatedScrollTargets()
513 for (size_t i = 0; i < kNumberOfTargets; i++) {
514 nsWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
515 if (*scrollTarget) {
516 nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(*scrollTarget);
517 if (scrollbarMediator) {
518 scrollbarMediator->ScrollbarActivityStopped();
520 *scrollTarget = nullptr;
525 } // namespace mozilla