123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- #include "WheelHandlingHelper.h"
- #include "mozilla/EventDispatcher.h"
- #include "mozilla/EventStateManager.h"
- #include "mozilla/MouseEvents.h"
- #include "mozilla/Preferences.h"
- #include "nsCOMPtr.h"
- #include "nsContentUtils.h"
- #include "nsIContent.h"
- #include "nsIDocument.h"
- #include "nsIPresShell.h"
- #include "nsIScrollableFrame.h"
- #include "nsITimer.h"
- #include "nsPluginFrame.h"
- #include "nsPresContext.h"
- #include "prtime.h"
- #include "Units.h"
- #include "AsyncScrollBase.h"
- namespace mozilla {
- /******************************************************************/
- /* mozilla::DeltaValues */
- /******************************************************************/
- DeltaValues::DeltaValues(WidgetWheelEvent* aEvent)
- : deltaX(aEvent->mDeltaX)
- , deltaY(aEvent->mDeltaY)
- {
- }
- /******************************************************************/
- /* mozilla::WheelHandlingUtils */
- /******************************************************************/
- /* static */ bool
- WheelHandlingUtils::CanScrollInRange(nscoord aMin, nscoord aValue, nscoord aMax,
- double aDirection)
- {
- return aDirection > 0.0 ? aValue < static_cast<double>(aMax) :
- static_cast<double>(aMin) < aValue;
- }
- /* static */ bool
- WheelHandlingUtils::CanScrollOn(nsIFrame* aFrame,
- double aDirectionX, double aDirectionY)
- {
- nsIScrollableFrame* scrollableFrame = do_QueryFrame(aFrame);
- if (scrollableFrame) {
- return CanScrollOn(scrollableFrame, aDirectionX, aDirectionY);
- }
- nsPluginFrame* pluginFrame = do_QueryFrame(aFrame);
- return pluginFrame && pluginFrame->WantsToHandleWheelEventAsDefaultAction();
- }
- /* static */ bool
- WheelHandlingUtils::CanScrollOn(nsIScrollableFrame* aScrollFrame,
- double aDirectionX, double aDirectionY)
- {
- MOZ_ASSERT(aScrollFrame);
- NS_ASSERTION(aDirectionX || aDirectionY,
- "One of the delta values must be non-zero at least");
- nsPoint scrollPt = aScrollFrame->GetScrollPosition();
- nsRect scrollRange = aScrollFrame->GetScrollRange();
- uint32_t directions = aScrollFrame->GetPerceivedScrollingDirections();
- return (aDirectionX && (directions & nsIScrollableFrame::HORIZONTAL) &&
- CanScrollInRange(scrollRange.x, scrollPt.x,
- scrollRange.XMost(), aDirectionX)) ||
- (aDirectionY && (directions & nsIScrollableFrame::VERTICAL) &&
- CanScrollInRange(scrollRange.y, scrollPt.y,
- scrollRange.YMost(), aDirectionY));
- }
- /******************************************************************/
- /* mozilla::WheelTransaction */
- /******************************************************************/
- nsWeakFrame WheelTransaction::sTargetFrame(nullptr);
- uint32_t WheelTransaction::sTime = 0;
- uint32_t WheelTransaction::sMouseMoved = 0;
- nsITimer* WheelTransaction::sTimer = nullptr;
- int32_t WheelTransaction::sScrollSeriesCounter = 0;
- bool WheelTransaction::sOwnScrollbars = false;
- /* static */ bool
- WheelTransaction::OutOfTime(uint32_t aBaseTime, uint32_t aThreshold)
- {
- uint32_t now = PR_IntervalToMilliseconds(PR_IntervalNow());
- return (now - aBaseTime > aThreshold);
- }
- /* static */ void
- WheelTransaction::OwnScrollbars(bool aOwn)
- {
- sOwnScrollbars = aOwn;
- }
- /* static */ void
- WheelTransaction::BeginTransaction(nsIFrame* aTargetFrame,
- WidgetWheelEvent* aEvent)
- {
- NS_ASSERTION(!sTargetFrame, "previous transaction is not finished!");
- MOZ_ASSERT(aEvent->mMessage == eWheel,
- "Transaction must be started with a wheel event");
- ScrollbarsForWheel::OwnWheelTransaction(false);
- sTargetFrame = aTargetFrame;
- sScrollSeriesCounter = 0;
- if (!UpdateTransaction(aEvent)) {
- NS_ERROR("BeginTransaction is called even cannot scroll the frame");
- EndTransaction();
- }
- }
- /* static */ bool
- WheelTransaction::UpdateTransaction(WidgetWheelEvent* aEvent)
- {
- nsIFrame* scrollToFrame = GetTargetFrame();
- nsIScrollableFrame* scrollableFrame = scrollToFrame->GetScrollTargetFrame();
- if (scrollableFrame) {
- scrollToFrame = do_QueryFrame(scrollableFrame);
- }
- if (!WheelHandlingUtils::CanScrollOn(scrollToFrame,
- aEvent->mDeltaX, aEvent->mDeltaY)) {
- OnFailToScrollTarget();
- // We should not modify the transaction state when the view will not be
- // scrolled actually.
- return false;
- }
- SetTimeout();
- if (sScrollSeriesCounter != 0 && OutOfTime(sTime, kScrollSeriesTimeoutMs)) {
- sScrollSeriesCounter = 0;
- }
- sScrollSeriesCounter++;
- // We should use current time instead of WidgetEvent.time.
- // 1. Some events doesn't have the correct creation time.
- // 2. If the computer runs slowly by other processes eating the CPU resource,
- // the event creation time doesn't keep real time.
- sTime = PR_IntervalToMilliseconds(PR_IntervalNow());
- sMouseMoved = 0;
- return true;
- }
- /* static */ void
- WheelTransaction::MayEndTransaction()
- {
- if (!sOwnScrollbars && ScrollbarsForWheel::IsActive()) {
- ScrollbarsForWheel::OwnWheelTransaction(true);
- } else {
- EndTransaction();
- }
- }
- /* static */ void
- WheelTransaction::EndTransaction()
- {
- if (sTimer) {
- sTimer->Cancel();
- }
- sTargetFrame = nullptr;
- sScrollSeriesCounter = 0;
- if (sOwnScrollbars) {
- sOwnScrollbars = false;
- ScrollbarsForWheel::OwnWheelTransaction(false);
- ScrollbarsForWheel::Inactivate();
- }
- }
- /* static */ bool
- WheelTransaction::WillHandleDefaultAction(WidgetWheelEvent* aWheelEvent,
- nsWeakFrame& aTargetWeakFrame)
- {
- nsIFrame* lastTargetFrame = GetTargetFrame();
- if (!lastTargetFrame) {
- BeginTransaction(aTargetWeakFrame.GetFrame(), aWheelEvent);
- } else if (lastTargetFrame != aTargetWeakFrame.GetFrame()) {
- EndTransaction();
- BeginTransaction(aTargetWeakFrame.GetFrame(), aWheelEvent);
- } else {
- UpdateTransaction(aWheelEvent);
- }
- // When the wheel event will not be handled with any frames,
- // UpdateTransaction() fires MozMouseScrollFailed event which is for
- // automated testing. In the event handler, the target frame might be
- // destroyed. Then, the caller shouldn't try to handle the default action.
- if (!aTargetWeakFrame.IsAlive()) {
- EndTransaction();
- return false;
- }
- return true;
- }
- /* static */ void
- WheelTransaction::OnEvent(WidgetEvent* aEvent)
- {
- if (!sTargetFrame) {
- return;
- }
- if (OutOfTime(sTime, GetTimeoutTime())) {
- // Even if the scroll event which is handled after timeout, but onTimeout
- // was not fired by timer, then the scroll event will scroll old frame,
- // therefore, we should call OnTimeout here and ensure to finish the old
- // transaction.
- OnTimeout(nullptr, nullptr);
- return;
- }
- switch (aEvent->mMessage) {
- case eWheel:
- if (sMouseMoved != 0 &&
- OutOfTime(sMouseMoved, GetIgnoreMoveDelayTime())) {
- // Terminate the current mousewheel transaction if the mouse moved more
- // than ignoremovedelay milliseconds ago
- EndTransaction();
- }
- return;
- case eMouseMove:
- case eDragOver: {
- WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
- if (mouseEvent->IsReal()) {
- // If the cursor is moving to be outside the frame,
- // terminate the scrollwheel transaction.
- nsIntPoint pt = GetScreenPoint(mouseEvent);
- nsIntRect r = sTargetFrame->GetScreenRect();
- if (!r.Contains(pt)) {
- EndTransaction();
- return;
- }
- // If the cursor is moving inside the frame, and it is less than
- // ignoremovedelay milliseconds since the last scroll operation, ignore
- // the mouse move; otherwise, record the current mouse move time to be
- // checked later
- if (!sMouseMoved && OutOfTime(sTime, GetIgnoreMoveDelayTime())) {
- sMouseMoved = PR_IntervalToMilliseconds(PR_IntervalNow());
- }
- }
- return;
- }
- case eKeyPress:
- case eKeyUp:
- case eKeyDown:
- case eMouseUp:
- case eMouseDown:
- case eMouseDoubleClick:
- case eMouseAuxClick:
- case eMouseClick:
- case eContextMenu:
- case eDrop:
- EndTransaction();
- return;
- default:
- break;
- }
- }
- /* static */ void
- WheelTransaction::Shutdown()
- {
- NS_IF_RELEASE(sTimer);
- }
- /* static */ void
- WheelTransaction::OnFailToScrollTarget()
- {
- NS_PRECONDITION(sTargetFrame, "We don't have mouse scrolling transaction");
- if (Preferences::GetBool("test.mousescroll", false)) {
- // This event is used for automated tests, see bug 442774.
- nsContentUtils::DispatchTrustedEvent(
- sTargetFrame->GetContent()->OwnerDoc(),
- sTargetFrame->GetContent(),
- NS_LITERAL_STRING("MozMouseScrollFailed"),
- true, true);
- }
- // The target frame might be destroyed in the event handler, at that time,
- // we need to finish the current transaction
- if (!sTargetFrame) {
- EndTransaction();
- }
- }
- /* static */ void
- WheelTransaction::OnTimeout(nsITimer* aTimer, void* aClosure)
- {
- if (!sTargetFrame) {
- // The transaction target was destroyed already
- EndTransaction();
- return;
- }
- // Store the sTargetFrame, the variable becomes null in EndTransaction.
- nsIFrame* frame = sTargetFrame;
- // We need to finish current transaction before DOM event firing. Because
- // the next DOM event might create strange situation for us.
- MayEndTransaction();
- if (Preferences::GetBool("test.mousescroll", false)) {
- // This event is used for automated tests, see bug 442774.
- nsContentUtils::DispatchTrustedEvent(
- frame->GetContent()->OwnerDoc(),
- frame->GetContent(),
- NS_LITERAL_STRING("MozMouseScrollTransactionTimeout"),
- true, true);
- }
- }
- /* static */ void
- WheelTransaction::SetTimeout()
- {
- if (!sTimer) {
- nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
- if (!timer) {
- return;
- }
- timer.swap(sTimer);
- }
- sTimer->Cancel();
- DebugOnly<nsresult> rv =
- sTimer->InitWithFuncCallback(OnTimeout, nullptr, GetTimeoutTime(),
- nsITimer::TYPE_ONE_SHOT);
- NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
- "nsITimer::InitWithFuncCallback failed");
- }
- /* static */ nsIntPoint
- WheelTransaction::GetScreenPoint(WidgetGUIEvent* aEvent)
- {
- NS_ASSERTION(aEvent, "aEvent is null");
- NS_ASSERTION(aEvent->mWidget, "aEvent-mWidget is null");
- return (aEvent->mRefPoint + aEvent->mWidget->WidgetToScreenOffset())
- .ToUnknownPoint();
- }
- /* static */ uint32_t
- WheelTransaction::GetTimeoutTime()
- {
- return Preferences::GetUint("mousewheel.transaction.timeout", 1500);
- }
- /* static */ uint32_t
- WheelTransaction::GetIgnoreMoveDelayTime()
- {
- return Preferences::GetUint("mousewheel.transaction.ignoremovedelay", 100);
- }
- /* static */ DeltaValues
- WheelTransaction::AccelerateWheelDelta(WidgetWheelEvent* aEvent,
- bool aAllowScrollSpeedOverride)
- {
- DeltaValues result(aEvent);
- // Don't accelerate the delta values if the event isn't line scrolling.
- if (aEvent->mDeltaMode != nsIDOMWheelEvent::DOM_DELTA_LINE) {
- return result;
- }
- if (aAllowScrollSpeedOverride) {
- result = OverrideSystemScrollSpeed(aEvent);
- }
- // Accelerate by the sScrollSeriesCounter
- int32_t start = GetAccelerationStart();
- if (start >= 0 && sScrollSeriesCounter >= start) {
- int32_t factor = GetAccelerationFactor();
- if (factor > 0) {
- result.deltaX = ComputeAcceleratedWheelDelta(result.deltaX, factor);
- result.deltaY = ComputeAcceleratedWheelDelta(result.deltaY, factor);
- }
- }
- return result;
- }
- /* static */ double
- WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta, int32_t aFactor)
- {
- return mozilla::ComputeAcceleratedWheelDelta(aDelta, sScrollSeriesCounter, aFactor);
- }
- /* static */ int32_t
- WheelTransaction::GetAccelerationStart()
- {
- return Preferences::GetInt("mousewheel.acceleration.start", -1);
- }
- /* static */ int32_t
- WheelTransaction::GetAccelerationFactor()
- {
- return Preferences::GetInt("mousewheel.acceleration.factor", -1);
- }
- /* static */ DeltaValues
- WheelTransaction::OverrideSystemScrollSpeed(WidgetWheelEvent* aEvent)
- {
- MOZ_ASSERT(sTargetFrame, "We don't have mouse scrolling transaction");
- MOZ_ASSERT(aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE);
- // If the event doesn't scroll to both X and Y, we don't need to do anything
- // here.
- if (!aEvent->mDeltaX && !aEvent->mDeltaY) {
- return DeltaValues(aEvent);
- }
- return DeltaValues(aEvent->OverriddenDeltaX(),
- aEvent->OverriddenDeltaY());
- }
- /******************************************************************/
- /* mozilla::ScrollbarsForWheel */
- /******************************************************************/
- const DeltaValues ScrollbarsForWheel::directions[kNumberOfTargets] = {
- DeltaValues(-1, 0), DeltaValues(+1, 0), DeltaValues(0, -1), DeltaValues(0, +1)
- };
- nsWeakFrame ScrollbarsForWheel::sActiveOwner = nullptr;
- nsWeakFrame ScrollbarsForWheel::sActivatedScrollTargets[kNumberOfTargets] = {
- nullptr, nullptr, nullptr, nullptr
- };
- bool ScrollbarsForWheel::sHadWheelStart = false;
- bool ScrollbarsForWheel::sOwnWheelTransaction = false;
- /* static */ void
- ScrollbarsForWheel::PrepareToScrollText(EventStateManager* aESM,
- nsIFrame* aTargetFrame,
- WidgetWheelEvent* aEvent)
- {
- if (aEvent->mMessage == eWheelOperationStart) {
- WheelTransaction::OwnScrollbars(false);
- if (!IsActive()) {
- TemporarilyActivateAllPossibleScrollTargets(aESM, aTargetFrame, aEvent);
- sHadWheelStart = true;
- }
- } else {
- DeactivateAllTemporarilyActivatedScrollTargets();
- }
- }
- /* static */ void
- ScrollbarsForWheel::SetActiveScrollTarget(nsIScrollableFrame* aScrollTarget)
- {
- if (!sHadWheelStart) {
- return;
- }
- nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(aScrollTarget);
- if (!scrollbarMediator) {
- return;
- }
- sHadWheelStart = false;
- sActiveOwner = do_QueryFrame(aScrollTarget);
- scrollbarMediator->ScrollbarActivityStarted();
- }
- /* static */ void
- ScrollbarsForWheel::MayInactivate()
- {
- if (!sOwnWheelTransaction && WheelTransaction::GetTargetFrame()) {
- WheelTransaction::OwnScrollbars(true);
- } else {
- Inactivate();
- }
- }
- /* static */ void
- ScrollbarsForWheel::Inactivate()
- {
- nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sActiveOwner);
- if (scrollbarMediator) {
- scrollbarMediator->ScrollbarActivityStopped();
- }
- sActiveOwner = nullptr;
- DeactivateAllTemporarilyActivatedScrollTargets();
- if (sOwnWheelTransaction) {
- sOwnWheelTransaction = false;
- WheelTransaction::OwnScrollbars(false);
- WheelTransaction::EndTransaction();
- }
- }
- /* static */ bool
- ScrollbarsForWheel::IsActive()
- {
- if (sActiveOwner) {
- return true;
- }
- for (size_t i = 0; i < kNumberOfTargets; ++i) {
- if (sActivatedScrollTargets[i]) {
- return true;
- }
- }
- return false;
- }
- /* static */ void
- ScrollbarsForWheel::OwnWheelTransaction(bool aOwn)
- {
- sOwnWheelTransaction = aOwn;
- }
- /* static */ void
- ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets(
- EventStateManager* aESM,
- nsIFrame* aTargetFrame,
- WidgetWheelEvent* aEvent)
- {
- for (size_t i = 0; i < kNumberOfTargets; i++) {
- const DeltaValues *dir = &directions[i];
- nsWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
- MOZ_ASSERT(!*scrollTarget, "scroll target still temporarily activated!");
- nsIScrollableFrame* target = do_QueryFrame(
- aESM->ComputeScrollTarget(aTargetFrame, dir->deltaX, dir->deltaY, aEvent,
- EventStateManager::COMPUTE_DEFAULT_ACTION_TARGET));
- nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(target);
- if (scrollbarMediator) {
- nsIFrame* targetFrame = do_QueryFrame(target);
- *scrollTarget = targetFrame;
- scrollbarMediator->ScrollbarActivityStarted();
- }
- }
- }
- /* static */ void
- ScrollbarsForWheel::DeactivateAllTemporarilyActivatedScrollTargets()
- {
- for (size_t i = 0; i < kNumberOfTargets; i++) {
- nsWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
- if (*scrollTarget) {
- nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(*scrollTarget);
- if (scrollbarMediator) {
- scrollbarMediator->ScrollbarActivityStopped();
- }
- *scrollTarget = nullptr;
- }
- }
- }
- } // namespace mozilla
|