summaryrefslogtreecommitdiffstats
path: root/widget/gtk/nsWindow.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /widget/gtk/nsWindow.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/gtk/nsWindow.cpp')
-rw-r--r--widget/gtk/nsWindow.cpp9813
1 files changed, 9813 insertions, 0 deletions
diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp
new file mode 100644
index 0000000000..8ed12c734d
--- /dev/null
+++ b/widget/gtk/nsWindow.cpp
@@ -0,0 +1,9813 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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 "nsWindow.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <dlfcn.h>
+#include <gdk/gdkkeysyms.h>
+#include <wchar.h>
+
+#include "VsyncSource.h"
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxImageSurface.h"
+#include "gfxPlatformGtk.h"
+#include "gfxUtils.h"
+#include "GLContextProvider.h"
+#include "GLContext.h"
+#include "GtkCompositorWidget.h"
+#include "gtkdrawing.h"
+#include "imgIContainer.h"
+#include "InputData.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Components.h"
+#include "mozilla/GRefPtr.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/WheelEventBinding.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/KnowsCompositor.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/layers/APZInputBridge.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/NativeKeyBindingsType.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_mozilla.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/SwipeTracker.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/WidgetUtils.h"
+#include "mozilla/WritingModes.h"
+#ifdef MOZ_X11
+# include "mozilla/X11Util.h"
+#endif
+#include "mozilla/XREAppData.h"
+#include "NativeKeyBindings.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsAppRunner.h"
+#include "nsDragService.h"
+#include "nsGTKToolkit.h"
+#include "nsGtkKeyUtils.h"
+#include "nsGtkCursors.h"
+#include "nsGfxCIID.h"
+#include "nsGtkUtils.h"
+#include "nsIFile.h"
+#include "nsIGSettingsService.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsImageToPixbuf.h"
+#include "nsINode.h"
+#include "nsIRollupListener.h"
+#include "nsIScreenManager.h"
+#include "nsIUserIdleServiceInternal.h"
+#include "nsIWidgetListener.h"
+#include "nsLayoutUtils.h"
+#include "nsMenuPopupFrame.h"
+#include "nsPresContext.h"
+#include "nsShmImage.h"
+#include "nsString.h"
+#include "nsWidgetsCID.h"
+#include "nsViewManager.h"
+#include "nsXPLookAndFeel.h"
+#include "prlink.h"
+#include "Screen.h"
+#include "ScreenHelperGTK.h"
+#include "SystemTimeConverter.h"
+#include "WidgetUtilsGtk.h"
+
+#ifdef ACCESSIBILITY
+# include "mozilla/a11y/LocalAccessible.h"
+# include "mozilla/a11y/Platform.h"
+# include "nsAccessibilityService.h"
+#endif
+
+#ifdef MOZ_X11
+# include <gdk/gdkkeysyms-compat.h>
+# include <X11/Xatom.h>
+# include <X11/extensions/XShm.h>
+# include <X11/extensions/shape.h>
+# include "gfxXlibSurface.h"
+# include "GLContextGLX.h" // for GLContextGLX::FindVisual()
+# include "GLContextEGL.h" // for GLContextEGL::FindVisual()
+# include "WindowSurfaceX11Image.h"
+# include "WindowSurfaceX11SHM.h"
+#endif
+#ifdef MOZ_WAYLAND
+# include <gdk/gdkkeysyms-compat.h>
+# include "nsIClipboard.h"
+# include "nsView.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+#ifdef MOZ_X11
+using mozilla::gl::GLContextEGL;
+using mozilla::gl::GLContextGLX;
+#endif
+
+// Don't put more than this many rects in the dirty region, just fluff
+// out to the bounding-box if there are more
+#define MAX_RECTS_IN_REGION 100
+
+#if !GTK_CHECK_VERSION(3, 18, 0)
+
+struct _GdkEventTouchpadPinch {
+ GdkEventType type;
+ GdkWindow* window;
+ gint8 send_event;
+ gint8 phase;
+ gint8 n_fingers;
+ guint32 time;
+ gdouble x;
+ gdouble y;
+ gdouble dx;
+ gdouble dy;
+ gdouble angle_delta;
+ gdouble scale;
+ gdouble x_root, y_root;
+ guint state;
+};
+
+typedef enum {
+ GDK_TOUCHPAD_GESTURE_PHASE_BEGIN,
+ GDK_TOUCHPAD_GESTURE_PHASE_UPDATE,
+ GDK_TOUCHPAD_GESTURE_PHASE_END,
+ GDK_TOUCHPAD_GESTURE_PHASE_CANCEL
+} GdkTouchpadGesturePhase;
+
+gint GDK_TOUCHPAD_GESTURE_MASK = 1 << 24;
+GdkEventType GDK_TOUCHPAD_PINCH = static_cast<GdkEventType>(42);
+
+#endif
+
+const gint kEvents = GDK_TOUCHPAD_GESTURE_MASK | GDK_EXPOSURE_MASK |
+ GDK_STRUCTURE_MASK | GDK_VISIBILITY_NOTIFY_MASK |
+ GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_SMOOTH_SCROLL_MASK | GDK_TOUCH_MASK | GDK_SCROLL_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_PROPERTY_CHANGE_MASK;
+
+/* utility functions */
+static bool is_mouse_in_window(GdkWindow* aWindow, gdouble aMouseX,
+ gdouble aMouseY);
+static nsWindow* get_window_for_gtk_widget(GtkWidget* widget);
+static nsWindow* get_window_for_gdk_window(GdkWindow* window);
+static GtkWidget* get_gtk_widget_for_gdk_window(GdkWindow* window);
+static GdkCursor* get_gtk_cursor(nsCursor aCursor);
+static GdkWindow* get_inner_gdk_window(GdkWindow* aWindow, gint x, gint y,
+ gint* retx, gint* rety);
+
+/* callbacks from widgets */
+static gboolean expose_event_cb(GtkWidget* widget, cairo_t* cr);
+static gboolean configure_event_cb(GtkWidget* widget, GdkEventConfigure* event);
+static void widget_map_cb(GtkWidget* widget);
+static void widget_unmap_cb(GtkWidget* widget);
+static void widget_unrealize_cb(GtkWidget* widget);
+static void size_allocate_cb(GtkWidget* widget, GtkAllocation* allocation);
+static void toplevel_window_size_allocate_cb(GtkWidget* widget,
+ GtkAllocation* allocation);
+static gboolean delete_event_cb(GtkWidget* widget, GdkEventAny* event);
+static gboolean enter_notify_event_cb(GtkWidget* widget,
+ GdkEventCrossing* event);
+static gboolean leave_notify_event_cb(GtkWidget* widget,
+ GdkEventCrossing* event);
+static gboolean motion_notify_event_cb(GtkWidget* widget,
+ GdkEventMotion* event);
+MOZ_CAN_RUN_SCRIPT static gboolean button_press_event_cb(GtkWidget* widget,
+ GdkEventButton* event);
+static gboolean button_release_event_cb(GtkWidget* widget,
+ GdkEventButton* event);
+static gboolean focus_in_event_cb(GtkWidget* widget, GdkEventFocus* event);
+static gboolean focus_out_event_cb(GtkWidget* widget, GdkEventFocus* event);
+static gboolean key_press_event_cb(GtkWidget* widget, GdkEventKey* event);
+static gboolean key_release_event_cb(GtkWidget* widget, GdkEventKey* event);
+static gboolean property_notify_event_cb(GtkWidget* widget,
+ GdkEventProperty* event);
+static gboolean scroll_event_cb(GtkWidget* widget, GdkEventScroll* event);
+
+static void hierarchy_changed_cb(GtkWidget* widget,
+ GtkWidget* previous_toplevel);
+static gboolean window_state_event_cb(GtkWidget* widget,
+ GdkEventWindowState* event);
+static void settings_xft_dpi_changed_cb(GtkSettings* settings,
+ GParamSpec* pspec, nsWindow* data);
+static void check_resize_cb(GtkContainer* container, gpointer user_data);
+static void screen_composited_changed_cb(GdkScreen* screen, gpointer user_data);
+static void widget_composited_changed_cb(GtkWidget* widget, gpointer user_data);
+
+static void scale_changed_cb(GtkWidget* widget, GParamSpec* aPSpec,
+ gpointer aPointer);
+static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent);
+static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent);
+
+static nsWindow* GetFirstNSWindowForGDKWindow(GdkWindow* aGdkWindow);
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+#ifdef MOZ_X11
+static GdkFilterReturn popup_take_focus_filter(GdkXEvent* gdk_xevent,
+ GdkEvent* event, gpointer data);
+#endif /* MOZ_X11 */
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+static gboolean drag_motion_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY, guint aTime, gpointer aData);
+static void drag_leave_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, guint aTime,
+ gpointer aData);
+static gboolean drag_drop_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY, guint aTime, gpointer aData);
+static void drag_data_received_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY,
+ GtkSelectionData* aSelectionData,
+ guint aInfo, guint32 aTime,
+ gpointer aData);
+
+/* initialization static functions */
+static nsresult initialize_prefs(void);
+
+static guint32 sLastUserInputTime = GDK_CURRENT_TIME;
+
+static SystemTimeConverter<guint32>& TimeConverter() {
+ static SystemTimeConverter<guint32> sTimeConverterSingleton;
+ return sTimeConverterSingleton;
+}
+
+bool nsWindow::sTransparentMainWindow = false;
+
+// forward declare from mozgtk
+extern "C" MOZ_EXPORT void mozgtk_linker_holder();
+
+namespace mozilla {
+
+#ifdef MOZ_X11
+class CurrentX11TimeGetter {
+ public:
+ explicit CurrentX11TimeGetter(GdkWindow* aWindow)
+ : mWindow(aWindow), mAsyncUpdateStart() {}
+
+ guint32 GetCurrentTime() const { return gdk_x11_get_server_time(mWindow); }
+
+ void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
+ // Check for in-flight request
+ if (!mAsyncUpdateStart.IsNull()) {
+ return;
+ }
+ mAsyncUpdateStart = aNow;
+
+ Display* xDisplay = GDK_WINDOW_XDISPLAY(mWindow);
+ Window xWindow = GDK_WINDOW_XID(mWindow);
+ unsigned char c = 'a';
+ Atom timeStampPropAtom = TimeStampPropAtom();
+ XChangeProperty(xDisplay, xWindow, timeStampPropAtom, timeStampPropAtom, 8,
+ PropModeReplace, &c, 1);
+ XFlush(xDisplay);
+ }
+
+ gboolean PropertyNotifyHandler(GtkWidget* aWidget, GdkEventProperty* aEvent) {
+ if (aEvent->atom != gdk_x11_xatom_to_atom(TimeStampPropAtom())) {
+ return FALSE;
+ }
+
+ guint32 eventTime = aEvent->time;
+ TimeStamp lowerBound = mAsyncUpdateStart;
+
+ TimeConverter().CompensateForBackwardsSkew(eventTime, lowerBound);
+ mAsyncUpdateStart = TimeStamp();
+ return TRUE;
+ }
+
+ private:
+ static Atom TimeStampPropAtom() {
+ return gdk_x11_get_xatom_by_name_for_display(gdk_display_get_default(),
+ "GDK_TIMESTAMP_PROP");
+ }
+
+ // This is safe because this class is stored as a member of mWindow and
+ // won't outlive it.
+ GdkWindow* mWindow;
+ TimeStamp mAsyncUpdateStart;
+};
+#endif
+
+} // namespace mozilla
+
+// The window from which the focus manager asks us to dispatch key events.
+static nsWindow* gFocusWindow = nullptr;
+static bool gBlockActivateEvent = false;
+static bool gGlobalsInitialized = false;
+static bool gUseAspectRatio = true;
+static uint32_t gLastTouchID = 0;
+// See Bug 1777269 for details. We don't know if the suspected leave notify
+// event is a correct one when we get it.
+// Store it and issue it later from enter notify event if it's correct,
+// throw it away otherwise.
+static GUniquePtr<GdkEventCrossing> sStoredLeaveNotifyEvent;
+
+#define NS_WINDOW_TITLE_MAX_LENGTH 4095
+
+// cursor cache
+static GdkCursor* gCursorCache[eCursorCount];
+
+// Sometimes this actually also includes the state of the modifier keys, but
+// only the button state bits are used.
+static guint gButtonState;
+
+static inline int32_t GetBitmapStride(int32_t width) {
+#if defined(MOZ_X11)
+ return (width + 7) / 8;
+#else
+ return cairo_format_stride_for_width(CAIRO_FORMAT_A1, width);
+#endif
+}
+
+static inline bool TimestampIsNewerThan(guint32 a, guint32 b) {
+ // Timestamps are just the least significant bits of a monotonically
+ // increasing function, and so the use of unsigned overflow arithmetic.
+ return a - b <= G_MAXUINT32 / 2;
+}
+
+static void UpdateLastInputEventTime(void* aGdkEvent) {
+ nsCOMPtr<nsIUserIdleServiceInternal> idleService =
+ do_GetService("@mozilla.org/widget/useridleservice;1");
+ if (idleService) {
+ idleService->ResetIdleTimeOut(0);
+ }
+
+ guint timestamp = gdk_event_get_time(static_cast<GdkEvent*>(aGdkEvent));
+ if (timestamp == GDK_CURRENT_TIME) return;
+
+ sLastUserInputTime = timestamp;
+}
+
+// Don't set parent (transient for) if nothing changes.
+// gtk_window_set_transient_for() blows up wl_subsurfaces used by aWindow
+// even if aParent is the same.
+static void GtkWindowSetTransientFor(GtkWindow* aWindow, GtkWindow* aParent) {
+ GtkWindow* parent = gtk_window_get_transient_for(aWindow);
+ if (parent != aParent) {
+ gtk_window_set_transient_for(aWindow, aParent);
+ }
+}
+
+#define gtk_window_set_transient_for(a, b) \
+ { \
+ MOZ_ASSERT_UNREACHABLE( \
+ "gtk_window_set_transient_for() can't be used directly."); \
+ }
+
+nsWindow::nsWindow()
+ : mIsDestroyed(false),
+ mIsShown(false),
+ mNeedsShow(false),
+ mIsMapped(false),
+ mEnabled(true),
+ mCreated(false),
+ mHandleTouchEvent(false),
+ mIsDragPopup(false),
+ mWindowScaleFactorChanged(true),
+ mCompositedScreen(gdk_screen_is_composited(gdk_screen_get_default())),
+ mIsAccelerated(false),
+ mWindowShouldStartDragging(false),
+ mHasMappedToplevel(false),
+ mRetryPointerGrab(false),
+ mPanInProgress(false),
+ mDrawToContainer(false),
+ mTitlebarBackdropState(false),
+ mIsPIPWindow(false),
+ mIsWaylandPanelWindow(false),
+ mIsChildWindow(false),
+ mAlwaysOnTop(false),
+ mNoAutoHide(false),
+ mIsTransparent(false),
+ mHasReceivedSizeAllocate(false),
+ mWidgetCursorLocked(false),
+ mPopupTrackInHierarchy(false),
+ mPopupTrackInHierarchyConfigured(false),
+ mHiddenPopupPositioned(false),
+ mTransparencyBitmapForTitlebar(false),
+ mHasAlphaVisual(false),
+ mPopupAnchored(false),
+ mPopupContextMenu(false),
+ mPopupMatchesLayout(false),
+ mPopupChanged(false),
+ mPopupTemporaryHidden(false),
+ mPopupClosed(false),
+ mPopupUseMoveToRect(false),
+ mWaitingForMoveToRectCallback(false),
+ mMovedAfterMoveToRect(false),
+ mResizedAfterMoveToRect(false),
+ mConfiguredClearColor(false),
+ mGotNonBlankPaint(false),
+ mNeedsToRetryCapturingMouse(false) {
+ mWindowType = WindowType::Child;
+ mSizeConstraints.mMaxSize = GetSafeWindowSize(mSizeConstraints.mMaxSize);
+
+ if (!gGlobalsInitialized) {
+ gGlobalsInitialized = true;
+
+ // It's OK if either of these fail, but it may not be one day.
+ initialize_prefs();
+
+#ifdef MOZ_WAYLAND
+ // Wayland provides clipboard data to application on focus-in event
+ // so we need to init our clipboard hooks before we create window
+ // and get focus.
+ if (GdkIsWaylandDisplay()) {
+ nsCOMPtr<nsIClipboard> clipboard =
+ do_GetService("@mozilla.org/widget/clipboard;1");
+ NS_ASSERTION(clipboard, "Failed to init clipboard!");
+ }
+#endif
+ }
+ // Dummy call to mozgtk to prevent the linker from removing
+ // the dependency with --as-needed.
+ // see toolkit/library/moz.build for details.
+ mozgtk_linker_holder();
+}
+
+nsWindow::~nsWindow() {
+ LOG("nsWindow::~nsWindow()");
+ Destroy();
+}
+
+/* static */
+void nsWindow::ReleaseGlobals() {
+ for (auto& cursor : gCursorCache) {
+ if (cursor) {
+ g_object_unref(cursor);
+ cursor = nullptr;
+ }
+ }
+}
+
+void nsWindow::DispatchActivateEvent(void) {
+ NS_ASSERTION(mContainer || mIsDestroyed,
+ "DispatchActivateEvent only intended for container windows");
+
+#ifdef ACCESSIBILITY
+ DispatchActivateEventAccessible();
+#endif // ACCESSIBILITY
+
+ if (mWidgetListener) mWidgetListener->WindowActivated();
+}
+
+void nsWindow::DispatchDeactivateEvent() {
+ if (mWidgetListener) {
+ mWidgetListener->WindowDeactivated();
+ }
+
+#ifdef ACCESSIBILITY
+ DispatchDeactivateEventAccessible();
+#endif // ACCESSIBILITY
+}
+
+void nsWindow::DispatchResized() {
+ LOG("nsWindow::DispatchResized() size [%d, %d]", (int)(mBounds.width),
+ (int)(mBounds.height));
+
+ mNeedsDispatchSize = LayoutDeviceIntSize(-1, -1);
+ if (mWidgetListener) {
+ mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
+ }
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
+ }
+}
+
+void nsWindow::MaybeDispatchResized() {
+ if (mNeedsDispatchSize != LayoutDeviceIntSize(-1, -1) && !mIsDestroyed) {
+ mBounds.SizeTo(mNeedsDispatchSize);
+ // Check mBounds size
+ if (mCompositorSession &&
+ !wr::WindowSizeSanityCheck(mBounds.width, mBounds.height)) {
+ gfxCriticalNoteOnce << "Invalid mBounds in MaybeDispatchResized "
+ << mBounds << " size state " << mSizeMode;
+ }
+
+ if (mWindowType == WindowType::TopLevel) {
+ UpdateTopLevelOpaqueRegion();
+ }
+
+ // Notify the GtkCompositorWidget of a ClientSizeChange
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
+ }
+
+ DispatchResized();
+ }
+}
+
+nsIWidgetListener* nsWindow::GetListener() {
+ return mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener;
+}
+
+nsresult nsWindow::DispatchEvent(WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) {
+#ifdef DEBUG
+ debug_DumpEvent(stdout, aEvent->mWidget, aEvent, "something", 0);
+#endif
+ aStatus = nsEventStatus_eIgnore;
+ nsIWidgetListener* listener = GetListener();
+ if (listener) {
+ aStatus = listener->HandleEvent(aEvent, mUseAttachedEvents);
+ }
+
+ return NS_OK;
+}
+
+void nsWindow::OnDestroy(void) {
+ if (mOnDestroyCalled) return;
+
+ mOnDestroyCalled = true;
+
+ // Prevent deletion.
+ nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
+
+ // release references to children, device context, toolkit + app shell
+ nsBaseWidget::OnDestroy();
+
+ // Remove association between this object and its parent and siblings.
+ nsBaseWidget::Destroy();
+ mParent = nullptr;
+
+ NotifyWindowDestroyed();
+}
+
+bool nsWindow::AreBoundsSane() {
+ // Check requested size, as mBounds might not have been updated.
+ return !mLastSizeRequest.IsEmpty();
+}
+
+// Walk the list of child windows and call destroy on them.
+void nsWindow::DestroyChildWindows() {
+ LOG("nsWindow::DestroyChildWindows()");
+ if (!mGdkWindow) {
+ return;
+ }
+ while (GList* children = gdk_window_peek_children(mGdkWindow)) {
+ GdkWindow* child = GDK_WINDOW(children->data);
+ nsWindow* kid = get_window_for_gdk_window(child);
+ if (kid) {
+ kid->Destroy();
+ }
+ }
+}
+
+void nsWindow::Destroy() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+
+ if (mIsDestroyed || !mCreated) {
+ return;
+ }
+
+ LOG("nsWindow::Destroy\n");
+
+ mIsDestroyed = true;
+ mCreated = false;
+
+ MozClearHandleID(mCompositorPauseTimeoutID, g_source_remove);
+
+ ClearTransparencyBitmap();
+
+#ifdef MOZ_WAYLAND
+ // Shut down our local vsync source
+ if (mWaylandVsyncSource) {
+ mWaylandVsyncSource->Shutdown();
+ mWaylandVsyncSource = nullptr;
+ }
+ mWaylandVsyncDispatcher = nullptr;
+#endif
+
+ /** Need to clean our LayerManager up while still alive */
+ DestroyLayerManager();
+
+ // Ensure any resources assigned to the window get cleaned up first
+ // to avoid double-freeing.
+ mSurfaceProvider.CleanupResources();
+
+ g_signal_handlers_disconnect_by_data(gtk_settings_get_default(), this);
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ if (rollupListener) {
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (static_cast<nsIWidget*>(this) == rollupWidget) {
+ rollupListener->Rollup({});
+ }
+ }
+
+ // dragService will be null after shutdown of the service manager.
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ if (dragService && this == dragService->GetMostRecentDestWindow()) {
+ dragService->ScheduleLeaveEvent();
+ }
+
+ NativeShow(false);
+
+ if (mIMContext) {
+ mIMContext->OnDestroyWindow(this);
+ }
+
+ // make sure that we remove ourself as the focus window
+ if (gFocusWindow == this) {
+ LOG("automatically losing focus...\n");
+ gFocusWindow = nullptr;
+ }
+
+ if (sStoredLeaveNotifyEvent) {
+ nsWindow* window =
+ get_window_for_gdk_window(sStoredLeaveNotifyEvent->window);
+ if (window == this) {
+ sStoredLeaveNotifyEvent = nullptr;
+ }
+ }
+
+ // We need to detach accessible object here because mContainer is a custom
+ // widget and doesn't call gtk_widget_real_destroy() from destroy handler
+ // as regular widgets.
+ if (AtkObject* ac = gtk_widget_get_accessible(GTK_WIDGET(mContainer))) {
+ gtk_accessible_set_widget(GTK_ACCESSIBLE(ac), nullptr);
+ }
+
+ gtk_widget_destroy(mShell);
+ mShell = nullptr;
+ mContainer = nullptr;
+
+ MOZ_ASSERT(!mGdkWindow,
+ "mGdkWindow should be NULL when mContainer is destroyed");
+
+#ifdef ACCESSIBILITY
+ if (mRootAccessible) {
+ mRootAccessible = nullptr;
+ }
+#endif
+
+ // Save until last because OnDestroy() may cause us to be deleted.
+ OnDestroy();
+}
+
+nsIWidget* nsWindow::GetParent() { return mParent; }
+
+float nsWindow::GetDPI() {
+ float dpi = 96.0f;
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ if (screen) {
+ screen->GetDpi(&dpi);
+ }
+ return dpi;
+}
+
+double nsWindow::GetDefaultScaleInternal() { return FractionalScaleFactor(); }
+
+DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScale() {
+#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay()) {
+ return DesktopToLayoutDeviceScale(GdkCeiledScaleFactor());
+ }
+#endif
+
+ // In Gtk/X11, we manage windows using device pixels.
+ return DesktopToLayoutDeviceScale(1.0);
+}
+
+DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScaleByScreen() {
+#ifdef MOZ_WAYLAND
+ // In Wayland there's no way to get absolute position of the window and use it
+ // to determine the screen factor of the monitor on which the window is
+ // placed. The window is notified of the current scale factor but not at this
+ // point, so the GdkScaleFactor can return wrong value which can lead to wrong
+ // popup placement. We need to use parent's window scale factor for the new
+ // one.
+ if (GdkIsWaylandDisplay()) {
+ nsView* view = nsView::GetViewFor(this);
+ if (view) {
+ nsView* parentView = view->GetParent();
+ if (parentView) {
+ nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr);
+ if (parentWidget) {
+ return DesktopToLayoutDeviceScale(
+ parentWidget->RoundsWidgetCoordinatesTo());
+ }
+ NS_WARNING("Widget has no parent");
+ }
+ } else {
+ NS_WARNING("Cannot find widget view");
+ }
+ }
+#endif
+ return nsBaseWidget::GetDesktopToDeviceScale();
+}
+
+// Reparent a child window to a new parent.
+void nsWindow::SetParent(nsIWidget* aNewParent) {
+ LOG("nsWindow::SetParent() new parent %p", aNewParent);
+ if (!mIsChildWindow) {
+ NS_WARNING("Used by child widgets only");
+ return;
+ }
+
+ nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
+ if (mParent) {
+ mParent->RemoveChild(this);
+ }
+ mParent = aNewParent;
+
+ // We're already deleted, quit.
+ if (!mGdkWindow || mIsDestroyed || !aNewParent) {
+ return;
+ }
+ aNewParent->AddChild(this);
+
+ auto* newParent = static_cast<nsWindow*>(aNewParent);
+
+ // New parent is deleted, quit.
+ if (newParent->mIsDestroyed) {
+ Destroy();
+ return;
+ }
+
+ GdkWindow* window = GetToplevelGdkWindow();
+ GdkWindow* parentWindow = newParent->GetToplevelGdkWindow();
+ LOG(" child GdkWindow %p set parent GdkWindow %p", window, parentWindow);
+ gdk_window_reparent(window, parentWindow, 0, 0);
+ SetHasMappedToplevel(newParent && newParent->mHasMappedToplevel);
+}
+
+bool nsWindow::WidgetTypeSupportsAcceleration() {
+ if (mWindowType == WindowType::Invisible) {
+ return false;
+ }
+
+ if (IsSmallPopup()) {
+ return false;
+ }
+ // Workaround for Bug 1479135
+ // We draw transparent popups on non-compositing screens by SW as we don't
+ // implement X shape masks in WebRender.
+ if (mWindowType == WindowType::Popup) {
+ return HasRemoteContent() && mCompositedScreen;
+ }
+
+ return true;
+}
+
+void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) {
+ MOZ_ASSERT(aNewParent, "null widget");
+ MOZ_ASSERT(!mIsDestroyed, "");
+ MOZ_ASSERT(!static_cast<nsWindow*>(aNewParent)->mIsDestroyed, "");
+ MOZ_ASSERT(
+ !mParent,
+ "nsWindow::ReparentNativeWidget() works on toplevel windows only.");
+
+ auto* newParent = static_cast<nsWindow*>(aNewParent);
+ GtkWindow* newParentWidget = GTK_WINDOW(newParent->GetGtkWidget());
+
+ LOG("nsWindow::ReparentNativeWidget new parent %p\n", newParent);
+ GtkWindowSetTransientFor(GTK_WINDOW(mShell), newParentWidget);
+}
+
+void nsWindow::SetModal(bool aModal) {
+ LOG("nsWindow::SetModal %d\n", aModal);
+ if (mIsDestroyed) return;
+ gtk_window_set_modal(GTK_WINDOW(mShell), aModal ? TRUE : FALSE);
+}
+
+// nsIWidget method, which means IsShown.
+bool nsWindow::IsVisible() const { return mIsShown; }
+
+bool nsWindow::IsMapped() const {
+ // TODO: Enable for X11 when Mozilla testsuite is moved to new
+ // testing environment from Ubuntu 18.04 which is broken.
+ return GdkIsWaylandDisplay() ? mIsMapped : true;
+}
+
+void nsWindow::RegisterTouchWindow() {
+ mHandleTouchEvent = true;
+ mTouches.Clear();
+}
+
+LayoutDeviceIntPoint nsWindow::GetScreenEdgeSlop() {
+ if (DrawsToCSDTitlebar()) {
+ return GetClientOffset();
+ }
+ return {};
+}
+
+void nsWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
+ if (!mShell || GdkIsWaylandDisplay()) {
+ return;
+ }
+
+ double dpiScale = GetDefaultScale().scale;
+
+ // we need to use the window size in logical screen pixels
+ int32_t logWidth = std::max(NSToIntRound(mBounds.width / dpiScale), 1);
+ int32_t logHeight = std::max(NSToIntRound(mBounds.height / dpiScale), 1);
+
+ /* get our playing field. use the current screen, or failing that
+ for any reason, use device caps for the default screen. */
+ nsCOMPtr<nsIScreenManager> screenmgr =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (!screenmgr) {
+ return;
+ }
+ nsCOMPtr<nsIScreen> screen;
+ screenmgr->ScreenForRect(aPoint.x, aPoint.y, logWidth, logHeight,
+ getter_AddRefs(screen));
+ // We don't have any screen so leave the coordinates as is
+ if (!screen) {
+ return;
+ }
+
+ // For normalized windows, use the desktop work area.
+ // For full screen windows, use the desktop.
+ DesktopIntRect screenRect = mSizeMode == nsSizeMode_Fullscreen
+ ? screen->GetRectDisplayPix()
+ : screen->GetAvailRectDisplayPix();
+
+ // Expand for the decoration size if needed.
+ auto slop =
+ DesktopIntPoint::Round(GetScreenEdgeSlop() / GetDesktopToDeviceScale());
+ screenRect.Inflate(slop.x, slop.y);
+
+ if (aPoint.x < screenRect.x) {
+ aPoint.x = screenRect.x;
+ } else if (aPoint.x >= screenRect.XMost() - logWidth) {
+ aPoint.x = screenRect.XMost() - logWidth;
+ }
+
+ if (aPoint.y < screenRect.y) {
+ aPoint.y = screenRect.y;
+ } else if (aPoint.y >= screenRect.YMost() - logHeight) {
+ aPoint.y = screenRect.YMost() - logHeight;
+ }
+}
+
+void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
+ mSizeConstraints.mMinSize = GetSafeWindowSize(aConstraints.mMinSize);
+ mSizeConstraints.mMaxSize = GetSafeWindowSize(aConstraints.mMaxSize);
+
+ ApplySizeConstraints();
+}
+
+bool nsWindow::DrawsToCSDTitlebar() const {
+ return mSizeMode == nsSizeMode_Normal &&
+ mGtkWindowDecoration == GTK_DECORATION_CLIENT && mDrawInTitlebar;
+}
+
+void nsWindow::AddCSDDecorationSize(int* aWidth, int* aHeight) {
+ if (!DrawsToCSDTitlebar()) {
+ return;
+ }
+ GtkBorder decorationSize = GetCSDDecorationSize(IsPopup());
+ *aWidth += decorationSize.left + decorationSize.right;
+ *aHeight += decorationSize.top + decorationSize.bottom;
+}
+
+#ifdef MOZ_WAYLAND
+bool nsWindow::GetCSDDecorationOffset(int* aDx, int* aDy) {
+ if (!DrawsToCSDTitlebar()) {
+ return false;
+ }
+ GtkBorder decorationSize = GetCSDDecorationSize(IsPopup());
+ *aDx = decorationSize.left;
+ *aDy = decorationSize.top;
+ return true;
+}
+#endif
+
+void nsWindow::ApplySizeConstraints() {
+ if (mShell) {
+ GdkGeometry geometry;
+ geometry.min_width =
+ DevicePixelsToGdkCoordRoundUp(mSizeConstraints.mMinSize.width);
+ geometry.min_height =
+ DevicePixelsToGdkCoordRoundUp(mSizeConstraints.mMinSize.height);
+ geometry.max_width =
+ DevicePixelsToGdkCoordRoundDown(mSizeConstraints.mMaxSize.width);
+ geometry.max_height =
+ DevicePixelsToGdkCoordRoundDown(mSizeConstraints.mMaxSize.height);
+
+ uint32_t hints = 0;
+ if (mSizeConstraints.mMinSize != LayoutDeviceIntSize()) {
+ if (GdkIsWaylandDisplay()) {
+ gtk_widget_set_size_request(GTK_WIDGET(mContainer), geometry.min_width,
+ geometry.min_height);
+ }
+ AddCSDDecorationSize(&geometry.min_width, &geometry.min_height);
+ hints |= GDK_HINT_MIN_SIZE;
+ }
+ if (mSizeConstraints.mMaxSize !=
+ LayoutDeviceIntSize(NS_MAXSIZE, NS_MAXSIZE)) {
+ AddCSDDecorationSize(&geometry.max_width, &geometry.max_height);
+ hints |= GDK_HINT_MAX_SIZE;
+ }
+
+ if (mAspectRatio != 0.0f) {
+ geometry.min_aspect = mAspectRatio;
+ geometry.max_aspect = mAspectRatio;
+ hints |= GDK_HINT_ASPECT;
+ }
+
+ gtk_window_set_geometry_hints(GTK_WINDOW(mShell), nullptr, &geometry,
+ GdkWindowHints(hints));
+ }
+}
+
+void nsWindow::Show(bool aState) {
+ if (aState == mIsShown) return;
+
+ mIsShown = aState;
+
+#ifdef MOZ_LOGGING
+ LOG("nsWindow::Show state %d frame %s\n", aState, GetFrameTag().get());
+ if (!aState && mSourceDragContext && GdkIsWaylandDisplay()) {
+ LOG(" closing Drag&Drop source window, D&D will be canceled!");
+ }
+#endif
+
+ // Ok, someone called show on a window that isn't sized to a sane
+ // value. Mark this window as needing to have Show() called on it
+ // and return.
+ if ((aState && !AreBoundsSane()) || !mCreated) {
+ LOG("\tbounds are insane or window hasn't been created yet\n");
+ mNeedsShow = true;
+ return;
+ }
+
+ // If someone is hiding this widget, clear any needing show flag.
+ if (!aState) mNeedsShow = false;
+
+#ifdef ACCESSIBILITY
+ if (aState && a11y::ShouldA11yBeEnabled()) CreateRootAccessible();
+#endif
+
+ NativeShow(aState);
+}
+
+void nsWindow::ResizeInt(const Maybe<LayoutDeviceIntPoint>& aMove,
+ LayoutDeviceIntSize aSize) {
+ LOG("nsWindow::ResizeInt w:%d h:%d\n", aSize.width, aSize.height);
+ const bool moved = aMove && *aMove != mBounds.TopLeft();
+ if (moved) {
+ mBounds.MoveTo(*aMove);
+ LOG(" with move to left:%d top:%d", aMove->x.value, aMove->y.value);
+ }
+
+ ConstrainSize(&aSize.width, &aSize.height);
+ LOG(" ConstrainSize: w:%d h;%d\n", aSize.width, aSize.height);
+
+ const bool resized = aSize != mLastSizeRequest || mBounds.Size() != aSize;
+#if MOZ_LOGGING
+ LOG(" resized %d aSize [%d, %d] mLastSizeRequest [%d, %d] mBounds [%d, %d]",
+ resized, aSize.width, aSize.height, mLastSizeRequest.width,
+ mLastSizeRequest.height, mBounds.width, mBounds.height);
+#endif
+
+ // For top-level windows, aSize should possibly be
+ // interpreted as frame bounds, but NativeMoveResize treats these as window
+ // bounds (Bug 581866).
+ mLastSizeRequest = aSize;
+ // Check size
+ if (mCompositorSession &&
+ !wr::WindowSizeSanityCheck(aSize.width, aSize.height)) {
+ gfxCriticalNoteOnce << "Invalid aSize in ResizeInt " << aSize
+ << " size state " << mSizeMode;
+ }
+
+ // Recalculate aspect ratio when resized from DOM
+ if (mAspectRatio != 0.0) {
+ LockAspectRatio(true);
+ }
+
+ if (!mCreated) {
+ return;
+ }
+
+ if (!moved && !resized) {
+ LOG(" not moved or resized, quit");
+ return;
+ }
+
+ NativeMoveResize(moved, resized);
+
+ // We optimistically assume size changes immediately in two cases:
+ // 1. Override-redirect window: Size is controlled by only us.
+ // 2. Managed window that has not not yet received a size-allocate event:
+ // Resize() Callers expect initial sizes to be applied synchronously.
+ // If the size request is not honored, then we'll correct in
+ // OnSizeAllocate().
+ //
+ // When a managed window has already received a size-allocate, we cannot
+ // assume we'll always get a notification if our request does not get
+ // honored: "If the configure request has not changed, we don't ever resend
+ // it, because it could mean fighting the user or window manager."
+ // https://gitlab.gnome.org/GNOME/gtk/-/blob/3.24.31/gtk/gtkwindow.c#L9782
+ // So we don't update mBounds until OnSizeAllocate() when we know the
+ // request is granted.
+ bool isOrWillBeVisible = mHasReceivedSizeAllocate || mNeedsShow || mIsShown;
+ if (!isOrWillBeVisible ||
+ gtk_window_get_window_type(GTK_WINDOW(mShell)) == GTK_WINDOW_POPUP) {
+ mBounds.SizeTo(aSize);
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyClientSizeChanged(aSize);
+ }
+ DispatchResized();
+ }
+}
+
+void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
+ LOG("nsWindow::Resize %f %f\n", aWidth, aHeight);
+
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ auto size = LayoutDeviceIntSize::Round(scale * aWidth, scale * aHeight);
+
+ ResizeInt(Nothing(), size);
+}
+
+void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) {
+ LOG("nsWindow::Resize [%f,%f] -> [%f x %f] repaint %d\n", aX, aY, aWidth,
+ aHeight, aRepaint);
+
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ auto size = LayoutDeviceIntSize::Round(scale * aWidth, scale * aHeight);
+ auto topLeft = LayoutDeviceIntPoint::Round(scale * aX, scale * aY);
+
+ ResizeInt(Some(topLeft), size);
+}
+
+void nsWindow::Enable(bool aState) { mEnabled = aState; }
+
+bool nsWindow::IsEnabled() const { return mEnabled; }
+
+void nsWindow::Move(double aX, double aY) {
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t x = NSToIntRound(aX * scale);
+ int32_t y = NSToIntRound(aY * scale);
+
+ LOG("nsWindow::Move to %d x %d\n", x, y);
+
+ if (mSizeMode != nsSizeMode_Normal && (mWindowType == WindowType::TopLevel ||
+ mWindowType == WindowType::Dialog)) {
+ LOG(" size state is not normal, bailing");
+ return;
+ }
+
+ // Since a popup window's x/y coordinates are in relation to to
+ // the parent, the parent might have moved so we always move a
+ // popup window.
+ LOG(" bounds %d x %d\n", mBounds.x, mBounds.y);
+ if (x == mBounds.x && y == mBounds.y && mWindowType != WindowType::Popup) {
+ LOG(" position is the same, return\n");
+ return;
+ }
+
+ // XXX Should we do some AreBoundsSane check here?
+
+ mBounds.x = x;
+ mBounds.y = y;
+
+ if (!mCreated) {
+ LOG(" is not created, return.\n");
+ return;
+ }
+
+ NativeMoveResize(/* move */ true, /* resize */ false);
+}
+
+bool nsWindow::IsPopup() const { return mWindowType == WindowType::Popup; }
+
+bool nsWindow::IsWaylandPopup() const {
+ return GdkIsWaylandDisplay() && IsPopup();
+}
+
+static nsMenuPopupFrame* GetMenuPopupFrame(nsIFrame* aFrame) {
+ return do_QueryFrame(aFrame);
+}
+
+void nsWindow::AppendPopupToHierarchyList(nsWindow* aToplevelWindow) {
+ mWaylandToplevel = aToplevelWindow;
+
+ nsWindow* popup = aToplevelWindow;
+ while (popup && popup->mWaylandPopupNext) {
+ popup = popup->mWaylandPopupNext;
+ }
+ popup->mWaylandPopupNext = this;
+
+ mWaylandPopupPrev = popup;
+ mWaylandPopupNext = nullptr;
+ mPopupChanged = true;
+ mPopupClosed = false;
+}
+
+void nsWindow::RemovePopupFromHierarchyList() {
+ // We're already removed from the popup hierarchy
+ if (!IsInPopupHierarchy()) {
+ return;
+ }
+ mWaylandPopupPrev->mWaylandPopupNext = mWaylandPopupNext;
+ if (mWaylandPopupNext) {
+ mWaylandPopupNext->mWaylandPopupPrev = mWaylandPopupPrev;
+ mWaylandPopupNext->mPopupChanged = true;
+ }
+ mWaylandPopupNext = mWaylandPopupPrev = nullptr;
+}
+
+// Gtk refuses to map popup window with x < 0 && y < 0 relative coordinates
+// see https://gitlab.gnome.org/GNOME/gtk/-/issues/4071
+// as a workaround just fool around and place the popup temporary to 0,0.
+bool nsWindow::WaylandPopupRemoveNegativePosition(int* aX, int* aY) {
+ // https://gitlab.gnome.org/GNOME/gtk/-/issues/4071 applies to temporary
+ // windows only
+ GdkWindow* window = gtk_widget_get_window(mShell);
+ if (!window || gdk_window_get_window_type(window) != GDK_WINDOW_TEMP) {
+ return false;
+ }
+
+ LOG("nsWindow::WaylandPopupRemoveNegativePosition()");
+
+ int x, y;
+ gtk_window_get_position(GTK_WINDOW(mShell), &x, &y);
+ bool moveBack = (x < 0 && y < 0);
+ if (moveBack) {
+ gtk_window_move(GTK_WINDOW(mShell), 0, 0);
+ if (aX) {
+ *aX = x;
+ }
+ if (aY) {
+ *aY = y;
+ }
+ }
+
+ gdk_window_get_geometry(window, &x, &y, nullptr, nullptr);
+ if (x < 0 && y < 0) {
+ gdk_window_move(window, 0, 0);
+ }
+
+ return moveBack;
+}
+
+void nsWindow::ShowWaylandPopupWindow() {
+ LOG("nsWindow::ShowWaylandPopupWindow. Expected to see visible.");
+ MOZ_ASSERT(IsWaylandPopup());
+
+ if (!mPopupTrackInHierarchy) {
+ LOG(" popup is not tracked in popup hierarchy, show it now");
+ gtk_widget_show(mShell);
+ return;
+ }
+
+ // Popup position was checked before gdk_window_move_to_rect() callback
+ // so just show it.
+ if (mPopupUseMoveToRect && mWaitingForMoveToRectCallback) {
+ LOG(" active move-to-rect callback, show it as is");
+ gtk_widget_show(mShell);
+ return;
+ }
+
+ if (gtk_widget_is_visible(mShell)) {
+ LOG(" is already visible, quit");
+ return;
+ }
+
+ int x, y;
+ bool moved = WaylandPopupRemoveNegativePosition(&x, &y);
+ gtk_widget_show(mShell);
+ if (moved) {
+ LOG(" move back to (%d, %d) and show", x, y);
+ gtk_window_move(GTK_WINDOW(mShell), x, y);
+ }
+}
+
+void nsWindow::WaylandPopupMarkAsClosed() {
+ LOG("nsWindow::WaylandPopupMarkAsClosed: [%p]\n", this);
+ mPopupClosed = true;
+ // If we have any child popup window notify it about
+ // parent switch.
+ if (mWaylandPopupNext) {
+ mWaylandPopupNext->mPopupChanged = true;
+ }
+}
+
+nsWindow* nsWindow::WaylandPopupFindLast(nsWindow* aPopup) {
+ while (aPopup && aPopup->mWaylandPopupNext) {
+ aPopup = aPopup->mWaylandPopupNext;
+ }
+ return aPopup;
+}
+
+// Hide and potentially removes popup from popup hierarchy.
+void nsWindow::HideWaylandPopupWindow(bool aTemporaryHide,
+ bool aRemoveFromPopupList) {
+ LOG("nsWindow::HideWaylandPopupWindow: remove from list %d\n",
+ aRemoveFromPopupList);
+ if (aRemoveFromPopupList) {
+ RemovePopupFromHierarchyList();
+ }
+
+ if (!mPopupClosed) {
+ mPopupClosed = !aTemporaryHide;
+ }
+
+ bool visible = gtk_widget_is_visible(mShell);
+ LOG(" gtk_widget_is_visible() = %d\n", visible);
+
+ // Restore only popups which are really visible
+ mPopupTemporaryHidden = aTemporaryHide && visible;
+
+ // Hide only visible popups or popups closed pernamently.
+ if (visible) {
+ gtk_widget_hide(mShell);
+
+ // If there's pending Move-To-Rect callback and we hide the popup
+ // the callback won't be called any more.
+ mWaitingForMoveToRectCallback = false;
+ }
+
+ if (mPopupClosed) {
+ LOG(" Clearing mMoveToRectPopupSize\n");
+ mMoveToRectPopupSize = {};
+#ifdef MOZ_WAYLAND
+ if (moz_container_wayland_is_waiting_to_show(mContainer)) {
+ // We need to clear rendering queue, see Bug 1782948.
+ LOG(" popup failed to show by Wayland compositor, clear rendering "
+ "queue.");
+ moz_container_wayland_clear_waiting_to_show_flag(mContainer);
+ ClearRenderingQueue();
+ }
+#endif
+ }
+}
+
+void nsWindow::HideWaylandToplevelWindow() {
+ LOG("nsWindow::HideWaylandToplevelWindow: [%p]\n", this);
+ if (mWaylandPopupNext) {
+ nsWindow* popup = WaylandPopupFindLast(mWaylandPopupNext);
+ while (popup->mWaylandToplevel != nullptr) {
+ nsWindow* prev = popup->mWaylandPopupPrev;
+ popup->HideWaylandPopupWindow(/* aTemporaryHide */ false,
+ /* aRemoveFromPopupList */ true);
+ popup = prev;
+ }
+ }
+ WaylandStopVsync();
+ gtk_widget_hide(mShell);
+}
+
+void nsWindow::ShowWaylandToplevelWindow() {
+ MOZ_ASSERT(!IsWaylandPopup());
+ LOG("nsWindow::ShowWaylandToplevelWindow");
+ gtk_widget_show(mShell);
+}
+
+void nsWindow::WaylandPopupRemoveClosedPopups() {
+ LOG("nsWindow::WaylandPopupRemoveClosedPopups()");
+ nsWindow* popup = this;
+ while (popup) {
+ nsWindow* next = popup->mWaylandPopupNext;
+ if (popup->mPopupClosed) {
+ popup->HideWaylandPopupWindow(/* aTemporaryHide */ false,
+ /* aRemoveFromPopupList */ true);
+ }
+ popup = next;
+ }
+}
+
+// Hide all tooltips except the latest one.
+void nsWindow::WaylandPopupHideTooltips() {
+ LOG("nsWindow::WaylandPopupHideTooltips");
+ MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
+
+ nsWindow* popup = mWaylandPopupNext;
+ while (popup && popup->mWaylandPopupNext) {
+ if (popup->mPopupType == PopupType::Tooltip) {
+ LOG(" hidding tooltip [%p]", popup);
+ popup->WaylandPopupMarkAsClosed();
+ }
+ popup = popup->mWaylandPopupNext;
+ }
+}
+
+void nsWindow::WaylandPopupCloseOrphanedPopups() {
+#ifdef MOZ_WAYLAND
+ LOG("nsWindow::WaylandPopupCloseOrphanedPopups");
+ MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
+
+ nsWindow* popup = mWaylandPopupNext;
+ bool dangling = false;
+ while (popup) {
+ if (!dangling &&
+ moz_container_wayland_is_waiting_to_show(popup->GetMozContainer())) {
+ LOG(" popup [%p] is waiting to show, close all child popups", popup);
+ dangling = true;
+ } else if (dangling) {
+ popup->WaylandPopupMarkAsClosed();
+ }
+ popup = popup->mWaylandPopupNext;
+ }
+#endif
+}
+
+// We can't show popups with remote content or overflow popups
+// on top of regular ones.
+// If there's any remote popup opened, close all parent popups of it.
+void nsWindow::CloseAllPopupsBeforeRemotePopup() {
+ LOG("nsWindow::CloseAllPopupsBeforeRemotePopup");
+ MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
+
+ // Don't waste time when there's only one popup opened.
+ if (!mWaylandPopupNext || mWaylandPopupNext->mWaylandPopupNext == nullptr) {
+ return;
+ }
+
+ // Find the first opened remote content popup
+ nsWindow* remotePopup = mWaylandPopupNext;
+ while (remotePopup) {
+ if (remotePopup->HasRemoteContent() ||
+ remotePopup->IsWidgetOverflowWindow()) {
+ LOG(" remote popup [%p]", remotePopup);
+ break;
+ }
+ remotePopup = remotePopup->mWaylandPopupNext;
+ }
+
+ if (!remotePopup) {
+ return;
+ }
+
+ // ...hide opened popups before the remote one.
+ nsWindow* popup = mWaylandPopupNext;
+ while (popup && popup != remotePopup) {
+ LOG(" hidding popup [%p]", popup);
+ popup->WaylandPopupMarkAsClosed();
+ popup = popup->mWaylandPopupNext;
+ }
+}
+
+static void GetLayoutPopupWidgetChain(
+ nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ pm->GetSubmenuWidgetChain(aLayoutWidgetHierarchy);
+ aLayoutWidgetHierarchy->Reverse();
+}
+
+// Compare 'this' popup position in Wayland widget hierarchy
+// (mWaylandPopupPrev/mWaylandPopupNext) with
+// 'this' popup position in layout hierarchy.
+//
+// When aMustMatchParent is true we also request
+// 'this' parents match, i.e. 'this' has the same parent in
+// both layout and widget hierarchy.
+bool nsWindow::IsPopupInLayoutPopupChain(
+ nsTArray<nsIWidget*>* aLayoutWidgetHierarchy, bool aMustMatchParent) {
+ int len = (int)aLayoutWidgetHierarchy->Length();
+ for (int i = 0; i < len; i++) {
+ if (this == (*aLayoutWidgetHierarchy)[i]) {
+ if (!aMustMatchParent) {
+ return true;
+ }
+
+ // Find correct parent popup for 'this' according to widget
+ // hierarchy. That means we need to skip closed popups.
+ nsWindow* parentPopup = nullptr;
+ if (mWaylandPopupPrev != mWaylandToplevel) {
+ parentPopup = mWaylandPopupPrev;
+ while (parentPopup != mWaylandToplevel && parentPopup->mPopupClosed) {
+ parentPopup = parentPopup->mWaylandPopupPrev;
+ }
+ }
+
+ if (i == 0) {
+ // We found 'this' popups as a first popup in layout hierarchy.
+ // It matches layout hierarchy if it's first widget also in
+ // wayland widget hierarchy (i.e. parent is null).
+ return parentPopup == nullptr;
+ }
+
+ return parentPopup == (*aLayoutWidgetHierarchy)[i - 1];
+ }
+ }
+ return false;
+}
+
+// Hide popups which are not in popup chain.
+void nsWindow::WaylandPopupHierarchyHideByLayout(
+ nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
+ LOG("nsWindow::WaylandPopupHierarchyHideByLayout");
+ MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
+
+ // Hide all popups which are not in layout popup chain
+ nsWindow* popup = mWaylandPopupNext;
+ while (popup) {
+ // Don't check closed popups and drag source popups and tooltips.
+ if (!popup->mPopupClosed && popup->mPopupType != PopupType::Tooltip &&
+ !popup->mSourceDragContext) {
+ if (!popup->IsPopupInLayoutPopupChain(aLayoutWidgetHierarchy,
+ /* aMustMatchParent */ false)) {
+ LOG(" hidding popup [%p]", popup);
+ popup->WaylandPopupMarkAsClosed();
+ }
+ }
+ popup = popup->mWaylandPopupNext;
+ }
+}
+
+// Mark popups outside of layout hierarchy
+void nsWindow::WaylandPopupHierarchyValidateByLayout(
+ nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
+ LOG("nsWindow::WaylandPopupHierarchyValidateByLayout");
+ nsWindow* popup = mWaylandPopupNext;
+ while (popup) {
+ if (popup->mPopupType == PopupType::Tooltip) {
+ popup->mPopupMatchesLayout = true;
+ } else if (!popup->mPopupClosed) {
+ popup->mPopupMatchesLayout = popup->IsPopupInLayoutPopupChain(
+ aLayoutWidgetHierarchy, /* aMustMatchParent */ true);
+ LOG(" popup [%p] parent window [%p] matches layout %d\n", (void*)popup,
+ (void*)popup->mWaylandPopupPrev, popup->mPopupMatchesLayout);
+ }
+ popup = popup->mWaylandPopupNext;
+ }
+}
+
+void nsWindow::WaylandPopupHierarchyHideTemporary() {
+ LOG("nsWindow::WaylandPopupHierarchyHideTemporary()");
+ nsWindow* popup = WaylandPopupFindLast(this);
+ while (popup && popup != this) {
+ LOG(" temporary hidding popup [%p]", popup);
+ nsWindow* prev = popup->mWaylandPopupPrev;
+ popup->HideWaylandPopupWindow(/* aTemporaryHide */ true,
+ /* aRemoveFromPopupList */ false);
+ popup = prev;
+ }
+}
+
+void nsWindow::WaylandPopupHierarchyShowTemporaryHidden() {
+ LOG("nsWindow::WaylandPopupHierarchyShowTemporaryHidden()");
+ nsWindow* popup = this;
+ while (popup) {
+ if (popup->mPopupTemporaryHidden) {
+ popup->mPopupTemporaryHidden = false;
+ LOG(" showing temporary hidden popup [%p]", popup);
+ popup->ShowWaylandPopupWindow();
+ }
+ popup = popup->mWaylandPopupNext;
+ }
+}
+
+void nsWindow::WaylandPopupHierarchyCalculatePositions() {
+ LOG("nsWindow::WaylandPopupHierarchyCalculatePositions()");
+
+ // Set widget hierarchy in Gtk
+ nsWindow* popup = mWaylandToplevel->mWaylandPopupNext;
+ while (popup) {
+ LOG(" popup [%p] set parent window [%p]", (void*)popup,
+ (void*)popup->mWaylandPopupPrev);
+ GtkWindowSetTransientFor(GTK_WINDOW(popup->mShell),
+ GTK_WINDOW(popup->mWaylandPopupPrev->mShell));
+ popup = popup->mWaylandPopupNext;
+ }
+
+ popup = this;
+ while (popup) {
+ // Anchored window has mPopupPosition already calculated against
+ // its parent, no need to recalculate.
+ LOG(" popup [%p] bounds [%d, %d] -> [%d x %d]", popup,
+ (int)(popup->mBounds.x / FractionalScaleFactor()),
+ (int)(popup->mBounds.y / FractionalScaleFactor()),
+ (int)(popup->mBounds.width / FractionalScaleFactor()),
+ (int)(popup->mBounds.height / FractionalScaleFactor()));
+#ifdef MOZ_LOGGING
+ if (LOG_ENABLED()) {
+ if (nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame())) {
+ auto r = LayoutDeviceRect::FromAppUnitsRounded(
+ popupFrame->GetRect(),
+ popupFrame->PresContext()->AppUnitsPerDevPixel());
+ LOG(" popup [%p] layout [%d, %d] -> [%d x %d]", popup, r.x, r.y,
+ r.width, r.height);
+ }
+ }
+#endif
+ if (popup->WaylandPopupIsFirst()) {
+ LOG(" popup [%p] has toplevel as parent", popup);
+ popup->mRelativePopupPosition = popup->mPopupPosition;
+ } else {
+ if (popup->mPopupAnchored) {
+ LOG(" popup [%p] is anchored", popup);
+ if (!popup->mPopupMatchesLayout) {
+ NS_WARNING("Anchored popup does not match layout!");
+ }
+ }
+ GdkPoint parent = popup->WaylandGetParentPosition();
+
+ LOG(" popup [%p] uses transformed coordinates\n", popup);
+ LOG(" parent position [%d, %d]\n", parent.x, parent.y);
+ LOG(" popup position [%d, %d]\n", popup->mPopupPosition.x,
+ popup->mPopupPosition.y);
+
+ popup->mRelativePopupPosition.x = popup->mPopupPosition.x - parent.x;
+ popup->mRelativePopupPosition.y = popup->mPopupPosition.y - parent.y;
+ }
+ LOG(" popup [%p] transformed popup coordinates from [%d, %d] to [%d, %d]",
+ popup, popup->mPopupPosition.x, popup->mPopupPosition.y,
+ popup->mRelativePopupPosition.x, popup->mRelativePopupPosition.y);
+ popup = popup->mWaylandPopupNext;
+ }
+}
+
+// The MenuList popups are used as dropdown menus for example in WebRTC
+// microphone/camera chooser or autocomplete widgets.
+bool nsWindow::WaylandPopupIsMenu() {
+ nsMenuPopupFrame* menuPopupFrame = GetMenuPopupFrame(GetFrame());
+ if (menuPopupFrame) {
+ return mPopupType == PopupType::Menu && !menuPopupFrame->IsMenuList();
+ }
+ return false;
+}
+
+bool nsWindow::WaylandPopupIsContextMenu() {
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+ if (!popupFrame) {
+ return false;
+ }
+ return popupFrame->IsContextMenu();
+}
+
+bool nsWindow::WaylandPopupIsPermanent() {
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+ if (!popupFrame) {
+ // We can always hide popups without frames.
+ return false;
+ }
+ return popupFrame->IsNoAutoHide();
+}
+
+bool nsWindow::WaylandPopupIsAnchored() {
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+ if (!popupFrame) {
+ // We can always hide popups without frames.
+ return false;
+ }
+ return !!popupFrame->GetAnchor();
+}
+
+bool nsWindow::IsWidgetOverflowWindow() {
+ if (this->GetFrame() && this->GetFrame()->GetContent()->GetID()) {
+ nsCString nodeId;
+ this->GetFrame()->GetContent()->GetID()->ToUTF8String(nodeId);
+ return nodeId.Equals("widget-overflow");
+ }
+ return false;
+}
+
+bool nsWindow::WaylandPopupIsFirst() {
+ return !mWaylandPopupPrev || !mWaylandPopupPrev->mWaylandToplevel;
+}
+
+nsWindow* nsWindow::GetEffectiveParent() {
+ GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+ if (!parentGtkWindow || !GTK_IS_WIDGET(parentGtkWindow)) {
+ return nullptr;
+ }
+ return get_window_for_gtk_widget(GTK_WIDGET(parentGtkWindow));
+}
+
+GdkPoint nsWindow::WaylandGetParentPosition() {
+ GdkPoint topLeft = {0, 0};
+ nsWindow* window = GetEffectiveParent();
+ if (window->IsPopup()) {
+ topLeft = DevicePixelsToGdkPointRoundDown(window->mBounds.TopLeft());
+ }
+ LOG("nsWindow::WaylandGetParentPosition() [%d, %d]\n", topLeft.x, topLeft.y);
+ return topLeft;
+}
+
+#ifdef MOZ_LOGGING
+void nsWindow::LogPopupHierarchy() {
+ if (!LOG_ENABLED()) {
+ return;
+ }
+
+ LOG("Widget Popup Hierarchy:\n");
+ if (!mWaylandToplevel->mWaylandPopupNext) {
+ LOG(" Empty\n");
+ } else {
+ int indent = 4;
+ nsWindow* popup = mWaylandToplevel->mWaylandPopupNext;
+ while (popup) {
+ nsPrintfCString indentString("%*s", indent, " ");
+ LOG("%s %s %s nsWindow [%p] Menu %d Permanent %d ContextMenu %d "
+ "Anchored %d Visible %d MovedByRect %d\n",
+ indentString.get(), popup->GetFrameTag().get(),
+ popup->GetPopupTypeName().get(), popup, popup->WaylandPopupIsMenu(),
+ popup->WaylandPopupIsPermanent(), popup->mPopupContextMenu,
+ popup->mPopupAnchored, gtk_widget_is_visible(popup->mShell),
+ popup->mPopupUseMoveToRect);
+ indent += 4;
+ popup = popup->mWaylandPopupNext;
+ }
+ }
+
+ LOG("Layout Popup Hierarchy:\n");
+ AutoTArray<nsIWidget*, 5> widgetChain;
+ GetLayoutPopupWidgetChain(&widgetChain);
+ if (widgetChain.Length() == 0) {
+ LOG(" Empty\n");
+ } else {
+ for (unsigned long i = 0; i < widgetChain.Length(); i++) {
+ nsWindow* window = static_cast<nsWindow*>(widgetChain[i]);
+ nsPrintfCString indentString("%*s", (int)(i + 1) * 4, " ");
+ if (window) {
+ LOG("%s %s %s nsWindow [%p] Menu %d Permanent %d ContextMenu %d "
+ "Anchored %d Visible %d MovedByRect %d\n",
+ indentString.get(), window->GetFrameTag().get(),
+ window->GetPopupTypeName().get(), window,
+ window->WaylandPopupIsMenu(), window->WaylandPopupIsPermanent(),
+ window->mPopupContextMenu, window->mPopupAnchored,
+ gtk_widget_is_visible(window->mShell), window->mPopupUseMoveToRect);
+ } else {
+ LOG("%s null window\n", indentString.get());
+ }
+ }
+ }
+}
+#endif
+
+nsWindow* nsWindow::WaylandPopupGetTopmostWindow() {
+ nsView* view = nsView::GetViewFor(this);
+ if (view) {
+ nsView* parentView = view->GetParent();
+ if (parentView) {
+ nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr);
+ if (parentWidget) {
+ nsWindow* parentnsWindow = static_cast<nsWindow*>(parentWidget);
+ LOG(" Topmost window: %p [nsWindow]\n", parentnsWindow);
+ return parentnsWindow;
+ }
+ }
+ }
+ return nullptr;
+}
+
+// Configure Wayland popup. If true is returned we need to track popup
+// in popup hierarchy. Otherwise we just show it as is.
+bool nsWindow::WaylandPopupConfigure() {
+ if (mIsDragPopup) {
+ return false;
+ }
+
+ // Don't track popups without frame
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+ if (!popupFrame) {
+ return false;
+ }
+
+ // Popup state can be changed, see Bug 1728952.
+ bool permanentStateMatches =
+ mPopupTrackInHierarchy == !WaylandPopupIsPermanent();
+
+ // Popup permanent state (noautohide attribute) can change during popup life.
+ if (mPopupTrackInHierarchyConfigured && permanentStateMatches) {
+ return mPopupTrackInHierarchy;
+ }
+
+ // Configure persistent popup params only once.
+ // WaylandPopupIsAnchored() can give it wrong value after
+ // nsMenuPopupFrame::MoveTo() call which we use in move-to-rect callback
+ // to position popup after wayland position change.
+ if (!mPopupTrackInHierarchyConfigured) {
+ mPopupAnchored = WaylandPopupIsAnchored();
+ mPopupContextMenu = WaylandPopupIsContextMenu();
+ }
+
+ LOG("nsWindow::WaylandPopupConfigure tracked %d anchored %d hint %d\n",
+ mPopupTrackInHierarchy, mPopupAnchored, int(mPopupHint));
+
+ // Permanent state changed and popup is mapped.
+ // We need to switch popup type but that's done when popup is mapped
+ // by Gtk so we need to unmap the popup here.
+ // It will be mapped again by gtk_widget_show().
+ if (!permanentStateMatches && mIsMapped) {
+ LOG(" permanent state change from %d to %d, unmapping",
+ mPopupTrackInHierarchy, !WaylandPopupIsPermanent());
+ gtk_widget_unmap(mShell);
+ }
+
+ mPopupTrackInHierarchy = !WaylandPopupIsPermanent();
+ LOG(" tracked in hierarchy %d\n", mPopupTrackInHierarchy);
+
+ // See gdkwindow-wayland.c and
+ // should_map_as_popup()/should_map_as_subsurface()
+ GdkWindowTypeHint gtkTypeHint;
+ switch (mPopupHint) {
+ case PopupType::Menu:
+ // GDK_WINDOW_TYPE_HINT_POPUP_MENU is mapped as xdg_popup by default.
+ // We use this type for all menu popups.
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
+ LOG(" popup type Menu");
+ break;
+ case PopupType::Tooltip:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
+ LOG(" popup type Tooltip");
+ break;
+ default:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
+ LOG(" popup type Utility");
+ break;
+ }
+
+ if (!mPopupTrackInHierarchy) {
+ // GDK_WINDOW_TYPE_HINT_UTILITY is mapped as wl_subsurface
+ // by default.
+ LOG(" not tracked in popup hierarchy, switch to Utility");
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
+ }
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
+
+ mPopupTrackInHierarchyConfigured = true;
+ return mPopupTrackInHierarchy;
+}
+
+bool nsWindow::IsInPopupHierarchy() {
+ return mPopupTrackInHierarchy && mWaylandToplevel && mWaylandPopupPrev;
+}
+
+void nsWindow::AddWindowToPopupHierarchy() {
+ LOG("nsWindow::AddWindowToPopupHierarchy\n");
+ if (!GetFrame()) {
+ LOG(" Window without frame cannot be added as popup!\n");
+ return;
+ }
+
+ // Check if we're already in the hierarchy
+ if (!IsInPopupHierarchy()) {
+ mWaylandToplevel = WaylandPopupGetTopmostWindow();
+ AppendPopupToHierarchyList(mWaylandToplevel);
+ }
+}
+
+// Wayland keeps strong popup window hierarchy. We need to track active
+// (visible) popup windows and make sure we hide popup on the same level
+// before we open another one on that level. It means that every open
+// popup needs to have an unique parent.
+void nsWindow::UpdateWaylandPopupHierarchy() {
+ LOG("nsWindow::UpdateWaylandPopupHierarchy\n");
+
+ // This popup hasn't been added to popup hierarchy yet so no need to
+ // do any configurations.
+ if (!IsInPopupHierarchy()) {
+ LOG(" popup isn't in hierarchy\n");
+ return;
+ }
+
+#ifdef MOZ_LOGGING
+ LogPopupHierarchy();
+ auto printPopupHierarchy = MakeScopeExit([&] { LogPopupHierarchy(); });
+#endif
+
+ // Hide all tooltips without the last one. Tooltip can't be popup parent.
+ mWaylandToplevel->WaylandPopupHideTooltips();
+
+ // See Bug 1709254 / https://gitlab.gnome.org/GNOME/gtk/-/issues/5092
+ // It's possible that Wayland compositor refuses to show
+ // a popup although Gtk claims it's visible.
+ // We don't know if the popup is shown or not.
+ // To avoid application crash refuse to create any child of such invisible
+ // popup and close any child of it now.
+ mWaylandToplevel->WaylandPopupCloseOrphanedPopups();
+
+ // Check if we have any remote content / overflow window in hierarchy.
+ // We can't attach such widget on top of other popup.
+ mWaylandToplevel->CloseAllPopupsBeforeRemotePopup();
+
+ // Check if your popup hierarchy matches layout hierarchy.
+ // For instance we should not connect hamburger menu on top
+ // of context menu.
+ // Close all popups from different layout chains if possible.
+ AutoTArray<nsIWidget*, 5> layoutPopupWidgetChain;
+ GetLayoutPopupWidgetChain(&layoutPopupWidgetChain);
+
+ mWaylandToplevel->WaylandPopupHierarchyHideByLayout(&layoutPopupWidgetChain);
+ mWaylandToplevel->WaylandPopupHierarchyValidateByLayout(
+ &layoutPopupWidgetChain);
+
+ // Now we have Popup hierarchy complete.
+ // Find first unchanged (and still open) popup to start with hierarchy
+ // changes.
+ nsWindow* changedPopup = mWaylandToplevel->mWaylandPopupNext;
+ while (changedPopup) {
+ // Stop when parent of this popup was changed and we need to recalc
+ // popup position.
+ if (changedPopup->mPopupChanged) {
+ break;
+ }
+ // Stop when this popup is closed.
+ if (changedPopup->mPopupClosed) {
+ break;
+ }
+ changedPopup = changedPopup->mWaylandPopupNext;
+ }
+
+ // We don't need to recompute popup positions, quit now.
+ if (!changedPopup) {
+ LOG(" changed Popup is null, quit.\n");
+ return;
+ }
+
+ LOG(" first changed popup [%p]\n", (void*)changedPopup);
+
+ // Hide parent popups if necessary (there are layout discontinuity)
+ // reposition the popup and show them again.
+ changedPopup->WaylandPopupHierarchyHideTemporary();
+
+ nsWindow* parentOfchangedPopup = nullptr;
+ if (changedPopup->mPopupClosed) {
+ parentOfchangedPopup = changedPopup->mWaylandPopupPrev;
+ }
+ changedPopup->WaylandPopupRemoveClosedPopups();
+
+ // It's possible that changedPopup was removed from widget hierarchy,
+ // in such case use child popup of the removed one if there's any.
+ if (!changedPopup->IsInPopupHierarchy()) {
+ if (!parentOfchangedPopup || !parentOfchangedPopup->mWaylandPopupNext) {
+ LOG(" last popup was removed, quit.\n");
+ return;
+ }
+ changedPopup = parentOfchangedPopup->mWaylandPopupNext;
+ }
+
+ GetLayoutPopupWidgetChain(&layoutPopupWidgetChain);
+ mWaylandToplevel->WaylandPopupHierarchyValidateByLayout(
+ &layoutPopupWidgetChain);
+
+ changedPopup->WaylandPopupHierarchyCalculatePositions();
+
+ nsWindow* popup = changedPopup;
+ while (popup) {
+ const bool useMoveToRect = [&] {
+ if (!StaticPrefs::widget_wayland_use_move_to_rect_AtStartup()) {
+ return false; // Not available.
+ }
+ if (!popup->mPopupMatchesLayout) {
+ // We can use move_to_rect only when popups in popup hierarchy matches
+ // layout hierarchy as move_to_rect request that parent/child
+ // popups are adjacent.
+ return false;
+ }
+ if (popup->mPopupHint == PopupType::Panel &&
+ popup->WaylandPopupIsFirst() &&
+ popup->WaylandPopupFitsToplevelWindow(/* aMove */ true)) {
+ // Workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/1986
+ //
+ // PopupType::Panel types are used for extension popups which may be
+ // resized. If such popup uses move-to-rect, we need to hide it before
+ // resize and show it again. That leads to massive flickering
+ // so use plain move if possible to avoid it.
+ //
+ // Bug 1760276 - don't use move-to-rect when popup is inside main
+ // Firefox window.
+ //
+ // Use it for first popups only due to another mutter bug
+ // https://gitlab.gnome.org/GNOME/gtk/-/issues/5089
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1784873
+ return false;
+ }
+ if (!popup->WaylandPopupIsFirst() &&
+ !popup->mWaylandPopupPrev->WaylandPopupIsFirst() &&
+ !popup->mWaylandPopupPrev->mPopupUseMoveToRect) {
+ // We can't use move-to-rect if there are more parents of
+ // wl_subsurface popups types.
+ //
+ // It's because wl_subsurface is ignored by xgd_popup
+ // (created by move-to-rect) so our popup scenario:
+ //
+ // toplevel -> xgd_popup(1) -> wl_subsurface(2) -> xgd_popup(3)
+ //
+ // looks for Wayland compositor as:
+ //
+ // toplevel -> xgd_popup(1) -> xgd_popup(3)
+ //
+ // If xgd_popup(1) and xgd_popup(3) are not connected
+ // move-to-rect applied to xgd_popup(3) fails and we get missing popup.
+ return false;
+ }
+ return true;
+ }();
+
+ LOG(" popup [%p] matches layout [%d] anchored [%d] first popup [%d] use "
+ "move-to-rect %d\n",
+ popup, popup->mPopupMatchesLayout, popup->mPopupAnchored,
+ popup->WaylandPopupIsFirst(), useMoveToRect);
+
+ popup->mPopupUseMoveToRect = useMoveToRect;
+ popup->WaylandPopupMoveImpl();
+ popup->mPopupChanged = false;
+ popup = popup->mWaylandPopupNext;
+ }
+
+ changedPopup->WaylandPopupHierarchyShowTemporaryHidden();
+}
+
+static void NativeMoveResizeCallback(GdkWindow* window,
+ const GdkRectangle* flipped_rect,
+ const GdkRectangle* final_rect,
+ gboolean flipped_x, gboolean flipped_y,
+ void* aWindow) {
+ LOG_POPUP("[%p] NativeMoveResizeCallback flipped_x %d flipped_y %d\n",
+ aWindow, flipped_x, flipped_y);
+ LOG_POPUP("[%p] new position [%d, %d] -> [%d x %d]", aWindow,
+ final_rect->x, final_rect->y, final_rect->width,
+ final_rect->height);
+ nsWindow* wnd = get_window_for_gdk_window(window);
+
+ wnd->NativeMoveResizeWaylandPopupCallback(final_rect, flipped_x, flipped_y);
+}
+
+// When popup is repositioned by widget code, we need to notify
+// layout about it. It's because we control popup placement
+// on widget on Wayland so layout may have old popup size/coordinates.
+void nsWindow::WaylandPopupPropagateChangesToLayout(bool aMove, bool aResize) {
+ LOG("nsWindow::WaylandPopupPropagateChangesToLayout()");
+
+ if (aResize) {
+ LOG(" needSizeUpdate\n");
+ if (nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame())) {
+ RefPtr<PresShell> presShell = popupFrame->PresShell();
+ presShell->FrameNeedsReflow(popupFrame, IntrinsicDirty::None,
+ NS_FRAME_IS_DIRTY);
+ }
+ }
+ if (aMove) {
+ LOG(" needPositionUpdate, bounds [%d, %d]", mBounds.x, mBounds.y);
+ NotifyWindowMoved(mBounds.x, mBounds.y, ByMoveToRect::Yes);
+ }
+}
+
+void nsWindow::NativeMoveResizeWaylandPopupCallback(
+ const GdkRectangle* aFinalSize, bool aFlippedX, bool aFlippedY) {
+ // We're getting move-to-rect callback without move-to-rect call.
+ // That indicates a compositor bug. It happens when a window is hidden and
+ // shown again before move-to-rect callback is fired.
+ // It may lead to incorrect popup placement as we may call
+ // gtk_window_move() between hide & show.
+ // See Bug 1777919, 1789581.
+#if MOZ_LOGGING
+ if (!mWaitingForMoveToRectCallback) {
+ LOG(" Bogus move-to-rect callback! Expect wrong popup coordinates.");
+ }
+#endif
+
+ mWaitingForMoveToRectCallback = false;
+
+ bool movedByLayout = mMovedAfterMoveToRect;
+ bool resizedByLayout = mResizedAfterMoveToRect;
+
+ // Popup was moved between move-to-rect call and move-to-rect callback
+ // and the coordinates from move-to-rect callback are outdated.
+ if (movedByLayout || resizedByLayout) {
+ LOG(" Another move/resize called during waiting for callback\n");
+ mMovedAfterMoveToRect = false;
+ mResizedAfterMoveToRect = false;
+ // Fire another round of move/resize to reflect latest request
+ // from layout.
+ NativeMoveResize(movedByLayout, resizedByLayout);
+ return;
+ }
+
+ LOG(" orig mBounds [%d, %d] -> [%d x %d]\n", mBounds.x, mBounds.y,
+ mBounds.width, mBounds.height);
+
+ LayoutDeviceIntRect newBounds = [&] {
+ GdkRectangle finalRect = *aFinalSize;
+ GdkPoint parent = WaylandGetParentPosition();
+ finalRect.x += parent.x;
+ finalRect.y += parent.y;
+ return GdkRectToDevicePixels(finalRect);
+ }();
+
+ LOG(" new mBounds [%d, %d] -> [%d x %d]", newBounds.x, newBounds.y,
+ newBounds.width, newBounds.height);
+
+ bool needsPositionUpdate = newBounds.TopLeft() != mBounds.TopLeft();
+ bool needsSizeUpdate = newBounds.Size() != mLastSizeRequest;
+
+ if (needsSizeUpdate) {
+ // Wayland compositor changed popup size request from layout.
+ // Set the constraints to use them in nsMenuPopupFrame::SetPopupPosition().
+ // Beware that gtk_window_resize() requests sizes asynchronously and so
+ // newBounds might not have the size from the most recent
+ // gtk_window_resize().
+ if (newBounds.width < mLastSizeRequest.width) {
+ mMoveToRectPopupSize.width = newBounds.width;
+ }
+ if (newBounds.height < mLastSizeRequest.height) {
+ mMoveToRectPopupSize.height = newBounds.height;
+ }
+ LOG(" mMoveToRectPopupSize set to [%d, %d]", mMoveToRectPopupSize.width,
+ mMoveToRectPopupSize.height);
+ }
+ mBounds = newBounds;
+ // Check mBounds size
+ if (mCompositorSession &&
+ !wr::WindowSizeSanityCheck(mBounds.width, mBounds.height)) {
+ gfxCriticalNoteOnce << "Invalid mBounds in PopupCallback " << mBounds
+ << " size state " << mSizeMode;
+ }
+ WaylandPopupPropagateChangesToLayout(needsPositionUpdate, needsSizeUpdate);
+}
+
+static GdkGravity PopupAlignmentToGdkGravity(int8_t aAlignment) {
+ switch (aAlignment) {
+ case POPUPALIGNMENT_NONE:
+ return GDK_GRAVITY_NORTH_WEST;
+ case POPUPALIGNMENT_TOPLEFT:
+ return GDK_GRAVITY_NORTH_WEST;
+ case POPUPALIGNMENT_TOPRIGHT:
+ return GDK_GRAVITY_NORTH_EAST;
+ case POPUPALIGNMENT_BOTTOMLEFT:
+ return GDK_GRAVITY_SOUTH_WEST;
+ case POPUPALIGNMENT_BOTTOMRIGHT:
+ return GDK_GRAVITY_SOUTH_EAST;
+ case POPUPALIGNMENT_LEFTCENTER:
+ return GDK_GRAVITY_WEST;
+ case POPUPALIGNMENT_RIGHTCENTER:
+ return GDK_GRAVITY_EAST;
+ case POPUPALIGNMENT_TOPCENTER:
+ return GDK_GRAVITY_NORTH;
+ case POPUPALIGNMENT_BOTTOMCENTER:
+ return GDK_GRAVITY_SOUTH;
+ }
+ return GDK_GRAVITY_STATIC;
+}
+
+bool nsWindow::IsPopupDirectionRTL() {
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+ return popupFrame && popupFrame->IsDirectionRTL();
+}
+
+// Position the popup directly by gtk_window_move() and try to keep it
+// on screen by just moving it in scope of it's parent window.
+//
+// It's used when we position noautihode popup and we don't use xdg_positioner.
+// See Bug 1718867
+void nsWindow::WaylandPopupSetDirectPosition() {
+ GdkPoint topLeft = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mLastSizeRequest);
+
+ LOG("nsWindow::WaylandPopupSetDirectPosition %d,%d -> %d x %d\n", topLeft.x,
+ topLeft.y, size.width, size.height);
+
+ mPopupPosition = {topLeft.x, topLeft.y};
+
+ if (mIsDragPopup) {
+ gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y);
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ // DND window is placed inside container so we need to make hard size
+ // request to ensure parent container is resized too.
+ gtk_widget_set_size_request(GTK_WIDGET(mShell), size.width, size.height);
+ return;
+ }
+
+ GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+ nsWindow* window = get_window_for_gtk_widget(GTK_WIDGET(parentGtkWindow));
+ if (!window) {
+ return;
+ }
+ GdkWindow* gdkWindow =
+ gtk_widget_get_window(GTK_WIDGET(window->GetMozContainer()));
+
+ int parentWidth = gdk_window_get_width(gdkWindow);
+ int popupWidth = size.width;
+
+ int x;
+ gdk_window_get_position(gdkWindow, &x, nullptr);
+
+ // If popup is bigger than main window just center it.
+ if (popupWidth > parentWidth) {
+ mPopupPosition.x = -(parentWidth - popupWidth) / 2 + x;
+ } else {
+ if (IsPopupDirectionRTL()) {
+ // Stick with right window edge
+ if (mPopupPosition.x < x) {
+ mPopupPosition.x = x;
+ }
+ } else {
+ // Stick with left window edge
+ if (mPopupPosition.x + popupWidth > parentWidth + x) {
+ mPopupPosition.x = parentWidth + x - popupWidth;
+ }
+ }
+ }
+
+ LOG(" set position [%d, %d]\n", mPopupPosition.x, mPopupPosition.y);
+ gtk_window_move(GTK_WINDOW(mShell), mPopupPosition.x, mPopupPosition.y);
+
+ LOG(" set size [%d, %d]\n", size.width, size.height);
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+
+ if (mPopupPosition.x != topLeft.x) {
+ mBounds.MoveTo(GdkPointToDevicePixels(mPopupPosition));
+ LOG(" setting new bounds [%d, %d]\n", mBounds.x, mBounds.y);
+ WaylandPopupPropagateChangesToLayout(/* move */ true, /* resize */ false);
+ }
+}
+
+bool nsWindow::WaylandPopupFitsToplevelWindow(bool aMove) {
+ LOG("nsWindow::WaylandPopupFitsToplevelWindow() move %d", aMove);
+
+ GtkWindow* parent = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+ GtkWindow* tmp = parent;
+ while ((tmp = gtk_window_get_transient_for(GTK_WINDOW(parent)))) {
+ parent = tmp;
+ }
+ GdkWindow* toplevelGdkWindow = gtk_widget_get_window(GTK_WIDGET(parent));
+
+ if (!toplevelGdkWindow) {
+ NS_WARNING("Toplevel widget without GdkWindow?");
+ return false;
+ }
+
+ int parentWidth = gdk_window_get_width(toplevelGdkWindow);
+ int parentHeight = gdk_window_get_height(toplevelGdkWindow);
+ LOG(" parent size %d x %d", parentWidth, parentHeight);
+
+ GdkPoint topLeft = aMove ? mPopupPosition
+ : DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mLastSizeRequest);
+ LOG(" popup topleft %d, %d size %d x %d", topLeft.x, topLeft.y, size.width,
+ size.height);
+ int fits = topLeft.x >= 0 && topLeft.y >= 0 &&
+ topLeft.x + size.width <= parentWidth &&
+ topLeft.y + size.height <= parentHeight;
+
+ LOG(" fits %d", fits);
+ return fits;
+}
+
+void nsWindow::NativeMoveResizeWaylandPopup(bool aMove, bool aResize) {
+ GdkPoint topLeft = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mLastSizeRequest);
+
+ LOG("nsWindow::NativeMoveResizeWaylandPopup Bounds %d,%d -> %d x %d move %d "
+ "resize %d\n",
+ topLeft.x, topLeft.y, size.width, size.height, aMove, aResize);
+
+ // Compositor may be confused by windows with width/height = 0
+ // and positioning such windows leads to Bug 1555866.
+ if (!AreBoundsSane()) {
+ LOG(" Bounds are not sane (width: %d height: %d)\n",
+ mLastSizeRequest.width, mLastSizeRequest.height);
+ return;
+ }
+
+ if (mWaitingForMoveToRectCallback) {
+ LOG(" waiting for move to rect, scheduling");
+ // mBounds position must not be overwritten before it is applied.
+ // OnConfigureEvent() will not set mBounds to an old position for
+ // GTK_WINDOW_POPUP.
+ MOZ_ASSERT(gtk_window_get_window_type(GTK_WINDOW(mShell)) ==
+ GTK_WINDOW_POPUP);
+ mMovedAfterMoveToRect = aMove;
+ mResizedAfterMoveToRect = aResize;
+ return;
+ }
+
+ mMovedAfterMoveToRect = false;
+ mResizedAfterMoveToRect = false;
+
+ bool trackedInHierarchy = WaylandPopupConfigure();
+
+ // Read popup position from layout if it was moved or newly created.
+ // This position is used by move-to-rect method as we need anchor and other
+ // info to place popup correctly.
+ // We need WaylandPopupConfigure() to be called before to have all needed
+ // popup info in place (mainly the anchored flag).
+ if (aMove) {
+ mPopupMoveToRectParams = WaylandPopupGetPositionFromLayout();
+ }
+
+ if (!trackedInHierarchy) {
+ WaylandPopupSetDirectPosition();
+ return;
+ }
+
+ if (aResize) {
+ LOG(" set size [%d, %d]\n", size.width, size.height);
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ }
+
+ if (!aMove && WaylandPopupFitsToplevelWindow(aMove)) {
+ // Popup position has not been changed and its position/size fits
+ // parent window so no need to reposition the window.
+ LOG(" fits parent window size, just resize\n");
+ return;
+ }
+
+ // Mark popup as changed as we're updating position/size.
+ mPopupChanged = true;
+
+ // Save popup position for former re-calculations when popup hierarchy
+ // is changed.
+ LOG(" popup position changed from [%d, %d] to [%d, %d]\n", mPopupPosition.x,
+ mPopupPosition.y, topLeft.x, topLeft.y);
+ mPopupPosition = {topLeft.x, topLeft.y};
+
+ UpdateWaylandPopupHierarchy();
+}
+
+struct PopupSides {
+ Maybe<Side> mVertical;
+ Maybe<Side> mHorizontal;
+};
+
+static PopupSides SidesForPopupAlignment(int8_t aAlignment) {
+ switch (aAlignment) {
+ case POPUPALIGNMENT_NONE:
+ break;
+ case POPUPALIGNMENT_TOPLEFT:
+ return {Some(eSideTop), Some(eSideLeft)};
+ case POPUPALIGNMENT_TOPRIGHT:
+ return {Some(eSideTop), Some(eSideRight)};
+ case POPUPALIGNMENT_BOTTOMLEFT:
+ return {Some(eSideBottom), Some(eSideLeft)};
+ case POPUPALIGNMENT_BOTTOMRIGHT:
+ return {Some(eSideBottom), Some(eSideRight)};
+ case POPUPALIGNMENT_LEFTCENTER:
+ return {Nothing(), Some(eSideLeft)};
+ case POPUPALIGNMENT_RIGHTCENTER:
+ return {Nothing(), Some(eSideRight)};
+ case POPUPALIGNMENT_TOPCENTER:
+ return {Some(eSideTop), Nothing()};
+ case POPUPALIGNMENT_BOTTOMCENTER:
+ return {Some(eSideBottom), Nothing()};
+ }
+ return {};
+}
+
+// We want to apply margins based on popup alignment (which would generally be
+// just an offset to apply to the popup). However, to deal with flipping
+// correctly, we apply the margin to the anchor when possible.
+struct ResolvedPopupMargin {
+ // A margin to be applied to the anchor.
+ nsMargin mAnchorMargin;
+ // An offset in app units to be applied to the popup for when we need to tell
+ // GTK to center inside the anchor precisely (so we can't really do better in
+ // presence of flips).
+ nsPoint mPopupOffset;
+};
+
+static ResolvedPopupMargin ResolveMargin(nsMenuPopupFrame* aFrame,
+ int8_t aPopupAlign,
+ int8_t aAnchorAlign,
+ bool aAnchoredToPoint,
+ bool aIsContextMenu) {
+ nsMargin margin = aFrame->GetMargin();
+ nsPoint offset;
+
+ if (aAnchoredToPoint) {
+ // Since GTK doesn't allow us to specify margins itself, when anchored to a
+ // point we can just assume we'll be aligned correctly... This is kind of
+ // annoying but alas.
+ //
+ // This calculation must match the relevant unanchored popup calculation in
+ // nsMenuPopupFrame::SetPopupPosition(), which should itself be the inverse
+ // inverse of nsMenuPopupFrame::MoveTo().
+ if (aIsContextMenu && aFrame->IsDirectionRTL()) {
+ offset.x = -margin.right;
+ } else {
+ offset.x = margin.left;
+ }
+ offset.y = margin.top;
+ return {nsMargin(), offset};
+ }
+
+ auto popupSides = SidesForPopupAlignment(aPopupAlign);
+ auto anchorSides = SidesForPopupAlignment(aAnchorAlign);
+ // Matched sides: Invert the margin, so that we pull in the right direction.
+ // Popup not aligned to any anchor side: We give up and use the offset,
+ // applying the margin from the popup side.
+ // Mismatched sides: We swap the margins so that we pull in the right
+ // direction, e.g. margin-left: -10px should shrink 10px the _right_ of the
+ // box, not the left of the box.
+ if (popupSides.mHorizontal == anchorSides.mHorizontal) {
+ margin.left = -margin.left;
+ margin.right = -margin.right;
+ } else if (!anchorSides.mHorizontal) {
+ auto popupSide = *popupSides.mHorizontal;
+ offset.x += popupSide == eSideRight ? -margin.Side(popupSide)
+ : margin.Side(popupSide);
+ margin.left = margin.right = 0;
+ } else {
+ std::swap(margin.left, margin.right);
+ }
+
+ // Same logic as above, but in the vertical direction.
+ if (popupSides.mVertical == anchorSides.mVertical) {
+ margin.top = -margin.top;
+ margin.bottom = -margin.bottom;
+ } else if (!anchorSides.mVertical) {
+ auto popupSide = *popupSides.mVertical;
+ offset.y += popupSide == eSideBottom ? -margin.Side(popupSide)
+ : margin.Side(popupSide);
+ margin.top = margin.bottom = 0;
+ } else {
+ std::swap(margin.top, margin.bottom);
+ }
+
+ return {margin, offset};
+}
+
+#ifdef MOZ_LOGGING
+void nsWindow::LogPopupAnchorHints(int aHints) {
+ static struct hints_ {
+ int hint;
+ char name[100];
+ } hints[] = {
+ {GDK_ANCHOR_FLIP_X, "GDK_ANCHOR_FLIP_X"},
+ {GDK_ANCHOR_FLIP_Y, "GDK_ANCHOR_FLIP_Y"},
+ {GDK_ANCHOR_SLIDE_X, "GDK_ANCHOR_SLIDE_X"},
+ {GDK_ANCHOR_SLIDE_Y, "GDK_ANCHOR_SLIDE_Y"},
+ {GDK_ANCHOR_RESIZE_X, "GDK_ANCHOR_RESIZE_X"},
+ {GDK_ANCHOR_RESIZE_Y, "GDK_ANCHOR_RESIZE_X"},
+ };
+
+ LOG(" PopupAnchorHints");
+ for (const auto& hint : hints) {
+ if (hint.hint & aHints) {
+ LOG(" %s", hint.name);
+ }
+ }
+}
+
+void nsWindow::LogPopupGravity(GdkGravity aGravity) {
+ static char gravity[][100]{"NONE",
+ "GDK_GRAVITY_NORTH_WEST",
+ "GDK_GRAVITY_NORTH",
+ "GDK_GRAVITY_NORTH_EAST",
+ "GDK_GRAVITY_WEST",
+ "GDK_GRAVITY_CENTER",
+ "GDK_GRAVITY_EAST",
+ "GDK_GRAVITY_SOUTH_WEST",
+ "GDK_GRAVITY_SOUTH",
+ "GDK_GRAVITY_SOUTH_EAST",
+ "GDK_GRAVITY_STATIC"};
+ LOG(" %s", gravity[aGravity]);
+}
+#endif
+
+const nsWindow::WaylandPopupMoveToRectParams
+nsWindow::WaylandPopupGetPositionFromLayout() {
+ LOG("nsWindow::WaylandPopupGetPositionFromLayout\n");
+
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+
+ const bool isTopContextMenu = mPopupContextMenu && !mPopupAnchored;
+ const bool isRTL = IsPopupDirectionRTL();
+ const bool anchored = popupFrame->IsAnchored();
+ int8_t popupAlign = POPUPALIGNMENT_TOPLEFT;
+ int8_t anchorAlign = POPUPALIGNMENT_BOTTOMRIGHT;
+ if (anchored) {
+ // See nsMenuPopupFrame::AdjustPositionForAnchorAlign.
+ popupAlign = popupFrame->GetPopupAlignment();
+ anchorAlign = popupFrame->GetPopupAnchor();
+ }
+ if (isRTL && (anchored || isTopContextMenu)) {
+ popupAlign = -popupAlign;
+ anchorAlign = -anchorAlign;
+ }
+
+ // Although we have mPopupPosition / mRelativePopupPosition here
+ // we can't use it. move-to-rect needs anchor rectangle to position a popup
+ // but we have only a point from Resize().
+ //
+ // So we need to extract popup position from nsMenuPopupFrame() and duplicate
+ // the layout work here.
+ LayoutDeviceIntRect anchorRect;
+ ResolvedPopupMargin popupMargin;
+ {
+ nsRect anchorRectAppUnits = popupFrame->GetUntransformedAnchorRect();
+ // This is a somewhat hacky way of applying the popup margin. We don't know
+ // if GTK will end up flipping the popup, in which case the offset we
+ // compute is just wrong / applied to the wrong side.
+ //
+ // Instead, we tell it to anchor us at a smaller or bigger rect depending on
+ // the margin, which achieves the same result if the popup is positioned
+ // correctly, but doesn't misposition the popup when flipped across the
+ // anchor.
+ popupMargin = ResolveMargin(popupFrame, popupAlign, anchorAlign,
+ anchorRectAppUnits.IsEmpty(), isTopContextMenu);
+ LOG(" layout popup CSS anchor (%d, %d) %s, margin %s offset %s\n",
+ popupAlign, anchorAlign, ToString(anchorRectAppUnits).c_str(),
+ ToString(popupMargin.mAnchorMargin).c_str(),
+ ToString(popupMargin.mPopupOffset).c_str());
+ anchorRectAppUnits.Inflate(popupMargin.mAnchorMargin);
+ LOG(" after margins %s\n", ToString(anchorRectAppUnits).c_str());
+ nscoord auPerDev = popupFrame->PresContext()->AppUnitsPerDevPixel();
+ anchorRect = LayoutDeviceIntRect::FromAppUnitsToNearest(anchorRectAppUnits,
+ auPerDev);
+ if (anchorRect.width < 0) {
+ auto w = -anchorRect.width;
+ anchorRect.width += w + 1;
+ anchorRect.x += w;
+ }
+ LOG(" final %s\n", ToString(anchorRect).c_str());
+ }
+
+ LOG(" relative popup rect position [%d, %d] -> [%d x %d]\n", anchorRect.x,
+ anchorRect.y, anchorRect.width, anchorRect.height);
+
+ // Get gravity and flip type
+ GdkGravity rectAnchor = PopupAlignmentToGdkGravity(anchorAlign);
+ GdkGravity menuAnchor = PopupAlignmentToGdkGravity(popupAlign);
+
+ LOG(" parentRect gravity: %d anchor gravity: %d\n", rectAnchor, menuAnchor);
+
+ // Gtk default is: GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE | GDK_ANCHOR_RESIZE.
+ // We want to SLIDE_X menu on the dual monitor setup rather than resize it
+ // on the other monitor.
+ GdkAnchorHints hints =
+ GdkAnchorHints(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_RESIZE);
+
+ // slideHorizontal from nsMenuPopupFrame::SetPopupPosition
+ int8_t position = popupFrame->GetAlignmentPosition();
+ if (position >= POPUPPOSITION_BEFORESTART &&
+ position <= POPUPPOSITION_AFTEREND) {
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE_X);
+ }
+ // slideVertical from nsMenuPopupFrame::SetPopupPosition
+ if (position >= POPUPPOSITION_STARTBEFORE &&
+ position <= POPUPPOSITION_ENDAFTER) {
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE_Y);
+ }
+
+ FlipType flipType = popupFrame->GetFlipType();
+ if (rectAnchor == GDK_GRAVITY_CENTER && menuAnchor == GDK_GRAVITY_CENTER) {
+ // only slide
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
+ } else {
+ switch (flipType) {
+ case FlipType_Both:
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_FLIP);
+ break;
+ case FlipType_Slide:
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
+ break;
+ case FlipType_Default:
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_FLIP);
+ break;
+ default:
+ break;
+ }
+ }
+ if (!WaylandPopupIsMenu()) {
+ // we don't want to slide menus to fit the screen rather resize them
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
+ }
+
+ // We want tooltips to flip verticaly or slide only.
+ // See nsMenuPopupFrame::SetPopupPosition().
+ // https://searchfox.org/mozilla-central/rev/d0f5bc50aff3462c9d1546b88d60c5cb020eb15c/layout/xul/nsMenuPopupFrame.cpp#1603
+ if (mPopupType == PopupType::Tooltip) {
+ hints = GdkAnchorHints(GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_SLIDE);
+ }
+
+ return {
+ anchorRect,
+ rectAnchor,
+ menuAnchor,
+ hints,
+ DevicePixelsToGdkPointRoundDown(LayoutDevicePoint::FromAppUnitsToNearest(
+ popupMargin.mPopupOffset,
+ popupFrame->PresContext()->AppUnitsPerDevPixel())),
+ true};
+}
+
+bool nsWindow::WaylandPopupAnchorAdjustForParentPopup(
+ GdkRectangle* aPopupAnchor, GdkPoint* aOffset) {
+ LOG("nsWindow::WaylandPopupAnchorAdjustForParentPopup");
+
+ GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+ if (!parentGtkWindow || !GTK_IS_WIDGET(parentGtkWindow)) {
+ NS_WARNING("Popup has no parent!");
+ return false;
+ }
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(parentGtkWindow));
+ if (!window) {
+ NS_WARNING("Popup parrent is not mapped!");
+ return false;
+ }
+
+ GdkRectangle parentWindowRect = {0, 0, gdk_window_get_width(window),
+ gdk_window_get_height(window)};
+ LOG(" parent window size %d x %d", parentWindowRect.width,
+ parentWindowRect.height);
+
+ // We can't have rectangle anchor with zero width/height.
+ if (!aPopupAnchor->width) {
+ aPopupAnchor->width = 1;
+ }
+ if (!aPopupAnchor->height) {
+ aPopupAnchor->height = 1;
+ }
+
+ GdkRectangle finalRect;
+ if (!gdk_rectangle_intersect(aPopupAnchor, &parentWindowRect, &finalRect)) {
+ return false;
+ }
+ *aPopupAnchor = finalRect;
+ LOG(" anchor is correct %d,%d -> %d x %d", finalRect.x, finalRect.y,
+ finalRect.width, finalRect.height);
+
+ *aOffset = mPopupMoveToRectParams.mOffset;
+ LOG(" anchor offset %d, %d", aOffset->x, aOffset->y);
+ return true;
+}
+
+bool nsWindow::WaylandPopupCheckAndGetAnchor(GdkRectangle* aPopupAnchor,
+ GdkPoint* aOffset) {
+ LOG("nsWindow::WaylandPopupCheckAndGetAnchor");
+
+ GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(mShell));
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+ if (!gdkWindow || !popupFrame) {
+ LOG(" can't use move-to-rect due missing gdkWindow or popupFrame");
+ return false;
+ }
+
+ if (popupFrame->IsFlippedByLayout()) {
+ LOG(" can't use move-to-rect, flipped / constrained by layout");
+ return false;
+ }
+
+ if (!mPopupMoveToRectParams.mAnchorSet) {
+ LOG(" can't use move-to-rect due missing anchor");
+ return false;
+ }
+ // Update popup layout coordinates from layout by recent popup hierarchy
+ // (calculate correct position according to parent window)
+ // and convert to Gtk coordinates.
+ LayoutDeviceIntRect anchorRect = mPopupMoveToRectParams.mAnchorRect;
+ if (!WaylandPopupIsFirst()) {
+ GdkPoint parent = WaylandGetParentPosition();
+ LOG(" subtract parent position from anchor [%d, %d]\n", parent.x,
+ parent.y);
+ anchorRect.MoveBy(-GdkPointToDevicePixels(parent));
+ }
+
+ *aPopupAnchor = DevicePixelsToGdkRectRoundOut(anchorRect);
+ LOG(" anchored to rectangle [%d, %d] -> [%d x %d]", aPopupAnchor->x,
+ aPopupAnchor->y, aPopupAnchor->width, aPopupAnchor->height);
+
+ if (!WaylandPopupAnchorAdjustForParentPopup(aPopupAnchor, aOffset)) {
+ LOG(" can't use move-to-rect, anchor is not placed inside of parent "
+ "window");
+ return false;
+ }
+
+ return true;
+}
+
+void nsWindow::WaylandPopupPrepareForMove() {
+ LOG("nsWindow::WaylandPopupPrepareForMove()");
+
+ if (mPopupHint == PopupType::Tooltip) {
+ // Don't fiddle with tooltips type, just hide it before move-to-rect
+ if (mPopupUseMoveToRect && gtk_widget_is_visible(mShell)) {
+ HideWaylandPopupWindow(/* aTemporaryHide */ true,
+ /* aRemoveFromPopupList */ false);
+ }
+ LOG(" it's tooltip, quit");
+ return;
+ }
+
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1785185#c8
+ // gtk_window_move() needs GDK_WINDOW_TYPE_HINT_UTILITY popup type.
+ // move-to-rect requires GDK_WINDOW_TYPE_HINT_POPUP_MENU popups type.
+ // We need to set it before map event when popup is hidden.
+ const GdkWindowTypeHint currentType =
+ gtk_window_get_type_hint(GTK_WINDOW(mShell));
+ const GdkWindowTypeHint requiredType = mPopupUseMoveToRect
+ ? GDK_WINDOW_TYPE_HINT_POPUP_MENU
+ : GDK_WINDOW_TYPE_HINT_UTILITY;
+
+ if (!mPopupUseMoveToRect && currentType == requiredType) {
+ LOG(" type matches and we're not forced to hide it, quit.");
+ return;
+ }
+
+ if (gtk_widget_is_visible(mShell)) {
+ HideWaylandPopupWindow(/* aTemporaryHide */ true,
+ /* aRemoveFromPopupList */ false);
+ }
+
+ if (currentType != requiredType) {
+ LOG(" set type %s",
+ requiredType == GDK_WINDOW_TYPE_HINT_POPUP_MENU ? "MENU" : "UTILITY");
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), requiredType);
+ }
+}
+
+// Plain popup move on Wayland - simply place popup on given location.
+// We can't just call gtk_window_move() as it's not effective on visible
+// popups.
+void nsWindow::WaylandPopupMovePlain(int aX, int aY) {
+ LOG("nsWindow::WaylandPopupMovePlain(%d, %d)", aX, aY);
+
+ // We can directly move only popups based on wl_subsurface type.
+ MOZ_DIAGNOSTIC_ASSERT(gtk_window_get_type_hint(GTK_WINDOW(mShell)) ==
+ GDK_WINDOW_TYPE_HINT_UTILITY ||
+ gtk_window_get_type_hint(GTK_WINDOW(mShell)) ==
+ GDK_WINDOW_TYPE_HINT_TOOLTIP);
+
+ gtk_window_move(GTK_WINDOW(mShell), aX, aY);
+
+ // gtk_window_move() can trick us. When widget is hidden gtk_window_move()
+ // does not move the widget but sets new widget coordinates when widget
+ // is mapped again.
+ //
+ // If popup used move-to-rect before
+ // (GdkWindow has POSITION_METHOD_MOVE_TO_RECT set), popup will use
+ // move-to-rect again when it's mapped and we'll get bogus move-to-rect
+ // callback.
+ //
+ // gdk_window_move() sets position_method to POSITION_METHOD_MOVE_RESIZE
+ // so we'll use plain move when popup is shown.
+ if (!gtk_widget_get_mapped(mShell)) {
+ GdkWindow* window = gtk_widget_get_window(mShell);
+ if (window) {
+ gdk_window_move(window, aX, aY);
+ }
+ }
+}
+
+void nsWindow::WaylandPopupMoveImpl() {
+ // Available as of GTK 3.24+
+ static auto sGdkWindowMoveToRect = (void (*)(
+ GdkWindow*, const GdkRectangle*, GdkGravity, GdkGravity, GdkAnchorHints,
+ gint, gint))dlsym(RTLD_DEFAULT, "gdk_window_move_to_rect");
+
+ if (mPopupUseMoveToRect && !sGdkWindowMoveToRect) {
+ LOG("can't use move-to-rect due missing gdk_window_move_to_rect()");
+ mPopupUseMoveToRect = false;
+ }
+
+ GdkRectangle gtkAnchorRect;
+ GdkPoint offset;
+ if (mPopupUseMoveToRect) {
+ mPopupUseMoveToRect =
+ WaylandPopupCheckAndGetAnchor(&gtkAnchorRect, &offset);
+ }
+
+ LOG("nsWindow::WaylandPopupMove");
+ LOG(" original widget popup position [%d, %d]\n", mPopupPosition.x,
+ mPopupPosition.y);
+ LOG(" relative widget popup position [%d, %d]\n", mRelativePopupPosition.x,
+ mRelativePopupPosition.y);
+ LOG(" popup use move to rect %d", mPopupUseMoveToRect);
+
+ WaylandPopupPrepareForMove();
+
+ if (!mPopupUseMoveToRect) {
+ WaylandPopupMovePlain(mRelativePopupPosition.x, mRelativePopupPosition.y);
+ // Layout already should be aware of our bounds, since we didn't change it
+ // from the widget side for flipping or so.
+ return;
+ }
+
+ // Correct popup position now. It will be updated by gdk_window_move_to_rect()
+ // anyway but we need to set it now to avoid a race condition here.
+ WaylandPopupRemoveNegativePosition();
+
+ GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(mShell));
+ if (!g_signal_handler_find(gdkWindow, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
+ FuncToGpointer(NativeMoveResizeCallback), this)) {
+ g_signal_connect(gdkWindow, "moved-to-rect",
+ G_CALLBACK(NativeMoveResizeCallback), this);
+ }
+ mWaitingForMoveToRectCallback = true;
+
+#ifdef MOZ_LOGGING
+ if (LOG_ENABLED()) {
+ LOG(" Call move-to-rect");
+ LOG(" Anchor rect [%d, %d] -> [%d x %d]", gtkAnchorRect.x, gtkAnchorRect.y,
+ gtkAnchorRect.width, gtkAnchorRect.height);
+ LOG(" Offset [%d, %d]", offset.x, offset.y);
+ LOG(" AnchorType");
+ LogPopupGravity(mPopupMoveToRectParams.mAnchorRectType);
+ LOG(" PopupAnchorType");
+ LogPopupGravity(mPopupMoveToRectParams.mPopupAnchorType);
+ LogPopupAnchorHints(mPopupMoveToRectParams.mHints);
+ }
+#endif
+
+ sGdkWindowMoveToRect(gdkWindow, &gtkAnchorRect,
+ mPopupMoveToRectParams.mAnchorRectType,
+ mPopupMoveToRectParams.mPopupAnchorType,
+ mPopupMoveToRectParams.mHints, offset.x, offset.y);
+}
+
+void nsWindow::SetZIndex(int32_t aZIndex) {
+ nsIWidget* oldPrev = GetPrevSibling();
+
+ nsBaseWidget::SetZIndex(aZIndex);
+
+ if (GetPrevSibling() == oldPrev) {
+ return;
+ }
+
+ NS_ASSERTION(!mContainer, "Expected Mozilla child widget");
+
+ // We skip the nsWindows that don't have mGdkWindows.
+ // These are probably in the process of being destroyed.
+ if (!mGdkWindow) {
+ return;
+ }
+
+ if (!GetNextSibling()) {
+ // We're to be on top.
+ if (mGdkWindow) {
+ gdk_window_raise(mGdkWindow);
+ }
+ } else {
+ // All the siblings before us need to be below our widget.
+ for (nsWindow* w = this; w;
+ w = static_cast<nsWindow*>(w->GetPrevSibling())) {
+ if (w->mGdkWindow) {
+ gdk_window_lower(w->mGdkWindow);
+ }
+ }
+ }
+}
+
+void nsWindow::SetSizeMode(nsSizeMode aMode) {
+ LOG("nsWindow::SetSizeMode %d\n", aMode);
+
+ // Return if there's no shell or our current state is the same as the mode we
+ // were just set to.
+ if (!mShell) {
+ LOG(" no shell");
+ return;
+ }
+
+ if (mSizeMode == aMode && mLastSizeModeRequest == aMode) {
+ LOG(" already set");
+ return;
+ }
+
+ // It is tempting to try to optimize calls below based only on current
+ // mSizeMode, but that wouldn't work if there's a size-request in flight
+ // (specially before show). See bug 1789823.
+ const auto SizeModeMightBe = [&](nsSizeMode aModeToTest) {
+ if (mSizeMode != mLastSizeModeRequest) {
+ // Arbitrary size mode requests might be ongoing.
+ return true;
+ }
+ return mSizeMode == aModeToTest;
+ };
+
+ if (aMode != nsSizeMode_Fullscreen && aMode != nsSizeMode_Minimized) {
+ // Fullscreen and minimized are compatible.
+ if (SizeModeMightBe(nsSizeMode_Fullscreen)) {
+ MakeFullScreen(false);
+ }
+ }
+
+ switch (aMode) {
+ case nsSizeMode_Maximized:
+ LOG(" set maximized");
+ gtk_window_maximize(GTK_WINDOW(mShell));
+ break;
+ case nsSizeMode_Minimized:
+ LOG(" set minimized");
+ gtk_window_iconify(GTK_WINDOW(mShell));
+ break;
+ case nsSizeMode_Fullscreen:
+ LOG(" set fullscreen");
+ MakeFullScreen(true);
+ break;
+ default:
+ MOZ_FALLTHROUGH_ASSERT("Unknown size mode");
+ case nsSizeMode_Normal:
+ LOG(" set normal");
+ if (SizeModeMightBe(nsSizeMode_Maximized)) {
+ gtk_window_unmaximize(GTK_WINDOW(mShell));
+ }
+ if (SizeModeMightBe(nsSizeMode_Minimized)) {
+ gtk_window_deiconify(GTK_WINDOW(mShell));
+ // We need this for actual deiconification on mutter.
+ gtk_window_present(GTK_WINDOW(mShell));
+ }
+ break;
+ }
+ mLastSizeModeRequest = aMode;
+}
+
+#define kDesktopMutterSchema "org.gnome.mutter"_ns
+#define kDesktopDynamicWorkspacesKey "dynamic-workspaces"_ns
+
+static bool WorkspaceManagementDisabled(GdkScreen* screen) {
+ if (Preferences::GetBool("widget.disable-workspace-management", false)) {
+ return true;
+ }
+ if (Preferences::HasUserValue("widget.workspace-management")) {
+ return Preferences::GetBool("widget.workspace-management");
+ }
+
+ if (IsGnomeDesktopEnvironment()) {
+ // Gnome uses dynamic workspaces by default so disable workspace management
+ // in that case.
+ bool usesDynamicWorkspaces = true;
+ nsCOMPtr<nsIGSettingsService> gsettings =
+ do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+ if (gsettings) {
+ nsCOMPtr<nsIGSettingsCollection> mutterSettings;
+ gsettings->GetCollectionForSchema(kDesktopMutterSchema,
+ getter_AddRefs(mutterSettings));
+ if (mutterSettings) {
+ mutterSettings->GetBoolean(kDesktopDynamicWorkspacesKey,
+ &usesDynamicWorkspaces);
+ }
+ }
+ return usesDynamicWorkspaces;
+ }
+
+ const auto& desktop = GetDesktopEnvironmentIdentifier();
+ return desktop.EqualsLiteral("bspwm") || desktop.EqualsLiteral("i3");
+}
+
+void nsWindow::GetWorkspaceID(nsAString& workspaceID) {
+ workspaceID.Truncate();
+
+ if (!GdkIsX11Display() || !mShell) {
+ return;
+ }
+
+#ifdef MOZ_X11
+ LOG("nsWindow::GetWorkspaceID()\n");
+
+ // Get the gdk window for this widget.
+ GdkWindow* gdk_window = gtk_widget_get_window(mShell);
+ if (!gdk_window) {
+ LOG(" missing Gdk window, quit.");
+ return;
+ }
+
+ if (WorkspaceManagementDisabled(gdk_window_get_screen(gdk_window))) {
+ LOG(" WorkspaceManagementDisabled, quit.");
+ return;
+ }
+
+ GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
+ GdkAtom type_returned;
+ int format_returned;
+ int length_returned;
+ long* wm_desktop;
+
+ if (!gdk_property_get(gdk_window, gdk_atom_intern("_NET_WM_DESKTOP", FALSE),
+ cardinal_atom,
+ 0, // offset
+ INT32_MAX, // length
+ FALSE, // delete
+ &type_returned, &format_returned, &length_returned,
+ (guchar**)&wm_desktop)) {
+ LOG(" gdk_property_get() failed, quit.");
+ return;
+ }
+
+ LOG(" got workspace ID %d", (int32_t)wm_desktop[0]);
+ workspaceID.AppendInt((int32_t)wm_desktop[0]);
+ g_free(wm_desktop);
+#endif
+}
+
+void nsWindow::MoveToWorkspace(const nsAString& workspaceIDStr) {
+ nsresult rv = NS_OK;
+ int32_t workspaceID = workspaceIDStr.ToInteger(&rv);
+
+ LOG("nsWindow::MoveToWorkspace() ID %d", workspaceID);
+ if (NS_FAILED(rv) || !workspaceID || !GdkIsX11Display() || !mShell) {
+ LOG(" MoveToWorkspace disabled, quit");
+ return;
+ }
+
+#ifdef MOZ_X11
+ // Get the gdk window for this widget.
+ GdkWindow* gdk_window = gtk_widget_get_window(mShell);
+ if (!gdk_window) {
+ LOG(" failed to get GdkWindow, quit.");
+ return;
+ }
+
+ // This code is inspired by some found in the 'gxtuner' project.
+ // https://github.com/brummer10/gxtuner/blob/792d453da0f3a599408008f0f1107823939d730d/deskpager.cpp#L50
+ XEvent xevent;
+ Display* xdisplay = gdk_x11_get_default_xdisplay();
+ GdkScreen* screen = gdk_window_get_screen(gdk_window);
+ Window root_win = GDK_WINDOW_XID(gdk_screen_get_root_window(screen));
+ GdkDisplay* display = gdk_window_get_display(gdk_window);
+ Atom type = gdk_x11_get_xatom_by_name_for_display(display, "_NET_WM_DESKTOP");
+
+ xevent.type = ClientMessage;
+ xevent.xclient.type = ClientMessage;
+ xevent.xclient.serial = 0;
+ xevent.xclient.send_event = TRUE;
+ xevent.xclient.display = xdisplay;
+ xevent.xclient.window = GDK_WINDOW_XID(gdk_window);
+ xevent.xclient.message_type = type;
+ xevent.xclient.format = 32;
+ xevent.xclient.data.l[0] = workspaceID;
+ xevent.xclient.data.l[1] = X11CurrentTime;
+ xevent.xclient.data.l[2] = 0;
+ xevent.xclient.data.l[3] = 0;
+ xevent.xclient.data.l[4] = 0;
+
+ XSendEvent(xdisplay, root_win, FALSE,
+ SubstructureNotifyMask | SubstructureRedirectMask, &xevent);
+
+ XFlush(xdisplay);
+ LOG(" moved to workspace");
+#endif
+}
+
+void nsWindow::SetUserTimeAndStartupTokenForActivatedWindow() {
+ nsGTKToolkit* toolkit = nsGTKToolkit::GetToolkit();
+ if (!toolkit || MOZ_UNLIKELY(mWindowType == WindowType::Invisible)) {
+ return;
+ }
+
+ mWindowActivationTokenFromEnv = toolkit->GetStartupToken();
+ if (!mWindowActivationTokenFromEnv.IsEmpty()) {
+ if (!GdkIsWaylandDisplay()) {
+ gtk_window_set_startup_id(GTK_WINDOW(mShell),
+ mWindowActivationTokenFromEnv.get());
+ // In the case of X11, the above call is all we need. For wayland we need
+ // to keep the token around until we take it in
+ // TransferFocusToWaylandWindow.
+ mWindowActivationTokenFromEnv.Truncate();
+ }
+ } else if (uint32_t timestamp = toolkit->GetFocusTimestamp()) {
+ // We don't have the data we need. Fall back to an
+ // approximation ... using the timestamp of the remote command
+ // being received as a guess for the timestamp of the user event
+ // that triggered it.
+ gdk_window_focus(gtk_widget_get_window(mShell), timestamp);
+ }
+
+ // If we used the startup ID, that already contains the focus timestamp;
+ // we don't want to reuse the timestamp next time we raise the window
+ toolkit->SetFocusTimestamp(0);
+ toolkit->SetStartupToken(""_ns);
+}
+
+/* static */
+guint32 nsWindow::GetLastUserInputTime() {
+ // gdk_x11_display_get_user_time/gtk_get_current_event_time tracks
+ // button and key presses, DESKTOP_STARTUP_ID used to start the app,
+ // drop events from external drags,
+ // WM_DELETE_WINDOW delete events, but not usually mouse motion nor
+ // button and key releases. Therefore use the most recent of
+ // gdk_x11_display_get_user_time and the last time that we have seen.
+#ifdef MOZ_X11
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ guint32 timestamp = GdkIsX11Display(gdkDisplay)
+ ? gdk_x11_display_get_user_time(gdkDisplay)
+ : gtk_get_current_event_time();
+#else
+ guint32 timestamp = gtk_get_current_event_time();
+#endif
+
+ if (sLastUserInputTime != GDK_CURRENT_TIME &&
+ TimestampIsNewerThan(sLastUserInputTime, timestamp)) {
+ return sLastUserInputTime;
+ }
+
+ return timestamp;
+}
+
+#ifdef MOZ_WAYLAND
+void nsWindow::FocusWaylandWindow(const char* aTokenID) {
+ MOZ_DIAGNOSTIC_ASSERT(aTokenID);
+
+ LOG("nsWindow::FocusWaylandWindow(%s)", aTokenID);
+ if (IsDestroyed()) {
+ LOG(" already destroyed, quit.");
+ return;
+ }
+ wl_surface* surface =
+ mGdkWindow ? gdk_wayland_window_get_wl_surface(mGdkWindow) : nullptr;
+ if (!surface) {
+ LOG(" mGdkWindow is not visible, quit.");
+ return;
+ }
+
+ LOG(" requesting xdg-activation, surface ID %d",
+ wl_proxy_get_id((struct wl_proxy*)surface));
+ xdg_activation_v1* xdg_activation = WaylandDisplayGet()->GetXdgActivation();
+ if (!xdg_activation) {
+ return;
+ }
+ xdg_activation_v1_activate(xdg_activation, aTokenID, surface);
+}
+
+// Transfer focus from gFocusWindow to aWindow and use xdg_activation
+// protocol for it.
+void nsWindow::TransferFocusToWaylandWindow(nsWindow* aWindow) {
+ LOGW("nsWindow::TransferFocusToWaylandWindow(%p) gFocusWindow %p", aWindow,
+ gFocusWindow);
+ auto promise = mozilla::widget::RequestWaylandFocusPromise();
+ if (NS_WARN_IF(!promise)) {
+ LOGW(" quit, failed to create TransferFocusToWaylandWindow [%p]", aWindow);
+ return;
+ }
+ promise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ /* resolve */
+ [window = RefPtr{aWindow}](nsCString token) {
+ window->FocusWaylandWindow(token.get());
+ },
+ /* reject */
+ [window = RefPtr{aWindow}](bool state) {
+ LOGW("TransferFocusToWaylandWindow [%p] failed", window.get());
+ });
+}
+#endif
+
+// Request activation of this window or give focus to this widget.
+// aRaise means whether we should request activation of this widget's
+// toplevel window.
+//
+// nsWindow::SetFocus(Raise::Yes) - Raise and give focus to toplevel window.
+// nsWindow::SetFocus(Raise::No) - Give focus to this window.
+void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
+ LOG("nsWindow::SetFocus Raise %d\n", aRaise == Raise::Yes);
+
+ // Raise the window if someone passed in true and the prefs are
+ // set properly.
+ GtkWidget* toplevelWidget = gtk_widget_get_toplevel(GTK_WIDGET(mContainer));
+
+ LOG(" gFocusWindow [%p]\n", gFocusWindow);
+ LOG(" mContainer [%p]\n", GTK_WIDGET(mContainer));
+ LOG(" Toplevel widget [%p]\n", toplevelWidget);
+
+ // Make sure that our owning widget has focus. If it doesn't try to
+ // grab it. Note that we don't set our focus flag in this case.
+ if (StaticPrefs::mozilla_widget_raise_on_setfocus_AtStartup() &&
+ aRaise == Raise::Yes && toplevelWidget &&
+ !gtk_widget_has_focus(toplevelWidget)) {
+ if (gtk_widget_get_visible(mShell)) {
+ LOG(" toplevel is not focused");
+ gdk_window_show_unraised(gtk_widget_get_window(mShell));
+ // Unset the urgency hint if possible.
+ SetUrgencyHint(mShell, false);
+ }
+ }
+
+ RefPtr<nsWindow> toplevelWindow = get_window_for_gtk_widget(toplevelWidget);
+ if (!toplevelWindow) {
+ LOG(" missing toplevel nsWindow, quit\n");
+ return;
+ }
+
+ if (aRaise == Raise::Yes) {
+ // means request toplevel activation.
+
+ // This is asynchronous. If and when the window manager accepts the request,
+ // then the focus widget will get a focus-in-event signal.
+ if (StaticPrefs::mozilla_widget_raise_on_setfocus_AtStartup() &&
+ toplevelWindow->mIsShown && toplevelWindow->mShell &&
+ !gtk_window_is_active(GTK_WINDOW(toplevelWindow->mShell))) {
+ LOG(" toplevel is visible but not active, requesting activation [%p]",
+ toplevelWindow.get());
+
+ // Take the time here explicitly for the call below.
+ const uint32_t timestamp = [&] {
+ if (nsGTKToolkit* toolkit = nsGTKToolkit::GetToolkit()) {
+ if (uint32_t t = toolkit->GetFocusTimestamp()) {
+ toolkit->SetFocusTimestamp(0);
+ return t;
+ }
+ }
+ return GetLastUserInputTime();
+ }();
+
+ toplevelWindow->SetUserTimeAndStartupTokenForActivatedWindow();
+ gtk_window_present_with_time(GTK_WINDOW(toplevelWindow->mShell),
+ timestamp);
+
+#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay()) {
+ auto existingToken =
+ std::move(toplevelWindow->mWindowActivationTokenFromEnv);
+ if (!existingToken.IsEmpty()) {
+ LOG(" has existing activation token.");
+ toplevelWindow->FocusWaylandWindow(existingToken.get());
+ } else {
+ LOG(" missing activation token, try to transfer from focused "
+ "window");
+ TransferFocusToWaylandWindow(toplevelWindow);
+ }
+ }
+#endif
+ }
+ return;
+ }
+
+ // aRaise == No means that keyboard events should be dispatched from this
+ // widget.
+
+ // Ensure GTK_WIDGET(mContainer) is the focused GtkWidget within its toplevel
+ // window.
+ //
+ // For WindowType::Popup, this GtkWidget may not actually be the one that
+ // receives the key events as it may be the parent window that is active.
+ if (!gtk_widget_is_focus(GTK_WIDGET(mContainer))) {
+ // This is synchronous. It takes focus from a plugin or from a widget
+ // in an embedder. The focus manager already knows that this window
+ // is active so gBlockActivateEvent avoids another (unnecessary)
+ // activate notification.
+ gBlockActivateEvent = true;
+ gtk_widget_grab_focus(GTK_WIDGET(mContainer));
+ gBlockActivateEvent = false;
+ }
+
+ // If this is the widget that already has focus, return.
+ if (gFocusWindow == this) {
+ LOG(" already have focus");
+ return;
+ }
+
+ // Set this window to be the focused child window
+ gFocusWindow = this;
+
+ if (mIMContext) {
+ mIMContext->OnFocusWindow(this);
+ }
+
+ LOG(" widget now has focus in SetFocus()");
+}
+
+LayoutDeviceIntRect nsWindow::GetScreenBounds() {
+ const LayoutDeviceIntPoint origin = [&] {
+ // XXX Can't we use mGdkWindow here?
+ //
+ // Use the point including window decorations. Don't do this for popups,
+ // because we get wrong coordinates for gtk for override-redirect windows in
+ // HiDPI screens, and those don't have window decorations anyways.
+ //
+ // See https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4820
+ if (mContainer && mWindowType != WindowType::Popup) {
+ gint x, y;
+ gdk_window_get_root_origin(gtk_widget_get_window(GTK_WIDGET(mContainer)),
+ &x, &y);
+ return GdkPointToDevicePixels({x, y});
+ }
+ return WidgetToScreenOffset();
+ }();
+
+ // mBounds.Size() is the window bounds, not the window-manager frame
+ // bounds (bug 581863). gdk_window_get_frame_extents would give the
+ // frame bounds, but mBounds.Size() is returned here for consistency
+ // with Resize.
+ const LayoutDeviceIntRect rect(origin, mBounds.Size());
+#if MOZ_LOGGING
+ if (MOZ_LOG_TEST(IsPopup() ? gWidgetPopupLog : gWidgetLog,
+ LogLevel::Verbose)) {
+ gint scale = GdkCeiledScaleFactor();
+ if (mLastLoggedScale != scale || !(mLastLoggedBoundSize == rect)) {
+ mLastLoggedScale = scale;
+ mLastLoggedBoundSize = rect;
+ LOG("GetScreenBounds %d,%d -> %d x %d, unscaled %d,%d -> %d x %d\n",
+ rect.x, rect.y, rect.width, rect.height, rect.x / scale,
+ rect.y / scale, rect.width / scale, rect.height / scale);
+ }
+ }
+#endif
+ return rect;
+}
+
+LayoutDeviceIntSize nsWindow::GetClientSize() {
+ return LayoutDeviceIntSize(mBounds.width, mBounds.height);
+}
+
+LayoutDeviceIntRect nsWindow::GetClientBounds() {
+ // GetBounds returns a rect whose top left represents the top left of the
+ // outer bounds, but whose width/height represent the size of the inner
+ // bounds (which is messed up).
+ LayoutDeviceIntRect rect = GetBounds();
+ rect.MoveBy(GetClientOffset());
+ return rect;
+}
+
+void nsWindow::RecomputeClientOffset(bool aNotify) {
+ if (mWindowType != WindowType::Dialog &&
+ mWindowType != WindowType::TopLevel) {
+ return;
+ }
+
+ auto oldOffset = mClientOffset;
+
+ mClientOffset = WidgetToScreenOffset() - mBounds.TopLeft();
+
+ if (aNotify && mClientOffset != oldOffset) {
+ // Send a WindowMoved notification. This ensures that BrowserParent picks up
+ // the new client offset and sends it to the child process if appropriate.
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+ }
+}
+
+gboolean nsWindow::OnPropertyNotifyEvent(GtkWidget* aWidget,
+ GdkEventProperty* aEvent) {
+ if (aEvent->atom == gdk_atom_intern("_NET_FRAME_EXTENTS", FALSE)) {
+ RecomputeClientOffset(/* aNotify = */ true);
+ return FALSE;
+ }
+ if (!mGdkWindow) {
+ return FALSE;
+ }
+#ifdef MOZ_X11
+ if (GetCurrentTimeGetter()->PropertyNotifyHandler(aWidget, aEvent)) {
+ return TRUE;
+ }
+#endif
+ return FALSE;
+}
+
+static GdkCursor* GetCursorForImage(const nsIWidget::Cursor& aCursor,
+ int32_t aWidgetScaleFactor) {
+ if (!aCursor.IsCustom()) {
+ return nullptr;
+ }
+ nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
+
+ // NOTE: GTK only allows integer scale factors, so we ceil to the larger scale
+ // factor and then tell gtk to scale it down. We ensure to scale at least to
+ // the GDK scale factor, so that cursors aren't downsized in HiDPI on wayland,
+ // see bug 1707533.
+ int32_t gtkScale = std::max(
+ aWidgetScaleFactor, int32_t(std::ceil(std::max(aCursor.mResolution.mX,
+ aCursor.mResolution.mY))));
+
+ // Reject cursors greater than 128 pixels in some direction, to prevent
+ // spoofing.
+ // XXX ideally we should rescale. Also, we could modify the API to
+ // allow trusted content to set larger cursors.
+ //
+ // TODO(emilio, bug 1445844): Unify the solution for this with other
+ // platforms.
+ if (size.width > 128 || size.height > 128) {
+ return nullptr;
+ }
+
+ nsIntSize rasterSize = size * gtkScale;
+ RefPtr<GdkPixbuf> pixbuf =
+ nsImageToPixbuf::ImageToPixbuf(aCursor.mContainer, Some(rasterSize));
+ if (!pixbuf) {
+ return nullptr;
+ }
+
+ // Looks like all cursors need an alpha channel (tested on Gtk 2.4.4). This
+ // is of course not documented anywhere...
+ // So add one if there isn't one yet
+ if (!gdk_pixbuf_get_has_alpha(pixbuf)) {
+ RefPtr<GdkPixbuf> alphaBuf =
+ dont_AddRef(gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0));
+ pixbuf = std::move(alphaBuf);
+ if (!pixbuf) {
+ return nullptr;
+ }
+ }
+
+ cairo_surface_t* surface =
+ gdk_cairo_surface_create_from_pixbuf(pixbuf, gtkScale, nullptr);
+ if (!surface) {
+ return nullptr;
+ }
+
+ auto CleanupSurface =
+ MakeScopeExit([&]() { cairo_surface_destroy(surface); });
+
+ return gdk_cursor_new_from_surface(gdk_display_get_default(), surface,
+ aCursor.mHotspotX, aCursor.mHotspotY);
+}
+
+void nsWindow::SetCursor(const Cursor& aCursor) {
+ // if we're not the toplevel window pass up the cursor request to
+ // the toplevel window to handle it.
+ if (!mContainer && mGdkWindow) {
+ if (nsWindow* window = GetContainerWindow()) {
+ window->SetCursor(aCursor);
+ }
+ return;
+ }
+
+ if (mWidgetCursorLocked) {
+ return;
+ }
+
+ // Only change cursor if it's actually been changed
+ if (!mUpdateCursor && mCursor == aCursor) {
+ return;
+ }
+
+ mUpdateCursor = false;
+ mCursor = aCursor;
+
+ // Try to set the cursor image first, and fall back to the numeric cursor.
+ bool fromImage = true;
+ GdkCursor* newCursor = GetCursorForImage(aCursor, GdkCeiledScaleFactor());
+ if (!newCursor) {
+ fromImage = false;
+ newCursor = get_gtk_cursor(aCursor.mDefaultCursor);
+ }
+
+ auto CleanupCursor = mozilla::MakeScopeExit([&]() {
+ // get_gtk_cursor returns a weak reference, which we shouldn't unref.
+ if (fromImage) {
+ g_object_unref(newCursor);
+ }
+ });
+
+ if (!newCursor || !mContainer) {
+ return;
+ }
+
+ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)),
+ newCursor);
+}
+
+void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
+ if (!mGdkWindow) return;
+
+ GdkRectangle rect = DevicePixelsToGdkRectRoundOut(aRect);
+ gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
+
+ LOG("Invalidate (rect): %d %d %d %d\n", rect.x, rect.y, rect.width,
+ rect.height);
+}
+
+void* nsWindow::GetNativeData(uint32_t aDataType) {
+ switch (aDataType) {
+ case NS_NATIVE_WINDOW:
+ case NS_NATIVE_WIDGET: {
+ return mGdkWindow;
+ }
+
+ case NS_NATIVE_SHELLWIDGET:
+ return GetToplevelWidget();
+
+ case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID:
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ return (void*)GDK_WINDOW_XID(gdk_window_get_toplevel(mGdkWindow));
+ }
+#endif
+ NS_WARNING(
+ "nsWindow::GetNativeData(): NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID is not "
+ "handled on Wayland!");
+ return nullptr;
+ case NS_RAW_NATIVE_IME_CONTEXT: {
+ void* pseudoIMEContext = GetPseudoIMEContext();
+ if (pseudoIMEContext) {
+ return pseudoIMEContext;
+ }
+ // If IME context isn't available on this widget, we should set |this|
+ // instead of nullptr.
+ if (!mIMContext) {
+ return this;
+ }
+ return mIMContext.get();
+ }
+ case NS_NATIVE_OPENGL_CONTEXT:
+ return nullptr;
+ case NS_NATIVE_EGL_WINDOW: {
+ void* eglWindow = nullptr;
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ eglWindow = mGdkWindow ? (void*)GDK_WINDOW_XID(mGdkWindow) : nullptr;
+ }
+#endif
+#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay()) {
+ if (mCompositorWidgetDelegate &&
+ mCompositorWidgetDelegate->AsGtkCompositorWidget() &&
+ mCompositorWidgetDelegate->AsGtkCompositorWidget()->IsHidden()) {
+ NS_WARNING("Getting OpenGL EGL window for hidden Gtk window!");
+ return nullptr;
+ }
+ eglWindow = moz_container_wayland_get_egl_window(
+ mContainer, FractionalScaleFactor());
+ }
+#endif
+ LOG("Get NS_NATIVE_EGL_WINDOW mGdkWindow %p returned eglWindow %p",
+ mGdkWindow, eglWindow);
+ return eglWindow;
+ }
+ default:
+ NS_WARNING("nsWindow::GetNativeData called with bad value");
+ return nullptr;
+ }
+}
+
+nsresult nsWindow::SetTitle(const nsAString& aTitle) {
+ if (!mShell) return NS_OK;
+
+ // convert the string into utf8 and set the title.
+#define UTF8_FOLLOWBYTE(ch) (((ch)&0xC0) == 0x80)
+ NS_ConvertUTF16toUTF8 titleUTF8(aTitle);
+ if (titleUTF8.Length() > NS_WINDOW_TITLE_MAX_LENGTH) {
+ // Truncate overlong titles (bug 167315). Make sure we chop after a
+ // complete sequence by making sure the next char isn't a follow-byte.
+ uint32_t len = NS_WINDOW_TITLE_MAX_LENGTH;
+ while (UTF8_FOLLOWBYTE(titleUTF8[len])) --len;
+ titleUTF8.Truncate(len);
+ }
+ gtk_window_set_title(GTK_WINDOW(mShell), (const char*)titleUTF8.get());
+
+ return NS_OK;
+}
+
+void nsWindow::SetIcon(const nsAString& aIconSpec) {
+ if (!mShell) return;
+
+ nsAutoCString iconName;
+
+ if (aIconSpec.EqualsLiteral("default")) {
+ nsAutoString brandName;
+ WidgetUtils::GetBrandShortName(brandName);
+ if (brandName.IsEmpty()) {
+ brandName.AssignLiteral(u"Mozilla");
+ }
+ AppendUTF16toUTF8(brandName, iconName);
+ ToLowerCase(iconName);
+ } else {
+ AppendUTF16toUTF8(aIconSpec, iconName);
+ }
+
+ nsCOMPtr<nsIFile> iconFile;
+ nsAutoCString path;
+
+ gint* iconSizes = gtk_icon_theme_get_icon_sizes(gtk_icon_theme_get_default(),
+ iconName.get());
+ bool foundIcon = (iconSizes[0] != 0);
+ g_free(iconSizes);
+
+ if (!foundIcon) {
+ // Look for icons with the following suffixes appended to the base name
+ // The last two entries (for the old XPM format) will be ignored unless
+ // no icons are found using other suffixes. XPM icons are deprecated.
+
+ const char16_t extensions[9][8] = {u".png", u"16.png", u"32.png",
+ u"48.png", u"64.png", u"128.png",
+ u"256.png", u".xpm", u"16.xpm"};
+
+ for (uint32_t i = 0; i < ArrayLength(extensions); i++) {
+ // Don't bother looking for XPM versions if we found a PNG.
+ if (i == ArrayLength(extensions) - 2 && foundIcon) break;
+
+ ResolveIconName(aIconSpec, nsDependentString(extensions[i]),
+ getter_AddRefs(iconFile));
+ if (iconFile) {
+ iconFile->GetNativePath(path);
+ GdkPixbuf* icon = gdk_pixbuf_new_from_file(path.get(), nullptr);
+ if (icon) {
+ gtk_icon_theme_add_builtin_icon(iconName.get(),
+ gdk_pixbuf_get_height(icon), icon);
+ g_object_unref(icon);
+ foundIcon = true;
+ }
+ }
+ }
+ }
+
+ // leave the default icon intact if no matching icons were found
+ if (foundIcon) {
+ gtk_window_set_icon_name(GTK_WINDOW(mShell), iconName.get());
+ }
+}
+
+/* TODO(bug 1655924): gdk_window_get_origin is can block waiting for the X
+ server for a long time, we would like to use the implementation below
+ instead. However, removing the synchronous x server queries causes a race
+ condition to surface, causing issues such as bug 1652743 and bug 1653711.
+
+
+ This code can be used instead of gdk_window_get_origin() but it cuases
+ such issues:
+
+ *aX = 0;
+ *aY = 0;
+ if (!aWindow) {
+ return;
+ }
+
+ GdkWindow* current = aWindow;
+ while (GdkWindow* parent = gdk_window_get_parent(current)) {
+ if (parent == current) {
+ break;
+ }
+
+ int x = 0;
+ int y = 0;
+ gdk_window_get_position(current, &x, &y);
+ *aX += x;
+ *aY += y;
+
+ current = parent;
+ }
+*/
+LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
+ // Don't use gdk_window_get_origin() on wl_subsurface Wayland popups
+ // https://gitlab.gnome.org/GNOME/gtk/-/issues/5287
+ if (IsWaylandPopup() && !mPopupUseMoveToRect) {
+ return mBounds.TopLeft();
+ }
+ nsIntPoint origin(0, 0);
+ if (mGdkWindow) {
+ gdk_window_get_origin(mGdkWindow, &origin.x.value, &origin.y.value);
+ }
+ return GdkPointToDevicePixels({origin.x, origin.y});
+}
+
+void nsWindow::CaptureRollupEvents(bool aDoCapture) {
+ LOG("CaptureRollupEvents(%d)\n", aDoCapture);
+ if (mIsDestroyed) {
+ return;
+ }
+
+ static constexpr auto kCaptureEventsMask =
+ GdkEventMask(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_TOUCH_MASK);
+
+ static bool sSystemNeedsPointerGrab = [&] {
+ if (GdkIsWaylandDisplay()) {
+ return false;
+ }
+ // We only need to grab the pointer for X servers that move the focus with
+ // the pointer (like twm, sawfish...). Since we roll up popups on focus out,
+ // not grabbing the pointer triggers rollup when the mouse enters the popup
+ // and leaves the main window, see bug 1807482.
+ //
+ // FVWM is also affected but less severely: the pointer can enter the
+ // popup, but if it briefly moves out of the popup and over the main window
+ // then we see a focus change and roll up the popup.
+ //
+ // We don't do it for most common desktops, if only because it causes X11
+ // crashes like bug 1607713.
+ const auto& desktop = GetDesktopEnvironmentIdentifier();
+ return desktop.EqualsLiteral("twm") || desktop.EqualsLiteral("sawfish") ||
+ StringBeginsWith(desktop, "fvwm"_ns);
+ }();
+
+ const bool grabPointer = [] {
+ switch (StaticPrefs::widget_gtk_grab_pointer()) {
+ case 0:
+ return false;
+ case 1:
+ return true;
+ default:
+ return sSystemNeedsPointerGrab;
+ }
+ }();
+
+ if (!grabPointer) {
+ return;
+ }
+
+ mNeedsToRetryCapturingMouse = false;
+ if (aDoCapture) {
+ if (mIsDragPopup || DragInProgress()) {
+ // Don't add a grab if a drag is in progress, or if the widget is a drag
+ // feedback popup. (panels with type="drag").
+ return;
+ }
+
+ if (!mHasMappedToplevel) {
+ // On X, capturing an unmapped window is pointless (returns
+ // GDK_GRAB_NOT_VIEWABLE). Avoid the X server round-trip and just retry
+ // when we're mapped.
+ mNeedsToRetryCapturingMouse = true;
+ return;
+ }
+
+ // gdk_pointer_grab is deprecated in favor of gdk_device_grab, but that
+ // causes a strange bug on X11, most obviously with nested popup menus:
+ // we somehow take the pointer position relative to the top left of the
+ // outer menu and use it as if it were relative to the submenu. This
+ // doesn't happen with gdk_pointer_grab even though the code is very
+ // similar. See the video attached to bug 1750721 for a demonstration,
+ // and see also bug 1820542 for when the same thing happened with
+ // another attempt to use gdk_device_grab.
+ //
+ // (gdk_device_grab is deprecated in favor of gdk_seat_grab as of 3.20,
+ // but at the time of this writing we still support older versions of
+ // GTK 3.)
+ GdkGrabStatus status =
+ gdk_pointer_grab(GetToplevelGdkWindow(),
+ /* owner_events = */ true, kCaptureEventsMask,
+ /* confine_to = */ nullptr,
+ /* cursor = */ nullptr, GetLastUserInputTime());
+ Unused << NS_WARN_IF(status != GDK_GRAB_SUCCESS);
+ LOG(" > pointer grab with status %d", int(status));
+ gtk_grab_add(GTK_WIDGET(mContainer));
+ } else {
+ // There may not have been a drag in process when aDoCapture was set,
+ // so make sure to remove any added grab. This is a no-op if the grab
+ // was not added to this widget.
+ gtk_grab_remove(GTK_WIDGET(mContainer));
+ gdk_pointer_ungrab(GetLastUserInputTime());
+ }
+}
+
+nsresult nsWindow::GetAttention(int32_t aCycleCount) {
+ LOG("nsWindow::GetAttention");
+
+ GtkWidget* top_window = GetToplevelWidget();
+ GtkWidget* top_focused_window =
+ gFocusWindow ? gFocusWindow->GetToplevelWidget() : nullptr;
+
+ // Don't get attention if the window is focused anyway.
+ if (top_window && (gtk_widget_get_visible(top_window)) &&
+ top_window != top_focused_window) {
+ SetUrgencyHint(top_window, true);
+ }
+
+ return NS_OK;
+}
+
+bool nsWindow::HasPendingInputEvent() {
+ // This sucks, but gtk/gdk has no way to answer the question we want while
+ // excluding paint events, and there's no X API that will let us peek
+ // without blocking or removing. To prevent event reordering, peek
+ // anything except expose events. Reordering expose and others should be
+ // ok, hopefully.
+ bool haveEvent = false;
+#ifdef MOZ_X11
+ XEvent ev;
+ if (GdkIsX11Display()) {
+ Display* display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+ haveEvent = XCheckMaskEvent(
+ display,
+ KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
+ EnterWindowMask | LeaveWindowMask | PointerMotionMask |
+ PointerMotionHintMask | Button1MotionMask | Button2MotionMask |
+ Button3MotionMask | Button4MotionMask | Button5MotionMask |
+ ButtonMotionMask | KeymapStateMask | VisibilityChangeMask |
+ StructureNotifyMask | ResizeRedirectMask | SubstructureNotifyMask |
+ SubstructureRedirectMask | FocusChangeMask | PropertyChangeMask |
+ ColormapChangeMask | OwnerGrabButtonMask,
+ &ev);
+ if (haveEvent) {
+ XPutBackEvent(display, &ev);
+ }
+ }
+#endif
+ return haveEvent;
+}
+
+#ifdef cairo_copy_clip_rectangle_list
+# error "Looks like we're including Mozilla's cairo instead of system cairo"
+#endif
+static bool ExtractExposeRegion(LayoutDeviceIntRegion& aRegion, cairo_t* cr) {
+ cairo_rectangle_list_t* rects = cairo_copy_clip_rectangle_list(cr);
+ if (rects->status != CAIRO_STATUS_SUCCESS) {
+ NS_WARNING("Failed to obtain cairo rectangle list.");
+ return false;
+ }
+
+ for (int i = 0; i < rects->num_rectangles; i++) {
+ const cairo_rectangle_t& r = rects->rectangles[i];
+ aRegion.Or(aRegion,
+ LayoutDeviceIntRect::Truncate((float)r.x, (float)r.y,
+ (float)r.width, (float)r.height));
+ }
+
+ cairo_rectangle_list_destroy(rects);
+ return true;
+}
+
+#ifdef MOZ_WAYLAND
+void nsWindow::CreateCompositorVsyncDispatcher() {
+ LOG_VSYNC("nsWindow::CreateCompositorVsyncDispatcher()");
+ if (!mWaylandVsyncSource) {
+ LOG_VSYNC(
+ " mWaylandVsyncSource is missing, create "
+ "nsBaseWidget::CompositorVsyncDispatcher()");
+ nsBaseWidget::CreateCompositorVsyncDispatcher();
+ return;
+ }
+ if (!mCompositorVsyncDispatcherLock) {
+ mCompositorVsyncDispatcherLock =
+ MakeUnique<Mutex>("mCompositorVsyncDispatcherLock");
+ }
+ MutexAutoLock lock(*mCompositorVsyncDispatcherLock);
+ if (!mCompositorVsyncDispatcher) {
+ LOG_VSYNC(" create CompositorVsyncDispatcher()");
+ mCompositorVsyncDispatcher =
+ new CompositorVsyncDispatcher(mWaylandVsyncDispatcher);
+ }
+}
+#endif
+
+gboolean nsWindow::OnExposeEvent(cairo_t* cr) {
+ // This might destroy us.
+ NotifyOcclusionState(OcclusionState::VISIBLE);
+ if (mIsDestroyed) {
+ return FALSE;
+ }
+
+ // Send any pending resize events so that layout can update.
+ // May run event loop and destroy us.
+ MaybeDispatchResized();
+ if (mIsDestroyed) {
+ return FALSE;
+ }
+
+ // Windows that are not visible will be painted after they become visible.
+ if (!mGdkWindow || !mHasMappedToplevel) {
+ return FALSE;
+ }
+#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay() && !moz_container_wayland_can_draw(mContainer)) {
+ return FALSE;
+ }
+#endif
+
+ nsIWidgetListener* listener = GetListener();
+ if (!listener) return FALSE;
+
+ LOG("nsWindow::OnExposeEvent GdkWindow [%p] XWindow [0x%lx]", mGdkWindow,
+ GetX11Window());
+
+ LayoutDeviceIntRegion exposeRegion;
+ if (!ExtractExposeRegion(exposeRegion, cr)) {
+ LOG(" no rects, quit");
+ return FALSE;
+ }
+
+ gint scale = GdkCeiledScaleFactor();
+ LayoutDeviceIntRegion region = exposeRegion;
+ region.ScaleRoundOut(scale, scale);
+
+ WindowRenderer* renderer = GetWindowRenderer();
+ WebRenderLayerManager* layerManager = renderer->AsWebRender();
+ KnowsCompositor* knowsCompositor = renderer->AsKnowsCompositor();
+
+ if (knowsCompositor && layerManager && mCompositorSession) {
+ if (!mConfiguredClearColor && !IsPopup()) {
+ layerManager->WrBridge()->SendSetDefaultClearColor(LookAndFeel::Color(
+ LookAndFeel::ColorID::Window, LookAndFeel::ColorSchemeForChrome(),
+ LookAndFeel::UseStandins::No));
+ mConfiguredClearColor = true;
+ }
+
+ // We need to paint to the screen even if nothing changed, since if we
+ // don't have a compositing window manager, our pixels could be stale.
+ layerManager->SetNeedsComposite(true);
+ layerManager->SendInvalidRegion(region.ToUnknownRegion());
+ }
+
+ RefPtr<nsWindow> strongThis(this);
+
+ // Dispatch WillPaintWindow notification to allow scripts etc. to run
+ // before we paint
+ {
+ listener->WillPaintWindow(this);
+
+ // If the window has been destroyed during the will paint notification,
+ // there is nothing left to do.
+ if (!mGdkWindow) return TRUE;
+
+ // Re-get the listener since the will paint notification might have
+ // killed it.
+ listener = GetListener();
+ if (!listener) return FALSE;
+ }
+
+ if (knowsCompositor && layerManager && layerManager->NeedsComposite()) {
+ layerManager->ScheduleComposite(wr::RenderReasons::WIDGET);
+ layerManager->SetNeedsComposite(false);
+ }
+
+ // Our bounds may have changed after calling WillPaintWindow. Clip
+ // to the new bounds here. The region is relative to this
+ // window.
+ region.And(region, LayoutDeviceIntRect(0, 0, mBounds.width, mBounds.height));
+
+ bool shaped = false;
+ if (TransparencyMode::Transparent == GetTransparencyMode()) {
+ auto* window = static_cast<nsWindow*>(GetTopLevelWidget());
+ if (mTransparencyBitmapForTitlebar) {
+ if (mSizeMode == nsSizeMode_Normal) {
+ window->UpdateTitlebarTransparencyBitmap();
+ } else {
+ window->ClearTransparencyBitmap();
+ }
+ } else {
+ if (mHasAlphaVisual) {
+ // Remove possible shape mask from when window manger was not
+ // previously compositing.
+ window->ClearTransparencyBitmap();
+ } else {
+ shaped = true;
+ }
+ }
+ }
+
+ if (region.IsEmpty()) {
+ return TRUE;
+ }
+
+ // If this widget uses OMTC...
+ if (renderer->GetBackendType() == LayersBackend::LAYERS_WR) {
+ listener->PaintWindow(this, region);
+
+ // Re-get the listener since the will paint notification might have
+ // killed it.
+ listener = GetListener();
+ if (!listener) return TRUE;
+
+ listener->DidPaintWindow();
+ return TRUE;
+ }
+
+ BufferMode layerBuffering = BufferMode::BUFFERED;
+ RefPtr<DrawTarget> dt = StartRemoteDrawingInRegion(region, &layerBuffering);
+ if (!dt || !dt->IsValid()) {
+ return FALSE;
+ }
+ Maybe<gfxContext> ctx;
+ IntRect boundsRect = region.GetBounds().ToUnknownRect();
+ IntPoint offset(0, 0);
+ if (dt->GetSize() == boundsRect.Size()) {
+ offset = boundsRect.TopLeft();
+ dt->SetTransform(Matrix::Translation(-offset));
+ }
+
+#ifdef MOZ_X11
+ if (shaped) {
+ // Collapse update area to the bounding box. This is so we only have to
+ // call UpdateTranslucentWindowAlpha once. After we have dropped
+ // support for non-Thebes graphics, UpdateTranslucentWindowAlpha will be
+ // our private interface so we can rework things to avoid this.
+ dt->PushClipRect(Rect(boundsRect));
+
+ // The double buffering is done here to extract the shape mask.
+ // (The shape mask won't be necessary when a visual with an alpha
+ // channel is used on compositing window managers.)
+ layerBuffering = BufferMode::BUFFER_NONE;
+ RefPtr<DrawTarget> destDT =
+ dt->CreateSimilarDrawTarget(boundsRect.Size(), SurfaceFormat::B8G8R8A8);
+ if (!destDT || !destDT->IsValid()) {
+ return FALSE;
+ }
+ destDT->SetTransform(Matrix::Translation(-boundsRect.TopLeft()));
+ ctx.emplace(destDT, /* aPreserveTransform */ true);
+ } else {
+ gfxUtils::ClipToRegion(dt, region.ToUnknownRegion());
+ ctx.emplace(dt, /* aPreserveTransform */ true);
+ }
+
+# if 0
+ // NOTE: Paint flashing region would be wrong for cairo, since
+ // cairo inflates the update region, etc. So don't paint flash
+ // for cairo.
+# ifdef DEBUG
+ // XXX aEvent->region may refer to a newly-invalid area. FIXME
+ if (0 && WANT_PAINT_FLASHING && gtk_widget_get_window(aEvent))
+ gdk_window_flash(mGdkWindow, 1, 100, aEvent->region);
+# endif
+# endif
+
+#endif // MOZ_X11
+
+ bool painted = false;
+ {
+ if (renderer->GetBackendType() == LayersBackend::LAYERS_NONE) {
+ if (GetTransparencyMode() == TransparencyMode::Transparent &&
+ layerBuffering == BufferMode::BUFFER_NONE && mHasAlphaVisual) {
+ // If our draw target is unbuffered and we use an alpha channel,
+ // clear the image beforehand to ensure we don't get artifacts from a
+ // reused SHM image. See bug 1258086.
+ dt->ClearRect(Rect(boundsRect));
+ }
+ AutoLayerManagerSetup setupLayerManager(
+ this, ctx.isNothing() ? nullptr : &ctx.ref(), layerBuffering);
+ painted = listener->PaintWindow(this, region);
+
+ // Re-get the listener since the will paint notification might have
+ // killed it.
+ listener = GetListener();
+ if (!listener) return TRUE;
+ }
+ }
+
+#ifdef MOZ_X11
+ // PaintWindow can Destroy us (bug 378273), avoid doing any paint
+ // operations below if that happened - it will lead to XError and exit().
+ if (shaped) {
+ if (MOZ_LIKELY(!mIsDestroyed)) {
+ if (painted) {
+ RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
+
+ UpdateAlpha(surf, boundsRect);
+
+ dt->DrawSurface(surf, Rect(boundsRect),
+ Rect(0, 0, boundsRect.width, boundsRect.height),
+ DrawSurfaceOptions(SamplingFilter::POINT),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+ }
+ }
+ }
+
+ ctx.reset();
+ dt->PopClip();
+
+#endif // MOZ_X11
+
+ EndRemoteDrawingInRegion(dt, region);
+
+ listener->DidPaintWindow();
+
+ // Synchronously flush any new dirty areas
+ cairo_region_t* dirtyArea = gdk_window_get_update_area(mGdkWindow);
+
+ if (dirtyArea) {
+ gdk_window_invalidate_region(mGdkWindow, dirtyArea, false);
+ cairo_region_destroy(dirtyArea);
+ gdk_window_process_updates(mGdkWindow, false);
+ }
+
+ // check the return value!
+ return TRUE;
+}
+
+void nsWindow::UpdateAlpha(SourceSurface* aSourceSurface,
+ nsIntRect aBoundsRect) {
+ // We need to create our own buffer to force the stride to match the
+ // expected stride.
+ int32_t stride =
+ GetAlignedStride<4>(aBoundsRect.width, BytesPerPixel(SurfaceFormat::A8));
+ if (stride == 0) {
+ return;
+ }
+ int32_t bufferSize = stride * aBoundsRect.height;
+ auto imageBuffer = MakeUniqueFallible<uint8_t[]>(bufferSize);
+ {
+ RefPtr<DrawTarget> drawTarget = gfxPlatform::CreateDrawTargetForData(
+ imageBuffer.get(), aBoundsRect.Size(), stride, SurfaceFormat::A8);
+
+ if (drawTarget) {
+ drawTarget->DrawSurface(aSourceSurface,
+ Rect(0, 0, aBoundsRect.width, aBoundsRect.height),
+ Rect(0, 0, aSourceSurface->GetSize().width,
+ aSourceSurface->GetSize().height),
+ DrawSurfaceOptions(SamplingFilter::POINT),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+ }
+ }
+ UpdateTranslucentWindowAlphaInternal(aBoundsRect, imageBuffer.get(), stride);
+}
+
+gboolean nsWindow::OnConfigureEvent(GtkWidget* aWidget,
+ GdkEventConfigure* aEvent) {
+ // These events are only received on toplevel windows.
+ //
+ // GDK ensures that the coordinates are the client window top-left wrt the
+ // root window.
+ //
+ // GDK calculates the cordinates for real ConfigureNotify events on
+ // managed windows (that would normally be relative to the parent
+ // window).
+ //
+ // Synthetic ConfigureNotify events are from the window manager and
+ // already relative to the root window. GDK creates all X windows with
+ // border_width = 0, so synthetic events also indicate the top-left of
+ // the client window.
+ //
+ // Override-redirect windows are children of the root window so parent
+ // coordinates are root coordinates.
+
+ LOG("configure event %d,%d -> %d x %d scale %d\n", aEvent->x, aEvent->y,
+ aEvent->width, aEvent->height,
+ mGdkWindow ? gdk_window_get_scale_factor(mGdkWindow) : -1);
+
+ if (mPendingConfigures > 0) {
+ mPendingConfigures--;
+ }
+
+ // Don't fire configure event for scale changes, we handle that
+ // OnScaleChanged event. Skip that for toplevel windows only.
+ if (mGdkWindow && (mWindowType == WindowType::TopLevel ||
+ mWindowType == WindowType::Dialog)) {
+ if (mWindowScaleFactor != gdk_window_get_scale_factor(mGdkWindow)) {
+ LOG(" scale factor changed to %d,return early",
+ gdk_window_get_scale_factor(mGdkWindow));
+ return FALSE;
+ }
+ }
+
+ LayoutDeviceIntRect screenBounds = GetScreenBounds();
+
+ if (mWindowType == WindowType::TopLevel ||
+ mWindowType == WindowType::Dialog) {
+ // This check avoids unwanted rollup on spurious configure events from
+ // Cygwin/X (bug 672103).
+ if (mBounds.x != screenBounds.x || mBounds.y != screenBounds.y) {
+ RollupAllMenus();
+ }
+ }
+
+ NS_ASSERTION(GTK_IS_WINDOW(aWidget),
+ "Configure event on widget that is not a GtkWindow");
+ if (mGdkWindow &&
+ gtk_window_get_window_type(GTK_WINDOW(aWidget)) == GTK_WINDOW_POPUP) {
+ // Override-redirect window
+ //
+ // These windows should not be moved by the window manager, and so any
+ // change in position is a result of our direction. mBounds has
+ // already been set in std::move() or Resize(), and that is more
+ // up-to-date than the position in the ConfigureNotify event if the
+ // event is from an earlier window move.
+ //
+ // Skipping the WindowMoved call saves context menus from an infinite
+ // loop when nsXULPopupManager::PopupMoved moves the window to the new
+ // position and nsMenuPopupFrame::SetPopupPosition adds
+ // offsetForContextMenu on each iteration.
+
+ // Our back buffer might have been invalidated while we drew the last
+ // frame, and its contents might be incorrect. See bug 1280653 comment 7
+ // and comment 10. Specifically we must ensure we recomposite the frame
+ // as soon as possible to avoid the corrupted frame being displayed.
+ GetWindowRenderer()->FlushRendering(wr::RenderReasons::WIDGET);
+ return FALSE;
+ }
+
+ mBounds.MoveTo(screenBounds.TopLeft());
+ RecomputeClientOffset(/* aNotify = */ false);
+
+ // XXX mozilla will invalidate the entire window after this move
+ // complete. wtf?
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+
+ return FALSE;
+}
+
+void nsWindow::OnMap() {
+ LOG("nsWindow::OnMap");
+ // Gtk mapped widget to screen. Configure underlying GdkWindow properly
+ // as our rendering target.
+ // This call means we have X11 (or Wayland) window we can render to by GL
+ // so we need to notify compositor about it.
+ mIsMapped = true;
+ ConfigureGdkWindow();
+}
+
+void nsWindow::OnUnmap() {
+ LOG("nsWindow::OnUnmap");
+
+ // Mark window as unmapped. It can be still used for rendering on X11
+ // untill OnUnrealize is called.
+ mIsMapped = false;
+
+ if (mSourceDragContext) {
+ static auto sGtkDragCancel =
+ (void (*)(GdkDragContext*))dlsym(RTLD_DEFAULT, "gtk_drag_cancel");
+ if (sGtkDragCancel) {
+ sGtkDragCancel(mSourceDragContext);
+ mSourceDragContext = nullptr;
+ }
+ }
+
+#ifdef MOZ_WAYLAND
+ // wl_surface owned by mContainer is going to be deleted.
+ // Make sure we don't paint to it on Wayland.
+ if (GdkIsWaylandDisplay()) {
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->DisableRendering();
+ }
+ if (moz_container_wayland_has_egl_window(mContainer)) {
+ // Widget is backed by OpenGL EGLSurface created over wl_surface
+ // owned by mContainer.
+ // RenderCompositorEGL::Resume() deletes recent EGLSurface based on
+ // wl_surface owned by mContainer and creates a new fallback EGLSurface.
+ // Then we can delete wl_surface in moz_container_wayland_unmap().
+ // We don't want to pause compositor as it may lead to whole
+ // browser freeze (Bug 1777664).
+ if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) {
+ remoteRenderer->SendResume();
+ }
+ }
+ if (GdkIsWaylandDisplay()) {
+ moz_container_wayland_unmap(GTK_WIDGET(mContainer));
+ }
+ }
+#endif
+ moz_container_unmap(GTK_WIDGET(mContainer));
+}
+
+void nsWindow::OnUnrealize() {
+ // The GdkWindows are about to be destroyed (but not deleted), so remove
+ // their references back to their container widget while the GdkWindow
+ // hierarchy is still available.
+ // This call means we *don't* have X11 (or Wayland) window we can render to.
+ LOG("nsWindow::OnUnrealize GdkWindow %p", mGdkWindow);
+ ReleaseGdkWindow();
+}
+
+void nsWindow::OnSizeAllocate(GtkAllocation* aAllocation) {
+ LOG("nsWindow::OnSizeAllocate %d,%d -> %d x %d\n", aAllocation->x,
+ aAllocation->y, aAllocation->width, aAllocation->height);
+
+ // Client offset are updated by _NET_FRAME_EXTENTS on X11 when system titlebar
+ // is enabled. In either cases (Wayland or system titlebar is off on X11)
+ // we don't get _NET_FRAME_EXTENTS X11 property notification so we derive
+ // it from mContainer position.
+ RecomputeClientOffset(/* aNotify = */ true);
+
+ mHasReceivedSizeAllocate = true;
+
+ LayoutDeviceIntSize size = GdkRectToDevicePixels(*aAllocation).Size();
+
+ // Sometimes the window manager gives us garbage sizes (way past the maximum
+ // texture size) causing crashes if we don't enforce size constraints again
+ // here.
+ ConstrainSize(&size.width, &size.height);
+
+ if (mBounds.Size() == size) {
+ LOG(" Already the same size");
+ // mBounds was set at Create() or Resize().
+ if (mNeedsDispatchSize != LayoutDeviceIntSize(-1, -1)) {
+ LOG(" No longer needs to dispatch %dx%d", mNeedsDispatchSize.width,
+ mNeedsDispatchSize.height);
+ mNeedsDispatchSize = LayoutDeviceIntSize(-1, -1);
+ }
+ return;
+ }
+
+ // Invalidate the new part of the window now for the pending paint to
+ // minimize background flashes (GDK does not do this for external resizes
+ // of toplevels.)
+ if (mGdkWindow) {
+ if (mBounds.width < size.width) {
+ GdkRectangle rect = DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect(
+ mBounds.width, 0, size.width - mBounds.width, size.height));
+ gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
+ }
+ if (mBounds.height < size.height) {
+ GdkRectangle rect = DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect(
+ 0, mBounds.height, size.width, size.height - mBounds.height));
+ gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
+ }
+ }
+
+ // If we update mBounds here, then inner/outerHeight are out of sync until
+ // we call WindowResized.
+ mNeedsDispatchSize = size;
+
+ // Gecko permits running nested event loops during processing of events,
+ // GtkWindow callers of gtk_widget_size_allocate expect the signal
+ // handlers to return sometime in the near future.
+ NS_DispatchToCurrentThread(NewRunnableMethod(
+ "nsWindow::MaybeDispatchResized", this, &nsWindow::MaybeDispatchResized));
+}
+
+void nsWindow::OnDeleteEvent() {
+ if (mWidgetListener) mWidgetListener->RequestWindowClose(this);
+}
+
+void nsWindow::OnEnterNotifyEvent(GdkEventCrossing* aEvent) {
+ LOG("enter notify (win=%p, sub=%p): %f, %f mode %d, detail %d\n",
+ aEvent->window, aEvent->subwindow, aEvent->x, aEvent->y, aEvent->mode,
+ aEvent->detail);
+ // This skips NotifyVirtual and NotifyNonlinearVirtual enter notify events
+ // when the pointer enters a child window. If the destination window is a
+ // Gecko window then we'll catch the corresponding event on that window,
+ // but we won't notice when the pointer directly enters a foreign (plugin)
+ // child window without passing over a visible portion of a Gecko window.
+ if (aEvent->subwindow) {
+ return;
+ }
+
+ // Check before checking for ungrab as the button state may have
+ // changed while a non-Gecko ancestor window had a pointer grab.
+ DispatchMissedButtonReleases(aEvent);
+
+ WidgetMouseEvent event(true, eMouseEnterIntoWidget, this,
+ WidgetMouseEvent::eReal);
+
+ event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+
+ LOG("OnEnterNotify");
+
+ DispatchInputEvent(&event);
+}
+
+// Some window managers send a bogus top-level leave-notify event on every
+// click. That confuses our event handling code in ways that can break websites,
+// see bug 1805939 for details.
+//
+// Make sure to only check this on bogus environments, since for environments
+// with CSD, gdk_device_get_window_at_position could return the window even when
+// the pointer is in the decoration area.
+static bool IsBogusLeaveNotifyEvent(GdkWindow* aWindow,
+ GdkEventCrossing* aEvent) {
+ static bool sBogusWm = [] {
+ if (GdkIsWaylandDisplay()) {
+ return false;
+ }
+ const auto& desktopEnv = GetDesktopEnvironmentIdentifier();
+ return desktopEnv.EqualsLiteral("fluxbox") || // Bug 1805939 comment 0.
+ desktopEnv.EqualsLiteral("blackbox") || // Bug 1805939 comment 32.
+ desktopEnv.EqualsLiteral("lg3d") || // Bug 1820405.
+ desktopEnv.EqualsLiteral("pekwm") || // Bug 1822911.
+ StringBeginsWith(desktopEnv, "fvwm"_ns);
+ }();
+
+ const bool shouldCheck = [] {
+ switch (StaticPrefs::widget_gtk_ignore_bogus_leave_notify()) {
+ case 0:
+ return false;
+ case 1:
+ return true;
+ default:
+ return sBogusWm;
+ }
+ }();
+
+ if (!shouldCheck) {
+ return false;
+ }
+ GdkDevice* pointer = GdkGetPointer();
+ GdkWindow* winAtPt =
+ gdk_device_get_window_at_position(pointer, nullptr, nullptr);
+ if (!winAtPt) {
+ return false;
+ }
+ // We're still in the same top level window, ignore this leave notify event.
+ GdkWindow* topLevelAtPt = gdk_window_get_toplevel(winAtPt);
+ GdkWindow* topLevelWidget = gdk_window_get_toplevel(aWindow);
+ return topLevelAtPt == topLevelWidget;
+}
+
+void nsWindow::OnLeaveNotifyEvent(GdkEventCrossing* aEvent) {
+ LOG("leave notify (win=%p, sub=%p): %f, %f mode %d, detail %d\n",
+ aEvent->window, aEvent->subwindow, aEvent->x, aEvent->y, aEvent->mode,
+ aEvent->detail);
+
+ // This ignores NotifyVirtual and NotifyNonlinearVirtual leave notify
+ // events when the pointer leaves a child window. If the destination
+ // window is a Gecko window then we'll catch the corresponding event on
+ // that window.
+ //
+ // XXXkt However, we will miss toplevel exits when the pointer directly
+ // leaves a foreign (plugin) child window without passing over a visible
+ // portion of a Gecko window.
+ if (aEvent->subwindow) {
+ return;
+ }
+
+ // The filter out for subwindows should make sure that this is targeted to
+ // this nsWindow.
+ const bool leavingTopLevel =
+ mWindowType == WindowType::TopLevel || mWindowType == WindowType::Dialog;
+
+ if (leavingTopLevel && IsBogusLeaveNotifyEvent(mGdkWindow, aEvent)) {
+ return;
+ }
+
+ WidgetMouseEvent event(true, eMouseExitFromWidget, this,
+ WidgetMouseEvent::eReal);
+
+ event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+ event.mExitFrom = Some(leavingTopLevel ? WidgetMouseEvent::ePlatformTopLevel
+ : WidgetMouseEvent::ePlatformChild);
+
+ LOG("OnLeaveNotify");
+
+ DispatchInputEvent(&event);
+}
+
+Maybe<GdkWindowEdge> nsWindow::CheckResizerEdge(
+ const LayoutDeviceIntPoint& aPoint) {
+ const bool canResize = [&] {
+ // Don't allow resizing maximized/fullscreen windows.
+ if (mSizeMode != nsSizeMode_Normal) {
+ return false;
+ }
+ if (mIsPIPWindow) {
+ // Note that since we do show resizers on left/right sides on PIP windows,
+ // we still want the resizers there, even when tiled.
+ return true;
+ }
+ if (!mDrawInTitlebar) {
+ return false;
+ }
+ // On KDE, allow for 1 extra pixel at the top of regular windows when
+ // drawing to the titlebar. This matches the native titlebar behavior on
+ // that environment. See bug 1813554.
+ //
+ // Don't do that on GNOME (see bug 1822764). If we wanted to do this on
+ // GNOME we'd need an extra check for mIsTiled, since the window is "stuck"
+ // to the top and bottom.
+ //
+ // Other DEs are untested.
+ return mDrawInTitlebar && IsKdeDesktopEnvironment();
+ }();
+
+ if (!canResize) {
+ return {};
+ }
+
+ // If we're not in a PiP window, allow 1px resizer edge from the top edge,
+ // and nothing else.
+ // This is to allow resizes of tiled windows on KDE, see bug 1813554.
+ const int resizerSize = (mIsPIPWindow ? 15 : 1) * GdkCeiledScaleFactor();
+
+ const int topDist = aPoint.y;
+ const int leftDist = aPoint.x;
+ const int rightDist = mBounds.width - aPoint.x;
+ const int bottomDist = mBounds.height - aPoint.y;
+
+ if (topDist <= resizerSize) {
+ if (rightDist <= resizerSize) {
+ return Some(GDK_WINDOW_EDGE_NORTH_EAST);
+ }
+ if (leftDist <= resizerSize) {
+ return Some(GDK_WINDOW_EDGE_NORTH_WEST);
+ }
+ return Some(GDK_WINDOW_EDGE_NORTH);
+ }
+
+ if (!mIsPIPWindow) {
+ return {};
+ }
+
+ if (bottomDist <= resizerSize) {
+ if (leftDist <= resizerSize) {
+ return Some(GDK_WINDOW_EDGE_SOUTH_WEST);
+ }
+ if (rightDist <= resizerSize) {
+ return Some(GDK_WINDOW_EDGE_SOUTH_EAST);
+ }
+ return Some(GDK_WINDOW_EDGE_SOUTH);
+ }
+
+ if (leftDist <= resizerSize) {
+ return Some(GDK_WINDOW_EDGE_WEST);
+ }
+ if (rightDist <= resizerSize) {
+ return Some(GDK_WINDOW_EDGE_EAST);
+ }
+ return {};
+}
+
+template <typename Event>
+static LayoutDeviceIntPoint GetRefPoint(nsWindow* aWindow, Event* aEvent) {
+ if (aEvent->window == aWindow->GetGdkWindow()) {
+ // we are the window that the event happened on so no need for expensive
+ // WidgetToScreenOffset
+ return aWindow->GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+ }
+ // XXX we're never quite sure which GdkWindow the event came from due to our
+ // custom bubbling in scroll_event_cb(), so use ScreenToWidget to translate
+ // the screen root coordinates into coordinates relative to this widget.
+ return aWindow->GdkEventCoordsToDevicePixels(aEvent->x_root, aEvent->y_root) -
+ aWindow->WidgetToScreenOffset();
+}
+
+void nsWindow::OnMotionNotifyEvent(GdkEventMotion* aEvent) {
+ if (!mGdkWindow) {
+ return;
+ }
+
+ if (mWindowShouldStartDragging) {
+ mWindowShouldStartDragging = false;
+ // find the top-level window
+ GdkWindow* gdk_window = gdk_window_get_toplevel(mGdkWindow);
+ MOZ_ASSERT(gdk_window, "gdk_window_get_toplevel should not return null");
+
+ bool canDrag = true;
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=789054
+ // To avoid crashes disable double-click on WM without _NET_WM_MOVERESIZE.
+ // See _should_perform_ewmh_drag() at gdkwindow-x11.c
+ GdkScreen* screen = gdk_window_get_screen(gdk_window);
+ GdkAtom atom = gdk_atom_intern("_NET_WM_MOVERESIZE", FALSE);
+ if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
+ canDrag = false;
+ }
+ }
+#endif
+
+ if (canDrag) {
+ gdk_window_begin_move_drag(gdk_window, 1, aEvent->x_root, aEvent->y_root,
+ aEvent->time);
+ return;
+ }
+ }
+
+ mWidgetCursorLocked = false;
+ const auto refPoint = GetRefPoint(this, aEvent);
+ if (auto edge = CheckResizerEdge(refPoint)) {
+ nsCursor cursor = eCursor_none;
+ switch (*edge) {
+ case GDK_WINDOW_EDGE_SOUTH:
+ case GDK_WINDOW_EDGE_NORTH:
+ cursor = eCursor_ns_resize;
+ break;
+ case GDK_WINDOW_EDGE_WEST:
+ case GDK_WINDOW_EDGE_EAST:
+ cursor = eCursor_ew_resize;
+ break;
+ case GDK_WINDOW_EDGE_NORTH_WEST:
+ case GDK_WINDOW_EDGE_SOUTH_EAST:
+ cursor = eCursor_nwse_resize;
+ break;
+ case GDK_WINDOW_EDGE_NORTH_EAST:
+ case GDK_WINDOW_EDGE_SOUTH_WEST:
+ cursor = eCursor_nesw_resize;
+ break;
+ }
+ SetCursor(Cursor{cursor});
+ // If we set resize cursor on widget level keep it locked and prevent layout
+ // to switch it back to default (by synthetic mouse events for instance)
+ // until resize is finished.
+ mWidgetCursorLocked = true;
+ return;
+ }
+
+ WidgetMouseEvent event(true, eMouseMove, this, WidgetMouseEvent::eReal);
+
+ gdouble pressure = 0;
+ gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
+ // Sometime gdk generate 0 pressure value between normal values
+ // We have to ignore that and use last valid value
+ if (pressure) {
+ mLastMotionPressure = pressure;
+ }
+ event.mPressure = mLastMotionPressure;
+ event.mRefPoint = refPoint;
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+
+ KeymapWrapper::InitInputEvent(event, aEvent->state);
+
+ DispatchInputEvent(&event);
+}
+
+// If the automatic pointer grab on ButtonPress has deactivated before
+// ButtonRelease, and the mouse button is released while the pointer is not
+// over any a Gecko window, then the ButtonRelease event will not be received.
+// (A similar situation exists when the pointer is grabbed with owner_events
+// True as the ButtonRelease may be received on a foreign [plugin] window).
+// Use this method to check for released buttons when the pointer returns to a
+// Gecko window.
+void nsWindow::DispatchMissedButtonReleases(GdkEventCrossing* aGdkEvent) {
+ guint changed = aGdkEvent->state ^ gButtonState;
+ // Only consider button releases.
+ // (Ignore button presses that occurred outside Gecko.)
+ guint released = changed & gButtonState;
+ gButtonState = aGdkEvent->state;
+
+ // Loop over each button, excluding mouse wheel buttons 4 and 5 for which
+ // GDK ignores releases.
+ for (guint buttonMask = GDK_BUTTON1_MASK; buttonMask <= GDK_BUTTON3_MASK;
+ buttonMask <<= 1) {
+ if (released & buttonMask) {
+ int16_t buttonType;
+ switch (buttonMask) {
+ case GDK_BUTTON1_MASK:
+ buttonType = MouseButton::ePrimary;
+ break;
+ case GDK_BUTTON2_MASK:
+ buttonType = MouseButton::eMiddle;
+ break;
+ default:
+ NS_ASSERTION(buttonMask == GDK_BUTTON3_MASK,
+ "Unexpected button mask");
+ buttonType = MouseButton::eSecondary;
+ }
+
+ LOG("Synthesized button %u release", guint(buttonType + 1));
+
+ // Dispatch a synthesized button up event to tell Gecko about the
+ // change in state. This event is marked as synthesized so that
+ // it is not dispatched as a DOM event, because we don't know the
+ // position, widget, modifiers, or time/order.
+ WidgetMouseEvent synthEvent(true, eMouseUp, this,
+ WidgetMouseEvent::eSynthesized);
+ synthEvent.mButton = buttonType;
+ DispatchInputEvent(&synthEvent);
+ }
+ }
+}
+
+void nsWindow::InitButtonEvent(WidgetMouseEvent& aEvent,
+ GdkEventButton* aGdkEvent,
+ const LayoutDeviceIntPoint& aRefPoint) {
+ aEvent.mRefPoint = aRefPoint;
+
+ guint modifierState = aGdkEvent->state;
+ // aEvent's state includes the button state from immediately before this
+ // event. If aEvent is a mousedown or mouseup event, we need to update
+ // the button state.
+ guint buttonMask = 0;
+ switch (aGdkEvent->button) {
+ case 1:
+ buttonMask = GDK_BUTTON1_MASK;
+ break;
+ case 2:
+ buttonMask = GDK_BUTTON2_MASK;
+ break;
+ case 3:
+ buttonMask = GDK_BUTTON3_MASK;
+ break;
+ }
+ if (aGdkEvent->type == GDK_BUTTON_RELEASE) {
+ modifierState &= ~buttonMask;
+ } else {
+ modifierState |= buttonMask;
+ }
+
+ KeymapWrapper::InitInputEvent(aEvent, modifierState);
+
+ aEvent.AssignEventTime(GetWidgetEventTime(aGdkEvent->time));
+
+ switch (aGdkEvent->type) {
+ case GDK_2BUTTON_PRESS:
+ aEvent.mClickCount = 2;
+ break;
+ case GDK_3BUTTON_PRESS:
+ aEvent.mClickCount = 3;
+ break;
+ // default is one click
+ default:
+ aEvent.mClickCount = 1;
+ }
+}
+
+static guint ButtonMaskFromGDKButton(guint button) {
+ return GDK_BUTTON1_MASK << (button - 1);
+}
+
+void nsWindow::DispatchContextMenuEventFromMouseEvent(
+ uint16_t domButton, GdkEventButton* aEvent,
+ const LayoutDeviceIntPoint& aRefPoint) {
+ if (domButton == MouseButton::eSecondary && MOZ_LIKELY(!mIsDestroyed)) {
+ WidgetMouseEvent contextMenuEvent(true, eContextMenu, this,
+ WidgetMouseEvent::eReal);
+ InitButtonEvent(contextMenuEvent, aEvent, aRefPoint);
+ contextMenuEvent.mPressure = mLastMotionPressure;
+ DispatchInputEvent(&contextMenuEvent);
+ }
+}
+
+void nsWindow::TryToShowNativeWindowMenu(GdkEventButton* aEvent) {
+ if (!gdk_window_show_window_menu(GetToplevelGdkWindow(), (GdkEvent*)aEvent)) {
+ NS_WARNING("Native context menu wasn't shown");
+ }
+}
+
+void nsWindow::OnButtonPressEvent(GdkEventButton* aEvent) {
+ LOG("Button %u press\n", aEvent->button);
+
+ SetLastMousePressEvent((GdkEvent*)aEvent);
+
+ // If you double click in GDK, it will actually generate a second
+ // GDK_BUTTON_PRESS before sending the GDK_2BUTTON_PRESS, and this is
+ // different than the DOM spec. GDK puts this in the queue
+ // programatically, so it's safe to assume that if there's a
+ // double click in the queue, it was generated so we can just drop
+ // this click.
+ GUniquePtr<GdkEvent> peekedEvent(gdk_event_peek());
+ if (peekedEvent) {
+ GdkEventType type = peekedEvent->any.type;
+ if (type == GDK_2BUTTON_PRESS || type == GDK_3BUTTON_PRESS) {
+ return;
+ }
+ }
+
+ nsWindow* containerWindow = GetContainerWindow();
+ if (!gFocusWindow && containerWindow) {
+ containerWindow->DispatchActivateEvent();
+ }
+
+ const auto refPoint = GetRefPoint(this, aEvent);
+
+ // check to see if we should rollup
+ if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false)) {
+ if (aEvent->button == 3 && mDraggableRegion.Contains(refPoint)) {
+ GUniquePtr<GdkEvent> eventCopy;
+ if (aEvent->type != GDK_BUTTON_PRESS) {
+ // If the user double-clicks too fast we'll get a 2BUTTON_PRESS event
+ // instead, and that isn't handled by open_window_menu, so coerce it
+ // into a regular press.
+ eventCopy.reset(gdk_event_copy((GdkEvent*)aEvent));
+ eventCopy->type = GDK_BUTTON_PRESS;
+ }
+ TryToShowNativeWindowMenu(eventCopy ? &eventCopy->button : aEvent);
+ }
+ return;
+ }
+
+ // Check to see if the event is within our window's resize region
+ if (auto edge = CheckResizerEdge(refPoint)) {
+ gdk_window_begin_resize_drag(gtk_widget_get_window(mShell), *edge,
+ aEvent->button, aEvent->x_root, aEvent->y_root,
+ aEvent->time);
+ return;
+ }
+
+ gdouble pressure = 0;
+ gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
+ mLastMotionPressure = pressure;
+
+ uint16_t domButton;
+ switch (aEvent->button) {
+ case 1:
+ domButton = MouseButton::ePrimary;
+ break;
+ case 2:
+ domButton = MouseButton::eMiddle;
+ break;
+ case 3:
+ domButton = MouseButton::eSecondary;
+ break;
+ // These are mapped to horizontal scroll
+ case 6:
+ case 7:
+ NS_WARNING("We're not supporting legacy horizontal scroll event");
+ return;
+ // Map buttons 8-9 to back/forward
+ case 8:
+ if (!Preferences::GetBool("mousebutton.4th.enabled", true)) {
+ return;
+ }
+ DispatchCommandEvent(nsGkAtoms::Back);
+ return;
+ case 9:
+ if (!Preferences::GetBool("mousebutton.5th.enabled", true)) {
+ return;
+ }
+ DispatchCommandEvent(nsGkAtoms::Forward);
+ return;
+ default:
+ return;
+ }
+
+ gButtonState |= ButtonMaskFromGDKButton(aEvent->button);
+
+ WidgetMouseEvent event(true, eMouseDown, this, WidgetMouseEvent::eReal);
+ event.mButton = domButton;
+ InitButtonEvent(event, aEvent, refPoint);
+ event.mPressure = mLastMotionPressure;
+
+ nsIWidget::ContentAndAPZEventStatus eventStatus = DispatchInputEvent(&event);
+
+ if ((mIsWaylandPanelWindow || mDraggableRegion.Contains(refPoint)) &&
+ domButton == MouseButton::ePrimary &&
+ eventStatus.mContentStatus != nsEventStatus_eConsumeNoDefault) {
+ mWindowShouldStartDragging = true;
+ }
+
+ // right menu click on linux should also pop up a context menu
+ if (!StaticPrefs::ui_context_menus_after_mouseup() &&
+ eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
+ DispatchContextMenuEventFromMouseEvent(domButton, aEvent, refPoint);
+ }
+}
+
+void nsWindow::OnButtonReleaseEvent(GdkEventButton* aEvent) {
+ LOG("Button %u release\n", aEvent->button);
+
+ SetLastMousePressEvent(nullptr);
+
+ if (!mGdkWindow) {
+ return;
+ }
+
+ if (mWindowShouldStartDragging) {
+ mWindowShouldStartDragging = false;
+ }
+
+ uint16_t domButton;
+ switch (aEvent->button) {
+ case 1:
+ domButton = MouseButton::ePrimary;
+ break;
+ case 2:
+ domButton = MouseButton::eMiddle;
+ break;
+ case 3:
+ domButton = MouseButton::eSecondary;
+ break;
+ default:
+ return;
+ }
+
+ gButtonState &= ~ButtonMaskFromGDKButton(aEvent->button);
+
+ const auto refPoint = GetRefPoint(this, aEvent);
+
+ WidgetMouseEvent event(true, eMouseUp, this, WidgetMouseEvent::eReal);
+ event.mButton = domButton;
+ InitButtonEvent(event, aEvent, refPoint);
+ gdouble pressure = 0;
+ gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
+ event.mPressure = pressure ? (float)pressure : (float)mLastMotionPressure;
+
+ // The mRefPoint is manipulated in DispatchInputEvent, we're saving it
+ // to use it for the doubleclick position check.
+ const LayoutDeviceIntPoint pos = event.mRefPoint;
+
+ nsIWidget::ContentAndAPZEventStatus eventStatus = DispatchInputEvent(&event);
+
+ const bool defaultPrevented =
+ eventStatus.mContentStatus == nsEventStatus_eConsumeNoDefault;
+ // Check if mouse position in titlebar and doubleclick happened to
+ // trigger restore/maximize.
+ if (!defaultPrevented && mDrawInTitlebar &&
+ event.mButton == MouseButton::ePrimary && event.mClickCount == 2 &&
+ mDraggableRegion.Contains(pos)) {
+ if (mSizeMode == nsSizeMode_Maximized) {
+ SetSizeMode(nsSizeMode_Normal);
+ } else if (mSizeMode == nsSizeMode_Normal) {
+ SetSizeMode(nsSizeMode_Maximized);
+ }
+ }
+ mLastMotionPressure = pressure;
+
+ // right menu click on linux should also pop up a context menu
+ if (StaticPrefs::ui_context_menus_after_mouseup() &&
+ eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
+ DispatchContextMenuEventFromMouseEvent(domButton, aEvent, refPoint);
+ }
+
+ // Open window manager menu on PIP window to allow user
+ // to place it on top / all workspaces.
+ if (mAlwaysOnTop && aEvent->button == 3) {
+ TryToShowNativeWindowMenu(aEvent);
+ }
+}
+
+void nsWindow::OnContainerFocusInEvent(GdkEventFocus* aEvent) {
+ LOG("OnContainerFocusInEvent");
+
+ // Unset the urgency hint, if possible
+ GtkWidget* top_window = GetToplevelWidget();
+ if (top_window && (gtk_widget_get_visible(top_window))) {
+ SetUrgencyHint(top_window, false);
+ }
+
+ // Return if being called within SetFocus because the focus manager
+ // already knows that the window is active.
+ if (gBlockActivateEvent) {
+ LOG("activated notification is blocked");
+ return;
+ }
+
+ // If keyboard input will be accepted, the focus manager will call
+ // SetFocus to set the correct window.
+ gFocusWindow = nullptr;
+
+ DispatchActivateEvent();
+
+ if (!gFocusWindow) {
+ // We don't really have a window for dispatching key events, but
+ // setting a non-nullptr value here prevents OnButtonPressEvent() from
+ // dispatching an activation notification if the widget is already
+ // active.
+ gFocusWindow = this;
+ }
+
+ LOG("Events sent from focus in event");
+}
+
+void nsWindow::OnContainerFocusOutEvent(GdkEventFocus* aEvent) {
+ LOG("OnContainerFocusOutEvent");
+
+ if (mWindowType == WindowType::TopLevel ||
+ mWindowType == WindowType::Dialog) {
+ // Rollup menus when a window is focused out unless a drag is occurring.
+ // This check is because drags grab the keyboard and cause a focus out on
+ // versions of GTK before 2.18.
+ const bool shouldRollupMenus = [&] {
+ nsCOMPtr<nsIDragService> dragService =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ nsCOMPtr<nsIDragSession> dragSession;
+ dragService->GetCurrentSession(getter_AddRefs(dragSession));
+ if (!dragSession) {
+ return true;
+ }
+ // We also roll up when a drag is from a different application
+ nsCOMPtr<nsINode> sourceNode;
+ dragSession->GetSourceNode(getter_AddRefs(sourceNode));
+ return !sourceNode;
+ }();
+
+ if (shouldRollupMenus) {
+ RollupAllMenus();
+ }
+
+ if (RefPtr pm = nsXULPopupManager::GetInstance()) {
+ pm->RollupTooltips();
+ }
+ }
+
+ if (gFocusWindow) {
+ RefPtr<nsWindow> kungFuDeathGrip = gFocusWindow;
+ if (gFocusWindow->mIMContext) {
+ gFocusWindow->mIMContext->OnBlurWindow(gFocusWindow);
+ }
+ gFocusWindow = nullptr;
+ }
+
+ DispatchDeactivateEvent();
+
+ if (IsChromeWindowTitlebar()) {
+ // DispatchDeactivateEvent() ultimately results in a call to
+ // BrowsingContext::SetIsActiveBrowserWindow(), which resets
+ // the state. We call UpdateMozWindowActive() to keep it in
+ // sync with GDK_WINDOW_STATE_FOCUSED.
+ UpdateMozWindowActive();
+ }
+
+ LOG("Done with container focus out");
+}
+
+bool nsWindow::DispatchCommandEvent(nsAtom* aCommand) {
+ nsEventStatus status;
+ WidgetCommandEvent appCommandEvent(true, aCommand, this);
+ DispatchEvent(&appCommandEvent, status);
+ return TRUE;
+}
+
+bool nsWindow::DispatchContentCommandEvent(EventMessage aMsg) {
+ nsEventStatus status;
+ WidgetContentCommandEvent event(true, aMsg, this);
+ DispatchEvent(&event, status);
+ return TRUE;
+}
+
+WidgetEventTime nsWindow::GetWidgetEventTime(guint32 aEventTime) {
+ return WidgetEventTime(GetEventTimeStamp(aEventTime));
+}
+
+TimeStamp nsWindow::GetEventTimeStamp(guint32 aEventTime) {
+ if (MOZ_UNLIKELY(!mGdkWindow)) {
+ // nsWindow has been Destroy()ed.
+ return TimeStamp::Now();
+ }
+ if (aEventTime == 0) {
+ // Some X11 and GDK events may be received with a time of 0 to indicate
+ // that they are synthetic events. Some input method editors do this.
+ // In this case too, just return the current timestamp.
+ return TimeStamp::Now();
+ }
+
+ TimeStamp eventTimeStamp;
+
+ if (GdkIsWaylandDisplay()) {
+ // Wayland compositors use monotonic time to set timestamps.
+ int64_t timestampTime = g_get_monotonic_time() / 1000;
+ guint32 refTimeTruncated = guint32(timestampTime);
+
+ timestampTime -= refTimeTruncated - aEventTime;
+ int64_t tick =
+ BaseTimeDurationPlatformUtils::TicksFromMilliseconds(timestampTime);
+ eventTimeStamp = TimeStamp::FromSystemTime(tick);
+ } else {
+#ifdef MOZ_X11
+ CurrentX11TimeGetter* getCurrentTime = GetCurrentTimeGetter();
+ MOZ_ASSERT(getCurrentTime,
+ "Null current time getter despite having a window");
+ eventTimeStamp =
+ TimeConverter().GetTimeStampFromSystemTime(aEventTime, *getCurrentTime);
+#endif
+ }
+ return eventTimeStamp;
+}
+
+#ifdef MOZ_X11
+mozilla::CurrentX11TimeGetter* nsWindow::GetCurrentTimeGetter() {
+ MOZ_ASSERT(mGdkWindow, "Expected mGdkWindow to be set");
+ if (MOZ_UNLIKELY(!mCurrentTimeGetter)) {
+ mCurrentTimeGetter = MakeUnique<CurrentX11TimeGetter>(mGdkWindow);
+ }
+ return mCurrentTimeGetter.get();
+}
+#endif
+
+gboolean nsWindow::OnKeyPressEvent(GdkEventKey* aEvent) {
+ LOG("OnKeyPressEvent");
+
+ KeymapWrapper::HandleKeyPressEvent(this, aEvent);
+ return TRUE;
+}
+
+gboolean nsWindow::OnKeyReleaseEvent(GdkEventKey* aEvent) {
+ LOG("OnKeyReleaseEvent");
+ if (NS_WARN_IF(!KeymapWrapper::HandleKeyReleaseEvent(this, aEvent))) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void nsWindow::OnScrollEvent(GdkEventScroll* aEvent) {
+ // check to see if we should rollup
+ if (CheckForRollup(aEvent->x_root, aEvent->y_root, true, false)) {
+ return;
+ }
+
+ // check for duplicate legacy scroll event, see GNOME bug 726878
+ if (aEvent->direction != GDK_SCROLL_SMOOTH &&
+ mLastScrollEventTime == aEvent->time) {
+ LOG("[%d] duplicate legacy scroll event %d\n", aEvent->time,
+ aEvent->direction);
+ return;
+ }
+ WidgetWheelEvent wheelEvent(true, eWheel, this);
+ wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_LINE;
+ switch (aEvent->direction) {
+ case GDK_SCROLL_SMOOTH: {
+ // As of GTK 3.4, all directional scroll events are provided by
+ // the GDK_SCROLL_SMOOTH direction on XInput2 and Wayland devices.
+ mLastScrollEventTime = aEvent->time;
+
+ // Special handling for touchpads to support flings
+ // (also known as kinetic/inertial/momentum scrolling)
+ GdkDevice* device = gdk_event_get_source_device((GdkEvent*)aEvent);
+ GdkInputSource source = gdk_device_get_source(device);
+ if (source == GDK_SOURCE_TOUCHSCREEN || source == GDK_SOURCE_TOUCHPAD ||
+ mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase.isSome()) {
+ if (StaticPrefs::apz_gtk_pangesture_enabled() &&
+ gtk_check_version(3, 20, 0) == nullptr) {
+ static auto sGdkEventIsScrollStopEvent =
+ (gboolean(*)(const GdkEvent*))dlsym(
+ RTLD_DEFAULT, "gdk_event_is_scroll_stop_event");
+
+ LOG("[%d] pan smooth event dx=%f dy=%f inprogress=%d\n", aEvent->time,
+ aEvent->delta_x, aEvent->delta_y, mPanInProgress);
+ PanGestureInput::PanGestureType eventType =
+ PanGestureInput::PANGESTURE_PAN;
+ if (sGdkEventIsScrollStopEvent((GdkEvent*)aEvent)) {
+ eventType = PanGestureInput::PANGESTURE_END;
+ mPanInProgress = false;
+ } else if (!mPanInProgress) {
+ eventType = PanGestureInput::PANGESTURE_START;
+ mPanInProgress = true;
+ } else if (mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase
+ .isSome()) {
+ switch (*mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase) {
+ case PHASE_BEGIN:
+ // we should never hit this because we'll hit the above case
+ // before this.
+ MOZ_ASSERT_UNREACHABLE();
+ eventType = PanGestureInput::PANGESTURE_START;
+ mPanInProgress = true;
+ break;
+ case PHASE_UPDATE:
+ // nothing to do here, eventtype should already be set
+ MOZ_ASSERT(mPanInProgress);
+ MOZ_ASSERT(eventType == PanGestureInput::PANGESTURE_PAN);
+ eventType = PanGestureInput::PANGESTURE_PAN;
+ break;
+ case PHASE_END:
+ MOZ_ASSERT(mPanInProgress);
+ eventType = PanGestureInput::PANGESTURE_END;
+ mPanInProgress = false;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE();
+ break;
+ }
+ }
+
+ mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase.reset();
+
+ const bool isPageMode =
+ StaticPrefs::apz_gtk_pangesture_delta_mode() != 2;
+ const double multiplier =
+ isPageMode
+ ? StaticPrefs::apz_gtk_pangesture_page_delta_mode_multiplier()
+ : StaticPrefs::
+ apz_gtk_pangesture_pixel_delta_mode_multiplier() *
+ FractionalScaleFactor();
+
+ ScreenPoint deltas(float(aEvent->delta_x * multiplier),
+ float(aEvent->delta_y * multiplier));
+
+ LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent);
+ PanGestureInput panEvent(
+ eventType, GetEventTimeStamp(aEvent->time),
+ ScreenPoint(touchPoint.x, touchPoint.y), deltas,
+ KeymapWrapper::ComputeKeyModifiers(aEvent->state));
+ panEvent.mDeltaType = isPageMode ? PanGestureInput::PANDELTA_PAGE
+ : PanGestureInput::PANDELTA_PIXEL;
+ panEvent.mSimulateMomentum =
+ StaticPrefs::apz_gtk_kinetic_scroll_enabled();
+
+ DispatchPanGesture(panEvent);
+
+ if (mCurrentSynthesizedTouchpadPan.mSavedObserver != 0) {
+ mozilla::widget::AutoObserverNotifier::NotifySavedObserver(
+ mCurrentSynthesizedTouchpadPan.mSavedObserver,
+ "touchpadpanevent");
+ mCurrentSynthesizedTouchpadPan.mSavedObserver = 0;
+ }
+
+ return;
+ }
+
+ // Older GTK doesn't support stop events, so we can't support fling
+ wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSLY;
+ }
+
+ // TODO - use a more appropriate scrolling unit than lines.
+ // Multiply event deltas by 3 to emulate legacy behaviour.
+ wheelEvent.mDeltaX = aEvent->delta_x * 3;
+ wheelEvent.mDeltaY = aEvent->delta_y * 3;
+ wheelEvent.mWheelTicksX = aEvent->delta_x;
+ wheelEvent.mWheelTicksY = aEvent->delta_y;
+ wheelEvent.mIsNoLineOrPageDelta = true;
+
+ break;
+ }
+ case GDK_SCROLL_UP:
+ wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = -3;
+ wheelEvent.mWheelTicksY = -1;
+ break;
+ case GDK_SCROLL_DOWN:
+ wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = 3;
+ wheelEvent.mWheelTicksY = 1;
+ break;
+ case GDK_SCROLL_LEFT:
+ wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = -1;
+ wheelEvent.mWheelTicksX = -1;
+ break;
+ case GDK_SCROLL_RIGHT:
+ wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = 1;
+ wheelEvent.mWheelTicksX = 1;
+ break;
+ }
+
+ wheelEvent.mRefPoint = GetRefPoint(this, aEvent);
+
+ KeymapWrapper::InitInputEvent(wheelEvent, aEvent->state);
+
+ wheelEvent.AssignEventTime(GetWidgetEventTime(aEvent->time));
+
+ DispatchInputEvent(&wheelEvent);
+}
+
+void nsWindow::DispatchPanGesture(PanGestureInput& aPanInput) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mSwipeTracker) {
+ // Give the swipe tracker a first pass at the event. If a new pan gesture
+ // has been started since the beginning of the swipe, the swipe tracker
+ // will know to ignore the event.
+ nsEventStatus status = mSwipeTracker->ProcessEvent(aPanInput);
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+ }
+
+ APZEventResult result;
+ if (mAPZC) {
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+
+ result = mAPZC->InputBridge()->ReceiveInputEvent(aPanInput);
+ if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+ }
+
+ WidgetWheelEvent event = aPanInput.ToWidgetEvent(this);
+ if (!mAPZC) {
+ if (MayStartSwipeForNonAPZ(aPanInput)) {
+ return;
+ }
+ } else {
+ event = MayStartSwipeForAPZ(aPanInput, result);
+ }
+
+ ProcessUntransformedAPZEvent(&event, result);
+}
+
+void nsWindow::OnWindowStateEvent(GtkWidget* aWidget,
+ GdkEventWindowState* aEvent) {
+ LOG("nsWindow::OnWindowStateEvent for %p changed 0x%x new_window_state "
+ "0x%x\n",
+ aWidget, aEvent->changed_mask, aEvent->new_window_state);
+
+ if (IS_MOZ_CONTAINER(aWidget)) {
+ // This event is notifying the container widget of changes to the
+ // toplevel window. Just detect changes affecting whether windows are
+ // viewable.
+ //
+ // (A visibility notify event is sent to each window that becomes
+ // viewable when the toplevel is mapped, but we can't rely on that for
+ // setting mHasMappedToplevel because these toplevel window state
+ // events are asynchronous. The windows in the hierarchy now may not
+ // be the same windows as when the toplevel was mapped, so they may
+ // not get VisibilityNotify events.)
+ bool mapped = !(aEvent->new_window_state &
+ (GDK_WINDOW_STATE_ICONIFIED | GDK_WINDOW_STATE_WITHDRAWN));
+ SetHasMappedToplevel(mapped);
+ LOG("\tquick return because IS_MOZ_CONTAINER(aWidget) is true\n");
+ return;
+ }
+ // else the widget is a shell widget.
+
+ // The block below is a bit evil.
+ //
+ // When a window is resized before it is shown, gtk_window_resize() delays
+ // resizes until the window is shown. If gtk_window_state_event() sees a
+ // GDK_WINDOW_STATE_MAXIMIZED change [1] before the window is shown, then
+ // gtk_window_compute_configure_request_size() ignores the values from the
+ // resize [2]. See bug 1449166 for an example of how this could happen.
+ //
+ // [1] https://gitlab.gnome.org/GNOME/gtk/blob/3.22.30/gtk/gtkwindow.c#L7967
+ // [2] https://gitlab.gnome.org/GNOME/gtk/blob/3.22.30/gtk/gtkwindow.c#L9377
+ //
+ // In order to provide a sensible size for the window when the user exits
+ // maximized state, we hide the GDK_WINDOW_STATE_MAXIMIZED change from
+ // gtk_window_state_event() so as to trick GTK into using the values from
+ // gtk_window_resize() in its configure request.
+ //
+ // We instead notify gtk_window_state_event() of the maximized state change
+ // once the window is shown.
+ //
+ // See https://gitlab.gnome.org/GNOME/gtk/issues/1044
+ //
+ // This may be fixed in Gtk 3.24+ but it's still live and kicking
+ // (Bug 1791779).
+ if (!mIsShown) {
+ aEvent->changed_mask = static_cast<GdkWindowState>(
+ aEvent->changed_mask & ~GDK_WINDOW_STATE_MAXIMIZED);
+ } else if (aEvent->changed_mask & GDK_WINDOW_STATE_WITHDRAWN &&
+ aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
+ aEvent->changed_mask = static_cast<GdkWindowState>(
+ aEvent->changed_mask | GDK_WINDOW_STATE_MAXIMIZED);
+ }
+
+ // This is a workaround for https://gitlab.gnome.org/GNOME/gtk/issues/1395
+ // Gtk+ controls window active appearance by window-state-event signal.
+ if (IsChromeWindowTitlebar() &&
+ (aEvent->changed_mask & GDK_WINDOW_STATE_FOCUSED)) {
+ // Emulate what Gtk+ does at gtk_window_state_event().
+ // We can't check GTK_STATE_FLAG_BACKDROP directly as it's set by Gtk+
+ // *after* this window-state-event handler.
+ mTitlebarBackdropState =
+ !(aEvent->new_window_state & GDK_WINDOW_STATE_FOCUSED);
+
+ // keep IsActiveBrowserWindow in sync with GDK_WINDOW_STATE_FOCUSED
+ UpdateMozWindowActive();
+
+ ForceTitlebarRedraw();
+ }
+
+ // We don't care about anything but changes in the maximized/icon/fullscreen
+ // states but we need a workaround for bug in Wayland:
+ // https://gitlab.gnome.org/GNOME/gtk/issues/67
+ // Under wayland the gtk_window_iconify implementation does NOT synthetize
+ // window_state_event where the GDK_WINDOW_STATE_ICONIFIED is set.
+ // During restore we won't get aEvent->changed_mask with
+ // the GDK_WINDOW_STATE_ICONIFIED so to detect that change we use the stored
+ // mSizeMode and obtaining a focus.
+ bool waylandWasIconified =
+ (GdkIsWaylandDisplay() &&
+ aEvent->changed_mask & GDK_WINDOW_STATE_FOCUSED &&
+ aEvent->new_window_state & GDK_WINDOW_STATE_FOCUSED &&
+ mSizeMode == nsSizeMode_Minimized);
+ if (!waylandWasIconified &&
+ (aEvent->changed_mask &
+ (GDK_WINDOW_STATE_ICONIFIED | GDK_WINDOW_STATE_MAXIMIZED |
+ GDK_WINDOW_STATE_TILED | GDK_WINDOW_STATE_FULLSCREEN)) == 0) {
+ LOG("\tearly return because no interesting bits changed\n");
+ return;
+ }
+
+ auto oldSizeMode = mSizeMode;
+ if (aEvent->new_window_state & GDK_WINDOW_STATE_ICONIFIED) {
+ LOG("\tIconified\n");
+ mSizeMode = nsSizeMode_Minimized;
+#ifdef ACCESSIBILITY
+ DispatchMinimizeEventAccessible();
+#endif // ACCESSIBILITY
+ } else if (aEvent->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) {
+ LOG("\tFullscreen\n");
+ mSizeMode = nsSizeMode_Fullscreen;
+ } else if (aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
+ LOG("\tMaximized\n");
+ mSizeMode = nsSizeMode_Maximized;
+#ifdef ACCESSIBILITY
+ DispatchMaximizeEventAccessible();
+#endif // ACCESSIBILITY
+ } else {
+ LOG("\tNormal\n");
+ mSizeMode = nsSizeMode_Normal;
+#ifdef ACCESSIBILITY
+ DispatchRestoreEventAccessible();
+#endif // ACCESSIBILITY
+ }
+
+ mIsTiled = aEvent->new_window_state & GDK_WINDOW_STATE_TILED;
+ LOG("\tTiled: %d\n", int(mIsTiled));
+
+ if (mWidgetListener && mSizeMode != oldSizeMode) {
+ mWidgetListener->SizeModeChanged(mSizeMode);
+ }
+
+ if (mDrawInTitlebar && mTransparencyBitmapForTitlebar) {
+ if (mSizeMode == nsSizeMode_Normal && !mIsTiled) {
+ UpdateTitlebarTransparencyBitmap();
+ } else {
+ ClearTransparencyBitmap();
+ }
+ }
+}
+
+void nsWindow::OnDPIChanged() {
+ // Update menu's font size etc.
+ // This affects style / layout because it affects system font sizes.
+ if (mWidgetListener) {
+ if (PresShell* presShell = mWidgetListener->GetPresShell()) {
+ presShell->BackingScaleFactorChanged();
+ }
+ mWidgetListener->UIResolutionChanged();
+ }
+ NotifyThemeChanged(ThemeChangeKind::StyleAndLayout);
+}
+
+void nsWindow::OnCheckResize() { mPendingConfigures++; }
+
+void nsWindow::OnCompositedChanged() {
+ // Update CSD after the change in alpha visibility. This only affects
+ // system metrics, not other theme shenanigans.
+ NotifyThemeChanged(ThemeChangeKind::MediaQueriesOnly);
+ mCompositedScreen = gdk_screen_is_composited(gdk_screen_get_default());
+}
+
+void nsWindow::OnScaleChanged() {
+ // Force scale factor recalculation
+ if (!mGdkWindow) {
+ mWindowScaleFactorChanged = true;
+ return;
+ }
+ LOG("OnScaleChanged -> %d\n", gdk_window_get_scale_factor(mGdkWindow));
+
+ // Gtk supply us sometimes with doubled events so stay calm in such case.
+ if (gdk_window_get_scale_factor(mGdkWindow) == mWindowScaleFactor) {
+ return;
+ }
+
+ // We pause compositor to avoid rendering of obsoleted remote content which
+ // produces flickering.
+ // Re-enable compositor again when remote content is updated or
+ // timeout happens.
+ PauseCompositorFlickering();
+
+ // Force scale factor recalculation
+ mWindowScaleFactorChanged = true;
+
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(GTK_WIDGET(mContainer), &allocation);
+ LayoutDeviceIntSize size = GdkRectToDevicePixels(allocation).Size();
+ mBounds.SizeTo(size);
+ // Check mBounds size
+ if (mCompositorSession &&
+ !wr::WindowSizeSanityCheck(mBounds.width, mBounds.height)) {
+ gfxCriticalNoteOnce << "Invalid mBounds in OnScaleChanged " << mBounds;
+ }
+
+ if (mWidgetListener) {
+ if (PresShell* presShell = mWidgetListener->GetPresShell()) {
+ presShell->BackingScaleFactorChanged();
+ }
+ }
+ // This affects style / layout because it affects system font sizes.
+ // Update menu's font size etc.
+ NotifyThemeChanged(ThemeChangeKind::StyleAndLayout);
+
+ DispatchResized();
+
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
+ }
+
+ if (mCursor.IsCustom()) {
+ mUpdateCursor = true;
+ SetCursor(Cursor{mCursor});
+ }
+}
+
+void nsWindow::DispatchDragEvent(EventMessage aMsg,
+ const LayoutDeviceIntPoint& aRefPoint,
+ guint aTime) {
+ LOGDRAG("nsWindow::DispatchDragEvent");
+ WidgetDragEvent event(true, aMsg, this);
+
+ InitDragEvent(event);
+
+ event.mRefPoint = aRefPoint;
+ event.AssignEventTime(GetWidgetEventTime(aTime));
+
+ DispatchInputEvent(&event);
+}
+
+void nsWindow::OnDragDataReceivedEvent(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY,
+ GtkSelectionData* aSelectionData,
+ guint aInfo, guint aTime,
+ gpointer aData) {
+ LOGDRAG("nsWindow::OnDragDataReceived");
+
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ nsDragService::AutoEventLoop loop(dragService);
+ dragService->TargetDataReceived(aWidget, aDragContext, aX, aY, aSelectionData,
+ aInfo, aTime);
+}
+
+nsWindow* nsWindow::GetTransientForWindowIfPopup() {
+ if (mWindowType != WindowType::Popup) {
+ return nullptr;
+ }
+ GtkWindow* toplevel = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+ if (toplevel) {
+ return get_window_for_gtk_widget(GTK_WIDGET(toplevel));
+ }
+ return nullptr;
+}
+
+bool nsWindow::IsHandlingTouchSequence(GdkEventSequence* aSequence) {
+ return mHandleTouchEvent && mTouches.Contains(aSequence);
+}
+
+gboolean nsWindow::OnTouchpadPinchEvent(GdkEventTouchpadPinch* aEvent) {
+ if (!StaticPrefs::apz_gtk_touchpad_pinch_enabled()) {
+ return TRUE;
+ }
+ // Do not respond to pinch gestures involving more than two fingers
+ // unless specifically preffed on. These are sometimes hooked up to other
+ // actions at the desktop environment level and having the browser also
+ // pinch can be undesirable.
+ if (aEvent->n_fingers > 2 &&
+ !StaticPrefs::apz_gtk_touchpad_pinch_three_fingers_enabled()) {
+ return FALSE;
+ }
+ auto pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
+ ScreenCoord currentSpan;
+ ScreenCoord previousSpan;
+
+ switch (aEvent->phase) {
+ case GDK_TOUCHPAD_GESTURE_PHASE_BEGIN:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_START;
+ currentSpan = aEvent->scale;
+ mCurrentTouchpadFocus = ViewAs<ScreenPixel>(
+ GetRefPoint(this, aEvent),
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+
+ // Assign PreviousSpan --> 0.999 to make mDeltaY field of the
+ // WidgetWheelEvent that this PinchGestureInput event will be converted
+ // to not equal Zero as our discussion because we observed that the
+ // scale of the PHASE_BEGIN event is 1.
+ previousSpan = 0.999;
+ break;
+
+ case GDK_TOUCHPAD_GESTURE_PHASE_UPDATE:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
+ mCurrentTouchpadFocus += ScreenPoint(aEvent->dx, aEvent->dy);
+ if (aEvent->scale == mLastPinchEventSpan) {
+ return FALSE;
+ }
+ currentSpan = aEvent->scale;
+ previousSpan = mLastPinchEventSpan;
+ break;
+
+ case GDK_TOUCHPAD_GESTURE_PHASE_END:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_END;
+ currentSpan = aEvent->scale;
+ previousSpan = mLastPinchEventSpan;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ PinchGestureInput event(
+ pinchGestureType, PinchGestureInput::TRACKPAD,
+ GetEventTimeStamp(aEvent->time), ExternalPoint(0, 0),
+ mCurrentTouchpadFocus,
+ 100.0 * ((aEvent->phase == GDK_TOUCHPAD_GESTURE_PHASE_END)
+ ? ScreenCoord(1.f)
+ : currentSpan),
+ 100.0 * ((aEvent->phase == GDK_TOUCHPAD_GESTURE_PHASE_END)
+ ? ScreenCoord(1.f)
+ : previousSpan),
+ KeymapWrapper::ComputeKeyModifiers(aEvent->state));
+
+ if (!event.SetLineOrPageDeltaY(this)) {
+ return FALSE;
+ }
+
+ mLastPinchEventSpan = aEvent->scale;
+ DispatchPinchGestureInput(event);
+ return TRUE;
+}
+
+gboolean nsWindow::OnTouchEvent(GdkEventTouch* aEvent) {
+ LOG("OnTouchEvent: x=%f y=%f type=%d\n", aEvent->x, aEvent->y, aEvent->type);
+ if (!mHandleTouchEvent) {
+ // If a popup window was spawned (e.g. as the result of a long-press)
+ // and touch events got diverted to that window within a touch sequence,
+ // ensure the touch event gets sent to the original window instead. We
+ // keep the checks here very conservative so that we only redirect
+ // events in this specific scenario.
+ nsWindow* targetWindow = GetTransientForWindowIfPopup();
+ if (targetWindow &&
+ targetWindow->IsHandlingTouchSequence(aEvent->sequence)) {
+ return targetWindow->OnTouchEvent(aEvent);
+ }
+
+ return FALSE;
+ }
+
+ EventMessage msg;
+ switch (aEvent->type) {
+ case GDK_TOUCH_BEGIN:
+ // check to see if we should rollup
+ if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false)) {
+ return FALSE;
+ }
+ msg = eTouchStart;
+ break;
+ case GDK_TOUCH_UPDATE:
+ msg = eTouchMove;
+ // Start dragging when motion events happens in the dragging area
+ if (mWindowShouldStartDragging) {
+ mWindowShouldStartDragging = false;
+ GdkWindow* gdk_window = gdk_window_get_toplevel(mGdkWindow);
+ MOZ_ASSERT(gdk_window,
+ "gdk_window_get_toplevel should not return null");
+
+ LOG(" start window dragging window\n");
+ gdk_window_begin_move_drag(gdk_window, 1, aEvent->x_root,
+ aEvent->y_root, aEvent->time);
+
+ // Cancel the event sequence. gdk will steal all subsequent events
+ // (including TOUCH_END).
+ msg = eTouchCancel;
+ }
+ break;
+ case GDK_TOUCH_END:
+ msg = eTouchEnd;
+ if (mWindowShouldStartDragging) {
+ LOG(" end of window dragging window\n");
+ mWindowShouldStartDragging = false;
+ }
+ break;
+ case GDK_TOUCH_CANCEL:
+ msg = eTouchCancel;
+ break;
+ default:
+ return FALSE;
+ }
+
+ const LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent);
+
+ int32_t id;
+ RefPtr<dom::Touch> touch;
+ if (mTouches.Remove(aEvent->sequence, getter_AddRefs(touch))) {
+ id = touch->mIdentifier;
+ } else {
+ id = ++gLastTouchID & 0x7FFFFFFF;
+ }
+
+ touch =
+ new dom::Touch(id, touchPoint, LayoutDeviceIntPoint(1, 1), 0.0f, 0.0f);
+
+ WidgetTouchEvent event(true, msg, this);
+ KeymapWrapper::InitInputEvent(event, aEvent->state);
+
+ if (msg == eTouchStart || msg == eTouchMove) {
+ mTouches.InsertOrUpdate(aEvent->sequence, std::move(touch));
+ // add all touch points to event object
+ for (const auto& data : mTouches.Values()) {
+ event.mTouches.AppendElement(new dom::Touch(*data));
+ }
+ } else if (msg == eTouchEnd || msg == eTouchCancel) {
+ *event.mTouches.AppendElement() = std::move(touch);
+ }
+
+ nsIWidget::ContentAndAPZEventStatus eventStatus = DispatchInputEvent(&event);
+
+ // There's a chance that we are in drag area and the event is not consumed
+ // by something on it.
+ if (msg == eTouchStart && mDraggableRegion.Contains(touchPoint) &&
+ eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
+ mWindowShouldStartDragging = true;
+ }
+ return TRUE;
+}
+
+// Return true if toplevel window is transparent.
+// It's transparent when we're running on composited screens
+// and we can draw main window without system titlebar.
+bool nsWindow::IsToplevelWindowTransparent() {
+ static bool transparencyConfigured = false;
+
+ if (!transparencyConfigured) {
+ if (gdk_screen_is_composited(gdk_screen_get_default())) {
+ // Some Gtk+ themes use non-rectangular toplevel windows. To fully
+ // support such themes we need to make toplevel window transparent
+ // with ARGB visual.
+ // It may cause performanance issue so make it configurable
+ // and enable it by default for selected window managers.
+ if (Preferences::HasUserValue("mozilla.widget.use-argb-visuals")) {
+ // argb visual is explicitly required so use it
+ sTransparentMainWindow =
+ Preferences::GetBool("mozilla.widget.use-argb-visuals");
+ } else {
+ // Enable transparent toplevel window if we can draw main window
+ // without system titlebar as Gtk+ themes use titlebar round corners.
+ sTransparentMainWindow =
+ GetSystemGtkWindowDecoration() != GTK_DECORATION_NONE;
+ }
+ }
+ transparencyConfigured = true;
+ }
+
+ return sTransparentMainWindow;
+}
+
+#ifdef MOZ_X11
+// Configure GL visual on X11.
+bool nsWindow::ConfigureX11GLVisual() {
+ auto* screen = gtk_widget_get_screen(mShell);
+ int visualId = 0;
+ bool haveVisual = false;
+
+ if (gfxVars::UseEGL()) {
+ haveVisual = GLContextEGL::FindVisual(&visualId);
+ }
+
+ // We are on GLX or use it as a fallback on Mesa, see
+ // https://gitlab.freedesktop.org/mesa/mesa/-/issues/149
+ if (!haveVisual) {
+ auto* display = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(mShell));
+ int screenNumber = GDK_SCREEN_XNUMBER(screen);
+ haveVisual = GLContextGLX::FindVisual(display, screenNumber, &visualId);
+ }
+
+ GdkVisual* gdkVisual = nullptr;
+ if (haveVisual) {
+ // If we're using CSD, rendering will go through mContainer, but
+ // it will inherit this visual as it is a child of mShell.
+ gdkVisual = gdk_x11_screen_lookup_visual(screen, visualId);
+ }
+ if (!gdkVisual) {
+ NS_WARNING("We're missing X11 Visual!");
+ // We try to use a fallback alpha visual
+ GdkScreen* screen = gtk_widget_get_screen(mShell);
+ gdkVisual = gdk_screen_get_rgba_visual(screen);
+ }
+ if (gdkVisual) {
+ gtk_widget_set_visual(mShell, gdkVisual);
+ mHasAlphaVisual = true;
+ return true;
+ }
+
+ return false;
+}
+#endif
+
+nsAutoCString nsWindow::GetFrameTag() const {
+ if (nsIFrame* frame = GetFrame()) {
+#ifdef DEBUG_FRAME_DUMP
+ return frame->ListTag();
+#else
+ nsAutoCString buf;
+ buf.AppendPrintf("Frame(%p)", frame);
+ if (nsIContent* content = frame->GetContent()) {
+ buf.Append(' ');
+ AppendUTF16toUTF8(content->NodeName(), buf);
+ }
+ return buf;
+#endif
+ }
+ return nsAutoCString("(no frame)");
+}
+
+nsCString nsWindow::GetPopupTypeName() {
+ switch (mPopupHint) {
+ case PopupType::Menu:
+ return nsCString("Menu");
+ case PopupType::Tooltip:
+ return nsCString("Tooltip");
+ case PopupType::Panel:
+ return nsCString("Panel/Utility");
+ default:
+ return nsCString("Unknown");
+ }
+}
+
+// Disables all rendering of GtkWidget from Gtk side.
+// We do our best to persuade Gtk/Gdk to ignore all painting
+// to the widget.
+static void GtkWidgetDisableUpdates(GtkWidget* aWidget) {
+ // Clear exposure mask - it disabled synthesized events.
+ GdkWindow* window = gtk_widget_get_window(aWidget);
+ gdk_window_set_events(window, (GdkEventMask)(gdk_window_get_events(window) &
+ (~GDK_EXPOSURE_MASK)));
+
+ // Remove before/after paint handles from frame clock.
+ // It disables widget content updates.
+ GdkFrameClock* frame_clock = gdk_window_get_frame_clock(window);
+ g_signal_handlers_disconnect_by_data(frame_clock, window);
+}
+
+Window nsWindow::GetX11Window() {
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ return mGdkWindow ? gdk_x11_window_get_xid(mGdkWindow) : X11None;
+ }
+#endif
+ return (Window) nullptr;
+}
+
+void nsWindow::EnsureGdkWindow() {
+ if (!mGdkWindow) {
+ mGdkWindow = gtk_widget_get_window(mDrawToContainer ? GTK_WIDGET(mContainer)
+ : mShell);
+ g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", this);
+ }
+}
+
+bool nsWindow::GetShapedState() {
+ return mIsTransparent && !mHasAlphaVisual && !mTransparencyBitmapForTitlebar;
+}
+
+void nsWindow::ConfigureCompositor() {
+ MOZ_DIAGNOSTIC_ASSERT(mCompositorState == COMPOSITOR_ENABLED);
+ MOZ_DIAGNOSTIC_ASSERT(mIsMapped);
+
+ LOG("nsWindow::ConfigureCompositor()");
+ auto startCompositing = [self = RefPtr{this}, this]() -> void {
+ LOG(" moz_container_wayland_add_or_fire_initial_draw_callback "
+ "ConfigureCompositor");
+
+ // too late
+ if (mIsDestroyed || !mIsMapped) {
+ LOG(" quit, mIsDestroyed = %d mIsMapped = %d", !!mIsDestroyed,
+ mIsMapped);
+ return;
+ }
+ // Compositor will be resumed later by ResumeCompositorFlickering().
+ if (mCompositorState == COMPOSITOR_PAUSED_FLICKERING) {
+ LOG(" quit, will be resumed by ResumeCompositorFlickering.");
+ return;
+ }
+ // Compositor will be resumed at nsWindow::SetCompositorWidgetDelegate().
+ if (!mCompositorWidgetDelegate) {
+ LOG(" quit, missing mCompositorWidgetDelegate");
+ return;
+ }
+
+ ResumeCompositorImpl();
+ };
+
+ if (GdkIsWaylandDisplay()) {
+#ifdef MOZ_WAYLAND
+ moz_container_wayland_add_or_fire_initial_draw_callback(mContainer,
+ startCompositing);
+#endif
+ } else {
+ startCompositing();
+ }
+}
+
+void nsWindow::ConfigureGdkWindow() {
+ LOG("nsWindow::ConfigureGdkWindow()");
+
+ EnsureGdkWindow();
+
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ GdkVisual* gdkVisual = gdk_window_get_visual(mGdkWindow);
+ Visual* visual = gdk_x11_visual_get_xvisual(gdkVisual);
+ int depth = gdk_visual_get_depth(gdkVisual);
+ mSurfaceProvider.Initialize(GetX11Window(), visual, depth,
+ GetShapedState());
+
+ // Set window manager hint to keep fullscreen windows composited.
+ //
+ // If the window were to get unredirected, there could be visible
+ // tearing because Gecko does not align its framebuffer updates with
+ // vblank.
+ SetCompositorHint(GTK_WIDGET_COMPOSIDED_ENABLED);
+ }
+#endif
+#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay()) {
+ mSurfaceProvider.Initialize(this);
+ }
+#endif
+
+ if (mIsDragPopup) {
+ if (GdkIsWaylandDisplay()) {
+ // Disable painting to the widget on Wayland as we paint directly to the
+ // widget. Wayland compositors does not paint wl_subsurface
+ // of D&D widget.
+ if (GtkWidget* parent = gtk_widget_get_parent(mShell)) {
+ GtkWidgetDisableUpdates(parent);
+ }
+ GtkWidgetDisableUpdates(mShell);
+ GtkWidgetDisableUpdates(GTK_WIDGET(mContainer));
+ } else {
+ // Disable rendering of parent container on X11 to avoid flickering.
+ if (GtkWidget* parent = gtk_widget_get_parent(mShell)) {
+ gtk_widget_set_opacity(parent, 0.0);
+ }
+ }
+ }
+
+ if (mWindowType == WindowType::Popup) {
+ if (mNoAutoHide) {
+ gint wmd = ConvertBorderStyles(mBorderStyle);
+ if (wmd != -1) {
+ gdk_window_set_decorations(mGdkWindow, (GdkWMDecoration)wmd);
+ }
+ }
+ // If the popup ignores mouse events, set an empty input shape.
+ SetInputRegion(mInputRegion);
+ }
+
+ RefreshWindowClass();
+
+ // We're not mapped yet but we have already created compositor.
+ if (mCompositorWidgetDelegate) {
+ ConfigureCompositor();
+ }
+
+ LOG(" finished, new GdkWindow %p XID 0x%lx\n", mGdkWindow, GetX11Window());
+}
+
+void nsWindow::ReleaseGdkWindow() {
+ LOG("nsWindow::ReleaseGdkWindow()");
+
+ DestroyChildWindows();
+
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->DisableRendering();
+ }
+
+ if (mGdkWindow) {
+ g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", nullptr);
+ mGdkWindow = nullptr;
+ }
+
+ mSurfaceProvider.CleanupResources();
+}
+
+nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ widget::InitData* aInitData) {
+ LOG("nsWindow::Create\n");
+
+ // only set the base parent if we're going to be a dialog or a
+ // toplevel
+ nsIWidget* baseParent =
+ aInitData && (aInitData->mWindowType == WindowType::Dialog ||
+ aInitData->mWindowType == WindowType::TopLevel ||
+ aInitData->mWindowType == WindowType::Invisible)
+ ? nullptr
+ : aParent;
+
+#ifdef ACCESSIBILITY
+ // Send a DBus message to check whether a11y is enabled
+ a11y::PreInit();
+#endif
+
+#ifdef MOZ_WAYLAND
+ // Ensure that KeymapWrapper is created on Wayland as we need it for
+ // keyboard focus tracking.
+ if (GdkIsWaylandDisplay()) {
+ KeymapWrapper::EnsureInstance();
+ }
+#endif
+
+ // Ensure that the toolkit is created.
+ nsGTKToolkit::GetToolkit();
+
+ // initialize all the common bits of this class
+ BaseCreate(baseParent, aInitData);
+
+ // and do our common creation
+ mParent = aParent;
+ mCreated = true;
+ // save our bounds
+ mBounds = aRect;
+ LOG(" mBounds: x:%d y:%d w:%d h:%d\n", mBounds.x, mBounds.y, mBounds.width,
+ mBounds.height);
+
+ ConstrainSize(&mBounds.width, &mBounds.height);
+ mLastSizeRequest = mBounds.Size();
+
+ GtkWidget* eventWidget = nullptr;
+ bool popupNeedsAlphaVisual = mWindowType == WindowType::Popup &&
+ (aInitData && aInitData->mTransparencyMode ==
+ TransparencyMode::Transparent);
+
+ // Figure out our parent window - only used for WindowType::Child
+ GdkWindow* parentGdkWindow = nullptr;
+ nsWindow* parentnsWindow = nullptr;
+
+ if (aParent) {
+ parentnsWindow = static_cast<nsWindow*>(aParent);
+ parentGdkWindow = parentnsWindow->mGdkWindow;
+ } else if (aNativeParent && GDK_IS_WINDOW(aNativeParent)) {
+ parentGdkWindow = GDK_WINDOW(aNativeParent);
+ parentnsWindow = get_window_for_gdk_window(parentGdkWindow);
+ if (!parentnsWindow) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (mWindowType == WindowType::Child) {
+ // We don't support WindowType::Child directly but emulate it by popup
+ // windows.
+ mWindowType = WindowType::Popup;
+ if (!parentnsWindow) {
+ if (aNativeParent && GTK_IS_CONTAINER(aNativeParent)) {
+ parentnsWindow = get_window_for_gtk_widget(GTK_WIDGET(aNativeParent));
+ }
+ }
+ mIsChildWindow = true;
+ LOG(" child widget, switch to popup. parent nsWindow %p", parentnsWindow);
+ }
+
+ if (mWindowType == WindowType::Popup && !parentnsWindow) {
+ LOG(" popup window without parent!");
+ if (GdkIsWaylandDisplay()) {
+ LOG(" switch to toplevel on Wayland.");
+ // Wayland does not allow to create popup without parent so switch to
+ // toplevel and mark as wayland panel.
+ mIsWaylandPanelWindow = true;
+ mWindowType = WindowType::TopLevel;
+ }
+ }
+
+ if (mWindowType != WindowType::Dialog && mWindowType != WindowType::Popup &&
+ mWindowType != WindowType::TopLevel &&
+ mWindowType != WindowType::Invisible) {
+ MOZ_ASSERT_UNREACHABLE("Unexpected eWindowType");
+ return NS_ERROR_FAILURE;
+ }
+
+ mAlwaysOnTop = aInitData && aInitData->mAlwaysOnTop;
+ mIsPIPWindow = aInitData && aInitData->mPIPWindow;
+ // mNoAutoHide seems to be always false here.
+ // The mNoAutoHide state is set later on nsMenuPopupFrame level
+ // and can be changed so we use WaylandPopupIsPermanent() to get
+ // recent popup config (Bug 1728952).
+ mNoAutoHide = aInitData && aInitData->mNoAutoHide;
+
+ // Popups that are not noautohide are only temporary. The are used
+ // for menus and the like and disappear when another window is used.
+ // For most popups, use the standard GtkWindowType GTK_WINDOW_POPUP,
+ // which will use a Window with the override-redirect attribute
+ // (for temporary windows).
+ // For long-lived windows, their stacking order is managed by the
+ // window manager, as indicated by GTK_WINDOW_TOPLEVEL.
+ // For Wayland we have to always use GTK_WINDOW_POPUP to control
+ // popup window position.
+ GtkWindowType type = GTK_WINDOW_TOPLEVEL;
+ if (mWindowType == WindowType::Popup) {
+ MOZ_ASSERT(aInitData);
+ type = GTK_WINDOW_POPUP;
+ if (GdkIsX11Display() && mNoAutoHide) {
+ type = GTK_WINDOW_TOPLEVEL;
+ }
+ }
+ mShell = gtk_window_new(type);
+
+ // Ensure gfxPlatform is initialized, since that is what initializes
+ // gfxVars, used below.
+ Unused << gfxPlatform::GetPlatform();
+
+ if (mWindowType == WindowType::TopLevel ||
+ mWindowType == WindowType::Dialog) {
+ mGtkWindowDecoration = GetSystemGtkWindowDecoration();
+ }
+
+ // Don't use transparency for PictureInPicture windows.
+ bool toplevelNeedsAlphaVisual = false;
+ if (mWindowType == WindowType::TopLevel && !mIsPIPWindow) {
+ toplevelNeedsAlphaVisual = IsToplevelWindowTransparent();
+ }
+
+ bool isGLVisualSet = false;
+ mIsAccelerated = ComputeShouldAccelerate();
+#ifdef MOZ_X11
+ if (GdkIsX11Display() && mIsAccelerated) {
+ isGLVisualSet = ConfigureX11GLVisual();
+ }
+#endif
+ if (!isGLVisualSet && (popupNeedsAlphaVisual || toplevelNeedsAlphaVisual)) {
+ // We're running on composited screen so we can use alpha visual
+ // for both toplevel and popups.
+ if (mCompositedScreen) {
+ GdkVisual* visual =
+ gdk_screen_get_rgba_visual(gtk_widget_get_screen(mShell));
+ if (visual) {
+ gtk_widget_set_visual(mShell, visual);
+ mHasAlphaVisual = true;
+ }
+ }
+ }
+
+ // Use X shape mask to draw round corners of Firefox titlebar.
+ // We don't use shape masks any more as we switched to ARGB visual
+ // by default and non-compositing screens use solid-csd decorations
+ // without round corners.
+ // Leave the shape mask code here as it can be used to draw round
+ // corners on EGL (https://gitlab.freedesktop.org/mesa/mesa/-/issues/149)
+ // or when custom titlebar theme is used.
+ mTransparencyBitmapForTitlebar = TitlebarUseShapeMask();
+
+ // We have a toplevel window with transparency.
+ // Calls to UpdateTitlebarTransparencyBitmap() from OnExposeEvent()
+ // occur before SetTransparencyMode() receives TransparencyMode::Transparent
+ // from layout, so set mIsTransparent here.
+ if (mWindowType == WindowType::TopLevel &&
+ (mHasAlphaVisual || mTransparencyBitmapForTitlebar)) {
+ mIsTransparent = true;
+ }
+
+ // We only move a general managed toplevel window if someone has
+ // actually placed the window somewhere. If no placement has taken
+ // place, we just let the window manager Do The Right Thing.
+ if (AreBoundsSane()) {
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
+ LOG("nsWindow::Create() Initial resize to %d x %d\n", size.width,
+ size.height);
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ }
+ if (mIsPIPWindow) {
+ LOG(" Is PIP Window\n");
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_UTILITY);
+ } else if (mWindowType == WindowType::Dialog) {
+ mGtkWindowRoleName = "Dialog";
+
+ SetDefaultIcon();
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_DIALOG);
+ LOG("nsWindow::Create(): dialog");
+ if (parentnsWindow) {
+ GtkWindowSetTransientFor(GTK_WINDOW(mShell),
+ GTK_WINDOW(parentnsWindow->GetGtkWidget()));
+ LOG(" set parent window [%p]\n", parentnsWindow);
+ }
+ } else if (mWindowType == WindowType::Popup) {
+ MOZ_ASSERT(aInitData);
+ mGtkWindowRoleName = "Popup";
+ mPopupHint = aInitData->mPopupHint;
+
+ LOG("nsWindow::Create() Popup");
+
+ if (mNoAutoHide) {
+ // ... but the window manager does not decorate this window,
+ // nor provide a separate taskbar icon.
+ if (mBorderStyle == BorderStyle::Default) {
+ gtk_window_set_decorated(GTK_WINDOW(mShell), FALSE);
+ } else {
+ bool decorate = bool(mBorderStyle & BorderStyle::Title);
+ gtk_window_set_decorated(GTK_WINDOW(mShell), decorate);
+ if (decorate) {
+ gtk_window_set_deletable(GTK_WINDOW(mShell),
+ bool(mBorderStyle & BorderStyle::Close));
+ }
+ }
+ gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mShell), TRUE);
+ // Element focus is managed by the parent window so the
+ // WM_HINTS input field is set to False to tell the window
+ // manager not to set input focus to this window ...
+ gtk_window_set_accept_focus(GTK_WINDOW(mShell), FALSE);
+#ifdef MOZ_X11
+ // ... but when the window manager offers focus through
+ // WM_TAKE_FOCUS, focus is requested on the parent window.
+ if (GdkIsX11Display()) {
+ gtk_widget_realize(mShell);
+ gdk_window_add_filter(gtk_widget_get_window(mShell),
+ popup_take_focus_filter, nullptr);
+ }
+#endif
+ }
+
+ if (aInitData->mIsDragPopup) {
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_DND);
+ mIsDragPopup = true;
+ LOG("nsWindow::Create() Drag popup\n");
+ } else if (GdkIsX11Display()) {
+ // Set the window hints on X11 only. Wayland popups are configured
+ // at WaylandPopupConfigure().
+ GdkWindowTypeHint gtkTypeHint;
+ switch (mPopupHint) {
+ case PopupType::Menu:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
+ break;
+ case PopupType::Tooltip:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
+ break;
+ default:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
+ break;
+ }
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
+ LOG("nsWindow::Create() popup type %s", GetPopupTypeName().get());
+ }
+ if (parentnsWindow) {
+ LOG(" set parent window [%p] %s", parentnsWindow,
+ parentnsWindow->mGtkWindowRoleName.get());
+ GtkWindow* parentWidget = GTK_WINDOW(parentnsWindow->GetGtkWidget());
+ GtkWindowSetTransientFor(GTK_WINDOW(mShell), parentWidget);
+
+ // If popup parent is modal, we need to make popup modal too.
+ if (mPopupHint != PopupType::Tooltip &&
+ gtk_window_get_modal(parentWidget)) {
+ gtk_window_set_modal(GTK_WINDOW(mShell), true);
+ }
+ }
+
+ // We need realized mShell at NativeMoveResize().
+ gtk_widget_realize(mShell);
+
+ // With popup windows, we want to set their position.
+ // Place them immediately on X11 and save initial popup position
+ // on Wayland as we place Wayland popup on show.
+ if (GdkIsX11Display()) {
+ NativeMoveResize(/* move */ true, /* resize */ false);
+ } else if (AreBoundsSane()) {
+ GdkRectangle rect = DevicePixelsToGdkRectRoundOut(mBounds);
+ mPopupPosition = {rect.x, rect.y};
+ }
+ } else { // must be WindowType::TopLevel
+ mGtkWindowRoleName = "Toplevel";
+ SetDefaultIcon();
+
+ LOG("nsWindow::Create() Toplevel\n");
+
+ // each toplevel window gets its own window group
+ GtkWindowGroup* group = gtk_window_group_new();
+ gtk_window_group_add_window(group, GTK_WINDOW(mShell));
+ g_object_unref(group);
+ }
+
+ if (mAlwaysOnTop) {
+ gtk_window_set_keep_above(GTK_WINDOW(mShell), TRUE);
+ }
+
+ // It is important that this happens before the realize() call below, so that
+ // we don't get bogus CSD margins on Wayland, see bug 1794577.
+ if (IsAlwaysUndecoratedWindow()) {
+ LOG(" Is undecorated Window\n");
+ gtk_window_set_titlebar(GTK_WINDOW(mShell), gtk_fixed_new());
+ gtk_window_set_decorated(GTK_WINDOW(mShell), false);
+ }
+
+ // Create a container to hold child windows and child GtkWidgets.
+ GtkWidget* container = moz_container_new();
+ mContainer = MOZ_CONTAINER(container);
+
+ // "csd" style is set when widget is realized so we need to call
+ // it explicitly now.
+ gtk_widget_realize(mShell);
+
+ /* There are several cases here:
+ *
+ * 1) We're running on Gtk+ without client side decorations.
+ * Content is rendered to mShell window and we listen
+ * to the Gtk+ events on mShell
+ * 2) We're running on Gtk+ and client side decorations
+ * are drawn by Gtk+ to mShell. Content is rendered to mContainer
+ * and we listen to the Gtk+ events on mContainer.
+ * 3) We're running on Wayland. All gecko content is rendered
+ * to mContainer and we listen to the Gtk+ events on mContainer.
+ */
+ GtkStyleContext* style = gtk_widget_get_style_context(mShell);
+ mDrawToContainer = GdkIsWaylandDisplay() ||
+ mGtkWindowDecoration == GTK_DECORATION_CLIENT ||
+ gtk_style_context_has_class(style, "csd");
+ eventWidget = mDrawToContainer ? container : mShell;
+
+ // Prevent GtkWindow from painting a background to avoid flickering.
+ gtk_widget_set_app_paintable(
+ eventWidget, StaticPrefs::widget_transparent_windows_AtStartup());
+
+ gtk_widget_add_events(eventWidget, kEvents);
+
+ if (mDrawToContainer) {
+ gtk_widget_add_events(mShell, GDK_PROPERTY_CHANGE_MASK);
+ gtk_widget_set_app_paintable(
+ mShell, StaticPrefs::widget_transparent_windows_AtStartup());
+ }
+ if (mTransparencyBitmapForTitlebar) {
+ moz_container_force_default_visual(mContainer);
+ }
+
+ // If we draw to mContainer window then configure it now because
+ // gtk_container_add() realizes the child widget.
+ gtk_widget_set_has_window(container, mDrawToContainer);
+
+ gtk_container_add(GTK_CONTAINER(mShell), container);
+
+ // alwaysontop windows are generally used for peripheral indicators,
+ // so we don't focus them by default.
+ if (mAlwaysOnTop) {
+ gtk_window_set_focus_on_map(GTK_WINDOW(mShell), FALSE);
+ }
+
+ gtk_widget_realize(container);
+
+ // make sure this is the focus widget in the container
+ gtk_widget_show(container);
+
+ if (!mAlwaysOnTop) {
+ gtk_widget_grab_focus(container);
+ }
+
+#ifdef MOZ_WAYLAND
+ if (mIsDragPopup && GdkIsWaylandDisplay()) {
+ LOG(" set commit to parent");
+ moz_container_wayland_set_commit_to_parent(mContainer);
+ }
+#endif
+
+ if (mWindowType == WindowType::Popup) {
+ MOZ_ASSERT(aInitData);
+ // gdk does not automatically set the cursor for "temporary"
+ // windows, which are what gtk uses for popups.
+
+ // force SetCursor to actually set the cursor, even though our internal
+ // state indicates that we already have the standard cursor.
+ mUpdateCursor = true;
+ SetCursor(Cursor{eCursor_standard});
+ }
+
+ if (mIsChildWindow && parentnsWindow) {
+ GdkWindow* window = GetToplevelGdkWindow();
+ GdkWindow* parentWindow = parentnsWindow->GetToplevelGdkWindow();
+ LOG(" child GdkWindow %p set parent GdkWindow %p", window, parentWindow);
+ gdk_window_reparent(window, parentWindow,
+ DevicePixelsToGdkCoordRoundDown(mBounds.x),
+ DevicePixelsToGdkCoordRoundDown(mBounds.y));
+ }
+
+ if (mDrawToContainer) {
+ // Also label mShell toplevel window,
+ // property_notify_event_cb callback also needs to find its way home
+ g_object_set_data(G_OBJECT(gtk_widget_get_window(mShell)), "nsWindow",
+ this);
+ }
+
+ g_object_set_data(G_OBJECT(mContainer), "nsWindow", this);
+ g_object_set_data(G_OBJECT(mShell), "nsWindow", this);
+
+ // attach listeners for events
+ g_signal_connect(mShell, "configure_event", G_CALLBACK(configure_event_cb),
+ nullptr);
+ g_signal_connect(mShell, "delete_event", G_CALLBACK(delete_event_cb),
+ nullptr);
+ g_signal_connect(mShell, "window_state_event",
+ G_CALLBACK(window_state_event_cb), nullptr);
+ g_signal_connect(mShell, "check-resize", G_CALLBACK(check_resize_cb),
+ nullptr);
+ g_signal_connect(mShell, "composited-changed",
+ G_CALLBACK(widget_composited_changed_cb), nullptr);
+ g_signal_connect(mShell, "property-notify-event",
+ G_CALLBACK(property_notify_event_cb), nullptr);
+
+ if (mWindowType == WindowType::TopLevel) {
+ g_signal_connect_after(mShell, "size_allocate",
+ G_CALLBACK(toplevel_window_size_allocate_cb),
+ nullptr);
+ }
+
+ GdkScreen* screen = gtk_widget_get_screen(mShell);
+ if (!g_signal_handler_find(screen, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
+ FuncToGpointer(screen_composited_changed_cb),
+ nullptr)) {
+ g_signal_connect(screen, "composited-changed",
+ G_CALLBACK(screen_composited_changed_cb), nullptr);
+ }
+
+ gtk_drag_dest_set((GtkWidget*)mShell, (GtkDestDefaults)0, nullptr, 0,
+ (GdkDragAction)0);
+ g_signal_connect(mShell, "drag_motion", G_CALLBACK(drag_motion_event_cb),
+ nullptr);
+ g_signal_connect(mShell, "drag_leave", G_CALLBACK(drag_leave_event_cb),
+ nullptr);
+ g_signal_connect(mShell, "drag_drop", G_CALLBACK(drag_drop_event_cb),
+ nullptr);
+ g_signal_connect(mShell, "drag_data_received",
+ G_CALLBACK(drag_data_received_event_cb), nullptr);
+
+ GtkSettings* default_settings = gtk_settings_get_default();
+ g_signal_connect_after(default_settings, "notify::gtk-xft-dpi",
+ G_CALLBACK(settings_xft_dpi_changed_cb), this);
+
+ // Widget signals
+ g_signal_connect(mContainer, "map", G_CALLBACK(widget_map_cb), nullptr);
+ g_signal_connect(mContainer, "unmap", G_CALLBACK(widget_unmap_cb), nullptr);
+ g_signal_connect(mContainer, "unrealize", G_CALLBACK(widget_unrealize_cb),
+ nullptr);
+ g_signal_connect_after(mContainer, "size_allocate",
+ G_CALLBACK(size_allocate_cb), nullptr);
+ g_signal_connect(mContainer, "hierarchy-changed",
+ G_CALLBACK(hierarchy_changed_cb), nullptr);
+ g_signal_connect(mContainer, "notify::scale-factor",
+ G_CALLBACK(scale_changed_cb), nullptr);
+ // Initialize mHasMappedToplevel.
+ hierarchy_changed_cb(GTK_WIDGET(mContainer), nullptr);
+ // Expose, focus, key, and drag events are sent even to GTK_NO_WINDOW
+ // widgets.
+ g_signal_connect(G_OBJECT(mContainer), "draw", G_CALLBACK(expose_event_cb),
+ nullptr);
+ g_signal_connect(mContainer, "focus_in_event", G_CALLBACK(focus_in_event_cb),
+ nullptr);
+ g_signal_connect(mContainer, "focus_out_event",
+ G_CALLBACK(focus_out_event_cb), nullptr);
+ g_signal_connect(mContainer, "key_press_event",
+ G_CALLBACK(key_press_event_cb), nullptr);
+ g_signal_connect(mContainer, "key_release_event",
+ G_CALLBACK(key_release_event_cb), nullptr);
+
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ GtkWidget* widgets[] = {GTK_WIDGET(mContainer),
+ !mDrawToContainer ? mShell : nullptr};
+ for (size_t i = 0; i < ArrayLength(widgets) && widgets[i]; ++i) {
+ // Double buffering is controlled by the window's owning
+ // widget. Disable double buffering for painting directly to the
+ // X Window.
+ gtk_widget_set_double_buffered(widgets[i], FALSE);
+ }
+ }
+#endif
+#ifdef MOZ_WAYLAND
+ // Initialize the window specific VsyncSource early in order to avoid races
+ // with BrowserParent::UpdateVsyncParentVsyncDispatcher().
+ // Only use for toplevel windows for now, see bug 1619246.
+ if (GdkIsWaylandDisplay() &&
+ StaticPrefs::widget_wayland_vsync_enabled_AtStartup() &&
+ mWindowType == WindowType::TopLevel) {
+ mWaylandVsyncSource = new WaylandVsyncSource(this);
+ mWaylandVsyncDispatcher = new VsyncDispatcher(mWaylandVsyncSource);
+ LOG_VSYNC(" created WaylandVsyncSource");
+ }
+#endif
+
+ // We create input contexts for all containers, except for
+ // toplevel popup windows
+ if (mWindowType != WindowType::Popup) {
+ mIMContext = new IMContextWrapper(this);
+ }
+
+ // These events are sent to the owning widget of the relevant window
+ // and propagate up to the first widget that handles the events, so we
+ // need only connect on mShell, if it exists, to catch events on its
+ // window and windows of mContainer.
+ g_signal_connect(eventWidget, "enter-notify-event",
+ G_CALLBACK(enter_notify_event_cb), nullptr);
+ g_signal_connect(eventWidget, "leave-notify-event",
+ G_CALLBACK(leave_notify_event_cb), nullptr);
+ g_signal_connect(eventWidget, "motion-notify-event",
+ G_CALLBACK(motion_notify_event_cb), nullptr);
+ g_signal_connect(eventWidget, "button-press-event",
+ G_CALLBACK(button_press_event_cb), nullptr);
+ g_signal_connect(eventWidget, "button-release-event",
+ G_CALLBACK(button_release_event_cb), nullptr);
+ g_signal_connect(eventWidget, "scroll-event", G_CALLBACK(scroll_event_cb),
+ nullptr);
+ if (gtk_check_version(3, 18, 0) == nullptr) {
+ g_signal_connect(eventWidget, "event", G_CALLBACK(generic_event_cb),
+ nullptr);
+ }
+ g_signal_connect(eventWidget, "touch-event", G_CALLBACK(touch_event_cb),
+ nullptr);
+
+ LOG(" nsWindow type %d %s\n", int(mWindowType),
+ mIsPIPWindow ? "PIP window" : "");
+ LOG(" mShell %p mContainer %p mGdkWindow %p XID 0x%lx\n", mShell, mContainer,
+ mGdkWindow, GetX11Window());
+
+ // Set default application name when it's empty.
+ if (mGtkWindowAppName.IsEmpty()) {
+ mGtkWindowAppName = gAppData->name;
+ }
+
+ return NS_OK;
+}
+
+void nsWindow::RefreshWindowClass(void) {
+ GdkWindow* gdkWindow = gtk_widget_get_window(mShell);
+ if (!gdkWindow) {
+ return;
+ }
+
+ if (!mGtkWindowRoleName.IsEmpty()) {
+ gdk_window_set_role(gdkWindow, mGtkWindowRoleName.get());
+ }
+
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ XClassHint* class_hint = XAllocClassHint();
+ if (!class_hint) return;
+
+ const char* res_name =
+ !mGtkWindowAppName.IsEmpty() ? mGtkWindowAppName.get() : gAppData->name;
+
+ const char* res_class = !mGtkWindowAppClass.IsEmpty()
+ ? mGtkWindowAppClass.get()
+ : gdk_get_program_class();
+
+ if (!res_name || !res_class) {
+ XFree(class_hint);
+ return;
+ }
+
+ class_hint->res_name = const_cast<char*>(res_name);
+ class_hint->res_class = const_cast<char*>(res_class);
+
+ // Can't use gtk_window_set_wmclass() for this; it prints
+ // a warning & refuses to make the change.
+ GdkDisplay* display = gdk_display_get_default();
+ XSetClassHint(GDK_DISPLAY_XDISPLAY(display),
+ gdk_x11_window_get_xid(gdkWindow), class_hint);
+ XFree(class_hint);
+ }
+#endif /* MOZ_X11 */
+}
+
+void nsWindow::SetWindowClass(const nsAString& xulWinType,
+ const nsAString& xulWinClass,
+ const nsAString& xulWinName) {
+ if (!mShell) return;
+
+ // If window type attribute is set, parse it into name and role
+ if (!xulWinType.IsEmpty()) {
+ char* res_name = ToNewCString(xulWinType, mozilla::fallible);
+ const char* role = nullptr;
+
+ if (res_name) {
+ // Parse res_name into a name and role. Characters other than
+ // [A-Za-z0-9_-] are converted to '_'. Anything after the first
+ // colon is assigned to role; if there's no colon, assign the
+ // whole thing to both role and res_name.
+ for (char* c = res_name; *c; c++) {
+ if (':' == *c) {
+ *c = 0;
+ role = c + 1;
+ } else if (!isascii(*c) ||
+ (!isalnum(*c) && ('_' != *c) && ('-' != *c))) {
+ *c = '_';
+ }
+ }
+ res_name[0] = (char)toupper(res_name[0]);
+ if (!role) role = res_name;
+
+ mGtkWindowAppName = res_name;
+ mGtkWindowRoleName = role;
+ free(res_name);
+ }
+ }
+
+ // If window class attribute is set, store it as app class
+ // If this attribute is not set, reset app class to default
+ if (!xulWinClass.IsEmpty()) {
+ CopyUTF16toUTF8(xulWinClass, mGtkWindowAppClass);
+ } else {
+ mGtkWindowAppClass = nullptr;
+ }
+
+ // If window class attribute is set, store it as app name
+ // If both name and type are not set, reset app name to default
+ if (!xulWinName.IsEmpty()) {
+ CopyUTF16toUTF8(xulWinName, mGtkWindowAppName);
+ } else if (xulWinType.IsEmpty()) {
+ mGtkWindowAppClass = nullptr;
+ }
+
+ RefreshWindowClass();
+}
+
+nsAutoCString nsWindow::GetDebugTag() const {
+ nsAutoCString tag;
+ tag.AppendPrintf("[%p]", this);
+ return tag;
+}
+
+void nsWindow::NativeMoveResize(bool aMoved, bool aResized) {
+ GdkPoint topLeft = [&] {
+ auto target = mBounds.TopLeft();
+ // gtk_window_move will undo the csd offset, but nothing else, so only add
+ // the client offset if drawing to the csd titlebar.
+ if (DrawsToCSDTitlebar()) {
+ target += mClientOffset;
+ }
+ return DevicePixelsToGdkPointRoundDown(target);
+ }();
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mLastSizeRequest);
+
+ LOG("nsWindow::NativeMoveResize move %d resize %d to %d,%d -> %d x %d\n",
+ aMoved, aResized, topLeft.x, topLeft.y, size.width, size.height);
+
+ if (aResized && !AreBoundsSane()) {
+ LOG(" bounds are insane, hidding the window");
+ // We have been resized but to incorrect size.
+ // If someone has set this so that the needs show flag is false
+ // and it needs to be hidden, update the flag and hide the
+ // window. This flag will be cleared the next time someone
+ // hides the window or shows it. It also prevents us from
+ // calling NativeShow(false) excessively on the window which
+ // causes unneeded X traffic.
+ if (!mNeedsShow && mIsShown) {
+ mNeedsShow = true;
+ NativeShow(false);
+ }
+ if (aMoved) {
+ LOG(" moving to %d x %d", topLeft.x, topLeft.y);
+ gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y);
+ }
+ return;
+ }
+
+ // Set position to hidden window on X11 may fail, so save the position
+ // and move it when it's shown.
+ if (aMoved && GdkIsX11Display() && IsPopup() &&
+ !gtk_widget_get_visible(GTK_WIDGET(mShell))) {
+ LOG(" store position of hidden popup window");
+ mHiddenPopupPositioned = true;
+ mPopupPosition = {topLeft.x, topLeft.y};
+ }
+
+ if (IsWaylandPopup()) {
+ NativeMoveResizeWaylandPopup(aMoved, aResized);
+ } else {
+ // x and y give the position of the window manager frame top-left.
+ if (aMoved) {
+ gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y);
+ }
+ if (aResized) {
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ if (mIsDragPopup) {
+ // DND window is placed inside container so we need to make hard size
+ // request to ensure parent container is resized too.
+ gtk_widget_set_size_request(GTK_WIDGET(mShell), size.width,
+ size.height);
+ }
+ }
+ }
+
+ if (aResized) {
+ // Recompute the input region, in case the window grew or shrunk.
+ SetInputRegion(mInputRegion);
+ }
+
+ // Does it need to be shown because bounds were previously insane?
+ if (mNeedsShow && mIsShown && aResized) {
+ NativeShow(true);
+ }
+}
+
+// We pause compositor to avoid rendering of obsoleted remote content which
+// produces flickering.
+// Re-enable compositor again when remote content is updated or
+// timeout happens.
+
+// Define maximal compositor pause when it's paused to avoid flickering,
+// in milliseconds.
+#define COMPOSITOR_PAUSE_TIMEOUT (1000)
+
+void nsWindow::PauseCompositorFlickering() {
+ bool pauseCompositor = (mWindowType == WindowType::TopLevel) &&
+ mCompositorState == COMPOSITOR_ENABLED &&
+ mCompositorWidgetDelegate && !mIsDestroyed;
+ if (!pauseCompositor) {
+ return;
+ }
+
+ LOG("nsWindow::PauseCompositorFlickering()");
+
+ MozClearHandleID(mCompositorPauseTimeoutID, g_source_remove);
+
+ CompositorBridgeChild* remoteRenderer = GetRemoteRenderer();
+ if (remoteRenderer) {
+ remoteRenderer->SendPause();
+ mCompositorState = COMPOSITOR_PAUSED_FLICKERING;
+ mCompositorPauseTimeoutID = (int)g_timeout_add(
+ COMPOSITOR_PAUSE_TIMEOUT,
+ [](void* data) -> gint {
+ nsWindow* window = static_cast<nsWindow*>(data);
+ if (!window->IsDestroyed()) {
+ window->ResumeCompositorFlickering();
+ }
+ return true;
+ },
+ this);
+ }
+}
+
+bool nsWindow::IsWaitingForCompositorResume() {
+ return mCompositorState == COMPOSITOR_PAUSED_FLICKERING;
+}
+
+void nsWindow::ResumeCompositorFlickering() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ LOG("nsWindow::ResumeCompositorFlickering()\n");
+
+ if (mIsDestroyed || !IsWaitingForCompositorResume()) {
+ LOG(" early quit\n");
+ return;
+ }
+
+ MozClearHandleID(mCompositorPauseTimeoutID, g_source_remove);
+
+ // mCompositorWidgetDelegate can be deleted during timeout.
+ // In such case just flip compositor back to enabled and let
+ // SetCompositorWidgetDelegate() or Map event resume it.
+ if (!mCompositorWidgetDelegate) {
+ mCompositorState = COMPOSITOR_ENABLED;
+ return;
+ }
+
+ ResumeCompositorImpl();
+}
+
+void nsWindow::ResumeCompositorFromCompositorThread() {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod("nsWindow::ResumeCompositorFlickering", this,
+ &nsWindow::ResumeCompositorFlickering);
+ NS_DispatchToMainThread(event.forget());
+}
+
+void nsWindow::ResumeCompositorImpl() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ LOG("nsWindow::ResumeCompositorImpl()\n");
+
+ MOZ_DIAGNOSTIC_ASSERT(mCompositorWidgetDelegate);
+ mCompositorWidgetDelegate->EnableRendering(GetX11Window(), GetShapedState());
+
+ // As WaylandStartVsync needs mCompositorWidgetDelegate this is the right
+ // time to start it.
+ WaylandStartVsync();
+
+ CompositorBridgeChild* remoteRenderer = GetRemoteRenderer();
+ MOZ_RELEASE_ASSERT(remoteRenderer);
+ remoteRenderer->SendResume();
+ remoteRenderer->SendForcePresent(wr::RenderReasons::WIDGET);
+ mCompositorState = COMPOSITOR_ENABLED;
+}
+
+void nsWindow::WaylandStartVsync() {
+#ifdef MOZ_WAYLAND
+ if (!mWaylandVsyncSource) {
+ return;
+ }
+
+ LOG_VSYNC("nsWindow::WaylandStartVsync");
+
+ MOZ_DIAGNOSTIC_ASSERT(mCompositorWidgetDelegate);
+ if (mCompositorWidgetDelegate->AsGtkCompositorWidget() &&
+ mCompositorWidgetDelegate->AsGtkCompositorWidget()
+ ->GetNativeLayerRoot()) {
+ LOG_VSYNC(" use source NativeLayerRootWayland");
+ mWaylandVsyncSource->MaybeUpdateSource(
+ mCompositorWidgetDelegate->AsGtkCompositorWidget()
+ ->GetNativeLayerRoot()
+ ->AsNativeLayerRootWayland());
+ } else {
+ LOG_VSYNC(" use source mContainer");
+ mWaylandVsyncSource->MaybeUpdateSource(mContainer);
+ }
+
+ mWaylandVsyncSource->EnableMonitor();
+#endif
+}
+
+void nsWindow::WaylandStopVsync() {
+#ifdef MOZ_WAYLAND
+ if (!mWaylandVsyncSource) {
+ return;
+ }
+
+ LOG_VSYNC("nsWindow::WaylandStopVsync");
+
+ // The widget is going to be hidden, so clear the surface of our
+ // vsync source.
+ mWaylandVsyncSource->DisableMonitor();
+ mWaylandVsyncSource->MaybeUpdateSource(nullptr);
+#endif
+}
+
+void nsWindow::NativeShow(bool aAction) {
+ if (aAction) {
+ // unset our flag now that our window has been shown
+ mNeedsShow = true;
+ auto removeShow = MakeScopeExit([&] { mNeedsShow = false; });
+
+ LOG("nsWindow::NativeShow show\n");
+
+ if (IsWaylandPopup()) {
+ mPopupClosed = false;
+ if (WaylandPopupConfigure()) {
+ AddWindowToPopupHierarchy();
+ UpdateWaylandPopupHierarchy();
+ if (mPopupClosed) {
+ return;
+ }
+ }
+ }
+ // Set up usertime/startupID metadata for the created window.
+ // On X11 we use gtk_window_set_startup_id() so we need to call it
+ // before show.
+ if (GdkIsX11Display()) {
+ SetUserTimeAndStartupTokenForActivatedWindow();
+ }
+ if (GdkIsWaylandDisplay()) {
+ if (IsWaylandPopup()) {
+ ShowWaylandPopupWindow();
+ } else {
+ ShowWaylandToplevelWindow();
+ }
+ } else {
+ LOG(" calling gtk_widget_show(mShell)\n");
+ gtk_widget_show(mShell);
+ }
+ if (GdkIsWaylandDisplay()) {
+ SetUserTimeAndStartupTokenForActivatedWindow();
+#ifdef MOZ_WAYLAND
+ auto token = std::move(mWindowActivationTokenFromEnv);
+ if (!token.IsEmpty()) {
+ FocusWaylandWindow(token.get());
+ }
+#endif
+ }
+ if (mHiddenPopupPositioned && IsPopup()) {
+ LOG(" re-position hidden popup window");
+ gtk_window_move(GTK_WINDOW(mShell), mPopupPosition.x, mPopupPosition.y);
+ mHiddenPopupPositioned = false;
+ }
+ } else {
+ LOG("nsWindow::NativeShow hide\n");
+ if (GdkIsWaylandDisplay()) {
+ if (IsWaylandPopup()) {
+ // We can't close tracked popups directly as they may have visible
+ // child popups. Just mark is as closed and let
+ // UpdateWaylandPopupHierarchy() do the job.
+ if (IsInPopupHierarchy()) {
+ WaylandPopupMarkAsClosed();
+ UpdateWaylandPopupHierarchy();
+ } else {
+ // Close untracked popups directly.
+ HideWaylandPopupWindow(/* aTemporaryHide */ false,
+ /* aRemoveFromPopupList */ true);
+ }
+ } else {
+ HideWaylandToplevelWindow();
+ }
+ } else {
+ // Workaround window freezes on GTK versions before 3.21.2 by
+ // ensuring that configure events get dispatched to windows before
+ // they are unmapped. See bug 1225044.
+ if (gtk_check_version(3, 21, 2) != nullptr && mPendingConfigures > 0) {
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(GTK_WIDGET(mShell), &allocation);
+
+ GdkEventConfigure event;
+ PodZero(&event);
+ event.type = GDK_CONFIGURE;
+ event.window = mGdkWindow;
+ event.send_event = TRUE;
+ event.x = allocation.x;
+ event.y = allocation.y;
+ event.width = allocation.width;
+ event.height = allocation.height;
+
+ auto* shellClass = GTK_WIDGET_GET_CLASS(mShell);
+ for (unsigned int i = 0; i < mPendingConfigures; i++) {
+ Unused << shellClass->configure_event(mShell, &event);
+ }
+ mPendingConfigures = 0;
+ }
+ gtk_widget_hide(mShell);
+
+ ClearTransparencyBitmap(); // Release some resources
+ }
+ }
+}
+
+void nsWindow::SetHasMappedToplevel(bool aState) {
+ LOG("nsWindow::SetHasMappedToplevel(%d)", aState);
+ if (aState == mHasMappedToplevel) {
+ return;
+ }
+ // Even when aState == mHasMappedToplevel (as when this method is called
+ // from Show()), child windows need to have their state checked, so don't
+ // return early.
+ mHasMappedToplevel = aState;
+ if (aState && mNeedsToRetryCapturingMouse) {
+ CaptureRollupEvents(true);
+ MOZ_ASSERT(!mNeedsToRetryCapturingMouse);
+ }
+}
+
+LayoutDeviceIntSize nsWindow::GetSafeWindowSize(LayoutDeviceIntSize aSize) {
+ // The X protocol uses CARD32 for window sizes, but the server (1.11.3)
+ // reads it as CARD16. Sizes of pixmaps, used for drawing, are (unsigned)
+ // CARD16 in the protocol, but the server's ProcCreatePixmap returns
+ // BadAlloc if dimensions cannot be represented by signed shorts.
+ // Because we are creating Cairo surfaces to represent window buffers,
+ // we also must ensure that the window can fit in a Cairo surface.
+ LayoutDeviceIntSize result = aSize;
+ int32_t maxSize = 32767;
+ if (mWindowRenderer && mWindowRenderer->AsKnowsCompositor()) {
+ maxSize = std::min(
+ maxSize, mWindowRenderer->AsKnowsCompositor()->GetMaxTextureSize());
+ }
+ if (result.width > maxSize) {
+ result.width = maxSize;
+ }
+ if (result.height > maxSize) {
+ result.height = maxSize;
+ }
+ return result;
+}
+
+void nsWindow::SetTransparencyMode(TransparencyMode aMode) {
+ bool isTransparent = aMode == TransparencyMode::Transparent;
+
+ if (mIsTransparent == isTransparent) {
+ return;
+ }
+
+ if (mWindowType != WindowType::Popup) {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1344839 reported
+ // problems cleaning the layer manager for toplevel windows.
+ // Ignore the request so as to workaround that.
+ // mIsTransparent is set in Create() if transparency may be required.
+ if (isTransparent) {
+ NS_WARNING("Transparent mode not supported on non-popup windows.");
+ }
+ return;
+ }
+
+ if (!isTransparent) {
+ ClearTransparencyBitmap();
+ } // else the new default alpha values are "all 1", so we don't
+ // need to change anything yet
+
+ mIsTransparent = isTransparent;
+
+ if (!mHasAlphaVisual) {
+ // The choice of layer manager depends on
+ // GtkCompositorWidgetInitData::Shaped(), which will need to change, so
+ // clean out the old layer manager.
+ DestroyLayerManager();
+ }
+}
+
+TransparencyMode nsWindow::GetTransparencyMode() {
+ return mIsTransparent ? TransparencyMode::Transparent
+ : TransparencyMode::Opaque;
+}
+
+gint nsWindow::GetInputRegionMarginInGdkCoords() {
+ return DevicePixelsToGdkCoordRoundDown(mInputRegion.mMargin);
+}
+
+void nsWindow::SetInputRegion(const InputRegion& aInputRegion) {
+ mInputRegion = aInputRegion;
+
+ GdkWindow* window =
+ mDrawToContainer ? gtk_widget_get_window(mShell) : mGdkWindow;
+ if (!window) {
+ return;
+ }
+
+ LOG("nsWindow::SetInputRegion(%d, %d)", aInputRegion.mFullyTransparent,
+ int(aInputRegion.mMargin));
+
+ cairo_rectangle_int_t rect = {0, 0, 0, 0};
+ cairo_region_t* region = nullptr;
+ auto releaseRegion = MakeScopeExit([&] {
+ if (region) {
+ cairo_region_destroy(region);
+ }
+ });
+
+ if (aInputRegion.mFullyTransparent) {
+ region = cairo_region_create_rectangle(&rect);
+ } else if (aInputRegion.mMargin != 0) {
+ LayoutDeviceIntRect inputRegion(LayoutDeviceIntPoint(), mLastSizeRequest);
+ inputRegion.Deflate(aInputRegion.mMargin);
+ GdkRectangle gdkRect = DevicePixelsToGdkRectRoundOut(inputRegion);
+ rect = {gdkRect.x, gdkRect.y, gdkRect.width, gdkRect.height};
+ region = cairo_region_create_rectangle(&rect);
+ }
+
+ gdk_window_input_shape_combine_region(window, region, 0, 0);
+
+ // On Wayland gdk_window_input_shape_combine_region() call is cached and
+ // applied to underlying wl_surface when GdkWindow is repainted.
+ // Force repaint of GdkWindow to apply the change immediately.
+ if (GdkIsWaylandDisplay()) {
+ gdk_window_invalidate_rect(window, nullptr, false);
+ }
+}
+
+// For setting the draggable titlebar region from CSS
+// with -moz-window-dragging: drag.
+void nsWindow::UpdateWindowDraggingRegion(
+ const LayoutDeviceIntRegion& aRegion) {
+ if (mDraggableRegion != aRegion) {
+ mDraggableRegion = aRegion;
+ }
+}
+
+LayoutDeviceIntCoord nsWindow::GetTitlebarRadius() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ int32_t cssCoord = LookAndFeel::GetInt(LookAndFeel::IntID::TitlebarRadius);
+ return GdkCoordToDevicePixels(cssCoord);
+}
+
+// See subtract_corners_from_region() at gtk/gtkwindow.c
+// We need to subtract corners from toplevel window opaque region
+// to draw transparent corners of default Gtk titlebar.
+// Both implementations (cairo_region_t and wl_region) needs to be synced.
+static void SubtractTitlebarCorners(cairo_region_t* aRegion, int aX, int aY,
+ int aWindowWidth, int aTitlebarRadius) {
+ if (!aTitlebarRadius) {
+ return;
+ }
+ cairo_rectangle_int_t rect = {aX, aY, aTitlebarRadius, aTitlebarRadius};
+ cairo_region_subtract_rectangle(aRegion, &rect);
+ rect = {
+ aX + aWindowWidth - aTitlebarRadius,
+ aY,
+ aTitlebarRadius,
+ aTitlebarRadius,
+ };
+ cairo_region_subtract_rectangle(aRegion, &rect);
+}
+
+void nsWindow::UpdateTopLevelOpaqueRegion(void) {
+ if (!mCompositedScreen) {
+ return;
+ }
+
+ GdkWindow* window =
+ mDrawToContainer ? gtk_widget_get_window(mShell) : mGdkWindow;
+ if (!window) {
+ return;
+ }
+ MOZ_ASSERT(gdk_window_get_window_type(window) == GDK_WINDOW_TOPLEVEL);
+
+ int x = 0;
+ int y = 0;
+
+ if (mDrawToContainer) {
+ gdk_window_get_position(mGdkWindow, &x, &y);
+ }
+
+ int width = DevicePixelsToGdkCoordRoundDown(mBounds.width);
+ int height = DevicePixelsToGdkCoordRoundDown(mBounds.height);
+
+ cairo_region_t* region = cairo_region_create();
+ cairo_rectangle_int_t rect = {x, y, width, height};
+ cairo_region_union_rectangle(region, &rect);
+
+ int radius = DoDrawTilebarCorners() ? int(GetTitlebarRadius()) : 0;
+ SubtractTitlebarCorners(region, x, y, width, radius);
+
+ gdk_window_set_opaque_region(window, region);
+
+ cairo_region_destroy(region);
+
+#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay()) {
+ moz_container_wayland_update_opaque_region(mContainer, radius);
+ }
+#endif
+}
+
+bool nsWindow::IsChromeWindowTitlebar() {
+ return mDrawInTitlebar && !mIsPIPWindow &&
+ mWindowType == WindowType::TopLevel;
+}
+
+bool nsWindow::DoDrawTilebarCorners() {
+ return IsChromeWindowTitlebar() && mSizeMode == nsSizeMode_Normal &&
+ !mIsTiled;
+}
+
+void nsWindow::ResizeTransparencyBitmap() {
+ if (!mTransparencyBitmap) return;
+
+ if (mBounds.width == mTransparencyBitmapWidth &&
+ mBounds.height == mTransparencyBitmapHeight) {
+ return;
+ }
+
+ int32_t newRowBytes = GetBitmapStride(mBounds.width);
+ int32_t newSize = newRowBytes * mBounds.height;
+ auto* newBits = new gchar[newSize];
+ // fill new mask with "transparent", first
+ memset(newBits, 0, newSize);
+
+ // Now copy the intersection of the old and new areas into the new mask
+ int32_t copyWidth = std::min(mBounds.width, mTransparencyBitmapWidth);
+ int32_t copyHeight = std::min(mBounds.height, mTransparencyBitmapHeight);
+ int32_t oldRowBytes = GetBitmapStride(mTransparencyBitmapWidth);
+ int32_t copyBytes = GetBitmapStride(copyWidth);
+
+ int32_t i;
+ gchar* fromPtr = mTransparencyBitmap;
+ gchar* toPtr = newBits;
+ for (i = 0; i < copyHeight; i++) {
+ memcpy(toPtr, fromPtr, copyBytes);
+ fromPtr += oldRowBytes;
+ toPtr += newRowBytes;
+ }
+
+ delete[] mTransparencyBitmap;
+ mTransparencyBitmap = newBits;
+ mTransparencyBitmapWidth = mBounds.width;
+ mTransparencyBitmapHeight = mBounds.height;
+}
+
+static bool ChangedMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
+ int32_t aMaskHeight, const nsIntRect& aRect,
+ uint8_t* aAlphas, int32_t aStride) {
+ int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
+ int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
+ for (y = aRect.y; y < yMax; y++) {
+ gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
+ uint8_t* alphas = aAlphas;
+ for (x = aRect.x; x < xMax; x++) {
+ bool newBit = *alphas > 0x7f;
+ alphas++;
+
+ gchar maskByte = maskBytes[x >> 3];
+ bool maskBit = (maskByte & (1 << (x & 7))) != 0;
+
+ if (maskBit != newBit) {
+ return true;
+ }
+ }
+ aAlphas += aStride;
+ }
+
+ return false;
+}
+
+static void UpdateMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
+ int32_t aMaskHeight, const nsIntRect& aRect,
+ uint8_t* aAlphas, int32_t aStride) {
+ int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
+ int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
+ for (y = aRect.y; y < yMax; y++) {
+ gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
+ uint8_t* alphas = aAlphas;
+ for (x = aRect.x; x < xMax; x++) {
+ bool newBit = *alphas > 0x7f;
+ alphas++;
+
+ gchar mask = 1 << (x & 7);
+ gchar maskByte = maskBytes[x >> 3];
+ // Note: '-newBit' turns 0 into 00...00 and 1 into 11...11
+ maskBytes[x >> 3] = (maskByte & ~mask) | (-newBit & mask);
+ }
+ aAlphas += aStride;
+ }
+}
+
+void nsWindow::ApplyTransparencyBitmap() {
+#ifdef MOZ_X11
+ // We use X11 calls where possible, because GDK handles expose events
+ // for shaped windows in a way that's incompatible with us (Bug 635903).
+ // It doesn't occur when the shapes are set through X.
+ Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
+ Window xDrawable = GDK_WINDOW_XID(mGdkWindow);
+ Pixmap maskPixmap = XCreateBitmapFromData(
+ xDisplay, xDrawable, mTransparencyBitmap, mTransparencyBitmapWidth,
+ mTransparencyBitmapHeight);
+ XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
+ ShapeSet);
+ XFreePixmap(xDisplay, maskPixmap);
+#endif // MOZ_X11
+}
+
+void nsWindow::ClearTransparencyBitmap() {
+ if (!mTransparencyBitmap) return;
+
+ delete[] mTransparencyBitmap;
+ mTransparencyBitmap = nullptr;
+ mTransparencyBitmapWidth = 0;
+ mTransparencyBitmapHeight = 0;
+
+ if (!mShell) return;
+
+#ifdef MOZ_X11
+ if (MOZ_UNLIKELY(!mGdkWindow)) {
+ return;
+ }
+
+ Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
+ Window xWindow = gdk_x11_window_get_xid(mGdkWindow);
+
+ XShapeCombineMask(xDisplay, xWindow, ShapeBounding, 0, 0, X11None, ShapeSet);
+#endif
+}
+
+nsresult nsWindow::UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
+ uint8_t* aAlphas,
+ int32_t aStride) {
+ NS_ASSERTION(mIsTransparent, "Window is not transparent");
+ NS_ASSERTION(!mTransparencyBitmapForTitlebar,
+ "Transparency bitmap is already used for titlebar rendering");
+
+ if (mTransparencyBitmap == nullptr) {
+ int32_t size = GetBitmapStride(mBounds.width) * mBounds.height;
+ mTransparencyBitmap = new gchar[size];
+ memset(mTransparencyBitmap, 255, size);
+ mTransparencyBitmapWidth = mBounds.width;
+ mTransparencyBitmapHeight = mBounds.height;
+ } else {
+ ResizeTransparencyBitmap();
+ }
+
+ nsIntRect rect;
+ rect.IntersectRect(aRect, nsIntRect(0, 0, mBounds.width, mBounds.height));
+
+ if (!ChangedMaskBits(mTransparencyBitmap, mBounds.width, mBounds.height, rect,
+ aAlphas, aStride)) {
+ // skip the expensive stuff if the mask bits haven't changed; hopefully
+ // this is the common case
+ return NS_OK;
+ }
+
+ UpdateMaskBits(mTransparencyBitmap, mBounds.width, mBounds.height, rect,
+ aAlphas, aStride);
+
+ if (!mNeedsShow) {
+ ApplyTransparencyBitmap();
+ }
+ return NS_OK;
+}
+
+#define TITLEBAR_HEIGHT 10
+
+LayoutDeviceIntRect nsWindow::GetTitlebarRect() {
+ if (!mGdkWindow || !mDrawInTitlebar) {
+ return LayoutDeviceIntRect();
+ }
+
+ int height = 0;
+ if (DoDrawTilebarCorners()) {
+ height = GdkCeiledScaleFactor() * TITLEBAR_HEIGHT;
+ }
+ return LayoutDeviceIntRect(0, 0, mBounds.width, height);
+}
+
+void nsWindow::UpdateTitlebarTransparencyBitmap() {
+ NS_ASSERTION(mTransparencyBitmapForTitlebar,
+ "Transparency bitmap is already used to draw window shape");
+
+ if (!mGdkWindow || !mDrawInTitlebar ||
+ (mBounds.width == mTransparencyBitmapWidth &&
+ mBounds.height == mTransparencyBitmapHeight)) {
+ return;
+ }
+
+ bool maskCreate =
+ !mTransparencyBitmap || mBounds.width > mTransparencyBitmapWidth;
+
+ bool maskUpdate =
+ !mTransparencyBitmap || mBounds.width != mTransparencyBitmapWidth;
+
+ LayoutDeviceIntCoord radius = GetTitlebarRadius();
+ if (maskCreate) {
+ delete[] mTransparencyBitmap;
+ int32_t size = GetBitmapStride(mBounds.width) * radius;
+ mTransparencyBitmap = new gchar[size];
+ mTransparencyBitmapWidth = mBounds.width;
+ } else {
+ mTransparencyBitmapWidth = mBounds.width;
+ }
+ mTransparencyBitmapHeight = mBounds.height;
+
+ if (maskUpdate) {
+ cairo_surface_t* surface = cairo_image_surface_create(
+ CAIRO_FORMAT_A8, mTransparencyBitmapWidth, radius);
+ if (!surface) return;
+
+ cairo_t* cr = cairo_create(surface);
+
+ GtkWidgetState state;
+ memset((void*)&state, 0, sizeof(state));
+ GdkRectangle rect = {0, 0, mTransparencyBitmapWidth, radius};
+
+ moz_gtk_widget_paint(MOZ_GTK_HEADER_BAR, cr, &rect, &state, 0,
+ GTK_TEXT_DIR_NONE);
+
+ cairo_destroy(cr);
+ cairo_surface_mark_dirty(surface);
+ cairo_surface_flush(surface);
+
+ UpdateMaskBits(mTransparencyBitmap, mTransparencyBitmapWidth, radius,
+ nsIntRect(0, 0, mTransparencyBitmapWidth, radius),
+ cairo_image_surface_get_data(surface),
+ cairo_format_stride_for_width(CAIRO_FORMAT_A8,
+ mTransparencyBitmapWidth));
+
+ cairo_surface_destroy(surface);
+ }
+
+#ifdef MOZ_X11
+ if (!mNeedsShow) {
+ Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
+ Window xDrawable = GDK_WINDOW_XID(mGdkWindow);
+
+ Pixmap maskPixmap =
+ XCreateBitmapFromData(xDisplay, xDrawable, mTransparencyBitmap,
+ mTransparencyBitmapWidth, radius);
+
+ XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
+ ShapeSet);
+
+ if (mTransparencyBitmapHeight > radius) {
+ XRectangle rect = {0, 0, (unsigned short)mTransparencyBitmapWidth,
+ (unsigned short)(mTransparencyBitmapHeight - radius)};
+ XShapeCombineRectangles(xDisplay, xDrawable, ShapeBounding, 0, radius,
+ &rect, 1, ShapeUnion, 0);
+ }
+
+ XFreePixmap(xDisplay, maskPixmap);
+ }
+#endif
+}
+
+GtkWidget* nsWindow::GetToplevelWidget() const { return mShell; }
+
+GdkWindow* nsWindow::GetToplevelGdkWindow() const {
+ return gtk_widget_get_window(mShell);
+}
+
+nsWindow* nsWindow::GetContainerWindow() const {
+ GtkWidget* owningWidget = GTK_WIDGET(mContainer);
+ if (!owningWidget) {
+ return nullptr;
+ }
+
+ nsWindow* window = get_window_for_gtk_widget(owningWidget);
+ NS_ASSERTION(window, "No nsWindow for container widget");
+ return window;
+}
+
+void nsWindow::SetUrgencyHint(GtkWidget* top_window, bool state) {
+ LOG(" nsWindow::SetUrgencyHint widget %p\n", top_window);
+
+ if (!top_window) return;
+
+ // TODO: Use xdg-activation on Wayland?
+ gdk_window_set_urgency_hint(gtk_widget_get_window(top_window), state);
+}
+
+void nsWindow::SetDefaultIcon(void) { SetIcon(u"default"_ns); }
+
+gint nsWindow::ConvertBorderStyles(BorderStyle aStyle) {
+ gint w = 0;
+
+ if (aStyle == BorderStyle::Default) return -1;
+
+ // note that we don't handle BorderStyle::Close yet
+ if (aStyle & BorderStyle::All) w |= GDK_DECOR_ALL;
+ if (aStyle & BorderStyle::Border) w |= GDK_DECOR_BORDER;
+ if (aStyle & BorderStyle::ResizeH) w |= GDK_DECOR_RESIZEH;
+ if (aStyle & BorderStyle::Title) w |= GDK_DECOR_TITLE;
+ if (aStyle & BorderStyle::Menu) w |= GDK_DECOR_MENU;
+ if (aStyle & BorderStyle::Minimize) w |= GDK_DECOR_MINIMIZE;
+ if (aStyle & BorderStyle::Maximize) w |= GDK_DECOR_MAXIMIZE;
+
+ return w;
+}
+
+class FullscreenTransitionWindow final : public nsISupports {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit FullscreenTransitionWindow(GtkWidget* aWidget);
+
+ GtkWidget* mWindow;
+
+ private:
+ ~FullscreenTransitionWindow();
+};
+
+NS_IMPL_ISUPPORTS0(FullscreenTransitionWindow)
+
+FullscreenTransitionWindow::FullscreenTransitionWindow(GtkWidget* aWidget) {
+ mWindow = gtk_window_new(GTK_WINDOW_POPUP);
+ GtkWindow* gtkWin = GTK_WINDOW(mWindow);
+
+ gtk_window_set_type_hint(gtkWin, GDK_WINDOW_TYPE_HINT_SPLASHSCREEN);
+ GtkWindowSetTransientFor(gtkWin, GTK_WINDOW(aWidget));
+ gtk_window_set_decorated(gtkWin, false);
+
+ GdkWindow* gdkWin = gtk_widget_get_window(aWidget);
+ GdkScreen* screen = gtk_widget_get_screen(aWidget);
+ gint monitorNum = gdk_screen_get_monitor_at_window(screen, gdkWin);
+ GdkRectangle monitorRect;
+ gdk_screen_get_monitor_geometry(screen, monitorNum, &monitorRect);
+ gtk_window_set_screen(gtkWin, screen);
+ gtk_window_move(gtkWin, monitorRect.x, monitorRect.y);
+ MOZ_ASSERT(monitorRect.width > 0 && monitorRect.height > 0,
+ "Can't resize window smaller than 1x1.");
+ gtk_window_resize(gtkWin, monitorRect.width, monitorRect.height);
+
+ GdkRGBA bgColor;
+ bgColor.red = bgColor.green = bgColor.blue = 0.0;
+ bgColor.alpha = 1.0;
+ gtk_widget_override_background_color(mWindow, GTK_STATE_FLAG_NORMAL,
+ &bgColor);
+
+ gtk_widget_set_opacity(mWindow, 0.0);
+ gtk_widget_show(mWindow);
+}
+
+FullscreenTransitionWindow::~FullscreenTransitionWindow() {
+ gtk_widget_destroy(mWindow);
+}
+
+class FullscreenTransitionData {
+ public:
+ FullscreenTransitionData(nsIWidget::FullscreenTransitionStage aStage,
+ uint16_t aDuration, nsIRunnable* aCallback,
+ FullscreenTransitionWindow* aWindow)
+ : mStage(aStage),
+ mStartTime(TimeStamp::Now()),
+ mDuration(TimeDuration::FromMilliseconds(aDuration)),
+ mCallback(aCallback),
+ mWindow(aWindow) {}
+
+ static const guint sInterval = 1000 / 30; // 30fps
+ static gboolean TimeoutCallback(gpointer aData);
+
+ private:
+ nsIWidget::FullscreenTransitionStage mStage;
+ TimeStamp mStartTime;
+ TimeDuration mDuration;
+ nsCOMPtr<nsIRunnable> mCallback;
+ RefPtr<FullscreenTransitionWindow> mWindow;
+};
+
+/* static */
+gboolean FullscreenTransitionData::TimeoutCallback(gpointer aData) {
+ bool finishing = false;
+ auto* data = static_cast<FullscreenTransitionData*>(aData);
+ gdouble opacity = (TimeStamp::Now() - data->mStartTime) / data->mDuration;
+ if (opacity >= 1.0) {
+ opacity = 1.0;
+ finishing = true;
+ }
+ if (data->mStage == nsIWidget::eAfterFullscreenToggle) {
+ opacity = 1.0 - opacity;
+ }
+ gtk_widget_set_opacity(data->mWindow->mWindow, opacity);
+
+ if (!finishing) {
+ return TRUE;
+ }
+ NS_DispatchToMainThread(data->mCallback.forget());
+ delete data;
+ return FALSE;
+}
+
+/* virtual */
+bool nsWindow::PrepareForFullscreenTransition(nsISupports** aData) {
+ if (!mCompositedScreen) {
+ return false;
+ }
+ *aData = do_AddRef(new FullscreenTransitionWindow(mShell)).take();
+ return true;
+}
+
+/* virtual */
+void nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback) {
+ auto* data = static_cast<FullscreenTransitionWindow*>(aData);
+ // This will be released at the end of the last timeout callback for it.
+ auto* transitionData =
+ new FullscreenTransitionData(aStage, aDuration, aCallback, data);
+ g_timeout_add_full(G_PRIORITY_HIGH, FullscreenTransitionData::sInterval,
+ FullscreenTransitionData::TimeoutCallback, transitionData,
+ nullptr);
+}
+
+already_AddRefed<widget::Screen> nsWindow::GetWidgetScreen() {
+ // Wayland can read screen directly
+ if (GdkIsWaylandDisplay()) {
+ if (RefPtr<Screen> screen = ScreenHelperGTK::GetScreenForWindow(this)) {
+ return screen.forget();
+ }
+ }
+
+ // GetScreenBounds() is slow for the GTK port so we override and use
+ // mBounds directly.
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ LayoutDeviceIntRect bounds = mBounds;
+ DesktopIntRect deskBounds = RoundedToInt(bounds / GetDesktopToDeviceScale());
+ return screenManager.ScreenForRect(deskBounds);
+}
+
+RefPtr<VsyncDispatcher> nsWindow::GetVsyncDispatcher() {
+#ifdef MOZ_WAYLAND
+ if (mWaylandVsyncDispatcher) {
+ return mWaylandVsyncDispatcher;
+ }
+#endif
+ return nullptr;
+}
+
+bool nsWindow::SynchronouslyRepaintOnResize() {
+ if (GdkIsWaylandDisplay()) {
+ // See Bug 1734368
+ // Don't request synchronous repaint on HW accelerated backend - mesa can be
+ // deadlocked when it's missing back buffer and main event loop is blocked.
+ return false;
+ }
+
+ // default is synced repaint.
+ return true;
+}
+
+static bool IsFullscreenSupported(GtkWidget* aShell) {
+#ifdef MOZ_X11
+ GdkScreen* screen = gtk_widget_get_screen(aShell);
+ GdkAtom atom = gdk_atom_intern("_NET_WM_STATE_FULLSCREEN", FALSE);
+ return gdk_x11_screen_supports_net_wm_hint(screen, atom);
+#else
+ return true;
+#endif
+}
+
+nsresult nsWindow::MakeFullScreen(bool aFullScreen) {
+ LOG("nsWindow::MakeFullScreen aFullScreen %d\n", aFullScreen);
+
+ if (GdkIsX11Display() && !IsFullscreenSupported(mShell)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (aFullScreen) {
+ if (mSizeMode != nsSizeMode_Fullscreen &&
+ mSizeMode != nsSizeMode_Minimized) {
+ mLastSizeModeBeforeFullscreen = mSizeMode;
+ }
+ if (mIsPIPWindow) {
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_NORMAL);
+ if (gUseAspectRatio) {
+ mAspectRatioSaved = mAspectRatio;
+ mAspectRatio = 0.0f;
+ ApplySizeConstraints();
+ }
+ }
+
+ gtk_window_fullscreen(GTK_WINDOW(mShell));
+ } else {
+ gtk_window_unfullscreen(GTK_WINDOW(mShell));
+
+ if (mIsPIPWindow) {
+ gtk_window_set_type_hint(GTK_WINDOW(mShell),
+ GDK_WINDOW_TYPE_HINT_UTILITY);
+ if (gUseAspectRatio) {
+ mAspectRatio = mAspectRatioSaved;
+ // ApplySizeConstraints();
+ }
+ }
+ }
+
+ MOZ_ASSERT(mLastSizeModeBeforeFullscreen != nsSizeMode_Fullscreen);
+ return NS_OK;
+}
+
+void nsWindow::SetWindowDecoration(BorderStyle aStyle) {
+ LOG("nsWindow::SetWindowDecoration() Border style %x\n", int(aStyle));
+
+ // We can't use mGdkWindow directly here as it can be
+ // derived from mContainer which is not a top-level GdkWindow.
+ GdkWindow* window = gtk_widget_get_window(mShell);
+
+ // Sawfish, metacity, and presumably other window managers get
+ // confused if we change the window decorations while the window
+ // is visible.
+ bool wasVisible = false;
+ if (gdk_window_is_visible(window)) {
+ gdk_window_hide(window);
+ wasVisible = true;
+ }
+
+ gint wmd = ConvertBorderStyles(aStyle);
+ if (wmd != -1) gdk_window_set_decorations(window, (GdkWMDecoration)wmd);
+
+ if (wasVisible) gdk_window_show(window);
+
+ // For some window managers, adding or removing window decorations
+ // requires unmapping and remapping our toplevel window. Go ahead
+ // and flush the queue here so that we don't end up with a BadWindow
+ // error later when this happens (when the persistence timer fires
+ // and GetWindowPos is called)
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ XSync(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), X11False);
+ } else
+#endif /* MOZ_X11 */
+ {
+ gdk_flush();
+ }
+}
+
+void nsWindow::HideWindowChrome(bool aShouldHide) {
+ SetWindowDecoration(aShouldHide ? BorderStyle::None : mBorderStyle);
+}
+
+bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
+ bool aAlwaysRollup) {
+ LOG("nsWindow::CheckForRollup() aAlwaysRollup %d", aAlwaysRollup);
+ nsIRollupListener* rollupListener = GetActiveRollupListener();
+ nsCOMPtr<nsIWidget> rollupWidget;
+ if (rollupListener) {
+ rollupWidget = rollupListener->GetRollupWidget();
+ }
+ if (!rollupWidget) {
+ return false;
+ }
+
+ auto* currentPopup =
+ (GdkWindow*)rollupWidget->GetNativeData(NS_NATIVE_WINDOW);
+ if (!aAlwaysRollup && is_mouse_in_window(currentPopup, aMouseX, aMouseY)) {
+ return false;
+ }
+ bool retVal = false;
+ if (aIsWheel) {
+ retVal = rollupListener->ShouldConsumeOnMouseWheelEvent();
+ if (!rollupListener->ShouldRollupOnMouseWheelEvent()) {
+ return retVal;
+ }
+ }
+ LayoutDeviceIntPoint point;
+ nsIRollupListener::RollupOptions options{0,
+ nsIRollupListener::FlushViews::Yes};
+ // if we're dealing with menus, we probably have submenus and
+ // we don't want to rollup if the click is in a parent menu of
+ // the current submenu
+ if (!aAlwaysRollup) {
+ AutoTArray<nsIWidget*, 5> widgetChain;
+ uint32_t sameTypeCount =
+ rollupListener->GetSubmenuWidgetChain(&widgetChain);
+ for (unsigned long i = 0; i < widgetChain.Length(); ++i) {
+ nsIWidget* widget = widgetChain[i];
+ auto* currWindow = (GdkWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
+ if (is_mouse_in_window(currWindow, aMouseX, aMouseY)) {
+ // Don't roll up if the mouse event occurred within a menu of the same
+ // type.
+ // If the mouse event occurred in a menu higher than that, roll up, but
+ // pass the number of popups to Rollup so that only those of the same
+ // type close up.
+ if (i < sameTypeCount) {
+ return retVal;
+ }
+ options.mCount = sameTypeCount;
+ break;
+ }
+ } // foreach parent menu widget
+ if (!aIsWheel) {
+ point = GdkEventCoordsToDevicePixels(aMouseX, aMouseY);
+ options.mPoint = &point;
+ }
+ }
+
+ if (mSizeMode == nsSizeMode_Minimized) {
+ // When we try to rollup in a minimized window, transitionend events for
+ // panels might not fire and thus we might not hide the popup after all,
+ // see bug 1810797.
+ options.mAllowAnimations = nsIRollupListener::AllowAnimations::No;
+ }
+
+ if (rollupListener->Rollup(options)) {
+ retVal = true;
+ }
+ return retVal;
+}
+
+/* static */
+bool nsWindow::DragInProgress() {
+ nsCOMPtr<nsIDragService> dragService =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ if (!dragService) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDragSession> currentDragSession;
+ dragService->GetCurrentSession(getter_AddRefs(currentDragSession));
+ return !!currentDragSession;
+}
+
+// This is an ugly workaround for
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1622107
+// We try to detect when Wayland compositor / gtk fails to deliver
+// info about finished D&D operations and cancel it on our own.
+MOZ_CAN_RUN_SCRIPT static void WaylandDragWorkaround(GdkEventButton* aEvent) {
+ static int buttonPressCountWithDrag = 0;
+
+ // We track only left button state as Firefox performs D&D on left
+ // button only.
+ if (aEvent->button != 1 || aEvent->type != GDK_BUTTON_PRESS) {
+ return;
+ }
+
+ nsCOMPtr<nsIDragService> dragService =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ if (!dragService) {
+ return;
+ }
+ nsCOMPtr<nsIDragSession> currentDragSession;
+ dragService->GetCurrentSession(getter_AddRefs(currentDragSession));
+
+ if (!currentDragSession) {
+ buttonPressCountWithDrag = 0;
+ return;
+ }
+
+ buttonPressCountWithDrag++;
+ if (buttonPressCountWithDrag > 1) {
+ NS_WARNING(
+ "Quit unfinished Wayland Drag and Drop operation. Buggy Wayland "
+ "compositor?");
+ buttonPressCountWithDrag = 0;
+ dragService->EndDragSession(false, 0);
+ }
+}
+
+static nsWindow* get_window_for_gtk_widget(GtkWidget* widget) {
+ gpointer user_data = g_object_get_data(G_OBJECT(widget), "nsWindow");
+ return static_cast<nsWindow*>(user_data);
+}
+
+static nsWindow* get_window_for_gdk_window(GdkWindow* window) {
+ gpointer user_data = g_object_get_data(G_OBJECT(window), "nsWindow");
+ return static_cast<nsWindow*>(user_data);
+}
+
+static bool is_mouse_in_window(GdkWindow* aWindow, gdouble aMouseX,
+ gdouble aMouseY) {
+ GdkWindow* window = aWindow;
+ if (!window) {
+ return false;
+ }
+
+ gint x = 0;
+ gint y = 0;
+
+ {
+ gint offsetX = 0;
+ gint offsetY = 0;
+
+ while (window) {
+ gint tmpX = 0;
+ gint tmpY = 0;
+
+ gdk_window_get_position(window, &tmpX, &tmpY);
+ GtkWidget* widget = get_gtk_widget_for_gdk_window(window);
+
+ // if this is a window, compute x and y given its origin and our
+ // offset
+ if (GTK_IS_WINDOW(widget)) {
+ x = tmpX + offsetX;
+ y = tmpY + offsetY;
+ break;
+ }
+
+ offsetX += tmpX;
+ offsetY += tmpY;
+ window = gdk_window_get_parent(window);
+ }
+ }
+
+ gint margin = 0;
+ if (nsWindow* w = get_window_for_gdk_window(aWindow)) {
+ margin = w->GetInputRegionMarginInGdkCoords();
+ }
+
+ x += margin;
+ y += margin;
+
+ gint w = gdk_window_get_width(aWindow) - margin;
+ gint h = gdk_window_get_height(aWindow) - margin;
+
+ return aMouseX > x && aMouseX < x + w && aMouseY > y && aMouseY < y + h;
+}
+
+static GtkWidget* get_gtk_widget_for_gdk_window(GdkWindow* window) {
+ gpointer user_data = nullptr;
+ gdk_window_get_user_data(window, &user_data);
+
+ return GTK_WIDGET(user_data);
+}
+
+static GdkCursor* get_gtk_cursor(nsCursor aCursor) {
+ GdkCursor* gdkcursor = nullptr;
+ uint8_t newType = 0xff;
+
+ if ((gdkcursor = gCursorCache[aCursor])) {
+ return gdkcursor;
+ }
+
+ GdkDisplay* defaultDisplay = gdk_display_get_default();
+
+ // The strategy here is to use standard GDK cursors, and, if not available,
+ // load by standard name with gdk_cursor_new_from_name.
+ // Spec is here: http://www.freedesktop.org/wiki/Specifications/cursor-spec/
+ switch (aCursor) {
+ case eCursor_standard:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_PTR);
+ break;
+ case eCursor_wait:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_WATCH);
+ break;
+ case eCursor_select:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_XTERM);
+ break;
+ case eCursor_hyperlink:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_HAND2);
+ break;
+ case eCursor_n_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_SIDE);
+ break;
+ case eCursor_s_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_SIDE);
+ break;
+ case eCursor_w_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_SIDE);
+ break;
+ case eCursor_e_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_RIGHT_SIDE);
+ break;
+ case eCursor_nw_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_LEFT_CORNER);
+ break;
+ case eCursor_se_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_RIGHT_CORNER);
+ break;
+ case eCursor_ne_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_RIGHT_CORNER);
+ break;
+ case eCursor_sw_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_LEFT_CORNER);
+ break;
+ case eCursor_crosshair:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_CROSSHAIR);
+ break;
+ case eCursor_move:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_FLEUR);
+ break;
+ case eCursor_help:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_QUESTION_ARROW);
+ break;
+ case eCursor_copy: // CSS3
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "copy");
+ if (!gdkcursor) newType = MOZ_CURSOR_COPY;
+ break;
+ case eCursor_alias:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "alias");
+ if (!gdkcursor) newType = MOZ_CURSOR_ALIAS;
+ break;
+ case eCursor_context_menu:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "context-menu");
+ if (!gdkcursor) newType = MOZ_CURSOR_CONTEXT_MENU;
+ break;
+ case eCursor_cell:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_PLUS);
+ break;
+ // Those two aren’t standardized. Trying both KDE’s and GNOME’s names
+ case eCursor_grab:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "openhand");
+ if (!gdkcursor) newType = MOZ_CURSOR_HAND_GRAB;
+ break;
+ case eCursor_grabbing:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "closedhand");
+ if (!gdkcursor) {
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "grabbing");
+ }
+ if (!gdkcursor) newType = MOZ_CURSOR_HAND_GRABBING;
+ break;
+ case eCursor_spinning:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "progress");
+ if (!gdkcursor) newType = MOZ_CURSOR_SPINNING;
+ break;
+ case eCursor_zoom_in:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "zoom-in");
+ if (!gdkcursor) newType = MOZ_CURSOR_ZOOM_IN;
+ break;
+ case eCursor_zoom_out:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "zoom-out");
+ if (!gdkcursor) newType = MOZ_CURSOR_ZOOM_OUT;
+ break;
+ case eCursor_not_allowed:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "not-allowed");
+ if (!gdkcursor) { // nonstandard, yet common
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "crossed_circle");
+ }
+ if (!gdkcursor) newType = MOZ_CURSOR_NOT_ALLOWED;
+ break;
+ case eCursor_no_drop:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "no-drop");
+ if (!gdkcursor) { // this nonstandard sequence makes it work on KDE and
+ // GNOME
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "forbidden");
+ }
+ if (!gdkcursor) {
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "circle");
+ }
+ if (!gdkcursor) newType = MOZ_CURSOR_NOT_ALLOWED;
+ break;
+ case eCursor_vertical_text:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "vertical-text");
+ if (!gdkcursor) {
+ newType = MOZ_CURSOR_VERTICAL_TEXT;
+ }
+ break;
+ case eCursor_all_scroll:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_FLEUR);
+ break;
+ case eCursor_nesw_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "size_bdiag");
+ if (!gdkcursor) newType = MOZ_CURSOR_NESW_RESIZE;
+ break;
+ case eCursor_nwse_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "size_fdiag");
+ if (!gdkcursor) newType = MOZ_CURSOR_NWSE_RESIZE;
+ break;
+ case eCursor_ns_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_SB_V_DOUBLE_ARROW);
+ break;
+ case eCursor_ew_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_SB_H_DOUBLE_ARROW);
+ break;
+ // Here, two better fitting cursors exist in some cursor themes. Try those
+ // first
+ case eCursor_row_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "split_v");
+ if (!gdkcursor) {
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_SB_V_DOUBLE_ARROW);
+ }
+ break;
+ case eCursor_col_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "split_h");
+ if (!gdkcursor) {
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_SB_H_DOUBLE_ARROW);
+ }
+ break;
+ case eCursor_none:
+ newType = MOZ_CURSOR_NONE;
+ break;
+ default:
+ NS_ASSERTION(aCursor, "Invalid cursor type");
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_PTR);
+ break;
+ }
+
+ // If by now we don't have a xcursor, this means we have to make a custom
+ // one. First, we try creating a named cursor based on the hash of our
+ // custom bitmap, as libXcursor has some magic to convert bitmapped cursors
+ // to themed cursors
+ if (newType != 0xFF && GtkCursors[newType].hash) {
+ gdkcursor =
+ gdk_cursor_new_from_name(defaultDisplay, GtkCursors[newType].hash);
+ }
+
+ // If we still don't have a xcursor, we now really create a bitmap cursor
+ if (newType != 0xff && !gdkcursor) {
+ GdkPixbuf* cursor_pixbuf =
+ gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32);
+ if (!cursor_pixbuf) return nullptr;
+
+ guchar* data = gdk_pixbuf_get_pixels(cursor_pixbuf);
+
+ // Read data from GtkCursors and compose RGBA surface from 1bit bitmap and
+ // mask GtkCursors bits and mask are 32x32 monochrome bitmaps (1 bit for
+ // each pixel) so it's 128 byte array (4 bytes for are one bitmap row and
+ // there are 32 rows here).
+ const unsigned char* bits = GtkCursors[newType].bits;
+ const unsigned char* mask_bits = GtkCursors[newType].mask_bits;
+
+ for (int i = 0; i < 128; i++) {
+ char bit = (char)*bits++;
+ char mask = (char)*mask_bits++;
+ for (int j = 0; j < 8; j++) {
+ unsigned char pix = ~(((bit >> j) & 0x01) * 0xff);
+ *data++ = pix;
+ *data++ = pix;
+ *data++ = pix;
+ *data++ = (((mask >> j) & 0x01) * 0xff);
+ }
+ }
+
+ gdkcursor = gdk_cursor_new_from_pixbuf(
+ gdk_display_get_default(), cursor_pixbuf, GtkCursors[newType].hot_x,
+ GtkCursors[newType].hot_y);
+
+ g_object_unref(cursor_pixbuf);
+ }
+
+ gCursorCache[aCursor] = gdkcursor;
+
+ return gdkcursor;
+}
+
+// gtk callbacks
+
+void draw_window_of_widget(GtkWidget* widget, GdkWindow* aWindow, cairo_t* cr) {
+ if (gtk_cairo_should_draw_window(cr, aWindow)) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ NS_WARNING("Cannot get nsWindow from GtkWidget");
+ } else {
+ cairo_save(cr);
+ gtk_cairo_transform_to_window(cr, widget, aWindow);
+ // TODO - window->OnExposeEvent() can destroy this or other windows,
+ // do we need to handle it somehow?
+ window->OnExposeEvent(cr);
+ cairo_restore(cr);
+ }
+ }
+}
+
+/* static */
+gboolean expose_event_cb(GtkWidget* widget, cairo_t* cr) {
+ draw_window_of_widget(widget, gtk_widget_get_window(widget), cr);
+
+ // A strong reference is already held during "draw" signal emission,
+ // but GTK+ 3.4 wants the object to live a little longer than that
+ // (bug 1225970).
+ g_object_ref(widget);
+ g_idle_add(
+ [](gpointer data) -> gboolean {
+ g_object_unref(data);
+ return G_SOURCE_REMOVE;
+ },
+ widget);
+
+ return FALSE;
+}
+
+static gboolean configure_event_cb(GtkWidget* widget,
+ GdkEventConfigure* event) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return FALSE;
+ }
+
+ return window->OnConfigureEvent(widget, event);
+}
+
+// Some Gtk widget code may call gtk_widget_unrealize() which destroys
+// mGdkWindow. We need to listen on this signal and re-create
+// mGdkWindow when we're already mapped.
+static void widget_map_cb(GtkWidget* widget) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+ window->OnMap();
+}
+
+static void widget_unmap_cb(GtkWidget* widget) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+ window->OnUnmap();
+}
+
+static void widget_unrealize_cb(GtkWidget* widget) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+ window->OnUnrealize();
+}
+
+static void size_allocate_cb(GtkWidget* widget, GtkAllocation* allocation) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+
+ window->OnSizeAllocate(allocation);
+}
+
+static void toplevel_window_size_allocate_cb(GtkWidget* widget,
+ GtkAllocation* allocation) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+
+ window->UpdateTopLevelOpaqueRegion();
+}
+
+static gboolean delete_event_cb(GtkWidget* widget, GdkEventAny* event) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return FALSE;
+ }
+
+ window->OnDeleteEvent();
+
+ return TRUE;
+}
+
+static gboolean enter_notify_event_cb(GtkWidget* widget,
+ GdkEventCrossing* event) {
+ RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
+ if (!window) {
+ return TRUE;
+ }
+
+ // We have stored leave notify - check if it's the correct one and
+ // fire it before enter notify in such case.
+ if (sStoredLeaveNotifyEvent) {
+ auto clearNofityEvent =
+ MakeScopeExit([&] { sStoredLeaveNotifyEvent = nullptr; });
+ if (event->x_root == sStoredLeaveNotifyEvent->x_root &&
+ event->y_root == sStoredLeaveNotifyEvent->y_root &&
+ window->ApplyEnterLeaveMutterWorkaround()) {
+ // Enter/Leave notify events has the same coordinates
+ // and uses know buggy window config.
+ // Consider it as a bogus one.
+ return TRUE;
+ }
+ RefPtr<nsWindow> leftWindow =
+ get_window_for_gdk_window(sStoredLeaveNotifyEvent->window);
+ if (leftWindow) {
+ leftWindow->OnLeaveNotifyEvent(sStoredLeaveNotifyEvent.get());
+ }
+ }
+
+ window->OnEnterNotifyEvent(event);
+ return TRUE;
+}
+
+static gboolean leave_notify_event_cb(GtkWidget* widget,
+ GdkEventCrossing* event) {
+ RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
+ if (!window) {
+ return TRUE;
+ }
+
+ if (window->ApplyEnterLeaveMutterWorkaround()) {
+ // The leave event is potentially wrong, don't fire it now but store
+ // it for further check at enter_notify_event_cb().
+ sStoredLeaveNotifyEvent.reset(reinterpret_cast<GdkEventCrossing*>(
+ gdk_event_copy(reinterpret_cast<GdkEvent*>(event))));
+ } else {
+ sStoredLeaveNotifyEvent = nullptr;
+ window->OnLeaveNotifyEvent(event);
+ }
+
+ return TRUE;
+}
+
+static nsWindow* GetFirstNSWindowForGDKWindow(GdkWindow* aGdkWindow) {
+ nsWindow* window;
+ while (!(window = get_window_for_gdk_window(aGdkWindow))) {
+ // The event has bubbled to the moz_container widget as passed into each
+ // caller's *widget parameter, but its corresponding nsWindow is an ancestor
+ // of the window that we need. Instead, look at event->window and find the
+ // first ancestor nsWindow of it because event->window may be in a plugin.
+ aGdkWindow = gdk_window_get_parent(aGdkWindow);
+ if (!aGdkWindow) {
+ window = nullptr;
+ break;
+ }
+ }
+ return window;
+}
+
+static gboolean motion_notify_event_cb(GtkWidget* widget,
+ GdkEventMotion* event) {
+ UpdateLastInputEventTime(event);
+
+ RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window) return FALSE;
+
+ window->OnMotionNotifyEvent(event);
+
+ return TRUE;
+}
+
+static gboolean button_press_event_cb(GtkWidget* widget,
+ GdkEventButton* event) {
+ UpdateLastInputEventTime(event);
+
+ RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window) return FALSE;
+
+ window->OnButtonPressEvent(event);
+
+ if (GdkIsWaylandDisplay()) {
+ WaylandDragWorkaround(event);
+ }
+
+ return TRUE;
+}
+
+static gboolean button_release_event_cb(GtkWidget* widget,
+ GdkEventButton* event) {
+ UpdateLastInputEventTime(event);
+
+ RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window) return FALSE;
+
+ window->OnButtonReleaseEvent(event);
+
+ return TRUE;
+}
+
+static gboolean focus_in_event_cb(GtkWidget* widget, GdkEventFocus* event) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) return FALSE;
+
+ window->OnContainerFocusInEvent(event);
+
+ return FALSE;
+}
+
+static gboolean focus_out_event_cb(GtkWidget* widget, GdkEventFocus* event) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) return FALSE;
+
+ window->OnContainerFocusOutEvent(event);
+
+ return FALSE;
+}
+
+#ifdef MOZ_X11
+// For long-lived popup windows that don't really take focus themselves but
+// may have elements that accept keyboard input when the parent window is
+// active, focus is handled specially. These windows include noautohide
+// panels. (This special handling is not necessary for temporary popups where
+// the keyboard is grabbed.)
+//
+// Mousing over or clicking on these windows should not cause them to steal
+// focus from their parent windows, so, the input field of WM_HINTS is set to
+// False to request that the window manager not set the input focus to this
+// window. http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7
+//
+// However, these windows can still receive WM_TAKE_FOCUS messages from the
+// window manager, so they can still detect when the user has indicated that
+// they wish to direct keyboard input at these windows. When the window
+// manager offers focus to these windows (after a mouse over or click, for
+// example), a request to make the parent window active is issued. When the
+// parent window becomes active, keyboard events will be received.
+
+static GdkFilterReturn popup_take_focus_filter(GdkXEvent* gdk_xevent,
+ GdkEvent* event, gpointer data) {
+ auto* xevent = static_cast<XEvent*>(gdk_xevent);
+ if (xevent->type != ClientMessage) return GDK_FILTER_CONTINUE;
+
+ XClientMessageEvent& xclient = xevent->xclient;
+ if (xclient.message_type != gdk_x11_get_xatom_by_name("WM_PROTOCOLS")) {
+ return GDK_FILTER_CONTINUE;
+ }
+
+ Atom atom = xclient.data.l[0];
+ if (atom != gdk_x11_get_xatom_by_name("WM_TAKE_FOCUS")) {
+ return GDK_FILTER_CONTINUE;
+ }
+
+ guint32 timestamp = xclient.data.l[1];
+
+ GtkWidget* widget = get_gtk_widget_for_gdk_window(event->any.window);
+ if (!widget) return GDK_FILTER_CONTINUE;
+
+ GtkWindow* parent = gtk_window_get_transient_for(GTK_WINDOW(widget));
+ if (!parent) return GDK_FILTER_CONTINUE;
+
+ if (gtk_window_is_active(parent)) {
+ return GDK_FILTER_REMOVE; // leave input focus on the parent
+ }
+
+ GdkWindow* parent_window = gtk_widget_get_window(GTK_WIDGET(parent));
+ if (!parent_window) return GDK_FILTER_CONTINUE;
+
+ // In case the parent has not been deconified.
+ gdk_window_show_unraised(parent_window);
+
+ // Request focus on the parent window.
+ // Use gdk_window_focus rather than gtk_window_present to avoid
+ // raising the parent window.
+ gdk_window_focus(parent_window, timestamp);
+ return GDK_FILTER_REMOVE;
+}
+#endif /* MOZ_X11 */
+
+static gboolean key_press_event_cb(GtkWidget* widget, GdkEventKey* event) {
+ LOGW("key_press_event_cb\n");
+
+ UpdateLastInputEventTime(event);
+
+ // find the window with focus and dispatch this event to that widget
+ nsWindow* window = get_window_for_gtk_widget(widget);
+ if (!window) return FALSE;
+
+ RefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
+
+#ifdef MOZ_X11
+ // Keyboard repeat can cause key press events to queue up when there are
+ // slow event handlers (bug 301029). Throttle these events by removing
+ // consecutive pending duplicate KeyPress events to the same window.
+ // We use the event time of the last one.
+ // Note: GDK calls XkbSetDetectableAutorepeat so that KeyRelease events
+ // are generated only when the key is physically released.
+# define NS_GDKEVENT_MATCH_MASK 0x1FFF // GDK_SHIFT_MASK .. GDK_BUTTON5_MASK
+ // Our headers undefine X11 KeyPress - let's redefine it here.
+# ifndef KeyPress
+# define KeyPress 2
+# endif
+ GdkDisplay* gdkDisplay = gtk_widget_get_display(widget);
+ if (GdkIsX11Display(gdkDisplay)) {
+ Display* dpy = GDK_DISPLAY_XDISPLAY(gdkDisplay);
+ while (XPending(dpy)) {
+ XEvent next_event;
+ XPeekEvent(dpy, &next_event);
+ GdkWindow* nextGdkWindow =
+ gdk_x11_window_lookup_for_display(gdkDisplay, next_event.xany.window);
+ if (nextGdkWindow != event->window || next_event.type != KeyPress ||
+ next_event.xkey.keycode != event->hardware_keycode ||
+ next_event.xkey.state != (event->state & NS_GDKEVENT_MATCH_MASK)) {
+ break;
+ }
+ XNextEvent(dpy, &next_event);
+ event->time = next_event.xkey.time;
+ }
+ }
+#endif
+
+ return focusWindow->OnKeyPressEvent(event);
+}
+
+static gboolean key_release_event_cb(GtkWidget* widget, GdkEventKey* event) {
+ LOGW("key_release_event_cb\n");
+
+ UpdateLastInputEventTime(event);
+
+ // find the window with focus and dispatch this event to that widget
+ nsWindow* window = get_window_for_gtk_widget(widget);
+ if (!window) return FALSE;
+
+ RefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
+ return focusWindow->OnKeyReleaseEvent(event);
+}
+
+static gboolean property_notify_event_cb(GtkWidget* aWidget,
+ GdkEventProperty* aEvent) {
+ RefPtr<nsWindow> window = get_window_for_gdk_window(aEvent->window);
+ if (!window) return FALSE;
+
+ return window->OnPropertyNotifyEvent(aWidget, aEvent);
+}
+
+static gboolean scroll_event_cb(GtkWidget* widget, GdkEventScroll* event) {
+ RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window) return FALSE;
+
+ window->OnScrollEvent(event);
+
+ return TRUE;
+}
+
+static void hierarchy_changed_cb(GtkWidget* widget,
+ GtkWidget* previous_toplevel) {
+ GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
+ GdkWindowState old_window_state = GDK_WINDOW_STATE_WITHDRAWN;
+ GdkEventWindowState event;
+
+ event.new_window_state = GDK_WINDOW_STATE_WITHDRAWN;
+
+ if (GTK_IS_WINDOW(previous_toplevel)) {
+ g_signal_handlers_disconnect_by_func(
+ previous_toplevel, FuncToGpointer(window_state_event_cb), widget);
+ GdkWindow* win = gtk_widget_get_window(previous_toplevel);
+ if (win) {
+ old_window_state = gdk_window_get_state(win);
+ }
+ }
+
+ if (GTK_IS_WINDOW(toplevel)) {
+ g_signal_connect_swapped(toplevel, "window-state-event",
+ G_CALLBACK(window_state_event_cb), widget);
+ GdkWindow* win = gtk_widget_get_window(toplevel);
+ if (win) {
+ event.new_window_state = gdk_window_get_state(win);
+ }
+ }
+
+ event.changed_mask =
+ static_cast<GdkWindowState>(old_window_state ^ event.new_window_state);
+
+ if (event.changed_mask) {
+ event.type = GDK_WINDOW_STATE;
+ event.window = nullptr;
+ event.send_event = TRUE;
+ window_state_event_cb(widget, &event);
+ }
+}
+
+static gboolean window_state_event_cb(GtkWidget* widget,
+ GdkEventWindowState* event) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) return FALSE;
+
+ window->OnWindowStateEvent(widget, event);
+
+ return FALSE;
+}
+
+static void settings_xft_dpi_changed_cb(GtkSettings* gtk_settings,
+ GParamSpec* pspec, nsWindow* data) {
+ RefPtr<nsWindow> window = data;
+ window->OnDPIChanged();
+ // Even though the window size in screen pixels has not changed,
+ // nsViewManager stores the dimensions in app units.
+ // DispatchResized() updates those.
+ window->DispatchResized();
+}
+
+static void check_resize_cb(GtkContainer* container, gpointer user_data) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(GTK_WIDGET(container));
+ if (!window) {
+ return;
+ }
+ window->OnCheckResize();
+}
+
+static void screen_composited_changed_cb(GdkScreen* screen,
+ gpointer user_data) {
+ // This callback can run before gfxPlatform::Init() in rare
+ // cases involving the profile manager. When this happens,
+ // we have no reason to reset any compositors as graphics
+ // hasn't been initialized yet.
+ if (GPUProcessManager::Get()) {
+ GPUProcessManager::Get()->ResetCompositors();
+ }
+}
+
+static void widget_composited_changed_cb(GtkWidget* widget,
+ gpointer user_data) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+ window->OnCompositedChanged();
+}
+
+static void scale_changed_cb(GtkWidget* widget, GParamSpec* aPSpec,
+ gpointer aPointer) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+
+ window->OnScaleChanged();
+}
+
+static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent) {
+ UpdateLastInputEventTime(aEvent);
+
+ RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(aEvent->window);
+ if (!window) {
+ return FALSE;
+ }
+
+ return window->OnTouchEvent(aEvent);
+}
+
+// This function called generic because there is no signal specific to touchpad
+// pinch events.
+static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent) {
+ if (aEvent->type != GDK_TOUCHPAD_PINCH) {
+ return FALSE;
+ }
+ // Using reinterpret_cast because the touchpad_pinch field of GdkEvent is not
+ // available in GTK+ versions lower than v3.18
+ GdkEventTouchpadPinch* event =
+ reinterpret_cast<GdkEventTouchpadPinch*>(aEvent);
+
+ RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
+
+ if (!window) {
+ return FALSE;
+ }
+ return window->OnTouchpadPinchEvent(event);
+}
+
+//////////////////////////////////////////////////////////////////////
+// These are all of our drag and drop operations
+
+void nsWindow::InitDragEvent(WidgetDragEvent& aEvent) {
+ // set the keyboard modifiers
+ guint modifierState = KeymapWrapper::GetCurrentModifierState();
+ KeymapWrapper::InitInputEvent(aEvent, modifierState);
+}
+
+static LayoutDeviceIntPoint GetWindowDropPosition(nsWindow* aWindow, int aX,
+ int aY) {
+ // Workaround for Bug 1710344
+ // Caused by Gtk issue https://gitlab.gnome.org/GNOME/gtk/-/issues/4437
+ if (aWindow->IsWaylandPopup()) {
+ int tx = 0, ty = 0;
+ gdk_window_get_position(aWindow->GetToplevelGdkWindow(), &tx, &ty);
+ aX += tx;
+ aY += ty;
+ }
+ LOGDRAG("WindowDropPosition [%d, %d]", aX, aY);
+ return aWindow->GdkPointToDevicePixels({aX, aY});
+}
+
+gboolean WindowDragMotionHandler(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX, gint aY,
+ guint aTime) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window) {
+ return FALSE;
+ }
+
+ // figure out which internal widget this drag motion actually happened on
+ nscoord retx = 0;
+ nscoord rety = 0;
+
+ GdkWindow* innerWindow = get_inner_gdk_window(gtk_widget_get_window(aWidget),
+ aX, aY, &retx, &rety);
+ RefPtr<nsWindow> innerMostWindow = get_window_for_gdk_window(innerWindow);
+ if (!innerMostWindow) {
+ innerMostWindow = window;
+ }
+ LOGDRAG("WindowDragMotionHandler target nsWindow [%p]",
+ innerMostWindow.get());
+
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ nsDragService::AutoEventLoop loop(dragService);
+ if (!dragService->ScheduleMotionEvent(
+ innerMostWindow, aDragContext,
+ GetWindowDropPosition(innerMostWindow, retx, rety), aTime)) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean drag_motion_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY, guint aTime, gpointer aData) {
+ return WindowDragMotionHandler(aWidget, aDragContext, aX, aY, aTime);
+}
+
+void WindowDragLeaveHandler(GtkWidget* aWidget) {
+ LOGDRAG("WindowDragLeaveHandler()\n");
+
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window) {
+ LOGDRAG(" Failed - can't find nsWindow!\n");
+ return;
+ }
+
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ nsDragService::AutoEventLoop loop(dragService);
+
+ nsWindow* mostRecentDragWindow = dragService->GetMostRecentDestWindow();
+ if (!mostRecentDragWindow) {
+ // This can happen when the target will not accept a drop. A GTK drag
+ // source sends the leave message to the destination before the
+ // drag-failed signal on the source widget, but the leave message goes
+ // via the X server, and so doesn't get processed at least until the
+ // event loop runs again.
+ LOGDRAG(" Failed - GetMostRecentDestWindow()!\n");
+ return;
+ }
+
+ if (aWidget != window->GetGtkWidget()) {
+ // When the drag moves between widgets, GTK can send leave signal for
+ // the old widget after the motion or drop signal for the new widget.
+ // We'll send the leave event when the motion or drop event is run.
+ LOGDRAG(" Failed - GtkWidget mismatch!\n");
+ return;
+ }
+
+ LOGDRAG("WindowDragLeaveHandler nsWindow %p\n", (void*)mostRecentDragWindow);
+ dragService->ScheduleLeaveEvent();
+}
+
+static void drag_leave_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, guint aTime,
+ gpointer aData) {
+ WindowDragLeaveHandler(aWidget);
+}
+
+gboolean WindowDragDropHandler(GtkWidget* aWidget, GdkDragContext* aDragContext,
+ gint aX, gint aY, guint aTime) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window) return FALSE;
+
+ // figure out which internal widget this drag motion actually happened on
+ nscoord retx = 0;
+ nscoord rety = 0;
+
+ GdkWindow* innerWindow = get_inner_gdk_window(gtk_widget_get_window(aWidget),
+ aX, aY, &retx, &rety);
+ RefPtr<nsWindow> innerMostWindow = get_window_for_gdk_window(innerWindow);
+
+ if (!innerMostWindow) {
+ innerMostWindow = window;
+ }
+
+ LOGDRAG("WindowDragDropHandler nsWindow [%p]", innerMostWindow.get());
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ nsDragService::AutoEventLoop loop(dragService);
+ return dragService->ScheduleDropEvent(
+ innerMostWindow, aDragContext,
+ GetWindowDropPosition(innerMostWindow, retx, rety), aTime);
+}
+
+static gboolean drag_drop_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY, guint aTime, gpointer aData) {
+ return WindowDragDropHandler(aWidget, aDragContext, aX, aY, aTime);
+}
+
+static void drag_data_received_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY,
+ GtkSelectionData* aSelectionData,
+ guint aInfo, guint aTime,
+ gpointer aData) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window) return;
+
+ window->OnDragDataReceivedEvent(aWidget, aDragContext, aX, aY, aSelectionData,
+ aInfo, aTime, aData);
+}
+
+static nsresult initialize_prefs(void) {
+ if (Preferences::HasUserValue("widget.use-aspect-ratio")) {
+ gUseAspectRatio = Preferences::GetBool("widget.use-aspect-ratio", true);
+ } else {
+ gUseAspectRatio = IsGnomeDesktopEnvironment() || IsKdeDesktopEnvironment();
+ }
+ return NS_OK;
+}
+
+// TODO: Can we simplify it for mShell/mContainer only scenario?
+static GdkWindow* get_inner_gdk_window(GdkWindow* aWindow, gint x, gint y,
+ gint* retx, gint* rety) {
+ gint cx, cy, cw, ch;
+ GList* children = gdk_window_peek_children(aWindow);
+ for (GList* child = g_list_last(children); child;
+ child = g_list_previous(child)) {
+ auto* childWindow = (GdkWindow*)child->data;
+ if (get_window_for_gdk_window(childWindow)) {
+ gdk_window_get_geometry(childWindow, &cx, &cy, &cw, &ch);
+ if ((cx < x) && (x < (cx + cw)) && (cy < y) && (y < (cy + ch)) &&
+ gdk_window_is_visible(childWindow)) {
+ return get_inner_gdk_window(childWindow, x - cx, y - cy, retx, rety);
+ }
+ }
+ }
+ *retx = x;
+ *rety = y;
+ return aWindow;
+}
+
+#ifdef ACCESSIBILITY
+void nsWindow::CreateRootAccessible() {
+ if (!mRootAccessible) {
+ LOG("nsWindow:: Create Toplevel Accessibility\n");
+ mRootAccessible = GetRootAccessible();
+ }
+}
+
+void nsWindow::DispatchEventToRootAccessible(uint32_t aEventType) {
+ if (!a11y::ShouldA11yBeEnabled()) {
+ return;
+ }
+
+ nsAccessibilityService* accService = GetOrCreateAccService();
+ if (!accService) {
+ return;
+ }
+
+ // Get the root document accessible and fire event to it.
+ a11y::LocalAccessible* acc = GetRootAccessible();
+ if (acc) {
+ accService->FireAccessibleEvent(aEventType, acc);
+ }
+}
+
+void nsWindow::DispatchActivateEventAccessible(void) {
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_ACTIVATE);
+}
+
+void nsWindow::DispatchDeactivateEventAccessible(void) {
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_DEACTIVATE);
+}
+
+void nsWindow::DispatchMaximizeEventAccessible(void) {
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_MAXIMIZE);
+}
+
+void nsWindow::DispatchMinimizeEventAccessible(void) {
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_MINIMIZE);
+}
+
+void nsWindow::DispatchRestoreEventAccessible(void) {
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_RESTORE);
+}
+
+#endif /* #ifdef ACCESSIBILITY */
+
+void nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) {
+ if (!mIMContext) {
+ return;
+ }
+ mIMContext->SetInputContext(this, &aContext, &aAction);
+}
+
+InputContext nsWindow::GetInputContext() {
+ InputContext context;
+ if (!mIMContext) {
+ context.mIMEState.mEnabled = IMEEnabled::Disabled;
+ context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
+ } else {
+ context = mIMContext->GetInputContext();
+ }
+ return context;
+}
+
+TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() {
+ if (NS_WARN_IF(!mIMContext)) {
+ return nullptr;
+ }
+ return mIMContext;
+}
+
+bool nsWindow::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();
+ }
+ }
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ keyBindings->GetEditCommands(aEvent, writingMode, aCommands);
+ return true;
+}
+
+already_AddRefed<DrawTarget> nsWindow::StartRemoteDrawingInRegion(
+ const LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode) {
+ return mSurfaceProvider.StartRemoteDrawingInRegion(aInvalidRegion,
+ aBufferMode);
+}
+
+void nsWindow::EndRemoteDrawingInRegion(
+ DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
+ mSurfaceProvider.EndRemoteDrawingInRegion(aDrawTarget, aInvalidRegion);
+}
+
+bool nsWindow::GetDragInfo(WidgetMouseEvent* aMouseEvent, GdkWindow** aWindow,
+ gint* aButton, gint* aRootX, gint* aRootY) {
+ if (aMouseEvent->mButton != MouseButton::ePrimary) {
+ // we can only begin a move drag with the left mouse button
+ return false;
+ }
+ *aButton = 1;
+
+ // get the gdk window for this widget
+ GdkWindow* gdk_window = mGdkWindow;
+ if (!gdk_window) {
+ return false;
+ }
+#ifdef DEBUG
+ // GDK_IS_WINDOW(...) expands to a statement-expression, and
+ // statement-expressions are not allowed in template-argument lists. So we
+ // have to make the MOZ_ASSERT condition indirect.
+ if (!GDK_IS_WINDOW(gdk_window)) {
+ MOZ_ASSERT(false, "must really be window");
+ }
+#endif
+
+ // find the top-level window
+ gdk_window = gdk_window_get_toplevel(gdk_window);
+ MOZ_ASSERT(gdk_window, "gdk_window_get_toplevel should not return null");
+ *aWindow = gdk_window;
+
+ if (!aMouseEvent->mWidget) {
+ return false;
+ }
+
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=789054
+ // To avoid crashes disable double-click on WM without _NET_WM_MOVERESIZE.
+ // See _should_perform_ewmh_drag() at gdkwindow-x11.c
+ // XXXsmaug remove this old hack. gtk should be fixed now.
+ GdkScreen* screen = gdk_window_get_screen(gdk_window);
+ GdkAtom atom = gdk_atom_intern("_NET_WM_MOVERESIZE", FALSE);
+ if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
+ static TimeStamp lastTimeStamp;
+ if (lastTimeStamp != aMouseEvent->mTimeStamp) {
+ lastTimeStamp = aMouseEvent->mTimeStamp;
+ } else {
+ return false;
+ }
+ }
+ }
+#endif
+
+ // FIXME: It would be nice to have the widget position at the time
+ // of the event, but it's relatively unlikely that the widget has
+ // moved since the mousedown. (On the other hand, it's quite likely
+ // that the mouse has moved, which is why we use the mouse position
+ // from the event.)
+ LayoutDeviceIntPoint offset = aMouseEvent->mWidget->WidgetToScreenOffset();
+ *aRootX = aMouseEvent->mRefPoint.x + offset.x;
+ *aRootY = aMouseEvent->mRefPoint.y + offset.y;
+
+ return true;
+}
+
+nsIWidget::WindowRenderer* nsWindow::GetWindowRenderer() {
+ if (mIsDestroyed) {
+ // Prevent external code from triggering the re-creation of the
+ // LayerManager/Compositor during shutdown. Just return what we currently
+ // have, which is most likely null.
+ return mWindowRenderer;
+ }
+
+ return nsBaseWidget::GetWindowRenderer();
+}
+
+void nsWindow::DidGetNonBlankPaint() {
+ if (mGotNonBlankPaint) {
+ return;
+ }
+ mGotNonBlankPaint = true;
+ if (!mConfiguredClearColor) {
+ // Nothing to do, we hadn't overridden the clear color.
+ mConfiguredClearColor = true;
+ return;
+ }
+ // Reset the clear color set in the expose event to transparent.
+ GetWindowRenderer()->AsWebRender()->WrBridge()->SendSetDefaultClearColor(
+ NS_TRANSPARENT);
+}
+
+/* nsWindow::SetCompositorWidgetDelegate() sets remote GtkCompositorWidget
+ * to render into with compositor.
+ *
+ * SetCompositorWidgetDelegate(delegate) is called from
+ * nsBaseWidget::CreateCompositor(), i.e. nsWindow::GetWindowRenderer().
+ *
+ * SetCompositorWidgetDelegate(null) is called from
+ * nsBaseWidget::DestroyCompositor().
+ */
+void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
+ LOG("nsWindow::SetCompositorWidgetDelegate %p mIsMapped %d "
+ "mCompositorWidgetDelegate %p\n",
+ delegate, mIsMapped, mCompositorWidgetDelegate);
+
+ if (delegate) {
+ mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate();
+ MOZ_ASSERT(mCompositorWidgetDelegate,
+ "nsWindow::SetCompositorWidgetDelegate called with a "
+ "non-PlatformCompositorWidgetDelegate");
+ if (mIsMapped) {
+ ConfigureCompositor();
+ }
+ } else {
+ mCompositorWidgetDelegate = nullptr;
+ }
+}
+
+nsresult nsWindow::SetNonClientMargins(const LayoutDeviceIntMargin& aMargins) {
+ SetDrawsInTitlebar(aMargins.top == 0);
+ return NS_OK;
+}
+
+bool nsWindow::IsAlwaysUndecoratedWindow() const {
+ if (mIsPIPWindow || mIsWaylandPanelWindow) {
+ return true;
+ }
+ if (mWindowType == WindowType::Dialog &&
+ mBorderStyle != BorderStyle::Default &&
+ mBorderStyle != BorderStyle::All &&
+ !(mBorderStyle & BorderStyle::Title) &&
+ !(mBorderStyle & BorderStyle::ResizeH)) {
+ return true;
+ }
+ return false;
+}
+
+void nsWindow::SetDrawsInTitlebar(bool aState) {
+ LOG("nsWindow::SetDrawsInTitlebar() State %d mGtkWindowDecoration %d\n",
+ aState, (int)mGtkWindowDecoration);
+
+ if (mGtkWindowDecoration == GTK_DECORATION_NONE ||
+ aState == mDrawInTitlebar) {
+ LOG(" already set, quit");
+ return;
+ }
+
+ if (IsAlwaysUndecoratedWindow()) {
+ MOZ_ASSERT(aState, "Unexpected decoration request");
+ MOZ_ASSERT(!gtk_window_get_decorated(GTK_WINDOW(mShell)));
+ return;
+ }
+
+ if (mGtkWindowDecoration == GTK_DECORATION_SYSTEM) {
+ SetWindowDecoration(aState ? BorderStyle::Border : mBorderStyle);
+ } else if (mGtkWindowDecoration == GTK_DECORATION_CLIENT) {
+ LOG(" Using CSD mode\n");
+
+ /* Window manager does not support GDK_DECOR_BORDER,
+ * emulate it by CSD.
+ *
+ * gtk_window_set_titlebar() works on unrealized widgets only,
+ * we need to handle mShell carefully here.
+ * When CSD is enabled mGdkWindow is owned by mContainer which is good
+ * as we can't delete our mGdkWindow. To make mShell unrealized while
+ * mContainer is preserved we temporary reparent mContainer to an
+ * invisible GtkWindow.
+ */
+ bool visible = !mNeedsShow && mIsShown;
+ if (visible) {
+ NativeShow(false);
+ }
+
+ // Using GTK_WINDOW_POPUP rather than
+ // GTK_WINDOW_TOPLEVEL in the hope that POPUP results in less
+ // initialization and window manager interaction.
+ GtkWidget* tmpWindow = gtk_window_new(GTK_WINDOW_POPUP);
+ gtk_widget_realize(tmpWindow);
+
+ gtk_widget_reparent(GTK_WIDGET(mContainer), tmpWindow);
+ gtk_widget_unrealize(GTK_WIDGET(mShell));
+
+ if (aState) {
+ // Add a hidden titlebar widget to trigger CSD, but disable the default
+ // titlebar. GtkFixed is a somewhat random choice for a simple unused
+ // widget. gtk_window_set_titlebar() takes ownership of the titlebar
+ // widget.
+ gtk_window_set_titlebar(GTK_WINDOW(mShell), gtk_fixed_new());
+ } else {
+ gtk_window_set_titlebar(GTK_WINDOW(mShell), nullptr);
+ }
+
+ /* A workaround for https://bugzilla.gnome.org/show_bug.cgi?id=791081
+ * gtk_widget_realize() throws:
+ * "In pixman_region32_init_rect: Invalid rectangle passed"
+ * when mShell has default 1x1 size.
+ */
+ GtkAllocation allocation = {0, 0, 0, 0};
+ gtk_widget_get_preferred_width(GTK_WIDGET(mShell), nullptr,
+ &allocation.width);
+ gtk_widget_get_preferred_height(GTK_WIDGET(mShell), nullptr,
+ &allocation.height);
+ gtk_widget_size_allocate(GTK_WIDGET(mShell), &allocation);
+
+ gtk_widget_realize(GTK_WIDGET(mShell));
+ gtk_widget_reparent(GTK_WIDGET(mContainer), GTK_WIDGET(mShell));
+
+ // Label mShell toplevel window so property_notify_event_cb callback
+ // can find its way home.
+ g_object_set_data(G_OBJECT(gtk_widget_get_window(mShell)), "nsWindow",
+ this);
+
+ if (AreBoundsSane()) {
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
+ LOG(" resize to %d x %d\n", size.width, size.height);
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ }
+
+ if (visible) {
+ mNeedsShow = true;
+ NativeShow(true);
+ }
+
+ gtk_widget_destroy(tmpWindow);
+ }
+
+ mDrawInTitlebar = aState;
+
+ if (mTransparencyBitmapForTitlebar) {
+ if (mDrawInTitlebar && mSizeMode == nsSizeMode_Normal && !mIsTiled) {
+ UpdateTitlebarTransparencyBitmap();
+ } else {
+ ClearTransparencyBitmap();
+ }
+ }
+}
+
+GtkWindow* nsWindow::GetCurrentTopmostWindow() const {
+ GtkWindow* parentWindow = GTK_WINDOW(GetGtkWidget());
+ GtkWindow* topmostParentWindow = nullptr;
+ while (parentWindow) {
+ topmostParentWindow = parentWindow;
+ parentWindow = gtk_window_get_transient_for(parentWindow);
+ }
+ return topmostParentWindow;
+}
+
+gint nsWindow::GdkCeiledScaleFactor() {
+ // We depend on notify::scale-factor callback which is reliable for toplevel
+ // windows only, so don't use scale cache for popup windows.
+ if (mWindowType == WindowType::TopLevel && !mWindowScaleFactorChanged) {
+ return mWindowScaleFactor;
+ }
+
+ GdkWindow* scaledGdkWindow = nullptr;
+ if (GdkIsWaylandDisplay()) {
+ // For popup windows/dialogs with parent window we need to get scale factor
+ // of the topmost window. Otherwise the scale factor of the popup is
+ // not updated during it's hidden.
+ if (mWindowType == WindowType::Popup || mWindowType == WindowType::Dialog) {
+ // Get toplevel window for scale factor:
+ GtkWindow* topmostParentWindow = GetCurrentTopmostWindow();
+ if (topmostParentWindow) {
+ scaledGdkWindow =
+ gtk_widget_get_window(GTK_WIDGET(topmostParentWindow));
+ } else {
+ NS_WARNING("Popup/Dialog has no parent.");
+ }
+ }
+ }
+ // Fallback for windows which parent has been unrealized.
+ if (!scaledGdkWindow) {
+ scaledGdkWindow = mGdkWindow;
+ }
+ if (scaledGdkWindow) {
+ mWindowScaleFactor = gdk_window_get_scale_factor(scaledGdkWindow);
+ mWindowScaleFactorChanged = false;
+ } else {
+ mWindowScaleFactor = ScreenHelperGTK::GetGTKMonitorScaleFactor();
+ }
+ return mWindowScaleFactor;
+}
+
+bool nsWindow::UseFractionalScale() const {
+#ifdef MOZ_WAYLAND
+ return GdkIsWaylandDisplay() &&
+ StaticPrefs::widget_wayland_fractional_buffer_scale_AtStartup() > 0 &&
+ WaylandDisplayGet()->GetViewporter();
+#else
+ return false;
+#endif
+}
+
+double nsWindow::FractionalScaleFactor() {
+#ifdef MOZ_WAYLAND
+ if (UseFractionalScale()) {
+ double scale =
+ StaticPrefs::widget_wayland_fractional_buffer_scale_AtStartup();
+ scale = std::max(scale, 0.5);
+ scale = std::min(scale, 8.0);
+ return scale;
+ }
+#endif
+ return GdkCeiledScaleFactor();
+}
+
+gint nsWindow::DevicePixelsToGdkCoordRoundUp(int aPixels) {
+ double scale = FractionalScaleFactor();
+ return ceil(aPixels / scale);
+}
+
+gint nsWindow::DevicePixelsToGdkCoordRoundDown(int aPixels) {
+ double scale = FractionalScaleFactor();
+ return floor(aPixels / scale);
+}
+
+GdkPoint nsWindow::DevicePixelsToGdkPointRoundDown(
+ const LayoutDeviceIntPoint& aPoint) {
+ double scale = FractionalScaleFactor();
+ return {int(aPoint.x / scale), int(aPoint.y / scale)};
+}
+
+GdkRectangle nsWindow::DevicePixelsToGdkRectRoundOut(
+ const LayoutDeviceIntRect& aRect) {
+ double scale = FractionalScaleFactor();
+ int x = floor(aRect.x / scale);
+ int y = floor(aRect.y / scale);
+ int right = ceil((aRect.x + aRect.width) / scale);
+ int bottom = ceil((aRect.y + aRect.height) / scale);
+ return {x, y, right - x, bottom - y};
+}
+
+GdkRectangle nsWindow::DevicePixelsToGdkSizeRoundUp(
+ const LayoutDeviceIntSize& aSize) {
+ double scale = FractionalScaleFactor();
+ gint width = ceil(aSize.width / scale);
+ gint height = ceil(aSize.height / scale);
+ return {0, 0, width, height};
+}
+
+int nsWindow::GdkCoordToDevicePixels(gint aCoord) {
+ return (int)(aCoord * FractionalScaleFactor());
+}
+
+LayoutDeviceIntPoint nsWindow::GdkEventCoordsToDevicePixels(gdouble aX,
+ gdouble aY) {
+ double scale = FractionalScaleFactor();
+ return LayoutDeviceIntPoint::Floor((float)(aX * scale), (float)(aY * scale));
+}
+
+LayoutDeviceIntPoint nsWindow::GdkPointToDevicePixels(const GdkPoint& aPoint) {
+ double scale = FractionalScaleFactor();
+ return LayoutDeviceIntPoint::Floor((float)(aPoint.x * scale),
+ (float)(aPoint.y * scale));
+}
+
+LayoutDeviceIntRect nsWindow::GdkRectToDevicePixels(const GdkRectangle& aRect) {
+ double scale = FractionalScaleFactor();
+ return LayoutDeviceIntRect::RoundIn(
+ (float)(aRect.x * scale), (float)(aRect.y * scale),
+ (float)(aRect.width * scale), (float)(aRect.height * scale));
+}
+
+nsresult nsWindow::SynthesizeNativeMouseEvent(
+ LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
+ MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
+ nsIObserver* aObserver) {
+ LOG("SynthesizeNativeMouseEvent(%d, %d, %d, %d, %d)", aPoint.x.value,
+ aPoint.y.value, int(aNativeMessage), int(aButton), int(aModifierFlags));
+
+ AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+
+ // When a button-press/release event is requested, create it here and put it
+ // in the event queue. This will not emit a motion event - this needs to be
+ // done explicitly *before* requesting a button-press/release. You will also
+ // need to wait for the motion event to be dispatched before requesting a
+ // button-press/release event to maintain the desired event order.
+ switch (aNativeMessage) {
+ case NativeMouseMessage::ButtonDown:
+ case NativeMouseMessage::ButtonUp: {
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+ event.type = aNativeMessage == NativeMouseMessage::ButtonDown
+ ? GDK_BUTTON_PRESS
+ : GDK_BUTTON_RELEASE;
+ switch (aButton) {
+ case MouseButton::ePrimary:
+ case MouseButton::eMiddle:
+ case MouseButton::eSecondary:
+ case MouseButton::eX1:
+ case MouseButton::eX2:
+ event.button.button = aButton + 1;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+ event.button.state =
+ KeymapWrapper::ConvertWidgetModifierToGdkState(aModifierFlags);
+ event.button.window = mGdkWindow;
+ event.button.time = GDK_CURRENT_TIME;
+
+ // Get device for event source
+ event.button.device = GdkGetPointer();
+
+ event.button.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
+ event.button.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ event.button.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
+ event.button.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
+
+ gdk_event_put(&event);
+ return NS_OK;
+ }
+ case NativeMouseMessage::Move: {
+ // We don't support specific events other than button-press/release. In
+ // all other cases we'll synthesize a motion event that will be emitted by
+ // gdk_display_warp_pointer().
+ // XXX How to activate native modifier for the other events?
+#ifdef MOZ_WAYLAND
+ // Impossible to warp the pointer on Wayland.
+ // For pointer lock, pointer-constraints and relative-pointer are used.
+ if (GdkIsWaylandDisplay()) {
+ return NS_OK;
+ }
+#endif
+ GdkScreen* screen = gdk_window_get_screen(mGdkWindow);
+ GdkPoint point = DevicePixelsToGdkPointRoundDown(aPoint);
+ gdk_device_warp(GdkGetPointer(), screen, point.x, point.y);
+ return NS_OK;
+ }
+ case NativeMouseMessage::EnterWindow:
+ case NativeMouseMessage::LeaveWindow:
+ MOZ_ASSERT_UNREACHABLE("Non supported mouse event on Linux");
+ return NS_ERROR_INVALID_ARG;
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+void nsWindow::CreateAndPutGdkScrollEvent(mozilla::LayoutDeviceIntPoint aPoint,
+ double aDeltaX, double aDeltaY) {
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+ event.type = GDK_SCROLL;
+ event.scroll.window = mGdkWindow;
+ event.scroll.time = GDK_CURRENT_TIME;
+ // Get device for event source
+ GdkDisplay* display = gdk_window_get_display(mGdkWindow);
+ GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
+ // See note in nsWindow::SynthesizeNativeTouchpadPan about the device we use
+ // here.
+ event.scroll.device = gdk_device_manager_get_client_pointer(device_manager);
+ event.scroll.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
+ event.scroll.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ event.scroll.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
+ event.scroll.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
+
+ // The delta values are backwards on Linux compared to Windows and Cocoa,
+ // hence the negation.
+ event.scroll.direction = GDK_SCROLL_SMOOTH;
+ event.scroll.delta_x = -aDeltaX;
+ event.scroll.delta_y = -aDeltaY;
+
+ gdk_event_put(&event);
+}
+
+nsresult nsWindow::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");
+
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+
+ CreateAndPutGdkScrollEvent(aPoint, aDeltaX, aDeltaY);
+
+ return NS_OK;
+}
+
+nsresult nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+
+ static std::map<uint32_t, GdkEventSequence*> sKnownPointers;
+
+ auto result = sKnownPointers.find(aPointerId);
+ switch (aPointerState) {
+ case TOUCH_CONTACT:
+ if (result == sKnownPointers.end()) {
+ // GdkEventSequence isn't a thing we can instantiate, and never gets
+ // dereferenced in the gtk code. It's an opaque pointer, the only
+ // requirement is that it be distinct from other instances of
+ // GdkEventSequence*.
+ event.touch.sequence = (GdkEventSequence*)((uintptr_t)aPointerId);
+ sKnownPointers[aPointerId] = event.touch.sequence;
+ event.type = GDK_TOUCH_BEGIN;
+ } else {
+ event.touch.sequence = result->second;
+ event.type = GDK_TOUCH_UPDATE;
+ }
+ break;
+ case TOUCH_REMOVE:
+ event.type = GDK_TOUCH_END;
+ if (result == sKnownPointers.end()) {
+ NS_WARNING("Tried to synthesize touch-end for unknown pointer!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ event.touch.sequence = result->second;
+ sKnownPointers.erase(result);
+ break;
+ case TOUCH_CANCEL:
+ event.type = GDK_TOUCH_CANCEL;
+ if (result == sKnownPointers.end()) {
+ NS_WARNING("Tried to synthesize touch-cancel for unknown pointer!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ event.touch.sequence = result->second;
+ sKnownPointers.erase(result);
+ break;
+ case TOUCH_HOVER:
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ event.touch.window = mGdkWindow;
+ event.touch.time = GDK_CURRENT_TIME;
+
+ GdkDisplay* display = gdk_window_get_display(mGdkWindow);
+ GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
+ event.touch.device = gdk_device_manager_get_client_pointer(device_manager);
+
+ event.touch.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
+ event.touch.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ event.touch.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
+ event.touch.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
+
+ gdk_event_put(&event);
+
+ return NS_OK;
+}
+
+nsresult nsWindow::SynthesizeNativeTouchPadPinch(
+ TouchpadGesturePhase aEventPhase, float aScale, LayoutDeviceIntPoint aPoint,
+ int32_t aModifierFlags) {
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+
+ GdkEventTouchpadPinch* touchpad_event =
+ reinterpret_cast<GdkEventTouchpadPinch*>(&event);
+ touchpad_event->type = GDK_TOUCHPAD_PINCH;
+
+ const ScreenIntPoint widgetToScreenOffset = ViewAs<ScreenPixel>(
+ WidgetToScreenOffset(),
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+
+ ScreenPoint pointInWindow =
+ ViewAs<ScreenPixel>(
+ aPoint,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent) -
+ widgetToScreenOffset;
+
+ gdouble dx = 0, dy = 0;
+
+ switch (aEventPhase) {
+ case PHASE_BEGIN:
+ touchpad_event->phase = GDK_TOUCHPAD_GESTURE_PHASE_BEGIN;
+ mCurrentSynthesizedTouchpadPinch = {pointInWindow, pointInWindow};
+ break;
+ case PHASE_UPDATE:
+ dx = pointInWindow.x - mCurrentSynthesizedTouchpadPinch.mCurrentFocus.x;
+ dy = pointInWindow.y - mCurrentSynthesizedTouchpadPinch.mCurrentFocus.y;
+ mCurrentSynthesizedTouchpadPinch.mCurrentFocus = pointInWindow;
+ touchpad_event->phase = GDK_TOUCHPAD_GESTURE_PHASE_UPDATE;
+ break;
+ case PHASE_END:
+ touchpad_event->phase = GDK_TOUCHPAD_GESTURE_PHASE_END;
+ break;
+
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ touchpad_event->window = mGdkWindow;
+ // We only set the fields of GdkEventTouchpadPinch which are
+ // actually used in OnTouchpadPinchEvent().
+ // GdkEventTouchpadPinch has additional fields.
+ // If OnTouchpadPinchEvent() is changed to use other fields, this function
+ // will need to change to set them as well.
+ touchpad_event->time = GDK_CURRENT_TIME;
+ touchpad_event->scale = aScale;
+ touchpad_event->x_root = DevicePixelsToGdkCoordRoundDown(
+ mCurrentSynthesizedTouchpadPinch.mBeginFocus.x +
+ ScreenCoord(widgetToScreenOffset.x));
+ touchpad_event->y_root = DevicePixelsToGdkCoordRoundDown(
+ mCurrentSynthesizedTouchpadPinch.mBeginFocus.y +
+ ScreenCoord(widgetToScreenOffset.y));
+
+ touchpad_event->x = DevicePixelsToGdkCoordRoundDown(
+ mCurrentSynthesizedTouchpadPinch.mBeginFocus.x);
+ touchpad_event->y = DevicePixelsToGdkCoordRoundDown(
+ mCurrentSynthesizedTouchpadPinch.mBeginFocus.y);
+
+ touchpad_event->dx = dx;
+ touchpad_event->dy = dy;
+
+ touchpad_event->state = aModifierFlags;
+
+ gdk_event_put(&event);
+
+ return NS_OK;
+}
+
+nsresult nsWindow::SynthesizeNativeTouchpadPan(TouchpadGesturePhase aEventPhase,
+ LayoutDeviceIntPoint aPoint,
+ double aDeltaX, double aDeltaY,
+ int32_t aModifierFlags,
+ nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "touchpadpanevent");
+
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+
+ // This should/could maybe send GdkEventTouchpadSwipe events, however we don't
+ // currently consume those (either real user input or testing events). So we
+ // send gdk scroll events to be more like what we do for real user input. If
+ // we start consuming GdkEventTouchpadSwipe and get those hooked up to swipe
+ // to nav, then maybe we should test those too.
+
+ mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase = Some(aEventPhase);
+ MOZ_ASSERT(mCurrentSynthesizedTouchpadPan.mSavedObserver == 0);
+ mCurrentSynthesizedTouchpadPan.mSavedObserver = notifier.SaveObserver();
+
+ // Note that CreateAndPutGdkScrollEvent sets the device source for the created
+ // event as the "client pointer" (a kind of default device) which will
+ // probably be of type mouse. We would ideally want to set the device of the
+ // created event to be a touchpad, but the system might not have a touchpad.
+ // To get around this we use
+ // mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase being something to
+ // indicate that we should treat the source of the event as touchpad in
+ // OnScrollEvent.
+ CreateAndPutGdkScrollEvent(aPoint, aDeltaX, aDeltaY);
+
+ return NS_OK;
+}
+
+nsWindow::GtkWindowDecoration nsWindow::GetSystemGtkWindowDecoration() {
+ static GtkWindowDecoration sGtkWindowDecoration = [] {
+ // Allow MOZ_GTK_TITLEBAR_DECORATION to override our heuristics
+ if (const char* decorationOverride =
+ getenv("MOZ_GTK_TITLEBAR_DECORATION")) {
+ if (strcmp(decorationOverride, "none") == 0) {
+ return GTK_DECORATION_NONE;
+ }
+ if (strcmp(decorationOverride, "client") == 0) {
+ return GTK_DECORATION_CLIENT;
+ }
+ if (strcmp(decorationOverride, "system") == 0) {
+ return GTK_DECORATION_SYSTEM;
+ }
+ }
+
+ // nsWindow::GetSystemGtkWindowDecoration can be called from various
+ // threads so we can't use gfxPlatformGtk here.
+ if (GdkIsWaylandDisplay()) {
+ return GTK_DECORATION_CLIENT;
+ }
+
+ // GTK_CSD forces CSD mode - use also CSD because window manager
+ // decorations does not work with CSD.
+ // We check GTK_CSD as well as gtk_window_should_use_csd() does.
+ if (const char* csdOverride = getenv("GTK_CSD")) {
+ return *csdOverride == '0' ? GTK_DECORATION_NONE : GTK_DECORATION_CLIENT;
+ }
+
+ // TODO: Consider switching this to GetDesktopEnvironmentIdentifier().
+ const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
+ if (!currentDesktop) {
+ return GTK_DECORATION_NONE;
+ }
+ if (strstr(currentDesktop, "i3")) {
+ return GTK_DECORATION_NONE;
+ }
+
+ // Tested desktops: pop:GNOME, KDE, Enlightenment, LXDE, openbox, MATE,
+ // X-Cinnamon, Pantheon, Deepin, GNOME, LXQt, Unity.
+ return GTK_DECORATION_CLIENT;
+ }();
+ return sGtkWindowDecoration;
+}
+
+bool nsWindow::TitlebarUseShapeMask() {
+ static int useShapeMask = []() {
+ // Don't use titlebar shape mask on Wayland
+ if (!GdkIsX11Display()) {
+ return false;
+ }
+
+ // We can't use shape masks on Mutter/X.org as we can't resize Firefox
+ // window there (Bug 1530252).
+ if (IsGnomeDesktopEnvironment()) {
+ return false;
+ }
+
+ return Preferences::GetBool("widget.titlebar-x11-use-shape-mask", false);
+ }();
+ return useShapeMask;
+}
+
+int32_t nsWindow::RoundsWidgetCoordinatesTo() { return GdkCeiledScaleFactor(); }
+
+void nsWindow::GetCompositorWidgetInitData(
+ mozilla::widget::CompositorWidgetInitData* aInitData) {
+ nsCString displayName;
+
+ LOG("nsWindow::GetCompositorWidgetInitData");
+ EnsureGdkWindow();
+
+ *aInitData = mozilla::widget::GtkCompositorWidgetInitData(
+ GetX11Window(), displayName, GetShapedState(), GdkIsX11Display(),
+ GetClientSize());
+
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ // Make sure the window XID is propagated to X server, we can fail otherwise
+ // in GPU process (Bug 1401634).
+ Display* display = DefaultXDisplay();
+ XFlush(display);
+ displayName = nsCString(XDisplayString(display));
+ }
+#endif
+}
+
+#ifdef MOZ_X11
+/* XApp progress support currently works by setting a property
+ * on a window with this Atom name. A supporting window manager
+ * will notice this and pass it along to whatever handling has
+ * been implemented on that end (e.g. passing it on to a taskbar
+ * widget.) There is no issue if WM support is lacking, this is
+ * simply ignored in that case.
+ *
+ * See https://github.com/linuxmint/xapps/blob/master/libxapp/xapp-gtk-window.c
+ * for further details.
+ */
+
+# define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS"
+
+static void set_window_hint_cardinal(Window xid, const gchar* atom_name,
+ gulong cardinal) {
+ GdkDisplay* display;
+
+ display = gdk_display_get_default();
+
+ if (cardinal > 0) {
+ XChangeProperty(GDK_DISPLAY_XDISPLAY(display), xid,
+ gdk_x11_get_xatom_by_name_for_display(display, atom_name),
+ XA_CARDINAL, 32, PropModeReplace, (guchar*)&cardinal, 1);
+ } else {
+ XDeleteProperty(GDK_DISPLAY_XDISPLAY(display), xid,
+ gdk_x11_get_xatom_by_name_for_display(display, atom_name));
+ }
+}
+#endif // MOZ_X11
+
+void nsWindow::SetProgress(unsigned long progressPercent) {
+#ifdef MOZ_X11
+
+ if (!GdkIsX11Display()) {
+ return;
+ }
+
+ if (!mShell) {
+ return;
+ }
+
+ progressPercent = MIN(progressPercent, 100);
+
+ set_window_hint_cardinal(GDK_WINDOW_XID(gtk_widget_get_window(mShell)),
+ PROGRESS_HINT, progressPercent);
+#endif // MOZ_X11
+}
+
+#ifdef MOZ_X11
+void nsWindow::SetCompositorHint(WindowComposeRequest aState) {
+ if (!GdkIsX11Display()) {
+ return;
+ }
+
+ gulong value = aState;
+ GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
+ gdk_property_change(gtk_widget_get_window(mShell),
+ gdk_atom_intern("_NET_WM_BYPASS_COMPOSITOR", FALSE),
+ cardinal_atom,
+ 32, // format
+ GDK_PROP_MODE_REPLACE, (guchar*)&value, 1);
+}
+#endif
+
+nsresult nsWindow::SetSystemFont(const nsCString& aFontName) {
+ GtkSettings* settings = gtk_settings_get_default();
+ g_object_set(settings, "gtk-font-name", aFontName.get(), nullptr);
+ return NS_OK;
+}
+
+nsresult nsWindow::GetSystemFont(nsCString& aFontName) {
+ GtkSettings* settings = gtk_settings_get_default();
+ gchar* fontName = nullptr;
+ g_object_get(settings, "gtk-font-name", &fontName, nullptr);
+ if (fontName) {
+ aFontName.Assign(fontName);
+ g_free(fontName);
+ }
+ return NS_OK;
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
+ nsCOMPtr<nsIWidget> window = new nsWindow();
+ return window.forget();
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
+ nsCOMPtr<nsIWidget> window = new nsWindow();
+ return window.forget();
+}
+
+#ifdef MOZ_WAYLAND
+static void relative_pointer_handle_relative_motion(
+ void* data, struct zwp_relative_pointer_v1* pointer, uint32_t time_hi,
+ uint32_t time_lo, wl_fixed_t dx_w, wl_fixed_t dy_w, wl_fixed_t dx_unaccel_w,
+ wl_fixed_t dy_unaccel_w) {
+ RefPtr<nsWindow> window(reinterpret_cast<nsWindow*>(data));
+
+ WidgetMouseEvent event(true, eMouseMove, window, WidgetMouseEvent::eReal);
+
+ event.mRefPoint = window->GetNativePointerLockCenter();
+ event.mRefPoint.x += wl_fixed_to_int(dx_w);
+ event.mRefPoint.y += wl_fixed_to_int(dy_w);
+
+ event.AssignEventTime(window->GetWidgetEventTime(time_lo));
+ window->DispatchInputEvent(&event);
+}
+
+static const struct zwp_relative_pointer_v1_listener relative_pointer_listener =
+ {
+ relative_pointer_handle_relative_motion,
+};
+
+void nsWindow::SetNativePointerLockCenter(
+ const LayoutDeviceIntPoint& aLockCenter) {
+ mNativePointerLockCenter = aLockCenter;
+}
+
+void nsWindow::LockNativePointer() {
+ if (!GdkIsWaylandDisplay()) {
+ return;
+ }
+
+ auto* waylandDisplay = WaylandDisplayGet();
+
+ auto* pointerConstraints = waylandDisplay->GetPointerConstraints();
+ if (!pointerConstraints) {
+ return;
+ }
+
+ auto* relativePointerMgr = waylandDisplay->GetRelativePointerManager();
+ if (!relativePointerMgr) {
+ return;
+ }
+
+ GdkDisplay* display = gdk_display_get_default();
+
+ GdkDeviceManager* manager = gdk_display_get_device_manager(display);
+ MOZ_ASSERT(manager);
+
+ GdkDevice* device = gdk_device_manager_get_client_pointer(manager);
+ if (!device) {
+ NS_WARNING("Could not find Wayland pointer to lock");
+ return;
+ }
+ wl_pointer* pointer = gdk_wayland_device_get_wl_pointer(device);
+ MOZ_ASSERT(pointer);
+
+ wl_surface* surface =
+ gdk_wayland_window_get_wl_surface(gtk_widget_get_window(GetGtkWidget()));
+ if (!surface) {
+ /* Can be null when the window is hidden.
+ * Though it's unlikely that a lock request comes in that case, be
+ * defensive. */
+ return;
+ }
+
+ if (mLockedPointer || mRelativePointer) {
+ UnlockNativePointer();
+ }
+
+ mLockedPointer = zwp_pointer_constraints_v1_lock_pointer(
+ pointerConstraints, surface, pointer, nullptr,
+ ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
+ if (!mLockedPointer) {
+ NS_WARNING("Could not lock Wayland pointer");
+ return;
+ }
+
+ mRelativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer(
+ relativePointerMgr, pointer);
+ if (!mRelativePointer) {
+ NS_WARNING("Could not create relative Wayland pointer");
+ zwp_locked_pointer_v1_destroy(mLockedPointer);
+ mLockedPointer = nullptr;
+ return;
+ }
+
+ zwp_relative_pointer_v1_add_listener(mRelativePointer,
+ &relative_pointer_listener, this);
+}
+
+void nsWindow::UnlockNativePointer() {
+ if (!GdkIsWaylandDisplay()) {
+ return;
+ }
+ if (mRelativePointer) {
+ zwp_relative_pointer_v1_destroy(mRelativePointer);
+ mRelativePointer = nullptr;
+ }
+ if (mLockedPointer) {
+ zwp_locked_pointer_v1_destroy(mLockedPointer);
+ mLockedPointer = nullptr;
+ }
+}
+#endif
+
+bool nsWindow::GetTopLevelWindowActiveState(nsIFrame* aFrame) {
+ // Used by window frame and button box rendering. We can end up in here in
+ // the content process when rendering one of these moz styles freely in a
+ // page. Fail in this case, there is no applicable window focus state.
+ if (!XRE_IsParentProcess()) {
+ return false;
+ }
+ // All headless windows are considered active so they are painted.
+ if (gfxPlatform::IsHeadless()) {
+ return true;
+ }
+ // Get the widget. nsIFrame's GetNearestWidget walks up the view chain
+ // until it finds a real window.
+ nsWindow* window = static_cast<nsWindow*>(aFrame->GetNearestWidget());
+ if (!window) {
+ return false;
+ }
+ return !window->mTitlebarBackdropState;
+}
+
+static nsIFrame* FindTitlebarFrame(nsIFrame* aFrame) {
+ for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
+ StyleAppearance appearance =
+ childFrame->StyleDisplay()->EffectiveAppearance();
+ if (appearance == StyleAppearance::MozWindowTitlebar ||
+ appearance == StyleAppearance::MozWindowTitlebarMaximized) {
+ return childFrame;
+ }
+
+ if (nsIFrame* foundFrame = FindTitlebarFrame(childFrame)) {
+ return foundFrame;
+ }
+ }
+ return nullptr;
+}
+
+nsIFrame* nsWindow::GetFrame() const {
+ nsView* view = nsView::GetViewFor(this);
+ if (!view) {
+ return nullptr;
+ }
+ return view->GetFrame();
+}
+
+void nsWindow::UpdateMozWindowActive() {
+ // Update activation state for the :-moz-window-inactive pseudoclass.
+ // Normally, this follows focus; we override it here to follow
+ // GDK_WINDOW_STATE_FOCUSED.
+ if (mozilla::dom::Document* document = GetDocument()) {
+ if (nsPIDOMWindowOuter* window = document->GetWindow()) {
+ if (RefPtr<mozilla::dom::BrowsingContext> bc =
+ window->GetBrowsingContext()) {
+ bc->SetIsActiveBrowserWindow(!mTitlebarBackdropState);
+ }
+ }
+ }
+}
+
+void nsWindow::ForceTitlebarRedraw() {
+ MOZ_ASSERT(mDrawInTitlebar, "We should not redraw invisible titlebar.");
+
+ if (!mWidgetListener || !mWidgetListener->GetPresShell()) {
+ return;
+ }
+
+ nsIFrame* frame = GetFrame();
+ if (!frame) {
+ return;
+ }
+
+ frame = FindTitlebarFrame(frame);
+ if (frame) {
+ nsIContent* content = frame->GetContent();
+ if (content) {
+ nsLayoutUtils::PostRestyleEvent(content->AsElement(), RestyleHint{0},
+ nsChangeHint_RepaintFrame);
+ }
+ }
+}
+
+void nsWindow::LockAspectRatio(bool aShouldLock) {
+ if (!gUseAspectRatio) {
+ return;
+ }
+
+ if (aShouldLock) {
+ int decWidth = 0, decHeight = 0;
+ AddCSDDecorationSize(&decWidth, &decHeight);
+
+ float width =
+ DevicePixelsToGdkCoordRoundDown(mLastSizeRequest.width) + decWidth;
+ float height =
+ DevicePixelsToGdkCoordRoundDown(mLastSizeRequest.height) + decHeight;
+
+ mAspectRatio = width / height;
+ LOG("nsWindow::LockAspectRatio() width %f height %f aspect %f", width,
+ height, mAspectRatio);
+ } else {
+ mAspectRatio = 0.0;
+ LOG("nsWindow::LockAspectRatio() removed aspect ratio");
+ }
+
+ ApplySizeConstraints();
+}
+
+nsWindow* nsWindow::GetFocusedWindow() { return gFocusWindow; }
+
+#ifdef MOZ_WAYLAND
+void nsWindow::SetEGLNativeWindowSize(
+ const LayoutDeviceIntSize& aEGLWindowSize) {
+ if (!mContainer || !GdkIsWaylandDisplay()) {
+ return;
+ }
+ if (moz_container_wayland_egl_window_needs_size_update(
+ mContainer, aEGLWindowSize.ToUnknownSize(), GdkCeiledScaleFactor())) {
+ LOG("nsWindow::SetEGLNativeWindowSize() %d x %d", aEGLWindowSize.width,
+ aEGLWindowSize.height);
+ moz_container_wayland_egl_window_set_size(mContainer,
+ aEGLWindowSize.ToUnknownSize());
+ moz_container_wayland_set_scale_factor(mContainer);
+ }
+}
+#endif
+
+nsWindow* nsWindow::GetWindow(GdkWindow* window) {
+ return get_window_for_gdk_window(window);
+}
+
+void nsWindow::ClearRenderingQueue() {
+ LOG("nsWindow::ClearRenderingQueue()");
+
+ if (mWidgetListener) {
+ mWidgetListener->RequestWindowClose(this);
+ }
+ DestroyLayerManager();
+}
+
+// Apply workaround for Mutter compositor bug (mzbz#1777269).
+//
+// When we open a popup window (tooltip for instance) attached to
+// GDK_WINDOW_TYPE_HINT_UTILITY parent popup, Mutter compositor sends bogus
+// leave/enter events to the GDK_WINDOW_TYPE_HINT_UTILITY popup.
+// That leads to immediate tooltip close. As a workaround ignore these
+// bogus events.
+//
+// We need to check two affected window types:
+//
+// - toplevel window with at least two child popups where the first one is
+// GDK_WINDOW_TYPE_HINT_UTILITY.
+// - GDK_WINDOW_TYPE_HINT_UTILITY popup with a child popup
+//
+// We need to mask two bogus leave/enter sequences:
+// 1) Leave (popup) -> Enter (toplevel)
+// 2) Leave (toplevel) -> Enter (popup)
+//
+// TODO: persistent (non-tracked) popups with tooltip/child popups?
+//
+bool nsWindow::ApplyEnterLeaveMutterWorkaround() {
+ // Leave (toplevel) case
+ if (mWindowType == WindowType::TopLevel && mWaylandPopupNext &&
+ mWaylandPopupNext->mWaylandPopupNext &&
+ gtk_window_get_type_hint(GTK_WINDOW(mWaylandPopupNext->GetGtkWidget())) ==
+ GDK_WINDOW_TYPE_HINT_UTILITY) {
+ LOG("nsWindow::ApplyEnterLeaveMutterWorkaround(): leave toplevel");
+ return true;
+ }
+ // Leave (popup) case
+ if (IsWaylandPopup() && mWaylandPopupNext &&
+ gtk_window_get_type_hint(GTK_WINDOW(mShell)) ==
+ GDK_WINDOW_TYPE_HINT_UTILITY) {
+ LOG("nsWindow::ApplyEnterLeaveMutterWorkaround(): leave popup");
+ return true;
+ }
+ return false;
+}
+
+void nsWindow::NotifyOcclusionState(mozilla::widget::OcclusionState aState) {
+ if (mWindowType != WindowType::TopLevel) {
+ return;
+ }
+
+ bool isFullyOccluded = aState == mozilla::widget::OcclusionState::OCCLUDED;
+ if (mIsFullyOccluded == isFullyOccluded) {
+ return;
+ }
+ mIsFullyOccluded = isFullyOccluded;
+
+ LOG("nsWindow::NotifyOcclusionState() mIsFullyOccluded %d", mIsFullyOccluded);
+ if (mWidgetListener) {
+ mWidgetListener->OcclusionStateChanged(mIsFullyOccluded);
+ }
+}
+
+void nsWindow::SetDragSource(GdkDragContext* aSourceDragContext) {
+ mSourceDragContext = aSourceDragContext;
+ if (IsPopup() &&
+ (widget::GdkIsWaylandDisplay() || widget::IsXWaylandProtocol())) {
+ if (auto* menuPopupFrame = GetMenuPopupFrame(GetFrame())) {
+ menuPopupFrame->SetIsDragSource(!!aSourceDragContext);
+ }
+ }
+}