Bug 1793267 - Make PostEventHandler::CheckPointerCaptureState synthesize ePointerMove and eMouseMove if nobody captures the pointer anymore r=smaug

When an element starts capturing a pointer, pointer/mouse boundary events are
dispatched by `EventStateManager::PreHandleEvent` [1].  However, when the
capturing element loses the capture, they are not dispatched.

When the pointer capture is implicitly released, the pointer may be over another
document.  Therefore, this patch synthesizes an internal `ePointerMove` and
`eMouseMove` on the widget to make `PresShell::HandleEvent` redirects the event
to proper document under the pointer.

Unfortunately, I add 2 manual tests into WPT.  The reason is, a drag operation
across document boundary with test driver does not work even if I specify the
pointer position within the parent document coordinates.  This is same both on
Firefox and Chrome.  Additionally, writing the new tests as a mochitest won't
work too.  If I use synthesized mouse events, I see similar failure.
Additionally, when I use native events, it works, but unstable to run on CI.

1. https://searchfox.org/mozilla-central/rev/669fac9888b173c02baa4c036e980c0c204dfe02/dom/events/EventStateManager.cpp#1139-1140

Differential Revision: https://phabricator.services.mozilla.com/D218896
This commit is contained in:
Masayuki Nakano 2024-08-20 03:42:26 +00:00
parent ae694129cf
commit 151b00226e
13 changed files with 414 additions and 22 deletions

View File

@ -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<nsIWidget> widget = aEvent->mWidget;
if (NS_WARN_IF(!widget)) {
return;
}
Maybe<WidgetMouseEvent> mouseMoveEvent;
Maybe<WidgetPointerEvent> 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

View File

@ -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);

View File

@ -7174,7 +7174,6 @@ nsresult PresShell::EventHandler::HandleEventUsingCoordinates(
// content outdated?
nsCOMPtr<nsIContent> 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<Element> pointerCapturingElement =
const RefPtr<Element> 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;

View File

@ -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

View File

@ -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]

View File

@ -0,0 +1,127 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<title>Pointer Events when mouse button up on the parent document while an element in a child document captures the pointer</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
"use strict";
setup({explicit_timeout: true});
addEventListener("load", () => {
promise_test(async t => {
const iframe = document.querySelector("iframe");
const button = iframe.contentDocument.querySelector("button");
const div = iframe.contentDocument.querySelector("div");
const divInParent = document.querySelector("iframe + div");
let pointerEvents = [];
let mouseEvents = [];
function recordPointerEvent(event) {
pointerEvents.push(event);
}
function recordMouseEvent(event) {
mouseEvents.push(event);
}
function stringifyEvent(event) {
function stringifyTarget(target) {
switch (target) {
case button:
return "<button> in child";
case div:
return "<div> in child which captured the pointer";
case divInParent:
return "<div> in parent";
default:
return target?.nodeName;
}
}
return `"${event.type}" on ${stringifyTarget(event.target)}`;
}
const pointerEventTypes = ["pointerup", "lostpointercapture", "pointerover", "pointerout", "pointerenter", "pointerleave", "pointermove"];
const mouseEventTypes = ["mouseup", "mouseover", "mouseout", "mouseenter", "mouseleave", "mousemove"];
const promisePointerUp = new Promise(resolve => {
button.addEventListener("pointerdown", event => {
div.setPointerCapture(event.pointerId);
iframe.contentWindow.addEventListener("pointerup", event => {
recordPointerEvent(event);
[button, div, divInParent].forEach(target => {
pointerEventTypes.forEach(type => {
target.addEventListener(type, recordPointerEvent);
});
mouseEventTypes.forEach(type => {
target.addEventListener(type, recordMouseEvent);
});
});
resolve();
}, {once: true});
}, {once: true});
});
await promisePointerUp;
await new Promise(
resolve => requestAnimationFrame(
() => requestAnimationFrame(resolve)
)
);
const stringifiedPointerEvents = [];
const stringifiedMouseEvents = [];
for (const event of pointerEvents) {
stringifiedPointerEvents.push(stringifyEvent(event));
}
for (const event of mouseEvents) {
stringifiedMouseEvents.push(stringifyEvent(event));
}
const stringifiedExpectedPointerEvents = [
stringifyEvent({ type: "pointerup", target: div }),
stringifyEvent({ type: "lostpointercapture", target: div }),
stringifyEvent({ type: "pointerout", target: div }),
stringifyEvent({ type: "pointerleave", target: div }),
stringifyEvent({ type: "pointerover", target: divInParent }),
stringifyEvent({ type: "pointerenter", target: divInParent }),
];
if (pointerEvents[pointerEvents.length - 1]?.type == "pointermove") {
stringifiedExpectedPointerEvents.push(
stringifyEvent({ type: "pointermove", target: divInParent })
);
}
const stringifiedExpectedMouseEvents = [
stringifyEvent({ type: "mouseup", target: div }),
stringifyEvent({ type: "mouseout", target: div }),
stringifyEvent({ type: "mouseleave", target: div }),
stringifyEvent({ type: "mouseover", target: divInParent }),
stringifyEvent({ type: "mouseenter", target: divInParent }),
];
if (mouseEvents[mouseEvents.length - 1]?.type == "mousemove") {
stringifiedExpectedMouseEvents.push(
stringifyEvent({ type: "mousemove", target: divInParent })
);
}
t.step(() => {
assert_array_equals(stringifiedPointerEvents, stringifiedExpectedPointerEvents)
assert_array_equals(stringifiedMouseEvents, stringifiedExpectedMouseEvents)
});
t.done();
}, "boundary events should be fired for notifying web apps of over the element in parent document");
}, {once: true});
</script>
</head>
<body>
<div>
<p>Test steps:</p>
<ol>
<li>Press the button with primary button of your mouse and start dragging</li>
<li>Move the mouse cursor over the red border box and release the mouse button</li>
</ol>
</div>
<iframe srcdoc="<button>Start dragging from this button</button><div><br></div>"></iframe>
<div style='border: 1px solid red'>And release mouse button over this box</div>
</body>
</html>

View File

@ -0,0 +1,128 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<title>Pointer Events when mouse button up on a sub-document while an element in parent document captures the pointer</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
"use strict";
setup({explicit_timeout: true});
addEventListener("load", () => {
promise_test(async t => {
const button = document.querySelector("button");
const div = document.querySelector("button + div");
const iframe = document.querySelector("iframe");
const divInChild = iframe.contentDocument.querySelector("div");
let pointerEvents = [];
let mouseEvents = [];
function recordPointerEvent(event) {
pointerEvents.push(event);
}
function recordMouseEvent(event) {
mouseEvents.push(event);
}
function stringifyEvent(event) {
function stringifyTarget(target) {
switch (target) {
case button:
return "<button>";
case div:
return "<div> in parent which captured the pointer";
case divInChild:
return "<div> in child";
default:
return target?.nodeName;
}
}
return `"${event.type}" on ${stringifyTarget(event.target)}`;
}
const pointerEventTypes = ["pointerup", "lostpointercapture", "pointerover", "pointerout", "pointerenter", "pointerleave", "pointermove"];
const mouseEventTypes = ["mouseup", "mouseover", "mouseout", "mouseenter", "mouseleave", "mousemove"];
const promisePointerUp = new Promise(resolve => {
button.addEventListener("pointerdown", event => {
div.setPointerCapture(event.pointerId);
addEventListener("pointerup", event => {
recordPointerEvent(event);
[button, div, divInChild].forEach(target => {
pointerEventTypes.forEach(type => {
target.addEventListener(type, recordPointerEvent);
});
mouseEventTypes.forEach(type => {
target.addEventListener(type, recordMouseEvent);
});
});
resolve();
}, {once: true});
}, {once: true});
});
await promisePointerUp;
await new Promise(
resolve => requestAnimationFrame(
() => requestAnimationFrame(resolve)
)
);
const stringifiedPointerEvents = [];
const stringifiedMouseEvents = [];
for (const event of pointerEvents) {
stringifiedPointerEvents.push(stringifyEvent(event));
}
for (const event of mouseEvents) {
stringifiedMouseEvents.push(stringifyEvent(event));
}
const stringifiedExpectedPointerEvents = [
stringifyEvent({ type: "pointerup", target: div }),
stringifyEvent({ type: "lostpointercapture", target: div }),
stringifyEvent({ type: "pointerout", target: div }),
stringifyEvent({ type: "pointerleave", target: div }),
stringifyEvent({ type: "pointerover", target: divInChild }),
stringifyEvent({ type: "pointerenter", target: divInChild }),
];
if (pointerEvents[pointerEvents.length - 1]?.type == "pointermove") {
stringifiedExpectedPointerEvents.push(
stringifyEvent({ type: "pointermove", target: divInChild })
);
}
const stringifiedExpectedMouseEvents = [
stringifyEvent({ type: "mouseup", target: div }),
stringifyEvent({ type: "mouseout", target: div }),
stringifyEvent({ type: "mouseleave", target: div }),
stringifyEvent({ type: "mouseover", target: divInChild }),
stringifyEvent({ type: "mouseenter", target: divInChild }),
];
if (mouseEvents[mouseEvents.length - 1]?.type == "mousemove") {
stringifiedExpectedMouseEvents.push(
stringifyEvent({ type: "mousemove", target: divInChild })
);
}
t.step(() => {
assert_array_equals(stringifiedPointerEvents, stringifiedExpectedPointerEvents)
assert_array_equals(stringifiedMouseEvents, stringifiedExpectedMouseEvents)
});
t.done();
}, "boundary events should be fired for notifying web apps of over the element in child document");
}, {once: true});
</script>
</head>
<body>
<div>
<p>Test steps:</p>
<ol>
<li>Press the button with primary button of your mouse and start dragging</li>
<li>Move the mouse cursor over the red border box and release the mouse button</li>
</ol>
</div>
<button>Start dragging from this button</button>
<div><br></div>
<iframe srcdoc="<div style='border: 1px solid red'>And release mouse button over this box</div>"></iframe>
</body>
</html>

View File

@ -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;
};
/******************************************************************************

View File

@ -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;
}

View File

@ -314,6 +314,7 @@ class MouseInput : public InputData {
* event or following "mouseup", set to true.
*/
bool mPreventClickEvent;
bool mIgnoreCapturingContent;
};
/**

View File

@ -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;
}

View File

@ -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
*

View File

@ -219,6 +219,9 @@ struct ParamTraits<mozilla::WidgetMouseEvent> {
using paramType = mozilla::WidgetMouseEvent;
static void Write(MessageWriter* aWriter, const paramType& aParam) {
MOZ_ASSERT(!aParam.mIgnoreCapturingContent);
MOZ_ASSERT(!aParam.mSynthesizeMoveAfterDispatch);
WriteParam(aWriter,
static_cast<const mozilla::WidgetMouseEventBase&>(aParam));
WriteParam(aWriter,