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