Files
tubestation/dom/events/PointerEventHandler.cpp
Masayuki Nakano a0a9b98b9b 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
2024-08-20 03:42:26 +00:00

928 lines
34 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* 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"
#include "PointerLockManager.h"
#include "nsRFPService.h"
#include "mozilla/PresShell.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "nsUserCharacteristics.h"
namespace mozilla {
using namespace dom;
Maybe<int32_t> PointerEventHandler::sSpoofedPointerId;
// Keeps a map between pointerId and element that currently capturing pointer
// with such pointerId. If pointerId is absent in this map then nobody is
// capturing it. Additionally keep information about pending capturing content.
static nsClassHashtable<nsUint32HashKey, PointerCaptureInfo>*
sPointerCaptureList;
// Keeps information about pointers such as pointerId, activeState, pointerType,
// primaryState
static nsClassHashtable<nsUint32HashKey, PointerInfo>* sActivePointersIds;
// Keeps track of which BrowserParent requested pointer capture for a pointer
// id.
static nsTHashMap<nsUint32HashKey, BrowserParent*>*
sPointerCaptureRemoteTargetTable = nullptr;
/* static */
void PointerEventHandler::InitializeStatics() {
MOZ_ASSERT(!sPointerCaptureList, "InitializeStatics called multiple times!");
sPointerCaptureList =
new nsClassHashtable<nsUint32HashKey, PointerCaptureInfo>;
sActivePointersIds = new nsClassHashtable<nsUint32HashKey, PointerInfo>;
if (XRE_IsParentProcess()) {
sPointerCaptureRemoteTargetTable =
new nsTHashMap<nsUint32HashKey, BrowserParent*>;
}
}
/* static */
void PointerEventHandler::ReleaseStatics() {
MOZ_ASSERT(sPointerCaptureList, "ReleaseStatics called without Initialize!");
delete sPointerCaptureList;
sPointerCaptureList = nullptr;
delete sActivePointersIds;
sActivePointersIds = nullptr;
if (sPointerCaptureRemoteTargetTable) {
MOZ_ASSERT(XRE_IsParentProcess());
delete sPointerCaptureRemoteTargetTable;
sPointerCaptureRemoteTargetTable = nullptr;
}
}
/* static */
bool PointerEventHandler::IsPointerEventImplicitCaptureForTouchEnabled() {
return StaticPrefs::dom_w3c_pointer_events_implicit_capture();
}
/* static */
void PointerEventHandler::UpdateActivePointerState(WidgetMouseEvent* aEvent,
nsIContent* aTargetContent) {
if (!aEvent) {
return;
}
switch (aEvent->mMessage) {
case eMouseEnterIntoWidget:
// In this case we have to know information about available mouse pointers
sActivePointersIds->InsertOrUpdate(
aEvent->pointerId,
MakeUnique<PointerInfo>(false, aEvent->mInputSource, true, false,
nullptr));
MaybeCacheSpoofedPointerID(aEvent->mInputSource, aEvent->pointerId);
break;
case ePointerDown:
// In this case we switch pointer to active state
if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) {
// XXXedgar, test could possibly synthesize a mousedown event on a
// coordinate outside the browser window and cause aTargetContent to be
// nullptr, not sure if this also happens on real usage.
sActivePointersIds->InsertOrUpdate(
pointerEvent->pointerId,
MakeUnique<PointerInfo>(
true, pointerEvent->mInputSource, pointerEvent->mIsPrimary,
pointerEvent->mFromTouchEvent,
aTargetContent ? aTargetContent->OwnerDoc() : nullptr));
MaybeCacheSpoofedPointerID(pointerEvent->mInputSource,
pointerEvent->pointerId);
}
break;
case ePointerCancel:
// pointercancel means a pointer is unlikely to continue to produce
// pointer events. In that case, we should turn off active state or remove
// the pointer from active pointers.
case ePointerUp:
// In this case we remove information about pointer or turn off active
// state
if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) {
if (pointerEvent->mInputSource !=
MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
sActivePointersIds->InsertOrUpdate(
pointerEvent->pointerId,
MakeUnique<PointerInfo>(false, pointerEvent->mInputSource,
pointerEvent->mIsPrimary,
pointerEvent->mFromTouchEvent, nullptr));
} else {
sActivePointersIds->Remove(pointerEvent->pointerId);
}
}
break;
case eMouseExitFromWidget:
// In this case we have to remove information about disappeared mouse
// pointers
sActivePointersIds->Remove(aEvent->pointerId);
break;
default:
MOZ_ASSERT_UNREACHABLE("event has invalid type");
break;
}
}
/* static */
void PointerEventHandler::RequestPointerCaptureById(uint32_t aPointerId,
Element* aElement) {
SetPointerCaptureById(aPointerId, aElement);
if (BrowserChild* browserChild =
BrowserChild::GetFrom(aElement->OwnerDoc()->GetDocShell())) {
browserChild->SendRequestPointerCapture(
aPointerId,
[aPointerId](bool aSuccess) {
if (!aSuccess) {
PointerEventHandler::ReleasePointerCaptureById(aPointerId);
}
},
[](mozilla::ipc::ResponseRejectReason) {});
}
}
/* static */
void PointerEventHandler::SetPointerCaptureById(uint32_t aPointerId,
Element* aElement) {
MOZ_ASSERT(aElement);
sPointerCaptureList->WithEntryHandle(aPointerId, [&](auto&& entry) {
if (entry) {
entry.Data()->mPendingElement = aElement;
} else {
entry.Insert(MakeUnique<PointerCaptureInfo>(aElement));
}
});
}
/* static */
PointerCaptureInfo* PointerEventHandler::GetPointerCaptureInfo(
uint32_t aPointerId) {
PointerCaptureInfo* pointerCaptureInfo = nullptr;
sPointerCaptureList->Get(aPointerId, &pointerCaptureInfo);
return pointerCaptureInfo;
}
/* static */
void PointerEventHandler::ReleasePointerCaptureById(uint32_t aPointerId) {
PointerCaptureInfo* pointerCaptureInfo = GetPointerCaptureInfo(aPointerId);
if (pointerCaptureInfo) {
if (Element* pendingElement = pointerCaptureInfo->mPendingElement) {
if (BrowserChild* browserChild = BrowserChild::GetFrom(
pendingElement->OwnerDoc()->GetDocShell())) {
browserChild->SendReleasePointerCapture(aPointerId);
}
}
pointerCaptureInfo->mPendingElement = nullptr;
}
}
/* static */
void PointerEventHandler::ReleaseAllPointerCapture() {
for (const auto& entry : *sPointerCaptureList) {
PointerCaptureInfo* data = entry.GetWeak();
if (data && data->mPendingElement) {
ReleasePointerCaptureById(entry.GetKey());
}
}
}
/* static */
bool PointerEventHandler::SetPointerCaptureRemoteTarget(
uint32_t aPointerId, dom::BrowserParent* aBrowserParent) {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(sPointerCaptureRemoteTargetTable);
MOZ_ASSERT(aBrowserParent);
if (PointerLockManager::GetLockedRemoteTarget()) {
return false;
}
BrowserParent* currentRemoteTarget =
PointerEventHandler::GetPointerCapturingRemoteTarget(aPointerId);
if (currentRemoteTarget && currentRemoteTarget != aBrowserParent) {
return false;
}
sPointerCaptureRemoteTargetTable->InsertOrUpdate(aPointerId, aBrowserParent);
return true;
}
/* static */
void PointerEventHandler::ReleasePointerCaptureRemoteTarget(
BrowserParent* aBrowserParent) {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(sPointerCaptureRemoteTargetTable);
MOZ_ASSERT(aBrowserParent);
sPointerCaptureRemoteTargetTable->RemoveIf([aBrowserParent](
const auto& iter) {
BrowserParent* browserParent = iter.Data();
MOZ_ASSERT(browserParent, "Null BrowserParent in pointer captured table?");
return aBrowserParent == browserParent;
});
}
/* static */
void PointerEventHandler::ReleasePointerCaptureRemoteTarget(
uint32_t aPointerId) {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(sPointerCaptureRemoteTargetTable);
sPointerCaptureRemoteTargetTable->Remove(aPointerId);
}
/* static */
BrowserParent* PointerEventHandler::GetPointerCapturingRemoteTarget(
uint32_t aPointerId) {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(sPointerCaptureRemoteTargetTable);
return sPointerCaptureRemoteTargetTable->Get(aPointerId);
}
/* static */
void PointerEventHandler::ReleaseAllPointerCaptureRemoteTarget() {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(sPointerCaptureRemoteTargetTable);
for (auto iter = sPointerCaptureRemoteTargetTable->Iter(); !iter.Done();
iter.Next()) {
BrowserParent* browserParent = iter.Data();
MOZ_ASSERT(browserParent, "Null BrowserParent in pointer captured table?");
Unused << browserParent->SendReleaseAllPointerCapture();
iter.Remove();
}
}
/* static */
const PointerInfo* PointerEventHandler::GetPointerInfo(uint32_t aPointerId) {
return sActivePointersIds->Get(aPointerId);
}
/* static */
void PointerEventHandler::MaybeProcessPointerCapture(WidgetGUIEvent* aEvent) {
switch (aEvent->mClass) {
case eMouseEventClass:
ProcessPointerCaptureForMouse(aEvent->AsMouseEvent());
break;
case eTouchEventClass:
ProcessPointerCaptureForTouch(aEvent->AsTouchEvent());
break;
default:
break;
}
}
/* static */
void PointerEventHandler::ProcessPointerCaptureForMouse(
WidgetMouseEvent* aEvent) {
if (!ShouldGeneratePointerEventFromMouse(aEvent)) {
return;
}
PointerCaptureInfo* info = GetPointerCaptureInfo(aEvent->pointerId);
if (!info || info->mPendingElement == info->mOverrideElement) {
return;
}
WidgetPointerEvent localEvent(*aEvent);
InitPointerEventFromMouse(&localEvent, aEvent, eVoidEvent);
CheckPointerCaptureState(&localEvent);
}
/* static */
void PointerEventHandler::ProcessPointerCaptureForTouch(
WidgetTouchEvent* aEvent) {
if (!ShouldGeneratePointerEventFromTouch(aEvent)) {
return;
}
for (uint32_t i = 0; i < aEvent->mTouches.Length(); ++i) {
Touch* touch = aEvent->mTouches[i];
if (!TouchManager::ShouldConvertTouchToPointer(touch, aEvent)) {
continue;
}
PointerCaptureInfo* info = GetPointerCaptureInfo(touch->Identifier());
if (!info || info->mPendingElement == info->mOverrideElement) {
continue;
}
WidgetPointerEvent event(aEvent->IsTrusted(), eVoidEvent, aEvent->mWidget);
InitPointerEventFromTouch(event, *aEvent, *touch);
CheckPointerCaptureState(&event);
}
}
/* static */
void PointerEventHandler::CheckPointerCaptureState(WidgetPointerEvent* aEvent) {
// Handle pending pointer capture before any pointer events except
// gotpointercapture / lostpointercapture.
if (!aEvent) {
return;
}
MOZ_ASSERT(aEvent->mClass == ePointerEventClass);
PointerCaptureInfo* captureInfo = GetPointerCaptureInfo(aEvent->pointerId);
// When fingerprinting resistance is enabled, we need to map other pointer
// ids into the spoofed one. We don't have to do the mapping if the capture
// info exists for the non-spoofed pointer id because of we won't allow
// content to set pointer capture other than the spoofed one. Thus, it must be
// from chrome if the capture info exists in this case. And we don't have to
// do anything if the pointer id is the same as the spoofed one.
if (nsContentUtils::ShouldResistFingerprinting("Efficiency Check",
RFPTarget::PointerEvents) &&
aEvent->pointerId != (uint32_t)GetSpoofedPointerIdForRFP() &&
!captureInfo) {
PointerCaptureInfo* spoofedCaptureInfo =
GetPointerCaptureInfo(GetSpoofedPointerIdForRFP());
// We need to check the target element's document should resist
// fingerprinting. If not, we don't need to send a capture event
// since the capture info of the original pointer id doesn't exist
// in this case.
if (!spoofedCaptureInfo || !spoofedCaptureInfo->mPendingElement ||
!spoofedCaptureInfo->mPendingElement->OwnerDoc()
->ShouldResistFingerprinting(RFPTarget::PointerEvents)) {
return;
}
captureInfo = spoofedCaptureInfo;
}
if (!captureInfo ||
captureInfo->mPendingElement == captureInfo->mOverrideElement) {
return;
}
RefPtr<Element> overrideElement = captureInfo->mOverrideElement;
RefPtr<Element> pendingElement = captureInfo->mPendingElement;
// Update captureInfo before dispatching event since sPointerCaptureList may
// be changed in the pointer event listener.
captureInfo->mOverrideElement = captureInfo->mPendingElement;
if (captureInfo->Empty()) {
sPointerCaptureList->Remove(aEvent->pointerId);
}
if (overrideElement) {
DispatchGotOrLostPointerCaptureEvent(/* aIsGotCapture */ false, aEvent,
overrideElement);
}
if (pendingElement) {
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 */
void PointerEventHandler::ImplicitlyCapturePointer(nsIFrame* aFrame,
WidgetEvent* aEvent) {
MOZ_ASSERT(aEvent->mMessage == ePointerDown);
if (!aFrame || !IsPointerEventImplicitCaptureForTouchEnabled()) {
return;
}
WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent();
NS_WARNING_ASSERTION(pointerEvent,
"Call ImplicitlyCapturePointer with non-pointer event");
if (!pointerEvent->mFromTouchEvent) {
// We only implicitly capture the pointer for touch device.
return;
}
nsCOMPtr<nsIContent> target;
aFrame->GetContentForEvent(aEvent, getter_AddRefs(target));
while (target && !target->IsElement()) {
target = target->GetParent();
}
if (NS_WARN_IF(!target)) {
return;
}
RequestPointerCaptureById(pointerEvent->pointerId, target->AsElement());
}
/* static */
void PointerEventHandler::ImplicitlyReleasePointerCapture(WidgetEvent* aEvent) {
MOZ_ASSERT(aEvent);
if (aEvent->mMessage != ePointerUp && aEvent->mMessage != ePointerCancel) {
return;
}
WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent();
ReleasePointerCaptureById(pointerEvent->pointerId);
CheckPointerCaptureState(pointerEvent);
}
/* static */
void PointerEventHandler::MaybeImplicitlyReleasePointerCapture(
WidgetGUIEvent* aEvent) {
MOZ_ASSERT(aEvent);
const EventMessage pointerEventMessage =
PointerEventHandler::ToPointerEventMessage(aEvent);
if (pointerEventMessage != ePointerUp &&
pointerEventMessage != ePointerCancel) {
return;
}
PointerEventHandler::MaybeProcessPointerCapture(aEvent);
}
/* static */
Element* PointerEventHandler::GetPointerCapturingElement(uint32_t aPointerId) {
PointerCaptureInfo* pointerCaptureInfo = GetPointerCaptureInfo(aPointerId);
if (pointerCaptureInfo) {
return pointerCaptureInfo->mOverrideElement;
}
return nullptr;
}
/* static */
Element* PointerEventHandler::GetPointerCapturingElement(
WidgetGUIEvent* aEvent) {
if ((aEvent->mClass != ePointerEventClass &&
aEvent->mClass != eMouseEventClass) ||
aEvent->mMessage == ePointerDown || aEvent->mMessage == eMouseDown) {
// Pointer capture should only be applied to all pointer events and mouse
// events except ePointerDown and eMouseDown;
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;
}
return GetPointerCapturingElement(mouseEvent->pointerId);
}
/* static */
void PointerEventHandler::ReleaseIfCaptureByDescendant(nsIContent* aContent) {
// We should check that aChild does not contain pointer capturing elements.
// If it does we should release the pointer capture for the elements.
if (!sPointerCaptureList->IsEmpty()) {
for (const auto& entry : *sPointerCaptureList) {
PointerCaptureInfo* data = entry.GetWeak();
if (data && data->mPendingElement &&
data->mPendingElement->IsInclusiveDescendantOf(aContent)) {
ReleasePointerCaptureById(entry.GetKey());
}
}
}
}
/* static */
void PointerEventHandler::PreHandlePointerEventsPreventDefault(
WidgetPointerEvent* aPointerEvent, WidgetGUIEvent* aMouseOrTouchEvent) {
if (!aPointerEvent->mIsPrimary || aPointerEvent->mMessage == ePointerDown) {
return;
}
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). 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);
aMouseOrTouchEvent->mFlags.mOnlyChromeDispatch = true;
if (aPointerEvent->mMessage == ePointerUp) {
pointerInfo->mPreventMouseEventByContent = false;
}
}
/* static */
void PointerEventHandler::PostHandlePointerEventsPreventDefault(
WidgetPointerEvent* aPointerEvent, WidgetGUIEvent* aMouseOrTouchEvent) {
if (!aPointerEvent->mIsPrimary || aPointerEvent->mMessage != ePointerDown ||
!aPointerEvent->DefaultPreventedByContent()) {
return;
}
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);
aMouseOrTouchEvent->mFlags.mOnlyChromeDispatch = true;
pointerInfo->mPreventMouseEventByContent = true;
}
/* static */
void PointerEventHandler::InitPointerEventFromMouse(
WidgetPointerEvent* aPointerEvent, WidgetMouseEvent* aMouseEvent,
EventMessage aMessage) {
MOZ_ASSERT(aPointerEvent);
MOZ_ASSERT(aMouseEvent);
aPointerEvent->pointerId = aMouseEvent->pointerId;
aPointerEvent->mInputSource = aMouseEvent->mInputSource;
aPointerEvent->mMessage = aMessage;
aPointerEvent->mButton = aMouseEvent->mMessage == eMouseMove
? MouseButton::eNotPressed
: aMouseEvent->mButton;
aPointerEvent->mButtons = aMouseEvent->mButtons;
aPointerEvent->mPressure =
aPointerEvent->mButtons
? aMouseEvent->mPressure ? aMouseEvent->mPressure : 0.5f
: 0.0f;
}
/* static */
void PointerEventHandler::InitPointerEventFromTouch(
WidgetPointerEvent& aPointerEvent, const WidgetTouchEvent& aTouchEvent,
const mozilla::dom::Touch& aTouch) {
// Use mButton/mButtons only when mButton got a value (from pen input)
int16_t button = aTouchEvent.mMessage == eTouchMove ? MouseButton::eNotPressed
: aTouchEvent.mButton != MouseButton::eNotPressed
? aTouchEvent.mButton
: MouseButton::ePrimary;
int16_t buttons = aTouchEvent.mMessage == eTouchEnd
? MouseButtonsFlag::eNoButtons
: aTouchEvent.mButton != MouseButton::eNotPressed
? aTouchEvent.mButtons
: MouseButtonsFlag::ePrimaryFlag;
// XXX: This doesn't support multi pen scenario (bug 1904865)
if (aTouchEvent.mInputSource == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
// Only the first touch would be the primary pointer.
aPointerEvent.mIsPrimary =
aTouchEvent.mMessage == eTouchStart
? !HasActiveTouchPointer()
: GetPointerPrimaryState(aTouch.Identifier());
}
aPointerEvent.pointerId = aTouch.Identifier();
aPointerEvent.mRefPoint = aTouch.mRefPoint;
aPointerEvent.mModifiers = aTouchEvent.mModifiers;
aPointerEvent.mWidth = aTouch.RadiusX(CallerType::System);
aPointerEvent.mHeight = aTouch.RadiusY(CallerType::System);
aPointerEvent.tiltX = aTouch.tiltX;
aPointerEvent.tiltY = aTouch.tiltY;
aPointerEvent.twist = aTouch.twist;
aPointerEvent.mTimeStamp = aTouchEvent.mTimeStamp;
aPointerEvent.mFlags = aTouchEvent.mFlags;
aPointerEvent.mButton = button;
aPointerEvent.mButtons = buttons;
aPointerEvent.mInputSource = aTouchEvent.mInputSource;
aPointerEvent.mFromTouchEvent = true;
aPointerEvent.mPressure = aTouch.mForce;
}
/* static */
EventMessage PointerEventHandler::ToPointerEventMessage(
const WidgetGUIEvent* aMouseOrTouchEvent) {
MOZ_ASSERT(aMouseOrTouchEvent);
switch (aMouseOrTouchEvent->mMessage) {
case eMouseMove:
return ePointerMove;
case eMouseUp:
return aMouseOrTouchEvent->AsMouseEvent()->mButtons ? ePointerMove
: ePointerUp;
case eMouseDown: {
const WidgetMouseEvent* mouseEvent = aMouseOrTouchEvent->AsMouseEvent();
return mouseEvent->mButtons & ~nsContentUtils::GetButtonsFlagForButton(
mouseEvent->mButton)
? ePointerMove
: ePointerDown;
}
case eTouchMove:
return ePointerMove;
case eTouchEnd:
return ePointerUp;
case eTouchStart:
return ePointerDown;
case eTouchCancel:
case eTouchPointerCancel:
return ePointerCancel;
default:
return eVoidEvent;
}
}
/* static */
void PointerEventHandler::DispatchPointerFromMouseOrTouch(
PresShell* aShell, nsIFrame* aEventTargetFrame,
nsIContent* aEventTargetContent, WidgetGUIEvent* aMouseOrTouchEvent,
bool aDontRetargetEvents, nsEventStatus* aStatus,
nsIContent** aMouseOrTouchEventTarget /* = nullptr */) {
MOZ_ASSERT(aEventTargetFrame || aEventTargetContent);
MOZ_ASSERT(aMouseOrTouchEvent);
EventMessage pointerMessage = eVoidEvent;
if (aMouseOrTouchEvent->mClass == eMouseEventClass) {
WidgetMouseEvent* mouseEvent = aMouseOrTouchEvent->AsMouseEvent();
// Don't dispatch pointer events caused by a mouse when simulating touch
// devices in RDM.
Document* doc = aShell->GetDocument();
if (!doc) {
return;
}
BrowsingContext* bc = doc->GetBrowsingContext();
if (bc && bc->TouchEventsOverride() == TouchEventsOverride::Enabled &&
bc->InRDMPane()) {
return;
}
// 1. If it is not mouse then it is likely will come as touch event
// 2. We don't synthesize pointer events for those events that are not
// dispatched to DOM.
if (!mouseEvent->convertToPointer ||
!aMouseOrTouchEvent->IsAllowedToDispatchDOMEvent()) {
return;
}
pointerMessage = PointerEventHandler::ToPointerEventMessage(mouseEvent);
if (pointerMessage == eVoidEvent) {
return;
}
WidgetPointerEvent event(*mouseEvent);
InitPointerEventFromMouse(&event, mouseEvent, pointerMessage);
event.convertToPointer = mouseEvent->convertToPointer = false;
RefPtr<PresShell> shell(aShell);
if (!aEventTargetFrame) {
shell = PresShell::GetShellForEventTarget(nullptr, aEventTargetContent);
if (!shell) {
return;
}
}
PreHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent);
// Dispatch pointer event to the same target which is found by the
// corresponding mouse event.
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
// copy the event
pointerMessage = PointerEventHandler::ToPointerEventMessage(touchEvent);
if (pointerMessage == eVoidEvent) {
return;
}
RefPtr<PresShell> shell(aShell);
for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
Touch* touch = touchEvent->mTouches[i];
if (!TouchManager::ShouldConvertTouchToPointer(touch, touchEvent)) {
continue;
}
WidgetPointerEvent event(touchEvent->IsTrusted(), pointerMessage,
touchEvent->mWidget);
InitPointerEventFromTouch(event, *touchEvent, *touch);
event.convertToPointer = touch->convertToPointer = false;
event.mCoalescedWidgetEvents = touch->mCoalescedWidgetEvents;
if (aMouseOrTouchEvent->mMessage == eTouchStart) {
// We already did hit test for touchstart in PresShell. We should
// dispatch pointerdown to the same target as touchstart.
nsCOMPtr<nsIContent> content =
nsIContent::FromEventTargetOrNull(touch->mTarget);
if (!content) {
continue;
}
nsIFrame* frame = content->GetPrimaryFrame();
shell = PresShell::GetShellForEventTarget(frame, content);
if (!shell) {
continue;
}
PreHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent);
shell->HandleEventWithTarget(&event, frame, content, aStatus, true,
aMouseOrTouchEventTarget);
PostHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent);
} else {
// We didn't hit test for other touch events. Spec doesn't mention that
// all pointer events should be dispatched to the same target as their
// corresponding touch events. Call PresShell::HandleEvent so that we do
// hit test for pointer events.
// FIXME: If aDontRetargetEvents is true and the event is fired on
// different document, we cannot track the pointer event target when
// it's removed from the tree.
PreHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent);
shell->HandleEvent(aEventTargetFrame, &event, aDontRetargetEvents,
aStatus);
PostHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent);
}
}
}
nsUserCharacteristics::StealPointerEvent(aMouseOrTouchEvent);
}
/* static */
void PointerEventHandler::NotifyDestroyPresContext(
nsPresContext* aPresContext) {
// Clean up pointer capture info
for (auto iter = sPointerCaptureList->Iter(); !iter.Done(); iter.Next()) {
PointerCaptureInfo* data = iter.UserData();
MOZ_ASSERT(data, "how could we have a null PointerCaptureInfo here?");
if (data->mPendingElement &&
data->mPendingElement->GetPresContext(Element::eForComposedDoc) ==
aPresContext) {
data->mPendingElement = nullptr;
}
if (data->mOverrideElement &&
data->mOverrideElement->GetPresContext(Element::eForComposedDoc) ==
aPresContext) {
data->mOverrideElement = nullptr;
}
if (data->Empty()) {
iter.Remove();
}
}
// Clean up active pointer info
for (auto iter = sActivePointersIds->Iter(); !iter.Done(); iter.Next()) {
PointerInfo* data = iter.UserData();
MOZ_ASSERT(data, "how could we have a null PointerInfo here?");
if (data->mActiveDocument &&
data->mActiveDocument->GetPresContext() == aPresContext) {
iter.Remove();
}
}
}
bool PointerEventHandler::IsDragAndDropEnabled(WidgetMouseEvent& aEvent) {
#ifdef XP_WIN
if (StaticPrefs::dom_w3c_pointer_events_dispatch_by_pointer_messages()) {
// WM_POINTER does not support drag and drop, see bug 1692277
return (aEvent.mInputSource != dom::MouseEvent_Binding::MOZ_SOURCE_PEN &&
aEvent.mReason != WidgetMouseEvent::eSynthesized); // bug 1692151
}
#endif
return true;
}
/* static */
uint16_t PointerEventHandler::GetPointerType(uint32_t aPointerId) {
PointerInfo* pointerInfo = nullptr;
if (sActivePointersIds->Get(aPointerId, &pointerInfo) && pointerInfo) {
return pointerInfo->mPointerType;
}
return MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
}
/* static */
bool PointerEventHandler::GetPointerPrimaryState(uint32_t aPointerId) {
PointerInfo* pointerInfo = nullptr;
if (sActivePointersIds->Get(aPointerId, &pointerInfo) && pointerInfo) {
return pointerInfo->mPrimaryState;
}
return false;
}
/* static */
bool PointerEventHandler::HasActiveTouchPointer() {
for (auto iter = sActivePointersIds->ConstIter(); !iter.Done(); iter.Next()) {
if (iter.Data()->mFromTouchEvent) {
return true;
}
}
return false;
}
/* static */
void PointerEventHandler::DispatchGotOrLostPointerCaptureEvent(
bool aIsGotCapture, const WidgetPointerEvent* aPointerEvent,
Element* aCaptureTarget) {
Document* targetDoc = aCaptureTarget->OwnerDoc();
RefPtr<PresShell> presShell = targetDoc->GetPresShell();
if (NS_WARN_IF(!presShell || presShell->IsDestroying())) {
return;
}
if (!aIsGotCapture && !aCaptureTarget->IsInComposedDoc()) {
// If the capturing element was removed from the DOM tree, fire
// ePointerLostCapture at the document.
PointerEventInit init;
init.mPointerId = aPointerEvent->pointerId;
init.mBubbles = true;
init.mComposed = true;
ConvertPointerTypeToString(aPointerEvent->mInputSource, init.mPointerType);
init.mIsPrimary = aPointerEvent->mIsPrimary;
RefPtr<PointerEvent> event;
event = PointerEvent::Constructor(aCaptureTarget, u"lostpointercapture"_ns,
init);
targetDoc->DispatchEvent(*event);
return;
}
nsEventStatus status = nsEventStatus_eIgnore;
WidgetPointerEvent localEvent(
aPointerEvent->IsTrusted(),
aIsGotCapture ? ePointerGotCapture : ePointerLostCapture,
aPointerEvent->mWidget);
localEvent.AssignPointerEventData(*aPointerEvent, true);
DebugOnly<nsresult> rv = presShell->HandleEventWithTarget(
&localEvent, aCaptureTarget->GetPrimaryFrame(), aCaptureTarget, &status);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"DispatchGotOrLostPointerCaptureEvent failed");
}
/* static */
void PointerEventHandler::MaybeCacheSpoofedPointerID(uint16_t aInputSource,
uint32_t aPointerId) {
if (sSpoofedPointerId.isSome() || aInputSource != SPOOFED_POINTER_INTERFACE) {
return;
}
sSpoofedPointerId.emplace(aPointerId);
}
} // namespace mozilla