summaryrefslogtreecommitdiffstats
path: root/widget/headless/HeadlessWidget.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /widget/headless/HeadlessWidget.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/headless/HeadlessWidget.cpp')
-rw-r--r--widget/headless/HeadlessWidget.cpp625
1 files changed, 625 insertions, 0 deletions
diff --git a/widget/headless/HeadlessWidget.cpp b/widget/headless/HeadlessWidget.cpp
new file mode 100644
index 0000000000..083d026d3c
--- /dev/null
+++ b/widget/headless/HeadlessWidget.cpp
@@ -0,0 +1,625 @@
+/* -*- 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 "HeadlessWidget.h"
+#include "ErrorList.h"
+#include "HeadlessCompositorWidget.h"
+#include "BasicEvents.h"
+#include "MouseEvents.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/NativeKeyBindingsType.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/WritingModes.h"
+#include "mozilla/widget/HeadlessWidgetTypes.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "mozilla/widget/Screen.h"
+#include "nsIScreen.h"
+#include "HeadlessKeyBindings.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+using mozilla::LogLevel;
+
+#ifdef MOZ_LOGGING
+
+# include "mozilla/Logging.h"
+static mozilla::LazyLogModule sWidgetLog("Widget");
+static mozilla::LazyLogModule sWidgetFocusLog("WidgetFocus");
+# define LOG(args) MOZ_LOG(sWidgetLog, mozilla::LogLevel::Debug, args)
+# define LOGFOCUS(args) \
+ MOZ_LOG(sWidgetFocusLog, mozilla::LogLevel::Debug, args)
+
+#else
+
+# define LOG(args)
+# define LOGFOCUS(args)
+
+#endif /* MOZ_LOGGING */
+
+/*static*/
+already_AddRefed<nsIWidget> nsIWidget::CreateHeadlessWidget() {
+ nsCOMPtr<nsIWidget> widget = new mozilla::widget::HeadlessWidget();
+ return widget.forget();
+}
+
+namespace mozilla {
+namespace widget {
+
+StaticAutoPtr<nsTArray<HeadlessWidget*>> HeadlessWidget::sActiveWindows;
+
+already_AddRefed<HeadlessWidget> HeadlessWidget::GetActiveWindow() {
+ if (!sActiveWindows) {
+ return nullptr;
+ }
+ auto length = sActiveWindows->Length();
+ if (length == 0) {
+ return nullptr;
+ }
+ RefPtr<HeadlessWidget> widget = sActiveWindows->ElementAt(length - 1);
+ return widget.forget();
+}
+
+HeadlessWidget::HeadlessWidget()
+ : mEnabled(true),
+ mVisible(false),
+ mDestroyed(false),
+ mAlwaysOnTop(false),
+ mTopLevel(nullptr),
+ mCompositorWidget(nullptr),
+ mSizeMode(nsSizeMode_Normal),
+ mLastSizeMode(nsSizeMode_Normal),
+ mEffectiveSizeMode(nsSizeMode_Normal),
+ mRestoreBounds(0, 0, 0, 0) {
+ if (!sActiveWindows) {
+ sActiveWindows = new nsTArray<HeadlessWidget*>();
+ ClearOnShutdown(&sActiveWindows);
+ }
+}
+
+HeadlessWidget::~HeadlessWidget() {
+ LOG(("HeadlessWidget::~HeadlessWidget() [%p]\n", (void*)this));
+
+ Destroy();
+}
+
+void HeadlessWidget::Destroy() {
+ if (mDestroyed) {
+ return;
+ }
+ LOG(("HeadlessWidget::Destroy [%p]\n", (void*)this));
+ mDestroyed = true;
+
+ if (sActiveWindows) {
+ int32_t index = sActiveWindows->IndexOf(this);
+ if (index != -1) {
+ RefPtr<HeadlessWidget> activeWindow = GetActiveWindow();
+ sActiveWindows->RemoveElementAt(index);
+ // If this is the currently active widget and there's a previously active
+ // widget, activate the previous widget.
+ RefPtr<HeadlessWidget> previousActiveWindow = GetActiveWindow();
+ if (this == activeWindow && previousActiveWindow &&
+ previousActiveWindow->mWidgetListener) {
+ previousActiveWindow->mWidgetListener->WindowActivated();
+ }
+ }
+ }
+
+ nsBaseWidget::OnDestroy();
+
+ nsBaseWidget::Destroy();
+}
+
+nsresult HeadlessWidget::Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ widget::InitData* aInitData) {
+ MOZ_ASSERT(!aNativeParent, "No native parents for headless widgets.");
+
+ BaseCreate(nullptr, aInitData);
+
+ mBounds = aRect;
+ mRestoreBounds = aRect;
+
+ mAlwaysOnTop = aInitData && aInitData->mAlwaysOnTop;
+
+ if (aParent) {
+ mTopLevel = aParent->GetTopLevelWidget();
+ } else {
+ mTopLevel = this;
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIWidget> HeadlessWidget::CreateChild(
+ const LayoutDeviceIntRect& aRect, widget::InitData* aInitData,
+ bool aForceUseIWidgetParent) {
+ nsCOMPtr<nsIWidget> widget = nsIWidget::CreateHeadlessWidget();
+ if (!widget) {
+ return nullptr;
+ }
+ if (NS_FAILED(widget->Create(this, nullptr, aRect, aInitData))) {
+ return nullptr;
+ }
+ return widget.forget();
+}
+
+void HeadlessWidget::GetCompositorWidgetInitData(
+ mozilla::widget::CompositorWidgetInitData* aInitData) {
+ *aInitData =
+ mozilla::widget::HeadlessCompositorWidgetInitData(GetClientSize());
+}
+
+nsIWidget* HeadlessWidget::GetTopLevelWidget() { return mTopLevel; }
+
+void HeadlessWidget::RaiseWindow() {
+ MOZ_ASSERT(mWindowType == WindowType::TopLevel ||
+ mWindowType == WindowType::Dialog ||
+ mWindowType == WindowType::Sheet,
+ "Raising a non-toplevel window.");
+
+ // Do nothing if this is the currently active window.
+ RefPtr<HeadlessWidget> activeWindow = GetActiveWindow();
+ if (activeWindow == this) {
+ return;
+ }
+
+ // Raise the window to the top of the stack.
+ nsWindowZ placement = nsWindowZTop;
+ nsCOMPtr<nsIWidget> actualBelow;
+ if (mWidgetListener)
+ mWidgetListener->ZLevelChanged(true, &placement, nullptr,
+ getter_AddRefs(actualBelow));
+
+ // Deactivate the last active window.
+ if (activeWindow && activeWindow->mWidgetListener) {
+ activeWindow->mWidgetListener->WindowDeactivated();
+ }
+
+ // Remove this window if it's already tracked.
+ int32_t index = sActiveWindows->IndexOf(this);
+ if (index != -1) {
+ sActiveWindows->RemoveElementAt(index);
+ }
+
+ // Activate this window.
+ sActiveWindows->AppendElement(this);
+ if (mWidgetListener) mWidgetListener->WindowActivated();
+}
+
+void HeadlessWidget::Show(bool aState) {
+ mVisible = aState;
+
+ LOG(("HeadlessWidget::Show [%p] state %d\n", (void*)this, aState));
+
+ // Top-level window and dialogs are activated/raised when shown.
+ // NB: alwaysontop windows are generally used for peripheral indicators,
+ // so we don't focus them by default.
+ if (aState && !mAlwaysOnTop &&
+ (mWindowType == WindowType::TopLevel ||
+ mWindowType == WindowType::Dialog || mWindowType == WindowType::Sheet)) {
+ RaiseWindow();
+ }
+
+ ApplySizeModeSideEffects();
+}
+
+bool HeadlessWidget::IsVisible() const { return mVisible; }
+
+void HeadlessWidget::SetFocus(Raise aRaise,
+ mozilla::dom::CallerType aCallerType) {
+ LOGFOCUS((" SetFocus %d [%p]\n", aRaise == Raise::Yes, (void*)this));
+
+ // This means we request activation of our toplevel window.
+ if (aRaise == Raise::Yes) {
+ HeadlessWidget* topLevel = (HeadlessWidget*)GetTopLevelWidget();
+
+ // The toplevel only becomes active if it's currently visible; otherwise, it
+ // will be activated anyway when it's shown.
+ if (topLevel->IsVisible()) topLevel->RaiseWindow();
+ }
+}
+
+void HeadlessWidget::Enable(bool aState) { mEnabled = aState; }
+
+bool HeadlessWidget::IsEnabled() const { return mEnabled; }
+
+void HeadlessWidget::Move(double aX, double aY) {
+ LOG(("HeadlessWidget::Move [%p] %f %f\n", (void*)this, aX, aY));
+
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t x = NSToIntRound(aX * scale);
+ int32_t y = NSToIntRound(aY * scale);
+
+ if (mWindowType == WindowType::TopLevel ||
+ mWindowType == WindowType::Dialog) {
+ SetSizeMode(nsSizeMode_Normal);
+ }
+
+ MoveInternal(x, y);
+}
+
+void HeadlessWidget::MoveInternal(int32_t aX, int32_t aY) {
+ // Since a popup window's x/y coordinates are in relation to
+ // the parent, the parent might have moved so we always move a
+ // popup window.
+ if (mBounds.IsEqualXY(aX, aY) && mWindowType != WindowType::Popup) {
+ return;
+ }
+
+ mBounds.MoveTo(aX, aY);
+ NotifyWindowMoved(aX, aY);
+}
+
+LayoutDeviceIntPoint HeadlessWidget::WidgetToScreenOffset() {
+ return mTopLevel->GetBounds().TopLeft();
+}
+
+WindowRenderer* HeadlessWidget::GetWindowRenderer() {
+ return nsBaseWidget::GetWindowRenderer();
+}
+
+void HeadlessWidget::SetCompositorWidgetDelegate(
+ CompositorWidgetDelegate* delegate) {
+ if (delegate) {
+ mCompositorWidget = delegate->AsHeadlessCompositorWidget();
+ MOZ_ASSERT(mCompositorWidget,
+ "HeadlessWidget::SetCompositorWidgetDelegate called with a "
+ "non-HeadlessCompositorWidget");
+ } else {
+ mCompositorWidget = nullptr;
+ }
+}
+
+void HeadlessWidget::Resize(double aWidth, double aHeight, bool aRepaint) {
+ int32_t width = NSToIntRound(aWidth);
+ int32_t height = NSToIntRound(aHeight);
+ ResizeInternal(width, height, aRepaint);
+}
+
+void HeadlessWidget::ResizeInternal(int32_t aWidth, int32_t aHeight,
+ bool aRepaint) {
+ ConstrainSize(&aWidth, &aHeight);
+ mBounds.SizeTo(LayoutDeviceIntSize(aWidth, aHeight));
+
+ if (mCompositorWidget) {
+ mCompositorWidget->NotifyClientSizeChanged(
+ LayoutDeviceIntSize(mBounds.Width(), mBounds.Height()));
+ }
+ if (mWidgetListener) {
+ mWidgetListener->WindowResized(this, mBounds.Width(), mBounds.Height());
+ }
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->WindowResized(this, mBounds.Width(),
+ mBounds.Height());
+ }
+}
+
+void HeadlessWidget::Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) {
+ MoveInternal(NSToIntRound(aX), NSToIntRound(aY));
+ Resize(aWidth, aHeight, aRepaint);
+}
+
+void HeadlessWidget::SetSizeMode(nsSizeMode aMode) {
+ LOG(("HeadlessWidget::SetSizeMode [%p] %d\n", (void*)this, aMode));
+
+ if (aMode == mSizeMode) {
+ return;
+ }
+
+ if (aMode == nsSizeMode_Normal && mSizeMode == nsSizeMode_Fullscreen) {
+ MakeFullScreen(false);
+ return;
+ }
+
+ mSizeMode = aMode;
+
+ // Normally in real widget backends a window event would be triggered that
+ // would cause the window manager to handle resizing the window. In headless
+ // the window must manually be resized.
+ ApplySizeModeSideEffects();
+}
+
+void HeadlessWidget::ApplySizeModeSideEffects() {
+ if (!mVisible || mEffectiveSizeMode == mSizeMode) {
+ return;
+ }
+
+ if (mEffectiveSizeMode == nsSizeMode_Normal) {
+ // Store the last normal size bounds so it can be restored when entering
+ // normal mode again.
+ mRestoreBounds = mBounds;
+ }
+
+ switch (mSizeMode) {
+ case nsSizeMode_Normal: {
+ MoveInternal(mRestoreBounds.X(), mRestoreBounds.Y());
+ ResizeInternal(mRestoreBounds.Width(), mRestoreBounds.Height(), false);
+ break;
+ }
+ case nsSizeMode_Minimized:
+ break;
+ case nsSizeMode_Maximized: {
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ if (screen) {
+ int32_t left, top, width, height;
+ if (NS_SUCCEEDED(
+ screen->GetRectDisplayPix(&left, &top, &width, &height))) {
+ MoveInternal(0, 0);
+ ResizeInternal(width, height, true);
+ }
+ }
+ break;
+ }
+ case nsSizeMode_Fullscreen:
+ // This will take care of resizing the window.
+ nsBaseWidget::InfallibleMakeFullScreen(true);
+ break;
+ default:
+ break;
+ }
+
+ mEffectiveSizeMode = mSizeMode;
+ if (mWidgetListener) {
+ mWidgetListener->SizeModeChanged(mSizeMode);
+ }
+}
+
+nsresult HeadlessWidget::MakeFullScreen(bool aFullScreen) {
+ // Directly update the size mode here so a later call SetSizeMode does
+ // nothing.
+ if (aFullScreen) {
+ if (mSizeMode != nsSizeMode_Fullscreen) {
+ mLastSizeMode = mSizeMode;
+ }
+ mSizeMode = nsSizeMode_Fullscreen;
+ } else {
+ mSizeMode = mLastSizeMode;
+ }
+
+ // Notify the listener first so size mode change events are triggered before
+ // resize events.
+ if (mWidgetListener) {
+ mWidgetListener->SizeModeChanged(mSizeMode);
+ }
+
+ // Real widget backends don't seem to follow a common approach for
+ // when and how many resize events are triggered during fullscreen
+ // transitions. InfallibleMakeFullScreen will trigger a resize, but it
+ // will be ignored if still transitioning to fullscreen, so it must be
+ // triggered on the next tick.
+ RefPtr<HeadlessWidget> self(this);
+ NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "HeadlessWidget::MakeFullScreen", [self, aFullScreen]() -> void {
+ self->InfallibleMakeFullScreen(aFullScreen);
+ }));
+
+ return NS_OK;
+}
+
+nsresult HeadlessWidget::AttachNativeKeyEvent(WidgetKeyboardEvent& aEvent) {
+ HeadlessKeyBindings& bindings = HeadlessKeyBindings::GetInstance();
+ return bindings.AttachNativeKeyEvent(aEvent);
+}
+
+bool HeadlessWidget::GetEditCommands(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands) {
+ // Validate the arguments.
+ if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
+ return false;
+ }
+
+ Maybe<WritingMode> writingMode;
+ if (aEvent.NeedsToRemapNavigationKey()) {
+ if (RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher()) {
+ writingMode = dispatcher->MaybeQueryWritingModeAtSelection();
+ }
+ }
+
+ HeadlessKeyBindings& bindings = HeadlessKeyBindings::GetInstance();
+ bindings.GetEditCommands(aType, aEvent, writingMode, aCommands);
+ return true;
+}
+
+nsresult HeadlessWidget::DispatchEvent(WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) {
+#ifdef DEBUG
+ debug_DumpEvent(stdout, aEvent->mWidget, aEvent, "HeadlessWidget", 0);
+#endif
+
+ aStatus = nsEventStatus_eIgnore;
+
+ if (mAttachedWidgetListener) {
+ aStatus = mAttachedWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
+ } else if (mWidgetListener) {
+ aStatus = mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
+ }
+
+ return NS_OK;
+}
+
+nsresult HeadlessWidget::SynthesizeNativeMouseEvent(
+ LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
+ MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
+ nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "mouseevent");
+ EventMessage msg;
+ switch (aNativeMessage) {
+ case NativeMouseMessage::Move:
+ msg = eMouseMove;
+ break;
+ case NativeMouseMessage::ButtonDown:
+ msg = eMouseDown;
+ break;
+ case NativeMouseMessage::ButtonUp:
+ msg = eMouseUp;
+ break;
+ case NativeMouseMessage::EnterWindow:
+ case NativeMouseMessage::LeaveWindow:
+ MOZ_ASSERT_UNREACHABLE("Unsupported synthesized mouse event");
+ return NS_ERROR_UNEXPECTED;
+ }
+ WidgetMouseEvent event(true, msg, this, WidgetMouseEvent::eReal);
+ event.mRefPoint = aPoint - WidgetToScreenOffset();
+ if (msg == eMouseDown || msg == eMouseUp) {
+ event.mButton = aButton;
+ }
+ if (msg == eMouseDown) {
+ event.mClickCount = 1;
+ }
+ event.AssignEventTime(WidgetEventTime());
+ DispatchInputEvent(&event);
+ return NS_OK;
+}
+
+nsresult HeadlessWidget::SynthesizeNativeMouseScrollEvent(
+ mozilla::LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage,
+ double aDeltaX, double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "mousescrollevent");
+ printf(">>> DEBUG_ME: Synth: aDeltaY=%f\n", aDeltaY);
+ // The various platforms seem to handle scrolling deltas differently,
+ // but the following seems to emulate it well enough.
+ WidgetWheelEvent event(true, eWheel, this);
+ event.mDeltaMode = MOZ_HEADLESS_SCROLL_DELTA_MODE;
+ event.mIsNoLineOrPageDelta = true;
+ event.mDeltaX = -aDeltaX * MOZ_HEADLESS_SCROLL_MULTIPLIER;
+ event.mDeltaY = -aDeltaY * MOZ_HEADLESS_SCROLL_MULTIPLIER;
+ event.mDeltaZ = -aDeltaZ * MOZ_HEADLESS_SCROLL_MULTIPLIER;
+ event.mRefPoint = aPoint - WidgetToScreenOffset();
+ event.AssignEventTime(WidgetEventTime());
+ DispatchInputEvent(&event);
+ return NS_OK;
+}
+
+nsresult HeadlessWidget::SynthesizeNativeTouchPoint(
+ uint32_t aPointerId, TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint, double aPointerPressure,
+ uint32_t aPointerOrientation, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ MOZ_ASSERT(NS_IsMainThread());
+ if (aPointerState == TOUCH_HOVER) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!mSynthesizedTouchInput) {
+ mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
+ }
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
+ mSynthesizedTouchInput.get(), TimeStamp::Now(), aPointerId, aPointerState,
+ pointInWindow, aPointerPressure, aPointerOrientation);
+ DispatchTouchInput(inputToDispatch);
+ return NS_OK;
+}
+
+nsresult HeadlessWidget::SynthesizeNativeTouchPadPinch(
+ TouchpadGesturePhase aEventPhase, float aScale, LayoutDeviceIntPoint aPoint,
+ int32_t aModifierFlags) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PinchGestureInput::PinchGestureType pinchGestureType =
+ PinchGestureInput::PINCHGESTURE_SCALE;
+ ScreenCoord CurrentSpan;
+ ScreenCoord PreviousSpan;
+ switch (aEventPhase) {
+ case PHASE_BEGIN:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_START;
+ CurrentSpan = aScale;
+ PreviousSpan = 0.999;
+ break;
+
+ case PHASE_UPDATE:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
+ if (aScale == mLastPinchSpan) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ CurrentSpan = aScale;
+ PreviousSpan = mLastPinchSpan;
+ break;
+
+ case PHASE_END:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_END;
+ CurrentSpan = aScale;
+ PreviousSpan = mLastPinchSpan;
+ break;
+
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ ScreenPoint touchpadPoint = ViewAs<ScreenPixel>(
+ aPoint - WidgetToScreenOffset(),
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+ // The headless widget does not support modifiers.
+ // Do not pass `aModifierFlags` because it contains native modifier values.
+ PinchGestureInput inputToDispatch(
+ pinchGestureType, PinchGestureInput::TRACKPAD, TimeStamp::Now(),
+ ExternalPoint(0, 0), touchpadPoint,
+ 100.0 * ((aEventPhase == PHASE_END) ? ScreenCoord(1.f) : CurrentSpan),
+ 100.0 * ((aEventPhase == PHASE_END) ? ScreenCoord(1.f) : PreviousSpan),
+ 0);
+
+ if (!inputToDispatch.SetLineOrPageDeltaY(this)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mLastPinchSpan = aScale;
+ DispatchPinchGestureInput(inputToDispatch);
+ return NS_OK;
+}
+
+nsresult HeadlessWidget::SynthesizeNativeTouchpadPan(
+ TouchpadGesturePhase aEventPhase, LayoutDeviceIntPoint aPoint,
+ double aDeltaX, double aDeltaY, int32_t aModifierFlags,
+ nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "touchpadpanevent");
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PanGestureInput::PanGestureType eventType = PanGestureInput::PANGESTURE_PAN;
+ switch (aEventPhase) {
+ case PHASE_BEGIN:
+ eventType = PanGestureInput::PANGESTURE_START;
+ break;
+ case PHASE_UPDATE:
+ eventType = PanGestureInput::PANGESTURE_PAN;
+ break;
+ case PHASE_END:
+ eventType = PanGestureInput::PANGESTURE_END;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ ScreenPoint touchpadPoint = ViewAs<ScreenPixel>(
+ aPoint - WidgetToScreenOffset(),
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+ PanGestureInput input(eventType, TimeStamp::Now(), touchpadPoint,
+ ScreenPoint(float(aDeltaX), float(aDeltaY)),
+ // Same as SynthesizeNativeTouchPadPinch case we ignore
+ // aModifierFlags.
+ 0);
+
+ input.mSimulateMomentum =
+ Preferences::GetBool("apz.test.headless.simulate_momentum");
+
+ DispatchPanGestureInput(input);
+
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla