Bug 1043608 - Rewrite touch event codepaths for Metro to deal with changes in the APZ touch block code (made in bug 1009733). r=jimm

This commit is contained in:
Kartikaya Gupta 2014-07-29 14:17:32 -04:00
parent b6f18d66d6
commit f735817b21
3 changed files with 112 additions and 190 deletions

View File

@ -230,6 +230,7 @@ APZController::HandleLongTap(const CSSPoint& aPoint,
int32_t aModifiers,
const ScrollableLayerGuid& aGuid)
{
ContentReceivedTouch(aGuid, false);
}
void

View File

@ -521,17 +521,6 @@ MetroInput::GetAllowedTouchBehavior(WidgetTouchEvent* aTransformedEvent, nsTArra
}
}
bool
MetroInput::IsTouchBehaviorForbidden(const nsTArray<TouchBehaviorFlags>& 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<uint32_t>& 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<TouchBehaviorFlags> 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> 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<TouchBehaviorFlags> 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

View File

@ -192,39 +192,26 @@ private:
// Note: event argument should be transformed via apzc before supplying to this method.
void GetAllowedTouchBehavior(WidgetTouchEvent* aTransformedEvent, nsTArray<TouchBehaviorFlags>& aOutBehaviors);
// Checks whether any touch behavior is allowed.
bool IsTouchBehaviorForbidden(const nsTArray<TouchBehaviorFlags>& 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<uint32_t> 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;