From f735817b213a10767bd77cbbb6823960b9f71804 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Tue, 29 Jul 2014 14:17:32 -0400 Subject: [PATCH] Bug 1043608 - Rewrite touch event codepaths for Metro to deal with changes in the APZ touch block code (made in bug 1009733). r=jimm --- widget/windows/winrt/APZController.cpp | 1 + widget/windows/winrt/MetroInput.cpp | 251 +++++++++---------------- widget/windows/winrt/MetroInput.h | 50 ++--- 3 files changed, 112 insertions(+), 190 deletions(-) diff --git a/widget/windows/winrt/APZController.cpp b/widget/windows/winrt/APZController.cpp index 8e3f66cfb4d9..1be76af3c976 100644 --- a/widget/windows/winrt/APZController.cpp +++ b/widget/windows/winrt/APZController.cpp @@ -230,6 +230,7 @@ APZController::HandleLongTap(const CSSPoint& aPoint, int32_t aModifiers, const ScrollableLayerGuid& aGuid) { + ContentReceivedTouch(aGuid, false); } void diff --git a/widget/windows/winrt/MetroInput.cpp b/widget/windows/winrt/MetroInput.cpp index ad15692fae45..b1d81a0b2ef7 100644 --- a/widget/windows/winrt/MetroInput.cpp +++ b/widget/windows/winrt/MetroInput.cpp @@ -521,17 +521,6 @@ MetroInput::GetAllowedTouchBehavior(WidgetTouchEvent* aTransformedEvent, nsTArra } } -bool -MetroInput::IsTouchBehaviorForbidden(const nsTArray& aTouchBehaviors) -{ - for (size_t i = 0; i < aTouchBehaviors.Length(); i++) { - if (aTouchBehaviors[i] == AllowedTouchBehavior::NONE) - return true; - } - - return false; -} - // This event is raised when the user pushes the left mouse button, presses a // pen to the surface, or presses a touch screen. HRESULT @@ -574,13 +563,7 @@ MetroInput::OnPointerPressed(UI::Core::ICoreWindow* aSender, if (mTouches.Count() == 1) { // If this is the first touchstart of a touch session reset some // tracking flags. - mContentConsumingTouch = false; - mApzConsumingTouch = false; mRecognizerWantsEvents = true; - mCancelable = true; - mCanceledIds.Clear(); - } else { - mCancelable = false; } InitTouchEventTouchList(touchEvent); @@ -1226,18 +1209,26 @@ static void DumpTouchBehavior(nsTArray& aBehavior) #define DUMP_ALLOWED_TOUCH_BEHAVIOR(...) void -MetroInput::HandleFirstTouchStartEvent(WidgetTouchEvent* aEvent) +MetroInput::HandleTouchStartEvent(WidgetTouchEvent* aEvent) { - nsEventStatus contentStatus = nsEventStatus_eIgnore; + // This is the start of a new touch block. See what the APZ wants to do with it. WidgetTouchEvent transformedEvent(*aEvent); DUMP_TOUCH_IDS("APZC(1)", aEvent); nsEventStatus result = mWidget->ApzReceiveInputEvent(&transformedEvent, &mTargetAPZCGuid); if (result == nsEventStatus_eConsumeNoDefault) { + // The APZ said: throw this event away entirely. + CancelGesture(); return; } + MOZ_ASSERT(mApzConsumingTouch); + if (result == nsEventStatus_eIgnore) { + // This didn't hit an APZC, so we can just skip the APZ processing for this block. + mApzConsumingTouch = false; + } - if (gTouchActionPropertyEnabled) { + // If the APZ is using this block, send it touch-action behavior. + if (gTouchActionPropertyEnabled && mApzConsumingTouch) { nsTArray touchBehaviors; // Retrieving touch behaviors from apzctm and from the content (if needed) // then setting it back to the apzc. The apzc we retrieved touch behaviors @@ -1250,49 +1241,41 @@ MetroInput::HandleFirstTouchStartEvent(WidgetTouchEvent* aEvent) // that were touched but touch behaviors would be taken from childs. DUMP_ALLOWED_TOUCH_BEHAVIOR(touchBehaviors); mWidget->ApzcSetAllowedTouchBehavior(mTargetAPZCGuid, touchBehaviors); - if (IsTouchBehaviorForbidden(touchBehaviors)) { - mContentConsumingTouch = true; - } } + // Pass the event on to content DUMP_TOUCH_IDS("DOM(2)", aEvent); + nsEventStatus contentStatus = nsEventStatus_eIgnore; mWidget->DispatchEvent(&transformedEvent, contentStatus); if (nsEventStatus_eConsumeNoDefault == contentStatus) { - mContentConsumingTouch = true; - } - - if (mContentConsumingTouch) { + // Content consumed the event, so we need to notify the APZ + // to not do anything with this touch block. + if (mApzConsumingTouch) { + mWidget->ApzContentConsumingTouch(mTargetAPZCGuid); + } mCancelable = false; - mWidget->ApzContentConsumingTouch(mTargetAPZCGuid); - DispatchTouchCancel(aEvent); - } - // Disable gesture based events (taps, swipes, rotation) if - // preventDefault is called on touchstart. - mRecognizerWantsEvents = !(nsEventStatus_eConsumeNoDefault == contentStatus); - - // If content is consuming touch don't generate any gesture based - // input - clear the recognizer state without sending any events. - if (!ShouldDeliverInputToRecognizer()) { - mGestureRecognizer->CompleteGesture(); + // Also cancel the gesture detection. + CancelGesture(); } } void MetroInput::HandleFirstTouchMoveEvent(WidgetTouchEvent* aEvent) { - mCancelable = false; - - nsEventStatus contentStatus = nsEventStatus_eIgnore; - nsEventStatus apzcStatus = nsEventStatus_eIgnore; - + // If the APZ is using this block, pass the event to it. WidgetTouchEvent transformedEvent(*aEvent); DUMP_TOUCH_IDS("APZC(2)", aEvent); - apzcStatus = mWidget->ApzReceiveInputEvent(&transformedEvent, &mTargetAPZCGuid); - if (apzcStatus == nsEventStatus_eConsumeNoDefault) { - return; + if (mApzConsumingTouch) { + nsEventStatus apzcStatus = mWidget->ApzReceiveInputEvent(&transformedEvent, &mTargetAPZCGuid); + if (apzcStatus == nsEventStatus_eConsumeNoDefault) { + // The APZ said: throw this event away entirely. + CancelGesture(); + return; + } } + // ==== RANDOM INTERLUDE ABOut POINTER EVENTS ==== // We need to dispatch here only touch event, not pointer one. // That's because according to the spec pointer events doesn't imply pointermove event // between pointerdown and pointercancel (If default touch behavior is triggered). @@ -1305,31 +1288,47 @@ MetroInput::HandleFirstTouchMoveEvent(WidgetTouchEvent* aEvent) // both touch and pointer event or only touch one. // Anyway it's worth to add this stuff only after patches from bug 822898 (Pointer events) are // fully commited. + // ==== END RANDOM INTERLUDE ABOut POINTER EVENTS ==== + + // And pass the untransformed event to content. DUMP_TOUCH_IDS("DOM(3)", aEvent); + nsEventStatus contentStatus = nsEventStatus_eIgnore; mWidget->DispatchEvent(&transformedEvent, contentStatus); - // Checking content result first since content can override apzc wish and disallow apzc touch - // behavior (via preventDefault). + // Let the apz know if content wants to consume touch events. + if (mCancelable) { + if (nsEventStatus_eConsumeNoDefault == contentStatus) { + mWidget->ApzContentConsumingTouch(mTargetAPZCGuid); + } else { + mWidget->ApzContentIgnoringTouch(mTargetAPZCGuid); + } + mCancelable = false; + } + // Cancel the gesture detection as well if content is taking this block. if (nsEventStatus_eConsumeNoDefault == contentStatus) { - // Touchmove handler consumed touch. - mContentConsumingTouch = true; - } else if (nsEventStatus_eConsumeDoDefault == apzcStatus) { - // Apzc triggered default behavior. - mApzConsumingTouch = true; + CancelGesture(); } +} - // Let the apz know if content wants to consume touch events, or cancel - // the touch block for content. - if (mContentConsumingTouch) { - mWidget->ApzContentConsumingTouch(mTargetAPZCGuid); - DispatchTouchCancel(aEvent); - } else { - mWidget->ApzContentIgnoringTouch(mTargetAPZCGuid); +void +MetroInput::SendPendingResponseToApz() +{ + // If this is called, content has missed its chance to consume this event block + // so we should notify the APZ that content is ignoring this touch block. + if (mCancelable) { + if (mApzConsumingTouch) { + mWidget->ApzContentIgnoringTouch(mTargetAPZCGuid); + } + mCancelable = false; } +} - if (mApzConsumingTouch) { - // Dispatching cancel to the content. - DispatchTouchCancel(&transformedEvent); +void +MetroInput::CancelGesture() +{ + if (mRecognizerWantsEvents) { + mRecognizerWantsEvents = false; + mGestureRecognizer->CompleteGesture(); } } @@ -1337,29 +1336,10 @@ void MetroInput::DeliverNextQueuedTouchEvent() { /* - * We go through states here and make different decisions in each: - * - * 1) Hit test for apz on first touchstart - * If non-apzc content/chrome is the target simplify event delivery from - * that point on by directing all input to chrome, bypassing the apz. - * 2) Process first touchstart and touchmove events - * If touch behavior value associated with the TouchStart's touches doesn't - * allow zooming or panning we explicitly set mContentConsumingTouch to true. - * Otherwise check the result and set mContentConsumingTouch appropriately. - * Deliver touch events to the apz (ignoring return result) and to content. - * 3) If mContentConsumingTouch is true: deliver touch to content after - * transforming through the apz. Also let the apz know content is - * consuming touch and deliver cancel event to apz. - * 4) If mContentConsumingTouch is false: check the result from the apz and - * set mApzConsumingTouch appropriately. - * 5) If mApzConsumingTouch is true: send a touchcancel to content - * and deliver all events to the apz. If the apz is doing something with - * the events we can save ourselves the overhead of delivering dom events. - * - * Notes: - * - never rely on the contents of mTouches here, since this is a delayed - * callback. mTouches will likely have been modified. + * Note: never rely on the contents of mTouches here, since this is a delayed + * callback. mTouches will likely have been modified. */ + nsEventStatus status = nsEventStatus_eIgnore; WidgetTouchEvent* event = @@ -1368,10 +1348,21 @@ MetroInput::DeliverNextQueuedTouchEvent() AutoDeleteEvent wrap(event); + // This is the start of a new touch block. If we haven't + // responded to the APZ about the last touch block, do that + // now, and reset variables for the new touch block. + if (event->message == NS_TOUCH_START) { + SendPendingResponseToApz(); + + mCancelable = true; + mApzConsumingTouch = true; + mTargetAPZCGuid = ScrollableLayerGuid(); + } + // Test for non-apz vs. apz target. To do this we only use the first touch // point since that will be the input batch target. Cache this for touch events // since HitTestChrome has to send a dom event. - if (mCancelable && event->message == NS_TOUCH_START) { + if (event->message == NS_TOUCH_START && event->touches.Length() == 1) { nsRefPtr touch = event->touches[0]; LayoutDeviceIntPoint pt = LayoutDeviceIntPoint::FromUntyped(touch->mRefPoint); // This is currently a general contained rect hit test, it may produce a false @@ -1382,7 +1373,8 @@ MetroInput::DeliverNextQueuedTouchEvent() } // If this event is destined for dom, deliver it directly there bypassing - // the apz. + // the apz. Continue doing this until the number of active touch points drops + // to zero. After that we recompute mNonApzTargetForTouch in the block above. if (mNonApzTargetForTouch) { DUMP_TOUCH_IDS("DOM(1)", event); mWidget->DispatchEvent(event, status); @@ -1390,8 +1382,7 @@ MetroInput::DeliverNextQueuedTouchEvent() // Disable gesture based events (taps, swipes, rotation) if // preventDefault is called on touchstart. if (nsEventStatus_eConsumeNoDefault == status) { - mRecognizerWantsEvents = false; - mGestureRecognizer->CompleteGesture(); + CancelGesture(); } if (event->message == NS_TOUCH_MOVE) { mCancelable = false; @@ -1400,88 +1391,30 @@ MetroInput::DeliverNextQueuedTouchEvent() return; } - if (mCancelable && event->message == NS_TOUCH_START) { - HandleFirstTouchStartEvent(event); + // Special handling for the start and first move events + if (event->message == NS_TOUCH_START) { + HandleTouchStartEvent(event); return; } else if (mCancelable && event->message == NS_TOUCH_MOVE) { HandleFirstTouchMoveEvent(event); return; } - // Let TouchEnd events go through even if mCancelable is true since we - // don't need to check whether it is prevented by content or consumed - // by apzc. - // If content is consuming touch, we may need to transform event coords - // through the apzc before sending to the dom. Otherwise send the event - // to apzc. - if (mContentConsumingTouch) { - // Only translate if we're dealing with web content that's transformed - // by the apzc. - TransformTouchEvent(event); - DUMP_TOUCH_IDS("DOM(4)", event); - mWidget->DispatchEvent(event, status); - return; - } + // If we get here, content has already had its chance to consume this event + // block. If it didn't do so we can inform the APZ that content is ignoring + // this event block. + SendPendingResponseToApz(); + // Normal processing of events. Send it to the APZ first for handling and + // untransformation. then pass the untransformed event to content. DUMP_TOUCH_IDS("APZC(3)", event); status = mWidget->ApzReceiveInputEvent(event, nullptr); if (status == nsEventStatus_eConsumeNoDefault) { + CancelGesture(); return; } - - // If we're getting a new touch (touch start) after some touch start/move - // events we need to reset touch behavior for touches. - if (gTouchActionPropertyEnabled && event->message == NS_TOUCH_START) { - nsTArray touchBehaviors; - GetAllowedTouchBehavior(event, touchBehaviors); - DUMP_ALLOWED_TOUCH_BEHAVIOR(touchBehaviors); - mWidget->ApzcSetAllowedTouchBehavior(mTargetAPZCGuid, touchBehaviors); - } - - // Send the event to content unless APZC is consuming it. - if (!mApzConsumingTouch) { - if (status == nsEventStatus_eConsumeDoDefault) { - mApzConsumingTouch = true; - DispatchTouchCancel(event); - return; - } - TransformTouchEvent(event); - DUMP_TOUCH_IDS("DOM(5)", event); - mWidget->DispatchEvent(event, status); - } -} - -void -MetroInput::DispatchTouchCancel(WidgetTouchEvent* aEvent) -{ - MOZ_ASSERT(aEvent); - // Send a touchcancel for each pointer id we have a corresponding start - // for. Note we can't rely on mTouches here since touchends remove points - // from it. - WidgetTouchEvent touchEvent(true, NS_TOUCH_CANCEL, mWidget.Get()); - WidgetTouchEvent::TouchArray& touches = aEvent->touches; - for (uint32_t i = 0; i < touches.Length(); ++i) { - dom::Touch* touch = touches[i]; - if (!touch) { - continue; - } - int32_t id = touch->Identifier(); - if (mCanceledIds.Contains(id)) { - continue; - } - mCanceledIds.AppendElement(id); - touchEvent.touches.AppendElement(touch); - } - if (!touchEvent.touches.Length()) { - return; - } - if (mContentConsumingTouch) { - DUMP_TOUCH_IDS("APZC(4)", &touchEvent); - mWidget->ApzReceiveInputEvent(&touchEvent, nullptr); - } else { - DUMP_TOUCH_IDS("DOM(6)", &touchEvent); - mWidget->DispatchEvent(&touchEvent, sThrowawayStatus); - } + DUMP_TOUCH_IDS("DOM(4)", event); + mWidget->DispatchEvent(event, status); } void diff --git a/widget/windows/winrt/MetroInput.h b/widget/windows/winrt/MetroInput.h index 8705059ef626..aab862c4f744 100644 --- a/widget/windows/winrt/MetroInput.h +++ b/widget/windows/winrt/MetroInput.h @@ -192,39 +192,26 @@ private: // Note: event argument should be transformed via apzc before supplying to this method. void GetAllowedTouchBehavior(WidgetTouchEvent* aTransformedEvent, nsTArray& aOutBehaviors); - // Checks whether any touch behavior is allowed. - bool IsTouchBehaviorForbidden(const nsTArray& aTouchBehaviors); - - // The W3C spec states that "whether preventDefault has been called" should - // be tracked on a per-touchpoint basis, but it also states that touchstart - // and touchmove events can contain multiple changed points. At the time of - // this writing, W3C touch events are in the process of being abandoned in - // favor of W3C pointer events, so it is unlikely that this ambiguity will - // be resolved. Additionally, nsPresShell tracks "whether preventDefault - // has been called" on a per-touch-session basis. We will follow a similar - // approach here. - // - // Specifically: - // If preventDefault is called on the FIRST touchstart event of a touch - // session, then no default actions associated with any touchstart, - // touchmove, or touchend events will be taken. This means that no - // mousedowns, mousemoves, mouseups, clicks, swipes, rotations, - // magnifications, etc will be dispatched during this touch session; - // only touchstart, touchmove, and touchend. - // - // If preventDefault is called on the FIRST touchmove event of a touch - // session, then no default actions associated with the _touchmove_ events - // will be dispatched. However, it is still possible that additional - // events will be generated based on the touchstart and touchend events. - // For example, a set of mousemove, mousedown, and mouseup events might - // be sent if a tap is detected. - bool mContentConsumingTouch; + // First, read the comment in gfx/layers/apz/src/TouchBlockState.h. + // The following booleans track the following pieces of state: + // mApzConsumingTouch - if events from the current touch block should be + // sent to the APZ. This is true unless the touchstart hit no APZ + // instances, in which case we don't need to send the rest of the touch + // block to the APZ code. + // mCancelable - if we have not yet notified the APZ code about the prevent- + // default status of the current touch block. This is flipped from true + // to false when this notification happens (or would have happened, in + // the case that mApzConsumingTouch is false). + // mRecognizerWantsEvents - If the gesture recognizer should be receiving + // events. This is normally true, but will be set to false if the APZ + // decides the touch block should be thrown away entirely, or if content + // consumes the touch block. + // XXX There is a hazard with mRecognizerWantsEvents because it is accessed + // both in the sync and async portions of the code. bool mApzConsumingTouch; bool mCancelable; bool mRecognizerWantsEvents; - nsTArray mCanceledIds; - // In the old Win32 way of doing things, we would receive a WM_TOUCH event // that told us the state of every touchpoint on the touch surface. If // multiple touchpoints had moved since the last update we would learn @@ -289,12 +276,13 @@ private: void DeliverNextQueuedEventIgnoreStatus(); void DeliverNextQueuedTouchEvent(); - void HandleFirstTouchStartEvent(WidgetTouchEvent* aEvent); + void HandleTouchStartEvent(WidgetTouchEvent* aEvent); void HandleFirstTouchMoveEvent(WidgetTouchEvent* aEvent); + void SendPendingResponseToApz(); + void CancelGesture(); // Sync event dispatching void DispatchEventIgnoreStatus(WidgetGUIEvent* aEvent); - void DispatchTouchCancel(WidgetTouchEvent* aEvent); nsDeque mInputEventQueue; mozilla::layers::ScrollableLayerGuid mTargetAPZCGuid;