Bug 1884032 [wpt PR 44942] - [css-color] add missing colorscheme-aware tests, a=testonly
[gecko.git] / widget / gtk / nsWindow.cpp
blob190ca27ec64d1d5c2a5b0ac209f8934d3e393911
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
3 */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "nsWindow.h"
10 #include <algorithm>
11 #include <cstdint>
12 #include <dlfcn.h>
13 #include <gdk/gdkkeysyms.h>
14 #include <wchar.h>
16 #include "VsyncSource.h"
17 #include "gfx2DGlue.h"
18 #include "gfxContext.h"
19 #include "gfxImageSurface.h"
20 #include "gfxPlatformGtk.h"
21 #include "gfxUtils.h"
22 #include "GLContextProvider.h"
23 #include "GLContext.h"
24 #include "GtkCompositorWidget.h"
25 #include "gtkdrawing.h"
26 #include "imgIContainer.h"
27 #include "InputData.h"
28 #include "mozilla/ArrayUtils.h"
29 #include "mozilla/Assertions.h"
30 #include "mozilla/Components.h"
31 #include "mozilla/GRefPtr.h"
32 #include "mozilla/dom/Document.h"
33 #include "mozilla/dom/WheelEventBinding.h"
34 #include "mozilla/gfx/2D.h"
35 #include "mozilla/gfx/gfxVars.h"
36 #include "mozilla/gfx/GPUProcessManager.h"
37 #include "mozilla/gfx/HelpersCairo.h"
38 #include "mozilla/layers/APZThreadUtils.h"
39 #include "mozilla/layers/LayersTypes.h"
40 #include "mozilla/layers/CompositorBridgeChild.h"
41 #include "mozilla/layers/CompositorBridgeParent.h"
42 #include "mozilla/layers/CompositorThread.h"
43 #include "mozilla/layers/KnowsCompositor.h"
44 #include "mozilla/layers/WebRenderBridgeChild.h"
45 #include "mozilla/layers/WebRenderLayerManager.h"
46 #include "mozilla/layers/APZInputBridge.h"
47 #include "mozilla/layers/IAPZCTreeManager.h"
48 #include "mozilla/Likely.h"
49 #include "mozilla/MiscEvents.h"
50 #include "mozilla/MouseEvents.h"
51 #include "mozilla/NativeKeyBindingsType.h"
52 #include "mozilla/Preferences.h"
53 #include "mozilla/PresShell.h"
54 #include "mozilla/ProfilerLabels.h"
55 #include "mozilla/ScopeExit.h"
56 #include "mozilla/StaticPrefs_apz.h"
57 #include "mozilla/StaticPrefs_layout.h"
58 #include "mozilla/StaticPrefs_mozilla.h"
59 #include "mozilla/StaticPrefs_ui.h"
60 #include "mozilla/StaticPrefs_widget.h"
61 #include "mozilla/SwipeTracker.h"
62 #include "mozilla/TextEventDispatcher.h"
63 #include "mozilla/TextEvents.h"
64 #include "mozilla/TimeStamp.h"
65 #include "mozilla/UniquePtrExtensions.h"
66 #include "mozilla/WidgetUtils.h"
67 #include "mozilla/WritingModes.h"
68 #ifdef MOZ_X11
69 # include "mozilla/X11Util.h"
70 #endif
71 #include "mozilla/XREAppData.h"
72 #include "NativeKeyBindings.h"
73 #include "nsAppDirectoryServiceDefs.h"
74 #include "nsAppRunner.h"
75 #include "nsDragService.h"
76 #include "nsGTKToolkit.h"
77 #include "nsGtkKeyUtils.h"
78 #include "nsGtkCursors.h"
79 #include "nsGfxCIID.h"
80 #include "nsGtkUtils.h"
81 #include "nsIFile.h"
82 #include "nsIGSettingsService.h"
83 #include "nsIInterfaceRequestorUtils.h"
84 #include "nsImageToPixbuf.h"
85 #include "nsINode.h"
86 #include "nsIRollupListener.h"
87 #include "nsIScreenManager.h"
88 #include "nsIUserIdleServiceInternal.h"
89 #include "nsIWidgetListener.h"
90 #include "nsLayoutUtils.h"
91 #include "nsMenuPopupFrame.h"
92 #include "nsPresContext.h"
93 #include "nsShmImage.h"
94 #include "nsString.h"
95 #include "nsWidgetsCID.h"
96 #include "nsViewManager.h"
97 #include "nsXPLookAndFeel.h"
98 #include "prlink.h"
99 #include "Screen.h"
100 #include "ScreenHelperGTK.h"
101 #include "SystemTimeConverter.h"
102 #include "WidgetUtilsGtk.h"
103 #include "NativeMenuGtk.h"
105 #ifdef ACCESSIBILITY
106 # include "mozilla/a11y/LocalAccessible.h"
107 # include "mozilla/a11y/Platform.h"
108 # include "nsAccessibilityService.h"
109 #endif
111 #ifdef MOZ_X11
112 # include <gdk/gdkkeysyms-compat.h>
113 # include <X11/Xatom.h>
114 # include <X11/extensions/XShm.h>
115 # include <X11/extensions/shape.h>
116 # include "gfxXlibSurface.h"
117 # include "GLContextGLX.h" // for GLContextGLX::FindVisual()
118 # include "GLContextEGL.h" // for GLContextEGL::FindVisual()
119 # include "WindowSurfaceX11Image.h"
120 # include "WindowSurfaceX11SHM.h"
121 #endif
122 #ifdef MOZ_WAYLAND
123 # include <gdk/gdkkeysyms-compat.h>
124 # include "nsIClipboard.h"
125 # include "nsView.h"
126 #endif
128 using namespace mozilla;
129 using namespace mozilla::gfx;
130 using namespace mozilla::layers;
131 using namespace mozilla::widget;
132 #ifdef MOZ_X11
133 using mozilla::gl::GLContextEGL;
134 using mozilla::gl::GLContextGLX;
135 #endif
137 // Don't put more than this many rects in the dirty region, just fluff
138 // out to the bounding-box if there are more
139 #define MAX_RECTS_IN_REGION 100
141 #if !GTK_CHECK_VERSION(3, 18, 0)
143 struct _GdkEventTouchpadPinch {
144 GdkEventType type;
145 GdkWindow* window;
146 gint8 send_event;
147 gint8 phase;
148 gint8 n_fingers;
149 guint32 time;
150 gdouble x;
151 gdouble y;
152 gdouble dx;
153 gdouble dy;
154 gdouble angle_delta;
155 gdouble scale;
156 gdouble x_root, y_root;
157 guint state;
160 typedef enum {
161 GDK_TOUCHPAD_GESTURE_PHASE_BEGIN,
162 GDK_TOUCHPAD_GESTURE_PHASE_UPDATE,
163 GDK_TOUCHPAD_GESTURE_PHASE_END,
164 GDK_TOUCHPAD_GESTURE_PHASE_CANCEL
165 } GdkTouchpadGesturePhase;
167 gint GDK_TOUCHPAD_GESTURE_MASK = 1 << 24;
168 GdkEventType GDK_TOUCHPAD_PINCH = static_cast<GdkEventType>(42);
170 #endif
172 const gint kEvents = GDK_TOUCHPAD_GESTURE_MASK | GDK_EXPOSURE_MASK |
173 GDK_STRUCTURE_MASK | GDK_VISIBILITY_NOTIFY_MASK |
174 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
175 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
176 GDK_SMOOTH_SCROLL_MASK | GDK_TOUCH_MASK | GDK_SCROLL_MASK |
177 GDK_POINTER_MOTION_MASK | GDK_PROPERTY_CHANGE_MASK;
179 /* utility functions */
180 static bool is_mouse_in_window(GdkWindow* aWindow, gdouble aMouseX,
181 gdouble aMouseY);
182 static nsWindow* get_window_for_gtk_widget(GtkWidget* widget);
183 static nsWindow* get_window_for_gdk_window(GdkWindow* window);
184 static GtkWidget* get_gtk_widget_for_gdk_window(GdkWindow* window);
185 static GdkCursor* get_gtk_cursor(nsCursor aCursor);
187 /* callbacks from widgets */
188 static gboolean expose_event_cb(GtkWidget* widget, cairo_t* cr);
189 static gboolean configure_event_cb(GtkWidget* widget, GdkEventConfigure* event);
190 static void widget_map_cb(GtkWidget* widget);
191 static void widget_unmap_cb(GtkWidget* widget);
192 static void size_allocate_cb(GtkWidget* widget, GtkAllocation* allocation);
193 static void toplevel_window_size_allocate_cb(GtkWidget* widget,
194 GtkAllocation* allocation);
195 static gboolean delete_event_cb(GtkWidget* widget, GdkEventAny* event);
196 static gboolean enter_notify_event_cb(GtkWidget* widget,
197 GdkEventCrossing* event);
198 static gboolean leave_notify_event_cb(GtkWidget* widget,
199 GdkEventCrossing* event);
200 static gboolean motion_notify_event_cb(GtkWidget* widget,
201 GdkEventMotion* event);
202 MOZ_CAN_RUN_SCRIPT static gboolean button_press_event_cb(GtkWidget* widget,
203 GdkEventButton* event);
204 static gboolean button_release_event_cb(GtkWidget* widget,
205 GdkEventButton* event);
206 static gboolean focus_in_event_cb(GtkWidget* widget, GdkEventFocus* event);
207 static gboolean focus_out_event_cb(GtkWidget* widget, GdkEventFocus* event);
208 static gboolean key_press_event_cb(GtkWidget* widget, GdkEventKey* event);
209 static gboolean key_release_event_cb(GtkWidget* widget, GdkEventKey* event);
210 static gboolean property_notify_event_cb(GtkWidget* widget,
211 GdkEventProperty* event);
212 static gboolean scroll_event_cb(GtkWidget* widget, GdkEventScroll* event);
213 static gboolean visibility_notify_event_cb(GtkWidget* widget,
214 GdkEventVisibility* event);
215 static void hierarchy_changed_cb(GtkWidget* widget,
216 GtkWidget* previous_toplevel);
217 static gboolean window_state_event_cb(GtkWidget* widget,
218 GdkEventWindowState* event);
219 static void settings_xft_dpi_changed_cb(GtkSettings* settings,
220 GParamSpec* pspec, nsWindow* data);
221 static void check_resize_cb(GtkContainer* container, gpointer user_data);
222 static void screen_composited_changed_cb(GdkScreen* screen, gpointer user_data);
223 static void widget_composited_changed_cb(GtkWidget* widget, gpointer user_data);
225 static void scale_changed_cb(GtkWidget* widget, GParamSpec* aPSpec,
226 gpointer aPointer);
227 static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent);
228 static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent);
230 static nsWindow* GetFirstNSWindowForGDKWindow(GdkWindow* aGdkWindow);
232 #ifdef __cplusplus
233 extern "C" {
234 #endif /* __cplusplus */
235 #ifdef MOZ_X11
236 static GdkFilterReturn popup_take_focus_filter(GdkXEvent* gdk_xevent,
237 GdkEvent* event, gpointer data);
238 #endif /* MOZ_X11 */
239 #ifdef __cplusplus
241 #endif /* __cplusplus */
243 static gboolean drag_motion_event_cb(GtkWidget* aWidget,
244 GdkDragContext* aDragContext, gint aX,
245 gint aY, guint aTime, gpointer aData);
246 static void drag_leave_event_cb(GtkWidget* aWidget,
247 GdkDragContext* aDragContext, guint aTime,
248 gpointer aData);
249 static gboolean drag_drop_event_cb(GtkWidget* aWidget,
250 GdkDragContext* aDragContext, gint aX,
251 gint aY, guint aTime, gpointer aData);
252 static void drag_data_received_event_cb(GtkWidget* aWidget,
253 GdkDragContext* aDragContext, gint aX,
254 gint aY,
255 GtkSelectionData* aSelectionData,
256 guint aInfo, guint32 aTime,
257 gpointer aData);
259 /* initialization static functions */
260 static nsresult initialize_prefs(void);
262 static guint32 sLastUserInputTime = GDK_CURRENT_TIME;
264 static SystemTimeConverter<guint32>& TimeConverter() {
265 static SystemTimeConverter<guint32> sTimeConverterSingleton;
266 return sTimeConverterSingleton;
269 bool nsWindow::sTransparentMainWindow = false;
271 // forward declare from mozgtk
272 extern "C" MOZ_EXPORT void mozgtk_linker_holder();
274 namespace mozilla {
276 #ifdef MOZ_X11
277 class CurrentX11TimeGetter {
278 public:
279 explicit CurrentX11TimeGetter(GdkWindow* aWindow) : mWindow(aWindow) {}
281 guint32 GetCurrentTime() const { return gdk_x11_get_server_time(mWindow); }
283 void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
284 // Check for in-flight request
285 if (!mAsyncUpdateStart.IsNull()) {
286 return;
288 mAsyncUpdateStart = aNow;
290 Display* xDisplay = GDK_WINDOW_XDISPLAY(mWindow);
291 Window xWindow = GDK_WINDOW_XID(mWindow);
292 unsigned char c = 'a';
293 Atom timeStampPropAtom = TimeStampPropAtom();
294 XChangeProperty(xDisplay, xWindow, timeStampPropAtom, timeStampPropAtom, 8,
295 PropModeReplace, &c, 1);
296 XFlush(xDisplay);
299 gboolean PropertyNotifyHandler(GtkWidget* aWidget, GdkEventProperty* aEvent) {
300 if (aEvent->atom != gdk_x11_xatom_to_atom(TimeStampPropAtom())) {
301 return FALSE;
304 guint32 eventTime = aEvent->time;
305 TimeStamp lowerBound = mAsyncUpdateStart;
307 TimeConverter().CompensateForBackwardsSkew(eventTime, lowerBound);
308 mAsyncUpdateStart = TimeStamp();
309 return TRUE;
312 private:
313 static Atom TimeStampPropAtom() {
314 return gdk_x11_get_xatom_by_name_for_display(gdk_display_get_default(),
315 "GDK_TIMESTAMP_PROP");
318 // This is safe because this class is stored as a member of mWindow and
319 // won't outlive it.
320 GdkWindow* mWindow;
321 TimeStamp mAsyncUpdateStart;
323 #endif
325 } // namespace mozilla
327 // The window from which the focus manager asks us to dispatch key events.
328 static nsWindow* gFocusWindow = nullptr;
329 static bool gBlockActivateEvent = false;
330 static bool gGlobalsInitialized = false;
331 static bool gUseAspectRatio = true;
332 static uint32_t gLastTouchID = 0;
333 // See Bug 1777269 for details. We don't know if the suspected leave notify
334 // event is a correct one when we get it.
335 // Store it and issue it later from enter notify event if it's correct,
336 // throw it away otherwise.
337 static GUniquePtr<GdkEventCrossing> sStoredLeaveNotifyEvent;
339 #define NS_WINDOW_TITLE_MAX_LENGTH 4095
341 // cursor cache
342 static GdkCursor* gCursorCache[eCursorCount];
344 // Sometimes this actually also includes the state of the modifier keys, but
345 // only the button state bits are used.
346 static guint gButtonState;
348 static inline int32_t GetBitmapStride(int32_t width) {
349 #if defined(MOZ_X11)
350 return (width + 7) / 8;
351 #else
352 return cairo_format_stride_for_width(CAIRO_FORMAT_A1, width);
353 #endif
356 static inline bool TimestampIsNewerThan(guint32 a, guint32 b) {
357 // Timestamps are just the least significant bits of a monotonically
358 // increasing function, and so the use of unsigned overflow arithmetic.
359 return a - b <= G_MAXUINT32 / 2;
362 static void UpdateLastInputEventTime(void* aGdkEvent) {
363 nsCOMPtr<nsIUserIdleServiceInternal> idleService =
364 do_GetService("@mozilla.org/widget/useridleservice;1");
365 if (idleService) {
366 idleService->ResetIdleTimeOut(0);
369 guint timestamp = gdk_event_get_time(static_cast<GdkEvent*>(aGdkEvent));
370 if (timestamp == GDK_CURRENT_TIME) {
371 return;
374 sLastUserInputTime = timestamp;
377 // Don't set parent (transient for) if nothing changes.
378 // gtk_window_set_transient_for() blows up wl_subsurfaces used by aWindow
379 // even if aParent is the same.
380 static void GtkWindowSetTransientFor(GtkWindow* aWindow, GtkWindow* aParent) {
381 GtkWindow* parent = gtk_window_get_transient_for(aWindow);
382 if (parent != aParent) {
383 gtk_window_set_transient_for(aWindow, aParent);
387 #define gtk_window_set_transient_for(a, b) \
389 MOZ_ASSERT_UNREACHABLE( \
390 "gtk_window_set_transient_for() can't be used directly."); \
393 nsWindow::nsWindow()
394 : mTitlebarRectMutex("nsWindow::mTitlebarRectMutex"),
395 mDestroyMutex("nsWindow::mDestroyMutex"),
396 mIsDestroyed(false),
397 mIsShown(false),
398 mNeedsShow(false),
399 mIsMapped(false),
400 mEnabled(true),
401 mCreated(false),
402 mHandleTouchEvent(false),
403 mIsDragPopup(false),
404 mCompositedScreen(gdk_screen_is_composited(gdk_screen_get_default())),
405 mIsAccelerated(false),
406 mIsAlert(false),
407 mWindowShouldStartDragging(false),
408 mHasMappedToplevel(false),
409 mRetryPointerGrab(false),
410 mPanInProgress(false),
411 mTitlebarBackdropState(false),
412 mIsChildWindow(false),
413 mAlwaysOnTop(false),
414 mNoAutoHide(false),
415 mIsTransparent(false),
416 mHasReceivedSizeAllocate(false),
417 mWidgetCursorLocked(false),
418 mUndecorated(false),
419 mPopupTrackInHierarchy(false),
420 mPopupTrackInHierarchyConfigured(false),
421 mHiddenPopupPositioned(false),
422 mTransparencyBitmapForTitlebar(false),
423 mHasAlphaVisual(false),
424 mPopupAnchored(false),
425 mPopupContextMenu(false),
426 mPopupMatchesLayout(false),
427 mPopupChanged(false),
428 mPopupTemporaryHidden(false),
429 mPopupClosed(false),
430 mPopupUseMoveToRect(false),
431 mWaitingForMoveToRectCallback(false),
432 mMovedAfterMoveToRect(false),
433 mResizedAfterMoveToRect(false),
434 mConfiguredClearColor(false),
435 mGotNonBlankPaint(false),
436 mNeedsToRetryCapturingMouse(false) {
437 mWindowType = WindowType::Child;
438 mSizeConstraints.mMaxSize = GetSafeWindowSize(mSizeConstraints.mMaxSize);
440 if (!gGlobalsInitialized) {
441 gGlobalsInitialized = true;
443 // It's OK if either of these fail, but it may not be one day.
444 initialize_prefs();
446 #ifdef MOZ_WAYLAND
447 // Wayland provides clipboard data to application on focus-in event
448 // so we need to init our clipboard hooks before we create window
449 // and get focus.
450 if (GdkIsWaylandDisplay()) {
451 nsCOMPtr<nsIClipboard> clipboard =
452 do_GetService("@mozilla.org/widget/clipboard;1");
453 NS_ASSERTION(clipboard, "Failed to init clipboard!");
455 #endif
457 // Dummy call to mozgtk to prevent the linker from removing
458 // the dependency with --as-needed.
459 // see toolkit/library/moz.build for details.
460 mozgtk_linker_holder();
463 nsWindow::~nsWindow() {
464 LOG("nsWindow::~nsWindow()");
465 Destroy();
468 /* static */
469 void nsWindow::ReleaseGlobals() {
470 for (auto& cursor : gCursorCache) {
471 if (cursor) {
472 g_object_unref(cursor);
473 cursor = nullptr;
478 void nsWindow::DispatchActivateEvent(void) {
479 #ifdef ACCESSIBILITY
480 DispatchActivateEventAccessible();
481 #endif // ACCESSIBILITY
483 if (mWidgetListener) mWidgetListener->WindowActivated();
486 void nsWindow::DispatchDeactivateEvent() {
487 if (mWidgetListener) {
488 mWidgetListener->WindowDeactivated();
491 #ifdef ACCESSIBILITY
492 DispatchDeactivateEventAccessible();
493 #endif // ACCESSIBILITY
496 void nsWindow::DispatchResized() {
497 LOG("nsWindow::DispatchResized() size [%d, %d]", (int)(mBounds.width),
498 (int)(mBounds.height));
500 mNeedsDispatchSize = LayoutDeviceIntSize(-1, -1);
501 if (mWidgetListener) {
502 mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
504 if (mAttachedWidgetListener) {
505 mAttachedWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
509 void nsWindow::MaybeDispatchResized() {
510 if (mNeedsDispatchSize != LayoutDeviceIntSize(-1, -1) && !mIsDestroyed) {
511 mBounds.SizeTo(mNeedsDispatchSize);
512 // Check mBounds size
513 if (mCompositorSession &&
514 !wr::WindowSizeSanityCheck(mBounds.width, mBounds.height)) {
515 gfxCriticalNoteOnce << "Invalid mBounds in MaybeDispatchResized "
516 << mBounds << " size state " << mSizeMode;
519 if (IsTopLevelWindowType()) {
520 UpdateTopLevelOpaqueRegion();
523 // Notify the GtkCompositorWidget of a ClientSizeChange
524 if (mCompositorWidgetDelegate) {
525 mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
528 DispatchResized();
532 nsIWidgetListener* nsWindow::GetListener() {
533 return mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener;
536 nsresult nsWindow::DispatchEvent(WidgetGUIEvent* aEvent,
537 nsEventStatus& aStatus) {
538 #ifdef DEBUG
539 debug_DumpEvent(stdout, aEvent->mWidget, aEvent, "something", 0);
540 #endif
541 aStatus = nsEventStatus_eIgnore;
542 nsIWidgetListener* listener = GetListener();
543 if (listener) {
544 aStatus = listener->HandleEvent(aEvent, mUseAttachedEvents);
547 return NS_OK;
550 void nsWindow::OnDestroy(void) {
551 if (mOnDestroyCalled) {
552 return;
555 mOnDestroyCalled = true;
557 // Prevent deletion.
558 nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
560 // release references to children, device context, toolkit + app shell
561 nsBaseWidget::OnDestroy();
563 // Remove association between this object and its parent and siblings.
564 nsBaseWidget::Destroy();
565 mParent = nullptr;
567 NotifyWindowDestroyed();
570 bool nsWindow::AreBoundsSane() {
571 // Check requested size, as mBounds might not have been updated.
572 return !mLastSizeRequest.IsEmpty();
575 void nsWindow::Destroy() {
576 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
578 if (mIsDestroyed || !mCreated) {
579 return;
582 LOG("nsWindow::Destroy\n");
584 MutexAutoLock lock(mDestroyMutex);
585 mIsDestroyed = true;
586 mCreated = false;
588 MozClearHandleID(mCompositorPauseTimeoutID, g_source_remove);
590 #ifdef MOZ_WAYLAND
591 // Shut down our local vsync source
592 if (mWaylandVsyncSource) {
593 mWaylandVsyncSource->Shutdown();
594 mWaylandVsyncSource = nullptr;
596 mWaylandVsyncDispatcher = nullptr;
597 #endif
599 // dragService will be null after shutdown of the service manager.
600 RefPtr<nsDragService> dragService = nsDragService::GetInstance();
601 if (dragService && this == dragService->GetMostRecentDestWindow()) {
602 dragService->ScheduleLeaveEvent();
605 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
606 if (rollupListener) {
607 nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
608 if (static_cast<nsIWidget*>(this) == rollupWidget) {
609 rollupListener->Rollup({});
613 NativeShow(false);
615 ClearTransparencyBitmap();
617 DestroyLayerManager();
619 // mSurfaceProvider holds reference to this nsWindow so we need to explicitly
620 // clear it here to avoid nsWindow leak.
621 mSurfaceProvider.CleanupResources();
623 g_signal_handlers_disconnect_by_data(gtk_settings_get_default(), this);
625 if (mIMContext) {
626 mIMContext->OnDestroyWindow(this);
629 // make sure that we remove ourself as the focus window
630 if (gFocusWindow == this) {
631 LOG("automatically losing focus...\n");
632 gFocusWindow = nullptr;
635 if (sStoredLeaveNotifyEvent) {
636 nsWindow* window =
637 get_window_for_gdk_window(sStoredLeaveNotifyEvent->window);
638 if (window == this) {
639 sStoredLeaveNotifyEvent = nullptr;
643 // We need to detach accessible object here because mContainer is a custom
644 // widget and doesn't call gtk_widget_real_destroy() from destroy handler
645 // as regular widgets.
646 if (AtkObject* ac = gtk_widget_get_accessible(GTK_WIDGET(mContainer))) {
647 gtk_accessible_set_widget(GTK_ACCESSIBLE(ac), nullptr);
650 gtk_widget_destroy(mShell);
651 mShell = nullptr;
652 mContainer = nullptr;
654 MOZ_ASSERT(!mGdkWindow,
655 "mGdkWindow should be NULL when mContainer is destroyed");
657 #ifdef ACCESSIBILITY
658 if (mRootAccessible) {
659 mRootAccessible = nullptr;
661 #endif
663 // Save until last because OnDestroy() may cause us to be deleted.
664 OnDestroy();
667 nsIWidget* nsWindow::GetParent() { return mParent; }
669 float nsWindow::GetDPI() {
670 float dpi = 96.0f;
671 nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
672 if (screen) {
673 screen->GetDpi(&dpi);
675 return dpi;
678 double nsWindow::GetDefaultScaleInternal() { return FractionalScaleFactor(); }
680 DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScale() {
681 #ifdef MOZ_WAYLAND
682 if (GdkIsWaylandDisplay()) {
683 return DesktopToLayoutDeviceScale(FractionalScaleFactor());
685 #endif
687 // In Gtk/X11, we manage windows using device pixels.
688 return DesktopToLayoutDeviceScale(1.0);
691 DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScaleByScreen() {
692 #ifdef MOZ_WAYLAND
693 // In Wayland there's no way to get absolute position of the window and use it
694 // to determine the screen factor of the monitor on which the window is
695 // placed. The window is notified of the current scale factor but not at this
696 // point, so the GdkScaleFactor can return wrong value which can lead to wrong
697 // popup placement. We need to use parent's window scale factor for the new
698 // one.
699 if (GdkIsWaylandDisplay()) {
700 nsView* view = nsView::GetViewFor(this);
701 if (view) {
702 nsView* parentView = view->GetParent();
703 if (parentView) {
704 nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr);
705 if (parentWidget) {
706 return DesktopToLayoutDeviceScale(
707 parentWidget->RoundsWidgetCoordinatesTo());
709 NS_WARNING("Widget has no parent");
711 } else {
712 NS_WARNING("Cannot find widget view");
715 #endif
716 return nsBaseWidget::GetDesktopToDeviceScale();
719 // Reparent a child window to a new parent.
720 void nsWindow::SetParent(nsIWidget* aNewParent) {
721 LOG("nsWindow::SetParent() new parent %p", aNewParent);
722 if (!mIsChildWindow) {
723 NS_WARNING("Used by child widgets only");
724 return;
727 nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
728 if (mParent) {
729 mParent->RemoveChild(this);
731 mParent = aNewParent;
733 // We're already deleted, quit.
734 if (!mGdkWindow || mIsDestroyed || !aNewParent) {
735 return;
737 aNewParent->AddChild(this);
739 auto* newParent = static_cast<nsWindow*>(aNewParent);
741 // New parent is deleted, quit.
742 if (newParent->mIsDestroyed) {
743 Destroy();
744 return;
747 GdkWindow* window = GetToplevelGdkWindow();
748 GdkWindow* parentWindow = newParent->GetToplevelGdkWindow();
749 LOG(" child GdkWindow %p set parent GdkWindow %p", window, parentWindow);
750 gdk_window_reparent(window, parentWindow, 0, 0);
751 SetHasMappedToplevel(newParent && newParent->mHasMappedToplevel);
754 bool nsWindow::WidgetTypeSupportsAcceleration() {
755 if (mWindowType == WindowType::Invisible) {
756 return false;
759 if (IsSmallPopup()) {
760 return false;
762 // Workaround for Bug 1479135
763 // We draw transparent popups on non-compositing screens by SW as we don't
764 // implement X shape masks in WebRender.
765 if (mWindowType == WindowType::Popup) {
766 return HasRemoteContent() && mCompositedScreen;
769 return true;
772 void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) {
773 MOZ_ASSERT(aNewParent, "null widget");
774 MOZ_ASSERT(!mIsDestroyed, "");
775 MOZ_ASSERT(!static_cast<nsWindow*>(aNewParent)->mIsDestroyed, "");
776 MOZ_ASSERT(
777 !mParent,
778 "nsWindow::ReparentNativeWidget() works on toplevel windows only.");
780 auto* newParent = static_cast<nsWindow*>(aNewParent);
781 GtkWindow* newParentWidget = GTK_WINDOW(newParent->GetGtkWidget());
783 LOG("nsWindow::ReparentNativeWidget new parent %p\n", newParent);
784 GtkWindowSetTransientFor(GTK_WINDOW(mShell), newParentWidget);
787 void nsWindow::SetModal(bool aModal) {
788 LOG("nsWindow::SetModal %d\n", aModal);
789 if (mIsDestroyed) {
790 return;
793 gtk_window_set_modal(GTK_WINDOW(mShell), aModal ? TRUE : FALSE);
796 // nsIWidget method, which means IsShown.
797 bool nsWindow::IsVisible() const { return mIsShown; }
799 bool nsWindow::IsMapped() const { return mIsMapped; }
801 void nsWindow::RegisterTouchWindow() {
802 mHandleTouchEvent = true;
803 mTouches.Clear();
806 LayoutDeviceIntPoint nsWindow::GetScreenEdgeSlop() {
807 if (DrawsToCSDTitlebar()) {
808 return GetClientOffset();
810 return {};
813 void nsWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
814 if (!mShell || GdkIsWaylandDisplay()) {
815 return;
818 double dpiScale = GetDefaultScale().scale;
820 // we need to use the window size in logical screen pixels
821 int32_t logWidth = std::max(NSToIntRound(mBounds.width / dpiScale), 1);
822 int32_t logHeight = std::max(NSToIntRound(mBounds.height / dpiScale), 1);
824 /* get our playing field. use the current screen, or failing that
825 for any reason, use device caps for the default screen. */
826 nsCOMPtr<nsIScreenManager> screenmgr =
827 do_GetService("@mozilla.org/gfx/screenmanager;1");
828 if (!screenmgr) {
829 return;
831 nsCOMPtr<nsIScreen> screen;
832 screenmgr->ScreenForRect(aPoint.x, aPoint.y, logWidth, logHeight,
833 getter_AddRefs(screen));
834 // We don't have any screen so leave the coordinates as is
835 if (!screen) {
836 return;
839 // For normalized windows, use the desktop work area.
840 // For full screen windows, use the desktop.
841 DesktopIntRect screenRect = mSizeMode == nsSizeMode_Fullscreen
842 ? screen->GetRectDisplayPix()
843 : screen->GetAvailRectDisplayPix();
845 // Expand for the decoration size if needed.
846 auto slop =
847 DesktopIntPoint::Round(GetScreenEdgeSlop() / GetDesktopToDeviceScale());
848 screenRect.Inflate(slop.x, slop.y);
850 if (aPoint.x < screenRect.x) {
851 aPoint.x = screenRect.x;
852 } else if (aPoint.x >= screenRect.XMost() - logWidth) {
853 aPoint.x = screenRect.XMost() - logWidth;
856 if (aPoint.y < screenRect.y) {
857 aPoint.y = screenRect.y;
858 } else if (aPoint.y >= screenRect.YMost() - logHeight) {
859 aPoint.y = screenRect.YMost() - logHeight;
863 void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
864 mSizeConstraints.mMinSize = GetSafeWindowSize(aConstraints.mMinSize);
865 mSizeConstraints.mMaxSize = GetSafeWindowSize(aConstraints.mMaxSize);
867 ApplySizeConstraints();
870 bool nsWindow::DrawsToCSDTitlebar() const {
871 return mSizeMode == nsSizeMode_Normal &&
872 mGtkWindowDecoration == GTK_DECORATION_CLIENT && mDrawInTitlebar;
875 void nsWindow::AddCSDDecorationSize(int* aWidth, int* aHeight) {
876 if (mSizeMode != nsSizeMode_Normal || mUndecorated ||
877 mGtkWindowDecoration != GTK_DECORATION_CLIENT || !GdkIsWaylandDisplay() ||
878 !IsGnomeDesktopEnvironment()) {
879 return;
882 GtkBorder decorationSize = GetCSDDecorationSize(IsPopup());
883 *aWidth += decorationSize.left + decorationSize.right;
884 *aHeight += decorationSize.top + decorationSize.bottom;
887 #ifdef MOZ_WAYLAND
888 bool nsWindow::GetCSDDecorationOffset(int* aDx, int* aDy) {
889 if (!DrawsToCSDTitlebar()) {
890 return false;
892 GtkBorder decorationSize = GetCSDDecorationSize(IsPopup());
893 *aDx = decorationSize.left;
894 *aDy = decorationSize.top;
895 return true;
897 #endif
899 void nsWindow::ApplySizeConstraints() {
900 if (mShell) {
901 GdkGeometry geometry;
902 geometry.min_width =
903 DevicePixelsToGdkCoordRoundUp(mSizeConstraints.mMinSize.width);
904 geometry.min_height =
905 DevicePixelsToGdkCoordRoundUp(mSizeConstraints.mMinSize.height);
906 geometry.max_width =
907 DevicePixelsToGdkCoordRoundDown(mSizeConstraints.mMaxSize.width);
908 geometry.max_height =
909 DevicePixelsToGdkCoordRoundDown(mSizeConstraints.mMaxSize.height);
911 uint32_t hints = 0;
912 if (mSizeConstraints.mMinSize != LayoutDeviceIntSize()) {
913 if (GdkIsWaylandDisplay()) {
914 gtk_widget_set_size_request(GTK_WIDGET(mContainer), geometry.min_width,
915 geometry.min_height);
917 AddCSDDecorationSize(&geometry.min_width, &geometry.min_height);
918 hints |= GDK_HINT_MIN_SIZE;
920 if (mSizeConstraints.mMaxSize !=
921 LayoutDeviceIntSize(NS_MAXSIZE, NS_MAXSIZE)) {
922 AddCSDDecorationSize(&geometry.max_width, &geometry.max_height);
923 hints |= GDK_HINT_MAX_SIZE;
926 if (mAspectRatio != 0.0f && !mAspectResizer) {
927 geometry.min_aspect = mAspectRatio;
928 geometry.max_aspect = mAspectRatio;
929 hints |= GDK_HINT_ASPECT;
932 gtk_window_set_geometry_hints(GTK_WINDOW(mShell), nullptr, &geometry,
933 GdkWindowHints(hints));
937 void nsWindow::Show(bool aState) {
938 if (aState == mIsShown) {
939 return;
942 mIsShown = aState;
944 #ifdef MOZ_LOGGING
945 LOG("nsWindow::Show state %d frame %s\n", aState, GetFrameTag().get());
946 if (!aState && mSourceDragContext && GdkIsWaylandDisplay()) {
947 LOG(" closing Drag&Drop source window, D&D will be canceled!");
949 #endif
951 // Ok, someone called show on a window that isn't sized to a sane
952 // value. Mark this window as needing to have Show() called on it
953 // and return.
954 if ((aState && !AreBoundsSane()) || !mCreated) {
955 LOG("\tbounds are insane or window hasn't been created yet\n");
956 mNeedsShow = true;
957 return;
960 // If someone is hiding this widget, clear any needing show flag.
961 if (!aState) mNeedsShow = false;
963 #ifdef ACCESSIBILITY
964 if (aState && a11y::ShouldA11yBeEnabled()) CreateRootAccessible();
965 #endif
967 NativeShow(aState);
968 RefreshWindowClass();
971 void nsWindow::ResizeInt(const Maybe<LayoutDeviceIntPoint>& aMove,
972 LayoutDeviceIntSize aSize) {
973 LOG("nsWindow::ResizeInt w:%d h:%d\n", aSize.width, aSize.height);
974 const bool moved = aMove && *aMove != mBounds.TopLeft();
975 if (moved) {
976 mBounds.MoveTo(*aMove);
977 LOG(" with move to left:%d top:%d", aMove->x.value, aMove->y.value);
980 ConstrainSize(&aSize.width, &aSize.height);
981 LOG(" ConstrainSize: w:%d h;%d\n", aSize.width, aSize.height);
983 const bool resized = aSize != mLastSizeRequest || mBounds.Size() != aSize;
984 #if MOZ_LOGGING
985 LOG(" resized %d aSize [%d, %d] mLastSizeRequest [%d, %d] mBounds [%d, %d]",
986 resized, aSize.width, aSize.height, mLastSizeRequest.width,
987 mLastSizeRequest.height, mBounds.width, mBounds.height);
988 #endif
990 // For top-level windows, aSize should possibly be
991 // interpreted as frame bounds, but NativeMoveResize treats these as window
992 // bounds (Bug 581866).
993 mLastSizeRequest = aSize;
994 // Check size
995 if (mCompositorSession &&
996 !wr::WindowSizeSanityCheck(aSize.width, aSize.height)) {
997 gfxCriticalNoteOnce << "Invalid aSize in ResizeInt " << aSize
998 << " size state " << mSizeMode;
1001 // Recalculate aspect ratio when resized from DOM
1002 if (mAspectRatio != 0.0) {
1003 LockAspectRatio(true);
1006 if (!mCreated) {
1007 return;
1010 if (!moved && !resized) {
1011 LOG(" not moved or resized, quit");
1012 return;
1015 NativeMoveResize(moved, resized);
1017 // We optimistically assume size changes immediately in two cases:
1018 // 1. Override-redirect window: Size is controlled by only us.
1019 // 2. Managed window that has not not yet received a size-allocate event:
1020 // Resize() Callers expect initial sizes to be applied synchronously.
1021 // If the size request is not honored, then we'll correct in
1022 // OnSizeAllocate().
1024 // When a managed window has already received a size-allocate, we cannot
1025 // assume we'll always get a notification if our request does not get
1026 // honored: "If the configure request has not changed, we don't ever resend
1027 // it, because it could mean fighting the user or window manager."
1028 // https://gitlab.gnome.org/GNOME/gtk/-/blob/3.24.31/gtk/gtkwindow.c#L9782
1029 // So we don't update mBounds until OnSizeAllocate() when we know the
1030 // request is granted.
1031 bool isOrWillBeVisible = mHasReceivedSizeAllocate || mNeedsShow || mIsShown;
1032 if (!isOrWillBeVisible ||
1033 gtk_window_get_window_type(GTK_WINDOW(mShell)) == GTK_WINDOW_POPUP) {
1034 mBounds.SizeTo(aSize);
1035 if (mCompositorWidgetDelegate) {
1036 mCompositorWidgetDelegate->NotifyClientSizeChanged(aSize);
1038 DispatchResized();
1042 void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
1043 LOG("nsWindow::Resize %f %f\n", aWidth, aHeight);
1045 double scale =
1046 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
1047 auto size = LayoutDeviceIntSize::Round(scale * aWidth, scale * aHeight);
1049 ResizeInt(Nothing(), size);
1052 void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
1053 bool aRepaint) {
1054 LOG("nsWindow::Resize [%f,%f] -> [%f x %f] repaint %d\n", aX, aY, aWidth,
1055 aHeight, aRepaint);
1057 double scale =
1058 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
1059 auto size = LayoutDeviceIntSize::Round(scale * aWidth, scale * aHeight);
1060 auto topLeft = LayoutDeviceIntPoint::Round(scale * aX, scale * aY);
1062 ResizeInt(Some(topLeft), size);
1065 void nsWindow::Enable(bool aState) { mEnabled = aState; }
1067 bool nsWindow::IsEnabled() const { return mEnabled; }
1069 void nsWindow::Move(double aX, double aY) {
1070 double scale =
1071 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
1072 int32_t x = NSToIntRound(aX * scale);
1073 int32_t y = NSToIntRound(aY * scale);
1075 LOG("nsWindow::Move to %d x %d\n", x, y);
1077 if (mSizeMode != nsSizeMode_Normal && IsTopLevelWindowType()) {
1078 LOG(" size state is not normal, bailing");
1079 return;
1082 // Since a popup window's x/y coordinates are in relation to to
1083 // the parent, the parent might have moved so we always move a
1084 // popup window.
1085 LOG(" bounds %d x %d\n", mBounds.x, mBounds.y);
1086 if (x == mBounds.x && y == mBounds.y && mWindowType != WindowType::Popup) {
1087 LOG(" position is the same, return\n");
1088 return;
1091 // XXX Should we do some AreBoundsSane check here?
1093 mBounds.x = x;
1094 mBounds.y = y;
1096 if (!mCreated) {
1097 LOG(" is not created, return.\n");
1098 return;
1101 NativeMoveResize(/* move */ true, /* resize */ false);
1104 bool nsWindow::IsPopup() const { return mWindowType == WindowType::Popup; }
1106 bool nsWindow::IsWaylandPopup() const {
1107 return GdkIsWaylandDisplay() && IsPopup();
1110 static nsMenuPopupFrame* GetMenuPopupFrame(nsIFrame* aFrame) {
1111 return do_QueryFrame(aFrame);
1114 void nsWindow::AppendPopupToHierarchyList(nsWindow* aToplevelWindow) {
1115 mWaylandToplevel = aToplevelWindow;
1117 nsWindow* popup = aToplevelWindow;
1118 while (popup && popup->mWaylandPopupNext) {
1119 popup = popup->mWaylandPopupNext;
1121 popup->mWaylandPopupNext = this;
1123 mWaylandPopupPrev = popup;
1124 mWaylandPopupNext = nullptr;
1125 mPopupChanged = true;
1126 mPopupClosed = false;
1129 void nsWindow::RemovePopupFromHierarchyList() {
1130 // We're already removed from the popup hierarchy
1131 if (!IsInPopupHierarchy()) {
1132 return;
1134 mWaylandPopupPrev->mWaylandPopupNext = mWaylandPopupNext;
1135 if (mWaylandPopupNext) {
1136 mWaylandPopupNext->mWaylandPopupPrev = mWaylandPopupPrev;
1137 mWaylandPopupNext->mPopupChanged = true;
1139 mWaylandPopupNext = mWaylandPopupPrev = nullptr;
1142 // Gtk refuses to map popup window with x < 0 && y < 0 relative coordinates
1143 // see https://gitlab.gnome.org/GNOME/gtk/-/issues/4071
1144 // as a workaround just fool around and place the popup temporary to 0,0.
1145 bool nsWindow::WaylandPopupRemoveNegativePosition(int* aX, int* aY) {
1146 // https://gitlab.gnome.org/GNOME/gtk/-/issues/4071 applies to temporary
1147 // windows only
1148 GdkWindow* window = GetToplevelGdkWindow();
1149 if (!window || gdk_window_get_window_type(window) != GDK_WINDOW_TEMP) {
1150 return false;
1153 LOG("nsWindow::WaylandPopupRemoveNegativePosition()");
1155 int x, y;
1156 gtk_window_get_position(GTK_WINDOW(mShell), &x, &y);
1157 bool moveBack = (x < 0 && y < 0);
1158 if (moveBack) {
1159 gtk_window_move(GTK_WINDOW(mShell), 0, 0);
1160 if (aX) {
1161 *aX = x;
1163 if (aY) {
1164 *aY = y;
1168 gdk_window_get_geometry(window, &x, &y, nullptr, nullptr);
1169 if (x < 0 && y < 0) {
1170 gdk_window_move(window, 0, 0);
1173 return moveBack;
1176 void nsWindow::ShowWaylandPopupWindow() {
1177 LOG("nsWindow::ShowWaylandPopupWindow. Expected to see visible.");
1178 MOZ_ASSERT(IsWaylandPopup());
1180 if (!mPopupTrackInHierarchy) {
1181 LOG(" popup is not tracked in popup hierarchy, show it now");
1182 gtk_widget_show(mShell);
1183 return;
1186 // Popup position was checked before gdk_window_move_to_rect() callback
1187 // so just show it.
1188 if (mPopupUseMoveToRect && mWaitingForMoveToRectCallback) {
1189 LOG(" active move-to-rect callback, show it as is");
1190 gtk_widget_show(mShell);
1191 return;
1194 if (gtk_widget_is_visible(mShell)) {
1195 LOG(" is already visible, quit");
1196 return;
1199 int x, y;
1200 bool moved = WaylandPopupRemoveNegativePosition(&x, &y);
1201 gtk_widget_show(mShell);
1202 if (moved) {
1203 LOG(" move back to (%d, %d) and show", x, y);
1204 gtk_window_move(GTK_WINDOW(mShell), x, y);
1208 void nsWindow::WaylandPopupMarkAsClosed() {
1209 LOG("nsWindow::WaylandPopupMarkAsClosed: [%p]\n", this);
1210 mPopupClosed = true;
1211 // If we have any child popup window notify it about
1212 // parent switch.
1213 if (mWaylandPopupNext) {
1214 mWaylandPopupNext->mPopupChanged = true;
1218 nsWindow* nsWindow::WaylandPopupFindLast(nsWindow* aPopup) {
1219 while (aPopup && aPopup->mWaylandPopupNext) {
1220 aPopup = aPopup->mWaylandPopupNext;
1222 return aPopup;
1225 // Hide and potentially removes popup from popup hierarchy.
1226 void nsWindow::HideWaylandPopupWindow(bool aTemporaryHide,
1227 bool aRemoveFromPopupList) {
1228 LOG("nsWindow::HideWaylandPopupWindow: remove from list %d\n",
1229 aRemoveFromPopupList);
1230 if (aRemoveFromPopupList) {
1231 RemovePopupFromHierarchyList();
1234 if (!mPopupClosed) {
1235 mPopupClosed = !aTemporaryHide;
1238 bool visible = gtk_widget_is_visible(mShell);
1239 LOG(" gtk_widget_is_visible() = %d\n", visible);
1241 // Restore only popups which are really visible
1242 mPopupTemporaryHidden = aTemporaryHide && visible;
1244 // Hide only visible popups or popups closed pernamently.
1245 if (visible) {
1246 gtk_widget_hide(mShell);
1248 // If there's pending Move-To-Rect callback and we hide the popup
1249 // the callback won't be called any more.
1250 mWaitingForMoveToRectCallback = false;
1253 if (mPopupClosed) {
1254 LOG(" Clearing mMoveToRectPopupSize\n");
1255 mMoveToRectPopupSize = {};
1256 #ifdef MOZ_WAYLAND
1257 if (moz_container_wayland_is_waiting_to_show(mContainer)) {
1258 // We need to clear rendering queue, see Bug 1782948.
1259 LOG(" popup failed to show by Wayland compositor, clear rendering "
1260 "queue.");
1261 moz_container_wayland_clear_waiting_to_show_flag(mContainer);
1262 ClearRenderingQueue();
1264 #endif
1268 void nsWindow::HideWaylandToplevelWindow() {
1269 LOG("nsWindow::HideWaylandToplevelWindow: [%p]\n", this);
1270 if (mWaylandPopupNext) {
1271 nsWindow* popup = WaylandPopupFindLast(mWaylandPopupNext);
1272 while (popup->mWaylandToplevel != nullptr) {
1273 nsWindow* prev = popup->mWaylandPopupPrev;
1274 popup->HideWaylandPopupWindow(/* aTemporaryHide */ false,
1275 /* aRemoveFromPopupList */ true);
1276 popup = prev;
1279 WaylandStopVsync();
1280 gtk_widget_hide(mShell);
1283 void nsWindow::ShowWaylandToplevelWindow() {
1284 MOZ_ASSERT(!IsWaylandPopup());
1285 LOG("nsWindow::ShowWaylandToplevelWindow");
1286 gtk_widget_show(mShell);
1289 void nsWindow::WaylandPopupRemoveClosedPopups() {
1290 LOG("nsWindow::WaylandPopupRemoveClosedPopups()");
1291 nsWindow* popup = this;
1292 while (popup) {
1293 nsWindow* next = popup->mWaylandPopupNext;
1294 if (popup->mPopupClosed) {
1295 popup->HideWaylandPopupWindow(/* aTemporaryHide */ false,
1296 /* aRemoveFromPopupList */ true);
1298 popup = next;
1302 // Hide all tooltips except the latest one.
1303 void nsWindow::WaylandPopupHideTooltips() {
1304 LOG("nsWindow::WaylandPopupHideTooltips");
1305 MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
1307 nsWindow* popup = mWaylandPopupNext;
1308 while (popup && popup->mWaylandPopupNext) {
1309 if (popup->mPopupType == PopupType::Tooltip) {
1310 LOG(" hidding tooltip [%p]", popup);
1311 popup->WaylandPopupMarkAsClosed();
1313 popup = popup->mWaylandPopupNext;
1317 void nsWindow::WaylandPopupCloseOrphanedPopups() {
1318 #ifdef MOZ_WAYLAND
1319 LOG("nsWindow::WaylandPopupCloseOrphanedPopups");
1320 MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
1322 nsWindow* popup = mWaylandPopupNext;
1323 bool dangling = false;
1324 while (popup) {
1325 if (!dangling &&
1326 moz_container_wayland_is_waiting_to_show(popup->GetMozContainer())) {
1327 LOG(" popup [%p] is waiting to show, close all child popups", popup);
1328 dangling = true;
1329 } else if (dangling) {
1330 popup->WaylandPopupMarkAsClosed();
1332 popup = popup->mWaylandPopupNext;
1334 #endif
1337 // We can't show popups with remote content or overflow popups
1338 // on top of regular ones.
1339 // If there's any remote popup opened, close all parent popups of it.
1340 void nsWindow::CloseAllPopupsBeforeRemotePopup() {
1341 LOG("nsWindow::CloseAllPopupsBeforeRemotePopup");
1342 MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
1344 // Don't waste time when there's only one popup opened.
1345 if (!mWaylandPopupNext || mWaylandPopupNext->mWaylandPopupNext == nullptr) {
1346 return;
1349 // Find the first opened remote content popup
1350 nsWindow* remotePopup = mWaylandPopupNext;
1351 while (remotePopup) {
1352 if (remotePopup->HasRemoteContent() ||
1353 remotePopup->IsWidgetOverflowWindow()) {
1354 LOG(" remote popup [%p]", remotePopup);
1355 break;
1357 remotePopup = remotePopup->mWaylandPopupNext;
1360 if (!remotePopup) {
1361 return;
1364 // ...hide opened popups before the remote one.
1365 nsWindow* popup = mWaylandPopupNext;
1366 while (popup && popup != remotePopup) {
1367 LOG(" hidding popup [%p]", popup);
1368 popup->WaylandPopupMarkAsClosed();
1369 popup = popup->mWaylandPopupNext;
1373 static void GetLayoutPopupWidgetChain(
1374 nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
1375 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1376 pm->GetSubmenuWidgetChain(aLayoutWidgetHierarchy);
1377 aLayoutWidgetHierarchy->Reverse();
1380 // Compare 'this' popup position in Wayland widget hierarchy
1381 // (mWaylandPopupPrev/mWaylandPopupNext) with
1382 // 'this' popup position in layout hierarchy.
1384 // When aMustMatchParent is true we also request
1385 // 'this' parents match, i.e. 'this' has the same parent in
1386 // both layout and widget hierarchy.
1387 bool nsWindow::IsPopupInLayoutPopupChain(
1388 nsTArray<nsIWidget*>* aLayoutWidgetHierarchy, bool aMustMatchParent) {
1389 int len = (int)aLayoutWidgetHierarchy->Length();
1390 for (int i = 0; i < len; i++) {
1391 if (this == (*aLayoutWidgetHierarchy)[i]) {
1392 if (!aMustMatchParent) {
1393 return true;
1396 // Find correct parent popup for 'this' according to widget
1397 // hierarchy. That means we need to skip closed popups.
1398 nsWindow* parentPopup = nullptr;
1399 if (mWaylandPopupPrev != mWaylandToplevel) {
1400 parentPopup = mWaylandPopupPrev;
1401 while (parentPopup != mWaylandToplevel && parentPopup->mPopupClosed) {
1402 parentPopup = parentPopup->mWaylandPopupPrev;
1406 if (i == 0) {
1407 // We found 'this' popups as a first popup in layout hierarchy.
1408 // It matches layout hierarchy if it's first widget also in
1409 // wayland widget hierarchy (i.e. parent is null).
1410 return parentPopup == nullptr;
1413 return parentPopup == (*aLayoutWidgetHierarchy)[i - 1];
1416 return false;
1419 // Hide popups which are not in popup chain.
1420 void nsWindow::WaylandPopupHierarchyHideByLayout(
1421 nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
1422 LOG("nsWindow::WaylandPopupHierarchyHideByLayout");
1423 MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
1425 // Hide all popups which are not in layout popup chain
1426 nsWindow* popup = mWaylandPopupNext;
1427 while (popup) {
1428 // Don't check closed popups and drag source popups and tooltips.
1429 if (!popup->mPopupClosed && popup->mPopupType != PopupType::Tooltip &&
1430 !popup->mSourceDragContext) {
1431 if (!popup->IsPopupInLayoutPopupChain(aLayoutWidgetHierarchy,
1432 /* aMustMatchParent */ false)) {
1433 LOG(" hidding popup [%p]", popup);
1434 popup->WaylandPopupMarkAsClosed();
1437 popup = popup->mWaylandPopupNext;
1441 // Mark popups outside of layout hierarchy
1442 void nsWindow::WaylandPopupHierarchyValidateByLayout(
1443 nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
1444 LOG("nsWindow::WaylandPopupHierarchyValidateByLayout");
1445 nsWindow* popup = mWaylandPopupNext;
1446 while (popup) {
1447 if (popup->mPopupType == PopupType::Tooltip) {
1448 popup->mPopupMatchesLayout = true;
1449 } else if (!popup->mPopupClosed) {
1450 popup->mPopupMatchesLayout = popup->IsPopupInLayoutPopupChain(
1451 aLayoutWidgetHierarchy, /* aMustMatchParent */ true);
1452 LOG(" popup [%p] parent window [%p] matches layout %d\n", (void*)popup,
1453 (void*)popup->mWaylandPopupPrev, popup->mPopupMatchesLayout);
1455 popup = popup->mWaylandPopupNext;
1459 void nsWindow::WaylandPopupHierarchyHideTemporary() {
1460 LOG("nsWindow::WaylandPopupHierarchyHideTemporary()");
1461 nsWindow* popup = WaylandPopupFindLast(this);
1462 while (popup && popup != this) {
1463 LOG(" temporary hidding popup [%p]", popup);
1464 nsWindow* prev = popup->mWaylandPopupPrev;
1465 popup->HideWaylandPopupWindow(/* aTemporaryHide */ true,
1466 /* aRemoveFromPopupList */ false);
1467 popup = prev;
1471 void nsWindow::WaylandPopupHierarchyShowTemporaryHidden() {
1472 LOG("nsWindow::WaylandPopupHierarchyShowTemporaryHidden()");
1473 nsWindow* popup = this;
1474 while (popup) {
1475 if (popup->mPopupTemporaryHidden) {
1476 popup->mPopupTemporaryHidden = false;
1477 LOG(" showing temporary hidden popup [%p]", popup);
1478 popup->ShowWaylandPopupWindow();
1480 popup = popup->mWaylandPopupNext;
1484 void nsWindow::WaylandPopupHierarchyCalculatePositions() {
1485 LOG("nsWindow::WaylandPopupHierarchyCalculatePositions()");
1487 // Set widget hierarchy in Gtk
1488 nsWindow* popup = mWaylandToplevel->mWaylandPopupNext;
1489 while (popup) {
1490 LOG(" popup [%p] set parent window [%p]", (void*)popup,
1491 (void*)popup->mWaylandPopupPrev);
1492 GtkWindowSetTransientFor(GTK_WINDOW(popup->mShell),
1493 GTK_WINDOW(popup->mWaylandPopupPrev->mShell));
1494 popup = popup->mWaylandPopupNext;
1497 popup = this;
1498 while (popup) {
1499 // Anchored window has mPopupPosition already calculated against
1500 // its parent, no need to recalculate.
1501 LOG(" popup [%p] bounds [%d, %d] -> [%d x %d]", popup,
1502 (int)(popup->mBounds.x / FractionalScaleFactor()),
1503 (int)(popup->mBounds.y / FractionalScaleFactor()),
1504 (int)(popup->mBounds.width / FractionalScaleFactor()),
1505 (int)(popup->mBounds.height / FractionalScaleFactor()));
1506 #ifdef MOZ_LOGGING
1507 if (LOG_ENABLED()) {
1508 if (nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame())) {
1509 auto r = LayoutDeviceRect::FromAppUnitsRounded(
1510 popupFrame->GetRect(),
1511 popupFrame->PresContext()->AppUnitsPerDevPixel());
1512 LOG(" popup [%p] layout [%d, %d] -> [%d x %d]", popup, r.x, r.y,
1513 r.width, r.height);
1516 #endif
1517 if (popup->WaylandPopupIsFirst()) {
1518 LOG(" popup [%p] has toplevel as parent", popup);
1519 popup->mRelativePopupPosition = popup->mPopupPosition;
1520 } else {
1521 if (popup->mPopupAnchored) {
1522 LOG(" popup [%p] is anchored", popup);
1523 if (!popup->mPopupMatchesLayout) {
1524 NS_WARNING("Anchored popup does not match layout!");
1527 GdkPoint parent = popup->WaylandGetParentPosition();
1529 LOG(" popup [%p] uses transformed coordinates\n", popup);
1530 LOG(" parent position [%d, %d]\n", parent.x, parent.y);
1531 LOG(" popup position [%d, %d]\n", popup->mPopupPosition.x,
1532 popup->mPopupPosition.y);
1534 popup->mRelativePopupPosition.x = popup->mPopupPosition.x - parent.x;
1535 popup->mRelativePopupPosition.y = popup->mPopupPosition.y - parent.y;
1537 LOG(" popup [%p] transformed popup coordinates from [%d, %d] to [%d, %d]",
1538 popup, popup->mPopupPosition.x, popup->mPopupPosition.y,
1539 popup->mRelativePopupPosition.x, popup->mRelativePopupPosition.y);
1540 popup = popup->mWaylandPopupNext;
1544 // The MenuList popups are used as dropdown menus for example in WebRTC
1545 // microphone/camera chooser or autocomplete widgets.
1546 bool nsWindow::WaylandPopupIsMenu() {
1547 nsMenuPopupFrame* menuPopupFrame = GetMenuPopupFrame(GetFrame());
1548 if (menuPopupFrame) {
1549 return mPopupType == PopupType::Menu && !menuPopupFrame->IsMenuList();
1551 return false;
1554 bool nsWindow::WaylandPopupIsContextMenu() {
1555 nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
1556 if (!popupFrame) {
1557 return false;
1559 return popupFrame->IsContextMenu();
1562 bool nsWindow::WaylandPopupIsPermanent() {
1563 nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
1564 if (!popupFrame) {
1565 // We can always hide popups without frames.
1566 return false;
1568 return popupFrame->IsNoAutoHide();
1571 bool nsWindow::WaylandPopupIsAnchored() {
1572 nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
1573 if (!popupFrame) {
1574 // We can always hide popups without frames.
1575 return false;
1577 return !!popupFrame->GetAnchor();
1580 bool nsWindow::IsWidgetOverflowWindow() {
1581 if (this->GetFrame() && this->GetFrame()->GetContent()->GetID()) {
1582 nsCString nodeId;
1583 this->GetFrame()->GetContent()->GetID()->ToUTF8String(nodeId);
1584 return nodeId.Equals("widget-overflow");
1586 return false;
1589 bool nsWindow::WaylandPopupIsFirst() {
1590 return !mWaylandPopupPrev || !mWaylandPopupPrev->mWaylandToplevel;
1593 nsWindow* nsWindow::GetEffectiveParent() {
1594 GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
1595 if (!parentGtkWindow || !GTK_IS_WIDGET(parentGtkWindow)) {
1596 return nullptr;
1598 return get_window_for_gtk_widget(GTK_WIDGET(parentGtkWindow));
1601 GdkPoint nsWindow::WaylandGetParentPosition() {
1602 GdkPoint topLeft = {0, 0};
1603 nsWindow* window = GetEffectiveParent();
1604 if (window->IsPopup()) {
1605 topLeft = DevicePixelsToGdkPointRoundDown(window->mBounds.TopLeft());
1607 LOG("nsWindow::WaylandGetParentPosition() [%d, %d]\n", topLeft.x, topLeft.y);
1608 return topLeft;
1611 #ifdef MOZ_LOGGING
1612 void nsWindow::LogPopupHierarchy() {
1613 if (!LOG_ENABLED()) {
1614 return;
1617 LOG("Widget Popup Hierarchy:\n");
1618 if (!mWaylandToplevel->mWaylandPopupNext) {
1619 LOG(" Empty\n");
1620 } else {
1621 int indent = 4;
1622 nsWindow* popup = mWaylandToplevel->mWaylandPopupNext;
1623 while (popup) {
1624 nsPrintfCString indentString("%*s", indent, " ");
1625 LOG("%s %s %s nsWindow [%p] Menu %d Permanent %d ContextMenu %d "
1626 "Anchored %d Visible %d MovedByRect %d\n",
1627 indentString.get(), popup->GetFrameTag().get(),
1628 popup->GetPopupTypeName().get(), popup, popup->WaylandPopupIsMenu(),
1629 popup->WaylandPopupIsPermanent(), popup->mPopupContextMenu,
1630 popup->mPopupAnchored, gtk_widget_is_visible(popup->mShell),
1631 popup->mPopupUseMoveToRect);
1632 indent += 4;
1633 popup = popup->mWaylandPopupNext;
1637 LOG("Layout Popup Hierarchy:\n");
1638 AutoTArray<nsIWidget*, 5> widgetChain;
1639 GetLayoutPopupWidgetChain(&widgetChain);
1640 if (widgetChain.Length() == 0) {
1641 LOG(" Empty\n");
1642 } else {
1643 for (unsigned long i = 0; i < widgetChain.Length(); i++) {
1644 nsWindow* window = static_cast<nsWindow*>(widgetChain[i]);
1645 nsPrintfCString indentString("%*s", (int)(i + 1) * 4, " ");
1646 if (window) {
1647 LOG("%s %s %s nsWindow [%p] Menu %d Permanent %d ContextMenu %d "
1648 "Anchored %d Visible %d MovedByRect %d\n",
1649 indentString.get(), window->GetFrameTag().get(),
1650 window->GetPopupTypeName().get(), window,
1651 window->WaylandPopupIsMenu(), window->WaylandPopupIsPermanent(),
1652 window->mPopupContextMenu, window->mPopupAnchored,
1653 gtk_widget_is_visible(window->mShell), window->mPopupUseMoveToRect);
1654 } else {
1655 LOG("%s null window\n", indentString.get());
1660 #endif
1662 nsWindow* nsWindow::GetTopmostWindow() {
1663 if (nsView* view = nsView::GetViewFor(this)) {
1664 if (nsView* parentView = view->GetParent()) {
1665 if (nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr)) {
1666 return static_cast<nsWindow*>(parentWidget);
1670 return nullptr;
1673 // Configure Wayland popup. If true is returned we need to track popup
1674 // in popup hierarchy. Otherwise we just show it as is.
1675 bool nsWindow::WaylandPopupConfigure() {
1676 if (mIsDragPopup) {
1677 return false;
1680 // Don't track popups without frame
1681 nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
1682 if (!popupFrame) {
1683 return false;
1686 // Popup state can be changed, see Bug 1728952.
1687 bool permanentStateMatches =
1688 mPopupTrackInHierarchy == !WaylandPopupIsPermanent();
1690 // Popup permanent state (noautohide attribute) can change during popup life.
1691 if (mPopupTrackInHierarchyConfigured && permanentStateMatches) {
1692 return mPopupTrackInHierarchy;
1695 // Configure persistent popup params only once.
1696 // WaylandPopupIsAnchored() can give it wrong value after
1697 // nsMenuPopupFrame::MoveTo() call which we use in move-to-rect callback
1698 // to position popup after wayland position change.
1699 if (!mPopupTrackInHierarchyConfigured) {
1700 mPopupAnchored = WaylandPopupIsAnchored();
1701 mPopupContextMenu = WaylandPopupIsContextMenu();
1704 LOG("nsWindow::WaylandPopupConfigure tracked %d anchored %d hint %d\n",
1705 mPopupTrackInHierarchy, mPopupAnchored, int(mPopupType));
1707 // Permanent state changed and popup is mapped.
1708 // We need to switch popup type but that's done when popup is mapped
1709 // by Gtk so we need to unmap the popup here.
1710 // It will be mapped again by gtk_widget_show().
1711 if (!permanentStateMatches && mIsMapped) {
1712 LOG(" permanent state change from %d to %d, unmapping",
1713 mPopupTrackInHierarchy, !WaylandPopupIsPermanent());
1714 gtk_widget_unmap(mShell);
1717 mPopupTrackInHierarchy = !WaylandPopupIsPermanent();
1718 LOG(" tracked in hierarchy %d\n", mPopupTrackInHierarchy);
1720 // See gdkwindow-wayland.c and
1721 // should_map_as_popup()/should_map_as_subsurface()
1722 GdkWindowTypeHint gtkTypeHint;
1723 switch (mPopupType) {
1724 case PopupType::Menu:
1725 // GDK_WINDOW_TYPE_HINT_POPUP_MENU is mapped as xdg_popup by default.
1726 // We use this type for all menu popups.
1727 gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
1728 LOG(" popup type Menu");
1729 break;
1730 case PopupType::Tooltip:
1731 gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
1732 LOG(" popup type Tooltip");
1733 break;
1734 default:
1735 gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
1736 LOG(" popup type Utility");
1737 break;
1740 if (!mPopupTrackInHierarchy) {
1741 // GDK_WINDOW_TYPE_HINT_UTILITY is mapped as wl_subsurface
1742 // by default.
1743 LOG(" not tracked in popup hierarchy, switch to Utility");
1744 gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
1746 gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
1748 mPopupTrackInHierarchyConfigured = true;
1749 return mPopupTrackInHierarchy;
1752 bool nsWindow::IsInPopupHierarchy() {
1753 return mPopupTrackInHierarchy && mWaylandToplevel && mWaylandPopupPrev;
1756 void nsWindow::AddWindowToPopupHierarchy() {
1757 LOG("nsWindow::AddWindowToPopupHierarchy\n");
1758 if (!GetFrame()) {
1759 LOG(" Window without frame cannot be added as popup!\n");
1760 return;
1763 // Check if we're already in the hierarchy
1764 if (!IsInPopupHierarchy()) {
1765 mWaylandToplevel = GetTopmostWindow();
1766 AppendPopupToHierarchyList(mWaylandToplevel);
1770 // Wayland keeps strong popup window hierarchy. We need to track active
1771 // (visible) popup windows and make sure we hide popup on the same level
1772 // before we open another one on that level. It means that every open
1773 // popup needs to have an unique parent.
1774 void nsWindow::UpdateWaylandPopupHierarchy() {
1775 LOG("nsWindow::UpdateWaylandPopupHierarchy\n");
1777 // This popup hasn't been added to popup hierarchy yet so no need to
1778 // do any configurations.
1779 if (!IsInPopupHierarchy()) {
1780 LOG(" popup isn't in hierarchy\n");
1781 return;
1784 #ifdef MOZ_LOGGING
1785 LogPopupHierarchy();
1786 auto printPopupHierarchy = MakeScopeExit([&] { LogPopupHierarchy(); });
1787 #endif
1789 // Hide all tooltips without the last one. Tooltip can't be popup parent.
1790 mWaylandToplevel->WaylandPopupHideTooltips();
1792 // See Bug 1709254 / https://gitlab.gnome.org/GNOME/gtk/-/issues/5092
1793 // It's possible that Wayland compositor refuses to show
1794 // a popup although Gtk claims it's visible.
1795 // We don't know if the popup is shown or not.
1796 // To avoid application crash refuse to create any child of such invisible
1797 // popup and close any child of it now.
1798 mWaylandToplevel->WaylandPopupCloseOrphanedPopups();
1800 // Check if we have any remote content / overflow window in hierarchy.
1801 // We can't attach such widget on top of other popup.
1802 mWaylandToplevel->CloseAllPopupsBeforeRemotePopup();
1804 // Check if your popup hierarchy matches layout hierarchy.
1805 // For instance we should not connect hamburger menu on top
1806 // of context menu.
1807 // Close all popups from different layout chains if possible.
1808 AutoTArray<nsIWidget*, 5> layoutPopupWidgetChain;
1809 GetLayoutPopupWidgetChain(&layoutPopupWidgetChain);
1811 mWaylandToplevel->WaylandPopupHierarchyHideByLayout(&layoutPopupWidgetChain);
1812 mWaylandToplevel->WaylandPopupHierarchyValidateByLayout(
1813 &layoutPopupWidgetChain);
1815 // Now we have Popup hierarchy complete.
1816 // Find first unchanged (and still open) popup to start with hierarchy
1817 // changes.
1818 nsWindow* changedPopup = mWaylandToplevel->mWaylandPopupNext;
1819 while (changedPopup) {
1820 // Stop when parent of this popup was changed and we need to recalc
1821 // popup position.
1822 if (changedPopup->mPopupChanged) {
1823 break;
1825 // Stop when this popup is closed.
1826 if (changedPopup->mPopupClosed) {
1827 break;
1829 changedPopup = changedPopup->mWaylandPopupNext;
1832 // We don't need to recompute popup positions, quit now.
1833 if (!changedPopup) {
1834 LOG(" changed Popup is null, quit.\n");
1835 return;
1838 LOG(" first changed popup [%p]\n", (void*)changedPopup);
1840 // Hide parent popups if necessary (there are layout discontinuity)
1841 // reposition the popup and show them again.
1842 changedPopup->WaylandPopupHierarchyHideTemporary();
1844 nsWindow* parentOfchangedPopup = nullptr;
1845 if (changedPopup->mPopupClosed) {
1846 parentOfchangedPopup = changedPopup->mWaylandPopupPrev;
1848 changedPopup->WaylandPopupRemoveClosedPopups();
1850 // It's possible that changedPopup was removed from widget hierarchy,
1851 // in such case use child popup of the removed one if there's any.
1852 if (!changedPopup->IsInPopupHierarchy()) {
1853 if (!parentOfchangedPopup || !parentOfchangedPopup->mWaylandPopupNext) {
1854 LOG(" last popup was removed, quit.\n");
1855 return;
1857 changedPopup = parentOfchangedPopup->mWaylandPopupNext;
1860 GetLayoutPopupWidgetChain(&layoutPopupWidgetChain);
1861 mWaylandToplevel->WaylandPopupHierarchyValidateByLayout(
1862 &layoutPopupWidgetChain);
1864 changedPopup->WaylandPopupHierarchyCalculatePositions();
1866 nsWindow* popup = changedPopup;
1867 while (popup) {
1868 const bool useMoveToRect = [&] {
1869 if (!StaticPrefs::widget_wayland_use_move_to_rect_AtStartup()) {
1870 return false; // Not available.
1872 if (!popup->mPopupMatchesLayout) {
1873 // We can use move_to_rect only when popups in popup hierarchy matches
1874 // layout hierarchy as move_to_rect request that parent/child
1875 // popups are adjacent.
1876 return false;
1878 if (popup->mPopupType == PopupType::Panel &&
1879 popup->WaylandPopupIsFirst() &&
1880 popup->WaylandPopupFitsToplevelWindow(/* aMove */ true)) {
1881 // Workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/1986
1883 // PopupType::Panel types are used for extension popups which may be
1884 // resized. If such popup uses move-to-rect, we need to hide it before
1885 // resize and show it again. That leads to massive flickering
1886 // so use plain move if possible to avoid it.
1888 // Bug 1760276 - don't use move-to-rect when popup is inside main
1889 // Firefox window.
1891 // Use it for first popups only due to another mutter bug
1892 // https://gitlab.gnome.org/GNOME/gtk/-/issues/5089
1893 // https://bugzilla.mozilla.org/show_bug.cgi?id=1784873
1894 return false;
1896 if (!popup->WaylandPopupIsFirst() &&
1897 !popup->mWaylandPopupPrev->WaylandPopupIsFirst() &&
1898 !popup->mWaylandPopupPrev->mPopupUseMoveToRect) {
1899 // We can't use move-to-rect if there are more parents of
1900 // wl_subsurface popups types.
1902 // It's because wl_subsurface is ignored by xgd_popup
1903 // (created by move-to-rect) so our popup scenario:
1905 // toplevel -> xgd_popup(1) -> wl_subsurface(2) -> xgd_popup(3)
1907 // looks for Wayland compositor as:
1909 // toplevel -> xgd_popup(1) -> xgd_popup(3)
1911 // If xgd_popup(1) and xgd_popup(3) are not connected
1912 // move-to-rect applied to xgd_popup(3) fails and we get missing popup.
1913 return false;
1915 return true;
1916 }();
1918 LOG(" popup [%p] matches layout [%d] anchored [%d] first popup [%d] use "
1919 "move-to-rect %d\n",
1920 popup, popup->mPopupMatchesLayout, popup->mPopupAnchored,
1921 popup->WaylandPopupIsFirst(), useMoveToRect);
1923 popup->mPopupUseMoveToRect = useMoveToRect;
1924 popup->WaylandPopupMoveImpl();
1925 popup->mPopupChanged = false;
1926 popup = popup->mWaylandPopupNext;
1929 changedPopup->WaylandPopupHierarchyShowTemporaryHidden();
1932 static void NativeMoveResizeCallback(GdkWindow* window,
1933 const GdkRectangle* flipped_rect,
1934 const GdkRectangle* final_rect,
1935 gboolean flipped_x, gboolean flipped_y,
1936 void* aWindow) {
1937 LOG_POPUP("[%p] NativeMoveResizeCallback flipped_x %d flipped_y %d\n",
1938 aWindow, flipped_x, flipped_y);
1939 LOG_POPUP("[%p] new position [%d, %d] -> [%d x %d]", aWindow,
1940 final_rect->x, final_rect->y, final_rect->width,
1941 final_rect->height);
1942 nsWindow* wnd = get_window_for_gdk_window(window);
1944 wnd->NativeMoveResizeWaylandPopupCallback(final_rect, flipped_x, flipped_y);
1947 // When popup is repositioned by widget code, we need to notify
1948 // layout about it. It's because we control popup placement
1949 // on widget on Wayland so layout may have old popup size/coordinates.
1950 void nsWindow::WaylandPopupPropagateChangesToLayout(bool aMove, bool aResize) {
1951 LOG("nsWindow::WaylandPopupPropagateChangesToLayout()");
1953 if (aResize) {
1954 LOG(" needSizeUpdate\n");
1955 if (nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame())) {
1956 RefPtr<PresShell> presShell = popupFrame->PresShell();
1957 presShell->FrameNeedsReflow(popupFrame, IntrinsicDirty::None,
1958 NS_FRAME_IS_DIRTY);
1961 if (aMove) {
1962 LOG(" needPositionUpdate, bounds [%d, %d]", mBounds.x, mBounds.y);
1963 NotifyWindowMoved(mBounds.x, mBounds.y, ByMoveToRect::Yes);
1967 void nsWindow::NativeMoveResizeWaylandPopupCallback(
1968 const GdkRectangle* aFinalSize, bool aFlippedX, bool aFlippedY) {
1969 // We're getting move-to-rect callback without move-to-rect call.
1970 // That indicates a compositor bug. It happens when a window is hidden and
1971 // shown again before move-to-rect callback is fired.
1972 // It may lead to incorrect popup placement as we may call
1973 // gtk_window_move() between hide & show.
1974 // See Bug 1777919, 1789581.
1975 #if MOZ_LOGGING
1976 if (!mWaitingForMoveToRectCallback) {
1977 LOG(" Bogus move-to-rect callback! Expect wrong popup coordinates.");
1979 #endif
1981 mWaitingForMoveToRectCallback = false;
1983 bool movedByLayout = mMovedAfterMoveToRect;
1984 bool resizedByLayout = mResizedAfterMoveToRect;
1986 // Popup was moved between move-to-rect call and move-to-rect callback
1987 // and the coordinates from move-to-rect callback are outdated.
1988 if (movedByLayout || resizedByLayout) {
1989 LOG(" Another move/resize called during waiting for callback\n");
1990 mMovedAfterMoveToRect = false;
1991 mResizedAfterMoveToRect = false;
1992 // Fire another round of move/resize to reflect latest request
1993 // from layout.
1994 NativeMoveResize(movedByLayout, resizedByLayout);
1995 return;
1998 LOG(" orig mBounds [%d, %d] -> [%d x %d]\n", mBounds.x, mBounds.y,
1999 mBounds.width, mBounds.height);
2001 LayoutDeviceIntRect newBounds = [&] {
2002 GdkRectangle finalRect = *aFinalSize;
2003 GdkPoint parent = WaylandGetParentPosition();
2004 finalRect.x += parent.x;
2005 finalRect.y += parent.y;
2006 return GdkRectToDevicePixels(finalRect);
2007 }();
2009 LOG(" new mBounds [%d, %d] -> [%d x %d]", newBounds.x, newBounds.y,
2010 newBounds.width, newBounds.height);
2012 bool needsPositionUpdate = newBounds.TopLeft() != mBounds.TopLeft();
2013 bool needsSizeUpdate = newBounds.Size() != mLastSizeRequest;
2015 if (needsSizeUpdate) {
2016 // Wayland compositor changed popup size request from layout.
2017 // Set the constraints to use them in nsMenuPopupFrame::SetPopupPosition().
2018 // Beware that gtk_window_resize() requests sizes asynchronously and so
2019 // newBounds might not have the size from the most recent
2020 // gtk_window_resize().
2021 if (newBounds.width < mLastSizeRequest.width) {
2022 mMoveToRectPopupSize.width = newBounds.width;
2024 if (newBounds.height < mLastSizeRequest.height) {
2025 mMoveToRectPopupSize.height = newBounds.height;
2027 LOG(" mMoveToRectPopupSize set to [%d, %d]", mMoveToRectPopupSize.width,
2028 mMoveToRectPopupSize.height);
2030 mBounds = newBounds;
2031 // Check mBounds size
2032 if (mCompositorSession &&
2033 !wr::WindowSizeSanityCheck(mBounds.width, mBounds.height)) {
2034 gfxCriticalNoteOnce << "Invalid mBounds in PopupCallback " << mBounds
2035 << " size state " << mSizeMode;
2037 WaylandPopupPropagateChangesToLayout(needsPositionUpdate, needsSizeUpdate);
2040 static GdkGravity PopupAlignmentToGdkGravity(int8_t aAlignment) {
2041 switch (aAlignment) {
2042 case POPUPALIGNMENT_NONE:
2043 return GDK_GRAVITY_NORTH_WEST;
2044 case POPUPALIGNMENT_TOPLEFT:
2045 return GDK_GRAVITY_NORTH_WEST;
2046 case POPUPALIGNMENT_TOPRIGHT:
2047 return GDK_GRAVITY_NORTH_EAST;
2048 case POPUPALIGNMENT_BOTTOMLEFT:
2049 return GDK_GRAVITY_SOUTH_WEST;
2050 case POPUPALIGNMENT_BOTTOMRIGHT:
2051 return GDK_GRAVITY_SOUTH_EAST;
2052 case POPUPALIGNMENT_LEFTCENTER:
2053 return GDK_GRAVITY_WEST;
2054 case POPUPALIGNMENT_RIGHTCENTER:
2055 return GDK_GRAVITY_EAST;
2056 case POPUPALIGNMENT_TOPCENTER:
2057 return GDK_GRAVITY_NORTH;
2058 case POPUPALIGNMENT_BOTTOMCENTER:
2059 return GDK_GRAVITY_SOUTH;
2061 return GDK_GRAVITY_STATIC;
2064 bool nsWindow::IsPopupDirectionRTL() {
2065 nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
2066 return popupFrame && popupFrame->IsDirectionRTL();
2069 // Position the popup directly by gtk_window_move() and try to keep it
2070 // on screen by just moving it in scope of it's parent window.
2072 // It's used when we position noautihode popup and we don't use xdg_positioner.
2073 // See Bug 1718867
2074 void nsWindow::WaylandPopupSetDirectPosition() {
2075 GdkPoint topLeft = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
2076 GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mLastSizeRequest);
2078 LOG("nsWindow::WaylandPopupSetDirectPosition %d,%d -> %d x %d\n", topLeft.x,
2079 topLeft.y, size.width, size.height);
2081 mPopupPosition = {topLeft.x, topLeft.y};
2083 if (mIsDragPopup) {
2084 gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y);
2085 gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
2086 // DND window is placed inside container so we need to make hard size
2087 // request to ensure parent container is resized too.
2088 gtk_widget_set_size_request(GTK_WIDGET(mShell), size.width, size.height);
2089 return;
2092 GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
2093 nsWindow* window = get_window_for_gtk_widget(GTK_WIDGET(parentGtkWindow));
2094 if (!window) {
2095 return;
2097 GdkWindow* gdkWindow = window->GetGdkWindow();
2098 if (!gdkWindow) {
2099 return;
2102 int parentWidth = gdk_window_get_width(gdkWindow);
2103 int popupWidth = size.width;
2105 int x;
2106 gdk_window_get_position(gdkWindow, &x, nullptr);
2108 // If popup is bigger than main window just center it.
2109 if (popupWidth > parentWidth) {
2110 mPopupPosition.x = -(parentWidth - popupWidth) / 2 + x;
2111 } else {
2112 if (IsPopupDirectionRTL()) {
2113 // Stick with right window edge
2114 if (mPopupPosition.x < x) {
2115 mPopupPosition.x = x;
2117 } else {
2118 // Stick with left window edge
2119 if (mPopupPosition.x + popupWidth > parentWidth + x) {
2120 mPopupPosition.x = parentWidth + x - popupWidth;
2125 LOG(" set position [%d, %d]\n", mPopupPosition.x, mPopupPosition.y);
2126 gtk_window_move(GTK_WINDOW(mShell), mPopupPosition.x, mPopupPosition.y);
2128 LOG(" set size [%d, %d]\n", size.width, size.height);
2129 gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
2131 if (mPopupPosition.x != topLeft.x) {
2132 mBounds.MoveTo(GdkPointToDevicePixels(mPopupPosition));
2133 LOG(" setting new bounds [%d, %d]\n", mBounds.x, mBounds.y);
2134 WaylandPopupPropagateChangesToLayout(/* move */ true, /* resize */ false);
2138 bool nsWindow::WaylandPopupFitsToplevelWindow(bool aMove) {
2139 LOG("nsWindow::WaylandPopupFitsToplevelWindow() move %d", aMove);
2141 GtkWindow* parent = gtk_window_get_transient_for(GTK_WINDOW(mShell));
2142 GtkWindow* tmp = parent;
2143 while ((tmp = gtk_window_get_transient_for(GTK_WINDOW(parent)))) {
2144 parent = tmp;
2146 GdkWindow* toplevelGdkWindow = gtk_widget_get_window(GTK_WIDGET(parent));
2147 if (!toplevelGdkWindow) {
2148 NS_WARNING("Toplevel widget without GdkWindow?");
2149 return false;
2152 int parentWidth = gdk_window_get_width(toplevelGdkWindow);
2153 int parentHeight = gdk_window_get_height(toplevelGdkWindow);
2154 LOG(" parent size %d x %d", parentWidth, parentHeight);
2156 GdkPoint topLeft = aMove ? mPopupPosition
2157 : DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
2158 GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mLastSizeRequest);
2159 LOG(" popup topleft %d, %d size %d x %d", topLeft.x, topLeft.y, size.width,
2160 size.height);
2161 int fits = topLeft.x >= 0 && topLeft.y >= 0 &&
2162 topLeft.x + size.width <= parentWidth &&
2163 topLeft.y + size.height <= parentHeight;
2165 LOG(" fits %d", fits);
2166 return fits;
2169 void nsWindow::NativeMoveResizeWaylandPopup(bool aMove, bool aResize) {
2170 GdkPoint topLeft = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
2171 GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mLastSizeRequest);
2173 LOG("nsWindow::NativeMoveResizeWaylandPopup Bounds %d,%d -> %d x %d move %d "
2174 "resize %d\n",
2175 topLeft.x, topLeft.y, size.width, size.height, aMove, aResize);
2177 // Compositor may be confused by windows with width/height = 0
2178 // and positioning such windows leads to Bug 1555866.
2179 if (!AreBoundsSane()) {
2180 LOG(" Bounds are not sane (width: %d height: %d)\n",
2181 mLastSizeRequest.width, mLastSizeRequest.height);
2182 return;
2185 if (mWaitingForMoveToRectCallback) {
2186 LOG(" waiting for move to rect, scheduling");
2187 // mBounds position must not be overwritten before it is applied.
2188 // OnConfigureEvent() will not set mBounds to an old position for
2189 // GTK_WINDOW_POPUP.
2190 MOZ_ASSERT(gtk_window_get_window_type(GTK_WINDOW(mShell)) ==
2191 GTK_WINDOW_POPUP);
2192 mMovedAfterMoveToRect = aMove;
2193 mResizedAfterMoveToRect = aResize;
2194 return;
2197 mMovedAfterMoveToRect = false;
2198 mResizedAfterMoveToRect = false;
2200 bool trackedInHierarchy = WaylandPopupConfigure();
2202 // Read popup position from layout if it was moved or newly created.
2203 // This position is used by move-to-rect method as we need anchor and other
2204 // info to place popup correctly.
2205 // We need WaylandPopupConfigure() to be called before to have all needed
2206 // popup info in place (mainly the anchored flag).
2207 if (aMove) {
2208 mPopupMoveToRectParams = WaylandPopupGetPositionFromLayout();
2211 if (!trackedInHierarchy) {
2212 WaylandPopupSetDirectPosition();
2213 return;
2216 if (aResize) {
2217 LOG(" set size [%d, %d]\n", size.width, size.height);
2218 gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
2221 if (!aMove && WaylandPopupFitsToplevelWindow(aMove)) {
2222 // Popup position has not been changed and its position/size fits
2223 // parent window so no need to reposition the window.
2224 LOG(" fits parent window size, just resize\n");
2225 return;
2228 // Mark popup as changed as we're updating position/size.
2229 mPopupChanged = true;
2231 // Save popup position for former re-calculations when popup hierarchy
2232 // is changed.
2233 LOG(" popup position changed from [%d, %d] to [%d, %d]\n", mPopupPosition.x,
2234 mPopupPosition.y, topLeft.x, topLeft.y);
2235 mPopupPosition = {topLeft.x, topLeft.y};
2237 UpdateWaylandPopupHierarchy();
2240 struct PopupSides {
2241 Maybe<Side> mVertical;
2242 Maybe<Side> mHorizontal;
2245 static PopupSides SidesForPopupAlignment(int8_t aAlignment) {
2246 switch (aAlignment) {
2247 case POPUPALIGNMENT_NONE:
2248 break;
2249 case POPUPALIGNMENT_TOPLEFT:
2250 return {Some(eSideTop), Some(eSideLeft)};
2251 case POPUPALIGNMENT_TOPRIGHT:
2252 return {Some(eSideTop), Some(eSideRight)};
2253 case POPUPALIGNMENT_BOTTOMLEFT:
2254 return {Some(eSideBottom), Some(eSideLeft)};
2255 case POPUPALIGNMENT_BOTTOMRIGHT:
2256 return {Some(eSideBottom), Some(eSideRight)};
2257 case POPUPALIGNMENT_LEFTCENTER:
2258 return {Nothing(), Some(eSideLeft)};
2259 case POPUPALIGNMENT_RIGHTCENTER:
2260 return {Nothing(), Some(eSideRight)};
2261 case POPUPALIGNMENT_TOPCENTER:
2262 return {Some(eSideTop), Nothing()};
2263 case POPUPALIGNMENT_BOTTOMCENTER:
2264 return {Some(eSideBottom), Nothing()};
2266 return {};
2269 // We want to apply margins based on popup alignment (which would generally be
2270 // just an offset to apply to the popup). However, to deal with flipping
2271 // correctly, we apply the margin to the anchor when possible.
2272 struct ResolvedPopupMargin {
2273 // A margin to be applied to the anchor.
2274 nsMargin mAnchorMargin;
2275 // An offset in app units to be applied to the popup for when we need to tell
2276 // GTK to center inside the anchor precisely (so we can't really do better in
2277 // presence of flips).
2278 nsPoint mPopupOffset;
2281 static ResolvedPopupMargin ResolveMargin(nsMenuPopupFrame* aFrame,
2282 int8_t aPopupAlign,
2283 int8_t aAnchorAlign,
2284 bool aAnchoredToPoint,
2285 bool aIsContextMenu) {
2286 nsMargin margin = aFrame->GetMargin();
2287 nsPoint offset;
2289 if (aAnchoredToPoint) {
2290 // Since GTK doesn't allow us to specify margins itself, when anchored to a
2291 // point we can just assume we'll be aligned correctly... This is kind of
2292 // annoying but alas.
2294 // This calculation must match the relevant unanchored popup calculation in
2295 // nsMenuPopupFrame::SetPopupPosition(), which should itself be the inverse
2296 // inverse of nsMenuPopupFrame::MoveTo().
2297 if (aIsContextMenu && aFrame->IsDirectionRTL()) {
2298 offset.x = -margin.right;
2299 } else {
2300 offset.x = margin.left;
2302 offset.y = margin.top;
2303 return {nsMargin(), offset};
2306 auto popupSides = SidesForPopupAlignment(aPopupAlign);
2307 auto anchorSides = SidesForPopupAlignment(aAnchorAlign);
2308 // Matched sides: Invert the margin, so that we pull in the right direction.
2309 // Popup not aligned to any anchor side: We give up and use the offset,
2310 // applying the margin from the popup side.
2311 // Mismatched sides: We swap the margins so that we pull in the right
2312 // direction, e.g. margin-left: -10px should shrink 10px the _right_ of the
2313 // box, not the left of the box.
2314 if (popupSides.mHorizontal == anchorSides.mHorizontal) {
2315 margin.left = -margin.left;
2316 margin.right = -margin.right;
2317 } else if (!anchorSides.mHorizontal) {
2318 auto popupSide = *popupSides.mHorizontal;
2319 offset.x += popupSide == eSideRight ? -margin.Side(popupSide)
2320 : margin.Side(popupSide);
2321 margin.left = margin.right = 0;
2322 } else {
2323 std::swap(margin.left, margin.right);
2326 // Same logic as above, but in the vertical direction.
2327 if (popupSides.mVertical == anchorSides.mVertical) {
2328 margin.top = -margin.top;
2329 margin.bottom = -margin.bottom;
2330 } else if (!anchorSides.mVertical) {
2331 auto popupSide = *popupSides.mVertical;
2332 offset.y += popupSide == eSideBottom ? -margin.Side(popupSide)
2333 : margin.Side(popupSide);
2334 margin.top = margin.bottom = 0;
2335 } else {
2336 std::swap(margin.top, margin.bottom);
2339 return {margin, offset};
2342 #ifdef MOZ_LOGGING
2343 void nsWindow::LogPopupAnchorHints(int aHints) {
2344 static struct hints_ {
2345 int hint;
2346 char name[100];
2347 } hints[] = {
2348 {GDK_ANCHOR_FLIP_X, "GDK_ANCHOR_FLIP_X"},
2349 {GDK_ANCHOR_FLIP_Y, "GDK_ANCHOR_FLIP_Y"},
2350 {GDK_ANCHOR_SLIDE_X, "GDK_ANCHOR_SLIDE_X"},
2351 {GDK_ANCHOR_SLIDE_Y, "GDK_ANCHOR_SLIDE_Y"},
2352 {GDK_ANCHOR_RESIZE_X, "GDK_ANCHOR_RESIZE_X"},
2353 {GDK_ANCHOR_RESIZE_Y, "GDK_ANCHOR_RESIZE_X"},
2356 LOG(" PopupAnchorHints");
2357 for (const auto& hint : hints) {
2358 if (hint.hint & aHints) {
2359 LOG(" %s", hint.name);
2364 void nsWindow::LogPopupGravity(GdkGravity aGravity) {
2365 static char gravity[][100]{"NONE",
2366 "GDK_GRAVITY_NORTH_WEST",
2367 "GDK_GRAVITY_NORTH",
2368 "GDK_GRAVITY_NORTH_EAST",
2369 "GDK_GRAVITY_WEST",
2370 "GDK_GRAVITY_CENTER",
2371 "GDK_GRAVITY_EAST",
2372 "GDK_GRAVITY_SOUTH_WEST",
2373 "GDK_GRAVITY_SOUTH",
2374 "GDK_GRAVITY_SOUTH_EAST",
2375 "GDK_GRAVITY_STATIC"};
2376 LOG(" %s", gravity[aGravity]);
2378 #endif
2380 const nsWindow::WaylandPopupMoveToRectParams
2381 nsWindow::WaylandPopupGetPositionFromLayout() {
2382 LOG("nsWindow::WaylandPopupGetPositionFromLayout\n");
2384 nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
2386 const bool isTopContextMenu = mPopupContextMenu && !mPopupAnchored;
2387 const bool isRTL = IsPopupDirectionRTL();
2388 const bool anchored = popupFrame->IsAnchored();
2389 int8_t popupAlign = POPUPALIGNMENT_TOPLEFT;
2390 int8_t anchorAlign = POPUPALIGNMENT_BOTTOMRIGHT;
2391 if (anchored) {
2392 // See nsMenuPopupFrame::AdjustPositionForAnchorAlign.
2393 popupAlign = popupFrame->GetPopupAlignment();
2394 anchorAlign = popupFrame->GetPopupAnchor();
2396 if (isRTL && (anchored || isTopContextMenu)) {
2397 popupAlign = -popupAlign;
2398 anchorAlign = -anchorAlign;
2401 // Although we have mPopupPosition / mRelativePopupPosition here
2402 // we can't use it. move-to-rect needs anchor rectangle to position a popup
2403 // but we have only a point from Resize().
2405 // So we need to extract popup position from nsMenuPopupFrame() and duplicate
2406 // the layout work here.
2407 LayoutDeviceIntRect anchorRect;
2408 ResolvedPopupMargin popupMargin;
2410 nsRect anchorRectAppUnits = popupFrame->GetUntransformedAnchorRect();
2411 // This is a somewhat hacky way of applying the popup margin. We don't know
2412 // if GTK will end up flipping the popup, in which case the offset we
2413 // compute is just wrong / applied to the wrong side.
2415 // Instead, we tell it to anchor us at a smaller or bigger rect depending on
2416 // the margin, which achieves the same result if the popup is positioned
2417 // correctly, but doesn't misposition the popup when flipped across the
2418 // anchor.
2419 popupMargin = ResolveMargin(popupFrame, popupAlign, anchorAlign,
2420 anchorRectAppUnits.IsEmpty(), isTopContextMenu);
2421 LOG(" layout popup CSS anchor (%d, %d) %s, margin %s offset %s\n",
2422 popupAlign, anchorAlign, ToString(anchorRectAppUnits).c_str(),
2423 ToString(popupMargin.mAnchorMargin).c_str(),
2424 ToString(popupMargin.mPopupOffset).c_str());
2425 anchorRectAppUnits.Inflate(popupMargin.mAnchorMargin);
2426 LOG(" after margins %s\n", ToString(anchorRectAppUnits).c_str());
2427 nscoord auPerDev = popupFrame->PresContext()->AppUnitsPerDevPixel();
2428 anchorRect = LayoutDeviceIntRect::FromAppUnitsToNearest(anchorRectAppUnits,
2429 auPerDev);
2430 if (anchorRect.width < 0) {
2431 auto w = -anchorRect.width;
2432 anchorRect.width += w + 1;
2433 anchorRect.x += w;
2435 LOG(" final %s\n", ToString(anchorRect).c_str());
2438 LOG(" relative popup rect position [%d, %d] -> [%d x %d]\n", anchorRect.x,
2439 anchorRect.y, anchorRect.width, anchorRect.height);
2441 // Get gravity and flip type
2442 GdkGravity rectAnchor = PopupAlignmentToGdkGravity(anchorAlign);
2443 GdkGravity menuAnchor = PopupAlignmentToGdkGravity(popupAlign);
2445 LOG(" parentRect gravity: %d anchor gravity: %d\n", rectAnchor, menuAnchor);
2447 // Gtk default is: GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE | GDK_ANCHOR_RESIZE.
2448 // We want to SLIDE_X menu on the dual monitor setup rather than resize it
2449 // on the other monitor.
2450 GdkAnchorHints hints =
2451 GdkAnchorHints(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_RESIZE);
2453 // slideHorizontal from nsMenuPopupFrame::SetPopupPosition
2454 int8_t position = popupFrame->GetAlignmentPosition();
2455 if (position >= POPUPPOSITION_BEFORESTART &&
2456 position <= POPUPPOSITION_AFTEREND) {
2457 hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE_X);
2459 // slideVertical from nsMenuPopupFrame::SetPopupPosition
2460 if (position >= POPUPPOSITION_STARTBEFORE &&
2461 position <= POPUPPOSITION_ENDAFTER) {
2462 hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE_Y);
2465 FlipType flipType = popupFrame->GetFlipType();
2466 if (rectAnchor == GDK_GRAVITY_CENTER && menuAnchor == GDK_GRAVITY_CENTER) {
2467 // only slide
2468 hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
2469 } else {
2470 switch (flipType) {
2471 case FlipType_Both:
2472 hints = GdkAnchorHints(hints | GDK_ANCHOR_FLIP);
2473 break;
2474 case FlipType_Slide:
2475 hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
2476 break;
2477 case FlipType_Default:
2478 hints = GdkAnchorHints(hints | GDK_ANCHOR_FLIP);
2479 break;
2480 default:
2481 break;
2484 if (!WaylandPopupIsMenu()) {
2485 // we don't want to slide menus to fit the screen rather resize them
2486 hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
2489 // We want tooltips to flip verticaly or slide only.
2490 // See nsMenuPopupFrame::SetPopupPosition().
2491 // https://searchfox.org/mozilla-central/rev/d0f5bc50aff3462c9d1546b88d60c5cb020eb15c/layout/xul/nsMenuPopupFrame.cpp#1603
2492 if (mPopupType == PopupType::Tooltip) {
2493 hints = GdkAnchorHints(GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_SLIDE);
2496 return {
2497 anchorRect,
2498 rectAnchor,
2499 menuAnchor,
2500 hints,
2501 DevicePixelsToGdkPointRoundDown(LayoutDevicePoint::FromAppUnitsToNearest(
2502 popupMargin.mPopupOffset,
2503 popupFrame->PresContext()->AppUnitsPerDevPixel())),
2504 true};
2507 bool nsWindow::WaylandPopupAnchorAdjustForParentPopup(
2508 GdkRectangle* aPopupAnchor, GdkPoint* aOffset) {
2509 LOG("nsWindow::WaylandPopupAnchorAdjustForParentPopup");
2511 GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
2512 if (!parentGtkWindow || !GTK_IS_WIDGET(parentGtkWindow)) {
2513 NS_WARNING("Popup has no parent!");
2514 return false;
2516 GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(parentGtkWindow));
2517 if (!window) {
2518 NS_WARNING("Popup parrent is not mapped!");
2519 return false;
2522 GdkRectangle parentWindowRect = {0, 0, gdk_window_get_width(window),
2523 gdk_window_get_height(window)};
2524 LOG(" parent window size %d x %d", parentWindowRect.width,
2525 parentWindowRect.height);
2527 // We can't have rectangle anchor with zero width/height.
2528 if (!aPopupAnchor->width) {
2529 aPopupAnchor->width = 1;
2531 if (!aPopupAnchor->height) {
2532 aPopupAnchor->height = 1;
2535 GdkRectangle finalRect;
2536 if (!gdk_rectangle_intersect(aPopupAnchor, &parentWindowRect, &finalRect)) {
2537 return false;
2539 *aPopupAnchor = finalRect;
2540 LOG(" anchor is correct %d,%d -> %d x %d", finalRect.x, finalRect.y,
2541 finalRect.width, finalRect.height);
2543 *aOffset = mPopupMoveToRectParams.mOffset;
2544 LOG(" anchor offset %d, %d", aOffset->x, aOffset->y);
2545 return true;
2548 bool nsWindow::WaylandPopupCheckAndGetAnchor(GdkRectangle* aPopupAnchor,
2549 GdkPoint* aOffset) {
2550 LOG("nsWindow::WaylandPopupCheckAndGetAnchor");
2552 GdkWindow* gdkWindow = GetToplevelGdkWindow();
2553 nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
2554 if (!gdkWindow || !popupFrame) {
2555 LOG(" can't use move-to-rect due missing gdkWindow or popupFrame");
2556 return false;
2559 if (popupFrame->IsConstrainedByLayout()) {
2560 LOG(" can't use move-to-rect, flipped / constrained by layout");
2561 return false;
2564 if (!mPopupMoveToRectParams.mAnchorSet) {
2565 LOG(" can't use move-to-rect due missing anchor");
2566 return false;
2568 // Update popup layout coordinates from layout by recent popup hierarchy
2569 // (calculate correct position according to parent window)
2570 // and convert to Gtk coordinates.
2571 LayoutDeviceIntRect anchorRect = mPopupMoveToRectParams.mAnchorRect;
2572 if (!WaylandPopupIsFirst()) {
2573 GdkPoint parent = WaylandGetParentPosition();
2574 LOG(" subtract parent position from anchor [%d, %d]\n", parent.x,
2575 parent.y);
2576 anchorRect.MoveBy(-GdkPointToDevicePixels(parent));
2579 *aPopupAnchor = DevicePixelsToGdkRectRoundOut(anchorRect);
2580 LOG(" anchored to rectangle [%d, %d] -> [%d x %d]", aPopupAnchor->x,
2581 aPopupAnchor->y, aPopupAnchor->width, aPopupAnchor->height);
2583 if (!WaylandPopupAnchorAdjustForParentPopup(aPopupAnchor, aOffset)) {
2584 LOG(" can't use move-to-rect, anchor is not placed inside of parent "
2585 "window");
2586 return false;
2589 return true;
2592 void nsWindow::WaylandPopupPrepareForMove() {
2593 LOG("nsWindow::WaylandPopupPrepareForMove()");
2595 if (mPopupType == PopupType::Tooltip) {
2596 // Don't fiddle with tooltips type, just hide it before move-to-rect
2597 if (mPopupUseMoveToRect && gtk_widget_is_visible(mShell)) {
2598 HideWaylandPopupWindow(/* aTemporaryHide */ true,
2599 /* aRemoveFromPopupList */ false);
2601 LOG(" it's tooltip, quit");
2602 return;
2605 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1785185#c8
2606 // gtk_window_move() needs GDK_WINDOW_TYPE_HINT_UTILITY popup type.
2607 // move-to-rect requires GDK_WINDOW_TYPE_HINT_POPUP_MENU popups type.
2608 // We need to set it before map event when popup is hidden.
2609 const GdkWindowTypeHint currentType =
2610 gtk_window_get_type_hint(GTK_WINDOW(mShell));
2611 const GdkWindowTypeHint requiredType = mPopupUseMoveToRect
2612 ? GDK_WINDOW_TYPE_HINT_POPUP_MENU
2613 : GDK_WINDOW_TYPE_HINT_UTILITY;
2615 if (!mPopupUseMoveToRect && currentType == requiredType) {
2616 LOG(" type matches and we're not forced to hide it, quit.");
2617 return;
2620 if (gtk_widget_is_visible(mShell)) {
2621 HideWaylandPopupWindow(/* aTemporaryHide */ true,
2622 /* aRemoveFromPopupList */ false);
2625 if (currentType != requiredType) {
2626 LOG(" set type %s",
2627 requiredType == GDK_WINDOW_TYPE_HINT_POPUP_MENU ? "MENU" : "UTILITY");
2628 gtk_window_set_type_hint(GTK_WINDOW(mShell), requiredType);
2632 // Plain popup move on Wayland - simply place popup on given location.
2633 // We can't just call gtk_window_move() as it's not effective on visible
2634 // popups.
2635 void nsWindow::WaylandPopupMovePlain(int aX, int aY) {
2636 LOG("nsWindow::WaylandPopupMovePlain(%d, %d)", aX, aY);
2638 // We can directly move only popups based on wl_subsurface type.
2639 MOZ_DIAGNOSTIC_ASSERT(gtk_window_get_type_hint(GTK_WINDOW(mShell)) ==
2640 GDK_WINDOW_TYPE_HINT_UTILITY ||
2641 gtk_window_get_type_hint(GTK_WINDOW(mShell)) ==
2642 GDK_WINDOW_TYPE_HINT_TOOLTIP);
2644 gtk_window_move(GTK_WINDOW(mShell), aX, aY);
2646 // gtk_window_move() can trick us. When widget is hidden gtk_window_move()
2647 // does not move the widget but sets new widget coordinates when widget
2648 // is mapped again.
2650 // If popup used move-to-rect before
2651 // (GdkWindow has POSITION_METHOD_MOVE_TO_RECT set), popup will use
2652 // move-to-rect again when it's mapped and we'll get bogus move-to-rect
2653 // callback.
2655 // gdk_window_move() sets position_method to POSITION_METHOD_MOVE_RESIZE
2656 // so we'll use plain move when popup is shown.
2657 if (!gtk_widget_get_mapped(mShell)) {
2658 if (GdkWindow* window = GetToplevelGdkWindow()) {
2659 gdk_window_move(window, aX, aY);
2664 void nsWindow::WaylandPopupMoveImpl() {
2665 // Available as of GTK 3.24+
2666 static auto sGdkWindowMoveToRect = (void (*)(
2667 GdkWindow*, const GdkRectangle*, GdkGravity, GdkGravity, GdkAnchorHints,
2668 gint, gint))dlsym(RTLD_DEFAULT, "gdk_window_move_to_rect");
2670 if (mPopupUseMoveToRect && !sGdkWindowMoveToRect) {
2671 LOG("can't use move-to-rect due missing gdk_window_move_to_rect()");
2672 mPopupUseMoveToRect = false;
2675 GdkRectangle gtkAnchorRect;
2676 GdkPoint offset;
2677 if (mPopupUseMoveToRect) {
2678 mPopupUseMoveToRect =
2679 WaylandPopupCheckAndGetAnchor(&gtkAnchorRect, &offset);
2682 LOG("nsWindow::WaylandPopupMove");
2683 LOG(" original widget popup position [%d, %d]\n", mPopupPosition.x,
2684 mPopupPosition.y);
2685 LOG(" relative widget popup position [%d, %d]\n", mRelativePopupPosition.x,
2686 mRelativePopupPosition.y);
2687 LOG(" popup use move to rect %d", mPopupUseMoveToRect);
2689 WaylandPopupPrepareForMove();
2691 if (!mPopupUseMoveToRect) {
2692 WaylandPopupMovePlain(mRelativePopupPosition.x, mRelativePopupPosition.y);
2693 // Layout already should be aware of our bounds, since we didn't change it
2694 // from the widget side for flipping or so.
2695 return;
2698 // Correct popup position now. It will be updated by gdk_window_move_to_rect()
2699 // anyway but we need to set it now to avoid a race condition here.
2700 WaylandPopupRemoveNegativePosition();
2702 GdkWindow* gdkWindow = GetToplevelGdkWindow();
2703 if (!g_signal_handler_find(gdkWindow, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
2704 FuncToGpointer(NativeMoveResizeCallback), this)) {
2705 g_signal_connect(gdkWindow, "moved-to-rect",
2706 G_CALLBACK(NativeMoveResizeCallback), this);
2708 mWaitingForMoveToRectCallback = true;
2710 #ifdef MOZ_LOGGING
2711 if (LOG_ENABLED()) {
2712 LOG(" Call move-to-rect");
2713 LOG(" Anchor rect [%d, %d] -> [%d x %d]", gtkAnchorRect.x, gtkAnchorRect.y,
2714 gtkAnchorRect.width, gtkAnchorRect.height);
2715 LOG(" Offset [%d, %d]", offset.x, offset.y);
2716 LOG(" AnchorType");
2717 LogPopupGravity(mPopupMoveToRectParams.mAnchorRectType);
2718 LOG(" PopupAnchorType");
2719 LogPopupGravity(mPopupMoveToRectParams.mPopupAnchorType);
2720 LogPopupAnchorHints(mPopupMoveToRectParams.mHints);
2722 #endif
2724 sGdkWindowMoveToRect(gdkWindow, &gtkAnchorRect,
2725 mPopupMoveToRectParams.mAnchorRectType,
2726 mPopupMoveToRectParams.mPopupAnchorType,
2727 mPopupMoveToRectParams.mHints, offset.x, offset.y);
2730 void nsWindow::SetZIndex(int32_t aZIndex) {
2731 nsIWidget* oldPrev = GetPrevSibling();
2733 nsBaseWidget::SetZIndex(aZIndex);
2735 if (GetPrevSibling() == oldPrev) {
2736 return;
2739 // We skip the nsWindows that don't have mGdkWindows.
2740 // These are probably in the process of being destroyed.
2741 if (!mGdkWindow) {
2742 return;
2745 if (!GetNextSibling()) {
2746 // We're to be on top.
2747 if (mGdkWindow) {
2748 gdk_window_raise(mGdkWindow);
2750 } else {
2751 // All the siblings before us need to be below our widget.
2752 for (nsWindow* w = this; w;
2753 w = static_cast<nsWindow*>(w->GetPrevSibling())) {
2754 if (w->mGdkWindow) {
2755 gdk_window_lower(w->mGdkWindow);
2761 void nsWindow::SetSizeMode(nsSizeMode aMode) {
2762 LOG("nsWindow::SetSizeMode %d\n", aMode);
2764 // Return if there's no shell or our current state is the same as the mode we
2765 // were just set to.
2766 if (!mShell) {
2767 LOG(" no shell");
2768 return;
2771 if (mSizeMode == aMode && mLastSizeModeRequest == aMode) {
2772 LOG(" already set");
2773 return;
2776 // It is tempting to try to optimize calls below based only on current
2777 // mSizeMode, but that wouldn't work if there's a size-request in flight
2778 // (specially before show). See bug 1789823.
2779 const auto SizeModeMightBe = [&](nsSizeMode aModeToTest) {
2780 if (mSizeMode != mLastSizeModeRequest) {
2781 // Arbitrary size mode requests might be ongoing.
2782 return true;
2784 return mSizeMode == aModeToTest;
2787 if (aMode != nsSizeMode_Fullscreen && aMode != nsSizeMode_Minimized) {
2788 // Fullscreen and minimized are compatible.
2789 if (SizeModeMightBe(nsSizeMode_Fullscreen)) {
2790 MakeFullScreen(false);
2794 switch (aMode) {
2795 case nsSizeMode_Maximized:
2796 LOG(" set maximized");
2797 gtk_window_maximize(GTK_WINDOW(mShell));
2798 break;
2799 case nsSizeMode_Minimized:
2800 LOG(" set minimized");
2801 gtk_window_iconify(GTK_WINDOW(mShell));
2802 break;
2803 case nsSizeMode_Fullscreen:
2804 LOG(" set fullscreen");
2805 MakeFullScreen(true);
2806 break;
2807 default:
2808 MOZ_FALLTHROUGH_ASSERT("Unknown size mode");
2809 case nsSizeMode_Normal:
2810 LOG(" set normal");
2811 if (SizeModeMightBe(nsSizeMode_Maximized)) {
2812 gtk_window_unmaximize(GTK_WINDOW(mShell));
2814 if (SizeModeMightBe(nsSizeMode_Minimized)) {
2815 gtk_window_deiconify(GTK_WINDOW(mShell));
2816 // We need this for actual deiconification on mutter.
2817 gtk_window_present(GTK_WINDOW(mShell));
2819 break;
2821 mLastSizeModeRequest = aMode;
2824 #define kDesktopMutterSchema "org.gnome.mutter"_ns
2825 #define kDesktopDynamicWorkspacesKey "dynamic-workspaces"_ns
2827 static bool WorkspaceManagementDisabled(GdkScreen* screen) {
2828 if (Preferences::GetBool("widget.disable-workspace-management", false)) {
2829 return true;
2831 if (Preferences::HasUserValue("widget.workspace-management")) {
2832 return Preferences::GetBool("widget.workspace-management");
2835 if (IsGnomeDesktopEnvironment()) {
2836 // Gnome uses dynamic workspaces by default so disable workspace management
2837 // in that case.
2838 bool usesDynamicWorkspaces = true;
2839 nsCOMPtr<nsIGSettingsService> gsettings =
2840 do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
2841 if (gsettings) {
2842 nsCOMPtr<nsIGSettingsCollection> mutterSettings;
2843 gsettings->GetCollectionForSchema(kDesktopMutterSchema,
2844 getter_AddRefs(mutterSettings));
2845 if (mutterSettings) {
2846 mutterSettings->GetBoolean(kDesktopDynamicWorkspacesKey,
2847 &usesDynamicWorkspaces);
2850 return usesDynamicWorkspaces;
2853 const auto& desktop = GetDesktopEnvironmentIdentifier();
2854 return desktop.EqualsLiteral("bspwm") || desktop.EqualsLiteral("i3");
2857 void nsWindow::GetWorkspaceID(nsAString& workspaceID) {
2858 workspaceID.Truncate();
2860 if (!GdkIsX11Display() || !mShell) {
2861 return;
2864 #ifdef MOZ_X11
2865 LOG("nsWindow::GetWorkspaceID()\n");
2867 // Get the gdk window for this widget.
2868 GdkWindow* gdk_window = GetToplevelGdkWindow();
2869 if (!gdk_window) {
2870 LOG(" missing Gdk window, quit.");
2871 return;
2874 if (WorkspaceManagementDisabled(gdk_window_get_screen(gdk_window))) {
2875 LOG(" WorkspaceManagementDisabled, quit.");
2876 return;
2879 GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
2880 GdkAtom type_returned;
2881 int format_returned;
2882 int length_returned;
2883 long* wm_desktop;
2885 if (!gdk_property_get(gdk_window, gdk_atom_intern("_NET_WM_DESKTOP", FALSE),
2886 cardinal_atom,
2887 0, // offset
2888 INT32_MAX, // length
2889 FALSE, // delete
2890 &type_returned, &format_returned, &length_returned,
2891 (guchar**)&wm_desktop)) {
2892 LOG(" gdk_property_get() failed, quit.");
2893 return;
2896 LOG(" got workspace ID %d", (int32_t)wm_desktop[0]);
2897 workspaceID.AppendInt((int32_t)wm_desktop[0]);
2898 g_free(wm_desktop);
2899 #endif
2902 void nsWindow::MoveToWorkspace(const nsAString& workspaceIDStr) {
2903 nsresult rv = NS_OK;
2904 int32_t workspaceID = workspaceIDStr.ToInteger(&rv);
2906 LOG("nsWindow::MoveToWorkspace() ID %d", workspaceID);
2907 if (NS_FAILED(rv) || !workspaceID || !GdkIsX11Display() || !mShell) {
2908 LOG(" MoveToWorkspace disabled, quit");
2909 return;
2912 #ifdef MOZ_X11
2913 // Get the gdk window for this widget.
2914 GdkWindow* gdk_window = GetToplevelGdkWindow();
2915 if (!gdk_window) {
2916 LOG(" failed to get GdkWindow, quit.");
2917 return;
2920 // This code is inspired by some found in the 'gxtuner' project.
2921 // https://github.com/brummer10/gxtuner/blob/792d453da0f3a599408008f0f1107823939d730d/deskpager.cpp#L50
2922 XEvent xevent;
2923 Display* xdisplay = gdk_x11_get_default_xdisplay();
2924 GdkScreen* screen = gdk_window_get_screen(gdk_window);
2925 Window root_win = GDK_WINDOW_XID(gdk_screen_get_root_window(screen));
2926 GdkDisplay* display = gdk_window_get_display(gdk_window);
2927 Atom type = gdk_x11_get_xatom_by_name_for_display(display, "_NET_WM_DESKTOP");
2929 xevent.type = ClientMessage;
2930 xevent.xclient.type = ClientMessage;
2931 xevent.xclient.serial = 0;
2932 xevent.xclient.send_event = TRUE;
2933 xevent.xclient.display = xdisplay;
2934 xevent.xclient.window = GDK_WINDOW_XID(gdk_window);
2935 xevent.xclient.message_type = type;
2936 xevent.xclient.format = 32;
2937 xevent.xclient.data.l[0] = workspaceID;
2938 xevent.xclient.data.l[1] = X11CurrentTime;
2939 xevent.xclient.data.l[2] = 0;
2940 xevent.xclient.data.l[3] = 0;
2941 xevent.xclient.data.l[4] = 0;
2943 XSendEvent(xdisplay, root_win, FALSE,
2944 SubstructureNotifyMask | SubstructureRedirectMask, &xevent);
2946 XFlush(xdisplay);
2947 LOG(" moved to workspace");
2948 #endif
2951 void nsWindow::SetUserTimeAndStartupTokenForActivatedWindow() {
2952 nsGTKToolkit* toolkit = nsGTKToolkit::GetToolkit();
2953 if (!toolkit || MOZ_UNLIKELY(mWindowType == WindowType::Invisible)) {
2954 return;
2957 mWindowActivationTokenFromEnv = toolkit->GetStartupToken();
2958 if (!mWindowActivationTokenFromEnv.IsEmpty()) {
2959 if (!GdkIsWaylandDisplay()) {
2960 gtk_window_set_startup_id(GTK_WINDOW(mShell),
2961 mWindowActivationTokenFromEnv.get());
2962 // In the case of X11, the above call is all we need. For wayland we need
2963 // to keep the token around until we take it in
2964 // TransferFocusToWaylandWindow.
2965 mWindowActivationTokenFromEnv.Truncate();
2967 } else if (uint32_t timestamp = toolkit->GetFocusTimestamp()) {
2968 // We don't have the data we need. Fall back to an
2969 // approximation ... using the timestamp of the remote command
2970 // being received as a guess for the timestamp of the user event
2971 // that triggered it.
2972 gdk_window_focus(GetToplevelGdkWindow(), timestamp);
2975 // If we used the startup ID, that already contains the focus timestamp;
2976 // we don't want to reuse the timestamp next time we raise the window
2977 toolkit->SetFocusTimestamp(0);
2978 toolkit->SetStartupToken(""_ns);
2981 /* static */
2982 guint32 nsWindow::GetLastUserInputTime() {
2983 // gdk_x11_display_get_user_time/gtk_get_current_event_time tracks
2984 // button and key presses, DESKTOP_STARTUP_ID used to start the app,
2985 // drop events from external drags,
2986 // WM_DELETE_WINDOW delete events, but not usually mouse motion nor
2987 // button and key releases. Therefore use the most recent of
2988 // gdk_x11_display_get_user_time and the last time that we have seen.
2989 #ifdef MOZ_X11
2990 GdkDisplay* gdkDisplay = gdk_display_get_default();
2991 guint32 timestamp = GdkIsX11Display(gdkDisplay)
2992 ? gdk_x11_display_get_user_time(gdkDisplay)
2993 : gtk_get_current_event_time();
2994 #else
2995 guint32 timestamp = gtk_get_current_event_time();
2996 #endif
2998 if (sLastUserInputTime != GDK_CURRENT_TIME &&
2999 TimestampIsNewerThan(sLastUserInputTime, timestamp)) {
3000 return sLastUserInputTime;
3003 return timestamp;
3006 #ifdef MOZ_WAYLAND
3007 void nsWindow::FocusWaylandWindow(const char* aTokenID) {
3008 MOZ_DIAGNOSTIC_ASSERT(aTokenID);
3010 LOG("nsWindow::FocusWaylandWindow(%s)", aTokenID);
3011 if (IsDestroyed()) {
3012 LOG(" already destroyed, quit.");
3013 return;
3015 wl_surface* surface =
3016 mGdkWindow ? gdk_wayland_window_get_wl_surface(mGdkWindow) : nullptr;
3017 if (!surface) {
3018 LOG(" mGdkWindow is not visible, quit.");
3019 return;
3022 LOG(" requesting xdg-activation, surface ID %d",
3023 wl_proxy_get_id((struct wl_proxy*)surface));
3024 xdg_activation_v1* xdg_activation = WaylandDisplayGet()->GetXdgActivation();
3025 if (!xdg_activation) {
3026 return;
3028 xdg_activation_v1_activate(xdg_activation, aTokenID, surface);
3031 // Transfer focus from gFocusWindow to aWindow and use xdg_activation
3032 // protocol for it.
3033 void nsWindow::TransferFocusToWaylandWindow(nsWindow* aWindow) {
3034 LOGW("nsWindow::TransferFocusToWaylandWindow(%p) gFocusWindow %p", aWindow,
3035 gFocusWindow);
3036 auto promise = mozilla::widget::RequestWaylandFocusPromise();
3037 if (NS_WARN_IF(!promise)) {
3038 LOGW(" quit, failed to create TransferFocusToWaylandWindow [%p]", aWindow);
3039 return;
3041 promise->Then(
3042 GetMainThreadSerialEventTarget(), __func__,
3043 /* resolve */
3044 [window = RefPtr{aWindow}](nsCString token) {
3045 window->FocusWaylandWindow(token.get());
3047 /* reject */
3048 [window = RefPtr{aWindow}](bool state) {
3049 LOGW("TransferFocusToWaylandWindow [%p] failed", window.get());
3052 #endif
3054 // Request activation of this window or give focus to this widget.
3055 // aRaise means whether we should request activation of this widget's
3056 // toplevel window.
3058 // nsWindow::SetFocus(Raise::Yes) - Raise and give focus to toplevel window.
3059 // nsWindow::SetFocus(Raise::No) - Give focus to this window.
3060 void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
3061 LOG("nsWindow::SetFocus Raise %d\n", aRaise == Raise::Yes);
3063 // Raise the window if someone passed in true and the prefs are
3064 // set properly.
3065 GtkWidget* toplevelWidget = gtk_widget_get_toplevel(GTK_WIDGET(mContainer));
3067 LOG(" gFocusWindow [%p]\n", gFocusWindow);
3068 LOG(" mContainer [%p]\n", GTK_WIDGET(mContainer));
3069 LOG(" Toplevel widget [%p]\n", toplevelWidget);
3071 // Make sure that our owning widget has focus. If it doesn't try to
3072 // grab it. Note that we don't set our focus flag in this case.
3073 if (StaticPrefs::mozilla_widget_raise_on_setfocus_AtStartup() &&
3074 aRaise == Raise::Yes && toplevelWidget &&
3075 !gtk_widget_has_focus(toplevelWidget)) {
3076 if (gtk_widget_get_visible(mShell)) {
3077 LOG(" toplevel is not focused");
3078 gdk_window_show_unraised(GetToplevelGdkWindow());
3079 // Unset the urgency hint if possible.
3080 SetUrgencyHint(mShell, false);
3084 RefPtr<nsWindow> toplevelWindow = get_window_for_gtk_widget(toplevelWidget);
3085 if (!toplevelWindow) {
3086 LOG(" missing toplevel nsWindow, quit\n");
3087 return;
3090 if (aRaise == Raise::Yes) {
3091 // means request toplevel activation.
3093 // This is asynchronous. If and when the window manager accepts the request,
3094 // then the focus widget will get a focus-in-event signal.
3095 if (StaticPrefs::mozilla_widget_raise_on_setfocus_AtStartup() &&
3096 toplevelWindow->mIsShown && toplevelWindow->mShell &&
3097 !gtk_window_is_active(GTK_WINDOW(toplevelWindow->mShell))) {
3098 LOG(" toplevel is visible but not active, requesting activation [%p]",
3099 toplevelWindow.get());
3101 // Take the time here explicitly for the call below.
3102 const uint32_t timestamp = [&] {
3103 if (nsGTKToolkit* toolkit = nsGTKToolkit::GetToolkit()) {
3104 if (uint32_t t = toolkit->GetFocusTimestamp()) {
3105 toolkit->SetFocusTimestamp(0);
3106 return t;
3109 return GetLastUserInputTime();
3110 }();
3112 toplevelWindow->SetUserTimeAndStartupTokenForActivatedWindow();
3113 gtk_window_present_with_time(GTK_WINDOW(toplevelWindow->mShell),
3114 timestamp);
3116 #ifdef MOZ_WAYLAND
3117 if (GdkIsWaylandDisplay()) {
3118 auto existingToken =
3119 std::move(toplevelWindow->mWindowActivationTokenFromEnv);
3120 if (!existingToken.IsEmpty()) {
3121 LOG(" has existing activation token.");
3122 toplevelWindow->FocusWaylandWindow(existingToken.get());
3123 } else {
3124 LOG(" missing activation token, try to transfer from focused "
3125 "window");
3126 TransferFocusToWaylandWindow(toplevelWindow);
3129 #endif
3131 return;
3134 // aRaise == No means that keyboard events should be dispatched from this
3135 // widget.
3137 // Ensure GTK_WIDGET(mContainer) is the focused GtkWidget within its toplevel
3138 // window.
3140 // For WindowType::Popup, this GtkWidget may not actually be the one that
3141 // receives the key events as it may be the parent window that is active.
3142 if (!gtk_widget_is_focus(GTK_WIDGET(mContainer))) {
3143 // This is synchronous. It takes focus from a plugin or from a widget
3144 // in an embedder. The focus manager already knows that this window
3145 // is active so gBlockActivateEvent avoids another (unnecessary)
3146 // activate notification.
3147 gBlockActivateEvent = true;
3148 gtk_widget_grab_focus(GTK_WIDGET(mContainer));
3149 gBlockActivateEvent = false;
3152 // If this is the widget that already has focus, return.
3153 if (gFocusWindow == this) {
3154 LOG(" already have focus");
3155 return;
3158 // Set this window to be the focused child window
3159 gFocusWindow = this;
3161 if (mIMContext) {
3162 mIMContext->OnFocusWindow(this);
3165 LOG(" widget now has focus in SetFocus()");
3168 LayoutDeviceIntRect nsWindow::GetScreenBounds() {
3169 if (!mGdkWindow) {
3170 return mBounds;
3173 const LayoutDeviceIntPoint origin = [&] {
3174 gint x, y;
3175 gdk_window_get_root_origin(mGdkWindow, &x, &y);
3177 // Workaround for https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4820
3178 // Bug 1775017 Gtk < 3.24.35 returns scaled values for
3179 // override redirected window on X11.
3180 if (gtk_check_version(3, 24, 35) != nullptr && GdkIsX11Display() &&
3181 gdk_window_get_window_type(mGdkWindow) == GDK_WINDOW_TEMP) {
3182 return LayoutDeviceIntPoint(x, y);
3184 return GdkPointToDevicePixels({x, y});
3185 }();
3187 // mBounds.Size() is the window bounds, not the window-manager frame
3188 // bounds (bug 581863). gdk_window_get_frame_extents would give the
3189 // frame bounds, but mBounds.Size() is returned here for consistency
3190 // with Resize.
3191 const LayoutDeviceIntRect rect(origin, mBounds.Size());
3192 #if MOZ_LOGGING
3193 if (MOZ_LOG_TEST(IsPopup() ? gWidgetPopupLog : gWidgetLog,
3194 LogLevel::Verbose)) {
3195 gint scale = GdkCeiledScaleFactor();
3196 if (mLastLoggedScale != scale || !(mLastLoggedBoundSize == rect)) {
3197 mLastLoggedScale = scale;
3198 mLastLoggedBoundSize = rect;
3199 LOG("GetScreenBounds %d,%d -> %d x %d, unscaled %d,%d -> %d x %d\n",
3200 rect.x, rect.y, rect.width, rect.height, rect.x / scale,
3201 rect.y / scale, rect.width / scale, rect.height / scale);
3204 #endif
3205 return rect;
3208 LayoutDeviceIntSize nsWindow::GetClientSize() {
3209 return LayoutDeviceIntSize(mBounds.width, mBounds.height);
3212 LayoutDeviceIntRect nsWindow::GetClientBounds() {
3213 // GetBounds returns a rect whose top left represents the top left of the
3214 // outer bounds, but whose width/height represent the size of the inner
3215 // bounds (which is messed up).
3216 LayoutDeviceIntRect rect = GetBounds();
3217 rect.MoveBy(GetClientOffset());
3218 return rect;
3221 void nsWindow::RecomputeClientOffset(bool aNotify) {
3222 if (!IsTopLevelWindowType()) {
3223 return;
3226 auto oldOffset = mClientOffset;
3228 mClientOffset = WidgetToScreenOffset() - mBounds.TopLeft();
3230 if (aNotify && mClientOffset != oldOffset) {
3231 // Send a WindowMoved notification. This ensures that BrowserParent picks up
3232 // the new client offset and sends it to the child process if appropriate.
3233 NotifyWindowMoved(mBounds.x, mBounds.y);
3237 gboolean nsWindow::OnPropertyNotifyEvent(GtkWidget* aWidget,
3238 GdkEventProperty* aEvent) {
3239 if (aEvent->atom == gdk_atom_intern("_NET_FRAME_EXTENTS", FALSE)) {
3240 RecomputeClientOffset(/* aNotify = */ true);
3241 return FALSE;
3243 if (!mGdkWindow) {
3244 return FALSE;
3246 #ifdef MOZ_X11
3247 if (GetCurrentTimeGetter()->PropertyNotifyHandler(aWidget, aEvent)) {
3248 return TRUE;
3250 #endif
3251 return FALSE;
3254 static GdkCursor* GetCursorForImage(const nsIWidget::Cursor& aCursor,
3255 int32_t aWidgetScaleFactor) {
3256 if (!aCursor.IsCustom()) {
3257 return nullptr;
3259 nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
3261 // NOTE: GTK only allows integer scale factors, so we ceil to the larger scale
3262 // factor and then tell gtk to scale it down. We ensure to scale at least to
3263 // the GDK scale factor, so that cursors aren't downsized in HiDPI on wayland,
3264 // see bug 1707533.
3265 int32_t gtkScale = std::max(
3266 aWidgetScaleFactor, int32_t(std::ceil(std::max(aCursor.mResolution.mX,
3267 aCursor.mResolution.mY))));
3269 // Reject cursors greater than 128 pixels in some direction, to prevent
3270 // spoofing.
3271 // XXX ideally we should rescale. Also, we could modify the API to
3272 // allow trusted content to set larger cursors.
3274 // TODO(emilio, bug 1445844): Unify the solution for this with other
3275 // platforms.
3276 if (size.width > 128 || size.height > 128) {
3277 return nullptr;
3280 nsIntSize rasterSize = size * gtkScale;
3281 RefPtr<GdkPixbuf> pixbuf =
3282 nsImageToPixbuf::ImageToPixbuf(aCursor.mContainer, Some(rasterSize));
3283 if (!pixbuf) {
3284 return nullptr;
3287 // Looks like all cursors need an alpha channel (tested on Gtk 2.4.4). This
3288 // is of course not documented anywhere...
3289 // So add one if there isn't one yet
3290 if (!gdk_pixbuf_get_has_alpha(pixbuf)) {
3291 RefPtr<GdkPixbuf> alphaBuf =
3292 dont_AddRef(gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0));
3293 pixbuf = std::move(alphaBuf);
3294 if (!pixbuf) {
3295 return nullptr;
3299 cairo_surface_t* surface =
3300 gdk_cairo_surface_create_from_pixbuf(pixbuf, gtkScale, nullptr);
3301 if (!surface) {
3302 return nullptr;
3305 auto CleanupSurface =
3306 MakeScopeExit([&]() { cairo_surface_destroy(surface); });
3308 return gdk_cursor_new_from_surface(gdk_display_get_default(), surface,
3309 aCursor.mHotspotX, aCursor.mHotspotY);
3312 void nsWindow::SetCursor(const Cursor& aCursor) {
3313 if (mWidgetCursorLocked || !mGdkWindow) {
3314 return;
3317 // Only change cursor if it's actually been changed
3318 if (!mUpdateCursor && mCursor == aCursor) {
3319 return;
3322 mUpdateCursor = false;
3323 mCursor = aCursor;
3325 // Try to set the cursor image first, and fall back to the numeric cursor.
3326 GdkCursor* imageCursor = nullptr;
3327 if (mCustomCursorAllowed) {
3328 imageCursor = GetCursorForImage(aCursor, GdkCeiledScaleFactor());
3331 // When using a custom cursor, clear the cursor first using eCursor_none, in
3332 // order to work around https://gitlab.gnome.org/GNOME/gtk/-/issues/6242
3333 GdkCursor* nonImageCursor =
3334 get_gtk_cursor(imageCursor ? eCursor_none : aCursor.mDefaultCursor);
3335 auto CleanupCursor = mozilla::MakeScopeExit([&]() {
3336 // get_gtk_cursor returns a weak reference, which we shouldn't unref.
3337 if (imageCursor) {
3338 g_object_unref(imageCursor);
3342 gdk_window_set_cursor(mGdkWindow, nonImageCursor);
3343 if (imageCursor) {
3344 gdk_window_set_cursor(mGdkWindow, imageCursor);
3348 void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
3349 if (!mGdkWindow) {
3350 return;
3353 GdkRectangle rect = DevicePixelsToGdkRectRoundOut(aRect);
3354 gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
3356 LOG("Invalidate (rect): %d %d %d %d\n", rect.x, rect.y, rect.width,
3357 rect.height);
3360 void* nsWindow::GetNativeData(uint32_t aDataType) {
3361 switch (aDataType) {
3362 case NS_NATIVE_WINDOW:
3363 case NS_NATIVE_WIDGET: {
3364 return mGdkWindow;
3367 case NS_NATIVE_SHELLWIDGET:
3368 return GetToplevelWidget();
3370 case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID:
3371 if (!mGdkWindow) {
3372 return nullptr;
3374 #ifdef MOZ_X11
3375 if (GdkIsX11Display()) {
3376 return (void*)GDK_WINDOW_XID(gdk_window_get_toplevel(mGdkWindow));
3378 #endif
3379 NS_WARNING(
3380 "nsWindow::GetNativeData(): NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID is not "
3381 "handled on Wayland!");
3382 return nullptr;
3383 case NS_RAW_NATIVE_IME_CONTEXT: {
3384 void* pseudoIMEContext = GetPseudoIMEContext();
3385 if (pseudoIMEContext) {
3386 return pseudoIMEContext;
3388 // If IME context isn't available on this widget, we should set |this|
3389 // instead of nullptr.
3390 if (!mIMContext) {
3391 return this;
3393 return mIMContext.get();
3395 case NS_NATIVE_OPENGL_CONTEXT:
3396 return nullptr;
3397 case NS_NATIVE_EGL_WINDOW: {
3398 void* eglWindow = nullptr;
3400 // We can't block on mutex here as it leads to a deadlock:
3401 // 1) mutex is taken at nsWindow::Destroy()
3402 // 2) NS_NATIVE_EGL_WINDOW is called from compositor/rendering thread,
3403 // blocking on mutex.
3404 // 3) DestroyCompositor() is called by nsWindow::Destroy(). As a sync
3405 // call it waits to compositor/rendering threads,
3406 // but they're blocked at 2).
3407 // It's fine if we return null EGL window during DestroyCompositor(),
3408 // in such case compositor painting is skipped.
3409 if (mDestroyMutex.TryLock()) {
3410 if (mGdkWindow && !mIsDestroyed) {
3411 #ifdef MOZ_X11
3412 if (GdkIsX11Display()) {
3413 eglWindow = (void*)GDK_WINDOW_XID(mGdkWindow);
3415 #endif
3416 #ifdef MOZ_WAYLAND
3417 if (GdkIsWaylandDisplay()) {
3418 bool hiddenWindow =
3419 mCompositorWidgetDelegate &&
3420 mCompositorWidgetDelegate->AsGtkCompositorWidget() &&
3421 mCompositorWidgetDelegate->AsGtkCompositorWidget()->IsHidden();
3422 if (!hiddenWindow) {
3423 eglWindow = moz_container_wayland_get_egl_window(
3424 mContainer, FractionalScaleFactor());
3427 #endif
3429 mDestroyMutex.Unlock();
3432 LOG("Get NS_NATIVE_EGL_WINDOW mGdkWindow %p returned eglWindow %p",
3433 mGdkWindow, eglWindow);
3434 return eglWindow;
3436 default:
3437 NS_WARNING("nsWindow::GetNativeData called with bad value");
3438 return nullptr;
3442 nsresult nsWindow::SetTitle(const nsAString& aTitle) {
3443 if (!mShell) {
3444 return NS_OK;
3447 // convert the string into utf8 and set the title.
3448 #define UTF8_FOLLOWBYTE(ch) (((ch) & 0xC0) == 0x80)
3449 NS_ConvertUTF16toUTF8 titleUTF8(aTitle);
3450 if (titleUTF8.Length() > NS_WINDOW_TITLE_MAX_LENGTH) {
3451 // Truncate overlong titles (bug 167315). Make sure we chop after a
3452 // complete sequence by making sure the next char isn't a follow-byte.
3453 uint32_t len = NS_WINDOW_TITLE_MAX_LENGTH;
3454 while (UTF8_FOLLOWBYTE(titleUTF8[len])) --len;
3455 titleUTF8.Truncate(len);
3457 gtk_window_set_title(GTK_WINDOW(mShell), (const char*)titleUTF8.get());
3459 return NS_OK;
3462 void nsWindow::SetIcon(const nsAString& aIconSpec) {
3463 if (!mShell) {
3464 return;
3467 nsAutoCString iconName;
3469 if (aIconSpec.EqualsLiteral("default")) {
3470 nsAutoString brandName;
3471 WidgetUtils::GetBrandShortName(brandName);
3472 if (brandName.IsEmpty()) {
3473 brandName.AssignLiteral(u"Mozilla");
3475 AppendUTF16toUTF8(brandName, iconName);
3476 ToLowerCase(iconName);
3477 } else {
3478 AppendUTF16toUTF8(aIconSpec, iconName);
3481 nsCOMPtr<nsIFile> iconFile;
3482 nsAutoCString path;
3484 gint* iconSizes = gtk_icon_theme_get_icon_sizes(gtk_icon_theme_get_default(),
3485 iconName.get());
3486 bool foundIcon = (iconSizes[0] != 0);
3487 g_free(iconSizes);
3489 if (!foundIcon) {
3490 // Look for icons with the following suffixes appended to the base name
3491 // The last two entries (for the old XPM format) will be ignored unless
3492 // no icons are found using other suffixes. XPM icons are deprecated.
3494 const char16_t extensions[9][8] = {u".png", u"16.png", u"32.png",
3495 u"48.png", u"64.png", u"128.png",
3496 u"256.png", u".xpm", u"16.xpm"};
3498 for (uint32_t i = 0; i < ArrayLength(extensions); i++) {
3499 // Don't bother looking for XPM versions if we found a PNG.
3500 if (i == ArrayLength(extensions) - 2 && foundIcon) break;
3502 ResolveIconName(aIconSpec, nsDependentString(extensions[i]),
3503 getter_AddRefs(iconFile));
3504 if (iconFile) {
3505 iconFile->GetNativePath(path);
3506 GdkPixbuf* icon = gdk_pixbuf_new_from_file(path.get(), nullptr);
3507 if (icon) {
3508 gtk_icon_theme_add_builtin_icon(iconName.get(),
3509 gdk_pixbuf_get_height(icon), icon);
3510 g_object_unref(icon);
3511 foundIcon = true;
3517 // leave the default icon intact if no matching icons were found
3518 if (foundIcon) {
3519 gtk_window_set_icon_name(GTK_WINDOW(mShell), iconName.get());
3523 /* TODO(bug 1655924): gdk_window_get_origin is can block waiting for the X
3524 server for a long time, we would like to use the implementation below
3525 instead. However, removing the synchronous x server queries causes a race
3526 condition to surface, causing issues such as bug 1652743 and bug 1653711.
3529 This code can be used instead of gdk_window_get_origin() but it cuases
3530 such issues:
3532 *aX = 0;
3533 *aY = 0;
3534 if (!aWindow) {
3535 return;
3538 GdkWindow* current = aWindow;
3539 while (GdkWindow* parent = gdk_window_get_parent(current)) {
3540 if (parent == current) {
3541 break;
3544 int x = 0;
3545 int y = 0;
3546 gdk_window_get_position(current, &x, &y);
3547 *aX += x;
3548 *aY += y;
3550 current = parent;
3553 LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
3554 // Don't use gdk_window_get_origin() on wl_subsurface Wayland popups
3555 // https://gitlab.gnome.org/GNOME/gtk/-/issues/5287
3556 if (IsWaylandPopup() && !mPopupUseMoveToRect) {
3557 return mBounds.TopLeft();
3559 nsIntPoint origin(0, 0);
3560 if (mGdkWindow) {
3561 gdk_window_get_origin(mGdkWindow, &origin.x.value, &origin.y.value);
3563 return GdkPointToDevicePixels({origin.x, origin.y});
3566 void nsWindow::CaptureRollupEvents(bool aDoCapture) {
3567 LOG("CaptureRollupEvents(%d)\n", aDoCapture);
3568 if (mIsDestroyed) {
3569 return;
3572 static constexpr auto kCaptureEventsMask =
3573 GdkEventMask(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
3574 GDK_POINTER_MOTION_MASK | GDK_TOUCH_MASK);
3576 static bool sSystemNeedsPointerGrab = [&] {
3577 if (GdkIsWaylandDisplay()) {
3578 return false;
3580 // We only need to grab the pointer for X servers that move the focus with
3581 // the pointer (like twm, sawfish...). Since we roll up popups on focus out,
3582 // not grabbing the pointer triggers rollup when the mouse enters the popup
3583 // and leaves the main window, see bug 1807482.
3585 // FVWM is also affected but less severely: the pointer can enter the
3586 // popup, but if it briefly moves out of the popup and over the main window
3587 // then we see a focus change and roll up the popup.
3589 // We don't do it for most common desktops, if only because it causes X11
3590 // crashes like bug 1607713.
3591 const auto& desktop = GetDesktopEnvironmentIdentifier();
3592 return desktop.EqualsLiteral("twm") || desktop.EqualsLiteral("sawfish") ||
3593 StringBeginsWith(desktop, "fvwm"_ns);
3594 }();
3596 const bool grabPointer = [] {
3597 switch (StaticPrefs::widget_gtk_grab_pointer()) {
3598 case 0:
3599 return false;
3600 case 1:
3601 return true;
3602 default:
3603 return sSystemNeedsPointerGrab;
3605 }();
3607 if (!grabPointer) {
3608 return;
3611 mNeedsToRetryCapturingMouse = false;
3612 if (aDoCapture) {
3613 if (mIsDragPopup || DragInProgress()) {
3614 // Don't add a grab if a drag is in progress, or if the widget is a drag
3615 // feedback popup. (panels with type="drag").
3616 return;
3619 if (!mHasMappedToplevel) {
3620 // On X, capturing an unmapped window is pointless (returns
3621 // GDK_GRAB_NOT_VIEWABLE). Avoid the X server round-trip and just retry
3622 // when we're mapped.
3623 mNeedsToRetryCapturingMouse = true;
3624 return;
3627 // gdk_pointer_grab is deprecated in favor of gdk_device_grab, but that
3628 // causes a strange bug on X11, most obviously with nested popup menus:
3629 // we somehow take the pointer position relative to the top left of the
3630 // outer menu and use it as if it were relative to the submenu. This
3631 // doesn't happen with gdk_pointer_grab even though the code is very
3632 // similar. See the video attached to bug 1750721 for a demonstration,
3633 // and see also bug 1820542 for when the same thing happened with
3634 // another attempt to use gdk_device_grab.
3636 // (gdk_device_grab is deprecated in favor of gdk_seat_grab as of 3.20,
3637 // but at the time of this writing we still support older versions of
3638 // GTK 3.)
3639 GdkGrabStatus status =
3640 gdk_pointer_grab(GetToplevelGdkWindow(),
3641 /* owner_events = */ true, kCaptureEventsMask,
3642 /* confine_to = */ nullptr,
3643 /* cursor = */ nullptr, GetLastUserInputTime());
3644 Unused << NS_WARN_IF(status != GDK_GRAB_SUCCESS);
3645 LOG(" > pointer grab with status %d", int(status));
3646 gtk_grab_add(GTK_WIDGET(mContainer));
3647 } else {
3648 // There may not have been a drag in process when aDoCapture was set,
3649 // so make sure to remove any added grab. This is a no-op if the grab
3650 // was not added to this widget.
3651 gtk_grab_remove(GTK_WIDGET(mContainer));
3652 gdk_pointer_ungrab(GetLastUserInputTime());
3656 nsresult nsWindow::GetAttention(int32_t aCycleCount) {
3657 LOG("nsWindow::GetAttention");
3659 GtkWidget* top_window = GetToplevelWidget();
3660 GtkWidget* top_focused_window =
3661 gFocusWindow ? gFocusWindow->GetToplevelWidget() : nullptr;
3663 // Don't get attention if the window is focused anyway.
3664 if (top_window && (gtk_widget_get_visible(top_window)) &&
3665 top_window != top_focused_window) {
3666 SetUrgencyHint(top_window, true);
3669 return NS_OK;
3672 bool nsWindow::HasPendingInputEvent() {
3673 // This sucks, but gtk/gdk has no way to answer the question we want while
3674 // excluding paint events, and there's no X API that will let us peek
3675 // without blocking or removing. To prevent event reordering, peek
3676 // anything except expose events. Reordering expose and others should be
3677 // ok, hopefully.
3678 bool haveEvent = false;
3679 #ifdef MOZ_X11
3680 XEvent ev;
3681 if (GdkIsX11Display()) {
3682 Display* display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
3683 haveEvent = XCheckMaskEvent(
3684 display,
3685 KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
3686 EnterWindowMask | LeaveWindowMask | PointerMotionMask |
3687 PointerMotionHintMask | Button1MotionMask | Button2MotionMask |
3688 Button3MotionMask | Button4MotionMask | Button5MotionMask |
3689 ButtonMotionMask | KeymapStateMask | VisibilityChangeMask |
3690 StructureNotifyMask | ResizeRedirectMask | SubstructureNotifyMask |
3691 SubstructureRedirectMask | FocusChangeMask | PropertyChangeMask |
3692 ColormapChangeMask | OwnerGrabButtonMask,
3693 &ev);
3694 if (haveEvent) {
3695 XPutBackEvent(display, &ev);
3698 #endif
3699 return haveEvent;
3702 #ifdef cairo_copy_clip_rectangle_list
3703 # error "Looks like we're including Mozilla's cairo instead of system cairo"
3704 #endif
3705 static bool ExtractExposeRegion(LayoutDeviceIntRegion& aRegion, cairo_t* cr) {
3706 cairo_rectangle_list_t* rects = cairo_copy_clip_rectangle_list(cr);
3707 if (rects->status != CAIRO_STATUS_SUCCESS) {
3708 NS_WARNING("Failed to obtain cairo rectangle list.");
3709 return false;
3712 for (int i = 0; i < rects->num_rectangles; i++) {
3713 const cairo_rectangle_t& r = rects->rectangles[i];
3714 aRegion.Or(aRegion,
3715 LayoutDeviceIntRect::Truncate((float)r.x, (float)r.y,
3716 (float)r.width, (float)r.height));
3719 cairo_rectangle_list_destroy(rects);
3720 return true;
3723 #ifdef MOZ_WAYLAND
3724 void nsWindow::CreateCompositorVsyncDispatcher() {
3725 LOG_VSYNC("nsWindow::CreateCompositorVsyncDispatcher()");
3726 if (!mWaylandVsyncSource) {
3727 LOG_VSYNC(
3728 " mWaylandVsyncSource is missing, create "
3729 "nsBaseWidget::CompositorVsyncDispatcher()");
3730 nsBaseWidget::CreateCompositorVsyncDispatcher();
3731 return;
3733 if (!mCompositorVsyncDispatcherLock) {
3734 mCompositorVsyncDispatcherLock =
3735 MakeUnique<Mutex>("mCompositorVsyncDispatcherLock");
3737 MutexAutoLock lock(*mCompositorVsyncDispatcherLock);
3738 if (!mCompositorVsyncDispatcher) {
3739 LOG_VSYNC(" create CompositorVsyncDispatcher()");
3740 mCompositorVsyncDispatcher =
3741 new CompositorVsyncDispatcher(mWaylandVsyncDispatcher);
3744 #endif
3746 void nsWindow::RequestRepaint(LayoutDeviceIntRegion& aRepaintRegion) {
3747 WindowRenderer* renderer = GetWindowRenderer();
3748 WebRenderLayerManager* layerManager = renderer->AsWebRender();
3749 KnowsCompositor* knowsCompositor = renderer->AsKnowsCompositor();
3751 if (knowsCompositor && layerManager && mCompositorSession) {
3752 if (!mConfiguredClearColor && !IsPopup()) {
3753 layerManager->WrBridge()->SendSetDefaultClearColor(LookAndFeel::Color(
3754 LookAndFeel::ColorID::Window, PreferenceSheet::ColorSchemeForChrome(),
3755 LookAndFeel::UseStandins::No));
3756 mConfiguredClearColor = true;
3759 // We need to paint to the screen even if nothing changed, since if we
3760 // don't have a compositing window manager, our pixels could be stale.
3761 layerManager->SetNeedsComposite(true);
3762 layerManager->SendInvalidRegion(aRepaintRegion.ToUnknownRegion());
3766 gboolean nsWindow::OnExposeEvent(cairo_t* cr) {
3767 // This might destroy us.
3768 NotifyOcclusionState(OcclusionState::VISIBLE);
3769 if (mIsDestroyed) {
3770 return FALSE;
3773 // Send any pending resize events so that layout can update.
3774 // May run event loop and destroy us.
3775 MaybeDispatchResized();
3776 if (mIsDestroyed) {
3777 return FALSE;
3780 // Windows that are not visible will be painted after they become visible.
3781 if (!mGdkWindow || !mHasMappedToplevel) {
3782 return FALSE;
3784 #ifdef MOZ_WAYLAND
3785 if (GdkIsWaylandDisplay() && !moz_container_wayland_can_draw(mContainer)) {
3786 return FALSE;
3788 #endif
3790 if (!GetListener()) {
3791 return FALSE;
3794 LOG("nsWindow::OnExposeEvent GdkWindow [%p] XID [0x%lx]", mGdkWindow,
3795 GetX11Window());
3797 LayoutDeviceIntRegion exposeRegion;
3798 if (!ExtractExposeRegion(exposeRegion, cr)) {
3799 LOG(" no rects, quit");
3800 return FALSE;
3803 gint scale = GdkCeiledScaleFactor();
3804 LayoutDeviceIntRegion region = exposeRegion;
3805 region.ScaleRoundOut(scale, scale);
3807 RequestRepaint(region);
3809 RefPtr<nsWindow> strongThis(this);
3811 // Dispatch WillPaintWindow notification to allow scripts etc. to run
3812 // before we paint. It also spins event loop which may show/hide the window
3813 // so we may have new renderer etc.
3814 GetListener()->WillPaintWindow(this);
3816 // If the window has been destroyed during the will paint notification,
3817 // there is nothing left to do.
3818 if (!mGdkWindow || mIsDestroyed) {
3819 return TRUE;
3822 // Re-get all rendering components since the will paint notification
3823 // might have killed it.
3824 nsIWidgetListener* listener = GetListener();
3825 if (!listener) return FALSE;
3827 WindowRenderer* renderer = GetWindowRenderer();
3828 WebRenderLayerManager* layerManager = renderer->AsWebRender();
3829 KnowsCompositor* knowsCompositor = renderer->AsKnowsCompositor();
3831 if (knowsCompositor && layerManager && layerManager->NeedsComposite()) {
3832 layerManager->ScheduleComposite(wr::RenderReasons::WIDGET);
3833 layerManager->SetNeedsComposite(false);
3836 // Our bounds may have changed after calling WillPaintWindow. Clip
3837 // to the new bounds here. The region is relative to this
3838 // window.
3839 region.And(region, LayoutDeviceIntRect(0, 0, mBounds.width, mBounds.height));
3841 bool shaped = false;
3842 if (TransparencyMode::Transparent == GetTransparencyMode()) {
3843 auto* window = static_cast<nsWindow*>(GetTopLevelWidget());
3844 if (mTransparencyBitmapForTitlebar) {
3845 if (mSizeMode == nsSizeMode_Normal) {
3846 window->UpdateTitlebarTransparencyBitmap();
3847 } else {
3848 window->ClearTransparencyBitmap();
3850 } else {
3851 if (mHasAlphaVisual) {
3852 // Remove possible shape mask from when window manger was not
3853 // previously compositing.
3854 window->ClearTransparencyBitmap();
3855 } else {
3856 shaped = true;
3861 if (region.IsEmpty()) {
3862 return TRUE;
3865 // If this widget uses OMTC...
3866 if (renderer->GetBackendType() == LayersBackend::LAYERS_WR) {
3867 listener->PaintWindow(this, region);
3869 // Re-get the listener since the will paint notification might have
3870 // killed it.
3871 listener = GetListener();
3872 if (!listener) {
3873 return TRUE;
3876 listener->DidPaintWindow();
3877 return TRUE;
3880 BufferMode layerBuffering = BufferMode::BUFFERED;
3881 RefPtr<DrawTarget> dt = StartRemoteDrawingInRegion(region, &layerBuffering);
3882 if (!dt || !dt->IsValid()) {
3883 return FALSE;
3885 Maybe<gfxContext> ctx;
3886 IntRect boundsRect = region.GetBounds().ToUnknownRect();
3887 IntPoint offset(0, 0);
3888 if (dt->GetSize() == boundsRect.Size()) {
3889 offset = boundsRect.TopLeft();
3890 dt->SetTransform(Matrix::Translation(-offset));
3893 #ifdef MOZ_X11
3894 if (shaped) {
3895 // Collapse update area to the bounding box. This is so we only have to
3896 // call UpdateTranslucentWindowAlpha once. After we have dropped
3897 // support for non-Thebes graphics, UpdateTranslucentWindowAlpha will be
3898 // our private interface so we can rework things to avoid this.
3899 dt->PushClipRect(Rect(boundsRect));
3901 // The double buffering is done here to extract the shape mask.
3902 // (The shape mask won't be necessary when a visual with an alpha
3903 // channel is used on compositing window managers.)
3904 layerBuffering = BufferMode::BUFFER_NONE;
3905 RefPtr<DrawTarget> destDT =
3906 dt->CreateSimilarDrawTarget(boundsRect.Size(), SurfaceFormat::B8G8R8A8);
3907 if (!destDT || !destDT->IsValid()) {
3908 return FALSE;
3910 destDT->SetTransform(Matrix::Translation(-boundsRect.TopLeft()));
3911 ctx.emplace(destDT, /* aPreserveTransform */ true);
3912 } else {
3913 gfxUtils::ClipToRegion(dt, region.ToUnknownRegion());
3914 ctx.emplace(dt, /* aPreserveTransform */ true);
3917 # if 0
3918 // NOTE: Paint flashing region would be wrong for cairo, since
3919 // cairo inflates the update region, etc. So don't paint flash
3920 // for cairo.
3921 # ifdef DEBUG
3922 // XXX aEvent->region may refer to a newly-invalid area. FIXME
3923 if (0 && WANT_PAINT_FLASHING && gtk_widget_get_window(aEvent))
3924 gdk_window_flash(mGdkWindow, 1, 100, aEvent->region);
3925 # endif
3926 # endif
3928 #endif // MOZ_X11
3930 bool painted = false;
3932 if (renderer->GetBackendType() == LayersBackend::LAYERS_NONE) {
3933 if (GetTransparencyMode() == TransparencyMode::Transparent &&
3934 layerBuffering == BufferMode::BUFFER_NONE && mHasAlphaVisual) {
3935 // If our draw target is unbuffered and we use an alpha channel,
3936 // clear the image beforehand to ensure we don't get artifacts from a
3937 // reused SHM image. See bug 1258086.
3938 dt->ClearRect(Rect(boundsRect));
3940 AutoLayerManagerSetup setupLayerManager(
3941 this, ctx.isNothing() ? nullptr : &ctx.ref(), layerBuffering);
3942 painted = listener->PaintWindow(this, region);
3944 // Re-get the listener since the will paint notification might have
3945 // killed it.
3946 listener = GetListener();
3947 if (!listener) {
3948 return TRUE;
3953 #ifdef MOZ_X11
3954 // PaintWindow can Destroy us (bug 378273), avoid doing any paint
3955 // operations below if that happened - it will lead to XError and exit().
3956 if (shaped) {
3957 if (MOZ_LIKELY(!mIsDestroyed)) {
3958 if (painted) {
3959 RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
3961 UpdateAlpha(surf, boundsRect);
3963 dt->DrawSurface(surf, Rect(boundsRect),
3964 Rect(0, 0, boundsRect.width, boundsRect.height),
3965 DrawSurfaceOptions(SamplingFilter::POINT),
3966 DrawOptions(1.0f, CompositionOp::OP_SOURCE));
3971 ctx.reset();
3972 dt->PopClip();
3974 #endif // MOZ_X11
3976 EndRemoteDrawingInRegion(dt, region);
3978 listener->DidPaintWindow();
3980 // Synchronously flush any new dirty areas
3981 cairo_region_t* dirtyArea = gdk_window_get_update_area(mGdkWindow);
3983 if (dirtyArea) {
3984 gdk_window_invalidate_region(mGdkWindow, dirtyArea, false);
3985 cairo_region_destroy(dirtyArea);
3986 gdk_window_process_updates(mGdkWindow, false);
3989 // check the return value!
3990 return TRUE;
3993 void nsWindow::UpdateAlpha(SourceSurface* aSourceSurface,
3994 nsIntRect aBoundsRect) {
3995 // We need to create our own buffer to force the stride to match the
3996 // expected stride.
3997 int32_t stride =
3998 GetAlignedStride<4>(aBoundsRect.width, BytesPerPixel(SurfaceFormat::A8));
3999 if (stride == 0) {
4000 return;
4002 int32_t bufferSize = stride * aBoundsRect.height;
4003 auto imageBuffer = MakeUniqueFallible<uint8_t[]>(bufferSize);
4005 RefPtr<DrawTarget> drawTarget = gfxPlatform::CreateDrawTargetForData(
4006 imageBuffer.get(), aBoundsRect.Size(), stride, SurfaceFormat::A8);
4008 if (drawTarget) {
4009 drawTarget->DrawSurface(aSourceSurface,
4010 Rect(0, 0, aBoundsRect.width, aBoundsRect.height),
4011 Rect(0, 0, aSourceSurface->GetSize().width,
4012 aSourceSurface->GetSize().height),
4013 DrawSurfaceOptions(SamplingFilter::POINT),
4014 DrawOptions(1.0f, CompositionOp::OP_SOURCE));
4017 UpdateTranslucentWindowAlphaInternal(aBoundsRect, imageBuffer.get(), stride);
4020 gboolean nsWindow::OnConfigureEvent(GtkWidget* aWidget,
4021 GdkEventConfigure* aEvent) {
4022 // These events are only received on toplevel windows.
4024 // GDK ensures that the coordinates are the client window top-left wrt the
4025 // root window.
4027 // GDK calculates the cordinates for real ConfigureNotify events on
4028 // managed windows (that would normally be relative to the parent
4029 // window).
4031 // Synthetic ConfigureNotify events are from the window manager and
4032 // already relative to the root window. GDK creates all X windows with
4033 // border_width = 0, so synthetic events also indicate the top-left of
4034 // the client window.
4036 // Override-redirect windows are children of the root window so parent
4037 // coordinates are root coordinates.
4039 #ifdef MOZ_LOGGING
4040 int scale = mGdkWindow ? gdk_window_get_scale_factor(mGdkWindow) : -1;
4041 LOG("configure event %d,%d -> %d x %d direct mGdkWindow scale %d (scaled "
4042 "size %d x %d)\n",
4043 aEvent->x, aEvent->y, aEvent->width, aEvent->height, scale,
4044 aEvent->width * scale, aEvent->height * scale);
4045 #endif
4047 if (mPendingConfigures > 0) {
4048 mPendingConfigures--;
4051 // Don't fire configure event for scale changes, we handle that
4052 // OnScaleChanged event. Skip that for toplevel windows only.
4053 if (mGdkWindow && IsTopLevelWindowType()) {
4054 if (mCeiledScaleFactor != gdk_window_get_scale_factor(mGdkWindow)) {
4055 LOG(" scale factor changed to %d,return early",
4056 gdk_window_get_scale_factor(mGdkWindow));
4057 return FALSE;
4061 LayoutDeviceIntRect screenBounds = GetScreenBounds();
4063 if (IsTopLevelWindowType()) {
4064 // This check avoids unwanted rollup on spurious configure events from
4065 // Cygwin/X (bug 672103).
4066 if (mBounds.x != screenBounds.x || mBounds.y != screenBounds.y) {
4067 RollupAllMenus();
4071 NS_ASSERTION(GTK_IS_WINDOW(aWidget),
4072 "Configure event on widget that is not a GtkWindow");
4073 if (mGdkWindow &&
4074 gtk_window_get_window_type(GTK_WINDOW(aWidget)) == GTK_WINDOW_POPUP) {
4075 // Override-redirect window
4077 // These windows should not be moved by the window manager, and so any
4078 // change in position is a result of our direction. mBounds has
4079 // already been set in std::move() or Resize(), and that is more
4080 // up-to-date than the position in the ConfigureNotify event if the
4081 // event is from an earlier window move.
4083 // Skipping the WindowMoved call saves context menus from an infinite
4084 // loop when nsXULPopupManager::PopupMoved moves the window to the new
4085 // position and nsMenuPopupFrame::SetPopupPosition adds
4086 // offsetForContextMenu on each iteration.
4088 // Our back buffer might have been invalidated while we drew the last
4089 // frame, and its contents might be incorrect. See bug 1280653 comment 7
4090 // and comment 10. Specifically we must ensure we recomposite the frame
4091 // as soon as possible to avoid the corrupted frame being displayed.
4092 GetWindowRenderer()->FlushRendering(wr::RenderReasons::WIDGET);
4093 return FALSE;
4096 mBounds.MoveTo(screenBounds.TopLeft());
4097 RecomputeClientOffset(/* aNotify = */ false);
4099 // XXX mozilla will invalidate the entire window after this move
4100 // complete. wtf?
4101 NotifyWindowMoved(mBounds.x, mBounds.y);
4103 return FALSE;
4106 void nsWindow::OnMap() {
4107 LOG("nsWindow::OnMap");
4108 // Gtk mapped widget to screen. Configure underlying GdkWindow properly
4109 // as our rendering target.
4110 // This call means we have X11 (or Wayland) window we can render to by GL
4111 // so we need to notify compositor about it.
4112 mIsMapped = true;
4113 ConfigureGdkWindow();
4116 void nsWindow::OnUnmap() {
4117 LOG("nsWindow::OnUnmap");
4119 mIsMapped = false;
4121 if (mSourceDragContext) {
4122 static auto sGtkDragCancel =
4123 (void (*)(GdkDragContext*))dlsym(RTLD_DEFAULT, "gtk_drag_cancel");
4124 if (sGtkDragCancel) {
4125 sGtkDragCancel(mSourceDragContext);
4126 mSourceDragContext = nullptr;
4130 // We don't have valid XWindow any more,
4131 // so clear stored ones at GtkCompositorWidget() for OMTC rendering
4132 // and mSurfaceProvider for legacy rendering.
4133 if (GdkIsX11Display()) {
4134 mSurfaceProvider.CleanupResources();
4135 if (mCompositorWidgetDelegate) {
4136 mCompositorWidgetDelegate->DisableRendering();
4141 void nsWindow::OnSizeAllocate(GtkAllocation* aAllocation) {
4142 LOG("nsWindow::OnSizeAllocate %d,%d -> %d x %d\n", aAllocation->x,
4143 aAllocation->y, aAllocation->width, aAllocation->height);
4145 // Client offset are updated by _NET_FRAME_EXTENTS on X11 when system titlebar
4146 // is enabled. In either cases (Wayland or system titlebar is off on X11)
4147 // we don't get _NET_FRAME_EXTENTS X11 property notification so we derive
4148 // it from mContainer position.
4149 RecomputeClientOffset(/* aNotify = */ true);
4151 mHasReceivedSizeAllocate = true;
4153 LayoutDeviceIntSize size = GdkRectToDevicePixels(*aAllocation).Size();
4155 // Sometimes the window manager gives us garbage sizes (way past the maximum
4156 // texture size) causing crashes if we don't enforce size constraints again
4157 // here.
4158 ConstrainSize(&size.width, &size.height);
4160 if (mBounds.Size() == size) {
4161 LOG(" Already the same size");
4162 // mBounds was set at Create() or Resize().
4163 if (mNeedsDispatchSize != LayoutDeviceIntSize(-1, -1)) {
4164 LOG(" No longer needs to dispatch %dx%d", mNeedsDispatchSize.width,
4165 mNeedsDispatchSize.height);
4166 mNeedsDispatchSize = LayoutDeviceIntSize(-1, -1);
4168 return;
4171 // Invalidate the new part of the window now for the pending paint to
4172 // minimize background flashes (GDK does not do this for external resizes
4173 // of toplevels.)
4174 if (mGdkWindow) {
4175 if (mBounds.width < size.width) {
4176 GdkRectangle rect = DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect(
4177 mBounds.width, 0, size.width - mBounds.width, size.height));
4178 gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
4180 if (mBounds.height < size.height) {
4181 GdkRectangle rect = DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect(
4182 0, mBounds.height, size.width, size.height - mBounds.height));
4183 gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
4187 // If we update mBounds here, then inner/outerHeight are out of sync until
4188 // we call WindowResized.
4189 mNeedsDispatchSize = size;
4191 // Gecko permits running nested event loops during processing of events,
4192 // GtkWindow callers of gtk_widget_size_allocate expect the signal
4193 // handlers to return sometime in the near future.
4194 NS_DispatchToCurrentThread(NewRunnableMethod(
4195 "nsWindow::MaybeDispatchResized", this, &nsWindow::MaybeDispatchResized));
4198 void nsWindow::OnDeleteEvent() {
4199 if (mWidgetListener) mWidgetListener->RequestWindowClose(this);
4202 void nsWindow::OnEnterNotifyEvent(GdkEventCrossing* aEvent) {
4203 LOG("enter notify (win=%p, sub=%p): %f, %f mode %d, detail %d\n",
4204 aEvent->window, aEvent->subwindow, aEvent->x, aEvent->y, aEvent->mode,
4205 aEvent->detail);
4206 // This skips NotifyVirtual and NotifyNonlinearVirtual enter notify events
4207 // when the pointer enters a child window. If the destination window is a
4208 // Gecko window then we'll catch the corresponding event on that window,
4209 // but we won't notice when the pointer directly enters a foreign (plugin)
4210 // child window without passing over a visible portion of a Gecko window.
4211 if (aEvent->subwindow) {
4212 return;
4215 // Check before checking for ungrab as the button state may have
4216 // changed while a non-Gecko ancestor window had a pointer grab.
4217 DispatchMissedButtonReleases(aEvent);
4219 WidgetMouseEvent event(true, eMouseEnterIntoWidget, this,
4220 WidgetMouseEvent::eReal);
4222 event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
4223 event.AssignEventTime(GetWidgetEventTime(aEvent->time));
4225 LOG("OnEnterNotify");
4227 DispatchInputEvent(&event);
4230 // Some window managers send a bogus top-level leave-notify event on every
4231 // click. That confuses our event handling code in ways that can break websites,
4232 // see bug 1805939 for details.
4234 // Make sure to only check this on bogus environments, since for environments
4235 // with CSD, gdk_device_get_window_at_position could return the window even when
4236 // the pointer is in the decoration area.
4237 static bool IsBogusLeaveNotifyEvent(GdkWindow* aWindow,
4238 GdkEventCrossing* aEvent) {
4239 static bool sBogusWm = [] {
4240 if (GdkIsWaylandDisplay()) {
4241 return false;
4243 const auto& desktopEnv = GetDesktopEnvironmentIdentifier();
4244 return desktopEnv.EqualsLiteral("fluxbox") || // Bug 1805939 comment 0.
4245 desktopEnv.EqualsLiteral("blackbox") || // Bug 1805939 comment 32.
4246 desktopEnv.EqualsLiteral("lg3d") || // Bug 1820405.
4247 desktopEnv.EqualsLiteral("pekwm") || // Bug 1822911.
4248 StringBeginsWith(desktopEnv, "fvwm"_ns);
4249 }();
4251 const bool shouldCheck = [] {
4252 switch (StaticPrefs::widget_gtk_ignore_bogus_leave_notify()) {
4253 case 0:
4254 return false;
4255 case 1:
4256 return true;
4257 default:
4258 return sBogusWm;
4260 }();
4262 if (!shouldCheck || !aWindow) {
4263 return false;
4265 GdkDevice* pointer = GdkGetPointer();
4266 GdkWindow* winAtPt =
4267 gdk_device_get_window_at_position(pointer, nullptr, nullptr);
4268 if (!winAtPt) {
4269 return false;
4271 // We're still in the same top level window, ignore this leave notify event.
4272 GdkWindow* topLevelAtPt = gdk_window_get_toplevel(winAtPt);
4273 GdkWindow* topLevelWidget = gdk_window_get_toplevel(aWindow);
4274 return topLevelAtPt == topLevelWidget;
4277 void nsWindow::OnLeaveNotifyEvent(GdkEventCrossing* aEvent) {
4278 LOG("leave notify (win=%p, sub=%p): %f, %f mode %d, detail %d\n",
4279 aEvent->window, aEvent->subwindow, aEvent->x, aEvent->y, aEvent->mode,
4280 aEvent->detail);
4282 // This ignores NotifyVirtual and NotifyNonlinearVirtual leave notify
4283 // events when the pointer leaves a child window. If the destination
4284 // window is a Gecko window then we'll catch the corresponding event on
4285 // that window.
4287 // XXXkt However, we will miss toplevel exits when the pointer directly
4288 // leaves a foreign (plugin) child window without passing over a visible
4289 // portion of a Gecko window.
4290 if (aEvent->subwindow) {
4291 return;
4294 // The filter out for subwindows should make sure that this is targeted to
4295 // this nsWindow.
4296 const bool leavingTopLevel = IsTopLevelWindowType();
4297 if (leavingTopLevel && IsBogusLeaveNotifyEvent(mGdkWindow, aEvent)) {
4298 return;
4301 WidgetMouseEvent event(true, eMouseExitFromWidget, this,
4302 WidgetMouseEvent::eReal);
4304 event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
4305 event.AssignEventTime(GetWidgetEventTime(aEvent->time));
4306 event.mExitFrom = Some(leavingTopLevel ? WidgetMouseEvent::ePlatformTopLevel
4307 : WidgetMouseEvent::ePlatformChild);
4309 LOG("OnLeaveNotify");
4311 DispatchInputEvent(&event);
4314 Maybe<GdkWindowEdge> nsWindow::CheckResizerEdge(
4315 const LayoutDeviceIntPoint& aPoint) {
4316 const bool canResize = [&] {
4317 // Don't allow resizing maximized/fullscreen windows.
4318 if (mSizeMode != nsSizeMode_Normal) {
4319 return false;
4321 if (mIsPIPWindow) {
4322 // Note that since we do show resizers on left/right sides on PIP windows,
4323 // we still want the resizers there, even when tiled.
4324 return true;
4326 if (!mDrawInTitlebar) {
4327 return false;
4329 // On KDE, allow for 1 extra pixel at the top of regular windows when
4330 // drawing to the titlebar. This matches the native titlebar behavior on
4331 // that environment. See bug 1813554.
4333 // Don't do that on GNOME (see bug 1822764). If we wanted to do this on
4334 // GNOME we'd need an extra check for mIsTiled, since the window is "stuck"
4335 // to the top and bottom.
4337 // Other DEs are untested.
4338 return mDrawInTitlebar && IsKdeDesktopEnvironment();
4339 }();
4341 if (!canResize) {
4342 return Nothing();
4345 // If we're not in a PiP window, allow 1px resizer edge from the top edge,
4346 // and nothing else.
4347 // This is to allow resizes of tiled windows on KDE, see bug 1813554.
4348 const int resizerHeight = (mIsPIPWindow ? 15 : 1) * GdkCeiledScaleFactor();
4349 const int resizerWidth = resizerHeight * 4;
4351 const int topDist = aPoint.y;
4352 const int leftDist = aPoint.x;
4353 const int rightDist = mBounds.width - aPoint.x;
4354 const int bottomDist = mBounds.height - aPoint.y;
4356 // We can't emulate resize of North/West edges on Wayland as we can't shift
4357 // toplevel window.
4358 bool waylandLimitedResize = mAspectRatio != 0.0f && GdkIsWaylandDisplay();
4360 if (topDist <= resizerHeight) {
4361 if (rightDist <= resizerWidth) {
4362 return Some(GDK_WINDOW_EDGE_NORTH_EAST);
4364 if (leftDist <= resizerWidth) {
4365 return Some(GDK_WINDOW_EDGE_NORTH_WEST);
4367 return waylandLimitedResize ? Nothing() : Some(GDK_WINDOW_EDGE_NORTH);
4370 if (!mIsPIPWindow) {
4371 return Nothing();
4374 if (bottomDist <= resizerHeight) {
4375 if (leftDist <= resizerWidth) {
4376 return Some(GDK_WINDOW_EDGE_SOUTH_WEST);
4378 if (rightDist <= resizerWidth) {
4379 return Some(GDK_WINDOW_EDGE_SOUTH_EAST);
4381 return Some(GDK_WINDOW_EDGE_SOUTH);
4384 if (leftDist <= resizerHeight) {
4385 if (topDist <= resizerWidth) {
4386 return Some(GDK_WINDOW_EDGE_NORTH_WEST);
4388 if (bottomDist <= resizerWidth) {
4389 return Some(GDK_WINDOW_EDGE_SOUTH_WEST);
4391 return waylandLimitedResize ? Nothing() : Some(GDK_WINDOW_EDGE_WEST);
4394 if (rightDist <= resizerHeight) {
4395 if (topDist <= resizerWidth) {
4396 return Some(GDK_WINDOW_EDGE_NORTH_EAST);
4398 if (bottomDist <= resizerWidth) {
4399 return Some(GDK_WINDOW_EDGE_SOUTH_EAST);
4401 return Some(GDK_WINDOW_EDGE_EAST);
4403 return Nothing();
4406 template <typename Event>
4407 static LayoutDeviceIntPoint GetRefPoint(nsWindow* aWindow, Event* aEvent) {
4408 if (aEvent->window == aWindow->GetGdkWindow()) {
4409 // we are the window that the event happened on so no need for expensive
4410 // WidgetToScreenOffset
4411 return aWindow->GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
4413 // XXX we're never quite sure which GdkWindow the event came from due to our
4414 // custom bubbling in scroll_event_cb(), so use ScreenToWidget to translate
4415 // the screen root coordinates into coordinates relative to this widget.
4416 return aWindow->GdkEventCoordsToDevicePixels(aEvent->x_root, aEvent->y_root) -
4417 aWindow->WidgetToScreenOffset();
4420 void nsWindow::EmulateResizeDrag(GdkEventMotion* aEvent) {
4421 auto newPoint = LayoutDeviceIntPoint::Floor(aEvent->x, aEvent->y);
4422 LayoutDeviceIntPoint diff = newPoint - mLastResizePoint;
4423 mLastResizePoint = newPoint;
4425 GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
4426 LayoutDeviceIntSize newSize(size.width + diff.x, size.height + diff.y);
4428 if (mAspectResizer.value() == GTK_ORIENTATION_VERTICAL) {
4429 newSize.width = int(newSize.height * mAspectRatio);
4430 } else { // GTK_ORIENTATION_HORIZONTAL
4431 newSize.height = int(newSize.width / mAspectRatio);
4433 LOG(" aspect ratio correction %d x %d aspect %f\n", newSize.width,
4434 newSize.height, mAspectRatio);
4435 gtk_window_resize(GTK_WINDOW(mShell), newSize.width, newSize.height);
4438 void nsWindow::OnMotionNotifyEvent(GdkEventMotion* aEvent) {
4439 if (!mGdkWindow) {
4440 return;
4443 // Emulate gdk_window_begin_resize_drag() for windows
4444 // with fixed aspect ratio on Wayland.
4445 if (mAspectResizer && mAspectRatio != 0.0f) {
4446 EmulateResizeDrag(aEvent);
4447 return;
4450 if (mWindowShouldStartDragging) {
4451 mWindowShouldStartDragging = false;
4452 GdkWindow* dragWindow = nullptr;
4454 // find the top-level window
4455 if (mGdkWindow) {
4456 dragWindow = gdk_window_get_toplevel(mGdkWindow);
4457 MOZ_ASSERT(dragWindow, "gdk_window_get_toplevel should not return null");
4460 #ifdef MOZ_X11
4461 if (dragWindow && GdkIsX11Display()) {
4462 // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=789054
4463 // To avoid crashes disable double-click on WM without _NET_WM_MOVERESIZE.
4464 // See _should_perform_ewmh_drag() at gdkwindow-x11.c
4465 GdkScreen* screen = gdk_window_get_screen(dragWindow);
4466 GdkAtom atom = gdk_atom_intern("_NET_WM_MOVERESIZE", FALSE);
4467 if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
4468 dragWindow = nullptr;
4471 #endif
4473 if (dragWindow) {
4474 gdk_window_begin_move_drag(dragWindow, 1, aEvent->x_root, aEvent->y_root,
4475 aEvent->time);
4476 return;
4480 mWidgetCursorLocked = false;
4481 const auto refPoint = GetRefPoint(this, aEvent);
4482 if (auto edge = CheckResizerEdge(refPoint)) {
4483 nsCursor cursor = eCursor_none;
4484 switch (*edge) {
4485 case GDK_WINDOW_EDGE_SOUTH:
4486 case GDK_WINDOW_EDGE_NORTH:
4487 cursor = eCursor_ns_resize;
4488 break;
4489 case GDK_WINDOW_EDGE_WEST:
4490 case GDK_WINDOW_EDGE_EAST:
4491 cursor = eCursor_ew_resize;
4492 break;
4493 case GDK_WINDOW_EDGE_NORTH_WEST:
4494 case GDK_WINDOW_EDGE_SOUTH_EAST:
4495 cursor = eCursor_nwse_resize;
4496 break;
4497 case GDK_WINDOW_EDGE_NORTH_EAST:
4498 case GDK_WINDOW_EDGE_SOUTH_WEST:
4499 cursor = eCursor_nesw_resize;
4500 break;
4502 SetCursor(Cursor{cursor});
4503 // If we set resize cursor on widget level keep it locked and prevent layout
4504 // to switch it back to default (by synthetic mouse events for instance)
4505 // until resize is finished. This affects PIP windows only.
4506 if (mIsPIPWindow) {
4507 mWidgetCursorLocked = true;
4509 return;
4512 WidgetMouseEvent event(true, eMouseMove, this, WidgetMouseEvent::eReal);
4514 gdouble pressure = 0;
4515 gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
4516 // Sometime gdk generate 0 pressure value between normal values
4517 // We have to ignore that and use last valid value
4518 if (pressure) {
4519 mLastMotionPressure = pressure;
4521 event.mPressure = mLastMotionPressure;
4522 event.mRefPoint = refPoint;
4523 event.AssignEventTime(GetWidgetEventTime(aEvent->time));
4525 KeymapWrapper::InitInputEvent(event, aEvent->state);
4527 DispatchInputEvent(&event);
4530 // If the automatic pointer grab on ButtonPress has deactivated before
4531 // ButtonRelease, and the mouse button is released while the pointer is not
4532 // over any a Gecko window, then the ButtonRelease event will not be received.
4533 // (A similar situation exists when the pointer is grabbed with owner_events
4534 // True as the ButtonRelease may be received on a foreign [plugin] window).
4535 // Use this method to check for released buttons when the pointer returns to a
4536 // Gecko window.
4537 void nsWindow::DispatchMissedButtonReleases(GdkEventCrossing* aGdkEvent) {
4538 guint changed = aGdkEvent->state ^ gButtonState;
4539 // Only consider button releases.
4540 // (Ignore button presses that occurred outside Gecko.)
4541 guint released = changed & gButtonState;
4542 gButtonState = aGdkEvent->state;
4544 // Loop over each button, excluding mouse wheel buttons 4 and 5 for which
4545 // GDK ignores releases.
4546 for (guint buttonMask = GDK_BUTTON1_MASK; buttonMask <= GDK_BUTTON3_MASK;
4547 buttonMask <<= 1) {
4548 if (released & buttonMask) {
4549 int16_t buttonType;
4550 switch (buttonMask) {
4551 case GDK_BUTTON1_MASK:
4552 buttonType = MouseButton::ePrimary;
4553 break;
4554 case GDK_BUTTON2_MASK:
4555 buttonType = MouseButton::eMiddle;
4556 break;
4557 default:
4558 NS_ASSERTION(buttonMask == GDK_BUTTON3_MASK,
4559 "Unexpected button mask");
4560 buttonType = MouseButton::eSecondary;
4563 LOG("Synthesized button %u release", guint(buttonType + 1));
4565 // Dispatch a synthesized button up event to tell Gecko about the
4566 // change in state. This event is marked as synthesized so that
4567 // it is not dispatched as a DOM event, because we don't know the
4568 // position, widget, modifiers, or time/order.
4569 WidgetMouseEvent synthEvent(true, eMouseUp, this,
4570 WidgetMouseEvent::eSynthesized);
4571 synthEvent.mButton = buttonType;
4572 DispatchInputEvent(&synthEvent);
4577 void nsWindow::InitButtonEvent(WidgetMouseEvent& aEvent,
4578 GdkEventButton* aGdkEvent,
4579 const LayoutDeviceIntPoint& aRefPoint) {
4580 aEvent.mRefPoint = aRefPoint;
4582 guint modifierState = aGdkEvent->state;
4583 // aEvent's state includes the button state from immediately before this
4584 // event. If aEvent is a mousedown or mouseup event, we need to update
4585 // the button state.
4586 guint buttonMask = 0;
4587 switch (aGdkEvent->button) {
4588 case 1:
4589 buttonMask = GDK_BUTTON1_MASK;
4590 break;
4591 case 2:
4592 buttonMask = GDK_BUTTON2_MASK;
4593 break;
4594 case 3:
4595 buttonMask = GDK_BUTTON3_MASK;
4596 break;
4598 if (aGdkEvent->type == GDK_BUTTON_RELEASE) {
4599 modifierState &= ~buttonMask;
4600 } else {
4601 modifierState |= buttonMask;
4604 KeymapWrapper::InitInputEvent(aEvent, modifierState);
4606 aEvent.AssignEventTime(GetWidgetEventTime(aGdkEvent->time));
4608 switch (aGdkEvent->type) {
4609 case GDK_2BUTTON_PRESS:
4610 aEvent.mClickCount = 2;
4611 break;
4612 case GDK_3BUTTON_PRESS:
4613 aEvent.mClickCount = 3;
4614 break;
4615 // default is one click
4616 default:
4617 aEvent.mClickCount = 1;
4621 static guint ButtonMaskFromGDKButton(guint button) {
4622 return GDK_BUTTON1_MASK << (button - 1);
4625 void nsWindow::DispatchContextMenuEventFromMouseEvent(
4626 uint16_t domButton, GdkEventButton* aEvent,
4627 const LayoutDeviceIntPoint& aRefPoint) {
4628 if (domButton == MouseButton::eSecondary && MOZ_LIKELY(!mIsDestroyed)) {
4629 WidgetMouseEvent contextMenuEvent(true, eContextMenu, this,
4630 WidgetMouseEvent::eReal);
4631 InitButtonEvent(contextMenuEvent, aEvent, aRefPoint);
4632 contextMenuEvent.mPressure = mLastMotionPressure;
4633 DispatchInputEvent(&contextMenuEvent);
4637 void nsWindow::TryToShowNativeWindowMenu(GdkEventButton* aEvent) {
4638 if (!gdk_window_show_window_menu(GetToplevelGdkWindow(), (GdkEvent*)aEvent)) {
4639 NS_WARNING("Native context menu wasn't shown");
4643 bool nsWindow::DoTitlebarAction(LookAndFeel::TitlebarEvent aEvent,
4644 GdkEventButton* aButtonEvent) {
4645 LOG("DoTitlebarAction %s click",
4646 aEvent == LookAndFeel::TitlebarEvent::Double_Click ? "double" : "middle");
4647 switch (LookAndFeel::GetTitlebarAction(aEvent)) {
4648 case LookAndFeel::TitlebarAction::WindowMenu:
4649 // Titlebar app menu
4650 LOG(" action menu");
4651 TryToShowNativeWindowMenu(aButtonEvent);
4652 break;
4653 // Lower is part of gtk_surface1 protocol which we can't support
4654 // as Gtk keeps it private. So emulate it by minimize.
4655 case LookAndFeel::TitlebarAction::WindowLower:
4656 case LookAndFeel::TitlebarAction::WindowMinimize:
4657 LOG(" action minimize");
4658 SetSizeMode(nsSizeMode_Minimized);
4659 break;
4660 case LookAndFeel::TitlebarAction::WindowMaximize:
4661 LOG(" action maximize");
4662 SetSizeMode(nsSizeMode_Maximized);
4663 break;
4664 case LookAndFeel::TitlebarAction::WindowMaximizeToggle:
4665 LOG(" action toggle maximize");
4666 if (mSizeMode == nsSizeMode_Maximized) {
4667 SetSizeMode(nsSizeMode_Normal);
4668 } else if (mSizeMode == nsSizeMode_Normal) {
4669 SetSizeMode(nsSizeMode_Maximized);
4671 break;
4672 case LookAndFeel::TitlebarAction::None:
4673 default:
4674 LOG(" action none");
4675 return false;
4677 return true;
4680 void nsWindow::OnButtonPressEvent(GdkEventButton* aEvent) {
4681 LOG("Button %u press\n", aEvent->button);
4683 SetLastMousePressEvent((GdkEvent*)aEvent);
4685 // If you double click in GDK, it will actually generate a second
4686 // GDK_BUTTON_PRESS before sending the GDK_2BUTTON_PRESS, and this is
4687 // different than the DOM spec. GDK puts this in the queue
4688 // programatically, so it's safe to assume that if there's a
4689 // double click in the queue, it was generated so we can just drop
4690 // this click.
4691 GUniquePtr<GdkEvent> peekedEvent(gdk_event_peek());
4692 if (peekedEvent) {
4693 GdkEventType type = peekedEvent->any.type;
4694 if (type == GDK_2BUTTON_PRESS || type == GDK_3BUTTON_PRESS) {
4695 return;
4699 nsWindow* containerWindow = GetContainerWindow();
4700 if (!gFocusWindow && containerWindow) {
4701 containerWindow->DispatchActivateEvent();
4704 const auto refPoint = GetRefPoint(this, aEvent);
4706 // check to see if we should rollup
4707 if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false)) {
4708 if (aEvent->button == 3 && mDraggableRegion.Contains(refPoint)) {
4709 GUniquePtr<GdkEvent> eventCopy;
4710 if (aEvent->type != GDK_BUTTON_PRESS) {
4711 // If the user double-clicks too fast we'll get a 2BUTTON_PRESS event
4712 // instead, and that isn't handled by open_window_menu, so coerce it
4713 // into a regular press.
4714 eventCopy.reset(gdk_event_copy((GdkEvent*)aEvent));
4715 eventCopy->type = GDK_BUTTON_PRESS;
4717 TryToShowNativeWindowMenu(eventCopy ? &eventCopy->button : aEvent);
4719 return;
4722 // Check to see if the event is within our window's resize region
4723 if (auto edge = CheckResizerEdge(refPoint)) {
4724 // On Wayland Gtk fails to vertically/horizontally resize windows
4725 // with fixed aspect ratio. We need to emulate
4726 // gdk_window_begin_resize_drag() at OnMotionNotifyEvent().
4727 if (mAspectRatio != 0.0f && GdkIsWaylandDisplay()) {
4728 mLastResizePoint = LayoutDeviceIntPoint::Floor(aEvent->x, aEvent->y);
4729 switch (*edge) {
4730 case GDK_WINDOW_EDGE_SOUTH:
4731 mAspectResizer = Some(GTK_ORIENTATION_VERTICAL);
4732 break;
4733 case GDK_WINDOW_EDGE_EAST:
4734 mAspectResizer = Some(GTK_ORIENTATION_HORIZONTAL);
4735 break;
4736 default:
4737 mAspectResizer.reset();
4738 break;
4740 ApplySizeConstraints();
4742 if (!mAspectResizer) {
4743 gdk_window_begin_resize_drag(GetToplevelGdkWindow(), *edge,
4744 aEvent->button, aEvent->x_root,
4745 aEvent->y_root, aEvent->time);
4747 return;
4750 gdouble pressure = 0;
4751 gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
4752 mLastMotionPressure = pressure;
4754 uint16_t domButton;
4755 switch (aEvent->button) {
4756 case 1:
4757 domButton = MouseButton::ePrimary;
4758 break;
4759 case 2:
4760 domButton = MouseButton::eMiddle;
4761 break;
4762 case 3:
4763 domButton = MouseButton::eSecondary;
4764 break;
4765 // These are mapped to horizontal scroll
4766 case 6:
4767 case 7:
4768 NS_WARNING("We're not supporting legacy horizontal scroll event");
4769 return;
4770 // Map buttons 8-9(10) to back/forward
4771 case 8:
4772 if (!Preferences::GetBool("mousebutton.4th.enabled", true)) {
4773 return;
4775 DispatchCommandEvent(nsGkAtoms::Back);
4776 return;
4777 case 9:
4778 case 10:
4779 if (!Preferences::GetBool("mousebutton.5th.enabled", true)) {
4780 return;
4782 DispatchCommandEvent(nsGkAtoms::Forward);
4783 return;
4784 default:
4785 return;
4788 gButtonState |= ButtonMaskFromGDKButton(aEvent->button);
4790 WidgetMouseEvent event(true, eMouseDown, this, WidgetMouseEvent::eReal);
4791 event.mButton = domButton;
4792 InitButtonEvent(event, aEvent, refPoint);
4793 event.mPressure = mLastMotionPressure;
4795 nsIWidget::ContentAndAPZEventStatus eventStatus = DispatchInputEvent(&event);
4797 const bool defaultPrevented =
4798 eventStatus.mContentStatus == nsEventStatus_eConsumeNoDefault;
4800 if (!defaultPrevented && mDraggableRegion.Contains(refPoint)) {
4801 if (domButton == MouseButton::ePrimary) {
4802 mWindowShouldStartDragging = true;
4803 } else if (domButton == MouseButton::eMiddle &&
4804 StaticPrefs::widget_gtk_titlebar_action_middle_click_enabled()) {
4805 DoTitlebarAction(nsXPLookAndFeel::TitlebarEvent::Middle_Click, aEvent);
4809 // right menu click on linux should also pop up a context menu
4810 if (!StaticPrefs::ui_context_menus_after_mouseup() &&
4811 eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
4812 DispatchContextMenuEventFromMouseEvent(domButton, aEvent, refPoint);
4816 void nsWindow::OnButtonReleaseEvent(GdkEventButton* aEvent) {
4817 LOG("Button %u release\n", aEvent->button);
4819 SetLastMousePressEvent(nullptr);
4821 if (!mGdkWindow) {
4822 return;
4825 if (mAspectResizer) {
4826 mAspectResizer = Nothing();
4827 return;
4830 if (mWindowShouldStartDragging) {
4831 mWindowShouldStartDragging = false;
4834 uint16_t domButton;
4835 switch (aEvent->button) {
4836 case 1:
4837 domButton = MouseButton::ePrimary;
4838 break;
4839 case 2:
4840 domButton = MouseButton::eMiddle;
4841 break;
4842 case 3:
4843 domButton = MouseButton::eSecondary;
4844 break;
4845 default:
4846 return;
4849 gButtonState &= ~ButtonMaskFromGDKButton(aEvent->button);
4851 const auto refPoint = GetRefPoint(this, aEvent);
4853 WidgetMouseEvent event(true, eMouseUp, this, WidgetMouseEvent::eReal);
4854 event.mButton = domButton;
4855 InitButtonEvent(event, aEvent, refPoint);
4856 gdouble pressure = 0;
4857 gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
4858 event.mPressure = pressure ? (float)pressure : (float)mLastMotionPressure;
4860 // The mRefPoint is manipulated in DispatchInputEvent, we're saving it
4861 // to use it for the doubleclick position check.
4862 const LayoutDeviceIntPoint pos = event.mRefPoint;
4864 nsIWidget::ContentAndAPZEventStatus eventStatus = DispatchInputEvent(&event);
4866 const bool defaultPrevented =
4867 eventStatus.mContentStatus == nsEventStatus_eConsumeNoDefault;
4868 // Check if mouse position in titlebar and doubleclick happened to
4869 // trigger defined action.
4870 if (!defaultPrevented && mDrawInTitlebar &&
4871 event.mButton == MouseButton::ePrimary && event.mClickCount == 2 &&
4872 mDraggableRegion.Contains(pos)) {
4873 DoTitlebarAction(nsXPLookAndFeel::TitlebarEvent::Double_Click, aEvent);
4875 mLastMotionPressure = pressure;
4877 // right menu click on linux should also pop up a context menu
4878 if (StaticPrefs::ui_context_menus_after_mouseup() &&
4879 eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
4880 DispatchContextMenuEventFromMouseEvent(domButton, aEvent, refPoint);
4883 // Open window manager menu on PIP window to allow user
4884 // to place it on top / all workspaces.
4885 if (mAlwaysOnTop && aEvent->button == 3) {
4886 TryToShowNativeWindowMenu(aEvent);
4890 void nsWindow::OnContainerFocusInEvent(GdkEventFocus* aEvent) {
4891 LOG("OnContainerFocusInEvent");
4893 // Unset the urgency hint, if possible
4894 GtkWidget* top_window = GetToplevelWidget();
4895 if (top_window && (gtk_widget_get_visible(top_window))) {
4896 SetUrgencyHint(top_window, false);
4899 // Return if being called within SetFocus because the focus manager
4900 // already knows that the window is active.
4901 if (gBlockActivateEvent) {
4902 LOG("activated notification is blocked");
4903 return;
4906 // If keyboard input will be accepted, the focus manager will call
4907 // SetFocus to set the correct window.
4908 gFocusWindow = nullptr;
4910 DispatchActivateEvent();
4912 if (!gFocusWindow) {
4913 // We don't really have a window for dispatching key events, but
4914 // setting a non-nullptr value here prevents OnButtonPressEvent() from
4915 // dispatching an activation notification if the widget is already
4916 // active.
4917 gFocusWindow = this;
4920 LOG("Events sent from focus in event");
4923 void nsWindow::OnContainerFocusOutEvent(GdkEventFocus* aEvent) {
4924 LOG("OnContainerFocusOutEvent");
4926 if (IsTopLevelWindowType()) {
4927 // Rollup menus when a window is focused out unless a drag is occurring.
4928 // This check is because drags grab the keyboard and cause a focus out on
4929 // versions of GTK before 2.18.
4930 const bool shouldRollupMenus = [&] {
4931 nsCOMPtr<nsIDragService> dragService =
4932 do_GetService("@mozilla.org/widget/dragservice;1");
4933 nsCOMPtr<nsIDragSession> dragSession;
4934 dragService->GetCurrentSession(getter_AddRefs(dragSession));
4935 if (!dragSession) {
4936 return true;
4938 // We also roll up when a drag is from a different application
4939 nsCOMPtr<nsINode> sourceNode;
4940 dragSession->GetSourceNode(getter_AddRefs(sourceNode));
4941 return !sourceNode;
4942 }();
4944 if (shouldRollupMenus) {
4945 RollupAllMenus();
4948 if (RefPtr pm = nsXULPopupManager::GetInstance()) {
4949 pm->RollupTooltips();
4953 if (gFocusWindow) {
4954 RefPtr<nsWindow> kungFuDeathGrip = gFocusWindow;
4955 if (gFocusWindow->mIMContext) {
4956 gFocusWindow->mIMContext->OnBlurWindow(gFocusWindow);
4958 gFocusWindow = nullptr;
4961 DispatchDeactivateEvent();
4963 if (IsChromeWindowTitlebar()) {
4964 // DispatchDeactivateEvent() ultimately results in a call to
4965 // BrowsingContext::SetIsActiveBrowserWindow(), which resets
4966 // the state. We call UpdateMozWindowActive() to keep it in
4967 // sync with GDK_WINDOW_STATE_FOCUSED.
4968 UpdateMozWindowActive();
4971 LOG("Done with container focus out");
4974 bool nsWindow::DispatchCommandEvent(nsAtom* aCommand) {
4975 nsEventStatus status;
4976 WidgetCommandEvent appCommandEvent(true, aCommand, this);
4977 DispatchEvent(&appCommandEvent, status);
4978 return TRUE;
4981 bool nsWindow::DispatchContentCommandEvent(EventMessage aMsg) {
4982 nsEventStatus status;
4983 WidgetContentCommandEvent event(true, aMsg, this);
4984 DispatchEvent(&event, status);
4985 return TRUE;
4988 WidgetEventTime nsWindow::GetWidgetEventTime(guint32 aEventTime) {
4989 return WidgetEventTime(GetEventTimeStamp(aEventTime));
4992 TimeStamp nsWindow::GetEventTimeStamp(guint32 aEventTime) {
4993 if (MOZ_UNLIKELY(!mGdkWindow)) {
4994 // nsWindow has been Destroy()ed.
4995 return TimeStamp::Now();
4997 if (aEventTime == 0) {
4998 // Some X11 and GDK events may be received with a time of 0 to indicate
4999 // that they are synthetic events. Some input method editors do this.
5000 // In this case too, just return the current timestamp.
5001 return TimeStamp::Now();
5004 TimeStamp eventTimeStamp;
5006 if (GdkIsWaylandDisplay()) {
5007 // Wayland compositors use monotonic time to set timestamps.
5008 int64_t timestampTime = g_get_monotonic_time() / 1000;
5009 guint32 refTimeTruncated = guint32(timestampTime);
5011 timestampTime -= refTimeTruncated - aEventTime;
5012 int64_t tick =
5013 BaseTimeDurationPlatformUtils::TicksFromMilliseconds(timestampTime);
5014 eventTimeStamp = TimeStamp::FromSystemTime(tick);
5015 } else {
5016 #ifdef MOZ_X11
5017 CurrentX11TimeGetter* getCurrentTime = GetCurrentTimeGetter();
5018 MOZ_ASSERT(getCurrentTime,
5019 "Null current time getter despite having a window");
5020 eventTimeStamp =
5021 TimeConverter().GetTimeStampFromSystemTime(aEventTime, *getCurrentTime);
5022 #endif
5024 return eventTimeStamp;
5027 #ifdef MOZ_X11
5028 mozilla::CurrentX11TimeGetter* nsWindow::GetCurrentTimeGetter() {
5029 MOZ_ASSERT(mGdkWindow, "Expected mGdkWindow to be set");
5030 if (MOZ_UNLIKELY(!mCurrentTimeGetter)) {
5031 mCurrentTimeGetter = MakeUnique<CurrentX11TimeGetter>(mGdkWindow);
5033 return mCurrentTimeGetter.get();
5035 #endif
5037 gboolean nsWindow::OnKeyPressEvent(GdkEventKey* aEvent) {
5038 LOG("OnKeyPressEvent");
5040 KeymapWrapper::HandleKeyPressEvent(this, aEvent);
5041 return TRUE;
5044 gboolean nsWindow::OnKeyReleaseEvent(GdkEventKey* aEvent) {
5045 LOG("OnKeyReleaseEvent");
5046 if (NS_WARN_IF(!KeymapWrapper::HandleKeyReleaseEvent(this, aEvent))) {
5047 return FALSE;
5049 return TRUE;
5052 void nsWindow::OnScrollEvent(GdkEventScroll* aEvent) {
5053 LOG("OnScrollEvent");
5055 // check to see if we should rollup
5056 if (CheckForRollup(aEvent->x_root, aEvent->y_root, true, false)) {
5057 return;
5060 // check for duplicate legacy scroll event, see GNOME bug 726878
5061 if (aEvent->direction != GDK_SCROLL_SMOOTH &&
5062 mLastScrollEventTime == aEvent->time) {
5063 LOG("[%d] duplicate legacy scroll event %d\n", aEvent->time,
5064 aEvent->direction);
5065 return;
5067 WidgetWheelEvent wheelEvent(true, eWheel, this);
5068 wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_LINE;
5069 switch (aEvent->direction) {
5070 case GDK_SCROLL_SMOOTH: {
5071 // As of GTK 3.4, all directional scroll events are provided by
5072 // the GDK_SCROLL_SMOOTH direction on XInput2 and Wayland devices.
5073 mLastScrollEventTime = aEvent->time;
5075 // Special handling for touchpads to support flings
5076 // (also known as kinetic/inertial/momentum scrolling)
5077 GdkDevice* device = gdk_event_get_source_device((GdkEvent*)aEvent);
5078 GdkInputSource source = gdk_device_get_source(device);
5079 if (source == GDK_SOURCE_TOUCHSCREEN || source == GDK_SOURCE_TOUCHPAD ||
5080 mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase.isSome()) {
5081 if (StaticPrefs::apz_gtk_pangesture_enabled() &&
5082 gtk_check_version(3, 20, 0) == nullptr) {
5083 static auto sGdkEventIsScrollStopEvent =
5084 (gboolean(*)(const GdkEvent*))dlsym(
5085 RTLD_DEFAULT, "gdk_event_is_scroll_stop_event");
5087 LOG("[%d] pan smooth event dx=%f dy=%f inprogress=%d\n", aEvent->time,
5088 aEvent->delta_x, aEvent->delta_y, mPanInProgress);
5089 auto eventType = PanGestureInput::PANGESTURE_PAN;
5090 if (sGdkEventIsScrollStopEvent((GdkEvent*)aEvent)) {
5091 eventType = PanGestureInput::PANGESTURE_END;
5092 mPanInProgress = false;
5093 } else if (!mPanInProgress) {
5094 eventType = PanGestureInput::PANGESTURE_START;
5095 mPanInProgress = true;
5096 } else if (mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase
5097 .isSome()) {
5098 switch (*mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase) {
5099 case PHASE_BEGIN:
5100 // we should never hit this because we'll hit the above case
5101 // before this.
5102 MOZ_ASSERT_UNREACHABLE();
5103 eventType = PanGestureInput::PANGESTURE_START;
5104 mPanInProgress = true;
5105 break;
5106 case PHASE_UPDATE:
5107 // nothing to do here, eventtype should already be set
5108 MOZ_ASSERT(mPanInProgress);
5109 MOZ_ASSERT(eventType == PanGestureInput::PANGESTURE_PAN);
5110 eventType = PanGestureInput::PANGESTURE_PAN;
5111 break;
5112 case PHASE_END:
5113 MOZ_ASSERT(mPanInProgress);
5114 eventType = PanGestureInput::PANGESTURE_END;
5115 mPanInProgress = false;
5116 break;
5117 default:
5118 MOZ_ASSERT_UNREACHABLE();
5119 break;
5123 mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase.reset();
5125 const bool isPageMode =
5126 #ifdef NIGHTLY_BUILD
5127 StaticPrefs::apz_gtk_pangesture_delta_mode() == 1;
5128 #else
5129 StaticPrefs::apz_gtk_pangesture_delta_mode() != 2;
5130 #endif
5131 const double multiplier =
5132 isPageMode
5133 ? StaticPrefs::apz_gtk_pangesture_page_delta_mode_multiplier()
5134 : StaticPrefs::
5135 apz_gtk_pangesture_pixel_delta_mode_multiplier() *
5136 FractionalScaleFactor();
5138 ScreenPoint deltas(float(aEvent->delta_x * multiplier),
5139 float(aEvent->delta_y * multiplier));
5141 LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent);
5142 PanGestureInput panEvent(
5143 eventType, GetEventTimeStamp(aEvent->time),
5144 ScreenPoint(touchPoint.x, touchPoint.y), deltas,
5145 KeymapWrapper::ComputeKeyModifiers(aEvent->state));
5146 panEvent.mDeltaType = isPageMode ? PanGestureInput::PANDELTA_PAGE
5147 : PanGestureInput::PANDELTA_PIXEL;
5148 panEvent.mSimulateMomentum =
5149 StaticPrefs::apz_gtk_kinetic_scroll_enabled();
5151 DispatchPanGesture(panEvent);
5153 if (mCurrentSynthesizedTouchpadPan.mSavedObserver != 0) {
5154 mozilla::widget::AutoObserverNotifier::NotifySavedObserver(
5155 mCurrentSynthesizedTouchpadPan.mSavedObserver,
5156 "touchpadpanevent");
5157 mCurrentSynthesizedTouchpadPan.mSavedObserver = 0;
5160 return;
5163 // Older GTK doesn't support stop events, so we can't support fling
5164 wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSLY;
5167 // TODO - use a more appropriate scrolling unit than lines.
5168 // Multiply event deltas by 3 to emulate legacy behaviour.
5169 wheelEvent.mDeltaX = aEvent->delta_x * 3;
5170 wheelEvent.mDeltaY = aEvent->delta_y * 3;
5171 wheelEvent.mWheelTicksX = aEvent->delta_x;
5172 wheelEvent.mWheelTicksY = aEvent->delta_y;
5173 wheelEvent.mIsNoLineOrPageDelta = true;
5175 break;
5177 case GDK_SCROLL_UP:
5178 wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = -3;
5179 wheelEvent.mWheelTicksY = -1;
5180 break;
5181 case GDK_SCROLL_DOWN:
5182 wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = 3;
5183 wheelEvent.mWheelTicksY = 1;
5184 break;
5185 case GDK_SCROLL_LEFT:
5186 wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = -1;
5187 wheelEvent.mWheelTicksX = -1;
5188 break;
5189 case GDK_SCROLL_RIGHT:
5190 wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = 1;
5191 wheelEvent.mWheelTicksX = 1;
5192 break;
5195 wheelEvent.mRefPoint = GetRefPoint(this, aEvent);
5197 KeymapWrapper::InitInputEvent(wheelEvent, aEvent->state);
5199 wheelEvent.AssignEventTime(GetWidgetEventTime(aEvent->time));
5201 DispatchInputEvent(&wheelEvent);
5204 void nsWindow::DispatchPanGesture(PanGestureInput& aPanInput) {
5205 MOZ_ASSERT(NS_IsMainThread());
5207 if (mSwipeTracker) {
5208 // Give the swipe tracker a first pass at the event. If a new pan gesture
5209 // has been started since the beginning of the swipe, the swipe tracker
5210 // will know to ignore the event.
5211 nsEventStatus status = mSwipeTracker->ProcessEvent(aPanInput);
5212 if (status == nsEventStatus_eConsumeNoDefault) {
5213 return;
5217 APZEventResult result;
5218 if (mAPZC) {
5219 MOZ_ASSERT(APZThreadUtils::IsControllerThread());
5221 result = mAPZC->InputBridge()->ReceiveInputEvent(aPanInput);
5222 if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
5223 return;
5227 WidgetWheelEvent event = aPanInput.ToWidgetEvent(this);
5228 if (!mAPZC) {
5229 if (MayStartSwipeForNonAPZ(aPanInput)) {
5230 return;
5232 } else {
5233 event = MayStartSwipeForAPZ(aPanInput, result);
5236 ProcessUntransformedAPZEvent(&event, result);
5239 void nsWindow::OnVisibilityNotifyEvent(GdkVisibilityState aState) {
5240 LOG("nsWindow::OnVisibilityNotifyEvent [%p] state 0x%x\n", this, aState);
5241 auto state = aState == GDK_VISIBILITY_FULLY_OBSCURED
5242 ? OcclusionState::OCCLUDED
5243 : OcclusionState::UNKNOWN;
5244 NotifyOcclusionState(state);
5247 void nsWindow::OnWindowStateEvent(GtkWidget* aWidget,
5248 GdkEventWindowState* aEvent) {
5249 LOG("nsWindow::OnWindowStateEvent for %p changed 0x%x new_window_state "
5250 "0x%x\n",
5251 aWidget, aEvent->changed_mask, aEvent->new_window_state);
5253 if (IS_MOZ_CONTAINER(aWidget)) {
5254 // This event is notifying the container widget of changes to the
5255 // toplevel window. Just detect changes affecting whether windows are
5256 // viewable.
5258 // (A visibility notify event is sent to each window that becomes
5259 // viewable when the toplevel is mapped, but we can't rely on that for
5260 // setting mHasMappedToplevel because these toplevel window state
5261 // events are asynchronous. The windows in the hierarchy now may not
5262 // be the same windows as when the toplevel was mapped, so they may
5263 // not get VisibilityNotify events.)
5264 bool mapped = !(aEvent->new_window_state &
5265 (GDK_WINDOW_STATE_ICONIFIED | GDK_WINDOW_STATE_WITHDRAWN));
5266 SetHasMappedToplevel(mapped);
5267 LOG("\tquick return because IS_MOZ_CONTAINER(aWidget) is true\n");
5268 return;
5270 // else the widget is a shell widget.
5272 // The block below is a bit evil.
5274 // When a window is resized before it is shown, gtk_window_resize() delays
5275 // resizes until the window is shown. If gtk_window_state_event() sees a
5276 // GDK_WINDOW_STATE_MAXIMIZED change [1] before the window is shown, then
5277 // gtk_window_compute_configure_request_size() ignores the values from the
5278 // resize [2]. See bug 1449166 for an example of how this could happen.
5280 // [1] https://gitlab.gnome.org/GNOME/gtk/blob/3.22.30/gtk/gtkwindow.c#L7967
5281 // [2] https://gitlab.gnome.org/GNOME/gtk/blob/3.22.30/gtk/gtkwindow.c#L9377
5283 // In order to provide a sensible size for the window when the user exits
5284 // maximized state, we hide the GDK_WINDOW_STATE_MAXIMIZED change from
5285 // gtk_window_state_event() so as to trick GTK into using the values from
5286 // gtk_window_resize() in its configure request.
5288 // We instead notify gtk_window_state_event() of the maximized state change
5289 // once the window is shown.
5291 // See https://gitlab.gnome.org/GNOME/gtk/issues/1044
5293 // This may be fixed in Gtk 3.24+ but it's still live and kicking
5294 // (Bug 1791779).
5295 if (!mIsShown) {
5296 aEvent->changed_mask = static_cast<GdkWindowState>(
5297 aEvent->changed_mask & ~GDK_WINDOW_STATE_MAXIMIZED);
5298 } else if (aEvent->changed_mask & GDK_WINDOW_STATE_WITHDRAWN &&
5299 aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
5300 aEvent->changed_mask = static_cast<GdkWindowState>(
5301 aEvent->changed_mask | GDK_WINDOW_STATE_MAXIMIZED);
5304 // This is a workaround for https://gitlab.gnome.org/GNOME/gtk/issues/1395
5305 // Gtk+ controls window active appearance by window-state-event signal.
5306 if (IsChromeWindowTitlebar() &&
5307 (aEvent->changed_mask & GDK_WINDOW_STATE_FOCUSED)) {
5308 // Emulate what Gtk+ does at gtk_window_state_event().
5309 // We can't check GTK_STATE_FLAG_BACKDROP directly as it's set by Gtk+
5310 // *after* this window-state-event handler.
5311 mTitlebarBackdropState =
5312 !(aEvent->new_window_state & GDK_WINDOW_STATE_FOCUSED);
5314 // keep IsActiveBrowserWindow in sync with GDK_WINDOW_STATE_FOCUSED
5315 UpdateMozWindowActive();
5317 ForceTitlebarRedraw();
5320 // We don't care about anything but changes in the maximized/icon/fullscreen
5321 // states but we need a workaround for bug in Wayland:
5322 // https://gitlab.gnome.org/GNOME/gtk/issues/67
5323 // Under wayland the gtk_window_iconify implementation does NOT synthetize
5324 // window_state_event where the GDK_WINDOW_STATE_ICONIFIED is set.
5325 // During restore we won't get aEvent->changed_mask with
5326 // the GDK_WINDOW_STATE_ICONIFIED so to detect that change we use the stored
5327 // mSizeMode and obtaining a focus.
5328 bool waylandWasIconified =
5329 (GdkIsWaylandDisplay() &&
5330 aEvent->changed_mask & GDK_WINDOW_STATE_FOCUSED &&
5331 aEvent->new_window_state & GDK_WINDOW_STATE_FOCUSED &&
5332 mSizeMode == nsSizeMode_Minimized);
5333 if (!waylandWasIconified &&
5334 (aEvent->changed_mask &
5335 (GDK_WINDOW_STATE_ICONIFIED | GDK_WINDOW_STATE_MAXIMIZED |
5336 GDK_WINDOW_STATE_TILED | GDK_WINDOW_STATE_FULLSCREEN)) == 0) {
5337 LOG("\tearly return because no interesting bits changed\n");
5338 return;
5341 auto oldSizeMode = mSizeMode;
5342 if (aEvent->new_window_state & GDK_WINDOW_STATE_ICONIFIED) {
5343 LOG("\tIconified\n");
5344 mSizeMode = nsSizeMode_Minimized;
5345 #ifdef ACCESSIBILITY
5346 DispatchMinimizeEventAccessible();
5347 #endif // ACCESSIBILITY
5348 } else if (aEvent->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) {
5349 LOG("\tFullscreen\n");
5350 mSizeMode = nsSizeMode_Fullscreen;
5351 } else if (aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
5352 LOG("\tMaximized\n");
5353 mSizeMode = nsSizeMode_Maximized;
5354 #ifdef ACCESSIBILITY
5355 DispatchMaximizeEventAccessible();
5356 #endif // ACCESSIBILITY
5357 } else {
5358 LOG("\tNormal\n");
5359 mSizeMode = nsSizeMode_Normal;
5360 #ifdef ACCESSIBILITY
5361 DispatchRestoreEventAccessible();
5362 #endif // ACCESSIBILITY
5365 mIsTiled = aEvent->new_window_state & GDK_WINDOW_STATE_TILED;
5366 LOG("\tTiled: %d\n", int(mIsTiled));
5368 if (mWidgetListener && mSizeMode != oldSizeMode) {
5369 mWidgetListener->SizeModeChanged(mSizeMode);
5372 if (mDrawInTitlebar && mTransparencyBitmapForTitlebar) {
5373 if (mSizeMode == nsSizeMode_Normal && !mIsTiled) {
5374 UpdateTitlebarTransparencyBitmap();
5375 } else {
5376 ClearTransparencyBitmap();
5378 } else {
5379 SetTitlebarRect();
5383 void nsWindow::OnDPIChanged() {
5384 // Update menu's font size etc.
5385 // This affects style / layout because it affects system font sizes.
5386 if (mWidgetListener) {
5387 if (PresShell* presShell = mWidgetListener->GetPresShell()) {
5388 presShell->BackingScaleFactorChanged();
5390 mWidgetListener->UIResolutionChanged();
5392 NotifyAPZOfDPIChange();
5395 void nsWindow::OnCheckResize() { mPendingConfigures++; }
5397 void nsWindow::OnCompositedChanged() {
5398 // Update CSD after the change in alpha visibility. This only affects
5399 // system metrics, not other theme shenanigans.
5400 NotifyThemeChanged(ThemeChangeKind::MediaQueriesOnly);
5401 mCompositedScreen = gdk_screen_is_composited(gdk_screen_get_default());
5404 void nsWindow::OnScaleChanged(bool aNotify) {
5405 if (!IsTopLevelWindowType()) {
5406 return;
5408 if (!mGdkWindow) {
5409 return; // We'll get there again when we configure the window.
5411 gint newCeiled = gdk_window_get_scale_factor(mGdkWindow);
5412 double newFractional = [&] {
5413 #ifdef MOZ_WAYLAND
5414 if (GdkIsWaylandDisplay()) {
5415 return moz_container_wayland_get_fractional_scale(mContainer);
5417 #endif
5418 return 0.0;
5419 }();
5421 if (mCeiledScaleFactor == newCeiled &&
5422 mFractionalScaleFactor == newFractional) {
5423 return;
5426 NotifyAPZOfDPIChange();
5428 LOG("OnScaleChanged %d, %f -> %d, %f\n", int(mCeiledScaleFactor),
5429 mFractionalScaleFactor, newCeiled, newFractional);
5431 mCeiledScaleFactor = newCeiled;
5432 mFractionalScaleFactor = newFractional;
5434 if (!aNotify) {
5435 return;
5438 // We pause compositor to avoid rendering of obsoleted remote content which
5439 // produces flickering.
5440 // Re-enable compositor again when remote content is updated or timeout
5441 // happens.
5442 PauseCompositorFlickering();
5444 GtkAllocation allocation;
5445 gtk_widget_get_allocation(GTK_WIDGET(mContainer), &allocation);
5446 LayoutDeviceIntSize size = GdkRectToDevicePixels(allocation).Size();
5447 mBounds.SizeTo(size);
5448 // Check mBounds size
5449 if (mCompositorSession &&
5450 !wr::WindowSizeSanityCheck(mBounds.width, mBounds.height)) {
5451 gfxCriticalNoteOnce << "Invalid mBounds in OnScaleChanged " << mBounds;
5454 if (mWidgetListener) {
5455 if (PresShell* presShell = mWidgetListener->GetPresShell()) {
5456 presShell->BackingScaleFactorChanged();
5460 DispatchResized();
5462 if (mCompositorWidgetDelegate) {
5463 mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
5466 if (mCursor.IsCustom()) {
5467 mUpdateCursor = true;
5468 SetCursor(Cursor{mCursor});
5472 void nsWindow::DispatchDragEvent(EventMessage aMsg,
5473 const LayoutDeviceIntPoint& aRefPoint,
5474 guint aTime) {
5475 LOGDRAG("nsWindow::DispatchDragEvent");
5476 WidgetDragEvent event(true, aMsg, this);
5478 InitDragEvent(event);
5480 event.mRefPoint = aRefPoint;
5481 event.AssignEventTime(GetWidgetEventTime(aTime));
5483 DispatchInputEvent(&event);
5486 void nsWindow::OnDragDataReceivedEvent(GtkWidget* aWidget,
5487 GdkDragContext* aDragContext, gint aX,
5488 gint aY,
5489 GtkSelectionData* aSelectionData,
5490 guint aInfo, guint aTime,
5491 gpointer aData) {
5492 LOGDRAG("nsWindow::OnDragDataReceived");
5494 RefPtr<nsDragService> dragService = nsDragService::GetInstance();
5495 nsDragService::AutoEventLoop loop(dragService);
5496 dragService->TargetDataReceived(aWidget, aDragContext, aX, aY, aSelectionData,
5497 aInfo, aTime);
5500 nsWindow* nsWindow::GetTransientForWindowIfPopup() {
5501 if (mWindowType != WindowType::Popup) {
5502 return nullptr;
5504 GtkWindow* toplevel = gtk_window_get_transient_for(GTK_WINDOW(mShell));
5505 if (toplevel) {
5506 return get_window_for_gtk_widget(GTK_WIDGET(toplevel));
5508 return nullptr;
5511 bool nsWindow::IsHandlingTouchSequence(GdkEventSequence* aSequence) {
5512 return mHandleTouchEvent && mTouches.Contains(aSequence);
5515 gboolean nsWindow::OnTouchpadPinchEvent(GdkEventTouchpadPinch* aEvent) {
5516 if (!StaticPrefs::apz_gtk_touchpad_pinch_enabled()) {
5517 return TRUE;
5519 // Do not respond to pinch gestures involving more than two fingers
5520 // unless specifically preffed on. These are sometimes hooked up to other
5521 // actions at the desktop environment level and having the browser also
5522 // pinch can be undesirable.
5523 if (aEvent->n_fingers > 2 &&
5524 !StaticPrefs::apz_gtk_touchpad_pinch_three_fingers_enabled()) {
5525 return FALSE;
5527 auto pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
5528 ScreenCoord currentSpan;
5529 ScreenCoord previousSpan;
5531 switch (aEvent->phase) {
5532 case GDK_TOUCHPAD_GESTURE_PHASE_BEGIN:
5533 pinchGestureType = PinchGestureInput::PINCHGESTURE_START;
5534 currentSpan = aEvent->scale;
5535 mCurrentTouchpadFocus = ViewAs<ScreenPixel>(
5536 GetRefPoint(this, aEvent),
5537 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
5539 // Assign PreviousSpan --> 0.999 to make mDeltaY field of the
5540 // WidgetWheelEvent that this PinchGestureInput event will be converted
5541 // to not equal Zero as our discussion because we observed that the
5542 // scale of the PHASE_BEGIN event is 1.
5543 previousSpan = 0.999;
5544 break;
5546 case GDK_TOUCHPAD_GESTURE_PHASE_UPDATE:
5547 pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
5548 mCurrentTouchpadFocus += ScreenPoint(aEvent->dx, aEvent->dy);
5549 if (aEvent->scale == mLastPinchEventSpan) {
5550 return FALSE;
5552 currentSpan = aEvent->scale;
5553 previousSpan = mLastPinchEventSpan;
5554 break;
5556 case GDK_TOUCHPAD_GESTURE_PHASE_END:
5557 pinchGestureType = PinchGestureInput::PINCHGESTURE_END;
5558 currentSpan = aEvent->scale;
5559 previousSpan = mLastPinchEventSpan;
5560 break;
5562 default:
5563 return FALSE;
5566 PinchGestureInput event(
5567 pinchGestureType, PinchGestureInput::TRACKPAD,
5568 GetEventTimeStamp(aEvent->time), ExternalPoint(0, 0),
5569 mCurrentTouchpadFocus,
5570 100.0 * ((aEvent->phase == GDK_TOUCHPAD_GESTURE_PHASE_END)
5571 ? ScreenCoord(1.f)
5572 : currentSpan),
5573 100.0 * ((aEvent->phase == GDK_TOUCHPAD_GESTURE_PHASE_END)
5574 ? ScreenCoord(1.f)
5575 : previousSpan),
5576 KeymapWrapper::ComputeKeyModifiers(aEvent->state));
5578 if (!event.SetLineOrPageDeltaY(this)) {
5579 return FALSE;
5582 mLastPinchEventSpan = aEvent->scale;
5583 DispatchPinchGestureInput(event);
5584 return TRUE;
5587 gboolean nsWindow::OnTouchEvent(GdkEventTouch* aEvent) {
5588 LOG("OnTouchEvent: x=%f y=%f type=%d\n", aEvent->x, aEvent->y, aEvent->type);
5589 if (!mHandleTouchEvent) {
5590 // If a popup window was spawned (e.g. as the result of a long-press)
5591 // and touch events got diverted to that window within a touch sequence,
5592 // ensure the touch event gets sent to the original window instead. We
5593 // keep the checks here very conservative so that we only redirect
5594 // events in this specific scenario.
5595 nsWindow* targetWindow = GetTransientForWindowIfPopup();
5596 if (targetWindow &&
5597 targetWindow->IsHandlingTouchSequence(aEvent->sequence)) {
5598 return targetWindow->OnTouchEvent(aEvent);
5601 return FALSE;
5604 EventMessage msg;
5605 switch (aEvent->type) {
5606 case GDK_TOUCH_BEGIN:
5607 // check to see if we should rollup
5608 if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false)) {
5609 return FALSE;
5611 msg = eTouchStart;
5612 break;
5613 case GDK_TOUCH_UPDATE:
5614 msg = eTouchMove;
5615 // Start dragging when motion events happens in the dragging area
5616 if (mWindowShouldStartDragging) {
5617 mWindowShouldStartDragging = false;
5618 if (mGdkWindow) {
5619 GdkWindow* gdk_window = gdk_window_get_toplevel(mGdkWindow);
5620 MOZ_ASSERT(gdk_window,
5621 "gdk_window_get_toplevel should not return null");
5623 LOG(" start window dragging window\n");
5624 gdk_window_begin_move_drag(gdk_window, 1, aEvent->x_root,
5625 aEvent->y_root, aEvent->time);
5627 // Cancel the event sequence. gdk will steal all subsequent events
5628 // (including TOUCH_END).
5629 msg = eTouchCancel;
5632 break;
5633 case GDK_TOUCH_END:
5634 msg = eTouchEnd;
5635 if (mWindowShouldStartDragging) {
5636 LOG(" end of window dragging window\n");
5637 mWindowShouldStartDragging = false;
5639 break;
5640 case GDK_TOUCH_CANCEL:
5641 msg = eTouchCancel;
5642 break;
5643 default:
5644 return FALSE;
5647 const LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent);
5649 int32_t id;
5650 RefPtr<dom::Touch> touch;
5651 if (mTouches.Remove(aEvent->sequence, getter_AddRefs(touch))) {
5652 id = touch->mIdentifier;
5653 } else {
5654 id = ++gLastTouchID & 0x7FFFFFFF;
5657 touch =
5658 new dom::Touch(id, touchPoint, LayoutDeviceIntPoint(1, 1), 0.0f, 0.0f);
5660 WidgetTouchEvent event(true, msg, this);
5661 KeymapWrapper::InitInputEvent(event, aEvent->state);
5663 if (msg == eTouchStart || msg == eTouchMove) {
5664 mTouches.InsertOrUpdate(aEvent->sequence, std::move(touch));
5665 // add all touch points to event object
5666 for (const auto& data : mTouches.Values()) {
5667 event.mTouches.AppendElement(new dom::Touch(*data));
5669 } else if (msg == eTouchEnd || msg == eTouchCancel) {
5670 *event.mTouches.AppendElement() = std::move(touch);
5673 nsIWidget::ContentAndAPZEventStatus eventStatus = DispatchInputEvent(&event);
5675 // There's a chance that we are in drag area and the event is not consumed
5676 // by something on it.
5677 if (msg == eTouchStart && mDraggableRegion.Contains(touchPoint) &&
5678 eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
5679 mWindowShouldStartDragging = true;
5681 return TRUE;
5684 // Return true if toplevel window is transparent.
5685 // It's transparent when we're running on composited screens
5686 // and we can draw main window without system titlebar.
5687 bool nsWindow::IsToplevelWindowTransparent() {
5688 static bool transparencyConfigured = false;
5690 if (!transparencyConfigured) {
5691 if (gdk_screen_is_composited(gdk_screen_get_default())) {
5692 // Some Gtk+ themes use non-rectangular toplevel windows. To fully
5693 // support such themes we need to make toplevel window transparent
5694 // with ARGB visual.
5695 // It may cause performanance issue so make it configurable
5696 // and enable it by default for selected window managers.
5697 if (Preferences::HasUserValue("mozilla.widget.use-argb-visuals")) {
5698 // argb visual is explicitly required so use it
5699 sTransparentMainWindow =
5700 Preferences::GetBool("mozilla.widget.use-argb-visuals");
5701 } else {
5702 // Enable transparent toplevel window if we can draw main window
5703 // without system titlebar as Gtk+ themes use titlebar round corners.
5704 sTransparentMainWindow =
5705 GetSystemGtkWindowDecoration() != GTK_DECORATION_NONE;
5708 transparencyConfigured = true;
5711 return sTransparentMainWindow;
5714 #ifdef MOZ_X11
5715 // Configure GL visual on X11.
5716 bool nsWindow::ConfigureX11GLVisual() {
5717 auto* screen = gtk_widget_get_screen(mShell);
5718 int visualId = 0;
5719 bool haveVisual = false;
5721 if (gfxVars::UseEGL()) {
5722 haveVisual = GLContextEGL::FindVisual(&visualId);
5725 // We are on GLX or use it as a fallback on Mesa, see
5726 // https://gitlab.freedesktop.org/mesa/mesa/-/issues/149
5727 if (!haveVisual) {
5728 auto* display = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(mShell));
5729 int screenNumber = GDK_SCREEN_XNUMBER(screen);
5730 haveVisual = GLContextGLX::FindVisual(display, screenNumber, &visualId);
5733 GdkVisual* gdkVisual = nullptr;
5734 if (haveVisual) {
5735 // If we're using CSD, rendering will go through mContainer, but
5736 // it will inherit this visual as it is a child of mShell.
5737 gdkVisual = gdk_x11_screen_lookup_visual(screen, visualId);
5739 if (!gdkVisual) {
5740 NS_WARNING("We're missing X11 Visual!");
5741 // We try to use a fallback alpha visual
5742 GdkScreen* screen = gtk_widget_get_screen(mShell);
5743 gdkVisual = gdk_screen_get_rgba_visual(screen);
5745 if (gdkVisual) {
5746 gtk_widget_set_visual(mShell, gdkVisual);
5747 mHasAlphaVisual = true;
5748 return true;
5751 return false;
5753 #endif
5755 nsAutoCString nsWindow::GetFrameTag() const {
5756 if (nsIFrame* frame = GetFrame()) {
5757 #ifdef DEBUG_FRAME_DUMP
5758 return frame->ListTag();
5759 #else
5760 nsAutoCString buf;
5761 buf.AppendPrintf("Frame(%p)", frame);
5762 if (nsIContent* content = frame->GetContent()) {
5763 buf.Append(' ');
5764 AppendUTF16toUTF8(content->NodeName(), buf);
5766 return buf;
5767 #endif
5769 return nsAutoCString("(no frame)");
5772 nsCString nsWindow::GetPopupTypeName() {
5773 switch (mPopupType) {
5774 case PopupType::Menu:
5775 return nsCString("Menu");
5776 case PopupType::Tooltip:
5777 return nsCString("Tooltip");
5778 case PopupType::Panel:
5779 return nsCString("Panel/Utility");
5780 default:
5781 return nsCString("Unknown");
5785 // Disables all rendering of GtkWidget from Gtk side.
5786 // We do our best to persuade Gtk/Gdk to ignore all painting
5787 // to the widget.
5788 static void GtkWidgetDisableUpdates(GtkWidget* aWidget) {
5789 // Clear exposure mask - it disabled synthesized events.
5790 GdkWindow* window = gtk_widget_get_window(aWidget);
5791 if (!window) {
5792 return;
5794 gdk_window_set_events(window, (GdkEventMask)(gdk_window_get_events(window) &
5795 (~GDK_EXPOSURE_MASK)));
5797 // Remove before/after paint handles from frame clock.
5798 // It disables widget content updates.
5799 GdkFrameClock* frame_clock = gdk_window_get_frame_clock(window);
5800 g_signal_handlers_disconnect_by_data(frame_clock, window);
5803 Window nsWindow::GetX11Window() {
5804 #ifdef MOZ_X11
5805 if (GdkIsX11Display()) {
5806 return mGdkWindow ? gdk_x11_window_get_xid(mGdkWindow) : X11None;
5808 #endif
5809 return (Window) nullptr;
5812 void nsWindow::EnsureGdkWindow() {
5813 MOZ_DIAGNOSTIC_ASSERT(mIsMapped);
5814 if (!mGdkWindow) {
5815 mGdkWindow = gtk_widget_get_window(GTK_WIDGET(mContainer));
5816 g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", this);
5817 if (mIMContext) {
5818 mIMContext->SetGdkWindow(mGdkWindow);
5823 bool nsWindow::GetShapedState() {
5824 return mIsTransparent && !mHasAlphaVisual && !mTransparencyBitmapForTitlebar;
5827 void nsWindow::ConfigureCompositor() {
5828 MOZ_DIAGNOSTIC_ASSERT(mCompositorState == COMPOSITOR_ENABLED);
5829 MOZ_DIAGNOSTIC_ASSERT(mIsMapped);
5831 LOG("nsWindow::ConfigureCompositor()");
5832 auto startCompositing = [self = RefPtr{this}, this]() -> void {
5833 LOG(" moz_container_wayland_add_or_fire_initial_draw_callback "
5834 "ConfigureCompositor");
5836 // too late
5837 if (mIsDestroyed || !mIsMapped) {
5838 LOG(" quit, mIsDestroyed = %d mIsMapped = %d", !!mIsDestroyed,
5839 mIsMapped);
5840 return;
5842 // Compositor will be resumed later by ResumeCompositorFlickering().
5843 if (mCompositorState == COMPOSITOR_PAUSED_FLICKERING) {
5844 LOG(" quit, will be resumed by ResumeCompositorFlickering.");
5845 return;
5847 // Compositor will be resumed at nsWindow::SetCompositorWidgetDelegate().
5848 if (!mCompositorWidgetDelegate) {
5849 LOG(" quit, missing mCompositorWidgetDelegate");
5850 return;
5853 ResumeCompositorImpl();
5856 if (GdkIsWaylandDisplay()) {
5857 #ifdef MOZ_WAYLAND
5858 moz_container_wayland_add_or_fire_initial_draw_callback(mContainer,
5859 startCompositing);
5860 #endif
5861 } else {
5862 startCompositing();
5866 void nsWindow::ConfigureGdkWindow() {
5867 LOG("nsWindow::ConfigureGdkWindow()");
5869 EnsureGdkWindow();
5870 OnScaleChanged(/* aNotify = */ false);
5872 if (mIsAlert) {
5873 gdk_window_set_override_redirect(mGdkWindow, TRUE);
5876 #ifdef MOZ_X11
5877 if (GdkIsX11Display()) {
5878 mSurfaceProvider.Initialize(GetX11Window(), GetShapedState());
5880 // Set window manager hint to keep fullscreen windows composited.
5882 // If the window were to get unredirected, there could be visible
5883 // tearing because Gecko does not align its framebuffer updates with
5884 // vblank.
5885 SetCompositorHint(GTK_WIDGET_COMPOSIDED_ENABLED);
5887 #endif
5888 #ifdef MOZ_WAYLAND
5889 if (GdkIsWaylandDisplay()) {
5890 mSurfaceProvider.Initialize(this);
5892 #endif
5894 if (mIsDragPopup) {
5895 if (GdkIsWaylandDisplay()) {
5896 // Disable painting to the widget on Wayland as we paint directly to the
5897 // widget. Wayland compositors does not paint wl_subsurface
5898 // of D&D widget.
5899 if (GtkWidget* parent = gtk_widget_get_parent(mShell)) {
5900 GtkWidgetDisableUpdates(parent);
5902 GtkWidgetDisableUpdates(mShell);
5903 GtkWidgetDisableUpdates(GTK_WIDGET(mContainer));
5904 } else {
5905 // Disable rendering of parent container on X11 to avoid flickering.
5906 if (GtkWidget* parent = gtk_widget_get_parent(mShell)) {
5907 gtk_widget_set_opacity(parent, 0.0);
5912 if (mWindowType == WindowType::Popup) {
5913 if (mNoAutoHide) {
5914 gint wmd = ConvertBorderStyles(mBorderStyle);
5915 if (wmd != -1) {
5916 gdk_window_set_decorations(mGdkWindow, (GdkWMDecoration)wmd);
5919 // If the popup ignores mouse events, set an empty input shape.
5920 SetInputRegion(mInputRegion);
5923 RefreshWindowClass();
5925 // We're not mapped yet but we have already created compositor.
5926 if (mCompositorWidgetDelegate) {
5927 ConfigureCompositor();
5930 LOG(" finished, new GdkWindow %p XID 0x%lx\n", mGdkWindow, GetX11Window());
5933 nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
5934 const LayoutDeviceIntRect& aRect,
5935 widget::InitData* aInitData) {
5936 LOG("nsWindow::Create\n");
5938 // only set the base parent if we're going to be a dialog or a
5939 // toplevel
5940 nsIWidget* baseParent =
5941 aInitData && (aInitData->mWindowType == WindowType::Dialog ||
5942 aInitData->mWindowType == WindowType::TopLevel ||
5943 aInitData->mWindowType == WindowType::Invisible)
5944 ? nullptr
5945 : aParent;
5947 #ifdef ACCESSIBILITY
5948 // Send a DBus message to check whether a11y is enabled
5949 a11y::PreInit();
5950 #endif
5952 #ifdef MOZ_WAYLAND
5953 // Ensure that KeymapWrapper is created on Wayland as we need it for
5954 // keyboard focus tracking.
5955 if (GdkIsWaylandDisplay()) {
5956 KeymapWrapper::EnsureInstance();
5958 #endif
5960 // Ensure that the toolkit is created.
5961 nsGTKToolkit::GetToolkit();
5963 // initialize all the common bits of this class
5964 BaseCreate(baseParent, aInitData);
5966 // and do our common creation
5967 mParent = aParent;
5968 // save our bounds
5969 mBounds = aRect;
5970 LOG(" mBounds: x:%d y:%d w:%d h:%d\n", mBounds.x, mBounds.y, mBounds.width,
5971 mBounds.height);
5973 ConstrainSize(&mBounds.width, &mBounds.height);
5974 mLastSizeRequest = mBounds.Size();
5976 bool popupNeedsAlphaVisual = mWindowType == WindowType::Popup &&
5977 (aInitData && aInitData->mTransparencyMode ==
5978 TransparencyMode::Transparent);
5980 // Figure out our parent window - only used for WindowType::Child
5981 nsWindow* parentnsWindow = nullptr;
5983 if (aParent) {
5984 parentnsWindow = static_cast<nsWindow*>(aParent);
5985 } else if (aNativeParent && GDK_IS_WINDOW(aNativeParent)) {
5986 parentnsWindow = get_window_for_gdk_window(GDK_WINDOW(aNativeParent));
5987 if (!parentnsWindow) {
5988 return NS_ERROR_FAILURE;
5992 if (mWindowType == WindowType::Child) {
5993 // We don't support WindowType::Child directly but emulate it by popup
5994 // windows.
5995 mWindowType = WindowType::Popup;
5996 if (!parentnsWindow) {
5997 if (aNativeParent && GTK_IS_CONTAINER(aNativeParent)) {
5998 parentnsWindow = get_window_for_gtk_widget(GTK_WIDGET(aNativeParent));
6001 mIsChildWindow = true;
6002 LOG(" child widget, switch to popup. parent nsWindow %p", parentnsWindow);
6005 MOZ_ASSERT_IF(mWindowType == WindowType::Popup, parentnsWindow);
6007 if (mWindowType != WindowType::Dialog && mWindowType != WindowType::Popup &&
6008 mWindowType != WindowType::TopLevel &&
6009 mWindowType != WindowType::Invisible) {
6010 MOZ_ASSERT_UNREACHABLE("Unexpected eWindowType");
6011 return NS_ERROR_FAILURE;
6014 mAlwaysOnTop = aInitData && aInitData->mAlwaysOnTop;
6015 // mNoAutoHide seems to be always false here.
6016 // The mNoAutoHide state is set later on nsMenuPopupFrame level
6017 // and can be changed so we use WaylandPopupIsPermanent() to get
6018 // recent popup config (Bug 1728952).
6019 mNoAutoHide = aInitData && aInitData->mNoAutoHide;
6020 mIsAlert = aInitData && aInitData->mIsAlert;
6022 // Popups that are not noautohide are only temporary. The are used
6023 // for menus and the like and disappear when another window is used.
6024 // For most popups, use the standard GtkWindowType GTK_WINDOW_POPUP,
6025 // which will use a Window with the override-redirect attribute
6026 // (for temporary windows).
6027 // For long-lived windows, their stacking order is managed by the
6028 // window manager, as indicated by GTK_WINDOW_TOPLEVEL.
6029 // For Wayland we have to always use GTK_WINDOW_POPUP to control
6030 // popup window position.
6031 GtkWindowType type = GTK_WINDOW_TOPLEVEL;
6032 if (mWindowType == WindowType::Popup) {
6033 MOZ_ASSERT(aInitData);
6034 type = GTK_WINDOW_POPUP;
6035 if (GdkIsX11Display() && mNoAutoHide) {
6036 type = GTK_WINDOW_TOPLEVEL;
6039 mShell = gtk_window_new(type);
6041 // Ensure gfxPlatform is initialized, since that is what initializes
6042 // gfxVars, used below.
6043 Unused << gfxPlatform::GetPlatform();
6045 if (IsTopLevelWindowType()) {
6046 mGtkWindowDecoration = GetSystemGtkWindowDecoration();
6047 // Inherit initial scale from our parent, or use the default monitor scale
6048 // otherwise.
6049 mCeiledScaleFactor = parentnsWindow
6050 ? int32_t(parentnsWindow->mCeiledScaleFactor)
6051 : ScreenHelperGTK::GetGTKMonitorScaleFactor();
6054 // Don't use transparency for PictureInPicture windows.
6055 bool toplevelNeedsAlphaVisual = false;
6056 if (mWindowType == WindowType::TopLevel && !mIsPIPWindow) {
6057 toplevelNeedsAlphaVisual = IsToplevelWindowTransparent();
6060 bool isGLVisualSet = false;
6061 mIsAccelerated = ComputeShouldAccelerate();
6062 #ifdef MOZ_X11
6063 if (GdkIsX11Display() && mIsAccelerated) {
6064 isGLVisualSet = ConfigureX11GLVisual();
6066 #endif
6067 if (!isGLVisualSet && (popupNeedsAlphaVisual || toplevelNeedsAlphaVisual)) {
6068 // We're running on composited screen so we can use alpha visual
6069 // for both toplevel and popups.
6070 if (mCompositedScreen) {
6071 GdkVisual* visual =
6072 gdk_screen_get_rgba_visual(gtk_widget_get_screen(mShell));
6073 if (visual) {
6074 gtk_widget_set_visual(mShell, visual);
6075 mHasAlphaVisual = true;
6080 // Use X shape mask to draw round corners of Firefox titlebar.
6081 // We don't use shape masks any more as we switched to ARGB visual
6082 // by default and non-compositing screens use solid-csd decorations
6083 // without round corners.
6084 // Leave the shape mask code here as it can be used to draw round
6085 // corners on EGL (https://gitlab.freedesktop.org/mesa/mesa/-/issues/149)
6086 // or when custom titlebar theme is used.
6087 mTransparencyBitmapForTitlebar = TitlebarUseShapeMask();
6089 // We have a toplevel window with transparency.
6090 // Calls to UpdateTitlebarTransparencyBitmap() from OnExposeEvent()
6091 // occur before SetTransparencyMode() receives TransparencyMode::Transparent
6092 // from layout, so set mIsTransparent here.
6093 if (mWindowType == WindowType::TopLevel &&
6094 (mHasAlphaVisual || mTransparencyBitmapForTitlebar)) {
6095 mIsTransparent = true;
6098 // We only move a general managed toplevel window if someone has
6099 // actually placed the window somewhere. If no placement has taken
6100 // place, we just let the window manager Do The Right Thing.
6101 if (AreBoundsSane()) {
6102 GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
6103 LOG("nsWindow::Create() Initial resize to %d x %d\n", size.width,
6104 size.height);
6105 gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
6107 if (mIsPIPWindow) {
6108 LOG(" Is PIP window\n");
6109 gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_UTILITY);
6110 } else if (mIsAlert) {
6111 LOG(" Is alert window\n");
6112 gtk_window_set_type_hint(GTK_WINDOW(mShell),
6113 GDK_WINDOW_TYPE_HINT_NOTIFICATION);
6114 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mShell), TRUE);
6115 } else if (mWindowType == WindowType::Dialog) {
6116 mGtkWindowRoleName = "Dialog";
6118 SetDefaultIcon();
6119 gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_DIALOG);
6120 LOG("nsWindow::Create(): dialog");
6121 if (parentnsWindow) {
6122 GtkWindowSetTransientFor(GTK_WINDOW(mShell),
6123 GTK_WINDOW(parentnsWindow->GetGtkWidget()));
6124 LOG(" set parent window [%p]\n", parentnsWindow);
6126 } else if (mWindowType == WindowType::Popup) {
6127 MOZ_ASSERT(aInitData);
6128 mGtkWindowRoleName = "Popup";
6130 LOG("nsWindow::Create() Popup");
6132 if (mNoAutoHide) {
6133 // ... but the window manager does not decorate this window,
6134 // nor provide a separate taskbar icon.
6135 if (mBorderStyle == BorderStyle::Default) {
6136 gtk_window_set_decorated(GTK_WINDOW(mShell), FALSE);
6137 } else {
6138 bool decorate = bool(mBorderStyle & BorderStyle::Title);
6139 gtk_window_set_decorated(GTK_WINDOW(mShell), decorate);
6140 if (decorate) {
6141 gtk_window_set_deletable(GTK_WINDOW(mShell),
6142 bool(mBorderStyle & BorderStyle::Close));
6145 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mShell), TRUE);
6146 // Element focus is managed by the parent window so the
6147 // WM_HINTS input field is set to False to tell the window
6148 // manager not to set input focus to this window ...
6149 gtk_window_set_accept_focus(GTK_WINDOW(mShell), FALSE);
6150 #ifdef MOZ_X11
6151 // ... but when the window manager offers focus through
6152 // WM_TAKE_FOCUS, focus is requested on the parent window.
6153 if (GdkIsX11Display()) {
6154 gtk_widget_realize(mShell);
6155 gdk_window_add_filter(GetToplevelGdkWindow(), popup_take_focus_filter,
6156 nullptr);
6158 #endif
6161 if (aInitData->mIsDragPopup) {
6162 gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_DND);
6163 mIsDragPopup = true;
6164 LOG("nsWindow::Create() Drag popup\n");
6165 } else if (GdkIsX11Display()) {
6166 // Set the window hints on X11 only. Wayland popups are configured
6167 // at WaylandPopupConfigure().
6168 GdkWindowTypeHint gtkTypeHint;
6169 switch (mPopupType) {
6170 case PopupType::Menu:
6171 gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
6172 break;
6173 case PopupType::Tooltip:
6174 gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
6175 break;
6176 default:
6177 gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
6178 break;
6180 gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
6181 LOG("nsWindow::Create() popup type %s", GetPopupTypeName().get());
6183 if (parentnsWindow) {
6184 LOG(" set parent window [%p] %s", parentnsWindow,
6185 parentnsWindow->mGtkWindowRoleName.get());
6186 GtkWindow* parentWidget = GTK_WINDOW(parentnsWindow->GetGtkWidget());
6187 GtkWindowSetTransientFor(GTK_WINDOW(mShell), parentWidget);
6189 // If popup parent is modal, we need to make popup modal too.
6190 if (mPopupType != PopupType::Tooltip &&
6191 gtk_window_get_modal(parentWidget)) {
6192 gtk_window_set_modal(GTK_WINDOW(mShell), true);
6196 // We need realized mShell at NativeMoveResize().
6197 gtk_widget_realize(mShell);
6199 // With popup windows, we want to set their position.
6200 // Place them immediately on X11 and save initial popup position
6201 // on Wayland as we place Wayland popup on show.
6202 if (GdkIsX11Display()) {
6203 NativeMoveResize(/* move */ true, /* resize */ false);
6204 } else if (AreBoundsSane()) {
6205 GdkRectangle rect = DevicePixelsToGdkRectRoundOut(mBounds);
6206 mPopupPosition = {rect.x, rect.y};
6208 } else { // must be WindowType::TopLevel
6209 mGtkWindowRoleName = "Toplevel";
6210 SetDefaultIcon();
6212 LOG("nsWindow::Create() Toplevel\n");
6214 // each toplevel window gets its own window group
6215 GtkWindowGroup* group = gtk_window_group_new();
6216 gtk_window_group_add_window(group, GTK_WINDOW(mShell));
6217 g_object_unref(group);
6220 if (mAlwaysOnTop) {
6221 gtk_window_set_keep_above(GTK_WINDOW(mShell), TRUE);
6224 // It is important that this happens before the realize() call below, so that
6225 // we don't get bogus CSD margins on Wayland, see bug 1794577.
6226 mUndecorated = IsAlwaysUndecoratedWindow();
6227 if (mUndecorated) {
6228 LOG(" Is undecorated Window\n");
6229 gtk_window_set_titlebar(GTK_WINDOW(mShell), gtk_fixed_new());
6230 gtk_window_set_decorated(GTK_WINDOW(mShell), false);
6233 // Create a container to hold child windows and child GtkWidgets.
6234 GtkWidget* container = moz_container_new();
6235 mContainer = MOZ_CONTAINER(container);
6237 // Prevent GtkWindow from painting a background to avoid flickering.
6238 gtk_widget_set_app_paintable(
6239 GTK_WIDGET(mContainer),
6240 StaticPrefs::widget_transparent_windows_AtStartup());
6242 gtk_widget_add_events(GTK_WIDGET(mContainer), kEvents);
6243 gtk_widget_add_events(mShell, GDK_PROPERTY_CHANGE_MASK);
6244 gtk_widget_set_app_paintable(
6245 mShell, StaticPrefs::widget_transparent_windows_AtStartup());
6247 if (mTransparencyBitmapForTitlebar) {
6248 moz_container_force_default_visual(mContainer);
6251 // If we draw to mContainer window then configure it now because
6252 // gtk_container_add() realizes the child widget.
6253 gtk_widget_set_has_window(container, true);
6254 gtk_container_add(GTK_CONTAINER(mShell), container);
6256 // alwaysontop windows are generally used for peripheral indicators,
6257 // so we don't focus them by default.
6258 if (mAlwaysOnTop) {
6259 gtk_window_set_focus_on_map(GTK_WINDOW(mShell), FALSE);
6262 gtk_widget_realize(container);
6264 // make sure this is the focus widget in the container
6265 gtk_widget_show(container);
6267 if (!mAlwaysOnTop) {
6268 gtk_widget_grab_focus(container);
6271 #ifdef MOZ_WAYLAND
6272 if (mIsDragPopup && GdkIsWaylandDisplay()) {
6273 LOG(" set commit to parent");
6274 moz_container_wayland_set_commit_to_parent(mContainer);
6276 #endif
6278 if (mWindowType == WindowType::TopLevel && gKioskMode) {
6279 if (gKioskMonitor != -1) {
6280 mKioskMonitor = Some(gKioskMonitor);
6281 LOG(" set kiosk mode monitor %d", mKioskMonitor.value());
6282 } else {
6283 LOG(" set kiosk mode");
6285 // Kiosk mode always use fullscreen.
6286 MakeFullScreen(/* aFullScreen */ true);
6289 if (mWindowType == WindowType::Popup) {
6290 MOZ_ASSERT(aInitData);
6291 // gdk does not automatically set the cursor for "temporary"
6292 // windows, which are what gtk uses for popups.
6294 // force SetCursor to actually set the cursor, even though our internal
6295 // state indicates that we already have the standard cursor.
6296 mUpdateCursor = true;
6297 SetCursor(Cursor{eCursor_standard});
6300 if (mIsChildWindow && parentnsWindow) {
6301 GdkWindow* window = GetToplevelGdkWindow();
6302 GdkWindow* parentWindow = parentnsWindow->GetToplevelGdkWindow();
6303 LOG(" child GdkWindow %p set parent GdkWindow %p", window, parentWindow);
6304 gdk_window_reparent(window, parentWindow,
6305 DevicePixelsToGdkCoordRoundDown(mBounds.x),
6306 DevicePixelsToGdkCoordRoundDown(mBounds.y));
6309 // Also label mShell toplevel window,
6310 // property_notify_event_cb callback also needs to find its way home
6311 g_object_set_data(G_OBJECT(GetToplevelGdkWindow()), "nsWindow", this);
6312 g_object_set_data(G_OBJECT(mContainer), "nsWindow", this);
6313 g_object_set_data(G_OBJECT(mShell), "nsWindow", this);
6315 // attach listeners for events
6316 g_signal_connect(mShell, "configure_event", G_CALLBACK(configure_event_cb),
6317 nullptr);
6318 g_signal_connect(mShell, "delete_event", G_CALLBACK(delete_event_cb),
6319 nullptr);
6320 g_signal_connect(mShell, "window_state_event",
6321 G_CALLBACK(window_state_event_cb), nullptr);
6322 g_signal_connect(mShell, "visibility-notify-event",
6323 G_CALLBACK(visibility_notify_event_cb), nullptr);
6324 g_signal_connect(mShell, "check-resize", G_CALLBACK(check_resize_cb),
6325 nullptr);
6326 g_signal_connect(mShell, "composited-changed",
6327 G_CALLBACK(widget_composited_changed_cb), nullptr);
6328 g_signal_connect(mShell, "property-notify-event",
6329 G_CALLBACK(property_notify_event_cb), nullptr);
6331 if (mWindowType == WindowType::TopLevel) {
6332 g_signal_connect_after(mShell, "size_allocate",
6333 G_CALLBACK(toplevel_window_size_allocate_cb),
6334 nullptr);
6337 GdkScreen* screen = gtk_widget_get_screen(mShell);
6338 if (!g_signal_handler_find(screen, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
6339 FuncToGpointer(screen_composited_changed_cb),
6340 nullptr)) {
6341 g_signal_connect(screen, "composited-changed",
6342 G_CALLBACK(screen_composited_changed_cb), nullptr);
6345 gtk_drag_dest_set((GtkWidget*)mShell, (GtkDestDefaults)0, nullptr, 0,
6346 (GdkDragAction)0);
6347 g_signal_connect(mShell, "drag_motion", G_CALLBACK(drag_motion_event_cb),
6348 nullptr);
6349 g_signal_connect(mShell, "drag_leave", G_CALLBACK(drag_leave_event_cb),
6350 nullptr);
6351 g_signal_connect(mShell, "drag_drop", G_CALLBACK(drag_drop_event_cb),
6352 nullptr);
6353 g_signal_connect(mShell, "drag_data_received",
6354 G_CALLBACK(drag_data_received_event_cb), nullptr);
6356 GtkSettings* default_settings = gtk_settings_get_default();
6357 g_signal_connect_after(default_settings, "notify::gtk-xft-dpi",
6358 G_CALLBACK(settings_xft_dpi_changed_cb), this);
6360 // Widget signals
6361 g_signal_connect(mContainer, "map", G_CALLBACK(widget_map_cb), nullptr);
6362 g_signal_connect(mContainer, "unmap", G_CALLBACK(widget_unmap_cb), nullptr);
6363 g_signal_connect_after(mContainer, "size_allocate",
6364 G_CALLBACK(size_allocate_cb), nullptr);
6365 g_signal_connect(mContainer, "hierarchy-changed",
6366 G_CALLBACK(hierarchy_changed_cb), nullptr);
6367 g_signal_connect(mContainer, "notify::scale-factor",
6368 G_CALLBACK(scale_changed_cb), nullptr);
6369 // Initialize mHasMappedToplevel.
6370 hierarchy_changed_cb(GTK_WIDGET(mContainer), nullptr);
6371 // Expose, focus, key, and drag events are sent even to GTK_NO_WINDOW
6372 // widgets.
6373 g_signal_connect(G_OBJECT(mContainer), "draw", G_CALLBACK(expose_event_cb),
6374 nullptr);
6375 g_signal_connect(mContainer, "focus_in_event", G_CALLBACK(focus_in_event_cb),
6376 nullptr);
6377 g_signal_connect(mContainer, "focus_out_event",
6378 G_CALLBACK(focus_out_event_cb), nullptr);
6379 g_signal_connect(mContainer, "key_press_event",
6380 G_CALLBACK(key_press_event_cb), nullptr);
6381 g_signal_connect(mContainer, "key_release_event",
6382 G_CALLBACK(key_release_event_cb), nullptr);
6384 #ifdef MOZ_X11
6385 if (GdkIsX11Display()) {
6386 gtk_widget_set_double_buffered(GTK_WIDGET(mContainer), FALSE);
6388 #endif
6389 #ifdef MOZ_WAYLAND
6390 // Initialize the window specific VsyncSource early in order to avoid races
6391 // with BrowserParent::UpdateVsyncParentVsyncDispatcher().
6392 // Only use for toplevel windows for now, see bug 1619246.
6393 if (GdkIsWaylandDisplay() &&
6394 StaticPrefs::widget_wayland_vsync_enabled_AtStartup() &&
6395 IsTopLevelWindowType()) {
6396 mWaylandVsyncSource = new WaylandVsyncSource(this);
6397 mWaylandVsyncDispatcher = new VsyncDispatcher(mWaylandVsyncSource);
6398 LOG_VSYNC(" created WaylandVsyncSource");
6400 #endif
6402 // We create input contexts for all containers, except for
6403 // toplevel popup windows
6404 if (mWindowType != WindowType::Popup) {
6405 mIMContext = new IMContextWrapper(this);
6408 // These events are sent to the owning widget of the relevant window
6409 // and propagate up to the first widget that handles the events, so we
6410 // need only connect on mShell, if it exists, to catch events on its
6411 // window and windows of mContainer.
6412 g_signal_connect(mContainer, "enter-notify-event",
6413 G_CALLBACK(enter_notify_event_cb), nullptr);
6414 g_signal_connect(mContainer, "leave-notify-event",
6415 G_CALLBACK(leave_notify_event_cb), nullptr);
6416 g_signal_connect(mContainer, "motion-notify-event",
6417 G_CALLBACK(motion_notify_event_cb), nullptr);
6418 g_signal_connect(mContainer, "button-press-event",
6419 G_CALLBACK(button_press_event_cb), nullptr);
6420 g_signal_connect(mContainer, "button-release-event",
6421 G_CALLBACK(button_release_event_cb), nullptr);
6422 g_signal_connect(mContainer, "scroll-event", G_CALLBACK(scroll_event_cb),
6423 nullptr);
6424 if (gtk_check_version(3, 18, 0) == nullptr) {
6425 g_signal_connect(mContainer, "event", G_CALLBACK(generic_event_cb),
6426 nullptr);
6428 g_signal_connect(mContainer, "touch-event", G_CALLBACK(touch_event_cb),
6429 nullptr);
6431 LOG(" nsWindow type %d %s\n", int(mWindowType),
6432 mIsPIPWindow ? "PIP window" : "");
6433 LOG(" mShell %p (window %p) mContainer %p mGdkWindow %p XID 0x%lx\n", mShell,
6434 GetToplevelGdkWindow(), mContainer, mGdkWindow, GetX11Window());
6436 // Set default application name when it's empty.
6437 if (mGtkWindowAppName.IsEmpty()) {
6438 mGtkWindowAppName = gAppData->name;
6441 mCreated = true;
6442 return NS_OK;
6445 void nsWindow::RefreshWindowClass(void) {
6446 GdkWindow* gdkWindow = GetToplevelGdkWindow();
6447 if (!gdkWindow) {
6448 return;
6451 if (!mGtkWindowRoleName.IsEmpty()) {
6452 gdk_window_set_role(gdkWindow, mGtkWindowRoleName.get());
6455 #ifdef MOZ_X11
6456 if (GdkIsX11Display()) {
6457 XClassHint* class_hint = XAllocClassHint();
6458 if (!class_hint) {
6459 return;
6462 const char* res_name =
6463 !mGtkWindowAppName.IsEmpty() ? mGtkWindowAppName.get() : gAppData->name;
6465 const char* res_class = !mGtkWindowAppClass.IsEmpty()
6466 ? mGtkWindowAppClass.get()
6467 : gdk_get_program_class();
6469 if (!res_name || !res_class) {
6470 XFree(class_hint);
6471 return;
6474 class_hint->res_name = const_cast<char*>(res_name);
6475 class_hint->res_class = const_cast<char*>(res_class);
6477 // Can't use gtk_window_set_wmclass() for this; it prints
6478 // a warning & refuses to make the change.
6479 GdkDisplay* display = gdk_display_get_default();
6480 XSetClassHint(GDK_DISPLAY_XDISPLAY(display),
6481 gdk_x11_window_get_xid(gdkWindow), class_hint);
6482 XFree(class_hint);
6484 #endif /* MOZ_X11 */
6486 #ifdef MOZ_WAYLAND
6487 static auto sGdkWaylandWindowSetApplicationId =
6488 (void (*)(GdkWindow*, const char*))dlsym(
6489 RTLD_DEFAULT, "gdk_wayland_window_set_application_id");
6491 if (GdkIsWaylandDisplay() && sGdkWaylandWindowSetApplicationId &&
6492 !mGtkWindowAppClass.IsEmpty()) {
6493 sGdkWaylandWindowSetApplicationId(gdkWindow, mGtkWindowAppClass.get());
6495 #endif /* MOZ_WAYLAND */
6498 void nsWindow::SetWindowClass(const nsAString& xulWinType,
6499 const nsAString& xulWinClass,
6500 const nsAString& xulWinName) {
6501 if (!mShell) {
6502 return;
6505 // If window type attribute is set, parse it into name and role
6506 if (!xulWinType.IsEmpty()) {
6507 char* res_name = ToNewCString(xulWinType, mozilla::fallible);
6508 const char* role = nullptr;
6510 if (res_name) {
6511 // Parse res_name into a name and role. Characters other than
6512 // [A-Za-z0-9_-] are converted to '_'. Anything after the first
6513 // colon is assigned to role; if there's no colon, assign the
6514 // whole thing to both role and res_name.
6515 for (char* c = res_name; *c; c++) {
6516 if (':' == *c) {
6517 *c = 0;
6518 role = c + 1;
6519 } else if (!isascii(*c) ||
6520 (!isalnum(*c) && ('_' != *c) && ('-' != *c))) {
6521 *c = '_';
6524 res_name[0] = (char)toupper(res_name[0]);
6525 if (!role) role = res_name;
6527 mGtkWindowAppName = res_name;
6528 mGtkWindowRoleName = role;
6529 free(res_name);
6533 // If window class attribute is set, store it as app class
6534 // If this attribute is not set, reset app class to default
6535 if (!xulWinClass.IsEmpty()) {
6536 CopyUTF16toUTF8(xulWinClass, mGtkWindowAppClass);
6537 } else {
6538 mGtkWindowAppClass = nullptr;
6541 // If window class attribute is set, store it as app name
6542 // If both name and type are not set, reset app name to default
6543 if (!xulWinName.IsEmpty()) {
6544 CopyUTF16toUTF8(xulWinName, mGtkWindowAppName);
6545 } else if (xulWinType.IsEmpty()) {
6546 mGtkWindowAppClass = nullptr;
6549 RefreshWindowClass();
6552 nsAutoCString nsWindow::GetDebugTag() const {
6553 nsAutoCString tag;
6554 tag.AppendPrintf("[%p]", this);
6555 return tag;
6558 void nsWindow::NativeMoveResize(bool aMoved, bool aResized) {
6559 GdkPoint topLeft = [&] {
6560 auto target = mBounds.TopLeft();
6561 // gtk_window_move will undo the csd offset, but nothing else, so only add
6562 // the client offset if drawing to the csd titlebar.
6563 if (DrawsToCSDTitlebar()) {
6564 target += mClientOffset;
6566 return DevicePixelsToGdkPointRoundDown(target);
6567 }();
6568 GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mLastSizeRequest);
6570 LOG("nsWindow::NativeMoveResize move %d resize %d to %d,%d -> %d x %d\n",
6571 aMoved, aResized, topLeft.x, topLeft.y, size.width, size.height);
6573 if (aResized && !AreBoundsSane()) {
6574 LOG(" bounds are insane, hidding the window");
6575 // We have been resized but to incorrect size.
6576 // If someone has set this so that the needs show flag is false
6577 // and it needs to be hidden, update the flag and hide the
6578 // window. This flag will be cleared the next time someone
6579 // hides the window or shows it. It also prevents us from
6580 // calling NativeShow(false) excessively on the window which
6581 // causes unneeded X traffic.
6582 if (!mNeedsShow && mIsShown) {
6583 mNeedsShow = true;
6584 NativeShow(false);
6586 if (aMoved) {
6587 LOG(" moving to %d x %d", topLeft.x, topLeft.y);
6588 gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y);
6590 return;
6593 // Set position to hidden window on X11 may fail, so save the position
6594 // and move it when it's shown.
6595 if (aMoved && GdkIsX11Display() && IsPopup() &&
6596 !gtk_widget_get_visible(GTK_WIDGET(mShell))) {
6597 LOG(" store position of hidden popup window");
6598 mHiddenPopupPositioned = true;
6599 mPopupPosition = {topLeft.x, topLeft.y};
6602 if (IsWaylandPopup()) {
6603 NativeMoveResizeWaylandPopup(aMoved, aResized);
6604 } else {
6605 // x and y give the position of the window manager frame top-left.
6606 if (aMoved) {
6607 gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y);
6609 if (aResized) {
6610 gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
6611 if (mIsDragPopup) {
6612 // DND window is placed inside container so we need to make hard size
6613 // request to ensure parent container is resized too.
6614 gtk_widget_set_size_request(GTK_WIDGET(mShell), size.width,
6615 size.height);
6620 if (aResized) {
6621 // Recompute the input region, in case the window grew or shrunk.
6622 SetInputRegion(mInputRegion);
6625 // Does it need to be shown because bounds were previously insane?
6626 if (mNeedsShow && mIsShown && aResized) {
6627 NativeShow(true);
6631 // We pause compositor to avoid rendering of obsoleted remote content which
6632 // produces flickering.
6633 // Re-enable compositor again when remote content is updated or
6634 // timeout happens.
6636 // Define maximal compositor pause when it's paused to avoid flickering,
6637 // in milliseconds.
6638 #define COMPOSITOR_PAUSE_TIMEOUT (1000)
6640 void nsWindow::PauseCompositorFlickering() {
6641 bool pauseCompositor = IsTopLevelWindowType() &&
6642 mCompositorState == COMPOSITOR_ENABLED &&
6643 mCompositorWidgetDelegate && !mIsDestroyed;
6644 if (!pauseCompositor) {
6645 return;
6648 LOG("nsWindow::PauseCompositorFlickering()");
6650 MozClearHandleID(mCompositorPauseTimeoutID, g_source_remove);
6652 CompositorBridgeChild* remoteRenderer = GetRemoteRenderer();
6653 if (remoteRenderer) {
6654 remoteRenderer->SendPause();
6655 mCompositorState = COMPOSITOR_PAUSED_FLICKERING;
6656 mCompositorPauseTimeoutID = (int)g_timeout_add(
6657 COMPOSITOR_PAUSE_TIMEOUT,
6658 [](void* data) -> gint {
6659 nsWindow* window = static_cast<nsWindow*>(data);
6660 if (!window->IsDestroyed()) {
6661 window->ResumeCompositorFlickering();
6663 return true;
6665 this);
6669 bool nsWindow::IsWaitingForCompositorResume() {
6670 return mCompositorState == COMPOSITOR_PAUSED_FLICKERING;
6673 void nsWindow::ResumeCompositorFlickering() {
6674 MOZ_RELEASE_ASSERT(NS_IsMainThread());
6676 LOG("nsWindow::ResumeCompositorFlickering()\n");
6678 if (mIsDestroyed || !IsWaitingForCompositorResume()) {
6679 LOG(" early quit\n");
6680 return;
6683 MozClearHandleID(mCompositorPauseTimeoutID, g_source_remove);
6685 // mCompositorWidgetDelegate can be deleted during timeout.
6686 // In such case just flip compositor back to enabled and let
6687 // SetCompositorWidgetDelegate() or Map event resume it.
6688 if (!mCompositorWidgetDelegate) {
6689 mCompositorState = COMPOSITOR_ENABLED;
6690 return;
6693 ResumeCompositorImpl();
6696 void nsWindow::ResumeCompositorFromCompositorThread() {
6697 nsCOMPtr<nsIRunnable> event =
6698 NewRunnableMethod("nsWindow::ResumeCompositorFlickering", this,
6699 &nsWindow::ResumeCompositorFlickering);
6700 NS_DispatchToMainThread(event.forget());
6703 void nsWindow::ResumeCompositorImpl() {
6704 MOZ_RELEASE_ASSERT(NS_IsMainThread());
6706 LOG("nsWindow::ResumeCompositorImpl()\n");
6708 MOZ_DIAGNOSTIC_ASSERT(mCompositorWidgetDelegate);
6709 mCompositorWidgetDelegate->EnableRendering(GetX11Window(), GetShapedState());
6711 // As WaylandStartVsync needs mCompositorWidgetDelegate this is the right
6712 // time to start it.
6713 WaylandStartVsync();
6715 CompositorBridgeChild* remoteRenderer = GetRemoteRenderer();
6716 MOZ_RELEASE_ASSERT(remoteRenderer);
6717 remoteRenderer->SendResume();
6718 remoteRenderer->SendForcePresent(wr::RenderReasons::WIDGET);
6719 mCompositorState = COMPOSITOR_ENABLED;
6722 void nsWindow::WaylandStartVsync() {
6723 #ifdef MOZ_WAYLAND
6724 if (!mWaylandVsyncSource) {
6725 return;
6728 LOG_VSYNC("nsWindow::WaylandStartVsync");
6730 MOZ_DIAGNOSTIC_ASSERT(mCompositorWidgetDelegate);
6731 if (mCompositorWidgetDelegate->AsGtkCompositorWidget() &&
6732 mCompositorWidgetDelegate->AsGtkCompositorWidget()
6733 ->GetNativeLayerRoot()) {
6734 LOG_VSYNC(" use source NativeLayerRootWayland");
6735 mWaylandVsyncSource->MaybeUpdateSource(
6736 mCompositorWidgetDelegate->AsGtkCompositorWidget()
6737 ->GetNativeLayerRoot()
6738 ->AsNativeLayerRootWayland());
6739 } else {
6740 LOG_VSYNC(" use source mContainer");
6741 mWaylandVsyncSource->MaybeUpdateSource(mContainer);
6744 mWaylandVsyncSource->EnableMonitor();
6745 #endif
6748 void nsWindow::WaylandStopVsync() {
6749 #ifdef MOZ_WAYLAND
6750 if (!mWaylandVsyncSource) {
6751 return;
6754 LOG_VSYNC("nsWindow::WaylandStopVsync");
6756 // The widget is going to be hidden, so clear the surface of our
6757 // vsync source.
6758 mWaylandVsyncSource->DisableMonitor();
6759 mWaylandVsyncSource->MaybeUpdateSource(nullptr);
6760 #endif
6763 void nsWindow::NativeShow(bool aAction) {
6764 if (aAction) {
6765 // unset our flag now that our window has been shown
6766 mNeedsShow = true;
6767 auto removeShow = MakeScopeExit([&] { mNeedsShow = false; });
6769 LOG("nsWindow::NativeShow show\n");
6771 if (IsWaylandPopup()) {
6772 mPopupClosed = false;
6773 if (WaylandPopupConfigure()) {
6774 AddWindowToPopupHierarchy();
6775 UpdateWaylandPopupHierarchy();
6776 if (mPopupClosed) {
6777 return;
6781 // Set up usertime/startupID metadata for the created window.
6782 // On X11 we use gtk_window_set_startup_id() so we need to call it
6783 // before show.
6784 if (GdkIsX11Display()) {
6785 SetUserTimeAndStartupTokenForActivatedWindow();
6787 if (GdkIsWaylandDisplay()) {
6788 if (IsWaylandPopup()) {
6789 ShowWaylandPopupWindow();
6790 } else {
6791 ShowWaylandToplevelWindow();
6793 } else {
6794 LOG(" calling gtk_widget_show(mShell)\n");
6795 gtk_widget_show(mShell);
6797 if (GdkIsWaylandDisplay()) {
6798 SetUserTimeAndStartupTokenForActivatedWindow();
6799 #ifdef MOZ_WAYLAND
6800 auto token = std::move(mWindowActivationTokenFromEnv);
6801 if (!token.IsEmpty()) {
6802 FocusWaylandWindow(token.get());
6804 #endif
6806 if (mHiddenPopupPositioned && IsPopup()) {
6807 LOG(" re-position hidden popup window");
6808 gtk_window_move(GTK_WINDOW(mShell), mPopupPosition.x, mPopupPosition.y);
6809 mHiddenPopupPositioned = false;
6811 } else {
6812 LOG("nsWindow::NativeShow hide\n");
6813 if (GdkIsWaylandDisplay()) {
6814 if (IsWaylandPopup()) {
6815 // We can't close tracked popups directly as they may have visible
6816 // child popups. Just mark is as closed and let
6817 // UpdateWaylandPopupHierarchy() do the job.
6818 if (IsInPopupHierarchy()) {
6819 WaylandPopupMarkAsClosed();
6820 UpdateWaylandPopupHierarchy();
6821 } else {
6822 // Close untracked popups directly.
6823 HideWaylandPopupWindow(/* aTemporaryHide */ false,
6824 /* aRemoveFromPopupList */ true);
6826 } else {
6827 HideWaylandToplevelWindow();
6829 } else {
6830 // Workaround window freezes on GTK versions before 3.21.2 by
6831 // ensuring that configure events get dispatched to windows before
6832 // they are unmapped. See bug 1225044.
6833 if (gtk_check_version(3, 21, 2) != nullptr && mPendingConfigures > 0) {
6834 GtkAllocation allocation;
6835 gtk_widget_get_allocation(GTK_WIDGET(mShell), &allocation);
6837 GdkEventConfigure event;
6838 PodZero(&event);
6839 event.type = GDK_CONFIGURE;
6840 event.window = mGdkWindow;
6841 event.send_event = TRUE;
6842 event.x = allocation.x;
6843 event.y = allocation.y;
6844 event.width = allocation.width;
6845 event.height = allocation.height;
6847 auto* shellClass = GTK_WIDGET_GET_CLASS(mShell);
6848 for (unsigned int i = 0; i < mPendingConfigures; i++) {
6849 Unused << shellClass->configure_event(mShell, &event);
6851 mPendingConfigures = 0;
6853 gtk_widget_hide(mShell);
6855 ClearTransparencyBitmap(); // Release some resources
6860 void nsWindow::SetHasMappedToplevel(bool aState) {
6861 LOG("nsWindow::SetHasMappedToplevel(%d)", aState);
6862 if (aState == mHasMappedToplevel) {
6863 return;
6865 // Even when aState == mHasMappedToplevel (as when this method is called
6866 // from Show()), child windows need to have their state checked, so don't
6867 // return early.
6868 mHasMappedToplevel = aState;
6869 if (aState && mNeedsToRetryCapturingMouse) {
6870 CaptureRollupEvents(true);
6871 MOZ_ASSERT(!mNeedsToRetryCapturingMouse);
6875 LayoutDeviceIntSize nsWindow::GetSafeWindowSize(LayoutDeviceIntSize aSize) {
6876 // The X protocol uses CARD32 for window sizes, but the server (1.11.3)
6877 // reads it as CARD16. Sizes of pixmaps, used for drawing, are (unsigned)
6878 // CARD16 in the protocol, but the server's ProcCreatePixmap returns
6879 // BadAlloc if dimensions cannot be represented by signed shorts.
6880 // Because we are creating Cairo surfaces to represent window buffers,
6881 // we also must ensure that the window can fit in a Cairo surface.
6882 LayoutDeviceIntSize result = aSize;
6883 int32_t maxSize = 32767;
6884 if (mWindowRenderer && mWindowRenderer->AsKnowsCompositor()) {
6885 maxSize = std::min(
6886 maxSize, mWindowRenderer->AsKnowsCompositor()->GetMaxTextureSize());
6888 if (result.width > maxSize) {
6889 result.width = maxSize;
6891 if (result.height > maxSize) {
6892 result.height = maxSize;
6894 return result;
6897 void nsWindow::SetTransparencyMode(TransparencyMode aMode) {
6898 bool isTransparent = aMode == TransparencyMode::Transparent;
6900 if (mIsTransparent == isTransparent) {
6901 return;
6904 if (mWindowType != WindowType::Popup) {
6905 // https://bugzilla.mozilla.org/show_bug.cgi?id=1344839 reported
6906 // problems cleaning the layer manager for toplevel windows.
6907 // Ignore the request so as to workaround that.
6908 // mIsTransparent is set in Create() if transparency may be required.
6909 if (isTransparent) {
6910 NS_WARNING("Transparent mode not supported on non-popup windows.");
6912 return;
6915 if (!isTransparent) {
6916 ClearTransparencyBitmap();
6917 } // else the new default alpha values are "all 1", so we don't
6918 // need to change anything yet
6920 mIsTransparent = isTransparent;
6922 if (!mHasAlphaVisual) {
6923 // The choice of layer manager depends on
6924 // GtkCompositorWidgetInitData::Shaped(), which will need to change, so
6925 // clean out the old layer manager.
6926 DestroyLayerManager();
6930 TransparencyMode nsWindow::GetTransparencyMode() {
6931 return mIsTransparent ? TransparencyMode::Transparent
6932 : TransparencyMode::Opaque;
6935 gint nsWindow::GetInputRegionMarginInGdkCoords() {
6936 return DevicePixelsToGdkCoordRoundDown(mInputRegion.mMargin);
6939 void nsWindow::SetInputRegion(const InputRegion& aInputRegion) {
6940 mInputRegion = aInputRegion;
6942 GdkWindow* window = GetToplevelGdkWindow();
6943 if (!window) {
6944 return;
6947 LOG("nsWindow::SetInputRegion(%d, %d)", aInputRegion.mFullyTransparent,
6948 int(aInputRegion.mMargin));
6950 cairo_rectangle_int_t rect = {0, 0, 0, 0};
6951 cairo_region_t* region = nullptr;
6952 auto releaseRegion = MakeScopeExit([&] {
6953 if (region) {
6954 cairo_region_destroy(region);
6958 if (aInputRegion.mFullyTransparent) {
6959 region = cairo_region_create_rectangle(&rect);
6960 } else if (aInputRegion.mMargin != 0) {
6961 LayoutDeviceIntRect inputRegion(LayoutDeviceIntPoint(), mLastSizeRequest);
6962 inputRegion.Deflate(aInputRegion.mMargin);
6963 GdkRectangle gdkRect = DevicePixelsToGdkRectRoundOut(inputRegion);
6964 rect = {gdkRect.x, gdkRect.y, gdkRect.width, gdkRect.height};
6965 region = cairo_region_create_rectangle(&rect);
6968 gdk_window_input_shape_combine_region(window, region, 0, 0);
6970 // On Wayland gdk_window_input_shape_combine_region() call is cached and
6971 // applied to underlying wl_surface when GdkWindow is repainted.
6972 // Force repaint of GdkWindow to apply the change immediately.
6973 if (GdkIsWaylandDisplay()) {
6974 gdk_window_invalidate_rect(window, nullptr, false);
6978 // For setting the draggable titlebar region from CSS
6979 // with -moz-window-dragging: drag.
6980 void nsWindow::UpdateWindowDraggingRegion(
6981 const LayoutDeviceIntRegion& aRegion) {
6982 if (mDraggableRegion != aRegion) {
6983 mDraggableRegion = aRegion;
6987 #ifdef MOZ_ENABLE_DBUS
6988 void nsWindow::SetDBusMenuBar(
6989 RefPtr<mozilla::widget::DBusMenuBar> aDbusMenuBar) {
6990 mDBusMenuBar = std::move(aDbusMenuBar);
6992 #endif
6994 LayoutDeviceIntCoord nsWindow::GetTitlebarRadius() {
6995 MOZ_RELEASE_ASSERT(NS_IsMainThread());
6996 int32_t cssCoord = LookAndFeel::GetInt(LookAndFeel::IntID::TitlebarRadius);
6997 return GdkCoordToDevicePixels(cssCoord);
7000 // See subtract_corners_from_region() at gtk/gtkwindow.c
7001 // We need to subtract corners from toplevel window opaque region
7002 // to draw transparent corners of default Gtk titlebar.
7003 // Both implementations (cairo_region_t and wl_region) needs to be synced.
7004 static void SubtractTitlebarCorners(cairo_region_t* aRegion, int aX, int aY,
7005 int aWindowWidth, int aWindowHeight,
7006 int aTitlebarRadius) {
7007 if (!aTitlebarRadius) {
7008 return;
7010 cairo_rectangle_int_t rect = {aX, aY, aTitlebarRadius, aTitlebarRadius};
7011 cairo_region_subtract_rectangle(aRegion, &rect);
7012 rect = {
7013 aX + aWindowWidth - aTitlebarRadius,
7015 aTitlebarRadius,
7016 aTitlebarRadius,
7018 cairo_region_subtract_rectangle(aRegion, &rect);
7019 rect = {
7021 aY + aWindowHeight - aTitlebarRadius,
7022 aTitlebarRadius,
7023 aTitlebarRadius,
7025 cairo_region_subtract_rectangle(aRegion, &rect);
7026 rect = {
7027 aX + aWindowWidth - aTitlebarRadius,
7028 aY + aWindowHeight - aTitlebarRadius,
7029 aTitlebarRadius,
7030 aTitlebarRadius,
7032 cairo_region_subtract_rectangle(aRegion, &rect);
7035 void nsWindow::UpdateTopLevelOpaqueRegion() {
7036 if (!mCompositedScreen) {
7037 return;
7040 GdkWindow* window = GetToplevelGdkWindow();
7041 if (!window) {
7042 return;
7044 MOZ_ASSERT(gdk_window_get_window_type(window) == GDK_WINDOW_TOPLEVEL);
7046 int x = 0;
7047 int y = 0;
7049 gdk_window_get_position(mGdkWindow, &x, &y);
7051 int width = DevicePixelsToGdkCoordRoundDown(mBounds.width);
7052 int height = DevicePixelsToGdkCoordRoundDown(mBounds.height);
7054 cairo_region_t* region = cairo_region_create();
7055 cairo_rectangle_int_t rect = {x, y, width, height};
7056 cairo_region_union_rectangle(region, &rect);
7058 // TODO: We actually could get a proper opaque region from layout, see
7059 // nsIWidget::UpdateOpaqueRegion. This could simplify titlebar drawing.
7060 int radius = DoDrawTilebarCorners() ? int(GetTitlebarRadius()) : 0;
7061 SubtractTitlebarCorners(region, x, y, width, height, radius);
7063 gdk_window_set_opaque_region(window, region);
7065 cairo_region_destroy(region);
7067 #ifdef MOZ_WAYLAND
7068 if (GdkIsWaylandDisplay()) {
7069 moz_container_wayland_update_opaque_region(mContainer, radius);
7071 #endif
7073 SetTitlebarRect();
7076 bool nsWindow::IsChromeWindowTitlebar() {
7077 return mDrawInTitlebar && !mIsPIPWindow &&
7078 mWindowType == WindowType::TopLevel;
7081 bool nsWindow::DoDrawTilebarCorners() {
7082 return IsChromeWindowTitlebar() && mSizeMode == nsSizeMode_Normal &&
7083 !mIsTiled;
7086 void nsWindow::ResizeTransparencyBitmap() {
7087 if (!mTransparencyBitmap) {
7088 return;
7091 if (mBounds.width == mTransparencyBitmapWidth &&
7092 mBounds.height == mTransparencyBitmapHeight) {
7093 return;
7096 int32_t newRowBytes = GetBitmapStride(mBounds.width);
7097 int32_t newSize = newRowBytes * mBounds.height;
7098 auto* newBits = new gchar[newSize];
7099 // fill new mask with "transparent", first
7100 memset(newBits, 0, newSize);
7102 // Now copy the intersection of the old and new areas into the new mask
7103 int32_t copyWidth = std::min(mBounds.width, mTransparencyBitmapWidth);
7104 int32_t copyHeight = std::min(mBounds.height, mTransparencyBitmapHeight);
7105 int32_t oldRowBytes = GetBitmapStride(mTransparencyBitmapWidth);
7106 int32_t copyBytes = GetBitmapStride(copyWidth);
7108 int32_t i;
7109 gchar* fromPtr = mTransparencyBitmap;
7110 gchar* toPtr = newBits;
7111 for (i = 0; i < copyHeight; i++) {
7112 memcpy(toPtr, fromPtr, copyBytes);
7113 fromPtr += oldRowBytes;
7114 toPtr += newRowBytes;
7117 delete[] mTransparencyBitmap;
7118 mTransparencyBitmap = newBits;
7119 mTransparencyBitmapWidth = mBounds.width;
7120 mTransparencyBitmapHeight = mBounds.height;
7123 static bool ChangedMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
7124 int32_t aMaskHeight, const nsIntRect& aRect,
7125 uint8_t* aAlphas, int32_t aStride) {
7126 int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
7127 int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
7128 for (y = aRect.y; y < yMax; y++) {
7129 gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
7130 uint8_t* alphas = aAlphas;
7131 for (x = aRect.x; x < xMax; x++) {
7132 bool newBit = *alphas > 0x7f;
7133 alphas++;
7135 gchar maskByte = maskBytes[x >> 3];
7136 bool maskBit = (maskByte & (1 << (x & 7))) != 0;
7138 if (maskBit != newBit) {
7139 return true;
7142 aAlphas += aStride;
7145 return false;
7148 static void UpdateMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
7149 int32_t aMaskHeight, const nsIntRect& aRect,
7150 uint8_t* aAlphas, int32_t aStride) {
7151 int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
7152 int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
7153 for (y = aRect.y; y < yMax; y++) {
7154 gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
7155 uint8_t* alphas = aAlphas;
7156 for (x = aRect.x; x < xMax; x++) {
7157 bool newBit = *alphas > 0x7f;
7158 alphas++;
7160 gchar mask = 1 << (x & 7);
7161 gchar maskByte = maskBytes[x >> 3];
7162 // Note: '-newBit' turns 0 into 00...00 and 1 into 11...11
7163 maskBytes[x >> 3] = (maskByte & ~mask) | (-newBit & mask);
7165 aAlphas += aStride;
7169 void nsWindow::ApplyTransparencyBitmap() {
7170 #ifdef MOZ_X11
7171 // We use X11 calls where possible, because GDK handles expose events
7172 // for shaped windows in a way that's incompatible with us (Bug 635903).
7173 // It doesn't occur when the shapes are set through X.
7174 Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
7175 Window xDrawable = GDK_WINDOW_XID(mGdkWindow);
7176 Pixmap maskPixmap = XCreateBitmapFromData(
7177 xDisplay, xDrawable, mTransparencyBitmap, mTransparencyBitmapWidth,
7178 mTransparencyBitmapHeight);
7179 XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
7180 ShapeSet);
7181 XFreePixmap(xDisplay, maskPixmap);
7182 #endif // MOZ_X11
7185 void nsWindow::ClearTransparencyBitmap() {
7186 if (!mTransparencyBitmap) {
7187 return;
7190 delete[] mTransparencyBitmap;
7191 mTransparencyBitmap = nullptr;
7192 mTransparencyBitmapWidth = 0;
7193 mTransparencyBitmapHeight = 0;
7195 if (!mShell) {
7196 return;
7199 #ifdef MOZ_X11
7200 if (MOZ_UNLIKELY(!mGdkWindow)) {
7201 return;
7204 Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
7205 Window xWindow = gdk_x11_window_get_xid(mGdkWindow);
7207 XShapeCombineMask(xDisplay, xWindow, ShapeBounding, 0, 0, X11None, ShapeSet);
7208 #endif
7211 nsresult nsWindow::UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
7212 uint8_t* aAlphas,
7213 int32_t aStride) {
7214 NS_ASSERTION(mIsTransparent, "Window is not transparent");
7215 NS_ASSERTION(!mTransparencyBitmapForTitlebar,
7216 "Transparency bitmap is already used for titlebar rendering");
7218 if (mTransparencyBitmap == nullptr) {
7219 int32_t size = GetBitmapStride(mBounds.width) * mBounds.height;
7220 mTransparencyBitmap = new gchar[size];
7221 memset(mTransparencyBitmap, 255, size);
7222 mTransparencyBitmapWidth = mBounds.width;
7223 mTransparencyBitmapHeight = mBounds.height;
7224 } else {
7225 ResizeTransparencyBitmap();
7228 nsIntRect rect;
7229 rect.IntersectRect(aRect, nsIntRect(0, 0, mBounds.width, mBounds.height));
7231 if (!ChangedMaskBits(mTransparencyBitmap, mBounds.width, mBounds.height, rect,
7232 aAlphas, aStride)) {
7233 // skip the expensive stuff if the mask bits haven't changed; hopefully
7234 // this is the common case
7235 return NS_OK;
7238 UpdateMaskBits(mTransparencyBitmap, mBounds.width, mBounds.height, rect,
7239 aAlphas, aStride);
7241 if (!mNeedsShow) {
7242 ApplyTransparencyBitmap();
7244 return NS_OK;
7247 #define TITLEBAR_HEIGHT 10
7249 void nsWindow::SetTitlebarRect() {
7250 MutexAutoLock lock(mTitlebarRectMutex);
7252 if (!mGdkWindow || !DoDrawTilebarCorners()) {
7253 mTitlebarRect = LayoutDeviceIntRect();
7254 return;
7256 mTitlebarRect = LayoutDeviceIntRect(0, 0, mBounds.width,
7257 GdkCeiledScaleFactor() * TITLEBAR_HEIGHT);
7260 LayoutDeviceIntRect nsWindow::GetTitlebarRect() {
7261 MutexAutoLock lock(mTitlebarRectMutex);
7262 return mTitlebarRect;
7265 void nsWindow::UpdateTitlebarTransparencyBitmap() {
7266 NS_ASSERTION(mTransparencyBitmapForTitlebar,
7267 "Transparency bitmap is already used to draw window shape");
7269 if (!mGdkWindow || !mDrawInTitlebar ||
7270 (mBounds.width == mTransparencyBitmapWidth &&
7271 mBounds.height == mTransparencyBitmapHeight)) {
7272 return;
7275 bool maskCreate =
7276 !mTransparencyBitmap || mBounds.width > mTransparencyBitmapWidth;
7278 bool maskUpdate =
7279 !mTransparencyBitmap || mBounds.width != mTransparencyBitmapWidth;
7281 LayoutDeviceIntCoord radius = GetTitlebarRadius();
7282 if (maskCreate) {
7283 delete[] mTransparencyBitmap;
7284 int32_t size = GetBitmapStride(mBounds.width) * radius;
7285 mTransparencyBitmap = new gchar[size];
7286 mTransparencyBitmapWidth = mBounds.width;
7287 } else {
7288 mTransparencyBitmapWidth = mBounds.width;
7290 mTransparencyBitmapHeight = mBounds.height;
7292 if (maskUpdate) {
7293 cairo_surface_t* surface = cairo_image_surface_create(
7294 CAIRO_FORMAT_A8, mTransparencyBitmapWidth, radius);
7295 if (!surface) {
7296 return;
7299 cairo_t* cr = cairo_create(surface);
7301 GtkWidgetState state;
7302 memset((void*)&state, 0, sizeof(state));
7303 GdkRectangle rect = {0, 0, mTransparencyBitmapWidth, radius};
7305 moz_gtk_widget_paint(MOZ_GTK_HEADER_BAR, cr, &rect, &state, 0,
7306 GTK_TEXT_DIR_NONE);
7308 cairo_destroy(cr);
7309 cairo_surface_mark_dirty(surface);
7310 cairo_surface_flush(surface);
7312 UpdateMaskBits(mTransparencyBitmap, mTransparencyBitmapWidth, radius,
7313 nsIntRect(0, 0, mTransparencyBitmapWidth, radius),
7314 cairo_image_surface_get_data(surface),
7315 cairo_format_stride_for_width(CAIRO_FORMAT_A8,
7316 mTransparencyBitmapWidth));
7318 cairo_surface_destroy(surface);
7321 #ifdef MOZ_X11
7322 if (!mNeedsShow) {
7323 Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
7324 Window xDrawable = GDK_WINDOW_XID(mGdkWindow);
7326 Pixmap maskPixmap =
7327 XCreateBitmapFromData(xDisplay, xDrawable, mTransparencyBitmap,
7328 mTransparencyBitmapWidth, radius);
7330 XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
7331 ShapeSet);
7333 if (mTransparencyBitmapHeight > radius) {
7334 XRectangle rect = {0, 0, (unsigned short)mTransparencyBitmapWidth,
7335 (unsigned short)(mTransparencyBitmapHeight - radius)};
7336 XShapeCombineRectangles(xDisplay, xDrawable, ShapeBounding, 0, radius,
7337 &rect, 1, ShapeUnion, 0);
7340 XFreePixmap(xDisplay, maskPixmap);
7342 #endif
7345 GtkWidget* nsWindow::GetToplevelWidget() const { return mShell; }
7347 GdkWindow* nsWindow::GetToplevelGdkWindow() const {
7348 return gtk_widget_get_window(mShell);
7351 nsWindow* nsWindow::GetContainerWindow() const {
7352 GtkWidget* owningWidget = GTK_WIDGET(mContainer);
7353 if (!owningWidget) {
7354 return nullptr;
7357 nsWindow* window = get_window_for_gtk_widget(owningWidget);
7358 NS_ASSERTION(window, "No nsWindow for container widget");
7359 return window;
7362 void nsWindow::SetUrgencyHint(GtkWidget* top_window, bool state) {
7363 LOG(" nsWindow::SetUrgencyHint widget %p\n", top_window);
7364 if (!top_window) {
7365 return;
7367 GdkWindow* window = gtk_widget_get_window(top_window);
7368 if (!window) {
7369 return;
7371 // TODO: Use xdg-activation on Wayland?
7372 gdk_window_set_urgency_hint(window, state);
7375 void nsWindow::SetDefaultIcon(void) { SetIcon(u"default"_ns); }
7377 gint nsWindow::ConvertBorderStyles(BorderStyle aStyle) {
7378 gint w = 0;
7380 if (aStyle == BorderStyle::Default) {
7381 return -1;
7384 // note that we don't handle BorderStyle::Close yet
7385 if (aStyle & BorderStyle::All) w |= GDK_DECOR_ALL;
7386 if (aStyle & BorderStyle::Border) w |= GDK_DECOR_BORDER;
7387 if (aStyle & BorderStyle::ResizeH) w |= GDK_DECOR_RESIZEH;
7388 if (aStyle & BorderStyle::Title) w |= GDK_DECOR_TITLE;
7389 if (aStyle & BorderStyle::Menu) w |= GDK_DECOR_MENU;
7390 if (aStyle & BorderStyle::Minimize) w |= GDK_DECOR_MINIMIZE;
7391 if (aStyle & BorderStyle::Maximize) w |= GDK_DECOR_MAXIMIZE;
7393 return w;
7396 class FullscreenTransitionWindow final : public nsISupports {
7397 public:
7398 NS_DECL_ISUPPORTS
7400 explicit FullscreenTransitionWindow(GtkWidget* aWidget);
7402 GtkWidget* mWindow;
7404 private:
7405 ~FullscreenTransitionWindow();
7408 NS_IMPL_ISUPPORTS0(FullscreenTransitionWindow)
7410 FullscreenTransitionWindow::FullscreenTransitionWindow(GtkWidget* aWidget) {
7411 mWindow = gtk_window_new(GTK_WINDOW_POPUP);
7412 GtkWindow* gtkWin = GTK_WINDOW(mWindow);
7414 gtk_window_set_type_hint(gtkWin, GDK_WINDOW_TYPE_HINT_SPLASHSCREEN);
7415 GtkWindowSetTransientFor(gtkWin, GTK_WINDOW(aWidget));
7416 gtk_window_set_decorated(gtkWin, false);
7418 GdkWindow* gdkWin = gtk_widget_get_window(aWidget);
7419 GdkScreen* screen = gtk_widget_get_screen(aWidget);
7420 gint monitorNum = gdk_screen_get_monitor_at_window(screen, gdkWin);
7421 GdkRectangle monitorRect;
7422 gdk_screen_get_monitor_geometry(screen, monitorNum, &monitorRect);
7423 gtk_window_set_screen(gtkWin, screen);
7424 gtk_window_move(gtkWin, monitorRect.x, monitorRect.y);
7425 MOZ_ASSERT(monitorRect.width > 0 && monitorRect.height > 0,
7426 "Can't resize window smaller than 1x1.");
7427 gtk_window_resize(gtkWin, monitorRect.width, monitorRect.height);
7429 GdkRGBA bgColor;
7430 bgColor.red = bgColor.green = bgColor.blue = 0.0;
7431 bgColor.alpha = 1.0;
7432 gtk_widget_override_background_color(mWindow, GTK_STATE_FLAG_NORMAL,
7433 &bgColor);
7435 gtk_widget_set_opacity(mWindow, 0.0);
7436 gtk_widget_show(mWindow);
7439 FullscreenTransitionWindow::~FullscreenTransitionWindow() {
7440 gtk_widget_destroy(mWindow);
7443 class FullscreenTransitionData {
7444 public:
7445 FullscreenTransitionData(nsIWidget::FullscreenTransitionStage aStage,
7446 uint16_t aDuration, nsIRunnable* aCallback,
7447 FullscreenTransitionWindow* aWindow)
7448 : mStage(aStage),
7449 mStartTime(TimeStamp::Now()),
7450 mDuration(TimeDuration::FromMilliseconds(aDuration)),
7451 mCallback(aCallback),
7452 mWindow(aWindow) {}
7454 static const guint sInterval = 1000 / 30; // 30fps
7455 static gboolean TimeoutCallback(gpointer aData);
7457 private:
7458 nsIWidget::FullscreenTransitionStage mStage;
7459 TimeStamp mStartTime;
7460 TimeDuration mDuration;
7461 nsCOMPtr<nsIRunnable> mCallback;
7462 RefPtr<FullscreenTransitionWindow> mWindow;
7465 /* static */
7466 gboolean FullscreenTransitionData::TimeoutCallback(gpointer aData) {
7467 bool finishing = false;
7468 auto* data = static_cast<FullscreenTransitionData*>(aData);
7469 gdouble opacity = (TimeStamp::Now() - data->mStartTime) / data->mDuration;
7470 if (opacity >= 1.0) {
7471 opacity = 1.0;
7472 finishing = true;
7474 if (data->mStage == nsIWidget::eAfterFullscreenToggle) {
7475 opacity = 1.0 - opacity;
7477 gtk_widget_set_opacity(data->mWindow->mWindow, opacity);
7479 if (!finishing) {
7480 return TRUE;
7482 NS_DispatchToMainThread(data->mCallback.forget());
7483 delete data;
7484 return FALSE;
7487 /* virtual */
7488 bool nsWindow::PrepareForFullscreenTransition(nsISupports** aData) {
7489 if (!mCompositedScreen) {
7490 return false;
7492 *aData = do_AddRef(new FullscreenTransitionWindow(mShell)).take();
7493 return true;
7496 /* virtual */
7497 void nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
7498 uint16_t aDuration,
7499 nsISupports* aData,
7500 nsIRunnable* aCallback) {
7501 auto* data = static_cast<FullscreenTransitionWindow*>(aData);
7502 // This will be released at the end of the last timeout callback for it.
7503 auto* transitionData =
7504 new FullscreenTransitionData(aStage, aDuration, aCallback, data);
7505 g_timeout_add_full(G_PRIORITY_HIGH, FullscreenTransitionData::sInterval,
7506 FullscreenTransitionData::TimeoutCallback, transitionData,
7507 nullptr);
7510 already_AddRefed<widget::Screen> nsWindow::GetWidgetScreen() {
7511 // Wayland can read screen directly
7512 if (GdkIsWaylandDisplay()) {
7513 if (RefPtr<Screen> screen = ScreenHelperGTK::GetScreenForWindow(this)) {
7514 return screen.forget();
7518 // GetScreenBounds() is slow for the GTK port so we override and use
7519 // mBounds directly.
7520 ScreenManager& screenManager = ScreenManager::GetSingleton();
7521 LayoutDeviceIntRect bounds = mBounds;
7522 DesktopIntRect deskBounds = RoundedToInt(bounds / GetDesktopToDeviceScale());
7523 return screenManager.ScreenForRect(deskBounds);
7526 RefPtr<VsyncDispatcher> nsWindow::GetVsyncDispatcher() {
7527 #ifdef MOZ_WAYLAND
7528 if (mWaylandVsyncDispatcher) {
7529 return mWaylandVsyncDispatcher;
7531 #endif
7532 return nullptr;
7535 bool nsWindow::SynchronouslyRepaintOnResize() {
7536 if (GdkIsWaylandDisplay()) {
7537 // See Bug 1734368
7538 // Don't request synchronous repaint on HW accelerated backend - mesa can be
7539 // deadlocked when it's missing back buffer and main event loop is blocked.
7540 return false;
7543 // default is synced repaint.
7544 return true;
7547 void nsWindow::KioskLockOnMonitor() {
7548 // Available as of GTK 3.18+
7549 static auto sGdkWindowFullscreenOnMonitor =
7550 (void (*)(GdkWindow* window, gint monitor))dlsym(
7551 RTLD_DEFAULT, "gdk_window_fullscreen_on_monitor");
7553 if (!sGdkWindowFullscreenOnMonitor) {
7554 return;
7557 int monitor = mKioskMonitor.value();
7558 if (monitor < 0 || monitor >= ScreenHelperGTK::GetMonitorCount()) {
7559 LOG("nsWindow::KioskLockOnMonitor() wrong monitor number! (%d)\n", monitor);
7560 return;
7563 LOG("nsWindow::KioskLockOnMonitor() locked on %d\n", monitor);
7564 sGdkWindowFullscreenOnMonitor(GetToplevelGdkWindow(), monitor);
7567 static bool IsFullscreenSupported(GtkWidget* aShell) {
7568 #ifdef MOZ_X11
7569 GdkScreen* screen = gtk_widget_get_screen(aShell);
7570 GdkAtom atom = gdk_atom_intern("_NET_WM_STATE_FULLSCREEN", FALSE);
7571 return gdk_x11_screen_supports_net_wm_hint(screen, atom);
7572 #else
7573 return true;
7574 #endif
7577 nsresult nsWindow::MakeFullScreen(bool aFullScreen) {
7578 LOG("nsWindow::MakeFullScreen aFullScreen %d\n", aFullScreen);
7580 if (GdkIsX11Display() && !IsFullscreenSupported(mShell)) {
7581 return NS_ERROR_NOT_AVAILABLE;
7584 if (aFullScreen) {
7585 if (mSizeMode != nsSizeMode_Fullscreen &&
7586 mSizeMode != nsSizeMode_Minimized) {
7587 mLastSizeModeBeforeFullscreen = mSizeMode;
7589 if (mIsPIPWindow) {
7590 gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_NORMAL);
7591 if (gUseAspectRatio) {
7592 mAspectRatioSaved = mAspectRatio;
7593 mAspectRatio = 0.0f;
7594 ApplySizeConstraints();
7598 if (mKioskMonitor.isSome()) {
7599 KioskLockOnMonitor();
7600 } else {
7601 gtk_window_fullscreen(GTK_WINDOW(mShell));
7603 } else {
7604 // Kiosk mode always use fullscreen mode.
7605 if (gKioskMode) {
7606 return NS_ERROR_NOT_AVAILABLE;
7609 gtk_window_unfullscreen(GTK_WINDOW(mShell));
7611 if (mIsPIPWindow && gUseAspectRatio) {
7612 mAspectRatio = mAspectRatioSaved;
7613 // ApplySizeConstraints();
7617 MOZ_ASSERT(mLastSizeModeBeforeFullscreen != nsSizeMode_Fullscreen);
7618 return NS_OK;
7621 void nsWindow::SetWindowDecoration(BorderStyle aStyle) {
7622 LOG("nsWindow::SetWindowDecoration() Border style %x\n", int(aStyle));
7624 // We can't use mGdkWindow directly here as it can be
7625 // derived from mContainer which is not a top-level GdkWindow.
7626 GdkWindow* window = GetToplevelGdkWindow();
7628 // Sawfish, metacity, and presumably other window managers get
7629 // confused if we change the window decorations while the window
7630 // is visible.
7631 bool wasVisible = false;
7632 if (gdk_window_is_visible(window)) {
7633 gdk_window_hide(window);
7634 wasVisible = true;
7637 gint wmd = ConvertBorderStyles(aStyle);
7638 if (wmd != -1) gdk_window_set_decorations(window, (GdkWMDecoration)wmd);
7640 if (wasVisible) gdk_window_show(window);
7642 // For some window managers, adding or removing window decorations
7643 // requires unmapping and remapping our toplevel window. Go ahead
7644 // and flush the queue here so that we don't end up with a BadWindow
7645 // error later when this happens (when the persistence timer fires
7646 // and GetWindowPos is called)
7647 #ifdef MOZ_X11
7648 if (GdkIsX11Display()) {
7649 XSync(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), X11False);
7650 } else
7651 #endif /* MOZ_X11 */
7653 gdk_flush();
7657 void nsWindow::HideWindowChrome(bool aShouldHide) {
7658 SetWindowDecoration(aShouldHide ? BorderStyle::None : mBorderStyle);
7661 bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
7662 bool aAlwaysRollup) {
7663 LOG("nsWindow::CheckForRollup() aAlwaysRollup %d", aAlwaysRollup);
7664 nsIRollupListener* rollupListener = GetActiveRollupListener();
7665 nsCOMPtr<nsIWidget> rollupWidget;
7666 if (rollupListener) {
7667 rollupWidget = rollupListener->GetRollupWidget();
7669 if (!rollupWidget) {
7670 return false;
7673 auto* rollupWindow =
7674 (GdkWindow*)rollupWidget->GetNativeData(NS_NATIVE_WINDOW);
7675 if (!aAlwaysRollup && is_mouse_in_window(rollupWindow, aMouseX, aMouseY)) {
7676 return false;
7678 bool retVal = false;
7679 if (aIsWheel) {
7680 retVal = rollupListener->ShouldConsumeOnMouseWheelEvent();
7681 if (!rollupListener->ShouldRollupOnMouseWheelEvent()) {
7682 return retVal;
7685 LayoutDeviceIntPoint point;
7686 nsIRollupListener::RollupOptions options{0,
7687 nsIRollupListener::FlushViews::Yes};
7688 // if we're dealing with menus, we probably have submenus and
7689 // we don't want to rollup if the click is in a parent menu of
7690 // the current submenu
7691 if (!aAlwaysRollup) {
7692 AutoTArray<nsIWidget*, 5> widgetChain;
7693 uint32_t sameTypeCount =
7694 rollupListener->GetSubmenuWidgetChain(&widgetChain);
7695 for (unsigned long i = 0; i < widgetChain.Length(); ++i) {
7696 nsIWidget* widget = widgetChain[i];
7697 auto* currWindow = (GdkWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
7698 if (is_mouse_in_window(currWindow, aMouseX, aMouseY)) {
7699 // Don't roll up if the mouse event occurred within a menu of the same
7700 // type.
7701 // If the mouse event occurred in a menu higher than that, roll up, but
7702 // pass the number of popups to Rollup so that only those of the same
7703 // type close up.
7704 if (i < sameTypeCount) {
7705 return retVal;
7707 options.mCount = sameTypeCount;
7708 break;
7710 } // foreach parent menu widget
7711 if (!aIsWheel) {
7712 point = GdkEventCoordsToDevicePixels(aMouseX, aMouseY);
7713 options.mPoint = &point;
7717 if (mSizeMode == nsSizeMode_Minimized) {
7718 // When we try to rollup in a minimized window, transitionend events for
7719 // panels might not fire and thus we might not hide the popup after all,
7720 // see bug 1810797.
7721 options.mAllowAnimations = nsIRollupListener::AllowAnimations::No;
7724 if (rollupListener->Rollup(options)) {
7725 retVal = true;
7727 return retVal;
7730 /* static */
7731 bool nsWindow::DragInProgress() {
7732 nsCOMPtr<nsIDragService> dragService =
7733 do_GetService("@mozilla.org/widget/dragservice;1");
7734 if (!dragService) {
7735 return false;
7738 nsCOMPtr<nsIDragSession> currentDragSession;
7739 dragService->GetCurrentSession(getter_AddRefs(currentDragSession));
7740 return !!currentDragSession;
7743 // This is an ugly workaround for
7744 // https://bugzilla.mozilla.org/show_bug.cgi?id=1622107
7745 // We try to detect when Wayland compositor / gtk fails to deliver
7746 // info about finished D&D operations and cancel it on our own.
7747 MOZ_CAN_RUN_SCRIPT static void WaylandDragWorkaround(GdkEventButton* aEvent) {
7748 static int buttonPressCountWithDrag = 0;
7750 // We track only left button state as Firefox performs D&D on left
7751 // button only.
7752 if (aEvent->button != 1 || aEvent->type != GDK_BUTTON_PRESS) {
7753 return;
7756 nsCOMPtr<nsIDragService> dragService =
7757 do_GetService("@mozilla.org/widget/dragservice;1");
7758 if (!dragService) {
7759 return;
7761 nsCOMPtr<nsIDragSession> currentDragSession;
7762 dragService->GetCurrentSession(getter_AddRefs(currentDragSession));
7764 if (!currentDragSession) {
7765 buttonPressCountWithDrag = 0;
7766 return;
7769 buttonPressCountWithDrag++;
7770 if (buttonPressCountWithDrag > 1) {
7771 NS_WARNING(
7772 "Quit unfinished Wayland Drag and Drop operation. Buggy Wayland "
7773 "compositor?");
7774 buttonPressCountWithDrag = 0;
7775 dragService->EndDragSession(false, 0);
7779 static nsWindow* get_window_for_gtk_widget(GtkWidget* widget) {
7780 gpointer user_data = g_object_get_data(G_OBJECT(widget), "nsWindow");
7781 return static_cast<nsWindow*>(user_data);
7784 static nsWindow* get_window_for_gdk_window(GdkWindow* window) {
7785 gpointer user_data = g_object_get_data(G_OBJECT(window), "nsWindow");
7786 return static_cast<nsWindow*>(user_data);
7789 static bool is_mouse_in_window(GdkWindow* aWindow, gdouble aMouseX,
7790 gdouble aMouseY) {
7791 GdkWindow* window = aWindow;
7792 if (!window) {
7793 return false;
7796 gint x = 0;
7797 gint y = 0;
7800 gint offsetX = 0;
7801 gint offsetY = 0;
7803 while (window) {
7804 gint tmpX = 0;
7805 gint tmpY = 0;
7807 gdk_window_get_position(window, &tmpX, &tmpY);
7808 GtkWidget* widget = get_gtk_widget_for_gdk_window(window);
7810 // if this is a window, compute x and y given its origin and our
7811 // offset
7812 if (GTK_IS_WINDOW(widget)) {
7813 x = tmpX + offsetX;
7814 y = tmpY + offsetY;
7815 break;
7818 offsetX += tmpX;
7819 offsetY += tmpY;
7820 window = gdk_window_get_parent(window);
7824 gint margin = 0;
7825 if (nsWindow* w = get_window_for_gdk_window(aWindow)) {
7826 margin = w->GetInputRegionMarginInGdkCoords();
7829 x += margin;
7830 y += margin;
7832 gint w = gdk_window_get_width(aWindow) - margin;
7833 gint h = gdk_window_get_height(aWindow) - margin;
7835 return aMouseX > x && aMouseX < x + w && aMouseY > y && aMouseY < y + h;
7838 static GtkWidget* get_gtk_widget_for_gdk_window(GdkWindow* window) {
7839 gpointer user_data = nullptr;
7840 gdk_window_get_user_data(window, &user_data);
7842 return GTK_WIDGET(user_data);
7845 static GdkCursor* get_gtk_cursor(nsCursor aCursor) {
7846 GdkCursor* gdkcursor = nullptr;
7847 uint8_t newType = 0xff;
7849 if ((gdkcursor = gCursorCache[aCursor])) {
7850 return gdkcursor;
7853 GdkDisplay* defaultDisplay = gdk_display_get_default();
7855 switch (aCursor) {
7856 case eCursor_standard:
7857 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "default");
7858 break;
7859 case eCursor_wait:
7860 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "wait");
7861 break;
7862 case eCursor_select:
7863 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "text");
7864 break;
7865 case eCursor_hyperlink:
7866 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "pointer");
7867 break;
7868 case eCursor_n_resize:
7869 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "n-resize");
7870 break;
7871 case eCursor_s_resize:
7872 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "s-resize");
7873 break;
7874 case eCursor_w_resize:
7875 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "w-resize");
7876 break;
7877 case eCursor_e_resize:
7878 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "e-resize");
7879 break;
7880 case eCursor_nw_resize:
7881 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "nw-resize");
7882 break;
7883 case eCursor_se_resize:
7884 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "se-resize");
7885 break;
7886 case eCursor_ne_resize:
7887 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "ne-resize");
7888 break;
7889 case eCursor_sw_resize:
7890 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "sw-resize");
7891 break;
7892 case eCursor_crosshair:
7893 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "crosshair");
7894 break;
7895 case eCursor_move:
7896 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "move");
7897 break;
7898 case eCursor_help:
7899 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "help");
7900 break;
7901 case eCursor_copy:
7902 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "copy");
7903 if (!gdkcursor) newType = MOZ_CURSOR_COPY;
7904 break;
7905 case eCursor_alias:
7906 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "alias");
7907 if (!gdkcursor) newType = MOZ_CURSOR_ALIAS;
7908 break;
7909 case eCursor_context_menu:
7910 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "context-menu");
7911 if (!gdkcursor) newType = MOZ_CURSOR_CONTEXT_MENU;
7912 break;
7913 case eCursor_cell:
7914 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "cell");
7915 break;
7916 case eCursor_grab:
7917 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "grab");
7918 if (!gdkcursor) newType = MOZ_CURSOR_HAND_GRAB;
7919 break;
7920 case eCursor_grabbing:
7921 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "grabbing");
7922 if (!gdkcursor) newType = MOZ_CURSOR_HAND_GRABBING;
7923 break;
7924 case eCursor_spinning:
7925 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "progress");
7926 if (!gdkcursor) newType = MOZ_CURSOR_SPINNING;
7927 break;
7928 case eCursor_zoom_in:
7929 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "zoom-in");
7930 if (!gdkcursor) newType = MOZ_CURSOR_ZOOM_IN;
7931 break;
7932 case eCursor_zoom_out:
7933 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "zoom-out");
7934 if (!gdkcursor) newType = MOZ_CURSOR_ZOOM_OUT;
7935 break;
7936 case eCursor_not_allowed:
7937 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "not-allowed");
7938 if (!gdkcursor) newType = MOZ_CURSOR_NOT_ALLOWED;
7939 break;
7940 case eCursor_no_drop:
7941 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "no-drop");
7942 if (!gdkcursor) { // this nonstandard sequence makes it work on KDE and
7943 // GNOME
7944 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "forbidden");
7946 if (!gdkcursor) {
7947 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "circle");
7949 if (!gdkcursor) newType = MOZ_CURSOR_NOT_ALLOWED;
7950 break;
7951 case eCursor_vertical_text:
7952 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "vertical-text");
7953 if (!gdkcursor) {
7954 newType = MOZ_CURSOR_VERTICAL_TEXT;
7956 break;
7957 case eCursor_all_scroll:
7958 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "move");
7959 break;
7960 case eCursor_nesw_resize:
7961 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "nesw-resize");
7962 if (!gdkcursor) newType = MOZ_CURSOR_NESW_RESIZE;
7963 break;
7964 case eCursor_nwse_resize:
7965 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "nwse-resize");
7966 if (!gdkcursor) newType = MOZ_CURSOR_NWSE_RESIZE;
7967 break;
7968 case eCursor_ns_resize:
7969 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "ns-resize");
7970 break;
7971 case eCursor_ew_resize:
7972 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "ew-resize");
7973 break;
7974 case eCursor_row_resize:
7975 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "row-resize");
7976 break;
7977 case eCursor_col_resize:
7978 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "col-resize");
7979 break;
7980 case eCursor_none:
7981 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "none");
7982 if (!gdkcursor) newType = MOZ_CURSOR_NONE;
7983 break;
7984 default:
7985 NS_ASSERTION(aCursor, "Invalid cursor type");
7986 gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "default");
7987 break;
7990 // If by now we don't have a xcursor, this means we have to make a custom
7991 // one. First, we try creating a named cursor based on the hash of our
7992 // custom bitmap, as libXcursor has some magic to convert bitmapped cursors
7993 // to themed cursors
7994 if (newType != 0xFF && GtkCursors[newType].hash) {
7995 gdkcursor =
7996 gdk_cursor_new_from_name(defaultDisplay, GtkCursors[newType].hash);
7999 // If we still don't have a xcursor, we now really create a bitmap cursor
8000 if (newType != 0xff && !gdkcursor) {
8001 GdkPixbuf* cursor_pixbuf =
8002 gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32);
8003 if (!cursor_pixbuf) {
8004 return nullptr;
8007 guchar* data = gdk_pixbuf_get_pixels(cursor_pixbuf);
8009 // Read data from GtkCursors and compose RGBA surface from 1bit bitmap and
8010 // mask GtkCursors bits and mask are 32x32 monochrome bitmaps (1 bit for
8011 // each pixel) so it's 128 byte array (4 bytes for are one bitmap row and
8012 // there are 32 rows here).
8013 const unsigned char* bits = GtkCursors[newType].bits;
8014 const unsigned char* mask_bits = GtkCursors[newType].mask_bits;
8016 for (int i = 0; i < 128; i++) {
8017 char bit = (char)*bits++;
8018 char mask = (char)*mask_bits++;
8019 for (int j = 0; j < 8; j++) {
8020 unsigned char pix = ~(((bit >> j) & 0x01) * 0xff);
8021 *data++ = pix;
8022 *data++ = pix;
8023 *data++ = pix;
8024 *data++ = (((mask >> j) & 0x01) * 0xff);
8028 gdkcursor = gdk_cursor_new_from_pixbuf(
8029 gdk_display_get_default(), cursor_pixbuf, GtkCursors[newType].hot_x,
8030 GtkCursors[newType].hot_y);
8032 g_object_unref(cursor_pixbuf);
8035 gCursorCache[aCursor] = gdkcursor;
8037 return gdkcursor;
8040 // gtk callbacks
8042 void draw_window_of_widget(GtkWidget* widget, GdkWindow* aWindow, cairo_t* cr) {
8043 if (gtk_cairo_should_draw_window(cr, aWindow)) {
8044 RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
8045 if (!window) {
8046 NS_WARNING("Cannot get nsWindow from GtkWidget");
8047 } else {
8048 cairo_save(cr);
8049 gtk_cairo_transform_to_window(cr, widget, aWindow);
8050 // TODO - window->OnExposeEvent() can destroy this or other windows,
8051 // do we need to handle it somehow?
8052 window->OnExposeEvent(cr);
8053 cairo_restore(cr);
8058 /* static */
8059 gboolean expose_event_cb(GtkWidget* widget, cairo_t* cr) {
8060 draw_window_of_widget(widget, gtk_widget_get_window(widget), cr);
8062 // A strong reference is already held during "draw" signal emission,
8063 // but GTK+ 3.4 wants the object to live a little longer than that
8064 // (bug 1225970).
8065 g_object_ref(widget);
8066 g_idle_add(
8067 [](gpointer data) -> gboolean {
8068 g_object_unref(data);
8069 return G_SOURCE_REMOVE;
8071 widget);
8073 return FALSE;
8076 static gboolean configure_event_cb(GtkWidget* widget,
8077 GdkEventConfigure* event) {
8078 RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
8079 if (!window) {
8080 return FALSE;
8083 return window->OnConfigureEvent(widget, event);
8086 // Some Gtk widget code may call gtk_widget_unrealize() which destroys
8087 // mGdkWindow. We need to listen on this signal and re-create
8088 // mGdkWindow when we're already mapped.
8089 static void widget_map_cb(GtkWidget* widget) {
8090 RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
8091 if (!window) {
8092 return;
8094 window->OnMap();
8097 static void widget_unmap_cb(GtkWidget* widget) {
8098 RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
8099 if (!window) {
8100 return;
8102 window->OnUnmap();
8105 static void size_allocate_cb(GtkWidget* widget, GtkAllocation* allocation) {
8106 RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
8107 if (!window) {
8108 return;
8111 window->OnSizeAllocate(allocation);
8114 static void toplevel_window_size_allocate_cb(GtkWidget* widget,
8115 GtkAllocation* allocation) {
8116 RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
8117 if (!window) {
8118 return;
8121 window->UpdateTopLevelOpaqueRegion();
8124 static gboolean delete_event_cb(GtkWidget* widget, GdkEventAny* event) {
8125 RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
8126 if (!window) {
8127 return FALSE;
8130 window->OnDeleteEvent();
8132 return TRUE;
8135 static gboolean enter_notify_event_cb(GtkWidget* widget,
8136 GdkEventCrossing* event) {
8137 RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
8138 if (!window) {
8139 return TRUE;
8142 // We have stored leave notify - check if it's the correct one and
8143 // fire it before enter notify in such case.
8144 if (sStoredLeaveNotifyEvent) {
8145 auto clearNofityEvent =
8146 MakeScopeExit([&] { sStoredLeaveNotifyEvent = nullptr; });
8147 if (event->x_root == sStoredLeaveNotifyEvent->x_root &&
8148 event->y_root == sStoredLeaveNotifyEvent->y_root &&
8149 window->ApplyEnterLeaveMutterWorkaround()) {
8150 // Enter/Leave notify events has the same coordinates
8151 // and uses know buggy window config.
8152 // Consider it as a bogus one.
8153 return TRUE;
8155 RefPtr<nsWindow> leftWindow =
8156 get_window_for_gdk_window(sStoredLeaveNotifyEvent->window);
8157 if (leftWindow) {
8158 leftWindow->OnLeaveNotifyEvent(sStoredLeaveNotifyEvent.get());
8162 window->OnEnterNotifyEvent(event);
8163 return TRUE;
8166 static gboolean leave_notify_event_cb(GtkWidget* widget,
8167 GdkEventCrossing* event) {
8168 RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
8169 if (!window) {
8170 return TRUE;
8173 if (window->ApplyEnterLeaveMutterWorkaround()) {
8174 // The leave event is potentially wrong, don't fire it now but store
8175 // it for further check at enter_notify_event_cb().
8176 sStoredLeaveNotifyEvent.reset(reinterpret_cast<GdkEventCrossing*>(
8177 gdk_event_copy(reinterpret_cast<GdkEvent*>(event))));
8178 } else {
8179 sStoredLeaveNotifyEvent = nullptr;
8180 window->OnLeaveNotifyEvent(event);
8183 return TRUE;
8186 static nsWindow* GetFirstNSWindowForGDKWindow(GdkWindow* aGdkWindow) {
8187 nsWindow* window;
8188 while (!(window = get_window_for_gdk_window(aGdkWindow))) {
8189 // The event has bubbled to the moz_container widget as passed into each
8190 // caller's *widget parameter, but its corresponding nsWindow is an ancestor
8191 // of the window that we need. Instead, look at event->window and find the
8192 // first ancestor nsWindow of it because event->window may be in a plugin.
8193 aGdkWindow = gdk_window_get_parent(aGdkWindow);
8194 if (!aGdkWindow) {
8195 window = nullptr;
8196 break;
8199 return window;
8202 static gboolean motion_notify_event_cb(GtkWidget* widget,
8203 GdkEventMotion* event) {
8204 UpdateLastInputEventTime(event);
8206 RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
8207 if (!window) {
8208 return FALSE;
8211 window->OnMotionNotifyEvent(event);
8213 return TRUE;
8216 static gboolean button_press_event_cb(GtkWidget* widget,
8217 GdkEventButton* event) {
8218 UpdateLastInputEventTime(event);
8220 if (event->button == 2 && !StaticPrefs::widget_gtk_middle_click_enabled()) {
8221 return FALSE;
8224 RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
8225 if (!window) {
8226 return FALSE;
8229 window->OnButtonPressEvent(event);
8231 if (GdkIsWaylandDisplay()) {
8232 WaylandDragWorkaround(event);
8235 return TRUE;
8238 static gboolean button_release_event_cb(GtkWidget* widget,
8239 GdkEventButton* event) {
8240 UpdateLastInputEventTime(event);
8242 if (event->button == 2 && !StaticPrefs::widget_gtk_middle_click_enabled()) {
8243 return FALSE;
8246 RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
8247 if (!window) {
8248 return FALSE;
8251 window->OnButtonReleaseEvent(event);
8253 return TRUE;
8256 static gboolean focus_in_event_cb(GtkWidget* widget, GdkEventFocus* event) {
8257 RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
8258 if (!window) {
8259 return FALSE;
8262 window->OnContainerFocusInEvent(event);
8264 return FALSE;
8267 static gboolean focus_out_event_cb(GtkWidget* widget, GdkEventFocus* event) {
8268 RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
8269 if (!window) {
8270 return FALSE;
8273 window->OnContainerFocusOutEvent(event);
8275 return FALSE;
8278 #ifdef MOZ_X11
8279 // For long-lived popup windows that don't really take focus themselves but
8280 // may have elements that accept keyboard input when the parent window is
8281 // active, focus is handled specially. These windows include noautohide
8282 // panels. (This special handling is not necessary for temporary popups where
8283 // the keyboard is grabbed.)
8285 // Mousing over or clicking on these windows should not cause them to steal
8286 // focus from their parent windows, so, the input field of WM_HINTS is set to
8287 // False to request that the window manager not set the input focus to this
8288 // window. http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7
8290 // However, these windows can still receive WM_TAKE_FOCUS messages from the
8291 // window manager, so they can still detect when the user has indicated that
8292 // they wish to direct keyboard input at these windows. When the window
8293 // manager offers focus to these windows (after a mouse over or click, for
8294 // example), a request to make the parent window active is issued. When the
8295 // parent window becomes active, keyboard events will be received.
8297 static GdkFilterReturn popup_take_focus_filter(GdkXEvent* gdk_xevent,
8298 GdkEvent* event, gpointer data) {
8299 auto* xevent = static_cast<XEvent*>(gdk_xevent);
8300 if (xevent->type != ClientMessage) {
8301 return GDK_FILTER_CONTINUE;
8304 XClientMessageEvent& xclient = xevent->xclient;
8305 if (xclient.message_type != gdk_x11_get_xatom_by_name("WM_PROTOCOLS")) {
8306 return GDK_FILTER_CONTINUE;
8309 Atom atom = xclient.data.l[0];
8310 if (atom != gdk_x11_get_xatom_by_name("WM_TAKE_FOCUS")) {
8311 return GDK_FILTER_CONTINUE;
8314 guint32 timestamp = xclient.data.l[1];
8316 GtkWidget* widget = get_gtk_widget_for_gdk_window(event->any.window);
8317 if (!widget) {
8318 return GDK_FILTER_CONTINUE;
8321 GtkWindow* parent = gtk_window_get_transient_for(GTK_WINDOW(widget));
8322 if (!parent) {
8323 return GDK_FILTER_CONTINUE;
8326 if (gtk_window_is_active(parent)) {
8327 return GDK_FILTER_REMOVE; // leave input focus on the parent
8330 GdkWindow* parent_window = gtk_widget_get_window(GTK_WIDGET(parent));
8331 if (!parent_window) {
8332 return GDK_FILTER_CONTINUE;
8335 // In case the parent has not been deiconified.
8336 gdk_window_show_unraised(parent_window);
8338 // Request focus on the parent window.
8339 // Use gdk_window_focus rather than gtk_window_present to avoid
8340 // raising the parent window.
8341 gdk_window_focus(parent_window, timestamp);
8342 return GDK_FILTER_REMOVE;
8344 #endif /* MOZ_X11 */
8346 static gboolean key_press_event_cb(GtkWidget* widget, GdkEventKey* event) {
8347 LOGW("key_press_event_cb\n");
8349 UpdateLastInputEventTime(event);
8351 // find the window with focus and dispatch this event to that widget
8352 nsWindow* window = get_window_for_gtk_widget(widget);
8353 if (!window) {
8354 return FALSE;
8357 RefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
8359 #ifdef MOZ_X11
8360 // Keyboard repeat can cause key press events to queue up when there are
8361 // slow event handlers (bug 301029). Throttle these events by removing
8362 // consecutive pending duplicate KeyPress events to the same window.
8363 // We use the event time of the last one.
8364 // Note: GDK calls XkbSetDetectableAutorepeat so that KeyRelease events
8365 // are generated only when the key is physically released.
8366 # define NS_GDKEVENT_MATCH_MASK 0x1FFF // GDK_SHIFT_MASK .. GDK_BUTTON5_MASK
8367 // Our headers undefine X11 KeyPress - let's redefine it here.
8368 # ifndef KeyPress
8369 # define KeyPress 2
8370 # endif
8371 GdkDisplay* gdkDisplay = gtk_widget_get_display(widget);
8372 if (GdkIsX11Display(gdkDisplay)) {
8373 Display* dpy = GDK_DISPLAY_XDISPLAY(gdkDisplay);
8374 while (XPending(dpy)) {
8375 XEvent next_event;
8376 XPeekEvent(dpy, &next_event);
8377 GdkWindow* nextGdkWindow =
8378 gdk_x11_window_lookup_for_display(gdkDisplay, next_event.xany.window);
8379 if (nextGdkWindow != event->window || next_event.type != KeyPress ||
8380 next_event.xkey.keycode != event->hardware_keycode ||
8381 next_event.xkey.state != (event->state & NS_GDKEVENT_MATCH_MASK)) {
8382 break;
8384 XNextEvent(dpy, &next_event);
8385 event->time = next_event.xkey.time;
8388 #endif
8390 return focusWindow->OnKeyPressEvent(event);
8393 static gboolean key_release_event_cb(GtkWidget* widget, GdkEventKey* event) {
8394 LOGW("key_release_event_cb\n");
8396 UpdateLastInputEventTime(event);
8398 // find the window with focus and dispatch this event to that widget
8399 nsWindow* window = get_window_for_gtk_widget(widget);
8400 if (!window) {
8401 return FALSE;
8404 RefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
8405 return focusWindow->OnKeyReleaseEvent(event);
8408 static gboolean property_notify_event_cb(GtkWidget* aWidget,
8409 GdkEventProperty* aEvent) {
8410 RefPtr<nsWindow> window = get_window_for_gdk_window(aEvent->window);
8411 if (!window) {
8412 return FALSE;
8415 return window->OnPropertyNotifyEvent(aWidget, aEvent);
8418 static gboolean scroll_event_cb(GtkWidget* widget, GdkEventScroll* event) {
8419 RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
8420 if (NS_WARN_IF(!window)) {
8421 return FALSE;
8424 window->OnScrollEvent(event);
8426 return TRUE;
8429 static gboolean visibility_notify_event_cb(GtkWidget* widget,
8430 GdkEventVisibility* event) {
8431 RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
8432 if (!window) {
8433 return FALSE;
8435 window->OnVisibilityNotifyEvent(event->state);
8436 return TRUE;
8439 static void hierarchy_changed_cb(GtkWidget* widget,
8440 GtkWidget* previous_toplevel) {
8441 GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
8442 GdkWindowState old_window_state = GDK_WINDOW_STATE_WITHDRAWN;
8443 GdkEventWindowState event;
8445 event.new_window_state = GDK_WINDOW_STATE_WITHDRAWN;
8447 if (GTK_IS_WINDOW(previous_toplevel)) {
8448 g_signal_handlers_disconnect_by_func(
8449 previous_toplevel, FuncToGpointer(window_state_event_cb), widget);
8450 GdkWindow* win = gtk_widget_get_window(previous_toplevel);
8451 if (win) {
8452 old_window_state = gdk_window_get_state(win);
8456 if (GTK_IS_WINDOW(toplevel)) {
8457 g_signal_connect_swapped(toplevel, "window-state-event",
8458 G_CALLBACK(window_state_event_cb), widget);
8459 GdkWindow* win = gtk_widget_get_window(toplevel);
8460 if (win) {
8461 event.new_window_state = gdk_window_get_state(win);
8465 event.changed_mask =
8466 static_cast<GdkWindowState>(old_window_state ^ event.new_window_state);
8468 if (event.changed_mask) {
8469 event.type = GDK_WINDOW_STATE;
8470 event.window = nullptr;
8471 event.send_event = TRUE;
8472 window_state_event_cb(widget, &event);
8476 static gboolean window_state_event_cb(GtkWidget* widget,
8477 GdkEventWindowState* event) {
8478 RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
8479 if (!window) {
8480 return FALSE;
8483 window->OnWindowStateEvent(widget, event);
8485 return FALSE;
8488 static void settings_xft_dpi_changed_cb(GtkSettings* gtk_settings,
8489 GParamSpec* pspec, nsWindow* data) {
8490 RefPtr<nsWindow> window = data;
8491 window->OnDPIChanged();
8492 // Even though the window size in screen pixels has not changed,
8493 // nsViewManager stores the dimensions in app units.
8494 // DispatchResized() updates those.
8495 window->DispatchResized();
8498 static void check_resize_cb(GtkContainer* container, gpointer user_data) {
8499 RefPtr<nsWindow> window = get_window_for_gtk_widget(GTK_WIDGET(container));
8500 if (!window) {
8501 return;
8503 window->OnCheckResize();
8506 static void screen_composited_changed_cb(GdkScreen* screen,
8507 gpointer user_data) {
8508 // This callback can run before gfxPlatform::Init() in rare
8509 // cases involving the profile manager. When this happens,
8510 // we have no reason to reset any compositors as graphics
8511 // hasn't been initialized yet.
8512 if (GPUProcessManager::Get()) {
8513 GPUProcessManager::Get()->ResetCompositors();
8517 static void widget_composited_changed_cb(GtkWidget* widget,
8518 gpointer user_data) {
8519 RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
8520 if (!window) {
8521 return;
8523 window->OnCompositedChanged();
8526 static void scale_changed_cb(GtkWidget* widget, GParamSpec* aPSpec,
8527 gpointer aPointer) {
8528 RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
8529 if (!window) {
8530 return;
8533 window->OnScaleChanged(/* aNotify = */ true);
8536 static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent) {
8537 UpdateLastInputEventTime(aEvent);
8539 RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(aEvent->window);
8540 if (!window) {
8541 return FALSE;
8544 return window->OnTouchEvent(aEvent);
8547 // This function called generic because there is no signal specific to touchpad
8548 // pinch events.
8549 static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent) {
8550 if (aEvent->type != GDK_TOUCHPAD_PINCH) {
8551 return FALSE;
8553 // Using reinterpret_cast because the touchpad_pinch field of GdkEvent is not
8554 // available in GTK+ versions lower than v3.18
8555 GdkEventTouchpadPinch* event =
8556 reinterpret_cast<GdkEventTouchpadPinch*>(aEvent);
8558 RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
8560 if (!window) {
8561 return FALSE;
8563 return window->OnTouchpadPinchEvent(event);
8566 //////////////////////////////////////////////////////////////////////
8567 // These are all of our drag and drop operations
8569 void nsWindow::InitDragEvent(WidgetDragEvent& aEvent) {
8570 // set the keyboard modifiers
8571 guint modifierState = KeymapWrapper::GetCurrentModifierState();
8572 KeymapWrapper::InitInputEvent(aEvent, modifierState);
8575 static LayoutDeviceIntPoint GetWindowDropPosition(nsWindow* aWindow, int aX,
8576 int aY) {
8577 // Workaround for Bug 1710344
8578 // Caused by Gtk issue https://gitlab.gnome.org/GNOME/gtk/-/issues/4437
8579 if (aWindow->IsWaylandPopup()) {
8580 int tx = 0, ty = 0;
8581 gdk_window_get_position(aWindow->GetToplevelGdkWindow(), &tx, &ty);
8582 aX += tx;
8583 aY += ty;
8585 LOGDRAG("WindowDropPosition [%d, %d]", aX, aY);
8586 return aWindow->GdkPointToDevicePixels({aX, aY});
8589 gboolean WindowDragMotionHandler(GtkWidget* aWidget,
8590 GdkDragContext* aDragContext, gint aX, gint aY,
8591 guint aTime) {
8592 RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
8593 if (!window || !window->GetGdkWindow()) {
8594 return FALSE;
8597 // We're getting aX,aY in mShell coordinates space.
8598 // mContainer is shifted by CSD decorations so translate the coords
8599 // to mContainer space where our content lives.
8600 if (aWidget == window->GetGtkWidget()) {
8601 int x, y;
8602 gdk_window_get_geometry(window->GetGdkWindow(), &x, &y, nullptr, nullptr);
8603 aX -= x;
8604 aY -= y;
8607 LOGDRAG("WindowDragMotionHandler target nsWindow [%p]", window.get());
8609 RefPtr<nsDragService> dragService = nsDragService::GetInstance();
8610 nsDragService::AutoEventLoop loop(dragService);
8611 if (!dragService->ScheduleMotionEvent(
8612 window, aDragContext, GetWindowDropPosition(window, aX, aY), aTime)) {
8613 return FALSE;
8615 return TRUE;
8618 static gboolean drag_motion_event_cb(GtkWidget* aWidget,
8619 GdkDragContext* aDragContext, gint aX,
8620 gint aY, guint aTime, gpointer aData) {
8621 return WindowDragMotionHandler(aWidget, aDragContext, aX, aY, aTime);
8624 void WindowDragLeaveHandler(GtkWidget* aWidget) {
8625 LOGDRAG("WindowDragLeaveHandler()\n");
8627 RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
8628 if (!window) {
8629 LOGDRAG(" Failed - can't find nsWindow!\n");
8630 return;
8633 RefPtr<nsDragService> dragService = nsDragService::GetInstance();
8634 nsDragService::AutoEventLoop loop(dragService);
8636 nsWindow* mostRecentDragWindow = dragService->GetMostRecentDestWindow();
8637 if (!mostRecentDragWindow) {
8638 // This can happen when the target will not accept a drop. A GTK drag
8639 // source sends the leave message to the destination before the
8640 // drag-failed signal on the source widget, but the leave message goes
8641 // via the X server, and so doesn't get processed at least until the
8642 // event loop runs again.
8643 LOGDRAG(" Failed - GetMostRecentDestWindow()!\n");
8644 return;
8647 if (aWidget != window->GetGtkWidget()) {
8648 // When the drag moves between widgets, GTK can send leave signal for
8649 // the old widget after the motion or drop signal for the new widget.
8650 // We'll send the leave event when the motion or drop event is run.
8651 LOGDRAG(" Failed - GtkWidget mismatch!\n");
8652 return;
8655 LOGDRAG("WindowDragLeaveHandler nsWindow %p\n", (void*)mostRecentDragWindow);
8656 dragService->ScheduleLeaveEvent();
8659 static void drag_leave_event_cb(GtkWidget* aWidget,
8660 GdkDragContext* aDragContext, guint aTime,
8661 gpointer aData) {
8662 WindowDragLeaveHandler(aWidget);
8665 gboolean WindowDragDropHandler(GtkWidget* aWidget, GdkDragContext* aDragContext,
8666 gint aX, gint aY, guint aTime) {
8667 RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
8668 if (!window || !window->GetGdkWindow()) {
8669 return FALSE;
8672 // We're getting aX,aY in mShell coordinates space.
8673 // mContainer is shifted by CSD decorations so translate the coords
8674 // to mContainer space where our content lives.
8675 if (aWidget == window->GetGtkWidget()) {
8676 int x, y;
8677 gdk_window_get_geometry(window->GetGdkWindow(), &x, &y, nullptr, nullptr);
8678 aX -= x;
8679 aY -= y;
8682 LOGDRAG("WindowDragDropHandler nsWindow [%p]", window.get());
8683 RefPtr<nsDragService> dragService = nsDragService::GetInstance();
8684 nsDragService::AutoEventLoop loop(dragService);
8685 return dragService->ScheduleDropEvent(
8686 window, aDragContext, GetWindowDropPosition(window, aX, aY), aTime);
8689 static gboolean drag_drop_event_cb(GtkWidget* aWidget,
8690 GdkDragContext* aDragContext, gint aX,
8691 gint aY, guint aTime, gpointer aData) {
8692 return WindowDragDropHandler(aWidget, aDragContext, aX, aY, aTime);
8695 static void drag_data_received_event_cb(GtkWidget* aWidget,
8696 GdkDragContext* aDragContext, gint aX,
8697 gint aY,
8698 GtkSelectionData* aSelectionData,
8699 guint aInfo, guint aTime,
8700 gpointer aData) {
8701 RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
8702 if (!window) {
8703 return;
8706 window->OnDragDataReceivedEvent(aWidget, aDragContext, aX, aY, aSelectionData,
8707 aInfo, aTime, aData);
8710 static nsresult initialize_prefs(void) {
8711 if (Preferences::HasUserValue("widget.use-aspect-ratio")) {
8712 gUseAspectRatio = Preferences::GetBool("widget.use-aspect-ratio", true);
8713 } else {
8714 gUseAspectRatio = IsGnomeDesktopEnvironment() || IsKdeDesktopEnvironment();
8716 return NS_OK;
8719 #ifdef ACCESSIBILITY
8720 void nsWindow::CreateRootAccessible() {
8721 if (!mRootAccessible) {
8722 LOG("nsWindow:: Create Toplevel Accessibility\n");
8723 mRootAccessible = GetRootAccessible();
8727 void nsWindow::DispatchEventToRootAccessible(uint32_t aEventType) {
8728 if (!a11y::ShouldA11yBeEnabled()) {
8729 return;
8732 nsAccessibilityService* accService = GetOrCreateAccService();
8733 if (!accService) {
8734 return;
8737 // Get the root document accessible and fire event to it.
8738 a11y::LocalAccessible* acc = GetRootAccessible();
8739 if (acc) {
8740 accService->FireAccessibleEvent(aEventType, acc);
8744 void nsWindow::DispatchActivateEventAccessible(void) {
8745 DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_ACTIVATE);
8748 void nsWindow::DispatchDeactivateEventAccessible(void) {
8749 DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_DEACTIVATE);
8752 void nsWindow::DispatchMaximizeEventAccessible(void) {
8753 DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_MAXIMIZE);
8756 void nsWindow::DispatchMinimizeEventAccessible(void) {
8757 DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_MINIMIZE);
8760 void nsWindow::DispatchRestoreEventAccessible(void) {
8761 DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_RESTORE);
8764 #endif /* #ifdef ACCESSIBILITY */
8766 void nsWindow::SetInputContext(const InputContext& aContext,
8767 const InputContextAction& aAction) {
8768 if (!mIMContext) {
8769 return;
8771 mIMContext->SetInputContext(this, &aContext, &aAction);
8774 InputContext nsWindow::GetInputContext() {
8775 InputContext context;
8776 if (!mIMContext) {
8777 context.mIMEState.mEnabled = IMEEnabled::Disabled;
8778 context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
8779 } else {
8780 context = mIMContext->GetInputContext();
8782 return context;
8785 TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() {
8786 if (NS_WARN_IF(!mIMContext)) {
8787 return nullptr;
8789 return mIMContext;
8792 bool nsWindow::GetEditCommands(NativeKeyBindingsType aType,
8793 const WidgetKeyboardEvent& aEvent,
8794 nsTArray<CommandInt>& aCommands) {
8795 // Validate the arguments.
8796 if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
8797 return false;
8800 Maybe<WritingMode> writingMode;
8801 if (aEvent.NeedsToRemapNavigationKey()) {
8802 if (RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher()) {
8803 writingMode = dispatcher->MaybeQueryWritingModeAtSelection();
8807 NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
8808 keyBindings->GetEditCommands(aEvent, writingMode, aCommands);
8809 return true;
8812 already_AddRefed<DrawTarget> nsWindow::StartRemoteDrawingInRegion(
8813 const LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode) {
8814 return mSurfaceProvider.StartRemoteDrawingInRegion(aInvalidRegion,
8815 aBufferMode);
8818 void nsWindow::EndRemoteDrawingInRegion(
8819 DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
8820 mSurfaceProvider.EndRemoteDrawingInRegion(aDrawTarget, aInvalidRegion);
8823 bool nsWindow::GetDragInfo(WidgetMouseEvent* aMouseEvent, GdkWindow** aWindow,
8824 gint* aButton, gint* aRootX, gint* aRootY) {
8825 if (aMouseEvent->mButton != MouseButton::ePrimary) {
8826 // we can only begin a move drag with the left mouse button
8827 return false;
8829 *aButton = 1;
8831 // get the gdk window for this widget
8832 GdkWindow* gdk_window = mGdkWindow;
8833 if (!gdk_window) {
8834 return false;
8836 #ifdef DEBUG
8837 // GDK_IS_WINDOW(...) expands to a statement-expression, and
8838 // statement-expressions are not allowed in template-argument lists. So we
8839 // have to make the MOZ_ASSERT condition indirect.
8840 if (!GDK_IS_WINDOW(gdk_window)) {
8841 MOZ_ASSERT(false, "must really be window");
8843 #endif
8845 // find the top-level window
8846 gdk_window = gdk_window_get_toplevel(gdk_window);
8847 MOZ_ASSERT(gdk_window, "gdk_window_get_toplevel should not return null");
8848 *aWindow = gdk_window;
8850 if (!aMouseEvent->mWidget) {
8851 return false;
8854 #ifdef MOZ_X11
8855 if (GdkIsX11Display()) {
8856 // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=789054
8857 // To avoid crashes disable double-click on WM without _NET_WM_MOVERESIZE.
8858 // See _should_perform_ewmh_drag() at gdkwindow-x11.c
8859 // XXXsmaug remove this old hack. gtk should be fixed now.
8860 GdkScreen* screen = gdk_window_get_screen(gdk_window);
8861 GdkAtom atom = gdk_atom_intern("_NET_WM_MOVERESIZE", FALSE);
8862 if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
8863 static TimeStamp lastTimeStamp;
8864 if (lastTimeStamp != aMouseEvent->mTimeStamp) {
8865 lastTimeStamp = aMouseEvent->mTimeStamp;
8866 } else {
8867 return false;
8871 #endif
8873 // FIXME: It would be nice to have the widget position at the time
8874 // of the event, but it's relatively unlikely that the widget has
8875 // moved since the mousedown. (On the other hand, it's quite likely
8876 // that the mouse has moved, which is why we use the mouse position
8877 // from the event.)
8878 LayoutDeviceIntPoint offset = aMouseEvent->mWidget->WidgetToScreenOffset();
8879 *aRootX = aMouseEvent->mRefPoint.x + offset.x;
8880 *aRootY = aMouseEvent->mRefPoint.y + offset.y;
8882 return true;
8885 nsIWidget::WindowRenderer* nsWindow::GetWindowRenderer() {
8886 if (mIsDestroyed) {
8887 // Prevent external code from triggering the re-creation of the
8888 // LayerManager/Compositor during shutdown. Just return what we currently
8889 // have, which is most likely null.
8890 return mWindowRenderer;
8893 return nsBaseWidget::GetWindowRenderer();
8896 void nsWindow::DidGetNonBlankPaint() {
8897 if (mGotNonBlankPaint) {
8898 return;
8900 mGotNonBlankPaint = true;
8901 if (!mConfiguredClearColor) {
8902 // Nothing to do, we hadn't overridden the clear color.
8903 mConfiguredClearColor = true;
8904 return;
8906 // Reset the clear color set in the expose event to transparent.
8907 GetWindowRenderer()->AsWebRender()->WrBridge()->SendSetDefaultClearColor(
8908 NS_TRANSPARENT);
8911 /* nsWindow::SetCompositorWidgetDelegate() sets remote GtkCompositorWidget
8912 * to render into with compositor.
8914 * SetCompositorWidgetDelegate(delegate) is called from
8915 * nsBaseWidget::CreateCompositor(), i.e. nsWindow::GetWindowRenderer().
8917 * SetCompositorWidgetDelegate(null) is called from
8918 * nsBaseWidget::DestroyCompositor().
8920 void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
8921 LOG("nsWindow::SetCompositorWidgetDelegate %p mIsMapped %d "
8922 "mCompositorWidgetDelegate %p\n",
8923 delegate, mIsMapped, mCompositorWidgetDelegate);
8925 MOZ_RELEASE_ASSERT(NS_IsMainThread());
8926 if (delegate) {
8927 mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate();
8928 MOZ_ASSERT(mCompositorWidgetDelegate,
8929 "nsWindow::SetCompositorWidgetDelegate called with a "
8930 "non-PlatformCompositorWidgetDelegate");
8931 if (mIsMapped) {
8932 ConfigureCompositor();
8934 } else {
8935 mCompositorWidgetDelegate = nullptr;
8939 nsresult nsWindow::SetNonClientMargins(const LayoutDeviceIntMargin& aMargins) {
8940 SetDrawsInTitlebar(aMargins.top == 0);
8941 return NS_OK;
8944 bool nsWindow::IsAlwaysUndecoratedWindow() const {
8945 if (mIsPIPWindow || gKioskMode) {
8946 return true;
8948 if (mWindowType == WindowType::Dialog &&
8949 mBorderStyle != BorderStyle::Default &&
8950 mBorderStyle != BorderStyle::All &&
8951 !(mBorderStyle & BorderStyle::Title) &&
8952 !(mBorderStyle & BorderStyle::ResizeH)) {
8953 return true;
8955 return false;
8958 void nsWindow::SetDrawsInTitlebar(bool aState) {
8959 LOG("nsWindow::SetDrawsInTitlebar() State %d mGtkWindowDecoration %d\n",
8960 aState, (int)mGtkWindowDecoration);
8962 if (mGtkWindowDecoration == GTK_DECORATION_NONE ||
8963 aState == mDrawInTitlebar) {
8964 LOG(" already set, quit");
8965 return;
8968 if (mUndecorated) {
8969 MOZ_ASSERT(aState, "Unexpected decoration request");
8970 MOZ_ASSERT(!gtk_window_get_decorated(GTK_WINDOW(mShell)));
8971 return;
8974 mDrawInTitlebar = aState;
8976 if (mGtkWindowDecoration == GTK_DECORATION_SYSTEM) {
8977 SetWindowDecoration(aState ? BorderStyle::Border : mBorderStyle);
8978 } else if (mGtkWindowDecoration == GTK_DECORATION_CLIENT) {
8979 LOG(" Using CSD mode\n");
8981 if (!gtk_widget_get_realized(GTK_WIDGET(mShell))) {
8982 LOG(" Using CSD mode fast path\n");
8983 gtk_window_set_titlebar(GTK_WINDOW(mShell),
8984 aState ? gtk_fixed_new() : nullptr);
8985 return;
8988 /* Window manager does not support GDK_DECOR_BORDER,
8989 * emulate it by CSD.
8991 * gtk_window_set_titlebar() works on unrealized widgets only,
8992 * we need to handle mShell carefully here.
8993 * When CSD is enabled mGdkWindow is owned by mContainer which is good
8994 * as we can't delete our mGdkWindow. To make mShell unrealized while
8995 * mContainer is preserved we temporary reparent mContainer to an
8996 * invisible GtkWindow.
8998 bool visible = !mNeedsShow && mIsShown;
8999 if (visible) {
9000 NativeShow(false);
9003 // Using GTK_WINDOW_POPUP rather than
9004 // GTK_WINDOW_TOPLEVEL in the hope that POPUP results in less
9005 // initialization and window manager interaction.
9006 GtkWidget* tmpWindow = gtk_window_new(GTK_WINDOW_POPUP);
9007 gtk_widget_realize(tmpWindow);
9009 gtk_widget_reparent(GTK_WIDGET(mContainer), tmpWindow);
9010 gtk_widget_unrealize(GTK_WIDGET(mShell));
9012 // Add a hidden titlebar widget to trigger CSD, but disable the default
9013 // titlebar. GtkFixed is a somewhat random choice for a simple unused
9014 // widget. gtk_window_set_titlebar() takes ownership of the titlebar
9015 // widget.
9016 gtk_window_set_titlebar(GTK_WINDOW(mShell),
9017 aState ? gtk_fixed_new() : nullptr);
9019 /* A workaround for https://bugzilla.gnome.org/show_bug.cgi?id=791081
9020 * gtk_widget_realize() throws:
9021 * "In pixman_region32_init_rect: Invalid rectangle passed"
9022 * when mShell has default 1x1 size.
9024 GtkAllocation allocation = {0, 0, 0, 0};
9025 gtk_widget_get_preferred_width(GTK_WIDGET(mShell), nullptr,
9026 &allocation.width);
9027 gtk_widget_get_preferred_height(GTK_WIDGET(mShell), nullptr,
9028 &allocation.height);
9029 gtk_widget_size_allocate(GTK_WIDGET(mShell), &allocation);
9031 gtk_widget_realize(GTK_WIDGET(mShell));
9032 gtk_widget_reparent(GTK_WIDGET(mContainer), GTK_WIDGET(mShell));
9034 // Label mShell toplevel window so property_notify_event_cb callback
9035 // can find its way home.
9036 g_object_set_data(G_OBJECT(GetToplevelGdkWindow()), "nsWindow", this);
9038 if (AreBoundsSane()) {
9039 GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
9040 LOG(" resize to %d x %d\n", size.width, size.height);
9041 gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
9044 if (visible) {
9045 mNeedsShow = true;
9046 NativeShow(true);
9049 gtk_widget_destroy(tmpWindow);
9052 if (mTransparencyBitmapForTitlebar) {
9053 if (mDrawInTitlebar && mSizeMode == nsSizeMode_Normal && !mIsTiled) {
9054 UpdateTitlebarTransparencyBitmap();
9055 } else {
9056 ClearTransparencyBitmap();
9058 } else {
9059 SetTitlebarRect();
9063 GtkWindow* nsWindow::GetCurrentTopmostWindow() const {
9064 GtkWindow* parentWindow = GTK_WINDOW(GetGtkWidget());
9065 GtkWindow* topmostParentWindow = nullptr;
9066 while (parentWindow) {
9067 topmostParentWindow = parentWindow;
9068 parentWindow = gtk_window_get_transient_for(parentWindow);
9070 return topmostParentWindow;
9073 gint nsWindow::GdkCeiledScaleFactor() {
9074 if (IsTopLevelWindowType()) {
9075 return mCeiledScaleFactor;
9077 if (nsWindow* topmost = GetTopmostWindow()) {
9078 return topmost->mCeiledScaleFactor;
9080 return ScreenHelperGTK::GetGTKMonitorScaleFactor();
9083 double nsWindow::FractionalScaleFactor() {
9084 #ifdef MOZ_WAYLAND
9085 double fractional_scale = [&] {
9086 if (IsTopLevelWindowType()) {
9087 return mFractionalScaleFactor;
9089 if (nsWindow* topmost = GetTopmostWindow()) {
9090 return topmost->mFractionalScaleFactor;
9092 return 0.0;
9093 }();
9094 if (fractional_scale != 0.0) {
9095 return fractional_scale;
9097 #endif
9098 return GdkCeiledScaleFactor();
9101 gint nsWindow::DevicePixelsToGdkCoordRoundUp(int aPixels) {
9102 double scale = FractionalScaleFactor();
9103 return ceil(aPixels / scale);
9106 gint nsWindow::DevicePixelsToGdkCoordRoundDown(int aPixels) {
9107 double scale = FractionalScaleFactor();
9108 return floor(aPixels / scale);
9111 GdkPoint nsWindow::DevicePixelsToGdkPointRoundDown(
9112 const LayoutDeviceIntPoint& aPoint) {
9113 double scale = FractionalScaleFactor();
9114 return {int(aPoint.x / scale), int(aPoint.y / scale)};
9117 GdkRectangle nsWindow::DevicePixelsToGdkRectRoundOut(
9118 const LayoutDeviceIntRect& aRect) {
9119 double scale = FractionalScaleFactor();
9120 int x = floor(aRect.x / scale);
9121 int y = floor(aRect.y / scale);
9122 int right = ceil((aRect.x + aRect.width) / scale);
9123 int bottom = ceil((aRect.y + aRect.height) / scale);
9124 return {x, y, right - x, bottom - y};
9127 GdkRectangle nsWindow::DevicePixelsToGdkSizeRoundUp(
9128 const LayoutDeviceIntSize& aSize) {
9129 double scale = FractionalScaleFactor();
9130 gint width = ceil(aSize.width / scale);
9131 gint height = ceil(aSize.height / scale);
9132 return {0, 0, width, height};
9135 int nsWindow::GdkCoordToDevicePixels(gint aCoord) {
9136 return (int)(aCoord * FractionalScaleFactor());
9139 LayoutDeviceIntPoint nsWindow::GdkEventCoordsToDevicePixels(gdouble aX,
9140 gdouble aY) {
9141 double scale = FractionalScaleFactor();
9142 return LayoutDeviceIntPoint::Floor((float)(aX * scale), (float)(aY * scale));
9145 LayoutDeviceIntPoint nsWindow::GdkPointToDevicePixels(const GdkPoint& aPoint) {
9146 double scale = FractionalScaleFactor();
9147 return LayoutDeviceIntPoint::Floor((float)(aPoint.x * scale),
9148 (float)(aPoint.y * scale));
9151 LayoutDeviceIntRect nsWindow::GdkRectToDevicePixels(const GdkRectangle& aRect) {
9152 double scale = FractionalScaleFactor();
9153 return LayoutDeviceIntRect::RoundIn(
9154 (float)(aRect.x * scale), (float)(aRect.y * scale),
9155 (float)(aRect.width * scale), (float)(aRect.height * scale));
9158 nsresult nsWindow::SynthesizeNativeMouseEvent(
9159 LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
9160 MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
9161 nsIObserver* aObserver) {
9162 LOG("SynthesizeNativeMouseEvent(%d, %d, %d, %d, %d)", aPoint.x.value,
9163 aPoint.y.value, int(aNativeMessage), int(aButton), int(aModifierFlags));
9165 AutoObserverNotifier notifier(aObserver, "mouseevent");
9167 if (!mGdkWindow) {
9168 return NS_OK;
9171 // When a button-press/release event is requested, create it here and put it
9172 // in the event queue. This will not emit a motion event - this needs to be
9173 // done explicitly *before* requesting a button-press/release. You will also
9174 // need to wait for the motion event to be dispatched before requesting a
9175 // button-press/release event to maintain the desired event order.
9176 switch (aNativeMessage) {
9177 case NativeMouseMessage::ButtonDown:
9178 case NativeMouseMessage::ButtonUp: {
9179 GdkEvent event;
9180 memset(&event, 0, sizeof(GdkEvent));
9181 event.type = aNativeMessage == NativeMouseMessage::ButtonDown
9182 ? GDK_BUTTON_PRESS
9183 : GDK_BUTTON_RELEASE;
9184 switch (aButton) {
9185 case MouseButton::ePrimary:
9186 case MouseButton::eMiddle:
9187 case MouseButton::eSecondary:
9188 case MouseButton::eX1:
9189 case MouseButton::eX2:
9190 event.button.button = aButton + 1;
9191 break;
9192 default:
9193 return NS_ERROR_INVALID_ARG;
9195 event.button.state =
9196 KeymapWrapper::ConvertWidgetModifierToGdkState(aModifierFlags);
9197 event.button.window = mGdkWindow;
9198 event.button.time = GDK_CURRENT_TIME;
9200 // Get device for event source
9201 event.button.device = GdkGetPointer();
9203 event.button.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
9204 event.button.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
9206 LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
9207 event.button.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
9208 event.button.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
9210 gdk_event_put(&event);
9211 return NS_OK;
9213 case NativeMouseMessage::Move: {
9214 // We don't support specific events other than button-press/release. In
9215 // all other cases we'll synthesize a motion event that will be emitted by
9216 // gdk_display_warp_pointer().
9217 // XXX How to activate native modifier for the other events?
9218 #ifdef MOZ_WAYLAND
9219 // Impossible to warp the pointer on Wayland.
9220 // For pointer lock, pointer-constraints and relative-pointer are used.
9221 if (GdkIsWaylandDisplay()) {
9222 return NS_OK;
9224 #endif
9225 GdkScreen* screen = gdk_window_get_screen(mGdkWindow);
9226 GdkPoint point = DevicePixelsToGdkPointRoundDown(aPoint);
9227 gdk_device_warp(GdkGetPointer(), screen, point.x, point.y);
9228 return NS_OK;
9230 case NativeMouseMessage::EnterWindow:
9231 case NativeMouseMessage::LeaveWindow:
9232 MOZ_ASSERT_UNREACHABLE("Non supported mouse event on Linux");
9233 return NS_ERROR_INVALID_ARG;
9235 return NS_ERROR_UNEXPECTED;
9238 void nsWindow::CreateAndPutGdkScrollEvent(mozilla::LayoutDeviceIntPoint aPoint,
9239 double aDeltaX, double aDeltaY) {
9240 GdkEvent event;
9241 memset(&event, 0, sizeof(GdkEvent));
9242 event.type = GDK_SCROLL;
9243 event.scroll.window = mGdkWindow;
9244 event.scroll.time = GDK_CURRENT_TIME;
9245 // Get device for event source
9246 GdkDisplay* display = gdk_window_get_display(mGdkWindow);
9247 GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
9248 // See note in nsWindow::SynthesizeNativeTouchpadPan about the device we use
9249 // here.
9250 event.scroll.device = gdk_device_manager_get_client_pointer(device_manager);
9251 event.scroll.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
9252 event.scroll.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
9254 LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
9255 event.scroll.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
9256 event.scroll.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
9258 // The delta values are backwards on Linux compared to Windows and Cocoa,
9259 // hence the negation.
9260 event.scroll.direction = GDK_SCROLL_SMOOTH;
9261 event.scroll.delta_x = -aDeltaX;
9262 event.scroll.delta_y = -aDeltaY;
9264 gdk_event_put(&event);
9267 nsresult nsWindow::SynthesizeNativeMouseScrollEvent(
9268 mozilla::LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage,
9269 double aDeltaX, double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
9270 uint32_t aAdditionalFlags, nsIObserver* aObserver) {
9271 AutoObserverNotifier notifier(aObserver, "mousescrollevent");
9273 if (!mGdkWindow) {
9274 return NS_OK;
9277 CreateAndPutGdkScrollEvent(aPoint, aDeltaX, aDeltaY);
9279 return NS_OK;
9282 nsresult nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId,
9283 TouchPointerState aPointerState,
9284 LayoutDeviceIntPoint aPoint,
9285 double aPointerPressure,
9286 uint32_t aPointerOrientation,
9287 nsIObserver* aObserver) {
9288 AutoObserverNotifier notifier(aObserver, "touchpoint");
9290 if (!mGdkWindow) {
9291 return NS_OK;
9294 GdkEvent event;
9295 memset(&event, 0, sizeof(GdkEvent));
9297 static std::map<uint32_t, GdkEventSequence*> sKnownPointers;
9299 auto result = sKnownPointers.find(aPointerId);
9300 switch (aPointerState) {
9301 case TOUCH_CONTACT:
9302 if (result == sKnownPointers.end()) {
9303 // GdkEventSequence isn't a thing we can instantiate, and never gets
9304 // dereferenced in the gtk code. It's an opaque pointer, the only
9305 // requirement is that it be distinct from other instances of
9306 // GdkEventSequence*.
9307 event.touch.sequence = (GdkEventSequence*)((uintptr_t)aPointerId);
9308 sKnownPointers[aPointerId] = event.touch.sequence;
9309 event.type = GDK_TOUCH_BEGIN;
9310 } else {
9311 event.touch.sequence = result->second;
9312 event.type = GDK_TOUCH_UPDATE;
9314 break;
9315 case TOUCH_REMOVE:
9316 event.type = GDK_TOUCH_END;
9317 if (result == sKnownPointers.end()) {
9318 NS_WARNING("Tried to synthesize touch-end for unknown pointer!");
9319 return NS_ERROR_UNEXPECTED;
9321 event.touch.sequence = result->second;
9322 sKnownPointers.erase(result);
9323 break;
9324 case TOUCH_CANCEL:
9325 event.type = GDK_TOUCH_CANCEL;
9326 if (result == sKnownPointers.end()) {
9327 NS_WARNING("Tried to synthesize touch-cancel for unknown pointer!");
9328 return NS_ERROR_UNEXPECTED;
9330 event.touch.sequence = result->second;
9331 sKnownPointers.erase(result);
9332 break;
9333 case TOUCH_HOVER:
9334 default:
9335 return NS_ERROR_NOT_IMPLEMENTED;
9338 event.touch.window = mGdkWindow;
9339 event.touch.time = GDK_CURRENT_TIME;
9341 GdkDisplay* display = gdk_window_get_display(mGdkWindow);
9342 GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
9343 event.touch.device = gdk_device_manager_get_client_pointer(device_manager);
9345 event.touch.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
9346 event.touch.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
9348 LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
9349 event.touch.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
9350 event.touch.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
9352 gdk_event_put(&event);
9354 return NS_OK;
9357 nsresult nsWindow::SynthesizeNativeTouchPadPinch(
9358 TouchpadGesturePhase aEventPhase, float aScale, LayoutDeviceIntPoint aPoint,
9359 int32_t aModifierFlags) {
9360 if (!mGdkWindow) {
9361 return NS_OK;
9363 GdkEvent event;
9364 memset(&event, 0, sizeof(GdkEvent));
9366 GdkEventTouchpadPinch* touchpad_event =
9367 reinterpret_cast<GdkEventTouchpadPinch*>(&event);
9368 touchpad_event->type = GDK_TOUCHPAD_PINCH;
9370 const ScreenIntPoint widgetToScreenOffset = ViewAs<ScreenPixel>(
9371 WidgetToScreenOffset(),
9372 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
9374 ScreenPoint pointInWindow =
9375 ViewAs<ScreenPixel>(
9376 aPoint,
9377 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent) -
9378 widgetToScreenOffset;
9380 gdouble dx = 0, dy = 0;
9382 switch (aEventPhase) {
9383 case PHASE_BEGIN:
9384 touchpad_event->phase = GDK_TOUCHPAD_GESTURE_PHASE_BEGIN;
9385 mCurrentSynthesizedTouchpadPinch = {pointInWindow, pointInWindow};
9386 break;
9387 case PHASE_UPDATE:
9388 dx = pointInWindow.x - mCurrentSynthesizedTouchpadPinch.mCurrentFocus.x;
9389 dy = pointInWindow.y - mCurrentSynthesizedTouchpadPinch.mCurrentFocus.y;
9390 mCurrentSynthesizedTouchpadPinch.mCurrentFocus = pointInWindow;
9391 touchpad_event->phase = GDK_TOUCHPAD_GESTURE_PHASE_UPDATE;
9392 break;
9393 case PHASE_END:
9394 touchpad_event->phase = GDK_TOUCHPAD_GESTURE_PHASE_END;
9395 break;
9397 default:
9398 return NS_ERROR_NOT_IMPLEMENTED;
9401 touchpad_event->window = mGdkWindow;
9402 // We only set the fields of GdkEventTouchpadPinch which are
9403 // actually used in OnTouchpadPinchEvent().
9404 // GdkEventTouchpadPinch has additional fields.
9405 // If OnTouchpadPinchEvent() is changed to use other fields, this function
9406 // will need to change to set them as well.
9407 touchpad_event->time = GDK_CURRENT_TIME;
9408 touchpad_event->scale = aScale;
9409 touchpad_event->x_root = DevicePixelsToGdkCoordRoundDown(
9410 mCurrentSynthesizedTouchpadPinch.mBeginFocus.x +
9411 ScreenCoord(widgetToScreenOffset.x));
9412 touchpad_event->y_root = DevicePixelsToGdkCoordRoundDown(
9413 mCurrentSynthesizedTouchpadPinch.mBeginFocus.y +
9414 ScreenCoord(widgetToScreenOffset.y));
9416 touchpad_event->x = DevicePixelsToGdkCoordRoundDown(
9417 mCurrentSynthesizedTouchpadPinch.mBeginFocus.x);
9418 touchpad_event->y = DevicePixelsToGdkCoordRoundDown(
9419 mCurrentSynthesizedTouchpadPinch.mBeginFocus.y);
9421 touchpad_event->dx = dx;
9422 touchpad_event->dy = dy;
9424 touchpad_event->state = aModifierFlags;
9426 gdk_event_put(&event);
9428 return NS_OK;
9431 nsresult nsWindow::SynthesizeNativeTouchpadPan(TouchpadGesturePhase aEventPhase,
9432 LayoutDeviceIntPoint aPoint,
9433 double aDeltaX, double aDeltaY,
9434 int32_t aModifierFlags,
9435 nsIObserver* aObserver) {
9436 AutoObserverNotifier notifier(aObserver, "touchpadpanevent");
9438 if (!mGdkWindow) {
9439 return NS_OK;
9442 // This should/could maybe send GdkEventTouchpadSwipe events, however we don't
9443 // currently consume those (either real user input or testing events). So we
9444 // send gdk scroll events to be more like what we do for real user input. If
9445 // we start consuming GdkEventTouchpadSwipe and get those hooked up to swipe
9446 // to nav, then maybe we should test those too.
9448 mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase = Some(aEventPhase);
9449 MOZ_ASSERT(mCurrentSynthesizedTouchpadPan.mSavedObserver == 0);
9450 mCurrentSynthesizedTouchpadPan.mSavedObserver = notifier.SaveObserver();
9452 // Note that CreateAndPutGdkScrollEvent sets the device source for the created
9453 // event as the "client pointer" (a kind of default device) which will
9454 // probably be of type mouse. We would ideally want to set the device of the
9455 // created event to be a touchpad, but the system might not have a touchpad.
9456 // To get around this we use
9457 // mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase being something to
9458 // indicate that we should treat the source of the event as touchpad in
9459 // OnScrollEvent.
9460 CreateAndPutGdkScrollEvent(aPoint, aDeltaX, aDeltaY);
9462 return NS_OK;
9465 nsWindow::GtkWindowDecoration nsWindow::GetSystemGtkWindowDecoration() {
9466 static GtkWindowDecoration sGtkWindowDecoration = [] {
9467 // Allow MOZ_GTK_TITLEBAR_DECORATION to override our heuristics
9468 if (const char* decorationOverride =
9469 getenv("MOZ_GTK_TITLEBAR_DECORATION")) {
9470 if (strcmp(decorationOverride, "none") == 0) {
9471 return GTK_DECORATION_NONE;
9473 if (strcmp(decorationOverride, "client") == 0) {
9474 return GTK_DECORATION_CLIENT;
9476 if (strcmp(decorationOverride, "system") == 0) {
9477 return GTK_DECORATION_SYSTEM;
9481 // nsWindow::GetSystemGtkWindowDecoration can be called from various
9482 // threads so we can't use gfxPlatformGtk here.
9483 if (GdkIsWaylandDisplay()) {
9484 return GTK_DECORATION_CLIENT;
9487 // GTK_CSD forces CSD mode - use also CSD because window manager
9488 // decorations does not work with CSD.
9489 // We check GTK_CSD as well as gtk_window_should_use_csd() does.
9490 if (const char* csdOverride = getenv("GTK_CSD")) {
9491 return *csdOverride == '0' ? GTK_DECORATION_NONE : GTK_DECORATION_CLIENT;
9494 // TODO: Consider switching this to GetDesktopEnvironmentIdentifier().
9495 const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
9496 if (!currentDesktop) {
9497 return GTK_DECORATION_NONE;
9499 if (strstr(currentDesktop, "i3")) {
9500 return GTK_DECORATION_NONE;
9503 // Tested desktops: pop:GNOME, KDE, Enlightenment, LXDE, openbox, MATE,
9504 // X-Cinnamon, Pantheon, Deepin, GNOME, LXQt, Unity.
9505 return GTK_DECORATION_CLIENT;
9506 }();
9507 return sGtkWindowDecoration;
9510 bool nsWindow::TitlebarUseShapeMask() {
9511 static int useShapeMask = []() {
9512 // Don't use titlebar shape mask on Wayland
9513 if (!GdkIsX11Display()) {
9514 return false;
9517 // We can't use shape masks on Mutter/X.org as we can't resize Firefox
9518 // window there (Bug 1530252).
9519 if (IsGnomeDesktopEnvironment()) {
9520 return false;
9523 return Preferences::GetBool("widget.titlebar-x11-use-shape-mask", false);
9524 }();
9525 return useShapeMask;
9528 int32_t nsWindow::RoundsWidgetCoordinatesTo() { return GdkCeiledScaleFactor(); }
9530 void nsWindow::GetCompositorWidgetInitData(
9531 mozilla::widget::CompositorWidgetInitData* aInitData) {
9532 nsCString displayName;
9534 LOG("nsWindow::GetCompositorWidgetInitData");
9536 *aInitData = mozilla::widget::GtkCompositorWidgetInitData(
9537 GetX11Window(), displayName, GetShapedState(), GdkIsX11Display(),
9538 GetClientSize());
9540 #ifdef MOZ_X11
9541 if (GdkIsX11Display()) {
9542 // Make sure the window XID is propagated to X server, we can fail otherwise
9543 // in GPU process (Bug 1401634).
9544 Display* display = DefaultXDisplay();
9545 XFlush(display);
9546 displayName = nsCString(XDisplayString(display));
9548 #endif
9551 #ifdef MOZ_X11
9552 /* XApp progress support currently works by setting a property
9553 * on a window with this Atom name. A supporting window manager
9554 * will notice this and pass it along to whatever handling has
9555 * been implemented on that end (e.g. passing it on to a taskbar
9556 * widget.) There is no issue if WM support is lacking, this is
9557 * simply ignored in that case.
9559 * See https://github.com/linuxmint/xapps/blob/master/libxapp/xapp-gtk-window.c
9560 * for further details.
9563 # define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS"
9565 static void set_window_hint_cardinal(Window xid, const gchar* atom_name,
9566 gulong cardinal) {
9567 GdkDisplay* display;
9569 display = gdk_display_get_default();
9571 if (cardinal > 0) {
9572 XChangeProperty(GDK_DISPLAY_XDISPLAY(display), xid,
9573 gdk_x11_get_xatom_by_name_for_display(display, atom_name),
9574 XA_CARDINAL, 32, PropModeReplace, (guchar*)&cardinal, 1);
9575 } else {
9576 XDeleteProperty(GDK_DISPLAY_XDISPLAY(display), xid,
9577 gdk_x11_get_xatom_by_name_for_display(display, atom_name));
9580 #endif // MOZ_X11
9582 void nsWindow::SetProgress(unsigned long progressPercent) {
9583 #ifdef MOZ_X11
9585 if (!GdkIsX11Display()) {
9586 return;
9589 if (!mShell) {
9590 return;
9593 progressPercent = MIN(progressPercent, 100);
9595 set_window_hint_cardinal(GDK_WINDOW_XID(GetToplevelGdkWindow()),
9596 PROGRESS_HINT, progressPercent);
9597 #endif // MOZ_X11
9600 #ifdef MOZ_X11
9601 void nsWindow::SetCompositorHint(WindowComposeRequest aState) {
9602 if (!GdkIsX11Display()) {
9603 return;
9606 gulong value = aState;
9607 GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
9608 gdk_property_change(GetToplevelGdkWindow(),
9609 gdk_atom_intern("_NET_WM_BYPASS_COMPOSITOR", FALSE),
9610 cardinal_atom,
9611 32, // format
9612 GDK_PROP_MODE_REPLACE, (guchar*)&value, 1);
9614 #endif
9616 nsresult nsWindow::SetSystemFont(const nsCString& aFontName) {
9617 GtkSettings* settings = gtk_settings_get_default();
9618 g_object_set(settings, "gtk-font-name", aFontName.get(), nullptr);
9619 return NS_OK;
9622 nsresult nsWindow::GetSystemFont(nsCString& aFontName) {
9623 GtkSettings* settings = gtk_settings_get_default();
9624 gchar* fontName = nullptr;
9625 g_object_get(settings, "gtk-font-name", &fontName, nullptr);
9626 if (fontName) {
9627 aFontName.Assign(fontName);
9628 g_free(fontName);
9630 return NS_OK;
9633 already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
9634 nsCOMPtr<nsIWidget> window = new nsWindow();
9635 return window.forget();
9638 already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
9639 nsCOMPtr<nsIWidget> window = new nsWindow();
9640 return window.forget();
9643 #ifdef MOZ_WAYLAND
9644 static void relative_pointer_handle_relative_motion(
9645 void* data, struct zwp_relative_pointer_v1* pointer, uint32_t time_hi,
9646 uint32_t time_lo, wl_fixed_t dx_w, wl_fixed_t dy_w, wl_fixed_t dx_unaccel_w,
9647 wl_fixed_t dy_unaccel_w) {
9648 RefPtr<nsWindow> window(reinterpret_cast<nsWindow*>(data));
9650 WidgetMouseEvent event(true, eMouseMove, window, WidgetMouseEvent::eReal);
9652 double scale = window->FractionalScaleFactor();
9653 event.mRefPoint = window->GetNativePointerLockCenter();
9654 event.mRefPoint.x += int(wl_fixed_to_double(dx_w) * scale);
9655 event.mRefPoint.y += int(wl_fixed_to_double(dy_w) * scale);
9657 event.AssignEventTime(window->GetWidgetEventTime(time_lo));
9658 window->DispatchInputEvent(&event);
9661 static const struct zwp_relative_pointer_v1_listener relative_pointer_listener =
9663 relative_pointer_handle_relative_motion,
9666 void nsWindow::SetNativePointerLockCenter(
9667 const LayoutDeviceIntPoint& aLockCenter) {
9668 mNativePointerLockCenter = aLockCenter;
9671 void nsWindow::LockNativePointer() {
9672 if (!GdkIsWaylandDisplay()) {
9673 return;
9676 auto* waylandDisplay = WaylandDisplayGet();
9678 auto* pointerConstraints = waylandDisplay->GetPointerConstraints();
9679 if (!pointerConstraints) {
9680 return;
9683 auto* relativePointerMgr = waylandDisplay->GetRelativePointerManager();
9684 if (!relativePointerMgr) {
9685 return;
9688 GdkDisplay* display = gdk_display_get_default();
9690 GdkDeviceManager* manager = gdk_display_get_device_manager(display);
9691 MOZ_ASSERT(manager);
9693 GdkDevice* device = gdk_device_manager_get_client_pointer(manager);
9694 if (!device) {
9695 NS_WARNING("Could not find Wayland pointer to lock");
9696 return;
9698 wl_pointer* pointer = gdk_wayland_device_get_wl_pointer(device);
9699 MOZ_ASSERT(pointer);
9701 wl_surface* surface =
9702 gdk_wayland_window_get_wl_surface(GetToplevelGdkWindow());
9703 if (!surface) {
9704 /* Can be null when the window is hidden.
9705 * Though it's unlikely that a lock request comes in that case, be
9706 * defensive. */
9707 return;
9710 if (mLockedPointer || mRelativePointer) {
9711 UnlockNativePointer();
9714 mLockedPointer = zwp_pointer_constraints_v1_lock_pointer(
9715 pointerConstraints, surface, pointer, nullptr,
9716 ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
9717 if (!mLockedPointer) {
9718 NS_WARNING("Could not lock Wayland pointer");
9719 return;
9722 mRelativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer(
9723 relativePointerMgr, pointer);
9724 if (!mRelativePointer) {
9725 NS_WARNING("Could not create relative Wayland pointer");
9726 zwp_locked_pointer_v1_destroy(mLockedPointer);
9727 mLockedPointer = nullptr;
9728 return;
9731 zwp_relative_pointer_v1_add_listener(mRelativePointer,
9732 &relative_pointer_listener, this);
9735 void nsWindow::UnlockNativePointer() {
9736 if (!GdkIsWaylandDisplay()) {
9737 return;
9739 if (mRelativePointer) {
9740 zwp_relative_pointer_v1_destroy(mRelativePointer);
9741 mRelativePointer = nullptr;
9743 if (mLockedPointer) {
9744 zwp_locked_pointer_v1_destroy(mLockedPointer);
9745 mLockedPointer = nullptr;
9748 #endif
9750 bool nsWindow::GetTopLevelWindowActiveState(nsIFrame* aFrame) {
9751 // Used by window frame and button box rendering. We can end up in here in
9752 // the content process when rendering one of these moz styles freely in a
9753 // page. Fail in this case, there is no applicable window focus state.
9754 if (!XRE_IsParentProcess()) {
9755 return false;
9757 // All headless windows are considered active so they are painted.
9758 if (gfxPlatform::IsHeadless()) {
9759 return true;
9761 // Get the widget. nsIFrame's GetNearestWidget walks up the view chain
9762 // until it finds a real window.
9763 nsWindow* window = static_cast<nsWindow*>(aFrame->GetNearestWidget());
9764 if (!window) {
9765 return false;
9767 return !window->mTitlebarBackdropState;
9770 static nsIFrame* FindTitlebarFrame(nsIFrame* aFrame) {
9771 for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
9772 StyleAppearance appearance =
9773 childFrame->StyleDisplay()->EffectiveAppearance();
9774 if (appearance == StyleAppearance::MozWindowTitlebar ||
9775 appearance == StyleAppearance::MozWindowTitlebarMaximized) {
9776 return childFrame;
9779 if (nsIFrame* foundFrame = FindTitlebarFrame(childFrame)) {
9780 return foundFrame;
9783 return nullptr;
9786 nsIFrame* nsWindow::GetFrame() const {
9787 nsView* view = nsView::GetViewFor(this);
9788 if (!view) {
9789 return nullptr;
9791 return view->GetFrame();
9794 void nsWindow::UpdateMozWindowActive() {
9795 // Update activation state for the :-moz-window-inactive pseudoclass.
9796 // Normally, this follows focus; we override it here to follow
9797 // GDK_WINDOW_STATE_FOCUSED.
9798 if (mozilla::dom::Document* document = GetDocument()) {
9799 if (nsPIDOMWindowOuter* window = document->GetWindow()) {
9800 if (RefPtr<mozilla::dom::BrowsingContext> bc =
9801 window->GetBrowsingContext()) {
9802 bc->SetIsActiveBrowserWindow(!mTitlebarBackdropState);
9808 void nsWindow::ForceTitlebarRedraw() {
9809 MOZ_ASSERT(mDrawInTitlebar, "We should not redraw invisible titlebar.");
9811 if (!mWidgetListener || !mWidgetListener->GetPresShell()) {
9812 return;
9815 nsIFrame* frame = GetFrame();
9816 if (!frame) {
9817 return;
9820 frame = FindTitlebarFrame(frame);
9821 if (frame) {
9822 nsIContent* content = frame->GetContent();
9823 if (content) {
9824 nsLayoutUtils::PostRestyleEvent(content->AsElement(), RestyleHint{0},
9825 nsChangeHint_RepaintFrame);
9830 void nsWindow::LockAspectRatio(bool aShouldLock) {
9831 if (!gUseAspectRatio) {
9832 return;
9835 if (aShouldLock) {
9836 int decWidth = 0, decHeight = 0;
9837 AddCSDDecorationSize(&decWidth, &decHeight);
9839 float width =
9840 DevicePixelsToGdkCoordRoundDown(mLastSizeRequest.width) + decWidth;
9841 float height =
9842 DevicePixelsToGdkCoordRoundDown(mLastSizeRequest.height) + decHeight;
9844 mAspectRatio = width / height;
9845 LOG("nsWindow::LockAspectRatio() width %f height %f aspect %f", width,
9846 height, mAspectRatio);
9847 } else {
9848 mAspectRatio = 0.0;
9849 LOG("nsWindow::LockAspectRatio() removed aspect ratio");
9852 ApplySizeConstraints();
9855 nsWindow* nsWindow::GetFocusedWindow() { return gFocusWindow; }
9857 #ifdef MOZ_WAYLAND
9858 bool nsWindow::SetEGLNativeWindowSize(
9859 const LayoutDeviceIntSize& aEGLWindowSize) {
9860 if (!GdkIsWaylandDisplay()) {
9861 return true;
9864 // SetEGLNativeWindowSize() is called from renderer/compositor thread,
9865 // make sure nsWindow is not destroyed.
9866 bool paint = false;
9868 // See NS_NATIVE_EGL_WINDOW why we can't block here.
9869 if (mDestroyMutex.TryLock()) {
9870 if (!mIsDestroyed) {
9871 gint scale = GdkCeiledScaleFactor();
9872 # ifdef MOZ_LOGGING
9873 if (LOG_ENABLED()) {
9874 static uintptr_t lastSizeLog = 0;
9875 uintptr_t sizeLog = uintptr_t(this) + aEGLWindowSize.width +
9876 aEGLWindowSize.height + scale +
9877 aEGLWindowSize.width / scale +
9878 aEGLWindowSize.height / scale;
9879 if (lastSizeLog != sizeLog) {
9880 lastSizeLog = sizeLog;
9881 LOG("nsWindow::SetEGLNativeWindowSize() %d x %d scale %d (unscaled "
9882 "%d x "
9883 "%d)",
9884 aEGLWindowSize.width, aEGLWindowSize.height, scale,
9885 aEGLWindowSize.width / scale, aEGLWindowSize.height / scale);
9888 # endif
9889 paint = moz_container_wayland_egl_window_set_size(
9890 mContainer, aEGLWindowSize.ToUnknownSize(), scale);
9892 mDestroyMutex.Unlock();
9894 return paint;
9896 #endif
9898 nsWindow* nsWindow::GetWindow(GdkWindow* window) {
9899 return get_window_for_gdk_window(window);
9902 void nsWindow::ClearRenderingQueue() {
9903 LOG("nsWindow::ClearRenderingQueue()");
9905 if (mWidgetListener) {
9906 mWidgetListener->RequestWindowClose(this);
9908 DestroyLayerManager();
9911 void nsWindow::DisableRendering() {
9912 LOG("nsWindow::DisableRendering()");
9914 if (mGdkWindow) {
9915 if (mIMContext) {
9916 mIMContext->SetGdkWindow(nullptr);
9918 g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", nullptr);
9919 mGdkWindow = nullptr;
9922 // Until Bug 1654938 is fixed we delete layer manager for hidden popups,
9923 // otherwise it can easily hold 1GB+ memory for long time.
9924 if (mWindowType == WindowType::Popup) {
9925 DestroyLayerManager();
9926 mSurfaceProvider.CleanupResources();
9927 } else {
9928 #ifdef MOZ_WAYLAND
9929 // Widget is backed by OpenGL EGLSurface created over wl_surface
9930 // owned by mContainer.
9931 // RenderCompositorEGL::Resume() deletes recent EGLSurface based on
9932 // wl_surface owned by mContainer and creates a new fallback EGLSurface.
9933 // Then we can delete wl_surface in moz_container_wayland_unmap().
9934 // We don't want to pause compositor as it may lead to whole
9935 // browser freeze (Bug 1777664).
9937 // We don't need to do such operation for SW backend as
9938 // WindowSurfaceWaylandMB::Commit() gets wl_surface from
9939 // MozContainer every commit.
9940 if (moz_container_wayland_has_egl_window(mContainer) &&
9941 mCompositorWidgetDelegate) {
9942 if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) {
9943 // Call DisableRendering() to make GtkCompositorWidget hidden.
9944 // Then SendResume() will create fallback EGLSurface, see
9945 // GLContextEGL::CreateEGLSurfaceForCompositorWidget().
9946 mCompositorWidgetDelegate->DisableRendering();
9947 remoteRenderer->SendResume();
9948 mCompositorWidgetDelegate->EnableRendering(GetX11Window(),
9949 GetShapedState());
9952 #endif
9956 // Apply workaround for Mutter compositor bug (mzbz#1777269).
9958 // When we open a popup window (tooltip for instance) attached to
9959 // GDK_WINDOW_TYPE_HINT_UTILITY parent popup, Mutter compositor sends bogus
9960 // leave/enter events to the GDK_WINDOW_TYPE_HINT_UTILITY popup.
9961 // That leads to immediate tooltip close. As a workaround ignore these
9962 // bogus events.
9964 // We need to check two affected window types:
9966 // - toplevel window with at least two child popups where the first one is
9967 // GDK_WINDOW_TYPE_HINT_UTILITY.
9968 // - GDK_WINDOW_TYPE_HINT_UTILITY popup with a child popup
9970 // We need to mask two bogus leave/enter sequences:
9971 // 1) Leave (popup) -> Enter (toplevel)
9972 // 2) Leave (toplevel) -> Enter (popup)
9974 // TODO: persistent (non-tracked) popups with tooltip/child popups?
9976 bool nsWindow::ApplyEnterLeaveMutterWorkaround() {
9977 // Leave (toplevel) case
9978 if (mWindowType == WindowType::TopLevel && mWaylandPopupNext &&
9979 mWaylandPopupNext->mWaylandPopupNext &&
9980 gtk_window_get_type_hint(GTK_WINDOW(mWaylandPopupNext->GetGtkWidget())) ==
9981 GDK_WINDOW_TYPE_HINT_UTILITY) {
9982 LOG("nsWindow::ApplyEnterLeaveMutterWorkaround(): leave toplevel");
9983 return true;
9985 // Leave (popup) case
9986 if (IsWaylandPopup() && mWaylandPopupNext &&
9987 gtk_window_get_type_hint(GTK_WINDOW(mShell)) ==
9988 GDK_WINDOW_TYPE_HINT_UTILITY) {
9989 LOG("nsWindow::ApplyEnterLeaveMutterWorkaround(): leave popup");
9990 return true;
9992 return false;
9995 void nsWindow::NotifyOcclusionState(OcclusionState aState) {
9996 if (!IsTopLevelWindowType()) {
9997 return;
10000 bool isFullyOccluded = aState == OcclusionState::OCCLUDED;
10001 if (mIsFullyOccluded == isFullyOccluded) {
10002 return;
10004 mIsFullyOccluded = isFullyOccluded;
10006 LOG("nsWindow::NotifyOcclusionState() mIsFullyOccluded %d", mIsFullyOccluded);
10007 if (mWidgetListener) {
10008 mWidgetListener->OcclusionStateChanged(mIsFullyOccluded);
10012 void nsWindow::SetDragSource(GdkDragContext* aSourceDragContext) {
10013 mSourceDragContext = aSourceDragContext;
10014 if (IsPopup() &&
10015 (widget::GdkIsWaylandDisplay() || widget::IsXWaylandProtocol())) {
10016 if (auto* menuPopupFrame = GetMenuPopupFrame(GetFrame())) {
10017 menuPopupFrame->SetIsDragSource(!!aSourceDragContext);