summaryrefslogtreecommitdiffstats
path: root/widget/headless/HeadlessWidget.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /widget/headless/HeadlessWidget.cpp
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--widget/headless/HeadlessWidget.cpp503
1 files changed, 503 insertions, 0 deletions
diff --git a/widget/headless/HeadlessWidget.cpp b/widget/headless/HeadlessWidget.cpp
new file mode 100644
index 0000000000..f4f3acda7e
--- /dev/null
+++ b/widget/headless/HeadlessWidget.cpp
@@ -0,0 +1,503 @@
+/* -*- 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 "HeadlessCompositorWidget.h"
+#include "Layers.h"
+#include "BasicLayers.h"
+#include "BasicEvents.h"
+#include "MouseEvents.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/widget/HeadlessWidgetTypes.h"
+#include "mozilla/widget/PlatformWidgetTypes.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),
+ mTopLevel(nullptr),
+ mCompositorWidget(nullptr),
+ 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,
+ nsWidgetInitData* aInitData) {
+ MOZ_ASSERT(!aNativeParent, "No native parents for headless widgets.");
+
+ BaseCreate(nullptr, aInitData);
+
+ mBounds = aRect;
+ mRestoreBounds = aRect;
+
+ if (aParent) {
+ mTopLevel = aParent->GetTopLevelWidget();
+ } else {
+ mTopLevel = this;
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIWidget> HeadlessWidget::CreateChild(
+ const LayoutDeviceIntRect& aRect, nsWidgetInitData* 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(mTopLevel == this || mWindowType == eWindowType_dialog ||
+ mWindowType == eWindowType_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.
+ if (aState && (mTopLevel == this || mWindowType == eWindowType_dialog ||
+ mWindowType == eWindowType_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 == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog) {
+ SetSizeMode(nsSizeMode_Normal);
+ }
+
+ // 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(x, y) && mWindowType != eWindowType_popup) {
+ return;
+ }
+
+ mBounds.MoveTo(x, y);
+ NotifyRollupGeometryChange();
+}
+
+LayoutDeviceIntPoint HeadlessWidget::WidgetToScreenOffset() {
+ return mTopLevel->GetBounds().TopLeft();
+}
+
+LayerManager* HeadlessWidget::GetLayerManager(
+ PLayerTransactionChild* aShadowManager, LayersBackend aBackendHint,
+ LayerManagerPersistence aPersistence) {
+ return nsBaseWidget::GetLayerManager(aShadowManager, aBackendHint,
+ aPersistence);
+}
+
+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);
+ ConstrainSize(&width, &height);
+ mBounds.SizeTo(LayoutDeviceIntSize(width, height));
+
+ 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) {
+ if (!mBounds.IsEqualXY(aX, aY)) {
+ NotifyWindowMoved(aX, aY);
+ }
+ return Resize(aWidth, aHeight, aRepaint);
+}
+
+void HeadlessWidget::SetSizeMode(nsSizeMode aMode) {
+ LOG(("HeadlessWidget::SetSizeMode [%p] %d\n", (void*)this, aMode));
+
+ if (aMode == mSizeMode) {
+ return;
+ }
+
+ nsBaseWidget::SetSizeMode(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: {
+ Resize(mRestoreBounds.X(), mRestoreBounds.Y(), 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))) {
+ Resize(0, 0, 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,
+ nsIScreen* aTargetScreen) {
+ // 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);
+ mWidgetListener->FullscreenChanged(aFullScreen);
+ }
+
+ // 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);
+ nsCOMPtr<nsIScreen> targetScreen(aTargetScreen);
+ NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "HeadlessWidget::MakeFullScreen",
+ [self, targetScreen, aFullScreen]() -> void {
+ self->InfallibleMakeFullScreen(aFullScreen, targetScreen);
+ }));
+
+ 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;
+ }
+
+ HeadlessKeyBindings& bindings = HeadlessKeyBindings::GetInstance();
+ bindings.GetEditCommands(aType, aEvent, 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,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "mouseevent");
+ EventMessage msg;
+ switch (aNativeMessage) {
+ case MOZ_HEADLESS_MOUSE_MOVE:
+ msg = eMouseMove;
+ break;
+ case MOZ_HEADLESS_MOUSE_DOWN:
+ msg = eMouseDown;
+ break;
+ case MOZ_HEADLESS_MOUSE_UP:
+ msg = eMouseUp;
+ break;
+ default:
+ 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 = MouseButton::ePrimary;
+ }
+ 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(), PR_IntervalNow(), TimeStamp::Now(),
+ aPointerId, aPointerState, pointInWindow, aPointerPressure,
+ aPointerOrientation);
+ DispatchTouchInput(inputToDispatch);
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla