Bug 1303704 Part1: [Pointer Event] Implement prevent default behavior of pointerdown. r=masayuki
MozReview-Commit-ID: AbO5cBjIwfJ
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -1284,21 +1284,23 @@ public:
};
class PointerInfo final
{
public:
uint16_t mPointerType;
bool mActiveState;
bool mPrimaryState;
+ bool mPreventMouseEventByContent;
explicit PointerInfo(bool aActiveState, uint16_t aPointerType,
bool aPrimaryState)
: mPointerType(aPointerType)
, mActiveState(aActiveState)
, mPrimaryState(aPrimaryState)
+ , mPreventMouseEventByContent(false)
{
}
};
static void DispatchGotOrLostPointerCaptureEvent(bool aIsGotCapture,
uint32_t aPointerId,
uint16_t aPointerType,
bool aIsPrimary,
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -6840,16 +6840,92 @@ FlushThrottledStyles(nsIDocument *aDocum
}
}
}
aDocument->EnumerateSubDocuments(FlushThrottledStyles, nullptr);
return true;
}
+/*
+ * This function handles the preventDefault behavior of pointerdown. When user
+ * preventDefault on pointerdown, We have to mark the active pointer to prevent
+ * sebsequent mouse events (except mouse transition events) and default
+ * behaviors.
+ *
+ * We add mPreventMouseEventByContent flag in PointerInfo to represent the
+ * active pointer won't firing compatible mouse events. It's set to true when
+ * content preventDefault on pointerdown
+ */
+static void
+PostHandlePointerEventsPreventDefault(WidgetPointerEvent* aPointerEvent,
+ WidgetGUIEvent* aMouseOrTouchEvent)
+{
+ if (!aPointerEvent->mIsPrimary || aPointerEvent->mMessage != ePointerDown ||
+ !aPointerEvent->DefaultPreventedByContent()) {
+ return;
+ }
+ nsIPresShell::PointerInfo* pointerInfo = nullptr;
+ if (!sActivePointersIds->Get(aPointerEvent->pointerId, &pointerInfo) ||
+ !pointerInfo) {
+ // We already added the PointerInfo for active pointer when
+ // PresShell::HandleEvent handling pointerdown event.
+#ifdef DEBUG
+ MOZ_CRASH("Got ePointerDown w/o active pointer info!!");
+#endif // #ifdef DEBUG
+ return;
+ }
+ // PreventDefault only applied for active pointers.
+ if (!pointerInfo->mActiveState) {
+ return;
+ }
+ aMouseOrTouchEvent->PreventDefault(false);
+ pointerInfo->mPreventMouseEventByContent = true;
+}
+
+/*
+ * This function handles the case when content had called preventDefault on the
+ * active pointer. In that case we have to prevent firing subsequent mouse
+ * to content. We check the flag PointerInfo::mPreventMouseEventByContent and
+ * call PreventDefault(false) to stop default behaviors and stop firing mouse
+ * events to content and chrome.
+ *
+ * note: mouse transition events are excluded
+ * note: we have to clean mPreventMouseEventByContent on pointerup for those
+ * devices support hover
+ * note: we don't suppress firing mouse events to chrome and system group
+ * handlers because they may implement default behaviors
+ */
+static void
+PreHandlePointerEventsPreventDefault(WidgetPointerEvent* aPointerEvent,
+ WidgetGUIEvent* aMouseOrTouchEvent)
+{
+ if (!aPointerEvent->mIsPrimary || aPointerEvent->mMessage == ePointerDown) {
+ return;
+ }
+ nsIPresShell::PointerInfo* pointerInfo = nullptr;
+ if (!sActivePointersIds->Get(aPointerEvent->pointerId, &pointerInfo) ||
+ !pointerInfo) {
+ // The PointerInfo for active pointer should be added for normal cases. But
+ // in some cases, we may receive mouse events before adding PointerInfo in
+ // sActivePointersIds. (e.g. receive mousemove before eMouseEnterIntoWidget
+ // or change preference 'dom.w3c_pointer_events.enabled' from off to on).
+ // In these cases, we could ignore them because they are not the events
+ // between a DefaultPrevented pointerdown and the corresponding pointerup.
+ return;
+ }
+ if (!pointerInfo->mPreventMouseEventByContent) {
+ return;
+ }
+ aMouseOrTouchEvent->PreventDefault(false);
+ if (aPointerEvent->mMessage == ePointerUp) {
+ pointerInfo->mPreventMouseEventByContent = false;
+ }
+}
+
static nsresult
DispatchPointerFromMouseOrTouch(PresShell* aShell,
nsIFrame* aFrame,
WidgetGUIEvent* aEvent,
bool aDontRetargetEvents,
nsEventStatus* aStatus,
nsIContent** aTargetContent)
{
@@ -6886,18 +6962,20 @@ DispatchPointerFromMouseOrTouch(PresShel
event.inputSource = mouseEvent->inputSource;
event.mMessage = pointerMessage;
event.button = button;
event.buttons = mouseEvent->buttons;
event.pressure = event.buttons ?
mouseEvent->pressure ? mouseEvent->pressure : 0.5f :
0.0f;
event.convertToPointer = mouseEvent->convertToPointer = false;
+ PreHandlePointerEventsPreventDefault(&event, aEvent);
aShell->HandleEvent(aFrame, &event, aDontRetargetEvents, aStatus,
aTargetContent);
+ PostHandlePointerEventsPreventDefault(&event, aEvent);
} else if (aEvent->mClass == eTouchEventClass) {
WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
// loop over all touches and dispatch pointer events on each touch
// copy the event
switch (touchEvent->mMessage) {
case eTouchMove:
pointerMessage = ePointerMove;
break;
@@ -6932,18 +7010,20 @@ DispatchPointerFromMouseOrTouch(PresShel
event.tiltY = touch->tiltY;
event.mTime = touchEvent->mTime;
event.mTimeStamp = touchEvent->mTimeStamp;
event.mFlags = touchEvent->mFlags;
event.button = WidgetMouseEvent::eLeftButton;
event.buttons = WidgetMouseEvent::eLeftButtonFlag;
event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
event.convertToPointer = touch->convertToPointer = false;
+ PreHandlePointerEventsPreventDefault(&event, aEvent);
aShell->HandleEvent(aFrame, &event, aDontRetargetEvents, aStatus,
aTargetContent);
+ PostHandlePointerEventsPreventDefault(&event, aEvent);
}
}
return NS_OK;
}
class ReleasePointerCaptureCaller
{
public:
--- a/widget/BasicEvents.h
+++ b/widget/BasicEvents.h
@@ -425,16 +425,19 @@ public:
/**
* Helper methods for methods of DOM Event.
*/
void StopPropagation() { mFlags.StopPropagation(); }
void StopImmediatePropagation() { mFlags.StopImmediatePropagation(); }
void StopCrossProcessForwarding() { mFlags.StopCrossProcessForwarding(); }
void PreventDefault(bool aCalledByDefaultHandler = true)
{
+ // Legacy mouse events shouldn't be prevented on ePointerDown by default
+ // handlers.
+ MOZ_RELEASE_ASSERT(!aCalledByDefaultHandler || mMessage != ePointerDown);
mFlags.PreventDefault(aCalledByDefaultHandler);
}
void PreventDefaultBeforeDispatch() { mFlags.PreventDefaultBeforeDispatch(); }
bool DefaultPrevented() const { return mFlags.DefaultPrevented(); }
bool DefaultPreventedByContent() const
{
return mFlags.DefaultPreventedByContent();
}
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -372,16 +372,27 @@ WidgetEvent::IsTargetedAtFocusedContent(
IsRetargetedNativeEventDelivererForPlugin();
}
bool
WidgetEvent::IsAllowedToDispatchDOMEvent() const
{
switch (mClass) {
case eMouseEventClass:
+ // When content PreventDefault on ePointerDown, we will stop dispatching
+ // the subsequent mouse events (eMouseDown, eMouseUp, eMouseMove). But we
+ // still need the mouse events to be handled in EventStateManager to
+ // generate other events (e.g. eMouseClick). So we only stop dispatching
+ // them to DOM.
+ if (DefaultPreventedByContent() &&
+ (mMessage == eMouseMove || mMessage == eMouseDown ||
+ mMessage == eMouseUp)) {
+ return false;
+ }
+ MOZ_FALLTHROUGH;
case ePointerEventClass:
// We want synthesized mouse moves to cause mouseover and mouseout
// DOM events (EventStateManager::PreHandleEvent), but not mousemove
// DOM events.
// Synthesized button up events also do not cause DOM events because they
// do not have a reliable mRefPoint.
return AsMouseEvent()->mReason == WidgetMouseEvent::eReal;