1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/events/WheelHandlingHelper.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,525 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 sw=2 et tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "WheelHandlingHelper.h" 1.11 + 1.12 +#include "mozilla/EventDispatcher.h" 1.13 +#include "mozilla/EventStateManager.h" 1.14 +#include "mozilla/MouseEvents.h" 1.15 +#include "mozilla/Preferences.h" 1.16 +#include "nsCOMPtr.h" 1.17 +#include "nsContentUtils.h" 1.18 +#include "nsIContent.h" 1.19 +#include "nsIDocument.h" 1.20 +#include "nsIPresShell.h" 1.21 +#include "nsIScrollableFrame.h" 1.22 +#include "nsITimer.h" 1.23 +#include "nsPresContext.h" 1.24 +#include "prtime.h" 1.25 +#include "Units.h" 1.26 + 1.27 +namespace mozilla { 1.28 + 1.29 +/******************************************************************/ 1.30 +/* mozilla::DeltaValues */ 1.31 +/******************************************************************/ 1.32 + 1.33 +DeltaValues::DeltaValues(WidgetWheelEvent* aEvent) 1.34 + : deltaX(aEvent->deltaX) 1.35 + , deltaY(aEvent->deltaY) 1.36 +{ 1.37 +} 1.38 + 1.39 +/******************************************************************/ 1.40 +/* mozilla::WheelHandlingUtils */ 1.41 +/******************************************************************/ 1.42 + 1.43 +/* static */ bool 1.44 +WheelHandlingUtils::CanScrollInRange(nscoord aMin, nscoord aValue, nscoord aMax, 1.45 + double aDirection) 1.46 +{ 1.47 + return aDirection > 0.0 ? aValue < static_cast<double>(aMax) : 1.48 + static_cast<double>(aMin) < aValue; 1.49 +} 1.50 + 1.51 +/* static */ bool 1.52 +WheelHandlingUtils::CanScrollOn(nsIScrollableFrame* aScrollFrame, 1.53 + double aDirectionX, double aDirectionY) 1.54 +{ 1.55 + MOZ_ASSERT(aScrollFrame); 1.56 + NS_ASSERTION(aDirectionX || aDirectionY, 1.57 + "One of the delta values must be non-zero at least"); 1.58 + 1.59 + nsPoint scrollPt = aScrollFrame->GetScrollPosition(); 1.60 + nsRect scrollRange = aScrollFrame->GetScrollRange(); 1.61 + uint32_t directions = aScrollFrame->GetPerceivedScrollingDirections(); 1.62 + 1.63 + return (aDirectionX && (directions & nsIScrollableFrame::HORIZONTAL) && 1.64 + CanScrollInRange(scrollRange.x, scrollPt.x, 1.65 + scrollRange.XMost(), aDirectionX)) || 1.66 + (aDirectionY && (directions & nsIScrollableFrame::VERTICAL) && 1.67 + CanScrollInRange(scrollRange.y, scrollPt.y, 1.68 + scrollRange.YMost(), aDirectionY)); 1.69 +} 1.70 + 1.71 +/******************************************************************/ 1.72 +/* mozilla::WheelTransaction */ 1.73 +/******************************************************************/ 1.74 + 1.75 +nsWeakFrame WheelTransaction::sTargetFrame(nullptr); 1.76 +uint32_t WheelTransaction::sTime = 0; 1.77 +uint32_t WheelTransaction::sMouseMoved = 0; 1.78 +nsITimer* WheelTransaction::sTimer = nullptr; 1.79 +int32_t WheelTransaction::sScrollSeriesCounter = 0; 1.80 +bool WheelTransaction::sOwnScrollbars = false; 1.81 + 1.82 +/* static */ bool 1.83 +WheelTransaction::OutOfTime(uint32_t aBaseTime, uint32_t aThreshold) 1.84 +{ 1.85 + uint32_t now = PR_IntervalToMilliseconds(PR_IntervalNow()); 1.86 + return (now - aBaseTime > aThreshold); 1.87 +} 1.88 + 1.89 +/* static */ void 1.90 +WheelTransaction::OwnScrollbars(bool aOwn) 1.91 +{ 1.92 + sOwnScrollbars = aOwn; 1.93 +} 1.94 + 1.95 +/* static */ void 1.96 +WheelTransaction::BeginTransaction(nsIFrame* aTargetFrame, 1.97 + WidgetWheelEvent* aEvent) 1.98 +{ 1.99 + NS_ASSERTION(!sTargetFrame, "previous transaction is not finished!"); 1.100 + MOZ_ASSERT(aEvent->message == NS_WHEEL_WHEEL, 1.101 + "Transaction must be started with a wheel event"); 1.102 + ScrollbarsForWheel::OwnWheelTransaction(false); 1.103 + sTargetFrame = aTargetFrame; 1.104 + sScrollSeriesCounter = 0; 1.105 + if (!UpdateTransaction(aEvent)) { 1.106 + NS_ERROR("BeginTransaction is called even cannot scroll the frame"); 1.107 + EndTransaction(); 1.108 + } 1.109 +} 1.110 + 1.111 +/* static */ bool 1.112 +WheelTransaction::UpdateTransaction(WidgetWheelEvent* aEvent) 1.113 +{ 1.114 + nsIScrollableFrame* sf = GetTargetFrame()->GetScrollTargetFrame(); 1.115 + NS_ENSURE_TRUE(sf, false); 1.116 + 1.117 + if (!WheelHandlingUtils::CanScrollOn(sf, aEvent->deltaX, aEvent->deltaY)) { 1.118 + OnFailToScrollTarget(); 1.119 + // We should not modify the transaction state when the view will not be 1.120 + // scrolled actually. 1.121 + return false; 1.122 + } 1.123 + 1.124 + SetTimeout(); 1.125 + 1.126 + if (sScrollSeriesCounter != 0 && OutOfTime(sTime, kScrollSeriesTimeout)) { 1.127 + sScrollSeriesCounter = 0; 1.128 + } 1.129 + sScrollSeriesCounter++; 1.130 + 1.131 + // We should use current time instead of WidgetEvent.time. 1.132 + // 1. Some events doesn't have the correct creation time. 1.133 + // 2. If the computer runs slowly by other processes eating the CPU resource, 1.134 + // the event creation time doesn't keep real time. 1.135 + sTime = PR_IntervalToMilliseconds(PR_IntervalNow()); 1.136 + sMouseMoved = 0; 1.137 + return true; 1.138 +} 1.139 + 1.140 +/* static */ void 1.141 +WheelTransaction::MayEndTransaction() 1.142 +{ 1.143 + if (!sOwnScrollbars && ScrollbarsForWheel::IsActive()) { 1.144 + ScrollbarsForWheel::OwnWheelTransaction(true); 1.145 + } else { 1.146 + EndTransaction(); 1.147 + } 1.148 +} 1.149 + 1.150 +/* static */ void 1.151 +WheelTransaction::EndTransaction() 1.152 +{ 1.153 + if (sTimer) { 1.154 + sTimer->Cancel(); 1.155 + } 1.156 + sTargetFrame = nullptr; 1.157 + sScrollSeriesCounter = 0; 1.158 + if (sOwnScrollbars) { 1.159 + sOwnScrollbars = false; 1.160 + ScrollbarsForWheel::OwnWheelTransaction(false); 1.161 + ScrollbarsForWheel::Inactivate(); 1.162 + } 1.163 +} 1.164 + 1.165 +/* static */ void 1.166 +WheelTransaction::OnEvent(WidgetEvent* aEvent) 1.167 +{ 1.168 + if (!sTargetFrame) { 1.169 + return; 1.170 + } 1.171 + 1.172 + if (OutOfTime(sTime, GetTimeoutTime())) { 1.173 + // Even if the scroll event which is handled after timeout, but onTimeout 1.174 + // was not fired by timer, then the scroll event will scroll old frame, 1.175 + // therefore, we should call OnTimeout here and ensure to finish the old 1.176 + // transaction. 1.177 + OnTimeout(nullptr, nullptr); 1.178 + return; 1.179 + } 1.180 + 1.181 + switch (aEvent->message) { 1.182 + case NS_WHEEL_WHEEL: 1.183 + if (sMouseMoved != 0 && 1.184 + OutOfTime(sMouseMoved, GetIgnoreMoveDelayTime())) { 1.185 + // Terminate the current mousewheel transaction if the mouse moved more 1.186 + // than ignoremovedelay milliseconds ago 1.187 + EndTransaction(); 1.188 + } 1.189 + return; 1.190 + case NS_MOUSE_MOVE: 1.191 + case NS_DRAGDROP_OVER: { 1.192 + WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); 1.193 + if (mouseEvent->IsReal()) { 1.194 + // If the cursor is moving to be outside the frame, 1.195 + // terminate the scrollwheel transaction. 1.196 + nsIntPoint pt = GetScreenPoint(mouseEvent); 1.197 + nsIntRect r = sTargetFrame->GetScreenRectExternal(); 1.198 + if (!r.Contains(pt)) { 1.199 + EndTransaction(); 1.200 + return; 1.201 + } 1.202 + 1.203 + // If the cursor is moving inside the frame, and it is less than 1.204 + // ignoremovedelay milliseconds since the last scroll operation, ignore 1.205 + // the mouse move; otherwise, record the current mouse move time to be 1.206 + // checked later 1.207 + if (!sMouseMoved && OutOfTime(sTime, GetIgnoreMoveDelayTime())) { 1.208 + sMouseMoved = PR_IntervalToMilliseconds(PR_IntervalNow()); 1.209 + } 1.210 + } 1.211 + return; 1.212 + } 1.213 + case NS_KEY_PRESS: 1.214 + case NS_KEY_UP: 1.215 + case NS_KEY_DOWN: 1.216 + case NS_MOUSE_BUTTON_UP: 1.217 + case NS_MOUSE_BUTTON_DOWN: 1.218 + case NS_MOUSE_DOUBLECLICK: 1.219 + case NS_MOUSE_CLICK: 1.220 + case NS_CONTEXTMENU: 1.221 + case NS_DRAGDROP_DROP: 1.222 + EndTransaction(); 1.223 + return; 1.224 + } 1.225 +} 1.226 + 1.227 +/* static */ void 1.228 +WheelTransaction::Shutdown() 1.229 +{ 1.230 + NS_IF_RELEASE(sTimer); 1.231 +} 1.232 + 1.233 +/* static */ void 1.234 +WheelTransaction::OnFailToScrollTarget() 1.235 +{ 1.236 + NS_PRECONDITION(sTargetFrame, "We don't have mouse scrolling transaction"); 1.237 + 1.238 + if (Preferences::GetBool("test.mousescroll", false)) { 1.239 + // This event is used for automated tests, see bug 442774. 1.240 + nsContentUtils::DispatchTrustedEvent( 1.241 + sTargetFrame->GetContent()->OwnerDoc(), 1.242 + sTargetFrame->GetContent(), 1.243 + NS_LITERAL_STRING("MozMouseScrollFailed"), 1.244 + true, true); 1.245 + } 1.246 + // The target frame might be destroyed in the event handler, at that time, 1.247 + // we need to finish the current transaction 1.248 + if (!sTargetFrame) { 1.249 + EndTransaction(); 1.250 + } 1.251 +} 1.252 + 1.253 +/* static */ void 1.254 +WheelTransaction::OnTimeout(nsITimer* aTimer, void* aClosure) 1.255 +{ 1.256 + if (!sTargetFrame) { 1.257 + // The transaction target was destroyed already 1.258 + EndTransaction(); 1.259 + return; 1.260 + } 1.261 + // Store the sTargetFrame, the variable becomes null in EndTransaction. 1.262 + nsIFrame* frame = sTargetFrame; 1.263 + // We need to finish current transaction before DOM event firing. Because 1.264 + // the next DOM event might create strange situation for us. 1.265 + MayEndTransaction(); 1.266 + 1.267 + if (Preferences::GetBool("test.mousescroll", false)) { 1.268 + // This event is used for automated tests, see bug 442774. 1.269 + nsContentUtils::DispatchTrustedEvent( 1.270 + frame->GetContent()->OwnerDoc(), 1.271 + frame->GetContent(), 1.272 + NS_LITERAL_STRING("MozMouseScrollTransactionTimeout"), 1.273 + true, true); 1.274 + } 1.275 +} 1.276 + 1.277 +/* static */ void 1.278 +WheelTransaction::SetTimeout() 1.279 +{ 1.280 + if (!sTimer) { 1.281 + nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID); 1.282 + if (!timer) { 1.283 + return; 1.284 + } 1.285 + timer.swap(sTimer); 1.286 + } 1.287 + sTimer->Cancel(); 1.288 + DebugOnly<nsresult> rv = 1.289 + sTimer->InitWithFuncCallback(OnTimeout, nullptr, GetTimeoutTime(), 1.290 + nsITimer::TYPE_ONE_SHOT); 1.291 + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "nsITimer::InitWithFuncCallback failed"); 1.292 +} 1.293 + 1.294 +/* static */ nsIntPoint 1.295 +WheelTransaction::GetScreenPoint(WidgetGUIEvent* aEvent) 1.296 +{ 1.297 + NS_ASSERTION(aEvent, "aEvent is null"); 1.298 + NS_ASSERTION(aEvent->widget, "aEvent-widget is null"); 1.299 + return LayoutDeviceIntPoint::ToUntyped(aEvent->refPoint) + 1.300 + aEvent->widget->WidgetToScreenOffset(); 1.301 +} 1.302 + 1.303 +/* static */ uint32_t 1.304 +WheelTransaction::GetTimeoutTime() 1.305 +{ 1.306 + return Preferences::GetUint("mousewheel.transaction.timeout", 1500); 1.307 +} 1.308 + 1.309 +/* static */ uint32_t 1.310 +WheelTransaction::GetIgnoreMoveDelayTime() 1.311 +{ 1.312 + return Preferences::GetUint("mousewheel.transaction.ignoremovedelay", 100); 1.313 +} 1.314 + 1.315 +/* static */ DeltaValues 1.316 +WheelTransaction::AccelerateWheelDelta(WidgetWheelEvent* aEvent, 1.317 + bool aAllowScrollSpeedOverride) 1.318 +{ 1.319 + DeltaValues result(aEvent); 1.320 + 1.321 + // Don't accelerate the delta values if the event isn't line scrolling. 1.322 + if (aEvent->deltaMode != nsIDOMWheelEvent::DOM_DELTA_LINE) { 1.323 + return result; 1.324 + } 1.325 + 1.326 + if (aAllowScrollSpeedOverride) { 1.327 + result = OverrideSystemScrollSpeed(aEvent); 1.328 + } 1.329 + 1.330 + // Accelerate by the sScrollSeriesCounter 1.331 + int32_t start = GetAccelerationStart(); 1.332 + if (start >= 0 && sScrollSeriesCounter >= start) { 1.333 + int32_t factor = GetAccelerationFactor(); 1.334 + if (factor > 0) { 1.335 + result.deltaX = ComputeAcceleratedWheelDelta(result.deltaX, factor); 1.336 + result.deltaY = ComputeAcceleratedWheelDelta(result.deltaY, factor); 1.337 + } 1.338 + } 1.339 + 1.340 + return result; 1.341 +} 1.342 + 1.343 +/* static */ double 1.344 +WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta, 1.345 + int32_t aFactor) 1.346 +{ 1.347 + if (aDelta == 0.0) { 1.348 + return 0; 1.349 + } 1.350 + 1.351 + return (aDelta * sScrollSeriesCounter * (double)aFactor / 10); 1.352 +} 1.353 + 1.354 +/* static */ int32_t 1.355 +WheelTransaction::GetAccelerationStart() 1.356 +{ 1.357 + return Preferences::GetInt("mousewheel.acceleration.start", -1); 1.358 +} 1.359 + 1.360 +/* static */ int32_t 1.361 +WheelTransaction::GetAccelerationFactor() 1.362 +{ 1.363 + return Preferences::GetInt("mousewheel.acceleration.factor", -1); 1.364 +} 1.365 + 1.366 +/* static */ DeltaValues 1.367 +WheelTransaction::OverrideSystemScrollSpeed(WidgetWheelEvent* aEvent) 1.368 +{ 1.369 + MOZ_ASSERT(sTargetFrame, "We don't have mouse scrolling transaction"); 1.370 + MOZ_ASSERT(aEvent->deltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE); 1.371 + 1.372 + // If the event doesn't scroll to both X and Y, we don't need to do anything 1.373 + // here. 1.374 + if (!aEvent->deltaX && !aEvent->deltaY) { 1.375 + return DeltaValues(aEvent); 1.376 + } 1.377 + 1.378 + // We shouldn't override the scrolling speed on non root scroll frame. 1.379 + if (sTargetFrame != 1.380 + sTargetFrame->PresContext()->PresShell()->GetRootScrollFrame()) { 1.381 + return DeltaValues(aEvent); 1.382 + } 1.383 + 1.384 + // Compute the overridden speed to nsIWidget. The widget can check the 1.385 + // conditions (e.g., checking the prefs, and also whether the user customized 1.386 + // the system settings of the mouse wheel scrolling or not), and can limit 1.387 + // the speed for preventing the unexpected high speed scrolling. 1.388 + nsCOMPtr<nsIWidget> widget(sTargetFrame->GetNearestWidget()); 1.389 + NS_ENSURE_TRUE(widget, DeltaValues(aEvent)); 1.390 + DeltaValues overriddenDeltaValues(0.0, 0.0); 1.391 + nsresult rv = 1.392 + widget->OverrideSystemMouseScrollSpeed(aEvent->deltaX, aEvent->deltaY, 1.393 + overriddenDeltaValues.deltaX, 1.394 + overriddenDeltaValues.deltaY); 1.395 + return NS_FAILED(rv) ? DeltaValues(aEvent) : overriddenDeltaValues; 1.396 +} 1.397 + 1.398 +/******************************************************************/ 1.399 +/* mozilla::ScrollbarsForWheel */ 1.400 +/******************************************************************/ 1.401 + 1.402 +const DeltaValues ScrollbarsForWheel::directions[kNumberOfTargets] = { 1.403 + DeltaValues(-1, 0), DeltaValues(+1, 0), DeltaValues(0, -1), DeltaValues(0, +1) 1.404 +}; 1.405 + 1.406 +nsWeakFrame ScrollbarsForWheel::sActiveOwner = nullptr; 1.407 +nsWeakFrame ScrollbarsForWheel::sActivatedScrollTargets[kNumberOfTargets] = { 1.408 + nullptr, nullptr, nullptr, nullptr 1.409 +}; 1.410 + 1.411 +bool ScrollbarsForWheel::sHadWheelStart = false; 1.412 +bool ScrollbarsForWheel::sOwnWheelTransaction = false; 1.413 + 1.414 +/* static */ void 1.415 +ScrollbarsForWheel::PrepareToScrollText(EventStateManager* aESM, 1.416 + nsIFrame* aTargetFrame, 1.417 + WidgetWheelEvent* aEvent) 1.418 +{ 1.419 + if (aEvent->message == NS_WHEEL_START) { 1.420 + WheelTransaction::OwnScrollbars(false); 1.421 + if (!IsActive()) { 1.422 + TemporarilyActivateAllPossibleScrollTargets(aESM, aTargetFrame, aEvent); 1.423 + sHadWheelStart = true; 1.424 + } 1.425 + } else { 1.426 + DeactivateAllTemporarilyActivatedScrollTargets(); 1.427 + } 1.428 +} 1.429 + 1.430 +/* static */ void 1.431 +ScrollbarsForWheel::SetActiveScrollTarget(nsIScrollableFrame* aScrollTarget) 1.432 +{ 1.433 + if (!sHadWheelStart) { 1.434 + return; 1.435 + } 1.436 + nsIScrollbarOwner* scrollbarOwner = do_QueryFrame(aScrollTarget); 1.437 + if (!scrollbarOwner) { 1.438 + return; 1.439 + } 1.440 + sHadWheelStart = false; 1.441 + sActiveOwner = do_QueryFrame(aScrollTarget); 1.442 + scrollbarOwner->ScrollbarActivityStarted(); 1.443 +} 1.444 + 1.445 +/* static */ void 1.446 +ScrollbarsForWheel::MayInactivate() 1.447 +{ 1.448 + if (!sOwnWheelTransaction && WheelTransaction::GetTargetFrame()) { 1.449 + WheelTransaction::OwnScrollbars(true); 1.450 + } else { 1.451 + Inactivate(); 1.452 + } 1.453 +} 1.454 + 1.455 +/* static */ void 1.456 +ScrollbarsForWheel::Inactivate() 1.457 +{ 1.458 + nsIScrollbarOwner* scrollbarOwner = do_QueryFrame(sActiveOwner); 1.459 + if (scrollbarOwner) { 1.460 + scrollbarOwner->ScrollbarActivityStopped(); 1.461 + } 1.462 + sActiveOwner = nullptr; 1.463 + DeactivateAllTemporarilyActivatedScrollTargets(); 1.464 + if (sOwnWheelTransaction) { 1.465 + sOwnWheelTransaction = false; 1.466 + WheelTransaction::OwnScrollbars(false); 1.467 + WheelTransaction::EndTransaction(); 1.468 + } 1.469 +} 1.470 + 1.471 +/* static */ bool 1.472 +ScrollbarsForWheel::IsActive() 1.473 +{ 1.474 + if (sActiveOwner) { 1.475 + return true; 1.476 + } 1.477 + for (size_t i = 0; i < kNumberOfTargets; ++i) { 1.478 + if (sActivatedScrollTargets[i]) { 1.479 + return true; 1.480 + } 1.481 + } 1.482 + return false; 1.483 +} 1.484 + 1.485 +/* static */ void 1.486 +ScrollbarsForWheel::OwnWheelTransaction(bool aOwn) 1.487 +{ 1.488 + sOwnWheelTransaction = aOwn; 1.489 +} 1.490 + 1.491 +/* static */ void 1.492 +ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets( 1.493 + EventStateManager* aESM, 1.494 + nsIFrame* aTargetFrame, 1.495 + WidgetWheelEvent* aEvent) 1.496 +{ 1.497 + for (size_t i = 0; i < kNumberOfTargets; i++) { 1.498 + const DeltaValues *dir = &directions[i]; 1.499 + nsWeakFrame* scrollTarget = &sActivatedScrollTargets[i]; 1.500 + MOZ_ASSERT(!*scrollTarget, "scroll target still temporarily activated!"); 1.501 + nsIScrollableFrame* target = 1.502 + aESM->ComputeScrollTarget(aTargetFrame, dir->deltaX, dir->deltaY, aEvent, 1.503 + EventStateManager::COMPUTE_DEFAULT_ACTION_TARGET); 1.504 + nsIScrollbarOwner* scrollbarOwner = do_QueryFrame(target); 1.505 + if (scrollbarOwner) { 1.506 + nsIFrame* targetFrame = do_QueryFrame(target); 1.507 + *scrollTarget = targetFrame; 1.508 + scrollbarOwner->ScrollbarActivityStarted(); 1.509 + } 1.510 + } 1.511 +} 1.512 + 1.513 +/* static */ void 1.514 +ScrollbarsForWheel::DeactivateAllTemporarilyActivatedScrollTargets() 1.515 +{ 1.516 + for (size_t i = 0; i < kNumberOfTargets; i++) { 1.517 + nsWeakFrame* scrollTarget = &sActivatedScrollTargets[i]; 1.518 + if (*scrollTarget) { 1.519 + nsIScrollbarOwner* scrollbarOwner = do_QueryFrame(*scrollTarget); 1.520 + if (scrollbarOwner) { 1.521 + scrollbarOwner->ScrollbarActivityStopped(); 1.522 + } 1.523 + *scrollTarget = nullptr; 1.524 + } 1.525 + } 1.526 +} 1.527 + 1.528 +} // namespace mozilla