End APZ wheel transactions when the mouse moves out of frame. (bug 1142866 part 2, r=kats,botond)

This commit is contained in:
David Anderson 2015-03-22 00:42:25 -07:00
Родитель a0a7580fcf
Коммит 302fa4036d
9 изменённых файлов: 186 добавлений и 26 удалений

Просмотреть файл

@ -806,6 +806,47 @@ APZCTreeManager::TransformCoordinateToGecko(const ScreenIntPoint& aPoint,
}
}
void
APZCTreeManager::UpdateWheelTransaction(WidgetInputEvent& aEvent)
{
WheelBlockState* txn = mInputQueue->GetCurrentWheelTransaction();
if (!txn) {
return;
}
// If the transaction has simply timed out, we don't need to do anything
// else.
if (txn->MaybeTimeout(TimeStamp::Now())) {
return;
}
switch (aEvent.message) {
case NS_MOUSE_MOVE:
case NS_DRAGDROP_OVER: {
WidgetMouseEvent* mouseEvent = aEvent.AsMouseEvent();
if (!mouseEvent->IsReal()) {
return;
}
ScreenIntPoint point =
ViewAs<ScreenPixel>(aEvent.refPoint, PixelCastJustification::LayoutDeviceToScreenForUntransformedEvent);
txn->OnMouseMove(point);
return;
}
case NS_KEY_PRESS:
case NS_KEY_UP:
case NS_KEY_DOWN:
case NS_MOUSE_BUTTON_UP:
case NS_MOUSE_BUTTON_DOWN:
case NS_MOUSE_DOUBLECLICK:
case NS_MOUSE_CLICK:
case NS_CONTEXTMENU:
case NS_DRAGDROP_DROP:
txn->EndTransaction();
return;
}
}
nsEventStatus
APZCTreeManager::ProcessEvent(WidgetInputEvent& aEvent,
ScrollableLayerGuid* aOutTargetGuid,
@ -814,6 +855,9 @@ APZCTreeManager::ProcessEvent(WidgetInputEvent& aEvent,
MOZ_ASSERT(NS_IsMainThread());
nsEventStatus result = nsEventStatus_eIgnore;
// Note, we call this before having transformed the reference point.
UpdateWheelTransaction(aEvent);
// Transform the refPoint.
// If the event hits an overscrolled APZC, instruct the caller to ignore it.
HitTestResult hitResult = HitNothing;

Просмотреть файл

@ -436,6 +436,7 @@ private:
nsEventStatus ProcessEvent(WidgetInputEvent& inputEvent,
ScrollableLayerGuid* aOutTargetGuid,
uint64_t* aOutInputBlockId);
void UpdateWheelTransaction(WidgetInputEvent& aEvent);
void UpdateZoomConstraintsRecursively(HitTestingTreeNode* aNode,
const ZoomConstraints& aConstraints);
void FlushRepaintsRecursively(HitTestingTreeNode* aNode);

Просмотреть файл

@ -1767,6 +1767,19 @@ ParentLayerPoint AsyncPanZoomController::ToParentLayerCoordinates(const ScreenPo
return TransformVector<ParentLayerPixel>(GetTransformToThis(), aVector, aAnchor);
}
bool AsyncPanZoomController::Contains(const ScreenIntPoint& aPoint) const
{
Matrix4x4 transformToThis = GetTransformToThis();
ParentLayerIntPoint point = TransformTo<ParentLayerPixel>(transformToThis, aPoint);
ParentLayerIntRect cb;
{
ReentrantMonitorAutoEnter lock(mMonitor);
GetFrameMetrics().mCompositionBounds.ToIntRect(&cb);
}
return cb.Contains(point);
}
ScreenCoord AsyncPanZoomController::PanDistance() const {
ParentLayerPoint panVector;
ParentLayerPoint panStart;

Просмотреть файл

@ -999,6 +999,10 @@ public:
return mAncestorTransform;
}
// Returns whether or not this apzc contains the given screen point within
// its composition bounds.
bool Contains(const ScreenIntPoint& aPoint) const;
bool IsOverscrolled() const {
return mX.IsOverscrolled() || mY.IsOverscrolled();
}

Просмотреть файл

@ -141,7 +141,7 @@ CancelableBlockState::IsReadyForHandling() const
}
void
CancelableBlockState::DispatchImmediate(const InputData& aEvent)
CancelableBlockState::DispatchImmediate(const InputData& aEvent) const
{
MOZ_ASSERT(!HasEvents());
MOZ_ASSERT(GetTargetApzc());
@ -158,7 +158,6 @@ WheelBlockState::WheelBlockState(const nsRefPtr<AsyncPanZoomController>& aTarget
, mTransactionEnded(false)
{
sLastWheelBlockId = GetBlockId();
Update(aInitialEvent);
if (aTargetConfirmed) {
// Find the nearest APZC in the overscroll handoff chain that is scrollable.
@ -167,7 +166,15 @@ WheelBlockState::WheelBlockState(const nsRefPtr<AsyncPanZoomController>& aTarget
// that case.
nsRefPtr<AsyncPanZoomController> apzc =
mOverscrollHandoffChain->FindFirstScrollable(aInitialEvent);
if (apzc && apzc != GetTargetApzc()) {
// If nothing is scrollable, we don't consider this block as starting a
// transaction.
if (!apzc) {
EndTransaction();
return;
}
if (apzc != GetTargetApzc()) {
UpdateTargetApzc(apzc);
}
}
@ -176,23 +183,36 @@ WheelBlockState::WheelBlockState(const nsRefPtr<AsyncPanZoomController>& aTarget
void
WheelBlockState::Update(const ScrollWheelInput& aEvent)
{
// We might not be in a transaction if the block never started in a
// transaction - for example, if nothing was scrollable.
if (!InTransaction()) {
return;
}
// If we can't scroll in the direction of the wheel event, we don't update
// the last move time. This allows us to timeout a transaction even if the
// mouse isn't moving.
//
// We skip this check if the target is not yet confirmed, so that when it is
// confirmed, we don't timeout the transaction.
nsRefPtr<AsyncPanZoomController> apzc = GetTargetApzc();
if (IsTargetConfirmed() && !apzc->CanScroll(aEvent)) {
return;
}
// Update the time of the last known good event, and reset the mouse move
// time to null. This will reset the delays on both the general transaction
// timeout and the mouse-move-in-frame timeout.
mLastEventTime = aEvent.mTimeStamp;
mLastMouseMove = TimeStamp();
}
void
WheelBlockState::AddEvent(const ScrollWheelInput& aEvent)
{
Update(aEvent);
mEvents.AppendElement(aEvent);
}
void
WheelBlockState::DispatchImmediate(const InputData& aEvent)
{
Update(aEvent.AsScrollWheelInput());
CancelableBlockState::DispatchImmediate(aEvent);
}
bool
WheelBlockState::IsReadyForHandling() const
{
@ -239,7 +259,7 @@ WheelBlockState::Type()
}
bool
WheelBlockState::ShouldAcceptNewEvent(const ScrollWheelInput& aEvent) const
WheelBlockState::ShouldAcceptNewEvent() const
{
if (!InTransaction()) {
// If we're not in a transaction, start a new one.
@ -250,17 +270,65 @@ WheelBlockState::ShouldAcceptNewEvent(const ScrollWheelInput& aEvent) const
return false;
}
return true;
}
bool
WheelBlockState::MaybeTimeout(const ScrollWheelInput& aEvent)
{
if (MaybeTimeout(aEvent.mTimeStamp)) {
return true;
}
if (!mLastMouseMove.IsNull()) {
// If there's a recent mouse movement, we can time out the transaction early.
TimeDuration duration = TimeStamp::Now() - mLastMouseMove;
if (duration.ToMilliseconds() >= gfxPrefs::MouseWheelIgnoreMoveDelayMs()) {
TBS_LOG("%p wheel transaction timed out after mouse move\n", this);
EndTransaction();
return true;
}
}
return false;
}
bool
WheelBlockState::MaybeTimeout(const TimeStamp& aTimeStamp)
{
// End the transaction if the event occurred > 1.5s after the most recently
// seen wheel event.
TimeDuration duration = aEvent.mTimeStamp - mLastEventTime;
if (duration.ToMilliseconds() >= gfxPrefs::MouseWheelTransactionTimeoutMs()) {
TimeDuration duration = aTimeStamp - mLastEventTime;
if (duration.ToMilliseconds() < gfxPrefs::MouseWheelTransactionTimeoutMs()) {
return false;
}
// Accept the event, even if we can't scroll anymore.
TBS_LOG("%p wheel transaction timed out\n", this);
EndTransaction();
return true;
}
void
WheelBlockState::OnMouseMove(const ScreenIntPoint& aPoint)
{
if (!GetTargetApzc()->Contains(aPoint)) {
EndTransaction();
return;
}
if (mLastMouseMove.IsNull()) {
// If the cursor is moving inside the frame, and it is more than the
// ignoremovedelay time since the last scroll operation, we record
// this as the most recent mouse movement.
TimeStamp now = TimeStamp::Now();
TimeDuration duration = now - mLastEventTime;
if (duration.ToMilliseconds() >= gfxPrefs::MouseWheelIgnoreMoveDelayMs()) {
mLastMouseMove = now;
}
}
}
bool
WheelBlockState::InTransaction() const
{
@ -283,6 +351,7 @@ WheelBlockState::AllowScrollHandoff() const
void
WheelBlockState::EndTransaction()
{
TBS_LOG("%p ending wheel transaction\n", this);
mTransactionEnded = true;
}

Просмотреть файл

@ -110,7 +110,7 @@ public:
* This input block must not have pending events, and its apzc must not be
* nullptr.
*/
virtual void DispatchImmediate(const InputData& aEvent);
void DispatchImmediate(const InputData& aEvent) const;
/**
* @return true iff this block has received all the information needed
@ -167,7 +167,6 @@ public:
void HandleEvents() override;
bool MustStayActive() override;
const char* Type() override;
void DispatchImmediate(const InputData& aEvent) override;
void AddEvent(const ScrollWheelInput& aEvent);
@ -176,13 +175,21 @@ public:
}
/**
* Returns whether or not the block should accept new events. If the APZC
* has been destroyed, or the block is not part of a wheel transaction, then
* this will return false.
*
* @return True if the event should be accepted, false otherwise.
* Determine whether this wheel block is accepting new events.
*/
bool ShouldAcceptNewEvent(const ScrollWheelInput& aEvent) const;
bool ShouldAcceptNewEvent() const;
/**
* Call to check whether a wheel event will cause the current transaction to
* timeout.
*/
bool MaybeTimeout(const ScrollWheelInput& aEvent);
/**
* Called from APZCTM when a mouse move or drag+drop event occurs, before
* the event has been processed.
*/
void OnMouseMove(const ScreenIntPoint& aPoint);
/**
* Returns whether or not the block is participating in a wheel transaction.
@ -205,7 +212,13 @@ public:
*/
bool AllowScrollHandoff() const;
private:
/**
* Called to check and possibly end the transaction due to a timeout.
*
* @return True if the transaction ended, false otherwise.
*/
bool MaybeTimeout(const TimeStamp& aTimeStamp);
/**
* Update the wheel transaction state for a new event.
*/
@ -214,6 +227,7 @@ private:
private:
nsTArray<ScrollWheelInput> mEvents;
TimeStamp mLastEventTime;
TimeStamp mLastMouseMove;
bool mTransactionEnded;
};

Просмотреть файл

@ -162,7 +162,10 @@ InputQueue::ReceiveScrollWheelInput(const nsRefPtr<AsyncPanZoomController>& aTar
// If the block is not accepting new events we'll create a new input block
// (and therefore a new wheel transaction).
if (block && !block->ShouldAcceptNewEvent(aEvent)) {
if (block &&
(!block->ShouldAcceptNewEvent() ||
block->MaybeTimeout(aEvent)))
{
block = nullptr;
}
}
@ -187,6 +190,8 @@ InputQueue::ReceiveScrollWheelInput(const nsRefPtr<AsyncPanZoomController>& aTar
*aOutInputBlockId = block->GetBlockId();
}
block->Update(aEvent);
// Note that the |aTarget| the APZCTM sent us may contradict the confirmed
// target set on the block. In this case the confirmed target (which may be
// null) should take priority. This is equivalent to just always using the

Просмотреть файл

@ -337,6 +337,7 @@ private:
// This affects whether events will be routed through APZ or not.
DECL_GFX_PREF(Once, "mousewheel.system_scroll_override_on_root_content.enabled",
MouseWheelHasScrollDeltaOverride, bool, false);
DECL_GFX_PREF(Live, "mousewheel.transaction.ignoremovedelay",MouseWheelIgnoreMoveDelayMs, int32_t, (int32_t)100);
DECL_GFX_PREF(Live, "mousewheel.transaction.timeout", MouseWheelTransactionTimeoutMs, int32_t, (int32_t)1500);

Просмотреть файл

@ -30,7 +30,12 @@ enum class PixelCastJustification : uint8_t {
// The transform that is usually used to convert between two coordinate
// systems is not available (for example, because the object that stores it
// is being destroyed), so fall back to the identity.
TransformNotAvailable
TransformNotAvailable,
// When an OS event is initially constructed, its reference point is
// technically in screen pixels, as it has not yet accounted for any
// asynchronous transforms. This justification is for viewing the initial
// reference point as a screen point.
LayoutDeviceToScreenForUntransformedEvent
};
template <class TargetUnits, class SourceUnits>
@ -45,6 +50,10 @@ template <class TargetUnits, class SourceUnits>
gfx::PointTyped<TargetUnits> ViewAs(const gfx::PointTyped<SourceUnits>& aPoint, PixelCastJustification) {
return gfx::PointTyped<TargetUnits>(aPoint.x, aPoint.y);
}
template <class TargetUnits, class SourceUnits>
gfx::IntPointTyped<TargetUnits> ViewAs(const gfx::IntPointTyped<SourceUnits>& aPoint, PixelCastJustification) {
return gfx::IntPointTyped<TargetUnits>(aPoint.x, aPoint.y);
}
template <class NewTargetUnits, class OldTargetUnits, class SourceUnits>
gfx::ScaleFactor<SourceUnits, NewTargetUnits> ViewTargetAs(
const gfx::ScaleFactor<SourceUnits, OldTargetUnits>& aScaleFactor,