/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace mozilla { // MouseLocation logs the mouse location and when/where enqueued synthesized // mouse move is flushed. If you don't need all mouse location recording at // eMouseMove, you can use MouseLocation:3,sync. Then, it's logged once per // 50 times. Otherwise, if you need to log all eMouseMove locations, you can // use MouseLocation:5,sync. // Note that this is actually available only on debug builds for saving the // runtime cost on opt builds.
LazyLogModule gLogMouseLocation("MouseLocation"); // PointerLocation logs all pointer locations and when/where enqueued // synthesized pointer move is flushed. If you don't need all pointer location // recording at ePointerMove, you can use PointerLocation:3,sync. Then, it's // logged once per 50 times. Otherwise, if you need to log all ePointerMove // locations, you can use PointerLocation:5,sync. // Note that this is actually available only on debug builds for saving the // runtime cost on opt builds.
LazyLogModule gLogPointerLocation("PointerLocation"); // Log the updates of sActivePointersIds.
LazyLogModule gLogActivePointers("ActivePointers");
// 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;
// Keep the capturing element at dispatching the last pointer up event to // consider the following click, auxclick or contextmenu event target. static StaticRefPtr<nsIWeakReference>
sPointerCapturingElementAtLastPointerUpEvent;
/* 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 */ bool PointerEventHandler::ShouldDispatchClickEventOnCapturingElement( const WidgetGUIEvent* aSourceEvent /* = nullptr */) { if (!StaticPrefs::
dom_w3c_pointer_events_dispatch_click_on_pointer_capturing_element()) { returnfalse;
} if (!aSourceEvent ||
!StaticPrefs::
dom_w3c_pointer_events_dispatch_click_on_pointer_capturing_element_except_touch()) { return true;
}
MOZ_ASSERT(aSourceEvent->mMessage == eMouseUp ||
aSourceEvent->mMessage == ePointerUp ||
aSourceEvent->mMessage == eTouchEnd); // Pointer Events defines that `click` event's userEvent is the preceding // `pointerup`. However, Chrome does not follow treat it as so when the // `click` is caused by a tap. For the compatibility with Chrome, we should // stop comforming to the spec until Chrome conforms to that. if (aSourceEvent->mClass == eTouchEventClass) { returnfalse;
} const WidgetMouseEvent* const sourceMouseEvent = aSourceEvent->AsMouseEvent(); return sourceMouseEvent &&
sourceMouseEvent->mInputSource != MouseEvent_Binding::MOZ_SOURCE_TOUCH;
}
PointerInfo* pointerInfo = sActivePointersIds->Get(aMouseEvent.pointerId); if (!pointerInfo) { // If there is no pointer info (i.e., no last pointer state too) and the // input device is not stationary or the caller wants to clear the last // state, we need to do nothing. if (!aMouseEvent.InputSourceSupportsHover() ||
aRefPoint == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) { return;
} // If there is no PointerInfo, we need to add an inactive PointeInfo to // store the state.
pointerInfo = InsertOrUpdateActivePointer(
aMouseEvent.pointerId,
MakeUnique<PointerInfo>(
PointerInfo::Active::No, aMouseEvent.mInputSource,
PointerInfo::Primary::Yes,
PointerInfo::FromTouchEvent::No, nullptr, nullptr,
static_cast<PointerInfo::SynthesizeForTests>(
aMouseEvent.mFlags.mIsSynthesizedForTests)),
aMouseEvent.mMessage, __func__)
.get();
} // If the input source is a stationary device and the point is defined, we may // need to dispatch synthesized ePointerMove at the pointer later. So, in // that case, we should store the data. if (aMouseEvent.InputSourceSupportsHover() &&
aRefPoint != nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
pointerInfo->RecordLastState(aRefPoint, aMouseEvent); #ifdef DEBUG if (MOZ_LOG_TEST(gLogPointerLocation, LogLevel::Info)) { static uint32_t sFrequentMessageCount = 0; constbool isFrequentMessage = aMouseEvent.mMessage == ePointerMove; if (!isFrequentMessage ||
MOZ_LOG_TEST(gLogPointerLocation, LogLevel::Verbose) ||
!(sFrequentMessageCount % 50)) {
MOZ_LOG(
gLogPointerLocation,
isFrequentMessage ? LogLevel::Debug : LogLevel::Info,
("got %s on widget:%p at {%d, %d} (pointerId=%u, source=%s)\n",
ToChar(aMouseEvent.mMessage), aMouseEvent.mWidget.get(),
sLastMouseInfo->mLastRefPointInRootDoc.x,
sLastMouseInfo->mLastRefPointInRootDoc.y, aMouseEvent.pointerId,
InputSourceToString(aMouseEvent.mInputSource).get()));
} if (isFrequentMessage) {
sFrequentMessageCount++;
} else { // Let's log the next ePointerMove after the other messages.
sFrequentMessageCount = 0;
}
} #endif// #ifdef DEBUG
} // Otherwise, i.e., if it's not a stationary device or the caller wants to // forget the point, we should clear the last position to abort to synthesize // ePointerMove. else {
pointerInfo->ClearLastState();
MOZ_LOG_DEBUG_ONLY(
gLogPointerLocation, LogLevel::Info,
("got %s on widget:%p, pointer location is cleared (pointerId=%u, " "source=%s)\n",
ToChar(aMouseEvent.mMessage), aMouseEvent.mWidget.get(),
aMouseEvent.pointerId,
InputSourceToString(aMouseEvent.mInputSource).get()));
}
}
/* static */ void PointerEventHandler::RecordMouseState(
PresShell& aRootPresShell, const WidgetMouseEvent& aMouseEvent) {
MOZ_ASSERT(aRootPresShell.IsRoot()); if (!sLastMouseInfo) {
sLastMouseInfo = new PointerInfo();
}
sLastMousePresShell = do_GetWeakReference(&aRootPresShell);
sLastMouseWidget = do_GetWeakReference(aMouseEvent.mWidget.get());
sLastMousePointerId = Some(aMouseEvent.pointerId);
sLastMouseInfo->mLastRefPointInRootDoc =
aRootPresShell.GetEventLocation(aMouseEvent);
sLastMouseInfo->mLastTargetGuid =
layers::InputAPZContext::GetTargetLayerGuid(); // FIXME: Don't trust the synthesized for tests flag of drag events. if (aMouseEvent.mClass != eDragEventClass) {
sLastMouseInfo->mInputSource = aMouseEvent.mInputSource;
sLastMouseInfo->mIsSynthesizedForTests =
aMouseEvent.mFlags.mIsSynthesizedForTests;
} #ifdef DEBUG if (MOZ_LOG_TEST(gLogMouseLocation, LogLevel::Info)) { static uint32_t sFrequentMessageCount = 0; constbool isFrequentMessage =
aMouseEvent.mMessage == eMouseMove || aMouseEvent.mMessage == eDragOver; if (!isFrequentMessage ||
MOZ_LOG_TEST(gLogMouseLocation, LogLevel::Verbose) ||
!(sFrequentMessageCount % 50)) {
MOZ_LOG(
gLogMouseLocation,
isFrequentMessage ? LogLevel::Debug : LogLevel::Info,
("[ps=%p]got %s on widget:%p at {%d, %d} (pointerId=%u, source=%s)\n",
&aRootPresShell, ToChar(aMouseEvent.mMessage),
aMouseEvent.mWidget.get(), sLastMouseInfo->mLastRefPointInRootDoc.x,
sLastMouseInfo->mLastRefPointInRootDoc.y, aMouseEvent.pointerId,
InputSourceToString(aMouseEvent.mInputSource).get()));
} if (isFrequentMessage) {
sFrequentMessageCount++;
} else { // Let's log the next eMouseMove or eDragOver after the other // messages.
sFrequentMessageCount = 0;
}
} #endif// #ifdef DEBUG
}
/* static */ void PointerEventHandler::UpdatePointerActiveState(WidgetMouseEvent* aEvent,
nsIContent* aTargetContent) { if (!aEvent) { return;
} switch (aEvent->mMessage) { case eMouseEnterIntoWidget: { const PointerInfo* const pointerInfo = GetPointerInfo(aEvent->pointerId); if (aEvent->mFlags.mIsSynthesizedForTests) { if (pointerInfo && !pointerInfo->mIsSynthesizedForTests) { // Do not overwrite the PointerInfo which is set by user input with // synthesized pointer move. return;
}
}
// Update sLastPointerId so that the synthesized eMouseMove which // PresShell schedules in response to eMouseEnterIntoWidget can pass the // IsLastPointerId check in EventStateManager::UpdateCursor. Without this, // a fullscreen window swap (or any other widget-level event that fires // eMouseExitFromWidget followed by eMouseEnterIntoWidget while the // pointer hasn't actually moved) clears sLastPointerId and never // restores it, leaving the cursor stuck in whatever state it had before // the swap until the next real pointer event (bug 2031413).
UpdateLastPointerId(aEvent->pointerId, aEvent->mMessage);
// In this case we have to know information about available mouse pointers
InsertOrUpdateActivePointer(
aEvent->pointerId,
MakeUnique<PointerInfo>(PointerInfo::Active::No, aEvent->mInputSource,
PointerInfo::Primary::Yes,
PointerInfo::FromTouchEvent::No, nullptr,
pointerInfo,
static_cast<PointerInfo::SynthesizeForTests>(
aEvent->mFlags.mIsSynthesizedForTests)),
aEvent->mMessage, __func__);
MaybeCacheSpoofedPointerID(aEvent->mInputSource, aEvent->pointerId); break;
} case ePointerMove: { if (aEvent->IsReal()) {
UpdateLastPointerId(aEvent->pointerId, aEvent->mMessage);
} // If the event is a synthesized mouse event, we should register the // pointerId for the test if the pointer is not there. if (!aEvent->mFlags.mIsSynthesizedForTests ||
aEvent->mInputSource != MouseEvent_Binding::MOZ_SOURCE_MOUSE) { return;
} const PointerInfo* const pointerInfo = GetPointerInfo(aEvent->pointerId); if (pointerInfo) { return;
}
InsertOrUpdateActivePointer(
aEvent->pointerId,
MakeUnique<PointerInfo>(
PointerInfo::Active::No, MouseEvent_Binding::MOZ_SOURCE_MOUSE,
PointerInfo::Primary::Yes, PointerInfo::FromTouchEvent::No,
nullptr, pointerInfo, PointerInfo::SynthesizeForTests::Yes),
aEvent->mMessage, __func__); return;
} case ePointerDown:
UpdateLastPointerId(aEvent->pointerId, aEvent->mMessage);
sPointerCapturingElementAtLastPointerUpEvent = nullptr; // 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.
InsertOrUpdateActivePointer(
pointerEvent->pointerId,
MakeUnique<PointerInfo>(
PointerInfo::Active::Yes, *pointerEvent,
aTargetContent ? aTargetContent->OwnerDoc() : nullptr,
GetPointerInfo(aEvent->pointerId)),
pointerEvent->mMessage, __func__);
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) {
UpdateLastPointerId(aEvent->pointerId, aEvent->mMessage);
InsertOrUpdateActivePointer(
pointerEvent->pointerId,
MakeUnique<PointerInfo>(PointerInfo::Active::No, *pointerEvent,
nullptr,
GetPointerInfo(aEvent->pointerId)),
pointerEvent->mMessage, __func__);
} else {
MaybeForgetLastPointerId(aEvent->pointerId, aEvent->mMessage); // XXX If the PointerInfo is registered with same pointerId as actual // pointer and the event is synthesized for tests, we unregister the // pointer unexpectedly here. However, it should be rare and // currently, we use only pointerId for the key. Therefore, we cannot // do nothing without changing the key.
RemoveActivePointer(aEvent->pointerId, aEvent->mMessage, __func__);
}
} break; case eMouseExitFromWidget: if (aEvent->mFlags.mIsSynthesizedForTests) { const PointerInfo* const pointerInfo =
GetPointerInfo(aEvent->pointerId); if (pointerInfo && !pointerInfo->mIsSynthesizedForTests) { // Do not remove the PointerInfo which is set by user input with // synthesized pointer move. return;
}
}
MaybeForgetLastPointerId(aEvent->pointerId, aEvent->mMessage); // In this case we have to remove information about disappeared mouse // pointers
RemoveActivePointer(aEvent->pointerId, aEvent->mMessage, __func__); break; default:
MOZ_ASSERT_UNREACHABLE("event has invalid type"); break;
}
}
/* static */
Maybe<uint32_t> PointerEventHandler::TryClaimOrphanedLastMouseInfo(
PresShell& aRootPresShell) {
MOZ_ASSERT(aRootPresShell.IsRoot()); if (!sLastMouseInfo || !sLastMouseInfo->HasLastState() ||
!sLastMousePointerId) { return Nothing();
} // If another live PresShell still owns the state, leave it alone. if (sLastMousePresShell) { const RefPtr<PresShell> previousOwner =
do_QueryReferent(sLastMousePresShell); if (previousOwner) { return Nothing();
}
} // The previous owner has been torn down. PresShell::IsRoot() only means // root of the in-process presContext tree, so the previous owner could // have been on a different top-level window than aRootPresShell. Only // claim the state if the widget that recorded it is the same as // aRootPresShell's widget; otherwise the cached coordinates would land // in the wrong window's root-frame space. if (!sLastMouseWidget) { return Nothing();
} const nsCOMPtr<nsIWidget> previousWidget = do_QueryReferent(sLastMouseWidget); if (!previousWidget || previousWidget != aRootPresShell.GetOwnWidget()) { return Nothing();
} // Rebind ownership to the new PresShell so subsequent calls to // GetLastMouseInfo(this) work.
sLastMousePresShell = do_GetWeakReference(&aRootPresShell); return sLastMousePointerId;
}
// 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);
captureInfo = nullptr;
}
if (overrideElement) {
DispatchGotOrLostPointerCaptureEvent(/* aIsGotCapture */ false, aEvent,
overrideElement); // A `lostpointercapture` event listener may have removed the new pointer // capture element from the tree. Then, we shouldn't dispatch // `gotpointercapture` on the node. if (pendingElement && !pendingElement->IsInComposedDoc()) { // We won't dispatch `gotpointercapture`, so, we should never fire // `lostpointercapture` on it at processing the next pending pointer // capture. if ((captureInfo = GetPointerCaptureInfo(aEvent->pointerId)) &&
captureInfo->mOverrideElement == pendingElement) {
captureInfo->mOverrideElement = nullptr; if (captureInfo->Empty()) {
sPointerCaptureList->Remove(aEvent->pointerId);
captureInfo = nullptr;
}
}
pendingElement = nullptr;
} else {
captureInfo = nullptr; // Maybe destroyed
}
} if (pendingElement) {
DispatchGotOrLostPointerCaptureEvent(/* aIsGotCapture */ true, aEvent,
pendingElement);
captureInfo = nullptr; // Maybe destroyed
}
// 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;
}
}
// 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.
widget->DispatchEvent(&event);
}
/* static */ void PointerEventHandler::ImplicitlyCapturePointer(nsIFrame* aFrame, const WidgetEvent& aEvent) {
MOZ_ASSERT(aEvent.mMessage == ePointerDown); if (!aFrame || !IsPointerEventImplicitCaptureForTouchEnabled()) { return;
} const 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;
}
nsIContent* target = aFrame->GetEventTargetContent(aEvent); if (NS_WARN_IF(!target) || NS_WARN_IF(!target->IsElement())) { return;
}
RequestPointerCaptureById(pointerEvent->pointerId, target->AsElement());
}
/* static */
Element* PointerEventHandler::GetPointerCapturingElementInternal(
CapturingState aCapturingState, const 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;
}
/* static */ void PointerEventHandler::ReleaseIfCaptureByDescendant(nsIContent* aContent) {
MOZ_ASSERT(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() && aContent->IsElement()) { for (constauto& 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;
}
}
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. However, PreShell can // be destroyed during handling of the pointerdown event, which causes the // PointerInfo to be removed.
MOZ_ASSERT(aPresShell->IsDestroying(), "If we got ePointerDown w/o active pointer info, the PresShell " "should be destroying!!"); return;
} // PreventDefault only applied for active pointers. if (!pointerInfo->mIsActive) { return;
}
aMouseOrTouchEvent->PreventDefault(false);
aMouseOrTouchEvent->mFlags.mOnlyChromeDispatch = true;
pointerInfo->mPreventMouseEventByContent = true;
}
// If the event is not a gotpointercapture, lostpointercapture, click, // auxclick or contextmenu event, run the process pending pointer capture // steps for this PointerEvent. switch (aPointerEvent.mMessage) { case ePointerGotCapture: case ePointerLostCapture: case ePointerClick: case ePointerAuxClick: case eContextMenu: break; default: {
Maybe<AutoConnectedAncestorTracker> trackTargetContent; if (targetContent->IsInComposedDoc()) {
trackTargetContent.emplace(*targetContent);
}
CheckPointerCaptureState(&aPointerEvent); // If the event target was disconnected from the document, we should use // its connected ancestor as the target of aPointerEvent. if (trackTargetContent && trackTargetContent->ContentWasRemoved()) {
MOZ_ASSERT(!targetWeakFrame);
targetContent = trackTargetContent->GetConnectedContent(); if (NS_WARN_IF(!targetContent)) {
targetWeakFrame = nullptr; // FIXME: If we lost the target content with dispatching // ePointerGotCapture or ePointerLostCapture event, we may need to // retarget the pointer event to the Document. return NS_ERROR_FAILURE;
}
} break;
}
}
// The active document of the pointerId and pointer capture for the pointerId // should be handled by EventStateManager::PreHandleEvent() immediately before // dispatching the event to the DOM.
// Pointer boundary events should be handled by // EventStateManager::PreHandleEvent() too.
nsWeakPtr pointerCapturingElementWeak =
do_GetWeakReference(aPointerCapturingElement);
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->Top()->InRDMPane()) { return;
}
// If it is not mouse then it is likely will come as touch event. if (!mouseEvent->convertToPointer) { return;
}
// Normal synthesized mouse move events are marked as "not convert to // pointer" by PresShell::ProcessSynthMouseOrPointerMoveEvent(). However: // 1. if the event is synthesized via nsIDOMWindowUtils, it's not marked as // so because there is no synthesized pointer move dispatcher. So, we need // to dispatch synthesized pointer move from here. This path may be used by // mochitests which check the synthesized mouse/pointer boundary event // behavior. // 2. if the event comes from another process and our content will be moved // underneath the mouse cursor. In this case, we should handle preceding // ePointerMove. // FIXME: In the latter case, we may need to synthesize ePointerMove for the // other pointers too. if (mouseEvent->IsSynthesized()) { if (!StaticPrefs::
dom_event_pointer_boundary_dispatch_when_layout_change() ||
!mouseEvent->InputSourceSupportsHover()) { return;
} // So, if the pointer is captured, we don't need to dispatch pointer // boundary events since pointer boundary events should be fired before // gotpointercapture.
PointerCaptureInfo* const captureInfo =
GetPointerCaptureInfo(mouseEvent->pointerId); if (captureInfo && captureInfo->mOverrideElement) { return;
}
}
pointerMessage = PointerEventHandler::ToPointerEventMessage(mouseEvent); if (pointerMessage == eVoidEvent) { return;
} #ifdef DEBUG if (pointerMessage == ePointerRawUpdate) { const nsIContent* const targetContent =
aEventTargetContent ? aEventTargetContent
: aEventTargetFrame->GetContent();
NS_ASSERTION(targetContent, "Where do we want to try to dispatch?"); if (targetContent) {
NS_ASSERTION(
targetContent->IsInComposedDoc(),
nsPrintfCString("Do we want to dispatch ePointerRawUpdate onto " "disconnected content? (targetContent=%s)",
ToString(*targetContent).c_str())
.get()); if (!NeedToDispatchPointerRawUpdate(targetContent->OwnerDoc())) {
NS_ASSERTION( false,
nsPrintfCString( "Did we fail to retarget the document? (targetContent=%s)",
ToString(*targetContent).c_str())
.get());
}
}
} #endif// #ifdef DEBUG
WidgetPointerEvent event =
WidgetPointerEvent::MakeCopyFromMouseEvent(*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(shell, &event, aMouseOrTouchEvent); // If pointer capture is released, we need to synthesize eMouseMove to // dispatch mouse boundary events later.
mouseEvent->mSynthesizeMoveAfterDispatch |=
event.mSynthesizeMoveAfterDispatch;
} elseif (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;
} // If the touch is a single tap release, we will dispatch click or auxclick // event later unless it's suppressed. The event target should be the // pointer capturing element right now, i.e., at dispatching ePointerUp. // Although we cannot know whether the touch is a single tap here, we should // store the last touch pointer capturing element. If this is not a single // tap end, the stored element will be ignored due to not dispatching click // nor auxclick. if (touchEvent->mMessage == eTouchEnd &&
touchEvent->mTouches.Length() == 1) {
MOZ_ASSERT(!pointerCapturingElementWeak);
pointerCapturingElementWeak = do_GetWeakReference(
GetPointerCapturingElement(touchEvent->mTouches[0]->Identifier()));
}
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;
}
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;
}
PreHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent);
shell->HandleEventWithTarget(&event, frame, content, aStatus, true,
aMouseOrTouchEventTarget);
PostHandlePointerEventsPreventDefault(shell, &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 false 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(shell, &event,
aMouseOrTouchEvent);
}
}
} // If we dispatched an ePointerUp event while an element capturing the // pointer, we should keep storing it to consider click, auxclick and // contextmenu event target later. if (!aShell->IsDestroying() && pointerMessage == ePointerUp &&
pointerCapturingElementWeak) {
SetPointerCapturingElementAtLastPointerUp(
std::move(pointerCapturingElementWeak));
}
}
/* 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();
}
} if (const RefPtr<Element> capturingElementAtLastPointerUp =
GetPointerCapturingElementAtLastPointerUp()) { // The pointer capturing element may belong to different document from the // destroying nsPresContext. Check whether the composed document's // nsPresContext is the destroying one or not. if (capturingElementAtLastPointerUp->GetPresContext(
Element::eForComposedDoc) == aPresContext) {
ReleasePointerCapturingElementAtLastPointerUp();
}
} // Clean up active pointer info. // XXX: This was added primarily for touch input. Could this cause any // web-compat issue for mouse input in edge cases? 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) { // We shouldn't start a drag session if the event is synthesized one because // aEvent doesn't have enough information for initializing the ePointerCancel. if (aEvent.IsSynthesized()) { returnfalse;
} // And we should not start with raw update events, which should be used only // for notifying web apps of the pointer state changes ASAP. if (aEvent.mMessage == ePointerRawUpdate) { returnfalse;
}
MOZ_ASSERT(aEvent.mMessage != eMouseRawUpdate); #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;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.