|
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/. */ |
|
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 "nsPresContext.h" |
|
21 #include "prtime.h" |
|
22 #include "Units.h" |
|
23 |
|
24 namespace mozilla { |
|
25 |
|
26 /******************************************************************/ |
|
27 /* mozilla::DeltaValues */ |
|
28 /******************************************************************/ |
|
29 |
|
30 DeltaValues::DeltaValues(WidgetWheelEvent* aEvent) |
|
31 : deltaX(aEvent->deltaX) |
|
32 , deltaY(aEvent->deltaY) |
|
33 { |
|
34 } |
|
35 |
|
36 /******************************************************************/ |
|
37 /* mozilla::WheelHandlingUtils */ |
|
38 /******************************************************************/ |
|
39 |
|
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 } |
|
47 |
|
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"); |
|
55 |
|
56 nsPoint scrollPt = aScrollFrame->GetScrollPosition(); |
|
57 nsRect scrollRange = aScrollFrame->GetScrollRange(); |
|
58 uint32_t directions = aScrollFrame->GetPerceivedScrollingDirections(); |
|
59 |
|
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 } |
|
67 |
|
68 /******************************************************************/ |
|
69 /* mozilla::WheelTransaction */ |
|
70 /******************************************************************/ |
|
71 |
|
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; |
|
78 |
|
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 } |
|
85 |
|
86 /* static */ void |
|
87 WheelTransaction::OwnScrollbars(bool aOwn) |
|
88 { |
|
89 sOwnScrollbars = aOwn; |
|
90 } |
|
91 |
|
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 } |
|
107 |
|
108 /* static */ bool |
|
109 WheelTransaction::UpdateTransaction(WidgetWheelEvent* aEvent) |
|
110 { |
|
111 nsIScrollableFrame* sf = GetTargetFrame()->GetScrollTargetFrame(); |
|
112 NS_ENSURE_TRUE(sf, false); |
|
113 |
|
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 } |
|
120 |
|
121 SetTimeout(); |
|
122 |
|
123 if (sScrollSeriesCounter != 0 && OutOfTime(sTime, kScrollSeriesTimeout)) { |
|
124 sScrollSeriesCounter = 0; |
|
125 } |
|
126 sScrollSeriesCounter++; |
|
127 |
|
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 } |
|
136 |
|
137 /* static */ void |
|
138 WheelTransaction::MayEndTransaction() |
|
139 { |
|
140 if (!sOwnScrollbars && ScrollbarsForWheel::IsActive()) { |
|
141 ScrollbarsForWheel::OwnWheelTransaction(true); |
|
142 } else { |
|
143 EndTransaction(); |
|
144 } |
|
145 } |
|
146 |
|
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 } |
|
161 |
|
162 /* static */ void |
|
163 WheelTransaction::OnEvent(WidgetEvent* aEvent) |
|
164 { |
|
165 if (!sTargetFrame) { |
|
166 return; |
|
167 } |
|
168 |
|
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 } |
|
177 |
|
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 } |
|
199 |
|
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 } |
|
223 |
|
224 /* static */ void |
|
225 WheelTransaction::Shutdown() |
|
226 { |
|
227 NS_IF_RELEASE(sTimer); |
|
228 } |
|
229 |
|
230 /* static */ void |
|
231 WheelTransaction::OnFailToScrollTarget() |
|
232 { |
|
233 NS_PRECONDITION(sTargetFrame, "We don't have mouse scrolling transaction"); |
|
234 |
|
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 } |
|
249 |
|
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(); |
|
263 |
|
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 } |
|
273 |
|
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 } |
|
290 |
|
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 } |
|
299 |
|
300 /* static */ uint32_t |
|
301 WheelTransaction::GetTimeoutTime() |
|
302 { |
|
303 return Preferences::GetUint("mousewheel.transaction.timeout", 1500); |
|
304 } |
|
305 |
|
306 /* static */ uint32_t |
|
307 WheelTransaction::GetIgnoreMoveDelayTime() |
|
308 { |
|
309 return Preferences::GetUint("mousewheel.transaction.ignoremovedelay", 100); |
|
310 } |
|
311 |
|
312 /* static */ DeltaValues |
|
313 WheelTransaction::AccelerateWheelDelta(WidgetWheelEvent* aEvent, |
|
314 bool aAllowScrollSpeedOverride) |
|
315 { |
|
316 DeltaValues result(aEvent); |
|
317 |
|
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 } |
|
322 |
|
323 if (aAllowScrollSpeedOverride) { |
|
324 result = OverrideSystemScrollSpeed(aEvent); |
|
325 } |
|
326 |
|
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 } |
|
336 |
|
337 return result; |
|
338 } |
|
339 |
|
340 /* static */ double |
|
341 WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta, |
|
342 int32_t aFactor) |
|
343 { |
|
344 if (aDelta == 0.0) { |
|
345 return 0; |
|
346 } |
|
347 |
|
348 return (aDelta * sScrollSeriesCounter * (double)aFactor / 10); |
|
349 } |
|
350 |
|
351 /* static */ int32_t |
|
352 WheelTransaction::GetAccelerationStart() |
|
353 { |
|
354 return Preferences::GetInt("mousewheel.acceleration.start", -1); |
|
355 } |
|
356 |
|
357 /* static */ int32_t |
|
358 WheelTransaction::GetAccelerationFactor() |
|
359 { |
|
360 return Preferences::GetInt("mousewheel.acceleration.factor", -1); |
|
361 } |
|
362 |
|
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); |
|
368 |
|
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 } |
|
374 |
|
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 } |
|
380 |
|
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 } |
|
394 |
|
395 /******************************************************************/ |
|
396 /* mozilla::ScrollbarsForWheel */ |
|
397 /******************************************************************/ |
|
398 |
|
399 const DeltaValues ScrollbarsForWheel::directions[kNumberOfTargets] = { |
|
400 DeltaValues(-1, 0), DeltaValues(+1, 0), DeltaValues(0, -1), DeltaValues(0, +1) |
|
401 }; |
|
402 |
|
403 nsWeakFrame ScrollbarsForWheel::sActiveOwner = nullptr; |
|
404 nsWeakFrame ScrollbarsForWheel::sActivatedScrollTargets[kNumberOfTargets] = { |
|
405 nullptr, nullptr, nullptr, nullptr |
|
406 }; |
|
407 |
|
408 bool ScrollbarsForWheel::sHadWheelStart = false; |
|
409 bool ScrollbarsForWheel::sOwnWheelTransaction = false; |
|
410 |
|
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 } |
|
426 |
|
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 } |
|
441 |
|
442 /* static */ void |
|
443 ScrollbarsForWheel::MayInactivate() |
|
444 { |
|
445 if (!sOwnWheelTransaction && WheelTransaction::GetTargetFrame()) { |
|
446 WheelTransaction::OwnScrollbars(true); |
|
447 } else { |
|
448 Inactivate(); |
|
449 } |
|
450 } |
|
451 |
|
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 } |
|
467 |
|
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 } |
|
481 |
|
482 /* static */ void |
|
483 ScrollbarsForWheel::OwnWheelTransaction(bool aOwn) |
|
484 { |
|
485 sOwnWheelTransaction = aOwn; |
|
486 } |
|
487 |
|
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 } |
|
509 |
|
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 } |
|
524 |
|
525 } // namespace mozilla |