gecko-dev/dom/events/PointerEventHandler.cpp

559 строки
19 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 "nsIFrame.h"
#include "PointerEvent.h"
#include "mozilla/PresShell.h"
namespace mozilla {
using namespace dom;
static bool sPointerEventEnabled = true;
static bool sPointerEventImplicitCapture = false;
class PointerInfo final
{
public:
uint16_t mPointerType;
bool mActiveState;
bool mPrimaryState;
bool mPreventMouseEventByContent;
explicit PointerInfo(bool aActiveState, uint16_t aPointerType,
bool aPrimaryState)
: mPointerType(aPointerType)
, mActiveState(aActiveState)
, mPrimaryState(aPrimaryState)
, mPreventMouseEventByContent(false)
{
}
};
// 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;
/* static */ void
PointerEventHandler::Initialize()
{
static bool initialized = false;
if (initialized) {
return;
}
initialized = true;
Preferences::AddBoolVarCache(&sPointerEventEnabled,
"dom.w3c_pointer_events.enabled", true);
Preferences::AddBoolVarCache(&sPointerEventImplicitCapture,
"dom.w3c_pointer_events.implicit_capture", true);
}
/* static */ void
PointerEventHandler::InitializeStatics()
{
MOZ_ASSERT(!sPointerCaptureList, "InitializeStatics called multiple times!");
sPointerCaptureList =
new nsClassHashtable<nsUint32HashKey, PointerCaptureInfo>;
sActivePointersIds = new nsClassHashtable<nsUint32HashKey, PointerInfo>;
}
/* static */ void
PointerEventHandler::ReleaseStatics()
{
MOZ_ASSERT(sPointerCaptureList, "ReleaseStatics called without Initialize!");
delete sPointerCaptureList;
sPointerCaptureList = nullptr;
delete sActivePointersIds;
sActivePointersIds = nullptr;
}
/* static */ bool
PointerEventHandler::IsPointerEventEnabled()
{
return sPointerEventEnabled;
}
/* static */ bool
PointerEventHandler::IsPointerEventImplicitCaptureForTouchEnabled()
{
return sPointerEventEnabled && sPointerEventImplicitCapture;
}
/* static */ void
PointerEventHandler::UpdateActivePointerState(WidgetMouseEvent* aEvent)
{
if (!IsPointerEventEnabled() || !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->inputSource, true));
break;
case ePointerDown:
// In this case we switch pointer to active state
if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) {
sActivePointersIds->Put(pointerEvent->pointerId,
new PointerInfo(true, pointerEvent->inputSource,
pointerEvent->mIsPrimary));
}
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->inputSource != nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
sActivePointersIds->Put(pointerEvent->pointerId,
new PointerInfo(false,
pointerEvent->inputSource,
pointerEvent->mIsPrimary));
} 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:
break;
}
}
/* static */ void
PointerEventHandler::SetPointerCaptureById(uint32_t aPointerId,
nsIContent* aContent)
{
MOZ_ASSERT(aContent);
if (nsIDOMMouseEvent::MOZ_SOURCE_MOUSE == GetPointerType(aPointerId)) {
nsIPresShell::SetCapturingContent(aContent, CAPTURE_PREVENTDRAG);
}
PointerCaptureInfo* pointerCaptureInfo = GetPointerCaptureInfo(aPointerId);
if (pointerCaptureInfo) {
pointerCaptureInfo->mPendingContent = aContent;
} else {
sPointerCaptureList->Put(aPointerId, new PointerCaptureInfo(aContent));
}
}
/* 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 && pointerCaptureInfo->mPendingContent) {
if (nsIDOMMouseEvent::MOZ_SOURCE_MOUSE == GetPointerType(aPointerId)) {
nsIPresShell::SetCapturingContent(nullptr, CAPTURE_PREVENTDRAG);
}
pointerCaptureInfo->mPendingContent = nullptr;
}
}
/* static */ void
PointerEventHandler::ReleaseAllPointerCapture()
{
for (auto iter = sPointerCaptureList->Iter(); !iter.Done(); iter.Next()) {
PointerCaptureInfo* data = iter.UserData();
if (data && data->mPendingContent) {
ReleasePointerCaptureById(iter.Key());
}
}
}
/* static */ bool
PointerEventHandler::GetPointerInfo(uint32_t aPointerId, bool& aActiveState)
{
PointerInfo* pointerInfo = nullptr;
if (sActivePointersIds->Get(aPointerId, &pointerInfo) && pointerInfo) {
aActiveState = pointerInfo->mActiveState;
return true;
}
return false;
}
/* static */ void
PointerEventHandler::CheckPointerCaptureState(WidgetPointerEvent* aEvent)
{
// Handle pending pointer capture before any pointer events except
// gotpointercapture / lostpointercapture.
if (!aEvent) {
return;
}
MOZ_ASSERT(IsPointerEventEnabled());
MOZ_ASSERT(aEvent->mClass == ePointerEventClass);
PointerCaptureInfo* captureInfo = GetPointerCaptureInfo(aEvent->pointerId);
if (captureInfo &&
captureInfo->mPendingContent != captureInfo->mOverrideContent) {
// cache captureInfo->mPendingContent since it may be changed in the pointer
// event listener
nsIContent* pendingContent = captureInfo->mPendingContent.get();
if (captureInfo->mOverrideContent) {
DispatchGotOrLostPointerCaptureEvent(/* aIsGotCapture */ false, aEvent,
captureInfo->mOverrideContent);
}
if (pendingContent) {
DispatchGotOrLostPointerCaptureEvent(/* aIsGotCapture */ true, aEvent,
pendingContent);
}
captureInfo->mOverrideContent = pendingContent;
if (captureInfo->Empty()) {
sPointerCaptureList->Remove(aEvent->pointerId);
}
}
}
/* static */ void
PointerEventHandler::ImplicitlyCapturePointer(nsIFrame* aFrame,
WidgetEvent* aEvent)
{
MOZ_ASSERT(aEvent->mMessage == ePointerDown);
if (!aFrame || !IsPointerEventEnabled() ||
!IsPointerEventImplicitCaptureForTouchEnabled()) {
return;
}
WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent();
NS_WARNING_ASSERTION(pointerEvent,
"Call ImplicitlyCapturePointer with non-pointer event");
if (pointerEvent->inputSource != nsIDOMMouseEvent::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;
}
SetPointerCaptureById(pointerEvent->pointerId, target);
}
/* 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 */ nsIContent*
PointerEventHandler::GetPointerCapturingContent(uint32_t aPointerId)
{
PointerCaptureInfo* pointerCaptureInfo = GetPointerCaptureInfo(aPointerId);
if (pointerCaptureInfo) {
return pointerCaptureInfo->mOverrideContent;
}
return nullptr;
}
/* static */ nsIFrame*
PointerEventHandler::GetPointerCapturingFrame(nsIFrame* aFrameUnderCursor,
WidgetGUIEvent* aEvent)
{
if (!IsPointerEventEnabled() || (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 aFrameUnderCursor;
}
WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
if (!mouseEvent) {
return aFrameUnderCursor;
}
// Find the content which captures the pointer.
nsIContent* capturingContent =
GetPointerCapturingContent(mouseEvent->pointerId);
if (!capturingContent) {
return aFrameUnderCursor;
}
// Return the content's primary frame as the target frame.
nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame();
return capturingFrame ? capturingFrame : aFrameUnderCursor;
}
/* 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->mPendingContent &&
nsContentUtils::ContentIsDescendantOf(data->mPendingContent,
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);
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);
pointerInfo->mPreventMouseEventByContent = true;
}
/* static */ void
PointerEventHandler::DispatchPointerFromMouseOrTouch(
PresShell* aShell,
nsIFrame* aFrame,
WidgetGUIEvent* aEvent,
bool aDontRetargetEvents,
nsEventStatus* aStatus,
nsIContent** aTargetContent)
{
MOZ_ASSERT(IsPointerEventEnabled());
MOZ_ASSERT(aFrame);
MOZ_ASSERT(aEvent);
EventMessage pointerMessage = eVoidEvent;
if (aEvent->mClass == eMouseEventClass) {
WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
// 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;
}
int16_t button = mouseEvent->button;
switch (mouseEvent->mMessage) {
case eMouseMove:
button = WidgetMouseEvent::eNoButton;
pointerMessage = ePointerMove;
break;
case eMouseUp:
pointerMessage = mouseEvent->buttons ? ePointerMove : ePointerUp;
break;
case eMouseDown:
pointerMessage =
mouseEvent->buttons & ~nsContentUtils::GetButtonsFlagForButton(button) ?
ePointerMove : ePointerDown;
break;
default:
return;
}
WidgetPointerEvent event(*mouseEvent);
event.pointerId = mouseEvent->pointerId;
event.inputSource = mouseEvent->inputSource;
event.mMessage = pointerMessage;
event.button = button;
event.buttons = mouseEvent->buttons;
event.pressure = event.buttons ?
mouseEvent->pressure ? mouseEvent->pressure : 0.5f :
0.0f;
event.convertToPointer = mouseEvent->convertToPointer = false;
PreHandlePointerEventsPreventDefault(&event, aEvent);
RefPtr<PresShell> shell(aShell);
shell->HandleEvent(aFrame, &event, aDontRetargetEvents, aStatus,
aTargetContent);
PostHandlePointerEventsPreventDefault(&event, aEvent);
} else if (aEvent->mClass == eTouchEventClass) {
WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
int16_t button = WidgetMouseEvent::eLeftButton;
int16_t buttons = WidgetMouseEvent::eLeftButtonFlag;
// loop over all touches and dispatch pointer events on each touch
// copy the event
switch (touchEvent->mMessage) {
case eTouchMove:
pointerMessage = ePointerMove;
button = WidgetMouseEvent::eNoButton;
break;
case eTouchEnd:
pointerMessage = ePointerUp;
buttons = WidgetMouseEvent::eNoButtonFlag;
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);
event.mIsPrimary = i == 0;
event.pointerId = touch->Identifier();
event.mRefPoint = touch->mRefPoint;
event.mModifiers = touchEvent->mModifiers;
event.mWidth = touch->RadiusX(CallerType::System);
event.mHeight = touch->RadiusY(CallerType::System);
event.tiltX = touch->tiltX;
event.tiltY = touch->tiltY;
event.mTime = touchEvent->mTime;
event.mTimeStamp = touchEvent->mTimeStamp;
event.mFlags = touchEvent->mFlags;
event.button = button;
event.buttons = buttons;
event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
event.convertToPointer = touch->convertToPointer = false;
PreHandlePointerEventsPreventDefault(&event, aEvent);
shell->HandleEvent(aFrame, &event, aDontRetargetEvents, aStatus,
aTargetContent);
PostHandlePointerEventsPreventDefault(&event, aEvent);
}
}
}
/* static */ uint16_t
PointerEventHandler::GetPointerType(uint32_t aPointerId)
{
PointerInfo* pointerInfo = nullptr;
if (sActivePointersIds->Get(aPointerId, &pointerInfo) && pointerInfo) {
return pointerInfo->mPointerType;
}
return nsIDOMMouseEvent::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,
nsIContent* aCaptureTarget)
{
nsIDocument* targetDoc = aCaptureTarget->OwnerDoc();
nsCOMPtr<nsIPresShell> shell = targetDoc->GetShell();
if (NS_WARN_IF(!shell)) {
return;
}
if (!aIsGotCapture && !aCaptureTarget->IsInUncomposedDoc()) {
// 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->inputSource, init.mPointerType);
init.mIsPrimary = aPointerEvent->mIsPrimary;
RefPtr<PointerEvent> event;
event = PointerEvent::Constructor(aCaptureTarget,
NS_LITERAL_STRING("lostpointercapture"),
init);
bool dummy;
targetDoc->DispatchEvent(event->InternalDOMEvent(), &dummy);
return;
}
nsEventStatus status = nsEventStatus_eIgnore;
WidgetPointerEvent localEvent(aPointerEvent->IsTrusted(),
aIsGotCapture ? ePointerGotCapture :
ePointerLostCapture,
aPointerEvent->mWidget);
localEvent.AssignPointerEventData(*aPointerEvent, true);
DebugOnly<nsresult> rv = shell->HandleEventWithTarget(
&localEvent,
aCaptureTarget->GetPrimaryFrame(),
aCaptureTarget, &status);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"DispatchGotOrLostPointerCaptureEvent failed");
}
} // namespace mozilla