mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-02 09:18:36 +02:00
Bug 1891304 - Make APZEventState manage whether the pointerdown was consumed by content or not r=smaug,hiro
The Pointer Events spec defines that: > Authors can prevent the firing of certain compatibility mouse events by > canceling the pointerdown event (if the isPrimary property is true). > <snip> > Note, however, that this does not prevent the mouseover, mouseenter, mouseout, > or mouseleave events from firing. https://w3c.github.io/pointerevents/#the-pointerdown-event The other browsers conform to this. Therefore, we should stop dispatching compatibility mouse events only if the preceding `pointerdown` is consumed by content. I.e., we need to keep dispatching touch events and `click` etc which indicate what should happen on the element. Currently, `APZEventState` does not manage whether the preceding `pointerdown` is canceled or not. So, it dispatches compatibility mouse events via `APZCCallbackHelper` after the consumed pointer is removed. Therefore, we need to make it manage whether the preceding `pointerdown` of the first touch is consumed or not and `APZCCallbackHelper` needs an option to dispatch the compatibility mouse events only to chrome (they are required to dispatch `click` etc). However, if `APZEventState` is not available like test API used in the remote process, `TouchManager` needs to manage it instead of `APZEventState`. I don't think only `TouchManager` should manage it because `APZEventState` manages complicated state of touch gestures and that can know whether the synthesizing compatibility mouse events related to the consumed `pointerdown` or not strictly. Therefore, this patch makes the `TouchManager` state used only in the path handling synthesized events for tests. Differential Revision: https://phabricator.services.mozilla.com/D208706
This commit is contained in:
parent
dfafc3b528
commit
888cde525c
8 changed files with 114 additions and 28 deletions
|
|
@ -147,6 +147,30 @@ SimpleTest.waitForFocus(async () => {
|
|||
);
|
||||
})();
|
||||
|
||||
await (async function test_single_tap_with_consuming_pointerdown() {
|
||||
await promiseFlushingAPZGestureState();
|
||||
info("test_single_tap_with_consuming_pointerdown: testing...");
|
||||
events = [];
|
||||
const waitForTouchEnd = promiseEvent("click");
|
||||
child.addEventListener("pointerdown", event => {
|
||||
event.preventDefault();
|
||||
}, {once: true});
|
||||
synthesizeTouch(child, 5, 5);
|
||||
await waitForTouchEnd;
|
||||
const result = stringifyEvents(events);
|
||||
const expected = stringifyEvents([
|
||||
{ type: "touchend", target: child },
|
||||
{ type: "click", target: child, detail: 1, button: 0, buttons: 0 },
|
||||
]);
|
||||
// If testing on Windows, the result is really unstable. Let's allow to
|
||||
// fail for now.
|
||||
(navigator.platform.includes("Win") && result != expected ? todo_is : is)(
|
||||
result,
|
||||
expected,
|
||||
`Single tap should not cause mouse events if pointerdown is consumed, but click event should be fired ${desc}`
|
||||
);
|
||||
})();
|
||||
|
||||
await (async function test_single_tap_with_consuming_touchstart() {
|
||||
await promiseFlushingAPZGestureState();
|
||||
info("test_single_tap_with_consuming_touchstart: testing...");
|
||||
|
|
@ -223,6 +247,8 @@ SimpleTest.waitForFocus(async () => {
|
|||
`Multiple touch should not cause mouse events ${desc}`
|
||||
);
|
||||
})();
|
||||
|
||||
// FIXME: Add long tap tests which won't frequently fail.
|
||||
}
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
#include "APZCCallbackHelper.h"
|
||||
|
||||
#include "APZEventState.h" // for PrecedingPointerDown
|
||||
|
||||
#include "gfxPlatform.h" // For gfxPlatform::UseTiling
|
||||
|
||||
#include "mozilla/AsyncEventDispatcher.h"
|
||||
|
|
@ -510,7 +512,8 @@ nsEventStatus APZCCallbackHelper::DispatchWidgetEvent(WidgetGUIEvent& aEvent) {
|
|||
|
||||
nsEventStatus APZCCallbackHelper::DispatchSynthesizedMouseEvent(
|
||||
EventMessage aMsg, const LayoutDevicePoint& aRefPoint, Modifiers aModifiers,
|
||||
int32_t aClickCount, nsIWidget* aWidget) {
|
||||
int32_t aClickCount, PrecedingPointerDown aPrecedingPointerDownState,
|
||||
nsIWidget* aWidget) {
|
||||
MOZ_ASSERT(aMsg == eMouseMove || aMsg == eMouseDown || aMsg == eMouseUp ||
|
||||
aMsg == eMouseLongTap);
|
||||
|
||||
|
|
@ -524,6 +527,15 @@ nsEventStatus APZCCallbackHelper::DispatchSynthesizedMouseEvent(
|
|||
if (aMsg == eMouseLongTap) {
|
||||
event.mFlags.mOnlyChromeDispatch = true;
|
||||
}
|
||||
// If the preceding `pointerdown` was canceled by content, we should not
|
||||
// dispatch the compatibility mouse events into the content, but they are
|
||||
// required to dispatch `click`, `dblclick` and `auxclick` events by
|
||||
// EventStateManager. Therefore, we need to dispatch them only to chrome.
|
||||
else if (aPrecedingPointerDownState ==
|
||||
PrecedingPointerDown::ConsumedByContent) {
|
||||
event.PreventDefault(false);
|
||||
event.mFlags.mOnlyChromeDispatch = true;
|
||||
}
|
||||
if (aMsg != eMouseMove) {
|
||||
event.mClickCount = aClickCount;
|
||||
}
|
||||
|
|
@ -551,21 +563,20 @@ PreventDefaultResult APZCCallbackHelper::DispatchMouseEvent(
|
|||
return preventDefaultResult;
|
||||
}
|
||||
|
||||
void APZCCallbackHelper::FireSingleTapEvent(const LayoutDevicePoint& aPoint,
|
||||
Modifiers aModifiers,
|
||||
int32_t aClickCount,
|
||||
nsIWidget* aWidget) {
|
||||
void APZCCallbackHelper::FireSingleTapEvent(
|
||||
const LayoutDevicePoint& aPoint, Modifiers aModifiers, int32_t aClickCount,
|
||||
PrecedingPointerDown aPrecedingPointerDownState, nsIWidget* aWidget) {
|
||||
if (aWidget->Destroyed()) {
|
||||
return;
|
||||
}
|
||||
APZCCH_LOG("Dispatching single-tap component events to %s\n",
|
||||
ToString(aPoint).c_str());
|
||||
DispatchSynthesizedMouseEvent(eMouseMove, aPoint, aModifiers, aClickCount,
|
||||
aWidget);
|
||||
aPrecedingPointerDownState, aWidget);
|
||||
DispatchSynthesizedMouseEvent(eMouseDown, aPoint, aModifiers, aClickCount,
|
||||
aWidget);
|
||||
aPrecedingPointerDownState, aWidget);
|
||||
DispatchSynthesizedMouseEvent(eMouseUp, aPoint, aModifiers, aClickCount,
|
||||
aWidget);
|
||||
aPrecedingPointerDownState, aWidget);
|
||||
}
|
||||
|
||||
static dom::Element* GetDisplayportElementFor(
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ namespace layers {
|
|||
|
||||
struct RepaintRequest;
|
||||
|
||||
namespace apz {
|
||||
enum class PrecedingPointerDown : bool;
|
||||
}
|
||||
|
||||
/* Refer to documentation on SendSetTargetAPZCNotification for this class */
|
||||
class DisplayportSetListener : public ManagedPostRefreshObserver {
|
||||
public:
|
||||
|
|
@ -61,6 +65,8 @@ class APZCCallbackHelper {
|
|||
typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid;
|
||||
|
||||
public:
|
||||
using PrecedingPointerDown = apz::PrecedingPointerDown;
|
||||
|
||||
static void NotifyLayerTransforms(const nsTArray<MatrixMessage>& aTransforms);
|
||||
|
||||
/* Applies the scroll and zoom parameters from the given RepaintRequest object
|
||||
|
|
@ -107,7 +113,8 @@ class APZCCallbackHelper {
|
|||
MOZ_CAN_RUN_SCRIPT
|
||||
static nsEventStatus DispatchSynthesizedMouseEvent(
|
||||
EventMessage aMsg, const LayoutDevicePoint& aRefPoint,
|
||||
Modifiers aModifiers, int32_t aClickCount, nsIWidget* aWidget);
|
||||
Modifiers aModifiers, int32_t aClickCount,
|
||||
PrecedingPointerDown aPrecedingPointerDownState, nsIWidget* aWidget);
|
||||
|
||||
/* Dispatch a mouse event with the given parameters.
|
||||
* Return whether or not any listeners have called preventDefault on the
|
||||
|
|
@ -123,9 +130,10 @@ class APZCCallbackHelper {
|
|||
/* Fire a single-tap event at the given point. The event is dispatched
|
||||
* via the given widget. */
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
static void FireSingleTapEvent(const LayoutDevicePoint& aPoint,
|
||||
Modifiers aModifiers, int32_t aClickCount,
|
||||
nsIWidget* aWidget);
|
||||
static void FireSingleTapEvent(
|
||||
const LayoutDevicePoint& aPoint, Modifiers aModifiers,
|
||||
int32_t aClickCount, PrecedingPointerDown aPrecedingPointerDownState,
|
||||
nsIWidget* aWidget);
|
||||
|
||||
/* Perform hit-testing on the touch points of |aEvent| to determine
|
||||
* which scrollable frames they target. If any of these frames don't have
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include "mozilla/ViewportUtils.h"
|
||||
#include "mozilla/dom/BrowserChild.h"
|
||||
#include "mozilla/dom/MouseEventBinding.h"
|
||||
#include "mozilla/dom/PointerEventHandler.h"
|
||||
#include "mozilla/layers/APZCCallbackHelper.h"
|
||||
#include "mozilla/layers/APZUtils.h"
|
||||
#include "mozilla/layers/IAPZCTreeManager.h"
|
||||
|
|
@ -100,13 +101,8 @@ APZEventState::APZEventState(nsIWidget* aWidget,
|
|||
,
|
||||
mActiveElementManager(new ActiveElementManager()),
|
||||
mContentReceivedInputBlockCallback(std::move(aCallback)),
|
||||
mPendingTouchPreventedResponse(false),
|
||||
mPendingTouchPreventedBlockId(0),
|
||||
mEndTouchState(apz::SingleTapState::NotClick),
|
||||
mFirstTouchCancelled(false),
|
||||
mTouchEndCancelled(false),
|
||||
mReceivedNonTouchStart(false),
|
||||
mTouchStartPrevented(false),
|
||||
mLastTouchIdentifier(0) {
|
||||
nsresult rv;
|
||||
mWidget = do_GetWeakReference(aWidget, &rv);
|
||||
|
|
@ -139,8 +135,9 @@ void APZEventState::ProcessSingleTap(const CSSPoint& aPoint,
|
|||
nsCOMPtr<nsIWidget> localWidget = do_QueryReferent(mWidget);
|
||||
if (localWidget) {
|
||||
widget::nsAutoRollup rollup(touchRollup);
|
||||
APZCCallbackHelper::FireSingleTapEvent(aPoint * aScale, aModifiers,
|
||||
aClickCount, localWidget);
|
||||
APZCCallbackHelper::FireSingleTapEvent(
|
||||
aPoint * aScale, aModifiers, aClickCount, mPrecedingPointerDownState,
|
||||
localWidget);
|
||||
}
|
||||
|
||||
mActiveElementManager->ProcessSingleTap();
|
||||
|
|
@ -161,7 +158,8 @@ PreventDefaultResult APZEventState::FireContextmenuEvents(
|
|||
// Note that we don't need to check whether mousemove event is consumed or
|
||||
// not because Chrome also ignores the result.
|
||||
APZCCallbackHelper::DispatchSynthesizedMouseEvent(
|
||||
eMouseMove, aPoint * aScale, aModifiers, 0 /* clickCount */, aWidget);
|
||||
eMouseMove, aPoint * aScale, aModifiers, 0 /* clickCount */,
|
||||
mPrecedingPointerDownState, aWidget);
|
||||
|
||||
// Converting the modifiers to DOM format for the DispatchMouseEvent call
|
||||
// is the most useless thing ever because nsDOMWindowUtils::SendMouseEvent
|
||||
|
|
@ -186,7 +184,7 @@ PreventDefaultResult APZEventState::FireContextmenuEvents(
|
|||
// If the contextmenu wasn't consumed, fire the eMouseLongTap event.
|
||||
nsEventStatus status = APZCCallbackHelper::DispatchSynthesizedMouseEvent(
|
||||
eMouseLongTap, aPoint * aScale, aModifiers,
|
||||
/*clickCount*/ 1, aWidget);
|
||||
/*clickCount*/ 1, mPrecedingPointerDownState, aWidget);
|
||||
APZES_LOG("eMouseLongTap event %s\n", ToString(status).c_str());
|
||||
#endif
|
||||
}
|
||||
|
|
@ -228,7 +226,8 @@ void APZEventState::ProcessLongTap(PresShell* aPresShell,
|
|||
// at this time, because things like text selection or dragging may want
|
||||
// to know about it.
|
||||
APZCCallbackHelper::DispatchSynthesizedMouseEvent(
|
||||
eMouseLongTap, aPoint * aScale, aModifiers, /*clickCount*/ 1, widget);
|
||||
eMouseLongTap, aPoint * aScale, aModifiers, /*clickCount*/ 1,
|
||||
mPrecedingPointerDownState, widget);
|
||||
#else
|
||||
PreventDefaultResult preventDefaultResult =
|
||||
FireContextmenuEvents(aPresShell, aPoint, aScale, aModifiers, widget);
|
||||
|
|
@ -324,6 +323,14 @@ void APZEventState::ProcessTouchEvent(
|
|||
// touchstart was prevented by content.
|
||||
if (mTouchCounter.GetActiveTouchCount() == 0) {
|
||||
mFirstTouchCancelled = isTouchPrevented;
|
||||
const PointerInfo* pointerInfo =
|
||||
!aEvent.mTouches.IsEmpty() ? PointerEventHandler::GetPointerInfo(
|
||||
aEvent.mTouches[0]->Identifier())
|
||||
: nullptr;
|
||||
mPrecedingPointerDownState =
|
||||
pointerInfo && pointerInfo->mPreventMouseEventByContent
|
||||
? PrecedingPointerDown::ConsumedByContent
|
||||
: PrecedingPointerDown::NotConsumed;
|
||||
} else {
|
||||
if (mFirstTouchCancelled && !isTouchPrevented) {
|
||||
APZES_LOG(
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ namespace layers {
|
|||
class ActiveElementManager;
|
||||
|
||||
namespace apz {
|
||||
enum class PrecedingPointerDown : bool { NotConsumed, ConsumedByContent };
|
||||
enum class SingleTapState : uint8_t;
|
||||
} // namespace apz
|
||||
|
||||
|
|
@ -56,6 +57,8 @@ class APZEventState final {
|
|||
typedef ScrollableLayerGuid::ViewID ViewID;
|
||||
|
||||
public:
|
||||
using PrecedingPointerDown = apz::PrecedingPointerDown;
|
||||
|
||||
APZEventState(nsIWidget* aWidget,
|
||||
ContentReceivedInputBlockCallback&& aCallback);
|
||||
|
||||
|
|
@ -107,17 +110,19 @@ class APZEventState final {
|
|||
RefPtr<ActiveElementManager> mActiveElementManager;
|
||||
ContentReceivedInputBlockCallback mContentReceivedInputBlockCallback;
|
||||
TouchCounter mTouchCounter;
|
||||
bool mPendingTouchPreventedResponse;
|
||||
ScrollableLayerGuid mPendingTouchPreventedGuid;
|
||||
uint64_t mPendingTouchPreventedBlockId;
|
||||
apz::SingleTapState mEndTouchState;
|
||||
bool mFirstTouchCancelled;
|
||||
bool mTouchEndCancelled;
|
||||
PrecedingPointerDown mPrecedingPointerDownState =
|
||||
PrecedingPointerDown::NotConsumed;
|
||||
bool mPendingTouchPreventedResponse = false;
|
||||
bool mFirstTouchCancelled = false;
|
||||
bool mTouchEndCancelled = false;
|
||||
// Set to true when we have received any one of
|
||||
// touch-move/touch-end/touch-cancel events in the touch block being
|
||||
// processed.
|
||||
bool mReceivedNonTouchStart;
|
||||
bool mTouchStartPrevented;
|
||||
bool mReceivedNonTouchStart = false;
|
||||
bool mTouchStartPrevented = false;
|
||||
|
||||
int32_t mLastTouchIdentifier;
|
||||
nsTArray<TouchBehaviorFlags> mTouchBlockAllowedBehaviors;
|
||||
|
|
|
|||
|
|
@ -7746,6 +7746,10 @@ void PresShell::EventHandler::MaybeSynthesizeCompatMouseEventsForTouchEnd(
|
|||
event.mClickCount = message == eMouseMove ? 0 : 1;
|
||||
event.mModifiers = aTouchEndEvent->mModifiers;
|
||||
event.convertToPointer = false;
|
||||
if (TouchManager::IsPrecedingTouchPointerDownConsumedByContent()) {
|
||||
event.PreventDefault(false);
|
||||
event.mFlags.mOnlyChromeDispatch = true;
|
||||
}
|
||||
nsEventStatus mouseEventStatus = nsEventStatus_eIgnore;
|
||||
presShell->HandleEvent(frameForPresShell, &event, false, &mouseEventStatus);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/EventTarget.h"
|
||||
#include "mozilla/dom/PointerEventHandler.h"
|
||||
#include "mozilla/layers/InputAPZContext.h"
|
||||
#include "nsIContent.h"
|
||||
#include "nsIFrame.h"
|
||||
|
|
@ -30,6 +31,7 @@ StaticAutoPtr<nsTHashMap<nsUint32HashKey, TouchManager::TouchInfo>>
|
|||
layers::LayersId TouchManager::sCaptureTouchLayersId;
|
||||
TimeStamp TouchManager::sSingleTouchStartTimeStamp;
|
||||
LayoutDeviceIntPoint TouchManager::sSingleTouchStartPoint;
|
||||
bool TouchManager::sPrecedingTouchPointerDownConsumedByContent = false;
|
||||
|
||||
/*static*/
|
||||
void TouchManager::InitializeStatics() {
|
||||
|
|
@ -271,7 +273,11 @@ bool TouchManager::PreHandleEvent(WidgetEvent* aEvent, nsEventStatus* aStatus,
|
|||
// event, all subsequent touch events will use the same layers id.
|
||||
sCaptureTouchLayersId = aEvent->mLayersId;
|
||||
sSingleTouchStartTimeStamp = aEvent->mTimeStamp;
|
||||
sSingleTouchStartPoint = aEvent->AsTouchEvent()->mTouches[0]->mRefPoint;
|
||||
sSingleTouchStartPoint = touchEvent->mTouches[0]->mRefPoint;
|
||||
const PointerInfo* pointerInfo = PointerEventHandler::GetPointerInfo(
|
||||
touchEvent->mTouches[0]->Identifier());
|
||||
sPrecedingTouchPointerDownConsumedByContent =
|
||||
pointerInfo && pointerInfo->mPreventMouseEventByContent;
|
||||
} else {
|
||||
touchEvent->mLayersId = sCaptureTouchLayersId;
|
||||
sSingleTouchStartTimeStamp = TimeStamp();
|
||||
|
|
@ -585,4 +591,9 @@ bool TouchManager::IsSingleTapEndToDoDefault(
|
|||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool TouchManager::IsPrecedingTouchPointerDownConsumedByContent() {
|
||||
return sPrecedingTouchPointerDownConsumedByContent;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
|||
|
|
@ -69,6 +69,10 @@ class TouchManager {
|
|||
// dispatch mouse events for touch events synthesized without APZ.
|
||||
static bool IsSingleTapEndToDoDefault(const WidgetTouchEvent* aTouchEndEvent);
|
||||
|
||||
// Returns true if the preceding `pointerdown` was consumed by content of
|
||||
// the last active pointers of touches.
|
||||
static bool IsPrecedingTouchPointerDownConsumedByContent();
|
||||
|
||||
private:
|
||||
void EvictTouches(dom::Document* aLimitToDocument = nullptr);
|
||||
static void EvictTouchPoint(RefPtr<dom::Touch>& aTouch,
|
||||
|
|
@ -89,10 +93,20 @@ class TouchManager {
|
|||
static layers::LayersId sCaptureTouchLayersId;
|
||||
// The last start of a single tap. This will be set to "Null" if the tap is
|
||||
// consumed or becomes not a single tap.
|
||||
// NOTE: This is used for touches without APZ, i.e., if they are synthesized
|
||||
// in-process for tests.
|
||||
static TimeStamp sSingleTouchStartTimeStamp;
|
||||
// The last start point of the single tap tracked with
|
||||
// sSingleTouchStartTimeStamp.
|
||||
// NOTE: This is used for touches without APZ, i.e., if they are synthesized
|
||||
// in-process for tests.
|
||||
static LayoutDeviceIntPoint sSingleTouchStartPoint;
|
||||
// Whether the preceding `pointerdown` of the last active touches is consumed
|
||||
// by content or not. If APZ is enabled, same state is managed by
|
||||
// APZEventState.
|
||||
// NOTE: This is used for touches without APZ, i.e., if they are synthesized
|
||||
// in-process for tests.
|
||||
static bool sPrecedingTouchPointerDownConsumedByContent;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
|||
Loading…
Reference in a new issue