gecko-dev/widget/InputData.cpp
Masayuki Nakano 151b00226e 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

957 lines
32 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "InputData.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "mozilla/dom/Touch.h"
#include "mozilla/dom/WheelEventBinding.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/SwipeTracker.h"
#include "mozilla/TextEvents.h"
#include "mozilla/TouchEvents.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsThreadUtils.h"
#include "UnitTransforms.h"
#include <type_traits>
namespace mozilla {
using namespace dom;
template WidgetMouseEvent MouseInput::ToWidgetEvent(nsIWidget* aWidget) const;
template WidgetPointerEvent MouseInput::ToWidgetEvent(nsIWidget* aWidget) const;
InputData::~InputData() = default;
InputData::InputData(InputType aInputType)
: mInputType(aInputType),
mFocusSequenceNumber(0),
mLayersId{0},
modifiers(0) {}
InputData::InputData(InputType aInputType, TimeStamp aTimeStamp,
Modifiers aModifiers)
: mInputType(aInputType),
mTimeStamp(aTimeStamp),
mFocusSequenceNumber(0),
mLayersId{0},
modifiers(aModifiers) {}
SingleTouchData::SingleTouchData(int32_t aIdentifier,
ScreenIntPoint aScreenPoint,
ScreenSize aRadius, float aRotationAngle,
float aForce)
: mIdentifier(aIdentifier),
mScreenPoint(aScreenPoint),
mRadius(aRadius),
mRotationAngle(aRotationAngle),
mForce(aForce) {}
SingleTouchData::SingleTouchData(int32_t aIdentifier,
ParentLayerPoint aLocalScreenPoint,
ScreenSize aRadius, float aRotationAngle,
float aForce)
: mIdentifier(aIdentifier),
mLocalScreenPoint(aLocalScreenPoint),
mRadius(aRadius),
mRotationAngle(aRotationAngle),
mForce(aForce) {}
SingleTouchData::SingleTouchData()
: mIdentifier(0), mRotationAngle(0.0), mForce(0.0) {}
already_AddRefed<Touch> SingleTouchData::ToNewDOMTouch() const {
MOZ_ASSERT(NS_IsMainThread(),
"Can only create dom::Touch instances on main thread");
RefPtr<Touch> touch =
new Touch(mIdentifier,
LayoutDeviceIntPoint::Truncate(mScreenPoint.x, mScreenPoint.y),
LayoutDeviceIntPoint::Truncate(mRadius.width, mRadius.height),
mRotationAngle, mForce);
touch->tiltX = mTiltX;
touch->tiltY = mTiltY;
touch->twist = mTwist;
return touch.forget();
}
MultiTouchInput::MultiTouchInput(MultiTouchType aType, uint32_t aTime,
TimeStamp aTimeStamp, Modifiers aModifiers)
: InputData(MULTITOUCH_INPUT, aTimeStamp, aModifiers),
mType(aType),
mHandledByAPZ(false) {}
MultiTouchInput::MultiTouchInput()
: InputData(MULTITOUCH_INPUT),
mType(MULTITOUCH_START),
mHandledByAPZ(false) {}
MultiTouchInput::MultiTouchInput(const WidgetTouchEvent& aTouchEvent)
: InputData(MULTITOUCH_INPUT, aTouchEvent.mTimeStamp,
aTouchEvent.mModifiers),
mHandledByAPZ(aTouchEvent.mFlags.mHandledByAPZ),
mButton(aTouchEvent.mButton),
mButtons(aTouchEvent.mButtons) {
MOZ_ASSERT(NS_IsMainThread(),
"Can only copy from WidgetTouchEvent on main thread");
switch (aTouchEvent.mMessage) {
case eTouchStart:
mType = MULTITOUCH_START;
break;
case eTouchMove:
mType = MULTITOUCH_MOVE;
break;
case eTouchEnd:
mType = MULTITOUCH_END;
break;
case eTouchCancel:
mType = MULTITOUCH_CANCEL;
break;
default:
MOZ_ASSERT_UNREACHABLE("Did not assign a type to a MultiTouchInput");
break;
}
mScreenOffset = ViewAs<ExternalPixel>(
aTouchEvent.mWidget->WidgetToScreenOffset(),
PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
for (size_t i = 0; i < aTouchEvent.mTouches.Length(); i++) {
const Touch* domTouch = aTouchEvent.mTouches[i];
// Extract data from weird interfaces.
int32_t identifier = domTouch->Identifier();
int32_t radiusX = domTouch->RadiusX(CallerType::System);
int32_t radiusY = domTouch->RadiusY(CallerType::System);
float rotationAngle = domTouch->RotationAngle(CallerType::System);
float force = domTouch->Force(CallerType::System);
SingleTouchData data(
identifier,
ViewAs<ScreenPixel>(
domTouch->mRefPoint,
PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent),
ScreenSize((float)radiusX, (float)radiusY), rotationAngle, force);
mTouches.AppendElement(data);
}
}
void MultiTouchInput::Translate(const ScreenPoint& aTranslation) {
ScreenIntPoint translation = RoundedToInt(aTranslation);
for (auto& touchData : mTouches) {
for (auto& historicalData : touchData.mHistoricalData) {
historicalData.mScreenPoint.MoveBy(translation.x, translation.y);
}
touchData.mScreenPoint.MoveBy(translation.x, translation.y);
}
}
WidgetTouchEvent MultiTouchInput::ToWidgetEvent(nsIWidget* aWidget,
uint16_t aInputSource) const {
MOZ_ASSERT(NS_IsMainThread(),
"Can only convert To WidgetTouchEvent on main thread");
MOZ_ASSERT(aInputSource ==
mozilla::dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH ||
aInputSource == mozilla::dom::MouseEvent_Binding::MOZ_SOURCE_PEN);
EventMessage touchEventMessage = eVoidEvent;
switch (mType) {
case MULTITOUCH_START:
touchEventMessage = eTouchStart;
break;
case MULTITOUCH_MOVE:
touchEventMessage = eTouchMove;
break;
case MULTITOUCH_END:
touchEventMessage = eTouchEnd;
break;
case MULTITOUCH_CANCEL:
touchEventMessage = eTouchCancel;
break;
default:
MOZ_ASSERT_UNREACHABLE(
"Did not assign a type to WidgetTouchEvent in MultiTouchInput");
break;
}
WidgetTouchEvent event(true, touchEventMessage, aWidget);
if (touchEventMessage == eVoidEvent) {
return event;
}
event.mModifiers = this->modifiers;
event.mTimeStamp = this->mTimeStamp;
event.mFlags.mHandledByAPZ = mHandledByAPZ;
event.mFocusSequenceNumber = mFocusSequenceNumber;
event.mLayersId = mLayersId;
event.mInputSource = aInputSource;
event.mButton = mButton;
event.mButtons = mButtons;
for (size_t i = 0; i < mTouches.Length(); i++) {
*event.mTouches.AppendElement() = mTouches[i].ToNewDOMTouch();
}
return event;
}
int32_t MultiTouchInput::IndexOfTouch(int32_t aTouchIdentifier) {
for (size_t i = 0; i < mTouches.Length(); i++) {
if (mTouches[i].mIdentifier == aTouchIdentifier) {
return (int32_t)i;
}
}
return -1;
}
bool MultiTouchInput::TransformToLocal(
const ScreenToParentLayerMatrix4x4& aTransform) {
for (auto& touchData : mTouches) {
for (auto& historicalData : touchData.mHistoricalData) {
Maybe<ParentLayerIntPoint> historicalPoint =
UntransformBy(aTransform, historicalData.mScreenPoint);
if (!historicalPoint) {
return false;
}
historicalData.mLocalScreenPoint = *historicalPoint;
}
Maybe<ParentLayerIntPoint> point =
UntransformBy(aTransform, touchData.mScreenPoint);
if (!point) {
return false;
}
touchData.mLocalScreenPoint = *point;
}
return true;
}
MouseInput::MouseInput()
: InputData(MOUSE_INPUT),
mType(MOUSE_NONE),
mButtonType(NONE),
mInputSource(0),
mButtons(0),
mHandledByAPZ(false),
mPreventClickEvent(false),
mIgnoreCapturingContent(false) {}
MouseInput::MouseInput(MouseType aType, ButtonType aButtonType,
uint16_t aInputSource, int16_t aButtons,
const ScreenPoint& aPoint, TimeStamp aTimeStamp,
Modifiers aModifiers)
: InputData(MOUSE_INPUT, aTimeStamp, aModifiers),
mType(aType),
mButtonType(aButtonType),
mInputSource(aInputSource),
mButtons(aButtons),
mOrigin(aPoint),
mHandledByAPZ(false),
mPreventClickEvent(false),
mIgnoreCapturingContent(false) {}
MouseInput::MouseInput(const WidgetMouseEventBase& aMouseEvent)
: InputData(MOUSE_INPUT, aMouseEvent.mTimeStamp, aMouseEvent.mModifiers),
mType(MOUSE_NONE),
mButtonType(NONE),
mInputSource(aMouseEvent.mInputSource),
mButtons(aMouseEvent.mButtons),
mHandledByAPZ(aMouseEvent.mFlags.mHandledByAPZ),
mPreventClickEvent(aMouseEvent.mClass == eMouseEventClass &&
aMouseEvent.AsMouseEvent()->mClickEventPrevented),
mIgnoreCapturingContent(
(aMouseEvent.mMessage == eMouseMove ||
aMouseEvent.mMessage == ePointerMove) &&
aMouseEvent.AsMouseEvent()->mIgnoreCapturingContent) {
MOZ_ASSERT(NS_IsMainThread(),
"Can only copy from WidgetTouchEvent on main thread");
mButtonType = NONE;
switch (aMouseEvent.mButton) {
case MouseButton::ePrimary:
mButtonType = MouseInput::PRIMARY_BUTTON;
break;
case MouseButton::eMiddle:
mButtonType = MouseInput::MIDDLE_BUTTON;
break;
case MouseButton::eSecondary:
mButtonType = MouseInput::SECONDARY_BUTTON;
break;
}
switch (aMouseEvent.mMessage) {
case eMouseMove:
mType = MOUSE_MOVE;
break;
case eMouseUp:
mType = MOUSE_UP;
break;
case eMouseDown:
mType = MOUSE_DOWN;
break;
case eDragStart:
mType = MOUSE_DRAG_START;
break;
case eDragEnd:
mType = MOUSE_DRAG_END;
break;
case eMouseEnterIntoWidget:
mType = MOUSE_WIDGET_ENTER;
break;
case eMouseExitFromWidget:
mType = MOUSE_WIDGET_EXIT;
break;
case eMouseExploreByTouch:
mType = MOUSE_EXPLORE_BY_TOUCH;
break;
case eMouseHitTest:
mType = MOUSE_HITTEST;
break;
case eContextMenu:
mType = MOUSE_CONTEXTMENU;
break;
default:
MOZ_ASSERT_UNREACHABLE("Mouse event type not supported");
break;
}
mOrigin = ScreenPoint(ViewAs<ScreenPixel>(
aMouseEvent.mRefPoint,
PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
}
bool MouseInput::IsLeftButton() const { return mButtonType == PRIMARY_BUTTON; }
bool MouseInput::TransformToLocal(
const ScreenToParentLayerMatrix4x4& aTransform) {
Maybe<ParentLayerPoint> point = UntransformBy(aTransform, mOrigin);
if (!point) {
return false;
}
mLocalOrigin = *point;
return true;
}
bool MouseInput::IsPointerEventType() const {
return mType == MOUSE_CONTEXTMENU;
}
template <class WidgetMouseOrPointerEvent>
WidgetMouseOrPointerEvent MouseInput::ToWidgetEvent(nsIWidget* aWidget) const {
MOZ_ASSERT(NS_IsMainThread(),
"Can only convert To WidgetTouchEvent on main thread");
const DebugOnly<bool> isPointerEvent =
std::is_same<WidgetMouseOrPointerEvent, WidgetPointerEvent>::value;
const DebugOnly<bool> isMouseEvent =
std::is_same<WidgetMouseOrPointerEvent, WidgetMouseEvent>::value;
MOZ_ASSERT(!IsPointerEventType() || isPointerEvent,
"Please use ToWidgetEvent<WidgetPointerEvent>() for the instance");
MOZ_ASSERT(IsPointerEventType() || isMouseEvent,
"Please use ToWidgetEvent<WidgetMouseEvent>() for the instance");
EventMessage msg = eVoidEvent;
uint32_t clickCount = 0;
Maybe<WidgetMouseEvent::ExitFrom> exitFrom;
switch (mType) {
case MOUSE_MOVE:
msg = eMouseMove;
break;
case MOUSE_UP:
msg = eMouseUp;
clickCount = 1;
break;
case MOUSE_DOWN:
msg = eMouseDown;
clickCount = 1;
break;
case MOUSE_DRAG_START:
msg = eDragStart;
break;
case MOUSE_DRAG_END:
msg = eDragEnd;
break;
case MOUSE_WIDGET_ENTER:
msg = eMouseEnterIntoWidget;
break;
case MOUSE_WIDGET_EXIT:
msg = eMouseExitFromWidget;
exitFrom = Some(WidgetMouseEvent::ePlatformChild);
break;
case MOUSE_EXPLORE_BY_TOUCH:
msg = eMouseExploreByTouch;
break;
case MOUSE_HITTEST:
msg = eMouseHitTest;
break;
case MOUSE_CONTEXTMENU:
msg = eContextMenu;
MOZ_ASSERT(mButtonType == MouseInput::SECONDARY_BUTTON);
break;
default:
MOZ_ASSERT_UNREACHABLE(
"Did not assign a type to WidgetMouseEvent in MouseInput");
break;
}
WidgetMouseOrPointerEvent event(true, msg, aWidget);
if (msg == eVoidEvent) {
return event;
}
switch (mButtonType) {
case MouseInput::PRIMARY_BUTTON:
event.mButton = MouseButton::ePrimary;
break;
case MouseInput::MIDDLE_BUTTON:
event.mButton = MouseButton::eMiddle;
break;
case MouseInput::SECONDARY_BUTTON:
event.mButton = MouseButton::eSecondary;
break;
case MouseInput::NONE:
default:
break;
}
event.mButtons = mButtons;
event.mModifiers = modifiers;
event.mTimeStamp = mTimeStamp;
event.mLayersId = mLayersId;
event.mFlags.mHandledByAPZ = mHandledByAPZ;
event.mRefPoint = RoundedToInt(ViewAs<LayoutDevicePixel>(
mOrigin,
PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
event.mClickCount = clickCount;
event.mInputSource = mInputSource;
event.mFocusSequenceNumber = mFocusSequenceNumber;
event.mExitFrom = exitFrom;
event.mClickEventPrevented = mPreventClickEvent;
event.mIgnoreCapturingContent = mIgnoreCapturingContent;
return event;
}
PanGestureInput::PanGestureInput()
: InputData(PANGESTURE_INPUT),
mType(PANGESTURE_MAYSTART),
mLineOrPageDeltaX(0),
mLineOrPageDeltaY(0),
mUserDeltaMultiplierX(1.0),
mUserDeltaMultiplierY(1.0),
mHandledByAPZ(false),
mOverscrollBehaviorAllowsSwipe(false),
mSimulateMomentum(false),
mIsNoLineOrPageDelta(true),
mMayTriggerSwipe(false) {}
PanGestureInput::PanGestureInput(PanGestureType aType, TimeStamp aTimeStamp,
const ScreenPoint& aPanStartPoint,
const ScreenPoint& aPanDisplacement,
Modifiers aModifiers)
: InputData(PANGESTURE_INPUT, aTimeStamp, aModifiers),
mType(aType),
mPanStartPoint(aPanStartPoint),
mPanDisplacement(aPanDisplacement),
mLineOrPageDeltaX(0),
mLineOrPageDeltaY(0),
mUserDeltaMultiplierX(1.0),
mUserDeltaMultiplierY(1.0),
mHandledByAPZ(false),
mOverscrollBehaviorAllowsSwipe(false),
mSimulateMomentum(false),
mIsNoLineOrPageDelta(true) {
mMayTriggerSwipe = SwipeTracker::CanTriggerSwipe(*this);
}
PanGestureInput::PanGestureInput(PanGestureType aType, TimeStamp aTimeStamp,
const ScreenPoint& aPanStartPoint,
const ScreenPoint& aPanDisplacement,
Modifiers aModifiers,
IsEligibleForSwipe aIsEligibleForSwipe)
: PanGestureInput(aType, aTimeStamp, aPanStartPoint, aPanDisplacement,
aModifiers) {
mMayTriggerSwipe &= bool(aIsEligibleForSwipe);
}
void PanGestureInput::SetLineOrPageDeltas(int32_t aLineOrPageDeltaX,
int32_t aLineOrPageDeltaY) {
mLineOrPageDeltaX = aLineOrPageDeltaX;
mLineOrPageDeltaY = aLineOrPageDeltaY;
mIsNoLineOrPageDelta = false;
}
bool PanGestureInput::IsMomentum() const {
switch (mType) {
case PanGestureInput::PANGESTURE_MOMENTUMSTART:
case PanGestureInput::PANGESTURE_MOMENTUMPAN:
case PanGestureInput::PANGESTURE_MOMENTUMEND:
return true;
default:
return false;
}
}
WidgetWheelEvent PanGestureInput::ToWidgetEvent(nsIWidget* aWidget) const {
WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
wheelEvent.mModifiers = this->modifiers;
wheelEvent.mTimeStamp = mTimeStamp;
wheelEvent.mLayersId = mLayersId;
wheelEvent.mRefPoint = RoundedToInt(ViewAs<LayoutDevicePixel>(
mPanStartPoint,
PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
wheelEvent.mButtons = 0;
wheelEvent.mMayHaveMomentum = true; // pan inputs may have momentum
wheelEvent.mIsMomentum = IsMomentum();
wheelEvent.mLineOrPageDeltaX = mLineOrPageDeltaX;
wheelEvent.mLineOrPageDeltaY = mLineOrPageDeltaY;
wheelEvent.mDeltaX = mPanDisplacement.x;
wheelEvent.mDeltaY = mPanDisplacement.y;
wheelEvent.mFlags.mHandledByAPZ = mHandledByAPZ;
wheelEvent.mFocusSequenceNumber = mFocusSequenceNumber;
wheelEvent.mIsNoLineOrPageDelta = mIsNoLineOrPageDelta;
if (mDeltaType == PanGestureInput::PANDELTA_PAGE) {
// widget/gtk is currently the only consumer that uses delta type
// PANDELTA_PAGE
// Emulate legacy widget/gtk behavior
wheelEvent.mDeltaMode = WheelEvent_Binding::DOM_DELTA_LINE;
wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSLY;
wheelEvent.mDeltaX *= 3;
wheelEvent.mDeltaY *= 3;
} else {
wheelEvent.mDeltaMode = WheelEvent_Binding::DOM_DELTA_PIXEL;
}
return wheelEvent;
}
bool PanGestureInput::TransformToLocal(
const ScreenToParentLayerMatrix4x4& aTransform) {
Maybe<ParentLayerPoint> panStartPoint =
UntransformBy(aTransform, mPanStartPoint);
if (!panStartPoint) {
return false;
}
mLocalPanStartPoint = *panStartPoint;
if (mDeltaType == PanGestureInput::PANDELTA_PAGE) {
// Skip transforming the pan displacement because we want
// raw page proportion counts.
mLocalPanDisplacement = ViewAs<ParentLayerPixel>(
mPanDisplacement, PixelCastJustification::DeltaIsPageProportion);
return true;
}
Maybe<ParentLayerPoint> panDisplacement =
UntransformVector(aTransform, mPanDisplacement, mPanStartPoint);
if (!panDisplacement) {
return false;
}
mLocalPanDisplacement = *panDisplacement;
return true;
}
ScreenPoint PanGestureInput::UserMultipliedPanDisplacement() const {
return ScreenPoint(mPanDisplacement.x * mUserDeltaMultiplierX,
mPanDisplacement.y * mUserDeltaMultiplierY);
}
ParentLayerPoint PanGestureInput::UserMultipliedLocalPanDisplacement() const {
return ParentLayerPoint(mLocalPanDisplacement.x * mUserDeltaMultiplierX,
mLocalPanDisplacement.y * mUserDeltaMultiplierY);
}
static int32_t TakeLargestInt(gfx::Coord* aCoord) {
int32_t result(aCoord->value); // truncate towards zero
aCoord->value -= result;
return result;
}
/* static */ gfx::IntPoint PanGestureInput::GetIntegerDeltaForEvent(
bool aIsStart, float x, float y) {
static gfx::Point sAccumulator(0.0f, 0.0f);
if (aIsStart) {
sAccumulator = gfx::Point(0.0f, 0.0f);
}
sAccumulator.x += x;
sAccumulator.y += y;
return gfx::IntPoint(TakeLargestInt(&sAccumulator.x),
TakeLargestInt(&sAccumulator.y));
}
PinchGestureInput::PinchGestureInput()
: InputData(PINCHGESTURE_INPUT),
mType(PINCHGESTURE_START),
mSource(UNKNOWN),
mHandledByAPZ(false) {}
PinchGestureInput::PinchGestureInput(
PinchGestureType aType, PinchGestureSource aSource, TimeStamp aTimeStamp,
const ExternalPoint& aScreenOffset, const ScreenPoint& aFocusPoint,
ScreenCoord aCurrentSpan, ScreenCoord aPreviousSpan, Modifiers aModifiers)
: InputData(PINCHGESTURE_INPUT, aTimeStamp, aModifiers),
mType(aType),
mSource(aSource),
mFocusPoint(aFocusPoint),
mScreenOffset(aScreenOffset),
mCurrentSpan(aCurrentSpan),
mPreviousSpan(aPreviousSpan),
mLineOrPageDeltaY(0),
mHandledByAPZ(false) {}
bool PinchGestureInput::TransformToLocal(
const ScreenToParentLayerMatrix4x4& aTransform) {
Maybe<ParentLayerPoint> point = UntransformBy(aTransform, mFocusPoint);
if (!point) {
return false;
}
mLocalFocusPoint = *point;
return true;
}
WidgetWheelEvent PinchGestureInput::ToWidgetEvent(nsIWidget* aWidget) const {
WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
wheelEvent.mModifiers = this->modifiers | MODIFIER_CONTROL;
wheelEvent.mTimeStamp = mTimeStamp;
wheelEvent.mLayersId = mLayersId;
wheelEvent.mRefPoint = RoundedToInt(ViewAs<LayoutDevicePixel>(
mFocusPoint,
PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
wheelEvent.mButtons = 0;
wheelEvent.mFlags.mHandledByAPZ = mHandledByAPZ;
wheelEvent.mDeltaMode = WheelEvent_Binding::DOM_DELTA_PIXEL;
wheelEvent.mDeltaY = ComputeDeltaY(aWidget);
wheelEvent.mLineOrPageDeltaY = mLineOrPageDeltaY;
MOZ_ASSERT(mType == PINCHGESTURE_END || wheelEvent.mDeltaY != 0.0);
return wheelEvent;
}
double PinchGestureInput::ComputeDeltaY(nsIWidget* aWidget) const {
#if defined(XP_DARWIN)
// This converts the pinch gesture value to a fake wheel event that has the
// control key pressed so that pages can implement custom pinch gesture
// handling. It may seem strange that this doesn't use a wheel event with
// the deltaZ property set, but this matches Chrome's behavior as described
// at https://code.google.com/p/chromium/issues/detail?id=289887
//
// The intent of the formula below is to produce numbers similar to Chrome's
// implementation of this feature. Chrome implements deltaY using the formula
// "-100 * log(1 + [event magnification])" which is unfortunately incorrect.
// All deltas for a single pinch gesture should sum to 0 if the start and end
// of a pinch gesture end up in the same place. This doesn't happen in Chrome
// because they followed Apple's misleading documentation, which implies that
// "1 + [event magnification]" is the scale factor. The scale factor is
// instead "pow(ratio, [event magnification])" so "[event magnification]" is
// already in log space.
//
// The multiplication by the backing scale factor below counteracts the
// division by the backing scale factor in WheelEvent.
// We want to set deltaY to |-100.0 * M * GetDefaultScaleInternal()| where M
// is [event magnification] but [event magnification] is only available in the
// macOS widget code so we have to reverse engineer from mCurrentSpan and
// mPreviousSpan (which are derived from [event magnification]) to get it.
// Specifically, we know |mCurrentSpan == 100.0| and |mPreviousSpan == 100.0 *
// (1.0 - M)|. We can calculate deltaY by solving the mPreviousSpan equation
// for M in terms of mPreviousSpan and plugging that into to the formula for
// deltaY.
return (mPreviousSpan - 100.0) *
(aWidget ? aWidget->GetDefaultScaleInternal() : 1.f);
#else
// This calculation is based on what the Windows and Linux widget code does.
// Specifically, it creates a PinchGestureInput with |mCurrentSpan == 100.0 *
// currentScale| and |mPreviousSpan == 100.0 * lastScale| where currentScale
// is the scale from the current OS event and lastScale is the scale when the
// previous OS event happened. On macOS [event magnification] is a relative
// change in scale factor, ie if the scale factor changed from 1 to 1.1 it
// will be 0.1, similarly if it changed from 1 to 0.9 it will be -0.1. To
// calculate the relative scale change on Windows we would calculate |M =
// currentScale - lastScale = (mCurrentSpan-mPreviousSpan)/100| and use the
// same formula as the macOS code
// (|-100.0 * M * GetDefaultScaleInternal()|).
return (mPreviousSpan - mCurrentSpan) *
(aWidget ? aWidget->GetDefaultScaleInternal() : 1.f);
#endif
}
bool PinchGestureInput::SetLineOrPageDeltaY(nsIWidget* aWidget) {
double deltaY = ComputeDeltaY(aWidget);
if (deltaY == 0 && mType != PINCHGESTURE_END) {
return false;
}
gfx::IntPoint lineOrPageDelta = PinchGestureInput::GetIntegerDeltaForEvent(
(mType == PINCHGESTURE_START), 0, deltaY);
mLineOrPageDeltaY = lineOrPageDelta.y;
if (mLineOrPageDeltaY == 0) {
// For PINCHGESTURE_SCALE events, don't dispatch them. Note that the delta
// isn't lost; it remains in the accumulator in GetIntegerDeltaForEvent().
if (mType == PINCHGESTURE_SCALE) {
return false;
}
// On Windows, drop PINCHGESTURE_START as well (the Windows widget code will
// defer the START event until we accumulate enough delta).
// The Linux widget code doesn't support this, so instead set the event's
// mLineOrPageDeltaY to the smallest nonzero amount in the relevant
// direction.
if (mType == PINCHGESTURE_START) {
#ifdef XP_WIN
return false;
#else
mLineOrPageDeltaY = (deltaY >= 0) ? 1 : -1;
#endif
}
// For PINCHGESTURE_END events, not dispatching a DOMMouseScroll for them is
// fine.
}
return true;
}
/* static */ gfx::IntPoint PinchGestureInput::GetIntegerDeltaForEvent(
bool aIsStart, float x, float y) {
static gfx::Point sAccumulator(0.0f, 0.0f);
if (aIsStart) {
sAccumulator = gfx::Point(0.0f, 0.0f);
}
sAccumulator.x += x;
sAccumulator.y += y;
return gfx::IntPoint(TakeLargestInt(&sAccumulator.x),
TakeLargestInt(&sAccumulator.y));
}
TapGestureInput::TapGestureInput()
: InputData(TAPGESTURE_INPUT), mType(TAPGESTURE_LONG) {}
TapGestureInput::TapGestureInput(TapGestureType aType, TimeStamp aTimeStamp,
const ScreenIntPoint& aPoint,
Modifiers aModifiers)
: InputData(TAPGESTURE_INPUT, aTimeStamp, aModifiers),
mType(aType),
mPoint(aPoint) {}
TapGestureInput::TapGestureInput(TapGestureType aType, TimeStamp aTimeStamp,
const ParentLayerPoint& aLocalPoint,
Modifiers aModifiers)
: InputData(TAPGESTURE_INPUT, aTimeStamp, aModifiers),
mType(aType),
mLocalPoint(aLocalPoint) {}
bool TapGestureInput::TransformToLocal(
const ScreenToParentLayerMatrix4x4& aTransform) {
Maybe<ParentLayerIntPoint> point = UntransformBy(aTransform, mPoint);
if (!point) {
return false;
}
mLocalPoint = *point;
return true;
}
WidgetSimpleGestureEvent TapGestureInput::ToWidgetEvent(
nsIWidget* aWidget) const {
WidgetSimpleGestureEvent event(true, eTapGesture, aWidget);
event.mTimeStamp = mTimeStamp;
event.mLayersId = mLayersId;
event.mRefPoint = ViewAs<LayoutDevicePixel>(
mPoint,
PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
event.mButtons = 0;
event.mClickCount = 1;
event.mModifiers = modifiers;
return event;
}
ScrollWheelInput::ScrollWheelInput()
: InputData(SCROLLWHEEL_INPUT),
mDeltaType(SCROLLDELTA_LINE),
mScrollMode(SCROLLMODE_INSTANT),
mHandledByAPZ(false),
mDeltaX(0.0),
mDeltaY(0.0),
mLineOrPageDeltaX(0),
mLineOrPageDeltaY(0),
mScrollSeriesNumber(0),
mUserDeltaMultiplierX(1.0),
mUserDeltaMultiplierY(1.0),
mMayHaveMomentum(false),
mIsMomentum(false),
mAPZAction(APZWheelAction::Scroll) {}
ScrollWheelInput::ScrollWheelInput(
TimeStamp aTimeStamp, Modifiers aModifiers, ScrollMode aScrollMode,
ScrollDeltaType aDeltaType, const ScreenPoint& aOrigin, double aDeltaX,
double aDeltaY, bool aAllowToOverrideSystemScrollSpeed,
WheelDeltaAdjustmentStrategy aWheelDeltaAdjustmentStrategy)
: InputData(SCROLLWHEEL_INPUT, aTimeStamp, aModifiers),
mDeltaType(aDeltaType),
mScrollMode(aScrollMode),
mOrigin(aOrigin),
mHandledByAPZ(false),
mDeltaX(aDeltaX),
mDeltaY(aDeltaY),
mLineOrPageDeltaX(0),
mLineOrPageDeltaY(0),
mScrollSeriesNumber(0),
mUserDeltaMultiplierX(1.0),
mUserDeltaMultiplierY(1.0),
mMayHaveMomentum(false),
mIsMomentum(false),
mAllowToOverrideSystemScrollSpeed(aAllowToOverrideSystemScrollSpeed),
mWheelDeltaAdjustmentStrategy(aWheelDeltaAdjustmentStrategy),
mAPZAction(APZWheelAction::Scroll) {}
ScrollWheelInput::ScrollWheelInput(const WidgetWheelEvent& aWheelEvent)
: InputData(SCROLLWHEEL_INPUT, aWheelEvent.mTimeStamp,
aWheelEvent.mModifiers),
mDeltaType(DeltaTypeForDeltaMode(aWheelEvent.mDeltaMode)),
mScrollMode(SCROLLMODE_INSTANT),
mHandledByAPZ(aWheelEvent.mFlags.mHandledByAPZ),
mDeltaX(aWheelEvent.mDeltaX),
mDeltaY(aWheelEvent.mDeltaY),
mWheelTicksX(aWheelEvent.mWheelTicksX),
mWheelTicksY(aWheelEvent.mWheelTicksX),
mLineOrPageDeltaX(aWheelEvent.mLineOrPageDeltaX),
mLineOrPageDeltaY(aWheelEvent.mLineOrPageDeltaY),
mScrollSeriesNumber(0),
mUserDeltaMultiplierX(1.0),
mUserDeltaMultiplierY(1.0),
mMayHaveMomentum(aWheelEvent.mMayHaveMomentum),
mIsMomentum(aWheelEvent.mIsMomentum),
mAllowToOverrideSystemScrollSpeed(
aWheelEvent.mAllowToOverrideSystemScrollSpeed),
mWheelDeltaAdjustmentStrategy(WheelDeltaAdjustmentStrategy::eNone),
mAPZAction(APZWheelAction::Scroll) {
mOrigin = ScreenPoint(ViewAs<ScreenPixel>(
aWheelEvent.mRefPoint,
PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
}
ScrollWheelInput::ScrollDeltaType ScrollWheelInput::DeltaTypeForDeltaMode(
uint32_t aDeltaMode) {
switch (aDeltaMode) {
case WheelEvent_Binding::DOM_DELTA_LINE:
return SCROLLDELTA_LINE;
case WheelEvent_Binding::DOM_DELTA_PAGE:
return SCROLLDELTA_PAGE;
case WheelEvent_Binding::DOM_DELTA_PIXEL:
return SCROLLDELTA_PIXEL;
default:
MOZ_CRASH();
}
return SCROLLDELTA_LINE;
}
uint32_t ScrollWheelInput::DeltaModeForDeltaType(ScrollDeltaType aDeltaType) {
switch (aDeltaType) {
case ScrollWheelInput::SCROLLDELTA_LINE:
return WheelEvent_Binding::DOM_DELTA_LINE;
case ScrollWheelInput::SCROLLDELTA_PAGE:
return WheelEvent_Binding::DOM_DELTA_PAGE;
case ScrollWheelInput::SCROLLDELTA_PIXEL:
default:
return WheelEvent_Binding::DOM_DELTA_PIXEL;
}
}
ScrollUnit ScrollWheelInput::ScrollUnitForDeltaType(
ScrollDeltaType aDeltaType) {
switch (aDeltaType) {
case SCROLLDELTA_LINE:
return ScrollUnit::LINES;
case SCROLLDELTA_PAGE:
return ScrollUnit::PAGES;
case SCROLLDELTA_PIXEL:
return ScrollUnit::DEVICE_PIXELS;
default:
MOZ_CRASH();
}
return ScrollUnit::LINES;
}
WidgetWheelEvent ScrollWheelInput::ToWidgetEvent(nsIWidget* aWidget) const {
WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
wheelEvent.mModifiers = this->modifiers;
wheelEvent.mTimeStamp = mTimeStamp;
wheelEvent.mLayersId = mLayersId;
wheelEvent.mRefPoint = RoundedToInt(ViewAs<LayoutDevicePixel>(
mOrigin,
PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
wheelEvent.mButtons = 0;
wheelEvent.mDeltaMode = DeltaModeForDeltaType(mDeltaType);
wheelEvent.mMayHaveMomentum = mMayHaveMomentum;
wheelEvent.mIsMomentum = mIsMomentum;
wheelEvent.mDeltaX = mDeltaX;
wheelEvent.mDeltaY = mDeltaY;
wheelEvent.mWheelTicksX = mWheelTicksX;
wheelEvent.mWheelTicksY = mWheelTicksY;
wheelEvent.mLineOrPageDeltaX = mLineOrPageDeltaX;
wheelEvent.mLineOrPageDeltaY = mLineOrPageDeltaY;
wheelEvent.mAllowToOverrideSystemScrollSpeed =
mAllowToOverrideSystemScrollSpeed;
wheelEvent.mFlags.mHandledByAPZ = mHandledByAPZ;
wheelEvent.mFocusSequenceNumber = mFocusSequenceNumber;
return wheelEvent;
}
bool ScrollWheelInput::TransformToLocal(
const ScreenToParentLayerMatrix4x4& aTransform) {
Maybe<ParentLayerPoint> point = UntransformBy(aTransform, mOrigin);
if (!point) {
return false;
}
mLocalOrigin = *point;
return true;
}
bool ScrollWheelInput::IsCustomizedByUserPrefs() const {
return mUserDeltaMultiplierX != 1.0 || mUserDeltaMultiplierY != 1.0;
}
KeyboardInput::KeyboardInput(const WidgetKeyboardEvent& aEvent)
: InputData(KEYBOARD_INPUT, aEvent.mTimeStamp, aEvent.mModifiers),
mKeyCode(aEvent.mKeyCode),
mCharCode(aEvent.mCharCode),
mHandledByAPZ(false) {
switch (aEvent.mMessage) {
case eKeyPress: {
mType = KeyboardInput::KEY_PRESS;
break;
}
case eKeyUp: {
mType = KeyboardInput::KEY_UP;
break;
}
case eKeyDown: {
mType = KeyboardInput::KEY_DOWN;
break;
}
default:
mType = KeyboardInput::KEY_OTHER;
break;
}
aEvent.GetShortcutKeyCandidates(mShortcutCandidates);
}
KeyboardInput::KeyboardInput()
: InputData(KEYBOARD_INPUT),
mType(KEY_DOWN),
mKeyCode(0),
mCharCode(0),
mHandledByAPZ(false) {}
} // namespace mozilla