diff options
Diffstat (limited to 'dom/events/PointerEventHandler.cpp')
-rw-r--r-- | dom/events/PointerEventHandler.cpp | 784 |
1 files changed, 784 insertions, 0 deletions
diff --git a/dom/events/PointerEventHandler.cpp b/dom/events/PointerEventHandler.cpp new file mode 100644 index 0000000000..98e2ba2382 --- /dev/null +++ b/dom/events/PointerEventHandler.cpp @@ -0,0 +1,784 @@ +/* -*- 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 "nsIFrame.h" +#include "PointerEvent.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/MouseEventBinding.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 nsDataHashtable<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 nsDataHashtable<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_enabled() && + StaticPrefs::dom_w3c_pointer_events_implicit_capture(); +} + +/* static */ +void PointerEventHandler::UpdateActivePointerState(WidgetMouseEvent* aEvent, + nsIContent* aTargetContent) { + if (!StaticPrefs::dom_w3c_pointer_events_enabled() || !aEvent) { + return; + } + switch (aEvent->mMessage) { + case eMouseEnterIntoWidget: + // In this case we have to know information about available mouse pointers + sActivePointersIds->Put( + aEvent->pointerId, + new PointerInfo(false, aEvent->mInputSource, true, 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->Put( + pointerEvent->pointerId, + new PointerInfo( + true, pointerEvent->mInputSource, pointerEvent->mIsPrimary, + 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->Put( + pointerEvent->pointerId, + new PointerInfo(false, pointerEvent->mInputSource, + pointerEvent->mIsPrimary, 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); + PointerCaptureInfo* pointerCaptureInfo = GetPointerCaptureInfo(aPointerId); + if (pointerCaptureInfo) { + pointerCaptureInfo->mPendingElement = aElement; + } else { + sPointerCaptureList->Put(aPointerId, new 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 (auto iter = sPointerCaptureList->Iter(); !iter.Done(); iter.Next()) { + PointerCaptureInfo* data = iter.UserData(); + if (data && data->mPendingElement) { + ReleasePointerCaptureById(iter.Key()); + } + } +} + +/* static */ +bool PointerEventHandler::SetPointerCaptureRemoteTarget( + uint32_t aPointerId, dom::BrowserParent* aBrowserParent) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(sPointerCaptureRemoteTargetTable); + MOZ_ASSERT(aBrowserParent); + + if (BrowserParent::GetPointerLockedRemoteTarget()) { + return false; + } + + BrowserParent* currentRemoteTarget = + PointerEventHandler::GetPointerCapturingRemoteTarget(aPointerId); + if (currentRemoteTarget && currentRemoteTarget != aBrowserParent) { + return false; + } + + sPointerCaptureRemoteTargetTable->Put(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, i == 0); + CheckPointerCaptureState(&event); + } +} + +/* static */ +void PointerEventHandler::CheckPointerCaptureState(WidgetPointerEvent* aEvent) { + // Handle pending pointer capture before any pointer events except + // gotpointercapture / lostpointercapture. + if (!aEvent) { + return; + } + MOZ_ASSERT(StaticPrefs::dom_w3c_pointer_events_enabled()); + 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() && + aEvent->pointerId != (uint32_t)GetSpoofedPointerIdForRFP() && + !captureInfo) { + PointerCaptureInfo* spoofedCaptureInfo = + GetPointerCaptureInfo(GetSpoofedPointerIdForRFP()); + + // We need to check the target element is content or chrome. If it is chrome + // we don't need to send a capture event since the capture info of the + // original pointer id doesn't exist in the case. + if (!spoofedCaptureInfo || + (spoofedCaptureInfo->mPendingElement && + spoofedCaptureInfo->mPendingElement->IsInChromeDocument())) { + 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); + } +} + +/* static */ +void PointerEventHandler::ImplicitlyCapturePointer(nsIFrame* aFrame, + WidgetEvent* aEvent) { + MOZ_ASSERT(aEvent->mMessage == ePointerDown); + if (!aFrame || !StaticPrefs::dom_w3c_pointer_events_enabled() || + !IsPointerEventImplicitCaptureForTouchEnabled()) { + return; + } + WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent(); + NS_WARNING_ASSERTION(pointerEvent, + "Call ImplicitlyCapturePointer with non-pointer event"); + if (pointerEvent->mInputSource != MouseEvent_Binding::MOZ_SOURCE_TOUCH) { + // 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 */ +Element* PointerEventHandler::GetPointerCapturingElement(uint32_t aPointerId) { + PointerCaptureInfo* pointerCaptureInfo = GetPointerCaptureInfo(aPointerId); + if (pointerCaptureInfo) { + return pointerCaptureInfo->mOverrideElement; + } + return nullptr; +} + +/* static */ +Element* PointerEventHandler::GetPointerCapturingElement( + WidgetGUIEvent* aEvent) { + if (!StaticPrefs::dom_w3c_pointer_events_enabled() || + (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; + } + + 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. + for (auto iter = sPointerCaptureList->Iter(); !iter.Done(); iter.Next()) { + PointerCaptureInfo* data = iter.UserData(); + if (data && data->mPendingElement && + data->mPendingElement->IsInclusiveDescendantOf(aContent)) { + ReleasePointerCaptureById(iter.Key()); + } + } +} + +/* 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 + // or change preference 'dom.w3c_pointer_events.enabled' from off to on). + // In these cases, we could ignore them because they are not the events + // between a DefaultPrevented pointerdown and the corresponding pointerup. + return; + } + if (!pointerInfo->mPreventMouseEventByContent) { + return; + } + aMouseOrTouchEvent->PreventDefault(false); + 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, WidgetTouchEvent* aTouchEvent, + mozilla::dom::Touch* aTouch, bool aIsPrimary) { + MOZ_ASSERT(aPointerEvent); + MOZ_ASSERT(aTouchEvent); + + int16_t button = aTouchEvent->mMessage == eTouchMove + ? MouseButton::eNotPressed + : MouseButton::ePrimary; + + int16_t buttons = aTouchEvent->mMessage == eTouchEnd + ? MouseButtonsFlag::eNoButtons + : MouseButtonsFlag::ePrimaryFlag; + + aPointerEvent->mIsPrimary = aIsPrimary; + 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->mTime = aTouchEvent->mTime; + aPointerEvent->mTimeStamp = aTouchEvent->mTimeStamp; + aPointerEvent->mFlags = aTouchEvent->mFlags; + aPointerEvent->mButton = button; + aPointerEvent->mButtons = buttons; + aPointerEvent->mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH; +} + +/* static */ +void PointerEventHandler::DispatchPointerFromMouseOrTouch( + PresShell* aShell, nsIFrame* aFrame, nsIContent* aContent, + WidgetGUIEvent* aEvent, bool aDontRetargetEvents, nsEventStatus* aStatus, + nsIContent** aTargetContent) { + MOZ_ASSERT(StaticPrefs::dom_w3c_pointer_events_enabled()); + MOZ_ASSERT(aFrame || aContent); + MOZ_ASSERT(aEvent); + + EventMessage pointerMessage = eVoidEvent; + if (aEvent->mClass == eMouseEventClass) { + WidgetMouseEvent* mouseEvent = aEvent->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 || + !aEvent->IsAllowedToDispatchDOMEvent()) { + return; + } + + switch (mouseEvent->mMessage) { + case eMouseMove: + pointerMessage = ePointerMove; + break; + case eMouseUp: + pointerMessage = mouseEvent->mButtons ? ePointerMove : ePointerUp; + break; + case eMouseDown: + pointerMessage = + mouseEvent->mButtons & ~nsContentUtils::GetButtonsFlagForButton( + mouseEvent->mButton) + ? ePointerMove + : ePointerDown; + break; + default: + return; + } + + WidgetPointerEvent event(*mouseEvent); + InitPointerEventFromMouse(&event, mouseEvent, pointerMessage); + event.convertToPointer = mouseEvent->convertToPointer = false; + RefPtr<PresShell> shell(aShell); + if (!aFrame) { + shell = PresShell::GetShellForEventTarget(nullptr, aContent); + if (!shell) { + return; + } + } + PreHandlePointerEventsPreventDefault(&event, aEvent); + // Dispatch pointer event to the same target which is found by the + // corresponding mouse event. + shell->HandleEventWithTarget(&event, aFrame, aContent, aStatus, true, + aTargetContent); + PostHandlePointerEventsPreventDefault(&event, aEvent); + } else if (aEvent->mClass == eTouchEventClass) { + WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); + // loop over all touches and dispatch pointer events on each touch + // copy the event + switch (touchEvent->mMessage) { + case eTouchMove: + pointerMessage = ePointerMove; + break; + case eTouchEnd: + pointerMessage = ePointerUp; + break; + case eTouchStart: + pointerMessage = ePointerDown; + break; + case eTouchCancel: + case eTouchPointerCancel: + pointerMessage = ePointerCancel; + break; + default: + 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, i == 0); + event.convertToPointer = touch->convertToPointer = false; + if (aEvent->mMessage == eTouchStart) { + // We already did hit test for touchstart in PresShell. We should + // dispatch pointerdown to the same target as touchstart. + nsCOMPtr<nsIContent> content = do_QueryInterface(touch->mTarget); + if (!content) { + continue; + } + + nsIFrame* frame = content->GetPrimaryFrame(); + shell = PresShell::GetShellForEventTarget(frame, content); + if (!shell) { + continue; + } + + PreHandlePointerEventsPreventDefault(&event, aEvent); + shell->HandleEventWithTarget(&event, frame, content, aStatus, true, + nullptr); + PostHandlePointerEventsPreventDefault(&event, aEvent); + } 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. + PreHandlePointerEventsPreventDefault(&event, aEvent); + shell->HandleEvent(aFrame, &event, aDontRetargetEvents, aStatus); + PostHandlePointerEventsPreventDefault(&event, aEvent); + } + } + } +} + +/* 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(); + } + } +} + +/* 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 */ +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 |