1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
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/. */
13 #include <gdk/gdkkeysyms.h>
16 #include "VsyncSource.h"
17 #include "gfx2DGlue.h"
18 #include "gfxContext.h"
19 #include "gfxImageSurface.h"
20 #include "gfxPlatformGtk.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"
69 # include "mozilla/X11Util.h"
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"
82 #include "nsIGSettingsService.h"
83 #include "nsIInterfaceRequestorUtils.h"
84 #include "nsImageToPixbuf.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"
95 #include "nsWidgetsCID.h"
96 #include "nsViewManager.h"
97 #include "nsXPLookAndFeel.h"
100 #include "ScreenHelperGTK.h"
101 #include "SystemTimeConverter.h"
102 #include "WidgetUtilsGtk.h"
103 #include "NativeMenuGtk.h"
106 # include "mozilla/a11y/LocalAccessible.h"
107 # include "mozilla/a11y/Platform.h"
108 # include "nsAccessibilityService.h"
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"
123 # include <gdk/gdkkeysyms-compat.h>
124 # include "nsIClipboard.h"
128 using namespace mozilla
;
129 using namespace mozilla::gfx
;
130 using namespace mozilla::layers
;
131 using namespace mozilla::widget
;
133 using mozilla::gl::GLContextEGL
;
134 using mozilla::gl::GLContextGLX
;
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
{
156 gdouble x_root
, y_root
;
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);
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
,
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
,
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
);
234 #endif /* __cplusplus */
236 static GdkFilterReturn
popup_take_focus_filter(GdkXEvent
* gdk_xevent
,
237 GdkEvent
* event
, gpointer data
);
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
,
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
,
255 GtkSelectionData
* aSelectionData
,
256 guint aInfo
, guint32 aTime
,
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();
277 class CurrentX11TimeGetter
{
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()) {
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);
299 gboolean
PropertyNotifyHandler(GtkWidget
* aWidget
, GdkEventProperty
* aEvent
) {
300 if (aEvent
->atom
!= gdk_x11_xatom_to_atom(TimeStampPropAtom())) {
304 guint32 eventTime
= aEvent
->time
;
305 TimeStamp lowerBound
= mAsyncUpdateStart
;
307 TimeConverter().CompensateForBackwardsSkew(eventTime
, lowerBound
);
308 mAsyncUpdateStart
= TimeStamp();
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
321 TimeStamp mAsyncUpdateStart
;
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
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
) {
350 return (width
+ 7) / 8;
352 return cairo_format_stride_for_width(CAIRO_FORMAT_A1
, width
);
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");
366 idleService
->ResetIdleTimeOut(0);
369 guint timestamp
= gdk_event_get_time(static_cast<GdkEvent
*>(aGdkEvent
));
370 if (timestamp
== GDK_CURRENT_TIME
) {
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."); \
394 : mTitlebarRectMutex("nsWindow::mTitlebarRectMutex"),
395 mDestroyMutex("nsWindow::mDestroyMutex"),
402 mHandleTouchEvent(false),
404 mCompositedScreen(gdk_screen_is_composited(gdk_screen_get_default())),
405 mIsAccelerated(false),
407 mWindowShouldStartDragging(false),
408 mHasMappedToplevel(false),
409 mRetryPointerGrab(false),
410 mPanInProgress(false),
411 mTitlebarBackdropState(false),
412 mIsChildWindow(false),
415 mIsTransparent(false),
416 mHasReceivedSizeAllocate(false),
417 mWidgetCursorLocked(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),
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.
447 // Wayland provides clipboard data to application on focus-in event
448 // so we need to init our clipboard hooks before we create window
450 if (GdkIsWaylandDisplay()) {
451 nsCOMPtr
<nsIClipboard
> clipboard
=
452 do_GetService("@mozilla.org/widget/clipboard;1");
453 NS_ASSERTION(clipboard
, "Failed to init clipboard!");
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()");
469 void nsWindow::ReleaseGlobals() {
470 for (auto& cursor
: gCursorCache
) {
472 g_object_unref(cursor
);
478 void nsWindow::DispatchActivateEvent(void) {
480 DispatchActivateEventAccessible();
481 #endif // ACCESSIBILITY
483 if (mWidgetListener
) mWidgetListener
->WindowActivated();
486 void nsWindow::DispatchDeactivateEvent() {
487 if (mWidgetListener
) {
488 mWidgetListener
->WindowDeactivated();
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());
532 nsIWidgetListener
* nsWindow::GetListener() {
533 return mAttachedWidgetListener
? mAttachedWidgetListener
: mWidgetListener
;
536 nsresult
nsWindow::DispatchEvent(WidgetGUIEvent
* aEvent
,
537 nsEventStatus
& aStatus
) {
539 debug_DumpEvent(stdout
, aEvent
->mWidget
, aEvent
, "something", 0);
541 aStatus
= nsEventStatus_eIgnore
;
542 nsIWidgetListener
* listener
= GetListener();
544 aStatus
= listener
->HandleEvent(aEvent
, mUseAttachedEvents
);
550 void nsWindow::OnDestroy(void) {
551 if (mOnDestroyCalled
) {
555 mOnDestroyCalled
= true;
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();
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
) {
582 LOG("nsWindow::Destroy\n");
584 MutexAutoLock
lock(mDestroyMutex
);
588 MozClearHandleID(mCompositorPauseTimeoutID
, g_source_remove
);
591 // Shut down our local vsync source
592 if (mWaylandVsyncSource
) {
593 mWaylandVsyncSource
->Shutdown();
594 mWaylandVsyncSource
= nullptr;
596 mWaylandVsyncDispatcher
= nullptr;
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({});
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);
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
) {
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
);
652 mContainer
= nullptr;
654 MOZ_ASSERT(!mGdkWindow
,
655 "mGdkWindow should be NULL when mContainer is destroyed");
658 if (mRootAccessible
) {
659 mRootAccessible
= nullptr;
663 // Save until last because OnDestroy() may cause us to be deleted.
667 nsIWidget
* nsWindow::GetParent() { return mParent
; }
669 float nsWindow::GetDPI() {
671 nsCOMPtr
<nsIScreen
> screen
= GetWidgetScreen();
673 screen
->GetDpi(&dpi
);
678 double nsWindow::GetDefaultScaleInternal() { return FractionalScaleFactor(); }
680 DesktopToLayoutDeviceScale
nsWindow::GetDesktopToDeviceScale() {
682 if (GdkIsWaylandDisplay()) {
683 return DesktopToLayoutDeviceScale(FractionalScaleFactor());
687 // In Gtk/X11, we manage windows using device pixels.
688 return DesktopToLayoutDeviceScale(1.0);
691 DesktopToLayoutDeviceScale
nsWindow::GetDesktopToDeviceScaleByScreen() {
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
699 if (GdkIsWaylandDisplay()) {
700 nsView
* view
= nsView::GetViewFor(this);
702 nsView
* parentView
= view
->GetParent();
704 nsIWidget
* parentWidget
= parentView
->GetNearestWidget(nullptr);
706 return DesktopToLayoutDeviceScale(
707 parentWidget
->RoundsWidgetCoordinatesTo());
709 NS_WARNING("Widget has no parent");
712 NS_WARNING("Cannot find widget view");
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");
727 nsCOMPtr
<nsIWidget
> kungFuDeathGrip
= this;
729 mParent
->RemoveChild(this);
731 mParent
= aNewParent
;
733 // We're already deleted, quit.
734 if (!mGdkWindow
|| mIsDestroyed
|| !aNewParent
) {
737 aNewParent
->AddChild(this);
739 auto* newParent
= static_cast<nsWindow
*>(aNewParent
);
741 // New parent is deleted, quit.
742 if (newParent
->mIsDestroyed
) {
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
) {
759 if (IsSmallPopup()) {
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
;
772 void nsWindow::ReparentNativeWidget(nsIWidget
* aNewParent
) {
773 MOZ_ASSERT(aNewParent
, "null widget");
774 MOZ_ASSERT(!mIsDestroyed
, "");
775 MOZ_ASSERT(!static_cast<nsWindow
*>(aNewParent
)->mIsDestroyed
, "");
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
);
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;
806 LayoutDeviceIntPoint
nsWindow::GetScreenEdgeSlop() {
807 if (DrawsToCSDTitlebar()) {
808 return GetClientOffset();
813 void nsWindow::ConstrainPosition(DesktopIntPoint
& aPoint
) {
814 if (!mShell
|| GdkIsWaylandDisplay()) {
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");
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
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.
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()) {
882 GtkBorder decorationSize
= GetCSDDecorationSize(IsPopup());
883 *aWidth
+= decorationSize
.left
+ decorationSize
.right
;
884 *aHeight
+= decorationSize
.top
+ decorationSize
.bottom
;
888 bool nsWindow::GetCSDDecorationOffset(int* aDx
, int* aDy
) {
889 if (!DrawsToCSDTitlebar()) {
892 GtkBorder decorationSize
= GetCSDDecorationSize(IsPopup());
893 *aDx
= decorationSize
.left
;
894 *aDy
= decorationSize
.top
;
899 void nsWindow::ApplySizeConstraints() {
901 GdkGeometry geometry
;
903 DevicePixelsToGdkCoordRoundUp(mSizeConstraints
.mMinSize
.width
);
904 geometry
.min_height
=
905 DevicePixelsToGdkCoordRoundUp(mSizeConstraints
.mMinSize
.height
);
907 DevicePixelsToGdkCoordRoundDown(mSizeConstraints
.mMaxSize
.width
);
908 geometry
.max_height
=
909 DevicePixelsToGdkCoordRoundDown(mSizeConstraints
.mMaxSize
.height
);
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
) {
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!");
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
954 if ((aState
&& !AreBoundsSane()) || !mCreated
) {
955 LOG("\tbounds are insane or window hasn't been created yet\n");
960 // If someone is hiding this widget, clear any needing show flag.
961 if (!aState
) mNeedsShow
= false;
964 if (aState
&& a11y::ShouldA11yBeEnabled()) CreateRootAccessible();
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();
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
;
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
);
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
;
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);
1010 if (!moved
&& !resized
) {
1011 LOG(" not moved or resized, quit");
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
);
1042 void nsWindow::Resize(double aWidth
, double aHeight
, bool aRepaint
) {
1043 LOG("nsWindow::Resize %f %f\n", aWidth
, aHeight
);
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
,
1054 LOG("nsWindow::Resize [%f,%f] -> [%f x %f] repaint %d\n", aX
, aY
, aWidth
,
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
) {
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");
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
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");
1091 // XXX Should we do some AreBoundsSane check here?
1097 LOG(" is not created, return.\n");
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()) {
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
1148 GdkWindow
* window
= GetToplevelGdkWindow();
1149 if (!window
|| gdk_window_get_window_type(window
) != GDK_WINDOW_TEMP
) {
1153 LOG("nsWindow::WaylandPopupRemoveNegativePosition()");
1156 gtk_window_get_position(GTK_WINDOW(mShell
), &x
, &y
);
1157 bool moveBack
= (x
< 0 && y
< 0);
1159 gtk_window_move(GTK_WINDOW(mShell
), 0, 0);
1168 gdk_window_get_geometry(window
, &x
, &y
, nullptr, nullptr);
1169 if (x
< 0 && y
< 0) {
1170 gdk_window_move(window
, 0, 0);
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
);
1186 // Popup position was checked before gdk_window_move_to_rect() callback
1188 if (mPopupUseMoveToRect
&& mWaitingForMoveToRectCallback
) {
1189 LOG(" active move-to-rect callback, show it as is");
1190 gtk_widget_show(mShell
);
1194 if (gtk_widget_is_visible(mShell
)) {
1195 LOG(" is already visible, quit");
1200 bool moved
= WaylandPopupRemoveNegativePosition(&x
, &y
);
1201 gtk_widget_show(mShell
);
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
1213 if (mWaylandPopupNext
) {
1214 mWaylandPopupNext
->mPopupChanged
= true;
1218 nsWindow
* nsWindow::WaylandPopupFindLast(nsWindow
* aPopup
) {
1219 while (aPopup
&& aPopup
->mWaylandPopupNext
) {
1220 aPopup
= aPopup
->mWaylandPopupNext
;
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.
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;
1254 LOG(" Clearing mMoveToRectPopupSize\n");
1255 mMoveToRectPopupSize
= {};
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 "
1261 moz_container_wayland_clear_waiting_to_show_flag(mContainer
);
1262 ClearRenderingQueue();
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);
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;
1293 nsWindow
* next
= popup
->mWaylandPopupNext
;
1294 if (popup
->mPopupClosed
) {
1295 popup
->HideWaylandPopupWindow(/* aTemporaryHide */ false,
1296 /* aRemoveFromPopupList */ true);
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() {
1319 LOG("nsWindow::WaylandPopupCloseOrphanedPopups");
1320 MOZ_ASSERT(mWaylandToplevel
== nullptr, "Should be called on toplevel only!");
1322 nsWindow
* popup
= mWaylandPopupNext
;
1323 bool dangling
= false;
1326 moz_container_wayland_is_waiting_to_show(popup
->GetMozContainer())) {
1327 LOG(" popup [%p] is waiting to show, close all child popups", popup
);
1329 } else if (dangling
) {
1330 popup
->WaylandPopupMarkAsClosed();
1332 popup
= popup
->mWaylandPopupNext
;
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) {
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
);
1357 remotePopup
= remotePopup
->mWaylandPopupNext
;
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
) {
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
;
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];
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
;
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
;
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);
1471 void nsWindow::WaylandPopupHierarchyShowTemporaryHidden() {
1472 LOG("nsWindow::WaylandPopupHierarchyShowTemporaryHidden()");
1473 nsWindow
* popup
= this;
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
;
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
;
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()));
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
,
1517 if (popup
->WaylandPopupIsFirst()) {
1518 LOG(" popup [%p] has toplevel as parent", popup
);
1519 popup
->mRelativePopupPosition
= popup
->mPopupPosition
;
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();
1554 bool nsWindow::WaylandPopupIsContextMenu() {
1555 nsMenuPopupFrame
* popupFrame
= GetMenuPopupFrame(GetFrame());
1559 return popupFrame
->IsContextMenu();
1562 bool nsWindow::WaylandPopupIsPermanent() {
1563 nsMenuPopupFrame
* popupFrame
= GetMenuPopupFrame(GetFrame());
1565 // We can always hide popups without frames.
1568 return popupFrame
->IsNoAutoHide();
1571 bool nsWindow::WaylandPopupIsAnchored() {
1572 nsMenuPopupFrame
* popupFrame
= GetMenuPopupFrame(GetFrame());
1574 // We can always hide popups without frames.
1577 return !!popupFrame
->GetAnchor();
1580 bool nsWindow::IsWidgetOverflowWindow() {
1581 if (this->GetFrame() && this->GetFrame()->GetContent()->GetID()) {
1583 this->GetFrame()->GetContent()->GetID()->ToUTF8String(nodeId
);
1584 return nodeId
.Equals("widget-overflow");
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
)) {
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
);
1612 void nsWindow::LogPopupHierarchy() {
1613 if (!LOG_ENABLED()) {
1617 LOG("Widget Popup Hierarchy:\n");
1618 if (!mWaylandToplevel
->mWaylandPopupNext
) {
1622 nsWindow
* popup
= mWaylandToplevel
->mWaylandPopupNext
;
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
);
1633 popup
= popup
->mWaylandPopupNext
;
1637 LOG("Layout Popup Hierarchy:\n");
1638 AutoTArray
<nsIWidget
*, 5> widgetChain
;
1639 GetLayoutPopupWidgetChain(&widgetChain
);
1640 if (widgetChain
.Length() == 0) {
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, " ");
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
);
1655 LOG("%s null window\n", indentString
.get());
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
);
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() {
1680 // Don't track popups without frame
1681 nsMenuPopupFrame
* popupFrame
= GetMenuPopupFrame(GetFrame());
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");
1730 case PopupType::Tooltip
:
1731 gtkTypeHint
= GDK_WINDOW_TYPE_HINT_TOOLTIP
;
1732 LOG(" popup type Tooltip");
1735 gtkTypeHint
= GDK_WINDOW_TYPE_HINT_UTILITY
;
1736 LOG(" popup type Utility");
1740 if (!mPopupTrackInHierarchy
) {
1741 // GDK_WINDOW_TYPE_HINT_UTILITY is mapped as wl_subsurface
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");
1759 LOG(" Window without frame cannot be added as popup!\n");
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");
1785 LogPopupHierarchy();
1786 auto printPopupHierarchy
= MakeScopeExit([&] { LogPopupHierarchy(); });
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
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
1818 nsWindow
* changedPopup
= mWaylandToplevel
->mWaylandPopupNext
;
1819 while (changedPopup
) {
1820 // Stop when parent of this popup was changed and we need to recalc
1822 if (changedPopup
->mPopupChanged
) {
1825 // Stop when this popup is closed.
1826 if (changedPopup
->mPopupClosed
) {
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");
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");
1857 changedPopup
= parentOfchangedPopup
->mWaylandPopupNext
;
1860 GetLayoutPopupWidgetChain(&layoutPopupWidgetChain
);
1861 mWaylandToplevel
->WaylandPopupHierarchyValidateByLayout(
1862 &layoutPopupWidgetChain
);
1864 changedPopup
->WaylandPopupHierarchyCalculatePositions();
1866 nsWindow
* popup
= changedPopup
;
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.
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
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
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.
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
,
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()");
1954 LOG(" needSizeUpdate\n");
1955 if (nsMenuPopupFrame
* popupFrame
= GetMenuPopupFrame(GetFrame())) {
1956 RefPtr
<PresShell
> presShell
= popupFrame
->PresShell();
1957 presShell
->FrameNeedsReflow(popupFrame
, IntrinsicDirty::None
,
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.
1976 if (!mWaitingForMoveToRectCallback
) {
1977 LOG(" Bogus move-to-rect callback! Expect wrong popup coordinates.");
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
1994 NativeMoveResize(movedByLayout
, resizedByLayout
);
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
);
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.
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
};
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
);
2092 GtkWindow
* parentGtkWindow
= gtk_window_get_transient_for(GTK_WINDOW(mShell
));
2093 nsWindow
* window
= get_window_for_gtk_widget(GTK_WIDGET(parentGtkWindow
));
2097 GdkWindow
* gdkWindow
= window
->GetGdkWindow();
2102 int parentWidth
= gdk_window_get_width(gdkWindow
);
2103 int popupWidth
= size
.width
;
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
;
2112 if (IsPopupDirectionRTL()) {
2113 // Stick with right window edge
2114 if (mPopupPosition
.x
< x
) {
2115 mPopupPosition
.x
= x
;
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
)))) {
2146 GdkWindow
* toplevelGdkWindow
= gtk_widget_get_window(GTK_WIDGET(parent
));
2147 if (!toplevelGdkWindow
) {
2148 NS_WARNING("Toplevel widget without GdkWindow?");
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
,
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
);
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 "
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
);
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
)) ==
2192 mMovedAfterMoveToRect
= aMove
;
2193 mResizedAfterMoveToRect
= aResize
;
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).
2208 mPopupMoveToRectParams
= WaylandPopupGetPositionFromLayout();
2211 if (!trackedInHierarchy
) {
2212 WaylandPopupSetDirectPosition();
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");
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
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();
2241 Maybe
<Side
> mVertical
;
2242 Maybe
<Side
> mHorizontal
;
2245 static PopupSides
SidesForPopupAlignment(int8_t aAlignment
) {
2246 switch (aAlignment
) {
2247 case POPUPALIGNMENT_NONE
:
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()};
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
,
2283 int8_t aAnchorAlign
,
2284 bool aAnchoredToPoint
,
2285 bool aIsContextMenu
) {
2286 nsMargin margin
= aFrame
->GetMargin();
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
;
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;
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;
2336 std::swap(margin
.top
, margin
.bottom
);
2339 return {margin
, offset
};
2343 void nsWindow::LogPopupAnchorHints(int aHints
) {
2344 static struct 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",
2370 "GDK_GRAVITY_CENTER",
2372 "GDK_GRAVITY_SOUTH_WEST",
2373 "GDK_GRAVITY_SOUTH",
2374 "GDK_GRAVITY_SOUTH_EAST",
2375 "GDK_GRAVITY_STATIC"};
2376 LOG(" %s", gravity
[aGravity
]);
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
;
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
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
,
2430 if (anchorRect
.width
< 0) {
2431 auto w
= -anchorRect
.width
;
2432 anchorRect
.width
+= w
+ 1;
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
) {
2468 hints
= GdkAnchorHints(hints
| GDK_ANCHOR_SLIDE
);
2472 hints
= GdkAnchorHints(hints
| GDK_ANCHOR_FLIP
);
2474 case FlipType_Slide
:
2475 hints
= GdkAnchorHints(hints
| GDK_ANCHOR_SLIDE
);
2477 case FlipType_Default
:
2478 hints
= GdkAnchorHints(hints
| GDK_ANCHOR_FLIP
);
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
);
2501 DevicePixelsToGdkPointRoundDown(LayoutDevicePoint::FromAppUnitsToNearest(
2502 popupMargin
.mPopupOffset
,
2503 popupFrame
->PresContext()->AppUnitsPerDevPixel())),
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!");
2516 GdkWindow
* window
= gtk_widget_get_window(GTK_WIDGET(parentGtkWindow
));
2518 NS_WARNING("Popup parrent is not mapped!");
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
)) {
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
);
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");
2559 if (popupFrame
->IsConstrainedByLayout()) {
2560 LOG(" can't use move-to-rect, flipped / constrained by layout");
2564 if (!mPopupMoveToRectParams
.mAnchorSet
) {
2565 LOG(" can't use move-to-rect due missing anchor");
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
,
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 "
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");
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.");
2620 if (gtk_widget_is_visible(mShell
)) {
2621 HideWaylandPopupWindow(/* aTemporaryHide */ true,
2622 /* aRemoveFromPopupList */ false);
2625 if (currentType
!= requiredType
) {
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
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
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
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
;
2677 if (mPopupUseMoveToRect
) {
2678 mPopupUseMoveToRect
=
2679 WaylandPopupCheckAndGetAnchor(>kAnchorRect
, &offset
);
2682 LOG("nsWindow::WaylandPopupMove");
2683 LOG(" original widget popup position [%d, %d]\n", mPopupPosition
.x
,
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.
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;
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
);
2717 LogPopupGravity(mPopupMoveToRectParams
.mAnchorRectType
);
2718 LOG(" PopupAnchorType");
2719 LogPopupGravity(mPopupMoveToRectParams
.mPopupAnchorType
);
2720 LogPopupAnchorHints(mPopupMoveToRectParams
.mHints
);
2724 sGdkWindowMoveToRect(gdkWindow
, >kAnchorRect
,
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
) {
2739 // We skip the nsWindows that don't have mGdkWindows.
2740 // These are probably in the process of being destroyed.
2745 if (!GetNextSibling()) {
2746 // We're to be on top.
2748 gdk_window_raise(mGdkWindow
);
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.
2771 if (mSizeMode
== aMode
&& mLastSizeModeRequest
== aMode
) {
2772 LOG(" already set");
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.
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);
2795 case nsSizeMode_Maximized
:
2796 LOG(" set maximized");
2797 gtk_window_maximize(GTK_WINDOW(mShell
));
2799 case nsSizeMode_Minimized
:
2800 LOG(" set minimized");
2801 gtk_window_iconify(GTK_WINDOW(mShell
));
2803 case nsSizeMode_Fullscreen
:
2804 LOG(" set fullscreen");
2805 MakeFullScreen(true);
2808 MOZ_FALLTHROUGH_ASSERT("Unknown size mode");
2809 case nsSizeMode_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
));
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)) {
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
2838 bool usesDynamicWorkspaces
= true;
2839 nsCOMPtr
<nsIGSettingsService
> gsettings
=
2840 do_GetService(NS_GSETTINGSSERVICE_CONTRACTID
);
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
) {
2865 LOG("nsWindow::GetWorkspaceID()\n");
2867 // Get the gdk window for this widget.
2868 GdkWindow
* gdk_window
= GetToplevelGdkWindow();
2870 LOG(" missing Gdk window, quit.");
2874 if (WorkspaceManagementDisabled(gdk_window_get_screen(gdk_window
))) {
2875 LOG(" WorkspaceManagementDisabled, quit.");
2879 GdkAtom cardinal_atom
= gdk_x11_xatom_to_atom(XA_CARDINAL
);
2880 GdkAtom type_returned
;
2881 int format_returned
;
2882 int length_returned
;
2885 if (!gdk_property_get(gdk_window
, gdk_atom_intern("_NET_WM_DESKTOP", FALSE
),
2888 INT32_MAX
, // length
2890 &type_returned
, &format_returned
, &length_returned
,
2891 (guchar
**)&wm_desktop
)) {
2892 LOG(" gdk_property_get() failed, quit.");
2896 LOG(" got workspace ID %d", (int32_t)wm_desktop
[0]);
2897 workspaceID
.AppendInt((int32_t)wm_desktop
[0]);
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");
2913 // Get the gdk window for this widget.
2914 GdkWindow
* gdk_window
= GetToplevelGdkWindow();
2916 LOG(" failed to get GdkWindow, quit.");
2920 // This code is inspired by some found in the 'gxtuner' project.
2921 // https://github.com/brummer10/gxtuner/blob/792d453da0f3a599408008f0f1107823939d730d/deskpager.cpp#L50
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
);
2947 LOG(" moved to workspace");
2951 void nsWindow::SetUserTimeAndStartupTokenForActivatedWindow() {
2952 nsGTKToolkit
* toolkit
= nsGTKToolkit::GetToolkit();
2953 if (!toolkit
|| MOZ_UNLIKELY(mWindowType
== WindowType::Invisible
)) {
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
);
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.
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();
2995 guint32 timestamp
= gtk_get_current_event_time();
2998 if (sLastUserInputTime
!= GDK_CURRENT_TIME
&&
2999 TimestampIsNewerThan(sLastUserInputTime
, timestamp
)) {
3000 return sLastUserInputTime
;
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.");
3015 wl_surface
* surface
=
3016 mGdkWindow
? gdk_wayland_window_get_wl_surface(mGdkWindow
) : nullptr;
3018 LOG(" mGdkWindow is not visible, quit.");
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
) {
3028 xdg_activation_v1_activate(xdg_activation
, aTokenID
, surface
);
3031 // Transfer focus from gFocusWindow to aWindow and use xdg_activation
3033 void nsWindow::TransferFocusToWaylandWindow(nsWindow
* aWindow
) {
3034 LOGW("nsWindow::TransferFocusToWaylandWindow(%p) gFocusWindow %p", aWindow
,
3036 auto promise
= mozilla::widget::RequestWaylandFocusPromise();
3037 if (NS_WARN_IF(!promise
)) {
3038 LOGW(" quit, failed to create TransferFocusToWaylandWindow [%p]", aWindow
);
3042 GetMainThreadSerialEventTarget(), __func__
,
3044 [window
= RefPtr
{aWindow
}](nsCString token
) {
3045 window
->FocusWaylandWindow(token
.get());
3048 [window
= RefPtr
{aWindow
}](bool state
) {
3049 LOGW("TransferFocusToWaylandWindow [%p] failed", window
.get());
3054 // Request activation of this window or give focus to this widget.
3055 // aRaise means whether we should request activation of this widget's
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
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");
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);
3109 return GetLastUserInputTime();
3112 toplevelWindow
->SetUserTimeAndStartupTokenForActivatedWindow();
3113 gtk_window_present_with_time(GTK_WINDOW(toplevelWindow
->mShell
),
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());
3124 LOG(" missing activation token, try to transfer from focused "
3126 TransferFocusToWaylandWindow(toplevelWindow
);
3134 // aRaise == No means that keyboard events should be dispatched from this
3137 // Ensure GTK_WIDGET(mContainer) is the focused GtkWidget within its toplevel
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");
3158 // Set this window to be the focused child window
3159 gFocusWindow
= this;
3162 mIMContext
->OnFocusWindow(this);
3165 LOG(" widget now has focus in SetFocus()");
3168 LayoutDeviceIntRect
nsWindow::GetScreenBounds() {
3173 const LayoutDeviceIntPoint origin
= [&] {
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
});
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
3191 const LayoutDeviceIntRect
rect(origin
, mBounds
.Size());
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
);
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());
3221 void nsWindow::RecomputeClientOffset(bool aNotify
) {
3222 if (!IsTopLevelWindowType()) {
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);
3247 if (GetCurrentTimeGetter()->PropertyNotifyHandler(aWidget
, aEvent
)) {
3254 static GdkCursor
* GetCursorForImage(const nsIWidget::Cursor
& aCursor
,
3255 int32_t aWidgetScaleFactor
) {
3256 if (!aCursor
.IsCustom()) {
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,
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
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
3276 if (size
.width
> 128 || size
.height
> 128) {
3280 nsIntSize rasterSize
= size
* gtkScale
;
3281 RefPtr
<GdkPixbuf
> pixbuf
=
3282 nsImageToPixbuf::ImageToPixbuf(aCursor
.mContainer
, Some(rasterSize
));
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
);
3299 cairo_surface_t
* surface
=
3300 gdk_cairo_surface_create_from_pixbuf(pixbuf
, gtkScale
, 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
) {
3317 // Only change cursor if it's actually been changed
3318 if (!mUpdateCursor
&& mCursor
== aCursor
) {
3322 mUpdateCursor
= false;
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.
3338 g_object_unref(imageCursor
);
3342 gdk_window_set_cursor(mGdkWindow
, nonImageCursor
);
3344 gdk_window_set_cursor(mGdkWindow
, imageCursor
);
3348 void nsWindow::Invalidate(const LayoutDeviceIntRect
& aRect
) {
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
,
3360 void* nsWindow::GetNativeData(uint32_t aDataType
) {
3361 switch (aDataType
) {
3362 case NS_NATIVE_WINDOW
:
3363 case NS_NATIVE_WIDGET
: {
3367 case NS_NATIVE_SHELLWIDGET
:
3368 return GetToplevelWidget();
3370 case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID
:
3375 if (GdkIsX11Display()) {
3376 return (void*)GDK_WINDOW_XID(gdk_window_get_toplevel(mGdkWindow
));
3380 "nsWindow::GetNativeData(): NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID is not "
3381 "handled on Wayland!");
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.
3393 return mIMContext
.get();
3395 case NS_NATIVE_OPENGL_CONTEXT
:
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
) {
3412 if (GdkIsX11Display()) {
3413 eglWindow
= (void*)GDK_WINDOW_XID(mGdkWindow
);
3417 if (GdkIsWaylandDisplay()) {
3419 mCompositorWidgetDelegate
&&
3420 mCompositorWidgetDelegate
->AsGtkCompositorWidget() &&
3421 mCompositorWidgetDelegate
->AsGtkCompositorWidget()->IsHidden();
3422 if (!hiddenWindow
) {
3423 eglWindow
= moz_container_wayland_get_egl_window(
3424 mContainer
, FractionalScaleFactor());
3429 mDestroyMutex
.Unlock();
3432 LOG("Get NS_NATIVE_EGL_WINDOW mGdkWindow %p returned eglWindow %p",
3433 mGdkWindow
, eglWindow
);
3437 NS_WARNING("nsWindow::GetNativeData called with bad value");
3442 nsresult
nsWindow::SetTitle(const nsAString
& aTitle
) {
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());
3462 void nsWindow::SetIcon(const nsAString
& aIconSpec
) {
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
);
3478 AppendUTF16toUTF8(aIconSpec
, iconName
);
3481 nsCOMPtr
<nsIFile
> iconFile
;
3484 gint
* iconSizes
= gtk_icon_theme_get_icon_sizes(gtk_icon_theme_get_default(),
3486 bool foundIcon
= (iconSizes
[0] != 0);
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
));
3505 iconFile
->GetNativePath(path
);
3506 GdkPixbuf
* icon
= gdk_pixbuf_new_from_file(path
.get(), nullptr);
3508 gtk_icon_theme_add_builtin_icon(iconName
.get(),
3509 gdk_pixbuf_get_height(icon
), icon
);
3510 g_object_unref(icon
);
3517 // leave the default icon intact if no matching icons were found
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
3538 GdkWindow* current = aWindow;
3539 while (GdkWindow* parent = gdk_window_get_parent(current)) {
3540 if (parent == current) {
3546 gdk_window_get_position(current, &x, &y);
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);
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
);
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()) {
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
);
3596 const bool grabPointer
= [] {
3597 switch (StaticPrefs::widget_gtk_grab_pointer()) {
3603 return sSystemNeedsPointerGrab
;
3611 mNeedsToRetryCapturingMouse
= false;
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").
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;
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
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
));
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);
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
3678 bool haveEvent
= false;
3681 if (GdkIsX11Display()) {
3682 Display
* display
= GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
3683 haveEvent
= XCheckMaskEvent(
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
,
3695 XPutBackEvent(display
, &ev
);
3702 #ifdef cairo_copy_clip_rectangle_list
3703 # error "Looks like we're including Mozilla's cairo instead of system cairo"
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.");
3712 for (int i
= 0; i
< rects
->num_rectangles
; i
++) {
3713 const cairo_rectangle_t
& r
= rects
->rectangles
[i
];
3715 LayoutDeviceIntRect::Truncate((float)r
.x
, (float)r
.y
,
3716 (float)r
.width
, (float)r
.height
));
3719 cairo_rectangle_list_destroy(rects
);
3724 void nsWindow::CreateCompositorVsyncDispatcher() {
3725 LOG_VSYNC("nsWindow::CreateCompositorVsyncDispatcher()");
3726 if (!mWaylandVsyncSource
) {
3728 " mWaylandVsyncSource is missing, create "
3729 "nsBaseWidget::CompositorVsyncDispatcher()");
3730 nsBaseWidget::CreateCompositorVsyncDispatcher();
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
);
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
);
3773 // Send any pending resize events so that layout can update.
3774 // May run event loop and destroy us.
3775 MaybeDispatchResized();
3780 // Windows that are not visible will be painted after they become visible.
3781 if (!mGdkWindow
|| !mHasMappedToplevel
) {
3785 if (GdkIsWaylandDisplay() && !moz_container_wayland_can_draw(mContainer
)) {
3790 if (!GetListener()) {
3794 LOG("nsWindow::OnExposeEvent GdkWindow [%p] XID [0x%lx]", mGdkWindow
,
3797 LayoutDeviceIntRegion exposeRegion
;
3798 if (!ExtractExposeRegion(exposeRegion
, cr
)) {
3799 LOG(" no rects, quit");
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
) {
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
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();
3848 window
->ClearTransparencyBitmap();
3851 if (mHasAlphaVisual
) {
3852 // Remove possible shape mask from when window manger was not
3853 // previously compositing.
3854 window
->ClearTransparencyBitmap();
3861 if (region
.IsEmpty()) {
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
3871 listener
= GetListener();
3876 listener
->DidPaintWindow();
3880 BufferMode layerBuffering
= BufferMode::BUFFERED
;
3881 RefPtr
<DrawTarget
> dt
= StartRemoteDrawingInRegion(region
, &layerBuffering
);
3882 if (!dt
|| !dt
->IsValid()) {
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
));
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()) {
3910 destDT
->SetTransform(Matrix::Translation(-boundsRect
.TopLeft()));
3911 ctx
.emplace(destDT
, /* aPreserveTransform */ true);
3913 gfxUtils::ClipToRegion(dt
, region
.ToUnknownRegion());
3914 ctx
.emplace(dt
, /* aPreserveTransform */ true);
3918 // NOTE: Paint flashing region would be wrong for cairo, since
3919 // cairo inflates the update region, etc. So don't paint flash
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
);
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
3946 listener
= GetListener();
3954 // PaintWindow can Destroy us (bug 378273), avoid doing any paint
3955 // operations below if that happened - it will lead to XError and exit().
3957 if (MOZ_LIKELY(!mIsDestroyed
)) {
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
));
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
);
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!
3993 void nsWindow::UpdateAlpha(SourceSurface
* aSourceSurface
,
3994 nsIntRect aBoundsRect
) {
3995 // We need to create our own buffer to force the stride to match the
3998 GetAlignedStride
<4>(aBoundsRect
.width
, BytesPerPixel(SurfaceFormat::A8
));
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
);
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
4027 // GDK calculates the cordinates for real ConfigureNotify events on
4028 // managed windows (that would normally be relative to the parent
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.
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 "
4043 aEvent
->x
, aEvent
->y
, aEvent
->width
, aEvent
->height
, scale
,
4044 aEvent
->width
* scale
, aEvent
->height
* scale
);
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
));
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
) {
4071 NS_ASSERTION(GTK_IS_WINDOW(aWidget
),
4072 "Configure event on widget that is not a GtkWindow");
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
);
4096 mBounds
.MoveTo(screenBounds
.TopLeft());
4097 RecomputeClientOffset(/* aNotify = */ false);
4099 // XXX mozilla will invalidate the entire window after this move
4101 NotifyWindowMoved(mBounds
.x
, mBounds
.y
);
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.
4113 ConfigureGdkWindow();
4116 void nsWindow::OnUnmap() {
4117 LOG("nsWindow::OnUnmap");
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
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);
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
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
,
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
) {
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()) {
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
);
4251 const bool shouldCheck
= [] {
4252 switch (StaticPrefs::widget_gtk_ignore_bogus_leave_notify()) {
4262 if (!shouldCheck
|| !aWindow
) {
4265 GdkDevice
* pointer
= GdkGetPointer();
4266 GdkWindow
* winAtPt
=
4267 gdk_device_get_window_at_position(pointer
, nullptr, nullptr);
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
,
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
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
) {
4294 // The filter out for subwindows should make sure that this is targeted to
4296 const bool leavingTopLevel
= IsTopLevelWindowType();
4297 if (leavingTopLevel
&& IsBogusLeaveNotifyEvent(mGdkWindow
, aEvent
)) {
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
) {
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.
4326 if (!mDrawInTitlebar
) {
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();
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
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
) {
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
);
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
) {
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
);
4450 if (mWindowShouldStartDragging
) {
4451 mWindowShouldStartDragging
= false;
4452 GdkWindow
* dragWindow
= nullptr;
4454 // find the top-level window
4456 dragWindow
= gdk_window_get_toplevel(mGdkWindow
);
4457 MOZ_ASSERT(dragWindow
, "gdk_window_get_toplevel should not return null");
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;
4474 gdk_window_begin_move_drag(dragWindow
, 1, aEvent
->x_root
, aEvent
->y_root
,
4480 mWidgetCursorLocked
= false;
4481 const auto refPoint
= GetRefPoint(this, aEvent
);
4482 if (auto edge
= CheckResizerEdge(refPoint
)) {
4483 nsCursor cursor
= eCursor_none
;
4485 case GDK_WINDOW_EDGE_SOUTH
:
4486 case GDK_WINDOW_EDGE_NORTH
:
4487 cursor
= eCursor_ns_resize
;
4489 case GDK_WINDOW_EDGE_WEST
:
4490 case GDK_WINDOW_EDGE_EAST
:
4491 cursor
= eCursor_ew_resize
;
4493 case GDK_WINDOW_EDGE_NORTH_WEST
:
4494 case GDK_WINDOW_EDGE_SOUTH_EAST
:
4495 cursor
= eCursor_nwse_resize
;
4497 case GDK_WINDOW_EDGE_NORTH_EAST
:
4498 case GDK_WINDOW_EDGE_SOUTH_WEST
:
4499 cursor
= eCursor_nesw_resize
;
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.
4507 mWidgetCursorLocked
= true;
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
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
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
;
4548 if (released
& buttonMask
) {
4550 switch (buttonMask
) {
4551 case GDK_BUTTON1_MASK
:
4552 buttonType
= MouseButton::ePrimary
;
4554 case GDK_BUTTON2_MASK
:
4555 buttonType
= MouseButton::eMiddle
;
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
) {
4589 buttonMask
= GDK_BUTTON1_MASK
;
4592 buttonMask
= GDK_BUTTON2_MASK
;
4595 buttonMask
= GDK_BUTTON3_MASK
;
4598 if (aGdkEvent
->type
== GDK_BUTTON_RELEASE
) {
4599 modifierState
&= ~buttonMask
;
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;
4612 case GDK_3BUTTON_PRESS
:
4613 aEvent
.mClickCount
= 3;
4615 // default is one click
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
);
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
);
4660 case LookAndFeel::TitlebarAction::WindowMaximize
:
4661 LOG(" action maximize");
4662 SetSizeMode(nsSizeMode_Maximized
);
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
);
4672 case LookAndFeel::TitlebarAction::None
:
4674 LOG(" action none");
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
4691 GUniquePtr
<GdkEvent
> peekedEvent(gdk_event_peek());
4693 GdkEventType type
= peekedEvent
->any
.type
;
4694 if (type
== GDK_2BUTTON_PRESS
|| type
== GDK_3BUTTON_PRESS
) {
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
);
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
);
4730 case GDK_WINDOW_EDGE_SOUTH
:
4731 mAspectResizer
= Some(GTK_ORIENTATION_VERTICAL
);
4733 case GDK_WINDOW_EDGE_EAST
:
4734 mAspectResizer
= Some(GTK_ORIENTATION_HORIZONTAL
);
4737 mAspectResizer
.reset();
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
);
4750 gdouble pressure
= 0;
4751 gdk_event_get_axis((GdkEvent
*)aEvent
, GDK_AXIS_PRESSURE
, &pressure
);
4752 mLastMotionPressure
= pressure
;
4755 switch (aEvent
->button
) {
4757 domButton
= MouseButton::ePrimary
;
4760 domButton
= MouseButton::eMiddle
;
4763 domButton
= MouseButton::eSecondary
;
4765 // These are mapped to horizontal scroll
4768 NS_WARNING("We're not supporting legacy horizontal scroll event");
4770 // Map buttons 8-9(10) to back/forward
4772 if (!Preferences::GetBool("mousebutton.4th.enabled", true)) {
4775 DispatchCommandEvent(nsGkAtoms::Back
);
4779 if (!Preferences::GetBool("mousebutton.5th.enabled", true)) {
4782 DispatchCommandEvent(nsGkAtoms::Forward
);
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);
4825 if (mAspectResizer
) {
4826 mAspectResizer
= Nothing();
4830 if (mWindowShouldStartDragging
) {
4831 mWindowShouldStartDragging
= false;
4835 switch (aEvent
->button
) {
4837 domButton
= MouseButton::ePrimary
;
4840 domButton
= MouseButton::eMiddle
;
4843 domButton
= MouseButton::eSecondary
;
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");
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
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
));
4938 // We also roll up when a drag is from a different application
4939 nsCOMPtr
<nsINode
> sourceNode
;
4940 dragSession
->GetSourceNode(getter_AddRefs(sourceNode
));
4944 if (shouldRollupMenus
) {
4948 if (RefPtr pm
= nsXULPopupManager::GetInstance()) {
4949 pm
->RollupTooltips();
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
);
4981 bool nsWindow::DispatchContentCommandEvent(EventMessage aMsg
) {
4982 nsEventStatus status
;
4983 WidgetContentCommandEvent
event(true, aMsg
, this);
4984 DispatchEvent(&event
, status
);
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
;
5013 BaseTimeDurationPlatformUtils::TicksFromMilliseconds(timestampTime
);
5014 eventTimeStamp
= TimeStamp::FromSystemTime(tick
);
5017 CurrentX11TimeGetter
* getCurrentTime
= GetCurrentTimeGetter();
5018 MOZ_ASSERT(getCurrentTime
,
5019 "Null current time getter despite having a window");
5021 TimeConverter().GetTimeStampFromSystemTime(aEventTime
, *getCurrentTime
);
5024 return eventTimeStamp
;
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();
5037 gboolean
nsWindow::OnKeyPressEvent(GdkEventKey
* aEvent
) {
5038 LOG("OnKeyPressEvent");
5040 KeymapWrapper::HandleKeyPressEvent(this, aEvent
);
5044 gboolean
nsWindow::OnKeyReleaseEvent(GdkEventKey
* aEvent
) {
5045 LOG("OnKeyReleaseEvent");
5046 if (NS_WARN_IF(!KeymapWrapper::HandleKeyReleaseEvent(this, aEvent
))) {
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)) {
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
,
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
5098 switch (*mCurrentSynthesizedTouchpadPan
.mTouchpadGesturePhase
) {
5100 // we should never hit this because we'll hit the above case
5102 MOZ_ASSERT_UNREACHABLE();
5103 eventType
= PanGestureInput::PANGESTURE_START
;
5104 mPanInProgress
= true;
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
;
5113 MOZ_ASSERT(mPanInProgress
);
5114 eventType
= PanGestureInput::PANGESTURE_END
;
5115 mPanInProgress
= false;
5118 MOZ_ASSERT_UNREACHABLE();
5123 mCurrentSynthesizedTouchpadPan
.mTouchpadGesturePhase
.reset();
5125 const bool isPageMode
=
5126 #ifdef NIGHTLY_BUILD
5127 StaticPrefs::apz_gtk_pangesture_delta_mode() == 1;
5129 StaticPrefs::apz_gtk_pangesture_delta_mode() != 2;
5131 const double multiplier
=
5133 ? StaticPrefs::apz_gtk_pangesture_page_delta_mode_multiplier()
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;
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;
5178 wheelEvent
.mDeltaY
= wheelEvent
.mLineOrPageDeltaY
= -3;
5179 wheelEvent
.mWheelTicksY
= -1;
5181 case GDK_SCROLL_DOWN
:
5182 wheelEvent
.mDeltaY
= wheelEvent
.mLineOrPageDeltaY
= 3;
5183 wheelEvent
.mWheelTicksY
= 1;
5185 case GDK_SCROLL_LEFT
:
5186 wheelEvent
.mDeltaX
= wheelEvent
.mLineOrPageDeltaX
= -1;
5187 wheelEvent
.mWheelTicksX
= -1;
5189 case GDK_SCROLL_RIGHT
:
5190 wheelEvent
.mDeltaX
= wheelEvent
.mLineOrPageDeltaX
= 1;
5191 wheelEvent
.mWheelTicksX
= 1;
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
) {
5217 APZEventResult result
;
5219 MOZ_ASSERT(APZThreadUtils::IsControllerThread());
5221 result
= mAPZC
->InputBridge()->ReceiveInputEvent(aPanInput
);
5222 if (result
.GetStatus() == nsEventStatus_eConsumeNoDefault
) {
5227 WidgetWheelEvent event
= aPanInput
.ToWidgetEvent(this);
5229 if (MayStartSwipeForNonAPZ(aPanInput
)) {
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 "
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
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");
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
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");
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
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();
5376 ClearTransparencyBitmap();
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()) {
5409 return; // We'll get there again when we configure the window.
5411 gint newCeiled
= gdk_window_get_scale_factor(mGdkWindow
);
5412 double newFractional
= [&] {
5414 if (GdkIsWaylandDisplay()) {
5415 return moz_container_wayland_get_fractional_scale(mContainer
);
5421 if (mCeiledScaleFactor
== newCeiled
&&
5422 mFractionalScaleFactor
== newFractional
) {
5426 NotifyAPZOfDPIChange();
5428 LOG("OnScaleChanged %d, %f -> %d, %f\n", int(mCeiledScaleFactor
),
5429 mFractionalScaleFactor
, newCeiled
, newFractional
);
5431 mCeiledScaleFactor
= newCeiled
;
5432 mFractionalScaleFactor
= newFractional
;
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
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();
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
,
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
,
5489 GtkSelectionData
* aSelectionData
,
5490 guint aInfo
, guint aTime
,
5492 LOGDRAG("nsWindow::OnDragDataReceived");
5494 RefPtr
<nsDragService
> dragService
= nsDragService::GetInstance();
5495 nsDragService::AutoEventLoop
loop(dragService
);
5496 dragService
->TargetDataReceived(aWidget
, aDragContext
, aX
, aY
, aSelectionData
,
5500 nsWindow
* nsWindow::GetTransientForWindowIfPopup() {
5501 if (mWindowType
!= WindowType::Popup
) {
5504 GtkWindow
* toplevel
= gtk_window_get_transient_for(GTK_WINDOW(mShell
));
5506 return get_window_for_gtk_widget(GTK_WIDGET(toplevel
));
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()) {
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()) {
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;
5546 case GDK_TOUCHPAD_GESTURE_PHASE_UPDATE
:
5547 pinchGestureType
= PinchGestureInput::PINCHGESTURE_SCALE
;
5548 mCurrentTouchpadFocus
+= ScreenPoint(aEvent
->dx
, aEvent
->dy
);
5549 if (aEvent
->scale
== mLastPinchEventSpan
) {
5552 currentSpan
= aEvent
->scale
;
5553 previousSpan
= mLastPinchEventSpan
;
5556 case GDK_TOUCHPAD_GESTURE_PHASE_END
:
5557 pinchGestureType
= PinchGestureInput::PINCHGESTURE_END
;
5558 currentSpan
= aEvent
->scale
;
5559 previousSpan
= mLastPinchEventSpan
;
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
)
5573 100.0 * ((aEvent
->phase
== GDK_TOUCHPAD_GESTURE_PHASE_END
)
5576 KeymapWrapper::ComputeKeyModifiers(aEvent
->state
));
5578 if (!event
.SetLineOrPageDeltaY(this)) {
5582 mLastPinchEventSpan
= aEvent
->scale
;
5583 DispatchPinchGestureInput(event
);
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();
5597 targetWindow
->IsHandlingTouchSequence(aEvent
->sequence
)) {
5598 return targetWindow
->OnTouchEvent(aEvent
);
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)) {
5613 case GDK_TOUCH_UPDATE
:
5615 // Start dragging when motion events happens in the dragging area
5616 if (mWindowShouldStartDragging
) {
5617 mWindowShouldStartDragging
= false;
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).
5635 if (mWindowShouldStartDragging
) {
5636 LOG(" end of window dragging window\n");
5637 mWindowShouldStartDragging
= false;
5640 case GDK_TOUCH_CANCEL
:
5647 const LayoutDeviceIntPoint touchPoint
= GetRefPoint(this, aEvent
);
5650 RefPtr
<dom::Touch
> touch
;
5651 if (mTouches
.Remove(aEvent
->sequence
, getter_AddRefs(touch
))) {
5652 id
= touch
->mIdentifier
;
5654 id
= ++gLastTouchID
& 0x7FFFFFFF;
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;
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");
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
;
5715 // Configure GL visual on X11.
5716 bool nsWindow::ConfigureX11GLVisual() {
5717 auto* screen
= gtk_widget_get_screen(mShell
);
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
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;
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
);
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
);
5746 gtk_widget_set_visual(mShell
, gdkVisual
);
5747 mHasAlphaVisual
= true;
5755 nsAutoCString
nsWindow::GetFrameTag() const {
5756 if (nsIFrame
* frame
= GetFrame()) {
5757 #ifdef DEBUG_FRAME_DUMP
5758 return frame
->ListTag();
5761 buf
.AppendPrintf("Frame(%p)", frame
);
5762 if (nsIContent
* content
= frame
->GetContent()) {
5764 AppendUTF16toUTF8(content
->NodeName(), buf
);
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");
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
5788 static void GtkWidgetDisableUpdates(GtkWidget
* aWidget
) {
5789 // Clear exposure mask - it disabled synthesized events.
5790 GdkWindow
* window
= gtk_widget_get_window(aWidget
);
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() {
5805 if (GdkIsX11Display()) {
5806 return mGdkWindow
? gdk_x11_window_get_xid(mGdkWindow
) : X11None
;
5809 return (Window
) nullptr;
5812 void nsWindow::EnsureGdkWindow() {
5813 MOZ_DIAGNOSTIC_ASSERT(mIsMapped
);
5815 mGdkWindow
= gtk_widget_get_window(GTK_WIDGET(mContainer
));
5816 g_object_set_data(G_OBJECT(mGdkWindow
), "nsWindow", this);
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");
5837 if (mIsDestroyed
|| !mIsMapped
) {
5838 LOG(" quit, mIsDestroyed = %d mIsMapped = %d", !!mIsDestroyed
,
5842 // Compositor will be resumed later by ResumeCompositorFlickering().
5843 if (mCompositorState
== COMPOSITOR_PAUSED_FLICKERING
) {
5844 LOG(" quit, will be resumed by ResumeCompositorFlickering.");
5847 // Compositor will be resumed at nsWindow::SetCompositorWidgetDelegate().
5848 if (!mCompositorWidgetDelegate
) {
5849 LOG(" quit, missing mCompositorWidgetDelegate");
5853 ResumeCompositorImpl();
5856 if (GdkIsWaylandDisplay()) {
5858 moz_container_wayland_add_or_fire_initial_draw_callback(mContainer
,
5866 void nsWindow::ConfigureGdkWindow() {
5867 LOG("nsWindow::ConfigureGdkWindow()");
5870 OnScaleChanged(/* aNotify = */ false);
5873 gdk_window_set_override_redirect(mGdkWindow
, TRUE
);
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
5885 SetCompositorHint(GTK_WIDGET_COMPOSIDED_ENABLED
);
5889 if (GdkIsWaylandDisplay()) {
5890 mSurfaceProvider
.Initialize(this);
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
5899 if (GtkWidget
* parent
= gtk_widget_get_parent(mShell
)) {
5900 GtkWidgetDisableUpdates(parent
);
5902 GtkWidgetDisableUpdates(mShell
);
5903 GtkWidgetDisableUpdates(GTK_WIDGET(mContainer
));
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
) {
5914 gint wmd
= ConvertBorderStyles(mBorderStyle
);
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
5940 nsIWidget
* baseParent
=
5941 aInitData
&& (aInitData
->mWindowType
== WindowType::Dialog
||
5942 aInitData
->mWindowType
== WindowType::TopLevel
||
5943 aInitData
->mWindowType
== WindowType::Invisible
)
5947 #ifdef ACCESSIBILITY
5948 // Send a DBus message to check whether a11y is enabled
5953 // Ensure that KeymapWrapper is created on Wayland as we need it for
5954 // keyboard focus tracking.
5955 if (GdkIsWaylandDisplay()) {
5956 KeymapWrapper::EnsureInstance();
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
5970 LOG(" mBounds: x:%d y:%d w:%d h:%d\n", mBounds
.x
, mBounds
.y
, mBounds
.width
,
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;
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
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
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();
6063 if (GdkIsX11Display() && mIsAccelerated
) {
6064 isGLVisualSet
= ConfigureX11GLVisual();
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
) {
6072 gdk_screen_get_rgba_visual(gtk_widget_get_screen(mShell
));
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
,
6105 gtk_window_resize(GTK_WINDOW(mShell
), size
.width
, size
.height
);
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";
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");
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
);
6138 bool decorate
= bool(mBorderStyle
& BorderStyle::Title
);
6139 gtk_window_set_decorated(GTK_WINDOW(mShell
), 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
);
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
,
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
;
6173 case PopupType::Tooltip
:
6174 gtkTypeHint
= GDK_WINDOW_TYPE_HINT_TOOLTIP
;
6177 gtkTypeHint
= GDK_WINDOW_TYPE_HINT_UTILITY
;
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";
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
);
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();
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.
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
);
6272 if (mIsDragPopup
&& GdkIsWaylandDisplay()) {
6273 LOG(" set commit to parent");
6274 moz_container_wayland_set_commit_to_parent(mContainer
);
6278 if (mWindowType
== WindowType::TopLevel
&& gKioskMode
) {
6279 if (gKioskMonitor
!= -1) {
6280 mKioskMonitor
= Some(gKioskMonitor
);
6281 LOG(" set kiosk mode monitor %d", mKioskMonitor
.value());
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
),
6318 g_signal_connect(mShell
, "delete_event", G_CALLBACK(delete_event_cb
),
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
),
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
),
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
),
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,
6347 g_signal_connect(mShell
, "drag_motion", G_CALLBACK(drag_motion_event_cb
),
6349 g_signal_connect(mShell
, "drag_leave", G_CALLBACK(drag_leave_event_cb
),
6351 g_signal_connect(mShell
, "drag_drop", G_CALLBACK(drag_drop_event_cb
),
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);
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
6373 g_signal_connect(G_OBJECT(mContainer
), "draw", G_CALLBACK(expose_event_cb
),
6375 g_signal_connect(mContainer
, "focus_in_event", G_CALLBACK(focus_in_event_cb
),
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);
6385 if (GdkIsX11Display()) {
6386 gtk_widget_set_double_buffered(GTK_WIDGET(mContainer
), FALSE
);
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");
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
),
6424 if (gtk_check_version(3, 18, 0) == nullptr) {
6425 g_signal_connect(mContainer
, "event", G_CALLBACK(generic_event_cb
),
6428 g_signal_connect(mContainer
, "touch-event", G_CALLBACK(touch_event_cb
),
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
;
6445 void nsWindow::RefreshWindowClass(void) {
6446 GdkWindow
* gdkWindow
= GetToplevelGdkWindow();
6451 if (!mGtkWindowRoleName
.IsEmpty()) {
6452 gdk_window_set_role(gdkWindow
, mGtkWindowRoleName
.get());
6456 if (GdkIsX11Display()) {
6457 XClassHint
* class_hint
= XAllocClassHint();
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
) {
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
);
6484 #endif /* MOZ_X11 */
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
) {
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;
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
++) {
6519 } else if (!isascii(*c
) ||
6520 (!isalnum(*c
) && ('_' != *c
) && ('-' != *c
))) {
6524 res_name
[0] = (char)toupper(res_name
[0]);
6525 if (!role
) role
= res_name
;
6527 mGtkWindowAppName
= res_name
;
6528 mGtkWindowRoleName
= role
;
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
);
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 {
6554 tag
.AppendPrintf("[%p]", this);
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
);
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
) {
6587 LOG(" moving to %d x %d", topLeft
.x
, topLeft
.y
);
6588 gtk_window_move(GTK_WINDOW(mShell
), topLeft
.x
, topLeft
.y
);
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
);
6605 // x and y give the position of the window manager frame top-left.
6607 gtk_window_move(GTK_WINDOW(mShell
), topLeft
.x
, topLeft
.y
);
6610 gtk_window_resize(GTK_WINDOW(mShell
), size
.width
, size
.height
);
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
,
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
) {
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
6636 // Define maximal compositor pause when it's paused to avoid flickering,
6638 #define COMPOSITOR_PAUSE_TIMEOUT (1000)
6640 void nsWindow::PauseCompositorFlickering() {
6641 bool pauseCompositor
= IsTopLevelWindowType() &&
6642 mCompositorState
== COMPOSITOR_ENABLED
&&
6643 mCompositorWidgetDelegate
&& !mIsDestroyed
;
6644 if (!pauseCompositor
) {
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();
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");
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
;
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() {
6724 if (!mWaylandVsyncSource
) {
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());
6740 LOG_VSYNC(" use source mContainer");
6741 mWaylandVsyncSource
->MaybeUpdateSource(mContainer
);
6744 mWaylandVsyncSource
->EnableMonitor();
6748 void nsWindow::WaylandStopVsync() {
6750 if (!mWaylandVsyncSource
) {
6754 LOG_VSYNC("nsWindow::WaylandStopVsync");
6756 // The widget is going to be hidden, so clear the surface of our
6758 mWaylandVsyncSource
->DisableMonitor();
6759 mWaylandVsyncSource
->MaybeUpdateSource(nullptr);
6763 void nsWindow::NativeShow(bool aAction
) {
6765 // unset our flag now that our window has been shown
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();
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
6784 if (GdkIsX11Display()) {
6785 SetUserTimeAndStartupTokenForActivatedWindow();
6787 if (GdkIsWaylandDisplay()) {
6788 if (IsWaylandPopup()) {
6789 ShowWaylandPopupWindow();
6791 ShowWaylandToplevelWindow();
6794 LOG(" calling gtk_widget_show(mShell)\n");
6795 gtk_widget_show(mShell
);
6797 if (GdkIsWaylandDisplay()) {
6798 SetUserTimeAndStartupTokenForActivatedWindow();
6800 auto token
= std::move(mWindowActivationTokenFromEnv
);
6801 if (!token
.IsEmpty()) {
6802 FocusWaylandWindow(token
.get());
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;
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();
6822 // Close untracked popups directly.
6823 HideWaylandPopupWindow(/* aTemporaryHide */ false,
6824 /* aRemoveFromPopupList */ true);
6827 HideWaylandToplevelWindow();
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
;
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
) {
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
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()) {
6886 maxSize
, mWindowRenderer
->AsKnowsCompositor()->GetMaxTextureSize());
6888 if (result
.width
> maxSize
) {
6889 result
.width
= maxSize
;
6891 if (result
.height
> maxSize
) {
6892 result
.height
= maxSize
;
6897 void nsWindow::SetTransparencyMode(TransparencyMode aMode
) {
6898 bool isTransparent
= aMode
== TransparencyMode::Transparent
;
6900 if (mIsTransparent
== isTransparent
) {
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.");
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();
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([&] {
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
);
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
) {
7010 cairo_rectangle_int_t rect
= {aX
, aY
, aTitlebarRadius
, aTitlebarRadius
};
7011 cairo_region_subtract_rectangle(aRegion
, &rect
);
7013 aX
+ aWindowWidth
- aTitlebarRadius
,
7018 cairo_region_subtract_rectangle(aRegion
, &rect
);
7021 aY
+ aWindowHeight
- aTitlebarRadius
,
7025 cairo_region_subtract_rectangle(aRegion
, &rect
);
7027 aX
+ aWindowWidth
- aTitlebarRadius
,
7028 aY
+ aWindowHeight
- aTitlebarRadius
,
7032 cairo_region_subtract_rectangle(aRegion
, &rect
);
7035 void nsWindow::UpdateTopLevelOpaqueRegion() {
7036 if (!mCompositedScreen
) {
7040 GdkWindow
* window
= GetToplevelGdkWindow();
7044 MOZ_ASSERT(gdk_window_get_window_type(window
) == GDK_WINDOW_TOPLEVEL
);
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
);
7068 if (GdkIsWaylandDisplay()) {
7069 moz_container_wayland_update_opaque_region(mContainer
, radius
);
7076 bool nsWindow::IsChromeWindowTitlebar() {
7077 return mDrawInTitlebar
&& !mIsPIPWindow
&&
7078 mWindowType
== WindowType::TopLevel
;
7081 bool nsWindow::DoDrawTilebarCorners() {
7082 return IsChromeWindowTitlebar() && mSizeMode
== nsSizeMode_Normal
&&
7086 void nsWindow::ResizeTransparencyBitmap() {
7087 if (!mTransparencyBitmap
) {
7091 if (mBounds
.width
== mTransparencyBitmapWidth
&&
7092 mBounds
.height
== mTransparencyBitmapHeight
) {
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
);
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;
7135 gchar maskByte
= maskBytes
[x
>> 3];
7136 bool maskBit
= (maskByte
& (1 << (x
& 7))) != 0;
7138 if (maskBit
!= newBit
) {
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;
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
);
7169 void nsWindow::ApplyTransparencyBitmap() {
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
,
7181 XFreePixmap(xDisplay
, maskPixmap
);
7185 void nsWindow::ClearTransparencyBitmap() {
7186 if (!mTransparencyBitmap
) {
7190 delete[] mTransparencyBitmap
;
7191 mTransparencyBitmap
= nullptr;
7192 mTransparencyBitmapWidth
= 0;
7193 mTransparencyBitmapHeight
= 0;
7200 if (MOZ_UNLIKELY(!mGdkWindow
)) {
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
);
7211 nsresult
nsWindow::UpdateTranslucentWindowAlphaInternal(const nsIntRect
& aRect
,
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
;
7225 ResizeTransparencyBitmap();
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
7238 UpdateMaskBits(mTransparencyBitmap
, mBounds
.width
, mBounds
.height
, rect
,
7242 ApplyTransparencyBitmap();
7247 #define TITLEBAR_HEIGHT 10
7249 void nsWindow::SetTitlebarRect() {
7250 MutexAutoLock
lock(mTitlebarRectMutex
);
7252 if (!mGdkWindow
|| !DoDrawTilebarCorners()) {
7253 mTitlebarRect
= LayoutDeviceIntRect();
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
)) {
7276 !mTransparencyBitmap
|| mBounds
.width
> mTransparencyBitmapWidth
;
7279 !mTransparencyBitmap
|| mBounds
.width
!= mTransparencyBitmapWidth
;
7281 LayoutDeviceIntCoord radius
= GetTitlebarRadius();
7283 delete[] mTransparencyBitmap
;
7284 int32_t size
= GetBitmapStride(mBounds
.width
) * radius
;
7285 mTransparencyBitmap
= new gchar
[size
];
7286 mTransparencyBitmapWidth
= mBounds
.width
;
7288 mTransparencyBitmapWidth
= mBounds
.width
;
7290 mTransparencyBitmapHeight
= mBounds
.height
;
7293 cairo_surface_t
* surface
= cairo_image_surface_create(
7294 CAIRO_FORMAT_A8
, mTransparencyBitmapWidth
, radius
);
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,
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
);
7323 Display
* xDisplay
= GDK_WINDOW_XDISPLAY(mGdkWindow
);
7324 Window xDrawable
= GDK_WINDOW_XID(mGdkWindow
);
7327 XCreateBitmapFromData(xDisplay
, xDrawable
, mTransparencyBitmap
,
7328 mTransparencyBitmapWidth
, radius
);
7330 XShapeCombineMask(xDisplay
, xDrawable
, ShapeBounding
, 0, 0, maskPixmap
,
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
);
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
) {
7357 nsWindow
* window
= get_window_for_gtk_widget(owningWidget
);
7358 NS_ASSERTION(window
, "No nsWindow for container widget");
7362 void nsWindow::SetUrgencyHint(GtkWidget
* top_window
, bool state
) {
7363 LOG(" nsWindow::SetUrgencyHint widget %p\n", top_window
);
7367 GdkWindow
* window
= gtk_widget_get_window(top_window
);
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
) {
7380 if (aStyle
== BorderStyle::Default
) {
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
;
7396 class FullscreenTransitionWindow final
: public nsISupports
{
7400 explicit FullscreenTransitionWindow(GtkWidget
* aWidget
);
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
);
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
,
7435 gtk_widget_set_opacity(mWindow
, 0.0);
7436 gtk_widget_show(mWindow
);
7439 FullscreenTransitionWindow::~FullscreenTransitionWindow() {
7440 gtk_widget_destroy(mWindow
);
7443 class FullscreenTransitionData
{
7445 FullscreenTransitionData(nsIWidget::FullscreenTransitionStage aStage
,
7446 uint16_t aDuration
, nsIRunnable
* aCallback
,
7447 FullscreenTransitionWindow
* aWindow
)
7449 mStartTime(TimeStamp::Now()),
7450 mDuration(TimeDuration::FromMilliseconds(aDuration
)),
7451 mCallback(aCallback
),
7454 static const guint sInterval
= 1000 / 30; // 30fps
7455 static gboolean
TimeoutCallback(gpointer aData
);
7458 nsIWidget::FullscreenTransitionStage mStage
;
7459 TimeStamp mStartTime
;
7460 TimeDuration mDuration
;
7461 nsCOMPtr
<nsIRunnable
> mCallback
;
7462 RefPtr
<FullscreenTransitionWindow
> mWindow
;
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) {
7474 if (data
->mStage
== nsIWidget::eAfterFullscreenToggle
) {
7475 opacity
= 1.0 - opacity
;
7477 gtk_widget_set_opacity(data
->mWindow
->mWindow
, opacity
);
7482 NS_DispatchToMainThread(data
->mCallback
.forget());
7488 bool nsWindow::PrepareForFullscreenTransition(nsISupports
** aData
) {
7489 if (!mCompositedScreen
) {
7492 *aData
= do_AddRef(new FullscreenTransitionWindow(mShell
)).take();
7497 void nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage
,
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
,
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() {
7528 if (mWaylandVsyncDispatcher
) {
7529 return mWaylandVsyncDispatcher
;
7535 bool nsWindow::SynchronouslyRepaintOnResize() {
7536 if (GdkIsWaylandDisplay()) {
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.
7543 // default is synced repaint.
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
) {
7557 int monitor
= mKioskMonitor
.value();
7558 if (monitor
< 0 || monitor
>= ScreenHelperGTK::GetMonitorCount()) {
7559 LOG("nsWindow::KioskLockOnMonitor() wrong monitor number! (%d)\n", monitor
);
7563 LOG("nsWindow::KioskLockOnMonitor() locked on %d\n", monitor
);
7564 sGdkWindowFullscreenOnMonitor(GetToplevelGdkWindow(), monitor
);
7567 static bool IsFullscreenSupported(GtkWidget
* aShell
) {
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
);
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
;
7585 if (mSizeMode
!= nsSizeMode_Fullscreen
&&
7586 mSizeMode
!= nsSizeMode_Minimized
) {
7587 mLastSizeModeBeforeFullscreen
= mSizeMode
;
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();
7601 gtk_window_fullscreen(GTK_WINDOW(mShell
));
7604 // Kiosk mode always use fullscreen mode.
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
);
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
7631 bool wasVisible
= false;
7632 if (gdk_window_is_visible(window
)) {
7633 gdk_window_hide(window
);
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)
7648 if (GdkIsX11Display()) {
7649 XSync(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), X11False
);
7651 #endif /* MOZ_X11 */
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
) {
7673 auto* rollupWindow
=
7674 (GdkWindow
*)rollupWidget
->GetNativeData(NS_NATIVE_WINDOW
);
7675 if (!aAlwaysRollup
&& is_mouse_in_window(rollupWindow
, aMouseX
, aMouseY
)) {
7678 bool retVal
= false;
7680 retVal
= rollupListener
->ShouldConsumeOnMouseWheelEvent();
7681 if (!rollupListener
->ShouldRollupOnMouseWheelEvent()) {
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
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
7704 if (i
< sameTypeCount
) {
7707 options
.mCount
= sameTypeCount
;
7710 } // foreach parent menu widget
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,
7721 options
.mAllowAnimations
= nsIRollupListener::AllowAnimations::No
;
7724 if (rollupListener
->Rollup(options
)) {
7731 bool nsWindow::DragInProgress() {
7732 nsCOMPtr
<nsIDragService
> dragService
=
7733 do_GetService("@mozilla.org/widget/dragservice;1");
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
7752 if (aEvent
->button
!= 1 || aEvent
->type
!= GDK_BUTTON_PRESS
) {
7756 nsCOMPtr
<nsIDragService
> dragService
=
7757 do_GetService("@mozilla.org/widget/dragservice;1");
7761 nsCOMPtr
<nsIDragSession
> currentDragSession
;
7762 dragService
->GetCurrentSession(getter_AddRefs(currentDragSession
));
7764 if (!currentDragSession
) {
7765 buttonPressCountWithDrag
= 0;
7769 buttonPressCountWithDrag
++;
7770 if (buttonPressCountWithDrag
> 1) {
7772 "Quit unfinished Wayland Drag and Drop operation. Buggy Wayland "
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
,
7791 GdkWindow
* window
= aWindow
;
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
7812 if (GTK_IS_WINDOW(widget
)) {
7820 window
= gdk_window_get_parent(window
);
7825 if (nsWindow
* w
= get_window_for_gdk_window(aWindow
)) {
7826 margin
= w
->GetInputRegionMarginInGdkCoords();
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
])) {
7853 GdkDisplay
* defaultDisplay
= gdk_display_get_default();
7856 case eCursor_standard
:
7857 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "default");
7860 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "wait");
7862 case eCursor_select
:
7863 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "text");
7865 case eCursor_hyperlink
:
7866 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "pointer");
7868 case eCursor_n_resize
:
7869 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "n-resize");
7871 case eCursor_s_resize
:
7872 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "s-resize");
7874 case eCursor_w_resize
:
7875 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "w-resize");
7877 case eCursor_e_resize
:
7878 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "e-resize");
7880 case eCursor_nw_resize
:
7881 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "nw-resize");
7883 case eCursor_se_resize
:
7884 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "se-resize");
7886 case eCursor_ne_resize
:
7887 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "ne-resize");
7889 case eCursor_sw_resize
:
7890 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "sw-resize");
7892 case eCursor_crosshair
:
7893 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "crosshair");
7896 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "move");
7899 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "help");
7902 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "copy");
7903 if (!gdkcursor
) newType
= MOZ_CURSOR_COPY
;
7906 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "alias");
7907 if (!gdkcursor
) newType
= MOZ_CURSOR_ALIAS
;
7909 case eCursor_context_menu
:
7910 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "context-menu");
7911 if (!gdkcursor
) newType
= MOZ_CURSOR_CONTEXT_MENU
;
7914 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "cell");
7917 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "grab");
7918 if (!gdkcursor
) newType
= MOZ_CURSOR_HAND_GRAB
;
7920 case eCursor_grabbing
:
7921 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "grabbing");
7922 if (!gdkcursor
) newType
= MOZ_CURSOR_HAND_GRABBING
;
7924 case eCursor_spinning
:
7925 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "progress");
7926 if (!gdkcursor
) newType
= MOZ_CURSOR_SPINNING
;
7928 case eCursor_zoom_in
:
7929 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "zoom-in");
7930 if (!gdkcursor
) newType
= MOZ_CURSOR_ZOOM_IN
;
7932 case eCursor_zoom_out
:
7933 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "zoom-out");
7934 if (!gdkcursor
) newType
= MOZ_CURSOR_ZOOM_OUT
;
7936 case eCursor_not_allowed
:
7937 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "not-allowed");
7938 if (!gdkcursor
) newType
= MOZ_CURSOR_NOT_ALLOWED
;
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
7944 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "forbidden");
7947 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "circle");
7949 if (!gdkcursor
) newType
= MOZ_CURSOR_NOT_ALLOWED
;
7951 case eCursor_vertical_text
:
7952 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "vertical-text");
7954 newType
= MOZ_CURSOR_VERTICAL_TEXT
;
7957 case eCursor_all_scroll
:
7958 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "move");
7960 case eCursor_nesw_resize
:
7961 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "nesw-resize");
7962 if (!gdkcursor
) newType
= MOZ_CURSOR_NESW_RESIZE
;
7964 case eCursor_nwse_resize
:
7965 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "nwse-resize");
7966 if (!gdkcursor
) newType
= MOZ_CURSOR_NWSE_RESIZE
;
7968 case eCursor_ns_resize
:
7969 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "ns-resize");
7971 case eCursor_ew_resize
:
7972 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "ew-resize");
7974 case eCursor_row_resize
:
7975 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "row-resize");
7977 case eCursor_col_resize
:
7978 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "col-resize");
7981 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "none");
7982 if (!gdkcursor
) newType
= MOZ_CURSOR_NONE
;
7985 NS_ASSERTION(aCursor
, "Invalid cursor type");
7986 gdkcursor
= gdk_cursor_new_from_name(defaultDisplay
, "default");
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
) {
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
) {
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);
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
;
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
);
8046 NS_WARNING("Cannot get nsWindow from GtkWidget");
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
);
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
8065 g_object_ref(widget
);
8067 [](gpointer data
) -> gboolean
{
8068 g_object_unref(data
);
8069 return G_SOURCE_REMOVE
;
8076 static gboolean
configure_event_cb(GtkWidget
* widget
,
8077 GdkEventConfigure
* event
) {
8078 RefPtr
<nsWindow
> window
= get_window_for_gtk_widget(widget
);
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
);
8097 static void widget_unmap_cb(GtkWidget
* widget
) {
8098 RefPtr
<nsWindow
> window
= get_window_for_gtk_widget(widget
);
8105 static void size_allocate_cb(GtkWidget
* widget
, GtkAllocation
* allocation
) {
8106 RefPtr
<nsWindow
> window
= get_window_for_gtk_widget(widget
);
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
);
8121 window
->UpdateTopLevelOpaqueRegion();
8124 static gboolean
delete_event_cb(GtkWidget
* widget
, GdkEventAny
* event
) {
8125 RefPtr
<nsWindow
> window
= get_window_for_gtk_widget(widget
);
8130 window
->OnDeleteEvent();
8135 static gboolean
enter_notify_event_cb(GtkWidget
* widget
,
8136 GdkEventCrossing
* event
) {
8137 RefPtr
<nsWindow
> window
= get_window_for_gdk_window(event
->window
);
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.
8155 RefPtr
<nsWindow
> leftWindow
=
8156 get_window_for_gdk_window(sStoredLeaveNotifyEvent
->window
);
8158 leftWindow
->OnLeaveNotifyEvent(sStoredLeaveNotifyEvent
.get());
8162 window
->OnEnterNotifyEvent(event
);
8166 static gboolean
leave_notify_event_cb(GtkWidget
* widget
,
8167 GdkEventCrossing
* event
) {
8168 RefPtr
<nsWindow
> window
= get_window_for_gdk_window(event
->window
);
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
))));
8179 sStoredLeaveNotifyEvent
= nullptr;
8180 window
->OnLeaveNotifyEvent(event
);
8186 static nsWindow
* GetFirstNSWindowForGDKWindow(GdkWindow
* aGdkWindow
) {
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
);
8202 static gboolean
motion_notify_event_cb(GtkWidget
* widget
,
8203 GdkEventMotion
* event
) {
8204 UpdateLastInputEventTime(event
);
8206 RefPtr
<nsWindow
> window
= GetFirstNSWindowForGDKWindow(event
->window
);
8211 window
->OnMotionNotifyEvent(event
);
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()) {
8224 RefPtr
<nsWindow
> window
= GetFirstNSWindowForGDKWindow(event
->window
);
8229 window
->OnButtonPressEvent(event
);
8231 if (GdkIsWaylandDisplay()) {
8232 WaylandDragWorkaround(event
);
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()) {
8246 RefPtr
<nsWindow
> window
= GetFirstNSWindowForGDKWindow(event
->window
);
8251 window
->OnButtonReleaseEvent(event
);
8256 static gboolean
focus_in_event_cb(GtkWidget
* widget
, GdkEventFocus
* event
) {
8257 RefPtr
<nsWindow
> window
= get_window_for_gtk_widget(widget
);
8262 window
->OnContainerFocusInEvent(event
);
8267 static gboolean
focus_out_event_cb(GtkWidget
* widget
, GdkEventFocus
* event
) {
8268 RefPtr
<nsWindow
> window
= get_window_for_gtk_widget(widget
);
8273 window
->OnContainerFocusOutEvent(event
);
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
);
8318 return GDK_FILTER_CONTINUE
;
8321 GtkWindow
* parent
= gtk_window_get_transient_for(GTK_WINDOW(widget
));
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
);
8357 RefPtr
<nsWindow
> focusWindow
= gFocusWindow
? gFocusWindow
: window
;
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.
8371 GdkDisplay
* gdkDisplay
= gtk_widget_get_display(widget
);
8372 if (GdkIsX11Display(gdkDisplay
)) {
8373 Display
* dpy
= GDK_DISPLAY_XDISPLAY(gdkDisplay
);
8374 while (XPending(dpy
)) {
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
)) {
8384 XNextEvent(dpy
, &next_event
);
8385 event
->time
= next_event
.xkey
.time
;
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
);
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
);
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
)) {
8424 window
->OnScrollEvent(event
);
8429 static gboolean
visibility_notify_event_cb(GtkWidget
* widget
,
8430 GdkEventVisibility
* event
) {
8431 RefPtr
<nsWindow
> window
= get_window_for_gdk_window(event
->window
);
8435 window
->OnVisibilityNotifyEvent(event
->state
);
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
);
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
);
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
);
8483 window
->OnWindowStateEvent(widget
, event
);
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
));
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
);
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
);
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
);
8544 return window
->OnTouchEvent(aEvent
);
8547 // This function called generic because there is no signal specific to touchpad
8549 static gboolean
generic_event_cb(GtkWidget
* widget
, GdkEvent
* aEvent
) {
8550 if (aEvent
->type
!= GDK_TOUCHPAD_PINCH
) {
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
);
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
,
8577 // Workaround for Bug 1710344
8578 // Caused by Gtk issue https://gitlab.gnome.org/GNOME/gtk/-/issues/4437
8579 if (aWindow
->IsWaylandPopup()) {
8581 gdk_window_get_position(aWindow
->GetToplevelGdkWindow(), &tx
, &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
,
8592 RefPtr
<nsWindow
> window
= get_window_for_gtk_widget(aWidget
);
8593 if (!window
|| !window
->GetGdkWindow()) {
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()) {
8602 gdk_window_get_geometry(window
->GetGdkWindow(), &x
, &y
, nullptr, nullptr);
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
)) {
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
);
8629 LOGDRAG(" Failed - can't find nsWindow!\n");
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");
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");
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
,
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()) {
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()) {
8677 gdk_window_get_geometry(window
->GetGdkWindow(), &x
, &y
, nullptr, nullptr);
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
,
8698 GtkSelectionData
* aSelectionData
,
8699 guint aInfo
, guint aTime
,
8701 RefPtr
<nsWindow
> window
= get_window_for_gtk_widget(aWidget
);
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);
8714 gUseAspectRatio
= IsGnomeDesktopEnvironment() || IsKdeDesktopEnvironment();
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()) {
8732 nsAccessibilityService
* accService
= GetOrCreateAccService();
8737 // Get the root document accessible and fire event to it.
8738 a11y::LocalAccessible
* acc
= GetRootAccessible();
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
) {
8771 mIMContext
->SetInputContext(this, &aContext
, &aAction
);
8774 InputContext
nsWindow::GetInputContext() {
8775 InputContext context
;
8777 context
.mIMEState
.mEnabled
= IMEEnabled::Disabled
;
8778 context
.mIMEState
.mOpen
= IMEState::OPEN_STATE_NOT_SUPPORTED
;
8780 context
= mIMContext
->GetInputContext();
8785 TextEventDispatcherListener
* nsWindow::GetNativeTextEventDispatcherListener() {
8786 if (NS_WARN_IF(!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
))) {
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
);
8812 already_AddRefed
<DrawTarget
> nsWindow::StartRemoteDrawingInRegion(
8813 const LayoutDeviceIntRegion
& aInvalidRegion
, BufferMode
* aBufferMode
) {
8814 return mSurfaceProvider
.StartRemoteDrawingInRegion(aInvalidRegion
,
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
8831 // get the gdk window for this widget
8832 GdkWindow
* gdk_window
= mGdkWindow
;
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");
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
) {
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
;
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
8878 LayoutDeviceIntPoint offset
= aMouseEvent
->mWidget
->WidgetToScreenOffset();
8879 *aRootX
= aMouseEvent
->mRefPoint
.x
+ offset
.x
;
8880 *aRootY
= aMouseEvent
->mRefPoint
.y
+ offset
.y
;
8885 nsIWidget::WindowRenderer
* nsWindow::GetWindowRenderer() {
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
) {
8900 mGotNonBlankPaint
= true;
8901 if (!mConfiguredClearColor
) {
8902 // Nothing to do, we hadn't overridden the clear color.
8903 mConfiguredClearColor
= true;
8906 // Reset the clear color set in the expose event to transparent.
8907 GetWindowRenderer()->AsWebRender()->WrBridge()->SendSetDefaultClearColor(
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());
8927 mCompositorWidgetDelegate
= delegate
->AsPlatformSpecificDelegate();
8928 MOZ_ASSERT(mCompositorWidgetDelegate
,
8929 "nsWindow::SetCompositorWidgetDelegate called with a "
8930 "non-PlatformCompositorWidgetDelegate");
8932 ConfigureCompositor();
8935 mCompositorWidgetDelegate
= nullptr;
8939 nsresult
nsWindow::SetNonClientMargins(const LayoutDeviceIntMargin
& aMargins
) {
8940 SetDrawsInTitlebar(aMargins
.top
== 0);
8944 bool nsWindow::IsAlwaysUndecoratedWindow() const {
8945 if (mIsPIPWindow
|| gKioskMode
) {
8948 if (mWindowType
== WindowType::Dialog
&&
8949 mBorderStyle
!= BorderStyle::Default
&&
8950 mBorderStyle
!= BorderStyle::All
&&
8951 !(mBorderStyle
& BorderStyle::Title
) &&
8952 !(mBorderStyle
& BorderStyle::ResizeH
)) {
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");
8969 MOZ_ASSERT(aState
, "Unexpected decoration request");
8970 MOZ_ASSERT(!gtk_window_get_decorated(GTK_WINDOW(mShell
)));
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);
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
;
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
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,
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
);
9049 gtk_widget_destroy(tmpWindow
);
9052 if (mTransparencyBitmapForTitlebar
) {
9053 if (mDrawInTitlebar
&& mSizeMode
== nsSizeMode_Normal
&& !mIsTiled
) {
9054 UpdateTitlebarTransparencyBitmap();
9056 ClearTransparencyBitmap();
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() {
9085 double fractional_scale
= [&] {
9086 if (IsTopLevelWindowType()) {
9087 return mFractionalScaleFactor
;
9089 if (nsWindow
* topmost
= GetTopmostWindow()) {
9090 return topmost
->mFractionalScaleFactor
;
9094 if (fractional_scale
!= 0.0) {
9095 return fractional_scale
;
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
,
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");
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
: {
9180 memset(&event
, 0, sizeof(GdkEvent
));
9181 event
.type
= aNativeMessage
== NativeMouseMessage::ButtonDown
9183 : GDK_BUTTON_RELEASE
;
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;
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
);
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?
9219 // Impossible to warp the pointer on Wayland.
9220 // For pointer lock, pointer-constraints and relative-pointer are used.
9221 if (GdkIsWaylandDisplay()) {
9225 GdkScreen
* screen
= gdk_window_get_screen(mGdkWindow
);
9226 GdkPoint point
= DevicePixelsToGdkPointRoundDown(aPoint
);
9227 gdk_device_warp(GdkGetPointer(), screen
, point
.x
, point
.y
);
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
) {
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
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");
9277 CreateAndPutGdkScrollEvent(aPoint
, aDeltaX
, aDeltaY
);
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");
9295 memset(&event
, 0, sizeof(GdkEvent
));
9297 static std::map
<uint32_t, GdkEventSequence
*> sKnownPointers
;
9299 auto result
= sKnownPointers
.find(aPointerId
);
9300 switch (aPointerState
) {
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
;
9311 event
.touch
.sequence
= result
->second
;
9312 event
.type
= GDK_TOUCH_UPDATE
;
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
);
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
);
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
);
9357 nsresult
nsWindow::SynthesizeNativeTouchPadPinch(
9358 TouchpadGesturePhase aEventPhase
, float aScale
, LayoutDeviceIntPoint aPoint
,
9359 int32_t aModifierFlags
) {
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
>(
9377 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent
) -
9378 widgetToScreenOffset
;
9380 gdouble dx
= 0, dy
= 0;
9382 switch (aEventPhase
) {
9384 touchpad_event
->phase
= GDK_TOUCHPAD_GESTURE_PHASE_BEGIN
;
9385 mCurrentSynthesizedTouchpadPinch
= {pointInWindow
, pointInWindow
};
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
;
9394 touchpad_event
->phase
= GDK_TOUCHPAD_GESTURE_PHASE_END
;
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
);
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");
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
9460 CreateAndPutGdkScrollEvent(aPoint
, aDeltaX
, aDeltaY
);
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
;
9507 return sGtkWindowDecoration
;
9510 bool nsWindow::TitlebarUseShapeMask() {
9511 static int useShapeMask
= []() {
9512 // Don't use titlebar shape mask on Wayland
9513 if (!GdkIsX11Display()) {
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()) {
9523 return Preferences::GetBool("widget.titlebar-x11-use-shape-mask", false);
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(),
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();
9546 displayName
= nsCString(XDisplayString(display
));
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
,
9567 GdkDisplay
* display
;
9569 display
= gdk_display_get_default();
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);
9576 XDeleteProperty(GDK_DISPLAY_XDISPLAY(display
), xid
,
9577 gdk_x11_get_xatom_by_name_for_display(display
, atom_name
));
9582 void nsWindow::SetProgress(unsigned long progressPercent
) {
9585 if (!GdkIsX11Display()) {
9593 progressPercent
= MIN(progressPercent
, 100);
9595 set_window_hint_cardinal(GDK_WINDOW_XID(GetToplevelGdkWindow()),
9596 PROGRESS_HINT
, progressPercent
);
9601 void nsWindow::SetCompositorHint(WindowComposeRequest aState
) {
9602 if (!GdkIsX11Display()) {
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
),
9612 GDK_PROP_MODE_REPLACE
, (guchar
*)&value
, 1);
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);
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);
9627 aFontName
.Assign(fontName
);
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();
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()) {
9676 auto* waylandDisplay
= WaylandDisplayGet();
9678 auto* pointerConstraints
= waylandDisplay
->GetPointerConstraints();
9679 if (!pointerConstraints
) {
9683 auto* relativePointerMgr
= waylandDisplay
->GetRelativePointerManager();
9684 if (!relativePointerMgr
) {
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
);
9695 NS_WARNING("Could not find Wayland pointer to lock");
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());
9704 /* Can be null when the window is hidden.
9705 * Though it's unlikely that a lock request comes in that case, be
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");
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;
9731 zwp_relative_pointer_v1_add_listener(mRelativePointer
,
9732 &relative_pointer_listener
, this);
9735 void nsWindow::UnlockNativePointer() {
9736 if (!GdkIsWaylandDisplay()) {
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;
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()) {
9757 // All headless windows are considered active so they are painted.
9758 if (gfxPlatform::IsHeadless()) {
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());
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
) {
9779 if (nsIFrame
* foundFrame
= FindTitlebarFrame(childFrame
)) {
9786 nsIFrame
* nsWindow::GetFrame() const {
9787 nsView
* view
= nsView::GetViewFor(this);
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()) {
9815 nsIFrame
* frame
= GetFrame();
9820 frame
= FindTitlebarFrame(frame
);
9822 nsIContent
* content
= frame
->GetContent();
9824 nsLayoutUtils::PostRestyleEvent(content
->AsElement(), RestyleHint
{0},
9825 nsChangeHint_RepaintFrame
);
9830 void nsWindow::LockAspectRatio(bool aShouldLock
) {
9831 if (!gUseAspectRatio
) {
9836 int decWidth
= 0, decHeight
= 0;
9837 AddCSDDecorationSize(&decWidth
, &decHeight
);
9840 DevicePixelsToGdkCoordRoundDown(mLastSizeRequest
.width
) + decWidth
;
9842 DevicePixelsToGdkCoordRoundDown(mLastSizeRequest
.height
) + decHeight
;
9844 mAspectRatio
= width
/ height
;
9845 LOG("nsWindow::LockAspectRatio() width %f height %f aspect %f", width
,
9846 height
, mAspectRatio
);
9849 LOG("nsWindow::LockAspectRatio() removed aspect ratio");
9852 ApplySizeConstraints();
9855 nsWindow
* nsWindow::GetFocusedWindow() { return gFocusWindow
; }
9858 bool nsWindow::SetEGLNativeWindowSize(
9859 const LayoutDeviceIntSize
& aEGLWindowSize
) {
9860 if (!GdkIsWaylandDisplay()) {
9864 // SetEGLNativeWindowSize() is called from renderer/compositor thread,
9865 // make sure nsWindow is not destroyed.
9868 // See NS_NATIVE_EGL_WINDOW why we can't block here.
9869 if (mDestroyMutex
.TryLock()) {
9870 if (!mIsDestroyed
) {
9871 gint scale
= GdkCeiledScaleFactor();
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 "
9884 aEGLWindowSize
.width
, aEGLWindowSize
.height
, scale
,
9885 aEGLWindowSize
.width
/ scale
, aEGLWindowSize
.height
/ scale
);
9889 paint
= moz_container_wayland_egl_window_set_size(
9890 mContainer
, aEGLWindowSize
.ToUnknownSize(), scale
);
9892 mDestroyMutex
.Unlock();
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()");
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();
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(),
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
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");
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");
9995 void nsWindow::NotifyOcclusionState(OcclusionState aState
) {
9996 if (!IsTopLevelWindowType()) {
10000 bool isFullyOccluded
= aState
== OcclusionState::OCCLUDED
;
10001 if (mIsFullyOccluded
== isFullyOccluded
) {
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
;
10015 (widget::GdkIsWaylandDisplay() || widget::IsXWaylandProtocol())) {
10016 if (auto* menuPopupFrame
= GetMenuPopupFrame(GetFrame())) {
10017 menuPopupFrame
->SetIsDragSource(!!aSourceDragContext
);