diff --git a/dom/events/Touch.cpp b/dom/events/Touch.cpp index b447aefc6aad..e0e5f2140690 100644 --- a/dom/events/Touch.cpp +++ b/dom/events/Touch.cpp @@ -50,6 +50,7 @@ Touch::Touch(EventTarget* aTarget, int32_t aRadiusY, float aRotationAngle, float aForce) + : mIsTouchEventSuppressed(false) { mTarget = aTarget; mIdentifier = aIdentifier; @@ -73,6 +74,7 @@ Touch::Touch(int32_t aIdentifier, LayoutDeviceIntPoint aRadius, float aRotationAngle, float aForce) + : mIsTouchEventSuppressed(false) { mIdentifier = aIdentifier; mPagePoint = CSSIntPoint(0, 0); @@ -93,6 +95,7 @@ Touch::Touch(const Touch& aOther) : mTarget(aOther.mTarget) , mRefPoint(aOther.mRefPoint) , mChanged(aOther.mChanged) + , mIsTouchEventSuppressed(aOther.mIsTouchEventSuppressed) , mMessage(aOther.mMessage) , mIdentifier(aOther.mIdentifier) , mPagePoint(aOther.mPagePoint) diff --git a/dom/events/Touch.h b/dom/events/Touch.h index 1af76a74d58c..f5fa9acbd045 100644 --- a/dom/events/Touch.h +++ b/dom/events/Touch.h @@ -82,6 +82,12 @@ public: nsCOMPtr mTarget; LayoutDeviceIntPoint mRefPoint; bool mChanged; + + // Is this touch instance being suppressed to dispatch touch event to content. + // We can't remove touch instance from WidgetTouchEvent::mTouches because we + // still need it when dispatching pointer events. + bool mIsTouchEventSuppressed; + uint32_t mMessage; int32_t mIdentifier; CSSIntPoint mPagePoint; diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp index e000f74298b5..9412a6abc567 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp @@ -7184,6 +7184,11 @@ PresShell::HandleEvent(nsIFrame* aFrame, if (!captureRetarget && !isWindowLevelMouseExit) { if (aEvent->mClass == eTouchEventClass) { frame = TouchManager::SetupTarget(aEvent->AsTouchEvent(), frame); + if (nsIFrame* newFrame = + TouchManager::SuppressInvalidPointsAndGetTargetedFrame( + aEvent->AsTouchEvent())) { + frame = newFrame; + } } else { uint32_t flags = 0; nsPoint eventPoint = @@ -7944,6 +7949,10 @@ PresShell::DispatchTouchEventToDOM(WidgetEvent* aEvent, // loop over all touches and dispatch events on any that have changed for (dom::Touch* touch : touchEvent->mTouches) { + // We should remove all suppressed touch instances in + // TouchManager::PreHandleEvent. + MOZ_ASSERT(!touch->mIsTouchEventSuppressed); + if (!touch || !touch->mChanged) { continue; } diff --git a/layout/base/TouchManager.cpp b/layout/base/TouchManager.cpp index d9563bc5b63e..009051b75038 100644 --- a/layout/base/TouchManager.cpp +++ b/layout/base/TouchManager.cpp @@ -129,6 +129,52 @@ TouchManager::SetupTarget(WidgetTouchEvent* aEvent, nsIFrame* aFrame) if (gfxPrefs::APZAllowZooming()) { flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME; } + + nsIFrame* target = aFrame; + for (int32_t i = aEvent->mTouches.Length(); i; ) { + --i; + dom::Touch* touch = aEvent->mTouches[i]; + + int32_t id = touch->Identifier(); + if (!TouchManager::HasCapturedTouch(id)) { + // find the target for this touch + nsPoint eventPoint = + nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, touch->mRefPoint, + aFrame); + target = FindFrameTargetedByInputEvent(aEvent, aFrame, eventPoint, flags); + if (target) { + nsCOMPtr targetContent; + target->GetContentForEvent(aEvent, getter_AddRefs(targetContent)); + while (targetContent && !targetContent->IsElement()) { + targetContent = targetContent->GetParent(); + } + touch->SetTarget(targetContent); + } else { + aEvent->mTouches.RemoveElementAt(i); + } + } else { + // This touch is an old touch, we need to ensure that is not + // marked as changed and set its target correctly + touch->mChanged = false; + RefPtr oldTouch = TouchManager::GetCapturedTouch(id); + if (oldTouch) { + touch->SetTarget(oldTouch->mTarget); + } + } + } + return target; +} + +/* static */ nsIFrame* +TouchManager::SuppressInvalidPointsAndGetTargetedFrame(WidgetTouchEvent* aEvent) +{ + MOZ_ASSERT(aEvent); + + if (!aEvent || aEvent->mMessage != eTouchStart) { + // All touch events except for touchstart use a captured target. + return nullptr; + } + // if this is a continuing session, ensure that all these events are // in the same document by taking the target of the events already in // the capture list @@ -137,73 +183,52 @@ TouchManager::SetupTarget(WidgetTouchEvent* aEvent, nsIFrame* aFrame) anyTarget = TouchManager::GetAnyCapturedTouchTarget(); } - nsPoint eventPoint; - nsIFrame* frame = aFrame; + nsIFrame* frame = nullptr; for (int32_t i = aEvent->mTouches.Length(); i; ) { --i; dom::Touch* touch = aEvent->mTouches[i]; + if (TouchManager::HasCapturedTouch(touch->Identifier())) { + continue; + } - int32_t id = touch->Identifier(); - if (!TouchManager::HasCapturedTouch(id)) { - // find the target for this touch - eventPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, - touch->mRefPoint, - aFrame); - nsIFrame* target = FindFrameTargetedByInputEvent(aEvent, - aFrame, - eventPoint, - flags); - if (target && !anyTarget) { - target->GetContentForEvent(aEvent, getter_AddRefs(anyTarget)); - while (anyTarget && !anyTarget->IsElement()) { - anyTarget = anyTarget->GetParent(); - } - touch->SetTarget(anyTarget); - } else { - nsIFrame* newTargetFrame = nullptr; - for (nsIFrame* f = target; f; - f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) { - if (f->PresContext()->Document() == anyTarget->OwnerDoc()) { - newTargetFrame = f; - break; - } - // We must be in a subdocument so jump directly to the root frame. - // GetParentOrPlaceholderForCrossDoc gets called immediately to - // jump up to the containing document. - f = f->PresShell()->GetRootFrame(); - } - - // if we couldn't find a target frame in the same document as - // anyTarget, remove the touch from the capture touch list, as - // well as the event->mTouches array. touchmove events that aren't - // in the captured touch list will be discarded - if (!newTargetFrame) { - aEvent->mTouches.RemoveElementAt(i); - } else { - target = newTargetFrame; - nsCOMPtr targetContent; - target->GetContentForEvent(aEvent, getter_AddRefs(targetContent)); - while (targetContent && !targetContent->IsElement()) { - targetContent = targetContent->GetParent(); - } - touch->SetTarget(targetContent); - } - } - if (target) { - frame = target; - } + MOZ_ASSERT(touch->mTarget); + nsCOMPtr targetContent = do_QueryInterface(touch->GetTarget()); + nsIFrame* targetFrame = targetContent->GetPrimaryFrame(); + if (targetFrame && !anyTarget) { + anyTarget = targetContent; } else { - // This touch is an old touch, we need to ensure that is not - // marked as changed and set its target correctly - touch->mChanged = false; - - RefPtr oldTouch = TouchManager::GetCapturedTouch(id); - if (oldTouch) { - touch->SetTarget(oldTouch->mTarget); + nsIFrame* newTargetFrame = nullptr; + for (nsIFrame* f = targetFrame; f; + f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) { + if (f->PresContext()->Document() == anyTarget->OwnerDoc()) { + newTargetFrame = f; + break; + } + // We must be in a subdocument so jump directly to the root frame. + // GetParentOrPlaceholderForCrossDoc gets called immediately to + // jump up to the containing document. + f = f->PresShell()->GetRootFrame(); } + // if we couldn't find a target frame in the same document as + // anyTarget, remove the touch from the capture touch list, as + // well as the event->mTouches array. touchmove events that aren't + // in the captured touch list will be discarded + if (!newTargetFrame) { + touch->mIsTouchEventSuppressed = true; + } else { + targetFrame = newTargetFrame; + } + targetFrame->GetContentForEvent(aEvent, getter_AddRefs(targetContent)); + while (targetContent && !targetContent->IsElement()) { + targetContent = targetContent->GetParent(); + } + touch->SetTarget(targetContent); + } + if (targetFrame) { + frame = targetFrame; } } - return FindFrameTargetedByInputEvent(aEvent, frame, eventPoint, flags); + return frame; } bool @@ -228,8 +253,10 @@ TouchManager::PreHandleEvent(WidgetEvent* aEvent, } } // Add any new touches to the queue - for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) { - Touch* touch = touchEvent->mTouches[i]; + WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches; + for (int32_t i = touches.Length(); i; ) { + --i; + Touch* touch = touches[i]; int32_t id = touch->Identifier(); if (!sCaptureTouchList->Get(id, nullptr)) { // If it is not already in the queue, it is a new touch @@ -239,6 +266,12 @@ TouchManager::PreHandleEvent(WidgetEvent* aEvent, TouchInfo info = { touch, GetNonAnonymousAncestor(touch->mTarget), true }; sCaptureTouchList->Put(id, info); + if (touch->mIsTouchEventSuppressed) { + // We're going to dispatch touch event. Remove this touch instance if + // it is suppressed. + touches.RemoveElementAt(i); + continue; + } } break; } @@ -286,6 +319,11 @@ TouchManager::PreHandleEvent(WidgetEvent* aEvent, if (oldTouch->mMessage != touch->mMessage) { aTouchIsNew = true; } + if (oldTouch->mIsTouchEventSuppressed) { + touch->mIsTouchEventSuppressed = true; + touches.RemoveElementAt(i); + continue; + } } // is nothing has changed, we should just return if (!haveChanged) { @@ -317,7 +355,8 @@ TouchManager::PreHandleEvent(WidgetEvent* aEvent, // need to make sure we only remove touches that are ending here WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches; - for (uint32_t i = 0; i < touches.Length(); ++i) { + for (int32_t i = touches.Length(); i; ) { + --i; Touch* touch = touches[i]; if (!touch) { continue; @@ -339,6 +378,10 @@ TouchManager::PreHandleEvent(WidgetEvent* aEvent, aCurrentEventContent = do_QueryInterface(targetPtr); touch->SetTarget(targetPtr); sCaptureTouchList->Remove(id); + if (info.mTouch->mIsTouchEventSuppressed) { + touches.RemoveElementAt(i); + continue; + } } // add any touches left in the touch list, but ensure changed=false AppendToTouchList(&touches); diff --git a/layout/base/TouchManager.h b/layout/base/TouchManager.h index e52b2bf9b2c6..0f3b7f59f912 100644 --- a/layout/base/TouchManager.h +++ b/layout/base/TouchManager.h @@ -35,6 +35,21 @@ public: // events are dispatched to the same target as touchstart. static nsIFrame* SetupTarget(WidgetTouchEvent* aEvent, nsIFrame* aFrame); + /** + * This function checks whether all touch points hit elements in the same + * document. If not, we try to find its cross document parent which is in the + * same document of the existing target as the event target. We mark the + * touch point as suppressed if can't find it. The suppressed touch points are + * removed in TouchManager::PreHandleEvent so that we don't dispatch them to + * content. + * + * @param aEvent A touch event to be checked. + * + * @return The targeted frame of aEvent. + */ + static nsIFrame* SuppressInvalidPointsAndGetTargetedFrame( + WidgetTouchEvent* aEvent); + bool PreHandleEvent(mozilla::WidgetEvent* aEvent, nsEventStatus* aStatus, bool& aTouchIsNew,