WheelHandlingHelper.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #include "WheelHandlingHelper.h"
  6. #include "mozilla/EventDispatcher.h"
  7. #include "mozilla/EventStateManager.h"
  8. #include "mozilla/MouseEvents.h"
  9. #include "mozilla/Preferences.h"
  10. #include "nsCOMPtr.h"
  11. #include "nsContentUtils.h"
  12. #include "nsIContent.h"
  13. #include "nsIDocument.h"
  14. #include "nsIPresShell.h"
  15. #include "nsIScrollableFrame.h"
  16. #include "nsITimer.h"
  17. #include "nsPluginFrame.h"
  18. #include "nsPresContext.h"
  19. #include "prtime.h"
  20. #include "Units.h"
  21. #include "AsyncScrollBase.h"
  22. namespace mozilla {
  23. /******************************************************************/
  24. /* mozilla::DeltaValues */
  25. /******************************************************************/
  26. DeltaValues::DeltaValues(WidgetWheelEvent* aEvent)
  27. : deltaX(aEvent->mDeltaX)
  28. , deltaY(aEvent->mDeltaY)
  29. {
  30. }
  31. /******************************************************************/
  32. /* mozilla::WheelHandlingUtils */
  33. /******************************************************************/
  34. /* static */ bool
  35. WheelHandlingUtils::CanScrollInRange(nscoord aMin, nscoord aValue, nscoord aMax,
  36. double aDirection)
  37. {
  38. return aDirection > 0.0 ? aValue < static_cast<double>(aMax) :
  39. static_cast<double>(aMin) < aValue;
  40. }
  41. /* static */ bool
  42. WheelHandlingUtils::CanScrollOn(nsIFrame* aFrame,
  43. double aDirectionX, double aDirectionY)
  44. {
  45. nsIScrollableFrame* scrollableFrame = do_QueryFrame(aFrame);
  46. if (scrollableFrame) {
  47. return CanScrollOn(scrollableFrame, aDirectionX, aDirectionY);
  48. }
  49. nsPluginFrame* pluginFrame = do_QueryFrame(aFrame);
  50. return pluginFrame && pluginFrame->WantsToHandleWheelEventAsDefaultAction();
  51. }
  52. /* static */ bool
  53. WheelHandlingUtils::CanScrollOn(nsIScrollableFrame* aScrollFrame,
  54. double aDirectionX, double aDirectionY)
  55. {
  56. MOZ_ASSERT(aScrollFrame);
  57. NS_ASSERTION(aDirectionX || aDirectionY,
  58. "One of the delta values must be non-zero at least");
  59. nsPoint scrollPt = aScrollFrame->GetScrollPosition();
  60. nsRect scrollRange = aScrollFrame->GetScrollRange();
  61. uint32_t directions = aScrollFrame->GetPerceivedScrollingDirections();
  62. return (aDirectionX && (directions & nsIScrollableFrame::HORIZONTAL) &&
  63. CanScrollInRange(scrollRange.x, scrollPt.x,
  64. scrollRange.XMost(), aDirectionX)) ||
  65. (aDirectionY && (directions & nsIScrollableFrame::VERTICAL) &&
  66. CanScrollInRange(scrollRange.y, scrollPt.y,
  67. scrollRange.YMost(), aDirectionY));
  68. }
  69. /******************************************************************/
  70. /* mozilla::WheelTransaction */
  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. /* static */ bool
  79. WheelTransaction::OutOfTime(uint32_t aBaseTime, uint32_t aThreshold)
  80. {
  81. uint32_t now = PR_IntervalToMilliseconds(PR_IntervalNow());
  82. return (now - aBaseTime > aThreshold);
  83. }
  84. /* static */ void
  85. WheelTransaction::OwnScrollbars(bool aOwn)
  86. {
  87. sOwnScrollbars = aOwn;
  88. }
  89. /* static */ void
  90. WheelTransaction::BeginTransaction(nsIFrame* aTargetFrame,
  91. WidgetWheelEvent* aEvent)
  92. {
  93. NS_ASSERTION(!sTargetFrame, "previous transaction is not finished!");
  94. MOZ_ASSERT(aEvent->mMessage == eWheel,
  95. "Transaction must be started with a wheel event");
  96. ScrollbarsForWheel::OwnWheelTransaction(false);
  97. sTargetFrame = aTargetFrame;
  98. sScrollSeriesCounter = 0;
  99. if (!UpdateTransaction(aEvent)) {
  100. NS_ERROR("BeginTransaction is called even cannot scroll the frame");
  101. EndTransaction();
  102. }
  103. }
  104. /* static */ bool
  105. WheelTransaction::UpdateTransaction(WidgetWheelEvent* aEvent)
  106. {
  107. nsIFrame* scrollToFrame = GetTargetFrame();
  108. nsIScrollableFrame* scrollableFrame = scrollToFrame->GetScrollTargetFrame();
  109. if (scrollableFrame) {
  110. scrollToFrame = do_QueryFrame(scrollableFrame);
  111. }
  112. if (!WheelHandlingUtils::CanScrollOn(scrollToFrame,
  113. aEvent->mDeltaX, aEvent->mDeltaY)) {
  114. OnFailToScrollTarget();
  115. // We should not modify the transaction state when the view will not be
  116. // scrolled actually.
  117. return false;
  118. }
  119. SetTimeout();
  120. if (sScrollSeriesCounter != 0 && OutOfTime(sTime, kScrollSeriesTimeoutMs)) {
  121. sScrollSeriesCounter = 0;
  122. }
  123. sScrollSeriesCounter++;
  124. // We should use current time instead of WidgetEvent.time.
  125. // 1. Some events doesn't have the correct creation time.
  126. // 2. If the computer runs slowly by other processes eating the CPU resource,
  127. // the event creation time doesn't keep real time.
  128. sTime = PR_IntervalToMilliseconds(PR_IntervalNow());
  129. sMouseMoved = 0;
  130. return true;
  131. }
  132. /* static */ void
  133. WheelTransaction::MayEndTransaction()
  134. {
  135. if (!sOwnScrollbars && ScrollbarsForWheel::IsActive()) {
  136. ScrollbarsForWheel::OwnWheelTransaction(true);
  137. } else {
  138. EndTransaction();
  139. }
  140. }
  141. /* static */ void
  142. WheelTransaction::EndTransaction()
  143. {
  144. if (sTimer) {
  145. sTimer->Cancel();
  146. }
  147. sTargetFrame = nullptr;
  148. sScrollSeriesCounter = 0;
  149. if (sOwnScrollbars) {
  150. sOwnScrollbars = false;
  151. ScrollbarsForWheel::OwnWheelTransaction(false);
  152. ScrollbarsForWheel::Inactivate();
  153. }
  154. }
  155. /* static */ bool
  156. WheelTransaction::WillHandleDefaultAction(WidgetWheelEvent* aWheelEvent,
  157. nsWeakFrame& aTargetWeakFrame)
  158. {
  159. nsIFrame* lastTargetFrame = GetTargetFrame();
  160. if (!lastTargetFrame) {
  161. BeginTransaction(aTargetWeakFrame.GetFrame(), aWheelEvent);
  162. } else if (lastTargetFrame != aTargetWeakFrame.GetFrame()) {
  163. EndTransaction();
  164. BeginTransaction(aTargetWeakFrame.GetFrame(), aWheelEvent);
  165. } else {
  166. UpdateTransaction(aWheelEvent);
  167. }
  168. // When the wheel event will not be handled with any frames,
  169. // UpdateTransaction() fires MozMouseScrollFailed event which is for
  170. // automated testing. In the event handler, the target frame might be
  171. // destroyed. Then, the caller shouldn't try to handle the default action.
  172. if (!aTargetWeakFrame.IsAlive()) {
  173. EndTransaction();
  174. return false;
  175. }
  176. return true;
  177. }
  178. /* static */ void
  179. WheelTransaction::OnEvent(WidgetEvent* aEvent)
  180. {
  181. if (!sTargetFrame) {
  182. return;
  183. }
  184. if (OutOfTime(sTime, GetTimeoutTime())) {
  185. // Even if the scroll event which is handled after timeout, but onTimeout
  186. // was not fired by timer, then the scroll event will scroll old frame,
  187. // therefore, we should call OnTimeout here and ensure to finish the old
  188. // transaction.
  189. OnTimeout(nullptr, nullptr);
  190. return;
  191. }
  192. switch (aEvent->mMessage) {
  193. case eWheel:
  194. if (sMouseMoved != 0 &&
  195. OutOfTime(sMouseMoved, GetIgnoreMoveDelayTime())) {
  196. // Terminate the current mousewheel transaction if the mouse moved more
  197. // than ignoremovedelay milliseconds ago
  198. EndTransaction();
  199. }
  200. return;
  201. case eMouseMove:
  202. case eDragOver: {
  203. WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
  204. if (mouseEvent->IsReal()) {
  205. // If the cursor is moving to be outside the frame,
  206. // terminate the scrollwheel transaction.
  207. nsIntPoint pt = GetScreenPoint(mouseEvent);
  208. nsIntRect r = sTargetFrame->GetScreenRect();
  209. if (!r.Contains(pt)) {
  210. EndTransaction();
  211. return;
  212. }
  213. // If the cursor is moving inside the frame, and it is less than
  214. // ignoremovedelay milliseconds since the last scroll operation, ignore
  215. // the mouse move; otherwise, record the current mouse move time to be
  216. // checked later
  217. if (!sMouseMoved && OutOfTime(sTime, GetIgnoreMoveDelayTime())) {
  218. sMouseMoved = PR_IntervalToMilliseconds(PR_IntervalNow());
  219. }
  220. }
  221. return;
  222. }
  223. case eKeyPress:
  224. case eKeyUp:
  225. case eKeyDown:
  226. case eMouseUp:
  227. case eMouseDown:
  228. case eMouseDoubleClick:
  229. case eMouseAuxClick:
  230. case eMouseClick:
  231. case eContextMenu:
  232. case eDrop:
  233. EndTransaction();
  234. return;
  235. default:
  236. break;
  237. }
  238. }
  239. /* static */ void
  240. WheelTransaction::Shutdown()
  241. {
  242. NS_IF_RELEASE(sTimer);
  243. }
  244. /* static */ void
  245. WheelTransaction::OnFailToScrollTarget()
  246. {
  247. NS_PRECONDITION(sTargetFrame, "We don't have mouse scrolling transaction");
  248. if (Preferences::GetBool("test.mousescroll", false)) {
  249. // This event is used for automated tests, see bug 442774.
  250. nsContentUtils::DispatchTrustedEvent(
  251. sTargetFrame->GetContent()->OwnerDoc(),
  252. sTargetFrame->GetContent(),
  253. NS_LITERAL_STRING("MozMouseScrollFailed"),
  254. true, true);
  255. }
  256. // The target frame might be destroyed in the event handler, at that time,
  257. // we need to finish the current transaction
  258. if (!sTargetFrame) {
  259. EndTransaction();
  260. }
  261. }
  262. /* static */ void
  263. WheelTransaction::OnTimeout(nsITimer* aTimer, void* aClosure)
  264. {
  265. if (!sTargetFrame) {
  266. // The transaction target was destroyed already
  267. EndTransaction();
  268. return;
  269. }
  270. // Store the sTargetFrame, the variable becomes null in EndTransaction.
  271. nsIFrame* frame = sTargetFrame;
  272. // We need to finish current transaction before DOM event firing. Because
  273. // the next DOM event might create strange situation for us.
  274. MayEndTransaction();
  275. if (Preferences::GetBool("test.mousescroll", false)) {
  276. // This event is used for automated tests, see bug 442774.
  277. nsContentUtils::DispatchTrustedEvent(
  278. frame->GetContent()->OwnerDoc(),
  279. frame->GetContent(),
  280. NS_LITERAL_STRING("MozMouseScrollTransactionTimeout"),
  281. true, true);
  282. }
  283. }
  284. /* static */ void
  285. WheelTransaction::SetTimeout()
  286. {
  287. if (!sTimer) {
  288. nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
  289. if (!timer) {
  290. return;
  291. }
  292. timer.swap(sTimer);
  293. }
  294. sTimer->Cancel();
  295. DebugOnly<nsresult> rv =
  296. sTimer->InitWithFuncCallback(OnTimeout, nullptr, GetTimeoutTime(),
  297. nsITimer::TYPE_ONE_SHOT);
  298. NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
  299. "nsITimer::InitWithFuncCallback failed");
  300. }
  301. /* static */ nsIntPoint
  302. WheelTransaction::GetScreenPoint(WidgetGUIEvent* aEvent)
  303. {
  304. NS_ASSERTION(aEvent, "aEvent is null");
  305. NS_ASSERTION(aEvent->mWidget, "aEvent-mWidget is null");
  306. return (aEvent->mRefPoint + aEvent->mWidget->WidgetToScreenOffset())
  307. .ToUnknownPoint();
  308. }
  309. /* static */ uint32_t
  310. WheelTransaction::GetTimeoutTime()
  311. {
  312. return Preferences::GetUint("mousewheel.transaction.timeout", 1500);
  313. }
  314. /* static */ uint32_t
  315. WheelTransaction::GetIgnoreMoveDelayTime()
  316. {
  317. return Preferences::GetUint("mousewheel.transaction.ignoremovedelay", 100);
  318. }
  319. /* static */ DeltaValues
  320. WheelTransaction::AccelerateWheelDelta(WidgetWheelEvent* aEvent,
  321. bool aAllowScrollSpeedOverride)
  322. {
  323. DeltaValues result(aEvent);
  324. // Don't accelerate the delta values if the event isn't line scrolling.
  325. if (aEvent->mDeltaMode != nsIDOMWheelEvent::DOM_DELTA_LINE) {
  326. return result;
  327. }
  328. if (aAllowScrollSpeedOverride) {
  329. result = OverrideSystemScrollSpeed(aEvent);
  330. }
  331. // Accelerate by the sScrollSeriesCounter
  332. int32_t start = GetAccelerationStart();
  333. if (start >= 0 && sScrollSeriesCounter >= start) {
  334. int32_t factor = GetAccelerationFactor();
  335. if (factor > 0) {
  336. result.deltaX = ComputeAcceleratedWheelDelta(result.deltaX, factor);
  337. result.deltaY = ComputeAcceleratedWheelDelta(result.deltaY, factor);
  338. }
  339. }
  340. return result;
  341. }
  342. /* static */ double
  343. WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta, int32_t aFactor)
  344. {
  345. return mozilla::ComputeAcceleratedWheelDelta(aDelta, sScrollSeriesCounter, aFactor);
  346. }
  347. /* static */ int32_t
  348. WheelTransaction::GetAccelerationStart()
  349. {
  350. return Preferences::GetInt("mousewheel.acceleration.start", -1);
  351. }
  352. /* static */ int32_t
  353. WheelTransaction::GetAccelerationFactor()
  354. {
  355. return Preferences::GetInt("mousewheel.acceleration.factor", -1);
  356. }
  357. /* static */ DeltaValues
  358. WheelTransaction::OverrideSystemScrollSpeed(WidgetWheelEvent* aEvent)
  359. {
  360. MOZ_ASSERT(sTargetFrame, "We don't have mouse scrolling transaction");
  361. MOZ_ASSERT(aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE);
  362. // If the event doesn't scroll to both X and Y, we don't need to do anything
  363. // here.
  364. if (!aEvent->mDeltaX && !aEvent->mDeltaY) {
  365. return DeltaValues(aEvent);
  366. }
  367. return DeltaValues(aEvent->OverriddenDeltaX(),
  368. aEvent->OverriddenDeltaY());
  369. }
  370. /******************************************************************/
  371. /* mozilla::ScrollbarsForWheel */
  372. /******************************************************************/
  373. const DeltaValues ScrollbarsForWheel::directions[kNumberOfTargets] = {
  374. DeltaValues(-1, 0), DeltaValues(+1, 0), DeltaValues(0, -1), DeltaValues(0, +1)
  375. };
  376. nsWeakFrame ScrollbarsForWheel::sActiveOwner = nullptr;
  377. nsWeakFrame ScrollbarsForWheel::sActivatedScrollTargets[kNumberOfTargets] = {
  378. nullptr, nullptr, nullptr, nullptr
  379. };
  380. bool ScrollbarsForWheel::sHadWheelStart = false;
  381. bool ScrollbarsForWheel::sOwnWheelTransaction = false;
  382. /* static */ void
  383. ScrollbarsForWheel::PrepareToScrollText(EventStateManager* aESM,
  384. nsIFrame* aTargetFrame,
  385. WidgetWheelEvent* aEvent)
  386. {
  387. if (aEvent->mMessage == eWheelOperationStart) {
  388. WheelTransaction::OwnScrollbars(false);
  389. if (!IsActive()) {
  390. TemporarilyActivateAllPossibleScrollTargets(aESM, aTargetFrame, aEvent);
  391. sHadWheelStart = true;
  392. }
  393. } else {
  394. DeactivateAllTemporarilyActivatedScrollTargets();
  395. }
  396. }
  397. /* static */ void
  398. ScrollbarsForWheel::SetActiveScrollTarget(nsIScrollableFrame* aScrollTarget)
  399. {
  400. if (!sHadWheelStart) {
  401. return;
  402. }
  403. nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(aScrollTarget);
  404. if (!scrollbarMediator) {
  405. return;
  406. }
  407. sHadWheelStart = false;
  408. sActiveOwner = do_QueryFrame(aScrollTarget);
  409. scrollbarMediator->ScrollbarActivityStarted();
  410. }
  411. /* static */ void
  412. ScrollbarsForWheel::MayInactivate()
  413. {
  414. if (!sOwnWheelTransaction && WheelTransaction::GetTargetFrame()) {
  415. WheelTransaction::OwnScrollbars(true);
  416. } else {
  417. Inactivate();
  418. }
  419. }
  420. /* static */ void
  421. ScrollbarsForWheel::Inactivate()
  422. {
  423. nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sActiveOwner);
  424. if (scrollbarMediator) {
  425. scrollbarMediator->ScrollbarActivityStopped();
  426. }
  427. sActiveOwner = nullptr;
  428. DeactivateAllTemporarilyActivatedScrollTargets();
  429. if (sOwnWheelTransaction) {
  430. sOwnWheelTransaction = false;
  431. WheelTransaction::OwnScrollbars(false);
  432. WheelTransaction::EndTransaction();
  433. }
  434. }
  435. /* static */ bool
  436. ScrollbarsForWheel::IsActive()
  437. {
  438. if (sActiveOwner) {
  439. return true;
  440. }
  441. for (size_t i = 0; i < kNumberOfTargets; ++i) {
  442. if (sActivatedScrollTargets[i]) {
  443. return true;
  444. }
  445. }
  446. return false;
  447. }
  448. /* static */ void
  449. ScrollbarsForWheel::OwnWheelTransaction(bool aOwn)
  450. {
  451. sOwnWheelTransaction = aOwn;
  452. }
  453. /* static */ void
  454. ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets(
  455. EventStateManager* aESM,
  456. nsIFrame* aTargetFrame,
  457. WidgetWheelEvent* aEvent)
  458. {
  459. for (size_t i = 0; i < kNumberOfTargets; i++) {
  460. const DeltaValues *dir = &directions[i];
  461. nsWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
  462. MOZ_ASSERT(!*scrollTarget, "scroll target still temporarily activated!");
  463. nsIScrollableFrame* target = do_QueryFrame(
  464. aESM->ComputeScrollTarget(aTargetFrame, dir->deltaX, dir->deltaY, aEvent,
  465. EventStateManager::COMPUTE_DEFAULT_ACTION_TARGET));
  466. nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(target);
  467. if (scrollbarMediator) {
  468. nsIFrame* targetFrame = do_QueryFrame(target);
  469. *scrollTarget = targetFrame;
  470. scrollbarMediator->ScrollbarActivityStarted();
  471. }
  472. }
  473. }
  474. /* static */ void
  475. ScrollbarsForWheel::DeactivateAllTemporarilyActivatedScrollTargets()
  476. {
  477. for (size_t i = 0; i < kNumberOfTargets; i++) {
  478. nsWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
  479. if (*scrollTarget) {
  480. nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(*scrollTarget);
  481. if (scrollbarMediator) {
  482. scrollbarMediator->ScrollbarActivityStopped();
  483. }
  484. *scrollTarget = nullptr;
  485. }
  486. }
  487. }
  488. } // namespace mozilla