diff --git a/dom/events/PointerEventHandler.cpp b/dom/events/PointerEventHandler.cpp index 79a7f3ad1617..e27ddc3cba68 100644 --- a/dom/events/PointerEventHandler.cpp +++ b/dom/events/PointerEventHandler.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "PointerEventHandler.h" +#include "mozilla/EventForwards.h" #include "nsIContentInlines.h" #include "nsIFrame.h" #include "PointerEvent.h" @@ -384,6 +385,62 @@ void PointerEventHandler::CheckPointerCaptureState(WidgetPointerEvent* aEvent) { DispatchGotOrLostPointerCaptureEvent(/* aIsGotCapture */ true, aEvent, pendingElement); } + + // If nobody captures the pointer and the pointer will not be removed, we need + // to dispatch pointer boundary events if the pointer will keep hovering over + // somewhere even after the pointer is up. + // XXX Do we need to check whether there is new pending pointer capture + // element? But if there is, what should we do? + if (overrideElement && !pendingElement && aEvent->mWidget && + aEvent->mMessage != ePointerCancel && + (aEvent->mMessage != ePointerUp || aEvent->InputSourceSupportsHover())) { + aEvent->mSynthesizeMoveAfterDispatch = true; + } +} + +/* static */ +void PointerEventHandler::SynthesizeMoveToDispatchBoundaryEvents( + const WidgetMouseEvent* aEvent) { + nsCOMPtr widget = aEvent->mWidget; + if (NS_WARN_IF(!widget)) { + return; + } + Maybe mouseMoveEvent; + Maybe pointerMoveEvent; + if (aEvent->mClass == eMouseEventClass) { + mouseMoveEvent.emplace(true, eMouseMove, aEvent->mWidget, + WidgetMouseEvent::eSynthesized); + } else if (aEvent->mClass == ePointerEventClass) { + pointerMoveEvent.emplace(true, ePointerMove, aEvent->mWidget); + pointerMoveEvent->mReason = WidgetMouseEvent::eSynthesized; + + const WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent(); + MOZ_ASSERT(pointerEvent); + pointerMoveEvent->mIsPrimary = pointerEvent->mIsPrimary; + pointerMoveEvent->mFromTouchEvent = pointerEvent->mFromTouchEvent; + pointerMoveEvent->mWidth = pointerEvent->mWidth; + pointerMoveEvent->mHeight = pointerEvent->mHeight; + } else { + MOZ_ASSERT_UNREACHABLE( + "The event must be WidgetMouseEvent or WidgetPointerEvent"); + } + WidgetMouseEvent& event = + mouseMoveEvent ? mouseMoveEvent.ref() : pointerMoveEvent.ref(); + event.mFlags.mIsSynthesizedForTests = aEvent->mFlags.mIsSynthesizedForTests; + event.mIgnoreCapturingContent = true; + event.mRefPoint = aEvent->mRefPoint; + event.mInputSource = aEvent->mInputSource; + event.mButtons = aEvent->mButtons; + event.mModifiers = aEvent->mModifiers; + event.convertToPointer = false; + event.AssignPointerHelperData(*aEvent); + + // XXX If the pointer is already over a document in different process, we + // cannot synthesize the pointermove/mousemove on the document since + // dispatching events to the parent process is currently allowed only in + // automation. + nsEventStatus eventStatus = nsEventStatus_eIgnore; + widget->DispatchEvent(&event, eventStatus); } /* static */ @@ -455,6 +512,16 @@ Element* PointerEventHandler::GetPointerCapturingElement( return nullptr; } + // PointerEventHandler may synthesize ePointerMove event before releasing the + // mouse capture (it's done by a default handler of eMouseUp) after handling + // ePointerUp. Then, we need to dispatch pointer boundary events for the + // element under the pointer to emulate a pointer move after a pointer + // capture. Therefore, we need to ignore the capturing element if the event + // dispatcher requests it. + if (aEvent->ShouldIgnoreCapturingContent()) { + return nullptr; + } + WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); if (!mouseEvent) { return nullptr; @@ -675,6 +742,10 @@ void PointerEventHandler::DispatchPointerFromMouseOrTouch( shell->HandleEventWithTarget(&event, aEventTargetFrame, aEventTargetContent, aStatus, true, aMouseOrTouchEventTarget); PostHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent); + // If pointer capture is released, we need to synthesize eMouseMove to + // dispatch mouse boundary events later. + mouseEvent->mSynthesizeMoveAfterDispatch |= + event.mSynthesizeMoveAfterDispatch; } else if (aMouseOrTouchEvent->mClass == eTouchEventClass) { WidgetTouchEvent* touchEvent = aMouseOrTouchEvent->AsTouchEvent(); // loop over all touches and dispatch pointer events on each touch diff --git a/dom/events/PointerEventHandler.h b/dom/events/PointerEventHandler.h index 87b41f9d94e5..322c0514a989 100644 --- a/dom/events/PointerEventHandler.h +++ b/dom/events/PointerEventHandler.h @@ -205,6 +205,16 @@ class PointerEventHandler final { bool aDontRetargetEvents, nsEventStatus* aStatus, nsIContent** aMouseOrTouchEventTarget = nullptr); + /** + * Synthesize eMouseMove or ePointerMove to dispatch mouse/pointer boundary + * events if they are required. This dispatches the event on the widget. + * Therefore, this dispatches the event on correct document in the same + * process. However, if there is a popup under the pointer or a document in a + * different process, this does not work as you expected. + */ + MOZ_CAN_RUN_SCRIPT static void SynthesizeMoveToDispatchBoundaryEvents( + const WidgetMouseEvent* aEvent); + static void InitPointerEventFromMouse(WidgetPointerEvent* aPointerEvent, WidgetMouseEvent* aMouseEvent, EventMessage aMessage); diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp index 83abf4b92eb0..ffcf4b6d4cef 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp @@ -7174,7 +7174,6 @@ nsresult PresShell::EventHandler::HandleEventUsingCoordinates( // content outdated? nsCOMPtr capturingContent = EventHandler::GetCapturingContentFor(aGUIEvent); - if (GetDocument() && aGUIEvent->mClass == eTouchEventClass) { PointerLockManager::Unlock(); } @@ -7215,7 +7214,7 @@ nsresult PresShell::EventHandler::HandleEventUsingCoordinates( } // Only capture mouse events and pointer events. - RefPtr pointerCapturingElement = + const RefPtr pointerCapturingElement = PointerEventHandler::GetPointerCapturingElement(aGUIEvent); if (pointerCapturingElement) { @@ -7760,11 +7759,23 @@ bool PresShell::EventHandler::MaybeDiscardEvent(WidgetGUIEvent* aGUIEvent) { // static nsIContent* PresShell::EventHandler::GetCapturingContentFor( WidgetGUIEvent* aGUIEvent) { - return (aGUIEvent->mClass == ePointerEventClass || - aGUIEvent->mClass == eWheelEventClass || - aGUIEvent->HasMouseEventMessage()) - ? PresShell::GetCapturingContent() - : nullptr; + if (aGUIEvent->mClass != ePointerEventClass && + aGUIEvent->mClass != eWheelEventClass && + !aGUIEvent->HasMouseEventMessage()) { + return nullptr; + } + + // PointerEventHandler may synthesize ePointerMove event before releasing the + // mouse capture (it's done by a default handler of eMouseUp) after handling + // ePointerUp. Then, we need to dispatch pointer boundary events for the + // element under the pointer to emulate a pointer move after a pointer + // capture. Therefore, we need to ignore the capturing element if the event + // dispatcher requests it. + if (aGUIEvent->ShouldIgnoreCapturingContent()) { + return nullptr; + } + + return PresShell::GetCapturingContent(); } bool PresShell::EventHandler::GetRetargetEventDocument( @@ -7799,8 +7810,10 @@ bool PresShell::EventHandler::GetRetargetEventDocument( return true; } - nsIContent* capturingContent = - EventHandler::GetCapturingContentFor(aGUIEvent); + const nsIContent* const capturingContent = + aGUIEvent->ShouldIgnoreCapturingContent() + ? nullptr + : EventHandler::GetCapturingContentFor(aGUIEvent); if (capturingContent) { // if the mouse is being captured then retarget the mouse event at the // document that is being captured. @@ -8691,15 +8704,15 @@ void PresShell::EventHandler::FinalizeHandlingEvent( if (aEvent->mMessage == eKeyDown) { mPresShell->mIsLastKeyDownCanceled = aEvent->mFlags.mDefaultPrevented; } - return; + break; } case eMouseUp: // reset the capturing content now that the mouse button is up PresShell::ReleaseCapturingContent(); - return; + break; case eMouseMove: PresShell::AllowMouseCapture(false); - return; + break; case eDrag: case eDragEnd: case eDragEnter: @@ -8714,7 +8727,7 @@ void PresShell::EventHandler::FinalizeHandlingEvent( if (dataTransfer) { dataTransfer->Disconnect(); } - return; + break; } case eTouchStart: case eTouchMove: @@ -8727,7 +8740,13 @@ void PresShell::EventHandler::FinalizeHandlingEvent( break; } default: - return; + break; + } + + if (WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) { + if (mouseEvent->mSynthesizeMoveAfterDispatch) { + PointerEventHandler::SynthesizeMoveToDispatchBoundaryEvents(mouseEvent); + } } } @@ -11989,6 +12008,10 @@ bool PresShell::EventHandler::EventTargetData::MaybeRetargetToActiveDocument( return false; } + if (aGUIEvent->ShouldIgnoreCapturingContent()) { + return false; + } + nsPresContext* activePresContext = activeESM->GetPresContext(); if (!activePresContext) { return false; diff --git a/testing/web-platform/meta/pointerevents/pointerevent_boundary_events_at_implicit_release_hoverable_pointers.html.ini b/testing/web-platform/meta/pointerevents/pointerevent_boundary_events_at_implicit_release_hoverable_pointers.html.ini index 26a4a38aa1a1..bcc24b3523fa 100644 --- a/testing/web-platform/meta/pointerevents/pointerevent_boundary_events_at_implicit_release_hoverable_pointers.html.ini +++ b/testing/web-platform/meta/pointerevents/pointerevent_boundary_events_at_implicit_release_hoverable_pointers.html.ini @@ -1,5 +1,3 @@ [pointerevent_boundary_events_at_implicit_release_hoverable_pointers.html] expected: if (os == "android") and fission: [OK, TIMEOUT] - [mouse Event sequence at implicit release on click] - expected: FAIL diff --git a/testing/web-platform/meta/pointerevents/pointerevent_capture_suppressing_mouse.html.ini b/testing/web-platform/meta/pointerevents/pointerevent_capture_suppressing_mouse.html.ini index 6e8ad5f9be21..915c198cf02a 100644 --- a/testing/web-platform/meta/pointerevents/pointerevent_capture_suppressing_mouse.html.ini +++ b/testing/web-platform/meta/pointerevents/pointerevent_capture_suppressing_mouse.html.ini @@ -1,9 +1,6 @@ [pointerevent_capture_suppressing_mouse.html] expected: if (os == "android") and fission: [OK, TIMEOUT] - [Test pointer capture.] - expected: FAIL - [Validate pointer events track pointer movement without pointer capture.] expected: if (os == "linux") and not debug and not fission: [PASS, FAIL] diff --git a/testing/web-platform/tests/pointerevents/pointerevent_capture_implicit_release_on_parent_doc_while_subdoc_captures-manual.html b/testing/web-platform/tests/pointerevents/pointerevent_capture_implicit_release_on_parent_doc_while_subdoc_captures-manual.html new file mode 100644 index 000000000000..b8524b378bef --- /dev/null +++ b/testing/web-platform/tests/pointerevents/pointerevent_capture_implicit_release_on_parent_doc_while_subdoc_captures-manual.html @@ -0,0 +1,127 @@ + + + + + +Pointer Events when mouse button up on the parent document while an element in a child document captures the pointer + + + + + +
+

Test steps:

+
    +
  1. Press the button with primary button of your mouse and start dragging
  2. +
  3. Move the mouse cursor over the red border box and release the mouse button
  4. +
+
+ +
And release mouse button over this box
+ + diff --git a/testing/web-platform/tests/pointerevents/pointerevent_capture_implicit_release_on_subdoc_while_parent_doc_captures-manual.html b/testing/web-platform/tests/pointerevents/pointerevent_capture_implicit_release_on_subdoc_while_parent_doc_captures-manual.html new file mode 100644 index 000000000000..44032c11e5fb --- /dev/null +++ b/testing/web-platform/tests/pointerevents/pointerevent_capture_implicit_release_on_subdoc_while_parent_doc_captures-manual.html @@ -0,0 +1,128 @@ + + + + + +Pointer Events when mouse button up on a sub-document while an element in parent document captures the pointer + + + + + +
+

Test steps:

+
    +
  1. Press the button with primary button of your mouse and start dragging
  2. +
  3. Move the mouse cursor over the red border box and release the mouse button
  4. +
+
+ +

+ + + diff --git a/widget/BasicEvents.h b/widget/BasicEvents.h index 240cd1d78e02..d4a5beb2a4c1 100644 --- a/widget/BasicEvents.h +++ b/widget/BasicEvents.h @@ -1020,6 +1020,12 @@ class WidgetEvent : public WidgetEventTime { } bool IsUserAction() const; + + /** + * Return true if the event should be handled without (pointer) capturing + * element. + */ + [[nodiscard]] bool ShouldIgnoreCapturingContent() const; }; /****************************************************************************** diff --git a/widget/InputData.cpp b/widget/InputData.cpp index b95d4a81780e..3c2cb3370d38 100644 --- a/widget/InputData.cpp +++ b/widget/InputData.cpp @@ -239,7 +239,8 @@ MouseInput::MouseInput() mInputSource(0), mButtons(0), mHandledByAPZ(false), - mPreventClickEvent(false) {} + mPreventClickEvent(false), + mIgnoreCapturingContent(false) {} MouseInput::MouseInput(MouseType aType, ButtonType aButtonType, uint16_t aInputSource, int16_t aButtons, @@ -252,7 +253,8 @@ MouseInput::MouseInput(MouseType aType, ButtonType aButtonType, mButtons(aButtons), mOrigin(aPoint), mHandledByAPZ(false), - mPreventClickEvent(false) {} + mPreventClickEvent(false), + mIgnoreCapturingContent(false) {} MouseInput::MouseInput(const WidgetMouseEventBase& aMouseEvent) : InputData(MOUSE_INPUT, aMouseEvent.mTimeStamp, aMouseEvent.mModifiers), @@ -262,7 +264,11 @@ MouseInput::MouseInput(const WidgetMouseEventBase& aMouseEvent) mButtons(aMouseEvent.mButtons), mHandledByAPZ(aMouseEvent.mFlags.mHandledByAPZ), mPreventClickEvent(aMouseEvent.mClass == eMouseEventClass && - aMouseEvent.AsMouseEvent()->mClickEventPrevented) { + aMouseEvent.AsMouseEvent()->mClickEventPrevented), + mIgnoreCapturingContent( + (aMouseEvent.mMessage == eMouseMove || + aMouseEvent.mMessage == ePointerMove) && + aMouseEvent.AsMouseEvent()->mIgnoreCapturingContent) { MOZ_ASSERT(NS_IsMainThread(), "Can only copy from WidgetTouchEvent on main thread"); @@ -430,6 +436,7 @@ WidgetMouseOrPointerEvent MouseInput::ToWidgetEvent(nsIWidget* aWidget) const { event.mFocusSequenceNumber = mFocusSequenceNumber; event.mExitFrom = exitFrom; event.mClickEventPrevented = mPreventClickEvent; + event.mIgnoreCapturingContent = mIgnoreCapturingContent; return event; } diff --git a/widget/InputData.h b/widget/InputData.h index b41c652ba6f5..9db5c2c9e206 100644 --- a/widget/InputData.h +++ b/widget/InputData.h @@ -314,6 +314,7 @@ class MouseInput : public InputData { * event or following "mouseup", set to true. */ bool mPreventClickEvent; + bool mIgnoreCapturingContent; }; /** diff --git a/widget/MouseEvents.h b/widget/MouseEvents.h index 363f021dc32b..413b3f00a965 100644 --- a/widget/MouseEvents.h +++ b/widget/MouseEvents.h @@ -366,9 +366,18 @@ class WidgetMouseEvent : public WidgetMouseEventBase, // Whether the event should ignore scroll frame bounds during dispatch. bool mIgnoreRootScrollFrame = false; + // Whether the event should be dispatched on a target limited in capturing + // content. + bool mIgnoreCapturingContent = false; + // Whether the event shouldn't cause click event. bool mClickEventPrevented = false; + // If this is set to true while the event is being dispatched, + // PresShell::EventHandler::FinalizeHandlingEvent will dispatch a synthesized + // eMouseMove or ePointerMove. + bool mSynthesizeMoveAfterDispatch = false; + void AssignMouseEventData(const WidgetMouseEvent& aEvent, bool aCopyTargets) { AssignMouseEventBaseData(aEvent, aCopyTargets); AssignPointerHelperData(aEvent, /* aCopyCoalescedEvents */ true); @@ -378,6 +387,7 @@ class WidgetMouseEvent : public WidgetMouseEventBase, mExitFrom = aEvent.mExitFrom; mClickCount = aEvent.mClickCount; mIgnoreRootScrollFrame = aEvent.mIgnoreRootScrollFrame; + mIgnoreCapturingContent = aEvent.mIgnoreCapturingContent; mClickEventPrevented = aEvent.mClickEventPrevented; } diff --git a/widget/WidgetEventImpl.cpp b/widget/WidgetEventImpl.cpp index 6ac766a294b4..709bb05f1a32 100644 --- a/widget/WidgetEventImpl.cpp +++ b/widget/WidgetEventImpl.cpp @@ -662,6 +662,17 @@ bool WidgetEvent::AllowFlushingPendingNotifications() const { return AsQueryContentEvent()->mNeedsToFlushLayout; } +bool WidgetEvent::ShouldIgnoreCapturingContent() const { + MOZ_ASSERT(IsUsingCoordinates()); + + if (MOZ_UNLIKELY(!IsTrusted())) { + return false; + } + return mClass == eMouseEventClass || mClass == ePointerEventClass + ? AsMouseEvent()->mIgnoreCapturingContent + : false; +} + /****************************************************************************** * mozilla::WidgetEvent * diff --git a/widget/nsGUIEventIPC.h b/widget/nsGUIEventIPC.h index 02775a7f27f5..3a0444586076 100644 --- a/widget/nsGUIEventIPC.h +++ b/widget/nsGUIEventIPC.h @@ -219,6 +219,9 @@ struct ParamTraits { using paramType = mozilla::WidgetMouseEvent; static void Write(MessageWriter* aWriter, const paramType& aParam) { + MOZ_ASSERT(!aParam.mIgnoreCapturingContent); + MOZ_ASSERT(!aParam.mSynthesizeMoveAfterDispatch); + WriteParam(aWriter, static_cast(aParam)); WriteParam(aWriter,