1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
3  */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 
8 #include "nsWindow.h"
9 
10 #include <algorithm>
11 #include <dlfcn.h>
12 #include <gdk/gdkkeysyms.h>
13 #include <wchar.h>
14 
15 #include "gfx2DGlue.h"
16 #include "gfxContext.h"
17 #include "gfxImageSurface.h"
18 #include "gfxPlatformGtk.h"
19 #include "gfxUtils.h"
20 #include "GLContextProvider.h"
21 #include "GLContext.h"
22 #include "GtkCompositorWidget.h"
23 #include "gtkdrawing.h"
24 #include "imgIContainer.h"
25 #include "InputData.h"
26 #include "Layers.h"
27 #include "mozilla/ArrayUtils.h"
28 #include "mozilla/Assertions.h"
29 #include "mozilla/Components.h"
30 #include "mozilla/dom/Document.h"
31 #include "mozilla/dom/WheelEventBinding.h"
32 #include "mozilla/gfx/2D.h"
33 #include "mozilla/gfx/gfxVars.h"
34 #include "mozilla/gfx/GPUProcessManager.h"
35 #include "mozilla/gfx/HelpersCairo.h"
36 #include "mozilla/layers/LayersTypes.h"
37 #include "mozilla/layers/CompositorBridgeChild.h"
38 #include "mozilla/layers/CompositorBridgeParent.h"
39 #include "mozilla/layers/CompositorThread.h"
40 #include "mozilla/layers/KnowsCompositor.h"
41 #include "mozilla/layers/WebRenderBridgeChild.h"
42 #include "mozilla/layers/WebRenderLayerManager.h"
43 #include "mozilla/layers/APZInputBridge.h"
44 #include "mozilla/layers/IAPZCTreeManager.h"
45 #include "mozilla/Likely.h"
46 #include "mozilla/MiscEvents.h"
47 #include "mozilla/MouseEvents.h"
48 #include "mozilla/NativeKeyBindingsType.h"
49 #include "mozilla/Preferences.h"
50 #include "mozilla/PresShell.h"
51 #include "mozilla/ProfilerLabels.h"
52 #include "mozilla/ScopeExit.h"
53 #include "mozilla/StaticPrefs_apz.h"
54 #include "mozilla/StaticPrefs_ui.h"
55 #include "mozilla/StaticPrefs_widget.h"
56 #include "mozilla/TextEventDispatcher.h"
57 #include "mozilla/TextEvents.h"
58 #include "mozilla/TimeStamp.h"
59 #include "mozilla/UniquePtrExtensions.h"
60 #include "mozilla/WidgetUtils.h"
61 #include "mozilla/WritingModes.h"
62 #include "mozilla/X11Util.h"
63 #include "mozilla/XREAppData.h"
64 #include "NativeKeyBindings.h"
65 #include "nsAppDirectoryServiceDefs.h"
66 #include "nsAppRunner.h"
67 #include "nsDragService.h"
68 #include "nsGTKToolkit.h"
69 #include "nsGtkKeyUtils.h"
70 #include "nsGtkCursors.h"
71 #include "nsGfxCIID.h"
72 #include "nsGtkUtils.h"
73 #include "nsIFile.h"
74 #include "nsIGSettingsService.h"
75 #include "nsIInterfaceRequestorUtils.h"
76 #include "nsImageToPixbuf.h"
77 #include "nsINode.h"
78 #include "nsIRollupListener.h"
79 #include "nsIScreenManager.h"
80 #include "nsIUserIdleServiceInternal.h"
81 #include "nsIWidgetListener.h"
82 #include "nsLayoutUtils.h"
83 #include "nsMenuPopupFrame.h"
84 #include "nsPresContext.h"
85 #include "nsShmImage.h"
86 #include "nsString.h"
87 #include "nsWidgetsCID.h"
88 #include "nsViewManager.h"
89 #include "nsXPLookAndFeel.h"
90 #include "prlink.h"
91 #include "ScreenHelperGTK.h"
92 #include "SystemTimeConverter.h"
93 #include "WidgetUtilsGtk.h"
94 #include "mozilla/X11Util.h"
95 
96 #ifdef ACCESSIBILITY
97 #  include "mozilla/a11y/LocalAccessible.h"
98 #  include "mozilla/a11y/Platform.h"
99 #  include "nsAccessibilityService.h"
100 #endif
101 
102 #ifdef MOZ_X11
103 #  include <gdk/gdkkeysyms-compat.h>
104 #  include <X11/Xatom.h>
105 #  include <X11/extensions/XShm.h>
106 #  include <X11/extensions/shape.h>
107 #  include "gfxXlibSurface.h"
108 #  include "GLContextGLX.h"  // for GLContextGLX::FindVisual()
109 #  include "GLContextEGL.h"  // for GLContextEGL::FindVisual()
110 #  include "WindowSurfaceX11Image.h"
111 #  include "WindowSurfaceX11SHM.h"
112 #endif
113 #ifdef MOZ_WAYLAND
114 #  include "nsIClipboard.h"
115 #  include "nsView.h"
116 #endif
117 
118 using namespace mozilla;
119 using namespace mozilla::gfx;
120 using namespace mozilla::layers;
121 using namespace mozilla::widget;
122 using mozilla::gl::GLContextEGL;
123 using mozilla::gl::GLContextGLX;
124 
125 // Don't put more than this many rects in the dirty region, just fluff
126 // out to the bounding-box if there are more
127 #define MAX_RECTS_IN_REGION 100
128 
129 #if !GTK_CHECK_VERSION(3, 18, 0)
130 
131 struct _GdkEventTouchpadPinch {
132   GdkEventType type;
133   GdkWindow* window;
134   gint8 send_event;
135   gint8 phase;
136   gint8 n_fingers;
137   guint32 time;
138   gdouble x;
139   gdouble y;
140   gdouble dx;
141   gdouble dy;
142   gdouble angle_delta;
143   gdouble scale;
144   gdouble x_root, y_root;
145   guint state;
146 };
147 
148 typedef enum {
149   GDK_TOUCHPAD_GESTURE_PHASE_BEGIN,
150   GDK_TOUCHPAD_GESTURE_PHASE_UPDATE,
151   GDK_TOUCHPAD_GESTURE_PHASE_END,
152   GDK_TOUCHPAD_GESTURE_PHASE_CANCEL
153 } GdkTouchpadGesturePhase;
154 
155 GdkEventMask GDK_TOUCHPAD_GESTURE_MASK = static_cast<GdkEventMask>(1 << 24);
156 GdkEventType GDK_TOUCHPAD_PINCH = static_cast<GdkEventType>(42);
157 
158 #endif
159 
160 const gint kEvents = GDK_TOUCHPAD_GESTURE_MASK | GDK_EXPOSURE_MASK |
161                      GDK_STRUCTURE_MASK | GDK_VISIBILITY_NOTIFY_MASK |
162                      GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
163                      GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
164                      GDK_SMOOTH_SCROLL_MASK | GDK_TOUCH_MASK | GDK_SCROLL_MASK |
165                      GDK_POINTER_MOTION_MASK | GDK_PROPERTY_CHANGE_MASK;
166 
167 #if !GTK_CHECK_VERSION(3, 22, 0)
168 typedef enum {
169   GDK_ANCHOR_FLIP_X = 1 << 0,
170   GDK_ANCHOR_FLIP_Y = 1 << 1,
171   GDK_ANCHOR_SLIDE_X = 1 << 2,
172   GDK_ANCHOR_SLIDE_Y = 1 << 3,
173   GDK_ANCHOR_RESIZE_X = 1 << 4,
174   GDK_ANCHOR_RESIZE_Y = 1 << 5,
175   GDK_ANCHOR_FLIP = GDK_ANCHOR_FLIP_X | GDK_ANCHOR_FLIP_Y,
176   GDK_ANCHOR_SLIDE = GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_SLIDE_Y,
177   GDK_ANCHOR_RESIZE = GDK_ANCHOR_RESIZE_X | GDK_ANCHOR_RESIZE_Y
178 } GdkAnchorHints;
179 #endif
180 
181 /* utility functions */
182 static bool is_mouse_in_window(GdkWindow* aWindow, gdouble aMouseX,
183                                gdouble aMouseY);
184 static nsWindow* get_window_for_gtk_widget(GtkWidget* widget);
185 static nsWindow* get_window_for_gdk_window(GdkWindow* window);
186 static GtkWidget* get_gtk_widget_for_gdk_window(GdkWindow* window);
187 static GdkCursor* get_gtk_cursor(nsCursor aCursor);
188 static GdkWindow* get_inner_gdk_window(GdkWindow* aWindow, gint x, gint y,
189                                        gint* retx, gint* rety);
190 
191 static int is_parent_ungrab_enter(GdkEventCrossing* aEvent);
192 static int is_parent_grab_leave(GdkEventCrossing* aEvent);
193 
194 /* callbacks from widgets */
195 static gboolean expose_event_cb(GtkWidget* widget, cairo_t* cr);
196 static gboolean configure_event_cb(GtkWidget* widget, GdkEventConfigure* event);
197 static void widget_map_cb(GtkWidget* widget);
198 static void widget_unrealize_cb(GtkWidget* widget);
199 static void size_allocate_cb(GtkWidget* widget, GtkAllocation* allocation);
200 static void toplevel_window_size_allocate_cb(GtkWidget* widget,
201                                              GtkAllocation* allocation);
202 static gboolean delete_event_cb(GtkWidget* widget, GdkEventAny* event);
203 static gboolean enter_notify_event_cb(GtkWidget* widget,
204                                       GdkEventCrossing* event);
205 static gboolean leave_notify_event_cb(GtkWidget* widget,
206                                       GdkEventCrossing* event);
207 static gboolean motion_notify_event_cb(GtkWidget* widget,
208                                        GdkEventMotion* event);
209 MOZ_CAN_RUN_SCRIPT static gboolean button_press_event_cb(GtkWidget* widget,
210                                                          GdkEventButton* event);
211 static gboolean button_release_event_cb(GtkWidget* widget,
212                                         GdkEventButton* event);
213 static gboolean focus_in_event_cb(GtkWidget* widget, GdkEventFocus* event);
214 static gboolean focus_out_event_cb(GtkWidget* widget, GdkEventFocus* event);
215 static gboolean key_press_event_cb(GtkWidget* widget, GdkEventKey* event);
216 static gboolean key_release_event_cb(GtkWidget* widget, GdkEventKey* event);
217 static gboolean property_notify_event_cb(GtkWidget* widget,
218                                          GdkEventProperty* event);
219 static gboolean scroll_event_cb(GtkWidget* widget, GdkEventScroll* event);
220 
221 static void hierarchy_changed_cb(GtkWidget* widget,
222                                  GtkWidget* previous_toplevel);
223 static gboolean window_state_event_cb(GtkWidget* widget,
224                                       GdkEventWindowState* event);
225 static void settings_xft_dpi_changed_cb(GtkSettings* settings,
226                                         GParamSpec* pspec, nsWindow* data);
227 static void check_resize_cb(GtkContainer* container, gpointer user_data);
228 static void screen_composited_changed_cb(GdkScreen* screen, gpointer user_data);
229 static void widget_composited_changed_cb(GtkWidget* widget, gpointer user_data);
230 
231 static void scale_changed_cb(GtkWidget* widget, GParamSpec* aPSpec,
232                              gpointer aPointer);
233 static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent);
234 static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent);
235 
236 static nsWindow* GetFirstNSWindowForGDKWindow(GdkWindow* aGdkWindow);
237 
238 #ifdef __cplusplus
239 extern "C" {
240 #endif /* __cplusplus */
241 #ifdef MOZ_X11
242 static GdkFilterReturn popup_take_focus_filter(GdkXEvent* gdk_xevent,
243                                                GdkEvent* event, gpointer data);
244 #endif /* MOZ_X11 */
245 #ifdef __cplusplus
246 }
247 #endif /* __cplusplus */
248 
249 static gboolean drag_motion_event_cb(GtkWidget* aWidget,
250                                      GdkDragContext* aDragContext, gint aX,
251                                      gint aY, guint aTime, gpointer aData);
252 static void drag_leave_event_cb(GtkWidget* aWidget,
253                                 GdkDragContext* aDragContext, guint aTime,
254                                 gpointer aData);
255 static gboolean drag_drop_event_cb(GtkWidget* aWidget,
256                                    GdkDragContext* aDragContext, gint aX,
257                                    gint aY, guint aTime, gpointer aData);
258 static void drag_data_received_event_cb(GtkWidget* aWidget,
259                                         GdkDragContext* aDragContext, gint aX,
260                                         gint aY,
261                                         GtkSelectionData* aSelectionData,
262                                         guint aInfo, guint32 aTime,
263                                         gpointer aData);
264 
265 /* initialization static functions */
266 static nsresult initialize_prefs(void);
267 
268 static guint32 sLastUserInputTime = GDK_CURRENT_TIME;
269 static guint32 sRetryGrabTime;
270 
TimeConverter()271 static SystemTimeConverter<guint32>& TimeConverter() {
272   static SystemTimeConverter<guint32> sTimeConverterSingleton;
273   return sTimeConverterSingleton;
274 }
275 
276 bool nsWindow::sTransparentMainWindow = false;
277 
278 namespace mozilla {
279 
280 class CurrentX11TimeGetter {
281  public:
CurrentX11TimeGetter(GdkWindow * aWindow)282   explicit CurrentX11TimeGetter(GdkWindow* aWindow)
283       : mWindow(aWindow), mAsyncUpdateStart() {}
284 
GetCurrentTime() const285   guint32 GetCurrentTime() const { return gdk_x11_get_server_time(mWindow); }
286 
GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp & aNow)287   void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
288     // Check for in-flight request
289     if (!mAsyncUpdateStart.IsNull()) {
290       return;
291     }
292     mAsyncUpdateStart = aNow;
293 
294     Display* xDisplay = GDK_WINDOW_XDISPLAY(mWindow);
295     Window xWindow = GDK_WINDOW_XID(mWindow);
296     unsigned char c = 'a';
297     Atom timeStampPropAtom = TimeStampPropAtom();
298     XChangeProperty(xDisplay, xWindow, timeStampPropAtom, timeStampPropAtom, 8,
299                     PropModeReplace, &c, 1);
300     XFlush(xDisplay);
301   }
302 
PropertyNotifyHandler(GtkWidget * aWidget,GdkEventProperty * aEvent)303   gboolean PropertyNotifyHandler(GtkWidget* aWidget, GdkEventProperty* aEvent) {
304     if (aEvent->atom != gdk_x11_xatom_to_atom(TimeStampPropAtom())) {
305       return FALSE;
306     }
307 
308     guint32 eventTime = aEvent->time;
309     TimeStamp lowerBound = mAsyncUpdateStart;
310 
311     TimeConverter().CompensateForBackwardsSkew(eventTime, lowerBound);
312     mAsyncUpdateStart = TimeStamp();
313     return TRUE;
314   }
315 
316  private:
TimeStampPropAtom()317   static Atom TimeStampPropAtom() {
318     return gdk_x11_get_xatom_by_name_for_display(gdk_display_get_default(),
319                                                  "GDK_TIMESTAMP_PROP");
320   }
321 
322   // This is safe because this class is stored as a member of mWindow and
323   // won't outlive it.
324   GdkWindow* mWindow;
325   TimeStamp mAsyncUpdateStart;
326 };
327 
328 }  // namespace mozilla
329 
330 static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID);
331 
332 // The window from which the focus manager asks us to dispatch key events.
333 static nsWindow* gFocusWindow = nullptr;
334 static bool gBlockActivateEvent = false;
335 static bool gGlobalsInitialized = false;
336 static bool gRaiseWindows = true;
337 static bool gTransparentWindows = true;
338 static bool gUseMoveToRect = true;
339 static bool gUseAspectRatio = true;
340 static uint32_t gLastTouchID = 0;
341 
342 #define NS_WINDOW_TITLE_MAX_LENGTH 4095
343 #define kWindowPositionSlop 20
344 
345 // cursor cache
346 static GdkCursor* gCursorCache[eCursorCount];
347 
348 // Sometimes this actually also includes the state of the modifier keys, but
349 // only the button state bits are used.
350 static guint gButtonState;
351 
GetBitmapStride(int32_t width)352 static inline int32_t GetBitmapStride(int32_t width) {
353 #if defined(MOZ_X11)
354   return (width + 7) / 8;
355 #else
356   return cairo_format_stride_for_width(CAIRO_FORMAT_A1, width);
357 #endif
358 }
359 
TimestampIsNewerThan(guint32 a,guint32 b)360 static inline bool TimestampIsNewerThan(guint32 a, guint32 b) {
361   // Timestamps are just the least significant bits of a monotonically
362   // increasing function, and so the use of unsigned overflow arithmetic.
363   return a - b <= G_MAXUINT32 / 2;
364 }
365 
UpdateLastInputEventTime(void * aGdkEvent)366 static void UpdateLastInputEventTime(void* aGdkEvent) {
367   nsCOMPtr<nsIUserIdleServiceInternal> idleService =
368       do_GetService("@mozilla.org/widget/useridleservice;1");
369   if (idleService) {
370     idleService->ResetIdleTimeOut(0);
371   }
372 
373   guint timestamp = gdk_event_get_time(static_cast<GdkEvent*>(aGdkEvent));
374   if (timestamp == GDK_CURRENT_TIME) return;
375 
376   sLastUserInputTime = timestamp;
377 }
378 
nsWindow()379 nsWindow::nsWindow()
380     : mIsDestroyed(false),
381       mNeedsDispatchResized(false),
382       mIsShown(false),
383       mNeedsShow(false),
384       mIsMapped(false),
385       mEnabled(true),
386       mCreated(false),
387       mHandleTouchEvent(false),
388       mIsDragPopup(false),
389       mWindowScaleFactorChanged(true),
390       mCompositedScreen(gdk_screen_is_composited(gdk_screen_get_default())),
391       mIsAccelerated(false),
392       mWindowShouldStartDragging(false),
393       mHasMappedToplevel(false),
394       mRetryPointerGrab(false),
395       mDrawToContainer(false),
396       mTitlebarBackdropState(false),
397       mIsPIPWindow(false),
398       mIsWaylandPanelWindow(false),
399       mIsChildWindow(false),
400       mAlwaysOnTop(false),
401       mNoAutoHide(false),
402       mMouseTransparent(false),
403       mIsTransparent(false),
404       mBoundsAreValid(true),
405       mPopupTrackInHierarchy(false),
406       mPopupTrackInHierarchyConfigured(false),
407       mHiddenPopupPositioned(false),
408       mTransparencyBitmapForTitlebar(false),
409       mHasAlphaVisual(false),
410       mPopupAnchored(false),
411       mPopupContextMenu(false),
412       mPopupMatchesLayout(false),
413       mPopupChanged(false),
414       mPopupTemporaryHidden(false),
415       mPopupClosed(false),
416       mPopupUseMoveToRect(false),
417       mPreferredPopupRectFlushed(false),
418       mWaitingForMoveToRectCallback(false),
419       mUpdatedByMoveToRectCallback(false),
420       mConfiguredClearColor(false),
421       mGotNonBlankPaint(false) {
422   mWindowType = eWindowType_child;
423   mSizeConstraints.mMaxSize = GetSafeWindowSize(mSizeConstraints.mMaxSize);
424 
425   if (!gGlobalsInitialized) {
426     gGlobalsInitialized = true;
427 
428     // It's OK if either of these fail, but it may not be one day.
429     initialize_prefs();
430 
431 #ifdef MOZ_WAYLAND
432     // Wayland provides clipboard data to application on focus-in event
433     // so we need to init our clipboard hooks before we create window
434     // and get focus.
435     if (GdkIsWaylandDisplay()) {
436       nsCOMPtr<nsIClipboard> clipboard =
437           do_GetService("@mozilla.org/widget/clipboard;1");
438       NS_ASSERTION(clipboard, "Failed to init clipboard!");
439     }
440 #endif
441   }
442 }
443 
~nsWindow()444 nsWindow::~nsWindow() {
445   LOG("nsWindow::~nsWindow()");
446 
447   delete[] mTransparencyBitmap;
448   mTransparencyBitmap = nullptr;
449 
450   Destroy();
451 }
452 
453 /* static */
ReleaseGlobals()454 void nsWindow::ReleaseGlobals() {
455   for (auto& cursor : gCursorCache) {
456     if (cursor) {
457       g_object_unref(cursor);
458       cursor = nullptr;
459     }
460   }
461 }
462 
DispatchActivateEvent(void)463 void nsWindow::DispatchActivateEvent(void) {
464   NS_ASSERTION(mContainer || mIsDestroyed,
465                "DispatchActivateEvent only intended for container windows");
466 
467 #ifdef ACCESSIBILITY
468   DispatchActivateEventAccessible();
469 #endif  // ACCESSIBILITY
470 
471   if (mWidgetListener) mWidgetListener->WindowActivated();
472 }
473 
DispatchDeactivateEvent(void)474 void nsWindow::DispatchDeactivateEvent(void) {
475   if (mWidgetListener) mWidgetListener->WindowDeactivated();
476 
477 #ifdef ACCESSIBILITY
478   DispatchDeactivateEventAccessible();
479 #endif  // ACCESSIBILITY
480 }
481 
DispatchResized()482 void nsWindow::DispatchResized() {
483   LOG("nsWindow::DispatchResized() size [%d, %d]", (int)(mBounds.width),
484       (int)(mBounds.height));
485 
486   mNeedsDispatchResized = false;
487   if (mWidgetListener) {
488     mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
489   }
490   if (mAttachedWidgetListener) {
491     mAttachedWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
492   }
493 }
494 
MaybeDispatchResized()495 void nsWindow::MaybeDispatchResized() {
496   if (mNeedsDispatchResized && !mIsDestroyed) {
497     DispatchResized();
498   }
499 }
500 
GetListener()501 nsIWidgetListener* nsWindow::GetListener() {
502   return mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener;
503 }
504 
DispatchEvent(WidgetGUIEvent * aEvent,nsEventStatus & aStatus)505 nsresult nsWindow::DispatchEvent(WidgetGUIEvent* aEvent,
506                                  nsEventStatus& aStatus) {
507 #ifdef DEBUG
508   debug_DumpEvent(stdout, aEvent->mWidget, aEvent, "something", 0);
509 #endif
510   aStatus = nsEventStatus_eIgnore;
511   nsIWidgetListener* listener = GetListener();
512   if (listener) {
513     aStatus = listener->HandleEvent(aEvent, mUseAttachedEvents);
514   }
515 
516   return NS_OK;
517 }
518 
OnDestroy(void)519 void nsWindow::OnDestroy(void) {
520   if (mOnDestroyCalled) return;
521 
522   mOnDestroyCalled = true;
523 
524   // Prevent deletion.
525   nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
526 
527   // release references to children, device context, toolkit + app shell
528   nsBaseWidget::OnDestroy();
529 
530   // Remove association between this object and its parent and siblings.
531   nsBaseWidget::Destroy();
532   mParent = nullptr;
533 
534   NotifyWindowDestroyed();
535 }
536 
AreBoundsSane()537 bool nsWindow::AreBoundsSane() {
538   return mBounds.width > 0 && mBounds.height > 0;
539 }
540 
541 // Walk the list of child windows and call destroy on them.
DestroyChildWindows()542 void nsWindow::DestroyChildWindows() {
543   LOG("nsWindow::DestroyChildWindows()");
544   if (!mGdkWindow) {
545     return;
546   }
547   while (GList* children = gdk_window_peek_children(mGdkWindow)) {
548     GdkWindow* child = GDK_WINDOW(children->data);
549     nsWindow* kid = get_window_for_gdk_window(child);
550     if (kid) {
551       kid->Destroy();
552     }
553   }
554 }
555 
Destroy()556 void nsWindow::Destroy() {
557   MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
558 
559   if (mIsDestroyed || !mCreated) return;
560 
561   LOG("nsWindow::Destroy\n");
562 
563   // Clear up WebRender queue
564   RevokeTransactionIdAllocator();
565 
566   DisableRenderingToWindow();
567 
568   mIsDestroyed = true;
569   mCreated = false;
570 
571   /** Need to clean our LayerManager up while still alive */
572   if (mWindowRenderer) {
573     mWindowRenderer->Destroy();
574   }
575   mWindowRenderer = nullptr;
576 
577 #ifdef MOZ_WAYLAND
578   // Shut down our local vsync source
579   if (mWaylandVsyncSource) {
580     mWaylandVsyncSource->Shutdown();
581     mWaylandVsyncSource = nullptr;
582   }
583   g_clear_pointer(&mXdgToken, xdg_activation_token_v1_destroy);
584 #endif
585 
586   if (mCompositorPauseTimeoutID) {
587     g_source_remove(mCompositorPauseTimeoutID);
588     mCompositorPauseTimeoutID = 0;
589   }
590 
591   // It is safe to call DestroyeCompositor several times (here and
592   // in the parent class) since it will take effect only once.
593   // The reason we call it here is because on gtk platforms we need
594   // to destroy the compositor before we destroy the gdk window (which
595   // destroys the the gl context attached to it).
596   DestroyCompositor();
597 
598   // Ensure any resources assigned to the window get cleaned up first
599   // to avoid double-freeing.
600   mSurfaceProvider.CleanupResources();
601 
602   g_signal_handlers_disconnect_by_data(gtk_settings_get_default(), this);
603 
604   nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
605   if (rollupListener) {
606     nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
607     if (static_cast<nsIWidget*>(this) == rollupWidget) {
608       rollupListener->Rollup(0, false, nullptr, nullptr);
609     }
610   }
611 
612   // dragService will be null after shutdown of the service manager.
613   RefPtr<nsDragService> dragService = nsDragService::GetInstance();
614   if (dragService && this == dragService->GetMostRecentDestWindow()) {
615     dragService->ScheduleLeaveEvent();
616   }
617 
618   NativeShow(false);
619 
620   if (mIMContext) {
621     mIMContext->OnDestroyWindow(this);
622   }
623 
624   // make sure that we remove ourself as the focus window
625   if (gFocusWindow == this) {
626     LOG("automatically losing focus...\n");
627     gFocusWindow = nullptr;
628   }
629 
630   gtk_widget_destroy(mShell);
631   mShell = nullptr;
632   mContainer = nullptr;
633 
634   MOZ_ASSERT(!mGdkWindow,
635              "mGdkWindow should be NULL when mContainer is destroyed");
636 
637 #ifdef ACCESSIBILITY
638   if (mRootAccessible) {
639     mRootAccessible = nullptr;
640   }
641 #endif
642 
643   // Save until last because OnDestroy() may cause us to be deleted.
644   OnDestroy();
645 }
646 
GetParent()647 nsIWidget* nsWindow::GetParent() { return mParent; }
648 
GetDPI()649 float nsWindow::GetDPI() {
650   float dpi = 96.0f;
651   nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
652   if (screen) {
653     screen->GetDpi(&dpi);
654   }
655   return dpi;
656 }
657 
GetDefaultScaleInternal()658 double nsWindow::GetDefaultScaleInternal() {
659   return FractionalScaleFactor() * gfxPlatformGtk::GetFontScaleFactor();
660 }
661 
GetDesktopToDeviceScale()662 DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScale() {
663 #ifdef MOZ_WAYLAND
664   if (GdkIsWaylandDisplay()) {
665     return DesktopToLayoutDeviceScale(GdkCeiledScaleFactor());
666   }
667 #endif
668 
669   // In Gtk/X11, we manage windows using device pixels.
670   return DesktopToLayoutDeviceScale(1.0);
671 }
672 
GetDesktopToDeviceScaleByScreen()673 DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScaleByScreen() {
674 #ifdef MOZ_WAYLAND
675   // In Wayland there's no way to get absolute position of the window and use it
676   // to determine the screen factor of the monitor on which the window is
677   // placed. The window is notified of the current scale factor but not at this
678   // point, so the GdkScaleFactor can return wrong value which can lead to wrong
679   // popup placement. We need to use parent's window scale factor for the new
680   // one.
681   if (GdkIsWaylandDisplay()) {
682     nsView* view = nsView::GetViewFor(this);
683     if (view) {
684       nsView* parentView = view->GetParent();
685       if (parentView) {
686         nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr);
687         if (parentWidget) {
688           return DesktopToLayoutDeviceScale(
689               parentWidget->RoundsWidgetCoordinatesTo());
690         }
691         NS_WARNING("Widget has no parent");
692       }
693     } else {
694       NS_WARNING("Cannot find widget view");
695     }
696   }
697 #endif
698   return nsBaseWidget::GetDesktopToDeviceScale();
699 }
700 
701 // Reparent a child window to a new parent.
SetParent(nsIWidget * aNewParent)702 void nsWindow::SetParent(nsIWidget* aNewParent) {
703   LOG("nsWindow::SetParent() new parent %p", aNewParent);
704   if (!mIsChildWindow) {
705     NS_WARNING("Used by child widgets only");
706     return;
707   }
708 
709   nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
710   if (mParent) {
711     mParent->RemoveChild(this);
712   }
713   mParent = aNewParent;
714 
715   // We're already deleted, quit.
716   if (!mGdkWindow || mIsDestroyed || !aNewParent) {
717     return;
718   }
719   aNewParent->AddChild(this);
720 
721   auto* newParent = static_cast<nsWindow*>(aNewParent);
722 
723   // New parent is deleted, quit.
724   if (newParent->mIsDestroyed) {
725     Destroy();
726     return;
727   }
728 
729   GdkWindow* window = GetToplevelGdkWindow();
730   GdkWindow* parentWindow = newParent->GetToplevelGdkWindow();
731   LOG("  child GdkWindow %p set parent GdkWindow %p", window, parentWindow);
732   gdk_window_reparent(window, parentWindow, 0, 0);
733 
734   bool parentHasMappedToplevel = newParent && newParent->mHasMappedToplevel;
735   if (mHasMappedToplevel != parentHasMappedToplevel) {
736     SetHasMappedToplevel(parentHasMappedToplevel);
737   }
738 }
739 
WidgetTypeSupportsAcceleration()740 bool nsWindow::WidgetTypeSupportsAcceleration() {
741   if (mWindowType == eWindowType_invisible) {
742     return false;
743   }
744 
745   if (IsSmallPopup()) {
746     return false;
747   }
748   // Workaround for Bug 1479135
749   // We draw transparent popups on non-compositing screens by SW as we don't
750   // implement X shape masks in WebRender.
751   if (mWindowType == eWindowType_popup) {
752     return HasRemoteContent() && mCompositedScreen;
753   }
754 
755   return true;
756 }
757 
ReparentNativeWidget(nsIWidget * aNewParent)758 void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) {
759   MOZ_ASSERT(aNewParent, "null widget");
760   MOZ_ASSERT(!mIsDestroyed, "");
761   MOZ_ASSERT(!static_cast<nsWindow*>(aNewParent)->mIsDestroyed, "");
762   MOZ_ASSERT(
763       !mParent,
764       "nsWindow::ReparentNativeWidget() works on toplevel windows only.");
765 
766   auto* newParent = static_cast<nsWindow*>(aNewParent);
767   GtkWindow* newParentWidget = GTK_WINDOW(newParent->GetGtkWidget());
768 
769   LOG("nsWindow::ReparentNativeWidget new parent %p\n", newParent);
770   gtk_window_set_transient_for(GTK_WINDOW(mShell), newParentWidget);
771 }
772 
SetModal(bool aModal)773 void nsWindow::SetModal(bool aModal) {
774   LOG("nsWindow::SetModal %d\n", aModal);
775   if (mIsDestroyed) return;
776   gtk_window_set_modal(GTK_WINDOW(mShell), aModal ? TRUE : FALSE);
777 }
778 
779 // nsIWidget method, which means IsShown.
IsVisible() const780 bool nsWindow::IsVisible() const { return mIsShown; }
781 
RegisterTouchWindow()782 void nsWindow::RegisterTouchWindow() {
783   mHandleTouchEvent = true;
784   mTouches.Clear();
785 }
786 
ConstrainPosition(bool aAllowSlop,int32_t * aX,int32_t * aY)787 void nsWindow::ConstrainPosition(bool aAllowSlop, int32_t* aX, int32_t* aY) {
788   if (!mShell || GdkIsWaylandDisplay()) {
789     return;
790   }
791 
792   double dpiScale = GetDefaultScale().scale;
793 
794   // we need to use the window size in logical screen pixels
795   int32_t logWidth = std::max(NSToIntRound(mBounds.width / dpiScale), 1);
796   int32_t logHeight = std::max(NSToIntRound(mBounds.height / dpiScale), 1);
797 
798   /* get our playing field. use the current screen, or failing that
799     for any reason, use device caps for the default screen. */
800   nsCOMPtr<nsIScreen> screen;
801   nsCOMPtr<nsIScreenManager> screenmgr =
802       do_GetService("@mozilla.org/gfx/screenmanager;1");
803   if (screenmgr) {
804     screenmgr->ScreenForRect(*aX, *aY, logWidth, logHeight,
805                              getter_AddRefs(screen));
806   }
807 
808   // We don't have any screen so leave the coordinates as is
809   if (!screen) return;
810 
811   nsIntRect screenRect;
812   if (mSizeMode != nsSizeMode_Fullscreen) {
813     // For normalized windows, use the desktop work area.
814     screen->GetAvailRectDisplayPix(&screenRect.x, &screenRect.y,
815                                    &screenRect.width, &screenRect.height);
816   } else {
817     // For full screen windows, use the desktop.
818     screen->GetRectDisplayPix(&screenRect.x, &screenRect.y, &screenRect.width,
819                               &screenRect.height);
820   }
821 
822   if (aAllowSlop) {
823     if (*aX < screenRect.x - logWidth + kWindowPositionSlop) {
824       *aX = screenRect.x - logWidth + kWindowPositionSlop;
825     } else if (*aX >= screenRect.XMost() - kWindowPositionSlop) {
826       *aX = screenRect.XMost() - kWindowPositionSlop;
827     }
828 
829     if (*aY < screenRect.y - logHeight + kWindowPositionSlop) {
830       *aY = screenRect.y - logHeight + kWindowPositionSlop;
831     } else if (*aY >= screenRect.YMost() - kWindowPositionSlop) {
832       *aY = screenRect.YMost() - kWindowPositionSlop;
833     }
834   } else {
835     if (*aX < screenRect.x) {
836       *aX = screenRect.x;
837     } else if (*aX >= screenRect.XMost() - logWidth) {
838       *aX = screenRect.XMost() - logWidth;
839     }
840 
841     if (*aY < screenRect.y) {
842       *aY = screenRect.y;
843     } else if (*aY >= screenRect.YMost() - logHeight) {
844       *aY = screenRect.YMost() - logHeight;
845     }
846   }
847 }
848 
SetSizeConstraints(const SizeConstraints & aConstraints)849 void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
850   mSizeConstraints.mMinSize = GetSafeWindowSize(aConstraints.mMinSize);
851   mSizeConstraints.mMaxSize = GetSafeWindowSize(aConstraints.mMaxSize);
852 
853   ApplySizeConstraints();
854 }
855 
AddCSDDecorationSize(int * aWidth,int * aHeight)856 void nsWindow::AddCSDDecorationSize(int* aWidth, int* aHeight) {
857   if (mSizeState == nsSizeMode_Normal &&
858       mGtkWindowDecoration == GTK_DECORATION_CLIENT && mDrawInTitlebar) {
859     GtkBorder decorationSize = GetCSDDecorationSize(IsPopup());
860     *aWidth += decorationSize.left + decorationSize.right;
861     *aHeight += decorationSize.top + decorationSize.bottom;
862   }
863 }
864 
865 #ifdef MOZ_WAYLAND
GetCSDDecorationOffset(int * aDx,int * aDy)866 bool nsWindow::GetCSDDecorationOffset(int* aDx, int* aDy) {
867   if (mSizeState == nsSizeMode_Normal &&
868       mGtkWindowDecoration == GTK_DECORATION_CLIENT && mDrawInTitlebar) {
869     GtkBorder decorationSize = GetCSDDecorationSize(IsPopup());
870     *aDx = decorationSize.left;
871     *aDy = decorationSize.top;
872     return true;
873   }
874   return false;
875 }
876 #endif
877 
ApplySizeConstraints(void)878 void nsWindow::ApplySizeConstraints(void) {
879   if (mShell) {
880     GdkGeometry geometry;
881     geometry.min_width =
882         DevicePixelsToGdkCoordRoundUp(mSizeConstraints.mMinSize.width);
883     geometry.min_height =
884         DevicePixelsToGdkCoordRoundUp(mSizeConstraints.mMinSize.height);
885     geometry.max_width =
886         DevicePixelsToGdkCoordRoundDown(mSizeConstraints.mMaxSize.width);
887     geometry.max_height =
888         DevicePixelsToGdkCoordRoundDown(mSizeConstraints.mMaxSize.height);
889 
890     uint32_t hints = 0;
891     if (mSizeConstraints.mMinSize != LayoutDeviceIntSize(0, 0)) {
892       if (GdkIsWaylandDisplay()) {
893         gtk_widget_set_size_request(GTK_WIDGET(mContainer), geometry.min_width,
894                                     geometry.min_height);
895       }
896       AddCSDDecorationSize(&geometry.min_width, &geometry.min_height);
897       hints |= GDK_HINT_MIN_SIZE;
898     }
899     if (mSizeConstraints.mMaxSize !=
900         LayoutDeviceIntSize(NS_MAXSIZE, NS_MAXSIZE)) {
901       AddCSDDecorationSize(&geometry.max_width, &geometry.max_height);
902       hints |= GDK_HINT_MAX_SIZE;
903     }
904 
905     if (mAspectRatio != 0.0f) {
906       geometry.min_aspect = mAspectRatio;
907       geometry.max_aspect = mAspectRatio;
908       hints |= GDK_HINT_ASPECT;
909     }
910 
911     gtk_window_set_geometry_hints(GTK_WINDOW(mShell), nullptr, &geometry,
912                                   GdkWindowHints(hints));
913   }
914 }
915 
Show(bool aState)916 void nsWindow::Show(bool aState) {
917   if (aState == mIsShown) return;
918 
919   mIsShown = aState;
920 
921   LOG("nsWindow::Show state %d frame %s\n", aState, GetFrameTag().get());
922 
923   if (aState) {
924     // Now that this window is shown, mHasMappedToplevel needs to be
925     // tracked on viewable descendants.
926     SetHasMappedToplevel(mHasMappedToplevel);
927   }
928 
929   // Ok, someone called show on a window that isn't sized to a sane
930   // value.  Mark this window as needing to have Show() called on it
931   // and return.
932   if ((aState && !AreBoundsSane()) || !mCreated) {
933     LOG("\tbounds are insane or window hasn't been created yet\n");
934     mNeedsShow = true;
935     return;
936   }
937 
938   // If someone is hiding this widget, clear any needing show flag.
939   if (!aState) mNeedsShow = false;
940 
941 #ifdef ACCESSIBILITY
942   if (aState && a11y::ShouldA11yBeEnabled()) CreateRootAccessible();
943 #endif
944 
945   NativeShow(aState);
946 }
947 
ResizeInt(int aX,int aY,int aWidth,int aHeight,bool aMove,bool aRepaint)948 void nsWindow::ResizeInt(int aX, int aY, int aWidth, int aHeight, bool aMove,
949                          bool aRepaint) {
950   LOG("nsWindow::ResizeInt x:%d y:%d -> w:%d h:%d repaint %d aMove %d\n", aX,
951       aY, aWidth, aHeight, aRepaint, aMove);
952 
953   ConstrainSize(&aWidth, &aHeight);
954 
955   LOG("  ConstrainSize: w:%d h;%d\n", aWidth, aHeight);
956 
957   if (aMove) {
958     mBounds.x = aX;
959     mBounds.y = aY;
960   }
961 
962   // If we used to have insane bounds, we may have skipped actually positioning
963   // the widget in NativeMoveResizeWaylandPopup, in which case we need to
964   // actually position it now as well.
965   if (!aMove && !AreBoundsSane() && IsWaylandPopup()) {
966     aMove = true;
967   }
968   // We have updated position from layout, move.
969   if (mPreferredPopupRectFlushed) {
970     aMove = true;
971   }
972 
973   // For top-level windows, aWidth and aHeight should possibly be
974   // interpreted as frame bounds, but NativeResize treats these as window
975   // bounds (Bug 581866).
976   mBounds.SizeTo(aWidth, aHeight);
977 
978   // We set correct mBounds in advance here. This can be invalided by state
979   // event.
980   mBoundsAreValid = true;
981 
982   // Recalculate aspect ratio when resized from DOM
983   if (mAspectRatio != 0.0) {
984     LockAspectRatio(true);
985   }
986 
987   if (!mCreated) {
988     return;
989   }
990 
991   NativeMoveResize(aMove, true);
992   NotifyRollupGeometryChange();
993 
994   DispatchResized();
995 }
996 
Resize(double aWidth,double aHeight,bool aRepaint)997 void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
998   LOG("nsWindow::Resize %f %f\n", aWidth, aHeight);
999 
1000   double scale =
1001       BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
1002   int32_t width = NSToIntRound(scale * aWidth);
1003   int32_t height = NSToIntRound(scale * aHeight);
1004 
1005   ResizeInt(0, 0, width, height, /* aMove */ false, aRepaint);
1006 }
1007 
Resize(double aX,double aY,double aWidth,double aHeight,bool aRepaint)1008 void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
1009                       bool aRepaint) {
1010   LOG("nsWindow::Resize [%f,%f] -> [%f x %f] repaint %d\n", aX, aY, aWidth,
1011       aHeight, aRepaint);
1012 
1013   double scale =
1014       BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
1015   int32_t width = NSToIntRound(scale * aWidth);
1016   int32_t height = NSToIntRound(scale * aHeight);
1017 
1018   int32_t x = NSToIntRound(scale * aX);
1019   int32_t y = NSToIntRound(scale * aY);
1020 
1021   ResizeInt(x, y, width, height, /* aMove */ true, aRepaint);
1022 }
1023 
Enable(bool aState)1024 void nsWindow::Enable(bool aState) { mEnabled = aState; }
1025 
IsEnabled() const1026 bool nsWindow::IsEnabled() const { return mEnabled; }
1027 
Move(double aX,double aY)1028 void nsWindow::Move(double aX, double aY) {
1029   double scale =
1030       BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
1031   int32_t x = NSToIntRound(aX * scale);
1032   int32_t y = NSToIntRound(aY * scale);
1033 
1034   LOG("nsWindow::Move to %d %d\n", x, y);
1035 
1036   if (mWindowType == eWindowType_toplevel ||
1037       mWindowType == eWindowType_dialog) {
1038     SetSizeMode(nsSizeMode_Normal);
1039   }
1040 
1041   // Since a popup window's x/y coordinates are in relation to to
1042   // the parent, the parent might have moved so we always move a
1043   // popup window.
1044   LOG("  bounds %d %d\n", mBounds.y, mBounds.y);
1045   if (x == mBounds.x && y == mBounds.y && mWindowType != eWindowType_popup) {
1046     LOG("  position is the same, return\n");
1047     return;
1048   }
1049 
1050   // XXX Should we do some AreBoundsSane check here?
1051 
1052   mBounds.x = x;
1053   mBounds.y = y;
1054 
1055   if (!mCreated) {
1056     LOG("  is not created, return.\n");
1057     return;
1058   }
1059 
1060   if (IsWaylandPopup()) {
1061     auto prefBounds = mPreferredPopupRect;
1062     if (prefBounds.TopLeft() != mBounds.TopLeft()) {
1063       NativeMoveResize(/* move */ true, /* resize */ false);
1064       NotifyRollupGeometryChange();
1065     } else {
1066       LOG("  mBounds same as mPreferredPopupRect, no need to move");
1067     }
1068   } else {
1069     NativeMoveResize(/* move */ true, /* resize */ false);
1070     NotifyRollupGeometryChange();
1071   }
1072 }
1073 
IsPopup() const1074 bool nsWindow::IsPopup() const { return mWindowType == eWindowType_popup; }
1075 
IsWaylandPopup() const1076 bool nsWindow::IsWaylandPopup() const {
1077   return GdkIsWaylandDisplay() && IsPopup();
1078 }
1079 
GetMenuPopupFrame(nsIFrame * aFrame)1080 static nsMenuPopupFrame* GetMenuPopupFrame(nsIFrame* aFrame) {
1081   return do_QueryFrame(aFrame);
1082 }
1083 
AppendPopupToHierarchyList(nsWindow * aToplevelWindow)1084 void nsWindow::AppendPopupToHierarchyList(nsWindow* aToplevelWindow) {
1085   mWaylandToplevel = aToplevelWindow;
1086 
1087   nsWindow* popup = aToplevelWindow;
1088   while (popup && popup->mWaylandPopupNext) {
1089     popup = popup->mWaylandPopupNext;
1090   }
1091   popup->mWaylandPopupNext = this;
1092 
1093   mWaylandPopupPrev = popup;
1094   mWaylandPopupNext = nullptr;
1095   mPopupChanged = true;
1096   mPopupClosed = false;
1097 }
1098 
RemovePopupFromHierarchyList()1099 void nsWindow::RemovePopupFromHierarchyList() {
1100   // We're already removed from the popup hierarchy
1101   if (!IsInPopupHierarchy()) {
1102     return;
1103   }
1104   mWaylandPopupPrev->mWaylandPopupNext = mWaylandPopupNext;
1105   if (mWaylandPopupNext) {
1106     mWaylandPopupNext->mWaylandPopupPrev = mWaylandPopupPrev;
1107     mWaylandPopupNext->mPopupChanged = true;
1108   }
1109   mWaylandPopupNext = mWaylandPopupPrev = nullptr;
1110 }
1111 
HideWaylandWindow()1112 void nsWindow::HideWaylandWindow() {
1113   LOG("nsWindow::HideWaylandWindow: [%p]\n", this);
1114   PauseCompositorHiddenWindow();
1115   gtk_widget_hide(mShell);
1116 }
1117 
1118 // Gtk refuses to map popup window with x < 0 && y < 0 relative coordinates
1119 // see https://gitlab.gnome.org/GNOME/gtk/-/issues/4071
1120 // as a workaround just fool around and place the popup temporary to 0,0.
WaylandPopupRemoveNegativePosition(int * aX,int * aY)1121 bool nsWindow::WaylandPopupRemoveNegativePosition(int* aX, int* aY) {
1122   LOG("nsWindow::WaylandPopupRemoveNegativePosition() [%p]\n", this);
1123 
1124   int x, y;
1125   GdkWindow* window = gtk_widget_get_window(mShell);
1126   gdk_window_get_origin(window, &x, &y);
1127   if (x >= 0 || y >= 0) {
1128     LOG("  coordinates are correct");
1129     return false;
1130   }
1131 
1132   LOG("  wrong coord (%d, %d) move to 0,0", x, y);
1133   gdk_window_move(window, 0, 0);
1134 
1135   if (aX) {
1136     *aX = x;
1137   }
1138   if (aY) {
1139     *aY = y;
1140   }
1141 
1142   return true;
1143 }
1144 
ShowWaylandWindow()1145 void nsWindow::ShowWaylandWindow() {
1146   LOG("nsWindow::ShowWaylandWindow: [%p]\n", this);
1147   if (!IsWaylandPopup()) {
1148     LOG("  toplevel, show it now");
1149     gtk_widget_show(mShell);
1150     return;
1151   }
1152 
1153   if (!mPopupTrackInHierarchy) {
1154     LOG("  popup is not tracked in popup hierarchy, show it now");
1155     gtk_widget_show(mShell);
1156     return;
1157   }
1158 
1159   // Popup position was checked before gdk_window_move_to_rect() callback
1160   // so just show it.
1161   if (mPopupUseMoveToRect && mWaitingForMoveToRectCallback) {
1162     LOG("  active move-to-rect callback, show it as is");
1163     gtk_widget_show(mShell);
1164     return;
1165   }
1166 
1167   if (gtk_widget_is_visible(mShell)) {
1168     LOG("  is already visible, quit");
1169     return;
1170   }
1171 
1172   int x, y;
1173   bool moved = WaylandPopupRemoveNegativePosition(&x, &y);
1174   gtk_widget_show(mShell);
1175   if (moved) {
1176     LOG("  move back to (%d, %d) and show", x, y);
1177     gdk_window_move(gtk_widget_get_window(mShell), x, y);
1178   }
1179 }
1180 
WaylandPopupMarkAsClosed()1181 void nsWindow::WaylandPopupMarkAsClosed() {
1182   LOG("nsWindow::WaylandPopupMarkAsClosed: [%p]\n", this);
1183   mPopupClosed = true;
1184   // If we have any child popup window notify it about
1185   // parent switch.
1186   if (mWaylandPopupNext) {
1187     mWaylandPopupNext->mPopupChanged = true;
1188   }
1189 }
1190 
WaylandPopupFindLast(nsWindow * aPopup)1191 nsWindow* nsWindow::WaylandPopupFindLast(nsWindow* aPopup) {
1192   while (aPopup && aPopup->mWaylandPopupNext) {
1193     aPopup = aPopup->mWaylandPopupNext;
1194   }
1195   return aPopup;
1196 }
1197 
1198 // Hide and potentially removes popup from popup hierarchy.
HideWaylandPopupWindow(bool aTemporaryHide,bool aRemoveFromPopupList)1199 void nsWindow::HideWaylandPopupWindow(bool aTemporaryHide,
1200                                       bool aRemoveFromPopupList) {
1201   LOG("nsWindow::HideWaylandPopupWindow: remove from list %d\n",
1202       aRemoveFromPopupList);
1203   if (aRemoveFromPopupList) {
1204     RemovePopupFromHierarchyList();
1205   }
1206 
1207   if (!mPopupClosed) {
1208     mPopupClosed = !aTemporaryHide;
1209   }
1210 
1211   bool visible = gtk_widget_is_visible(mShell);
1212   LOG("  gtk_widget_is_visible() = %d\n", visible);
1213 
1214   // Restore only popups which are really visible
1215   mPopupTemporaryHidden = aTemporaryHide && visible;
1216 
1217   // Hide only visible popups or popups closed pernamently.
1218   if (visible) {
1219     HideWaylandWindow();
1220 
1221     // If there's pending Move-To-Rect callback and we hide the popup
1222     // the callback won't be called any more.
1223     mWaitingForMoveToRectCallback = false;
1224   }
1225 
1226   // Clear rendering transactions of closed window and disable rendering to it
1227   // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1717451#c27
1228   // for details).
1229   if (mPopupClosed) {
1230     RevokeTransactionIdAllocator();
1231   }
1232 }
1233 
HideWaylandToplevelWindow()1234 void nsWindow::HideWaylandToplevelWindow() {
1235   LOG("nsWindow::HideWaylandToplevelWindow: [%p]\n", this);
1236   if (mWaylandPopupNext) {
1237     nsWindow* popup = WaylandPopupFindLast(mWaylandPopupNext);
1238     while (popup->mWaylandToplevel != nullptr) {
1239       nsWindow* prev = popup->mWaylandPopupPrev;
1240       popup->HideWaylandPopupWindow(/* aTemporaryHide */ false,
1241                                     /* aRemoveFromPopupList */ true);
1242       popup = prev;
1243     }
1244   }
1245   HideWaylandWindow();
1246 }
1247 
WaylandPopupRemoveClosedPopups()1248 void nsWindow::WaylandPopupRemoveClosedPopups() {
1249   LOG("nsWindow::WaylandPopupRemoveClosedPopups: [%p]\n", this);
1250   nsWindow* popup = this;
1251   while (popup) {
1252     nsWindow* next = popup->mWaylandPopupNext;
1253     if (popup->mPopupClosed) {
1254       popup->HideWaylandPopupWindow(/* aTemporaryHide */ false,
1255                                     /* aRemoveFromPopupList */ true);
1256     }
1257     popup = next;
1258   }
1259 }
1260 
1261 // Hide all tooltips except the latest one.
WaylandPopupHideTooltips()1262 void nsWindow::WaylandPopupHideTooltips() {
1263   LOG("nsWindow::WaylandPopupHideTooltips");
1264   MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
1265 
1266   nsWindow* popup = mWaylandPopupNext;
1267   while (popup && popup->mWaylandPopupNext) {
1268     if (popup->mPopupType == ePopupTypeTooltip) {
1269       LOG("  hidding tooltip [%p]", popup);
1270       popup->WaylandPopupMarkAsClosed();
1271     }
1272     popup = popup->mWaylandPopupNext;
1273   }
1274 }
1275 
1276 // We can't show popups with remote content or overflow popups
1277 // on top of regular ones.
1278 // If there's any remote popup opened, close all parent popups of it.
CloseAllPopupsBeforeRemotePopup()1279 void nsWindow::CloseAllPopupsBeforeRemotePopup() {
1280   LOG("nsWindow::CloseAllPopupsBeforeRemotePopup");
1281   MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
1282 
1283   // Don't waste time when there's only one popup opened.
1284   if (!mWaylandPopupNext || mWaylandPopupNext->mWaylandPopupNext == nullptr) {
1285     return;
1286   }
1287 
1288   // Find the first opened remote content popup
1289   nsWindow* remotePopup = mWaylandPopupNext;
1290   while (remotePopup) {
1291     if (remotePopup->HasRemoteContent() ||
1292         remotePopup->IsWidgetOverflowWindow()) {
1293       LOG("  remote popup [%p]", remotePopup);
1294       break;
1295     }
1296     remotePopup = remotePopup->mWaylandPopupNext;
1297   }
1298 
1299   if (!remotePopup) {
1300     return;
1301   }
1302 
1303   // ...hide opened popups before the remote one.
1304   nsWindow* popup = mWaylandPopupNext;
1305   while (popup && popup != remotePopup) {
1306     LOG("  hidding popup [%p]", popup);
1307     popup->WaylandPopupMarkAsClosed();
1308     popup = popup->mWaylandPopupNext;
1309   }
1310 }
1311 
GetLayoutPopupWidgetChain(nsTArray<nsIWidget * > * aLayoutWidgetHierarchy)1312 static void GetLayoutPopupWidgetChain(
1313     nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
1314   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1315   pm->GetSubmenuWidgetChain(aLayoutWidgetHierarchy);
1316   aLayoutWidgetHierarchy->Reverse();
1317 }
1318 
1319 // Compare 'this' popup position in Wayland widget hierarchy
1320 // (mWaylandPopupPrev/mWaylandPopupNext) with
1321 // 'this' popup position in layout hierarchy.
1322 //
1323 // When aMustMatchParent is true we also request
1324 // 'this' parents match, i.e. 'this' has the same parent in
1325 // both layout and widget hierarchy.
IsPopupInLayoutPopupChain(nsTArray<nsIWidget * > * aLayoutWidgetHierarchy,bool aMustMatchParent)1326 bool nsWindow::IsPopupInLayoutPopupChain(
1327     nsTArray<nsIWidget*>* aLayoutWidgetHierarchy, bool aMustMatchParent) {
1328   int len = (int)aLayoutWidgetHierarchy->Length();
1329   for (int i = 0; i < len; i++) {
1330     if (this == (*aLayoutWidgetHierarchy)[i]) {
1331       if (!aMustMatchParent) {
1332         return true;
1333       }
1334 
1335       // Find correct parent popup for 'this' according to widget
1336       // hierarchy. That means we need to skip closed popups.
1337       nsWindow* parentPopup = nullptr;
1338       if (mWaylandPopupPrev != mWaylandToplevel) {
1339         parentPopup = mWaylandPopupPrev;
1340         while (parentPopup != mWaylandToplevel && parentPopup->mPopupClosed) {
1341           parentPopup = parentPopup->mWaylandPopupPrev;
1342         }
1343       }
1344 
1345       if (i == 0) {
1346         // We found 'this' popups as a first popup in layout hierarchy.
1347         // It matches layout hierarchy if it's first widget also in
1348         // wayland widget hierarchy (i.e. parent is null).
1349         return parentPopup == nullptr;
1350       }
1351 
1352       return parentPopup == (*aLayoutWidgetHierarchy)[i - 1];
1353     }
1354   }
1355   return false;
1356 }
1357 
1358 // Hide popups which are not in popup chain.
WaylandPopupHierarchyHideByLayout(nsTArray<nsIWidget * > * aLayoutWidgetHierarchy)1359 void nsWindow::WaylandPopupHierarchyHideByLayout(
1360     nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
1361   LOG("nsWindow::WaylandPopupHierarchyHideByLayout");
1362   MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
1363 
1364   // Hide all popups which are not in layout popup chain
1365   nsWindow* popup = mWaylandPopupNext;
1366   while (popup) {
1367     // Tooltips are not tracked in layout chain
1368     if (!popup->mPopupClosed && popup->mPopupType != ePopupTypeTooltip) {
1369       if (!popup->IsPopupInLayoutPopupChain(aLayoutWidgetHierarchy,
1370                                             /* aMustMatchParent */ false)) {
1371         LOG("  hidding popup [%p]", popup);
1372         popup->WaylandPopupMarkAsClosed();
1373       }
1374     }
1375     popup = popup->mWaylandPopupNext;
1376   }
1377 }
1378 
1379 // Mark popups outside of layout hierarchy
WaylandPopupHierarchyValidateByLayout(nsTArray<nsIWidget * > * aLayoutWidgetHierarchy)1380 void nsWindow::WaylandPopupHierarchyValidateByLayout(
1381     nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
1382   LOG("nsWindow::WaylandPopupHierarchyValidateByLayout");
1383   nsWindow* popup = mWaylandPopupNext;
1384   while (popup) {
1385     if (popup->mPopupType == ePopupTypeTooltip) {
1386       popup->mPopupMatchesLayout = true;
1387     } else if (!popup->mPopupClosed) {
1388       popup->mPopupMatchesLayout = popup->IsPopupInLayoutPopupChain(
1389           aLayoutWidgetHierarchy, /* aMustMatchParent */ true);
1390       LOG("  popup [%p] parent window [%p] matches layout %d\n", (void*)popup,
1391           (void*)popup->mWaylandPopupPrev, popup->mPopupMatchesLayout);
1392     }
1393     popup = popup->mWaylandPopupNext;
1394   }
1395 }
1396 
WaylandPopupHierarchyHideTemporary()1397 void nsWindow::WaylandPopupHierarchyHideTemporary() {
1398   LOG("nsWindow::WaylandPopupHierarchyHideTemporary() [%p]", this);
1399   nsWindow* popup = WaylandPopupFindLast(this);
1400   while (popup) {
1401     LOG("  temporary hidding popup [%p]", popup);
1402     nsWindow* prev = popup->mWaylandPopupPrev;
1403     popup->HideWaylandPopupWindow(/* aTemporaryHide */ true,
1404                                   /* aRemoveFromPopupList */ false);
1405     if (popup == this) {
1406       break;
1407     }
1408     popup = prev;
1409   }
1410 }
1411 
WaylandPopupHierarchyShowTemporaryHidden()1412 void nsWindow::WaylandPopupHierarchyShowTemporaryHidden() {
1413   LOG("nsWindow::WaylandPopupHierarchyShowTemporaryHidden()");
1414   nsWindow* popup = this;
1415   while (popup) {
1416     if (popup->mPopupTemporaryHidden) {
1417       popup->mPopupTemporaryHidden = false;
1418       LOG("  showing temporary hidden popup [%p]", popup);
1419       popup->ShowWaylandWindow();
1420     }
1421     popup = popup->mWaylandPopupNext;
1422   }
1423 }
1424 
WaylandPopupHierarchyCalculatePositions()1425 void nsWindow::WaylandPopupHierarchyCalculatePositions() {
1426   LOG("nsWindow::WaylandPopupHierarchyCalculatePositions()");
1427 
1428   // Set widget hierarchy in Gtk
1429   nsWindow* popup = mWaylandToplevel->mWaylandPopupNext;
1430   while (popup) {
1431     LOG("  popup [%p] set parent window [%p]", (void*)popup,
1432         (void*)popup->mWaylandPopupPrev);
1433     gtk_window_set_transient_for(GTK_WINDOW(popup->mShell),
1434                                  GTK_WINDOW(popup->mWaylandPopupPrev->mShell));
1435     popup = popup->mWaylandPopupNext;
1436   }
1437 
1438   popup = this;
1439   while (popup) {
1440     // Anchored window has mPopupPosition already calculated against
1441     // its parent, no need to recalculate.
1442     LOG("  popup [%p] bounds [%d, %d] -> [%d x %d]", popup,
1443         (int)(popup->mBounds.x / FractionalScaleFactor()),
1444         (int)(popup->mBounds.y / FractionalScaleFactor()),
1445         (int)(popup->mBounds.width / FractionalScaleFactor()),
1446         (int)(popup->mBounds.height / FractionalScaleFactor()));
1447 #ifdef MOZ_LOGGING
1448     if (LOG_ENABLED()) {
1449       if (nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame())) {
1450         auto r = LayoutDeviceRect::FromAppUnitsRounded(
1451             popupFrame->GetRect(),
1452             popupFrame->PresContext()->AppUnitsPerDevPixel());
1453         LOG("  popup [%p] layout [%d, %d] -> [%d x %d]", popup, r.x, r.y,
1454             r.width, r.height);
1455       }
1456     }
1457 #endif
1458     if (popup->mPopupContextMenu && !popup->mPopupAnchored) {
1459       LOG("  popup [%p] is first context menu", popup);
1460       popup->mRelativePopupPosition = popup->mPopupPosition;
1461     } else if (popup->mPopupAnchored) {
1462       LOG("  popup [%p] is anchored", popup);
1463       if (!popup->mPopupMatchesLayout) {
1464         NS_WARNING("Anchored popup does not match layout!");
1465       }
1466       popup->mRelativePopupPosition = popup->mPopupPosition;
1467     } else if (popup->mWaylandPopupPrev->mWaylandToplevel == nullptr) {
1468       LOG("  popup [%p] has toplevel as parent", popup);
1469       popup->mRelativePopupPosition = popup->mPopupPosition;
1470     } else {
1471       GdkPoint parent = WaylandGetParentPosition();
1472 
1473       LOG("  popup [%p] uses transformed coordinates\n", popup);
1474       LOG("    parent position [%d, %d]\n", parent.x, parent.y);
1475       LOG("    popup position [%d, %d]\n", popup->mPopupPosition.x,
1476           popup->mPopupPosition.y);
1477 
1478       popup->mRelativePopupPosition.x = popup->mPopupPosition.x - parent.x;
1479       popup->mRelativePopupPosition.y = popup->mPopupPosition.y - parent.y;
1480     }
1481     LOG("  popup [%p] transformed popup coordinates from [%d, %d] to [%d, %d]",
1482         popup, popup->mPopupPosition.x, popup->mPopupPosition.y,
1483         popup->mRelativePopupPosition.x, popup->mRelativePopupPosition.y);
1484     popup = popup->mWaylandPopupNext;
1485   }
1486 }
1487 
1488 // The MenuList popups are used as dropdown menus for example in WebRTC
1489 // microphone/camera chooser or autocomplete widgets.
WaylandPopupIsMenu()1490 bool nsWindow::WaylandPopupIsMenu() {
1491   nsMenuPopupFrame* menuPopupFrame = GetMenuPopupFrame(GetFrame());
1492   if (menuPopupFrame) {
1493     return mPopupType == ePopupTypeMenu && !menuPopupFrame->IsMenuList();
1494   }
1495   return false;
1496 }
1497 
WaylandPopupIsContextMenu()1498 bool nsWindow::WaylandPopupIsContextMenu() {
1499   nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
1500   if (!popupFrame) {
1501     return false;
1502   }
1503   return popupFrame->IsContextMenu();
1504 }
1505 
WaylandPopupIsPermanent()1506 bool nsWindow::WaylandPopupIsPermanent() {
1507   nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
1508   if (!popupFrame) {
1509     // We can always hide popups without frames.
1510     return false;
1511   }
1512   return popupFrame->IsNoAutoHide();
1513 }
1514 
WaylandPopupIsAnchored()1515 bool nsWindow::WaylandPopupIsAnchored() {
1516   nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
1517   if (!popupFrame) {
1518     // We can always hide popups without frames.
1519     return false;
1520   }
1521   return popupFrame->GetAnchor() != nullptr;
1522 }
1523 
IsWidgetOverflowWindow()1524 bool nsWindow::IsWidgetOverflowWindow() {
1525   if (this->GetFrame() && this->GetFrame()->GetContent()->GetID()) {
1526     nsCString nodeId;
1527     this->GetFrame()->GetContent()->GetID()->ToUTF8String(nodeId);
1528     return nodeId.Equals("widget-overflow");
1529   }
1530   return false;
1531 }
1532 
WaylandGetParentPosition()1533 GdkPoint nsWindow::WaylandGetParentPosition() {
1534   // Don't call WaylandGetParentPosition on X11 as it causes X11 roundtrips.
1535   // gdk_window_get_origin is very fast on Wayland as the
1536   // window position is cached by Gtk.
1537   MOZ_DIAGNOSTIC_ASSERT(GdkIsWaylandDisplay());
1538 
1539   GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
1540   if (!parentGtkWindow || !GTK_IS_WIDGET(parentGtkWindow)) {
1541     NS_WARNING("Popup has no parent!");
1542     return {0, 0};
1543   }
1544   GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(parentGtkWindow));
1545   if (!window) {
1546     NS_WARNING("Popup parrent is not mapped!");
1547     return {0, 0};
1548   }
1549   gint x = 0, y = 0;
1550   gdk_window_get_origin(window, &x, &y);
1551   return {x, y};
1552 }
1553 
1554 #ifdef MOZ_LOGGING
LogPopupHierarchy()1555 void nsWindow::LogPopupHierarchy() {
1556   if (!LOG_ENABLED()) {
1557     return;
1558   }
1559 
1560   LOG("Widget Popup Hierarchy:\n");
1561   if (!mWaylandToplevel->mWaylandPopupNext) {
1562     LOG("    Empty\n");
1563   } else {
1564     int indent = 4;
1565     nsWindow* popup = mWaylandToplevel->mWaylandPopupNext;
1566     while (popup) {
1567       nsPrintfCString indentString("%*s", indent, " ");
1568       LOG("%s %s %s nsWindow [%p] Menu %d Permanent %d ContextMenu %d "
1569           "Anchored %d Visible %d\n",
1570           indentString.get(), popup->GetFrameTag().get(),
1571           popup->GetPopupTypeName().get(), popup, popup->WaylandPopupIsMenu(),
1572           popup->WaylandPopupIsPermanent(), popup->mPopupContextMenu,
1573           popup->mPopupAnchored, gtk_widget_is_visible(popup->mShell));
1574       indent += 4;
1575       popup = popup->mWaylandPopupNext;
1576     }
1577   }
1578 
1579   LOG("Layout Popup Hierarchy:\n");
1580   AutoTArray<nsIWidget*, 5> widgetChain;
1581   GetLayoutPopupWidgetChain(&widgetChain);
1582   if (widgetChain.Length() == 0) {
1583     LOG("    Empty\n");
1584   } else {
1585     for (unsigned long i = 0; i < widgetChain.Length(); i++) {
1586       nsWindow* window = static_cast<nsWindow*>(widgetChain[i]);
1587       nsPrintfCString indentString("%*s", (int)(i + 1) * 4, " ");
1588       if (window) {
1589         LOG("%s %s %s nsWindow [%p] Menu %d Permanent %d ContextMenu %d "
1590             "Anchored %d Visible %d\n",
1591             indentString.get(), window->GetFrameTag().get(),
1592             window->GetPopupTypeName().get(), window,
1593             window->WaylandPopupIsMenu(), window->WaylandPopupIsPermanent(),
1594             window->mPopupContextMenu, window->mPopupAnchored,
1595             gtk_widget_is_visible(window->mShell));
1596       } else {
1597         LOG("%s null window\n", indentString.get());
1598       }
1599     }
1600   }
1601 }
1602 #endif
1603 
WaylandPopupGetTopmostWindow()1604 nsWindow* nsWindow::WaylandPopupGetTopmostWindow() {
1605   nsView* view = nsView::GetViewFor(this);
1606   if (view) {
1607     nsView* parentView = view->GetParent();
1608     if (parentView) {
1609       nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr);
1610       if (parentWidget) {
1611         nsWindow* parentnsWindow = static_cast<nsWindow*>(parentWidget);
1612         LOG("  Topmost window: %p [nsWindow]\n", parentnsWindow);
1613         return parentnsWindow;
1614       }
1615     }
1616   }
1617   return nullptr;
1618 }
1619 
WaylandPopupNeedsTrackInHierarchy()1620 bool nsWindow::WaylandPopupNeedsTrackInHierarchy() {
1621   if (mIsDragPopup) {
1622     return false;
1623   }
1624 
1625   // Don't track popups without frame
1626   nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
1627   if (!popupFrame) {
1628     return false;
1629   }
1630 
1631   // Popup state can be changed, see Bug 1728952.
1632   bool permanentStateMatches =
1633       mPopupTrackInHierarchy == !WaylandPopupIsPermanent();
1634 
1635   // Popup permanent state (noautohide attribute) can change during popup life.
1636   if (mPopupTrackInHierarchyConfigured && permanentStateMatches) {
1637     return mPopupTrackInHierarchy;
1638   }
1639 
1640   mPopupAnchored = WaylandPopupIsAnchored();
1641   mPopupContextMenu = WaylandPopupIsContextMenu();
1642 
1643   LOG("nsWindow::WaylandPopupNeedsTrackInHierarchy tracked %d anchored %d\n",
1644       mPopupTrackInHierarchy, mPopupAnchored);
1645 
1646   // Permanent state changed and popup is mapped.
1647   // We need to switch popup type but that's done when popup is mapped
1648   // by Gtk so we need to unmap the popup here.
1649   // It will be mapped again by gtk_widget_show().
1650   if (!permanentStateMatches && mIsMapped) {
1651     LOG("  permanent state change from %d to %d, unmapping",
1652         mPopupTrackInHierarchy, !WaylandPopupIsPermanent());
1653     gtk_widget_unmap(mShell);
1654   }
1655 
1656   mPopupTrackInHierarchy = !WaylandPopupIsPermanent();
1657   LOG("  tracked in hierarchy %d\n", mPopupTrackInHierarchy);
1658 
1659   // See gdkwindow-wayland.c and
1660   // should_map_as_popup()/should_map_as_subsurface()
1661   GdkWindowTypeHint gtkTypeHint;
1662   switch (mPopupHint) {
1663     case ePopupTypeMenu:
1664       // GDK_WINDOW_TYPE_HINT_POPUP_MENU is mapped as xdg_popup by default.
1665       // We use this type for all menu popups.
1666       gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
1667       LOG("  popup type Menu");
1668       break;
1669     case ePopupTypeTooltip:
1670       gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
1671       LOG("  popup type Tooltip");
1672       break;
1673     default:  // popup panel type
1674       // GDK_WINDOW_TYPE_HINT_UTILITY is mapped as wl_subsurface
1675       // by default. It's used for panels attached to toplevel
1676       // window.
1677       gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
1678       LOG("  popup type Utility");
1679       break;
1680   }
1681 
1682   if (!mPopupTrackInHierarchy) {
1683     gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
1684   }
1685   gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
1686 
1687   mPopupTrackInHierarchyConfigured = true;
1688   return mPopupTrackInHierarchy;
1689 }
1690 
IsInPopupHierarchy()1691 bool nsWindow::IsInPopupHierarchy() {
1692   return mPopupTrackInHierarchy && mWaylandToplevel && mWaylandPopupPrev;
1693 }
1694 
AddWindowToPopupHierarchy()1695 void nsWindow::AddWindowToPopupHierarchy() {
1696   LOG("nsWindow::AddWindowToPopupHierarchy\n");
1697   if (!GetFrame()) {
1698     LOG("  Window without frame cannot be added as popup!\n");
1699     return;
1700   }
1701 
1702   // Check if we're already in the hierarchy
1703   if (!IsInPopupHierarchy()) {
1704     mWaylandToplevel = WaylandPopupGetTopmostWindow();
1705     AppendPopupToHierarchyList(mWaylandToplevel);
1706   }
1707 }
1708 
1709 // Wayland keeps strong popup window hierarchy. We need to track active
1710 // (visible) popup windows and make sure we hide popup on the same level
1711 // before we open another one on that level. It means that every open
1712 // popup needs to have an unique parent.
UpdateWaylandPopupHierarchy()1713 void nsWindow::UpdateWaylandPopupHierarchy() {
1714   LOG("nsWindow::UpdateWaylandPopupHierarchy\n");
1715 
1716   // This popup hasn't been added to popup hierarchy yet so no need to
1717   // do any configurations.
1718   if (!IsInPopupHierarchy()) {
1719     LOG("  popup isn't in hierarchy\n");
1720     return;
1721   }
1722 
1723 #ifdef MOZ_LOGGING
1724   LogPopupHierarchy();
1725   auto printPopupHierarchy = MakeScopeExit([&] { LogPopupHierarchy(); });
1726 #endif
1727 
1728   // Hide all tooltips without the last one. Tooltip can't be popup parent.
1729   mWaylandToplevel->WaylandPopupHideTooltips();
1730 
1731   // Check if we have any remote content / overflow window in hierarchy.
1732   // We can't attach such widget on top of other popup.
1733   mWaylandToplevel->CloseAllPopupsBeforeRemotePopup();
1734 
1735   // Check if your popup hierarchy matches layout hierarchy.
1736   // For instance we should not connect hamburger menu on top
1737   // of context menu.
1738   // Close all popups from different layout chains if possible.
1739   AutoTArray<nsIWidget*, 5> layoutPopupWidgetChain;
1740   GetLayoutPopupWidgetChain(&layoutPopupWidgetChain);
1741 
1742   mWaylandToplevel->WaylandPopupHierarchyHideByLayout(&layoutPopupWidgetChain);
1743   mWaylandToplevel->WaylandPopupHierarchyValidateByLayout(
1744       &layoutPopupWidgetChain);
1745 
1746   // Now we have Popup hierarchy complete.
1747   // Find first unchanged (and still open) popup to start with hierarchy
1748   // changes.
1749   nsWindow* changedPopup = mWaylandToplevel->mWaylandPopupNext;
1750   while (changedPopup) {
1751     // Stop when parent of this popup was changed and we need to recalc
1752     // popup position.
1753     if (changedPopup->mPopupChanged) {
1754       break;
1755     }
1756     // Stop when this popup is closed.
1757     if (changedPopup->mPopupClosed) {
1758       break;
1759     }
1760     changedPopup = changedPopup->mWaylandPopupNext;
1761   }
1762 
1763   // We don't need to recompute popup positions, quit now.
1764   if (!changedPopup) {
1765     LOG("  changed Popup is null, quit.\n");
1766     return;
1767   }
1768 
1769   LOG("  first changed popup [%p]\n", (void*)changedPopup);
1770 
1771   // Hide parent popups if necessary (there are layout discontinuity)
1772   // reposition the popup and show them again.
1773   changedPopup->WaylandPopupHierarchyHideTemporary();
1774 
1775   nsWindow* parentOfchangedPopup = nullptr;
1776   if (changedPopup->mPopupClosed) {
1777     parentOfchangedPopup = changedPopup->mWaylandPopupPrev;
1778   }
1779   changedPopup->WaylandPopupRemoveClosedPopups();
1780 
1781   // It's possible that changedPopup was removed from widget hierarchy,
1782   // in such case use child popup of the removed one if there's any.
1783   if (!changedPopup->IsInPopupHierarchy()) {
1784     if (!parentOfchangedPopup || !parentOfchangedPopup->mWaylandPopupNext) {
1785       LOG("  last popup was removed, quit.\n");
1786       return;
1787     }
1788     changedPopup = parentOfchangedPopup->mWaylandPopupNext;
1789   }
1790 
1791   GetLayoutPopupWidgetChain(&layoutPopupWidgetChain);
1792   mWaylandToplevel->WaylandPopupHierarchyValidateByLayout(
1793       &layoutPopupWidgetChain);
1794 
1795   changedPopup->WaylandPopupHierarchyCalculatePositions();
1796 
1797   nsWindow* popup = changedPopup;
1798   while (popup) {
1799     // We can use move_to_rect only when popups in popup hierarchy matches
1800     // layout hierarchy as move_to_rect request that parent/child
1801     // popups are adjacent.
1802     bool useMoveToRect = gUseMoveToRect && popup->mPopupMatchesLayout;
1803     if (useMoveToRect) {
1804       // We use move_to_rect when:
1805       // - Popup is anchored, i.e. it has an anchor defined by layout
1806       //   (mPopupAnchored).
1807       // - Popup isn't anchored but it has toplevel as parent, i.e.
1808       //   it's first popup.
1809       useMoveToRect = (mPopupType == ePopupTypeTooltip) ||
1810                       (popup->mPopupAnchored ||
1811                        (!popup->mPopupAnchored &&
1812                         popup->mWaylandPopupPrev->mWaylandToplevel == nullptr));
1813     }
1814     LOG("  popup [%p] matches layout [%d] anchored [%d] first popup [%d] use "
1815         "move-to-rect %d\n",
1816         popup, popup->mPopupMatchesLayout, popup->mPopupAnchored,
1817         popup->mWaylandPopupPrev->mWaylandToplevel == nullptr, useMoveToRect);
1818 
1819     popup->mPopupUseMoveToRect = useMoveToRect && !mUpdatedByMoveToRectCallback;
1820     popup->WaylandPopupMove();
1821     popup->mPopupChanged = false;
1822     popup = popup->mWaylandPopupNext;
1823   }
1824 
1825   changedPopup->WaylandPopupHierarchyShowTemporaryHidden();
1826 }
1827 
NativeMoveResizeCallback(GdkWindow * window,const GdkRectangle * flipped_rect,const GdkRectangle * final_rect,gboolean flipped_x,gboolean flipped_y,void * aWindow)1828 static void NativeMoveResizeCallback(GdkWindow* window,
1829                                      const GdkRectangle* flipped_rect,
1830                                      const GdkRectangle* final_rect,
1831                                      gboolean flipped_x, gboolean flipped_y,
1832                                      void* aWindow) {
1833   LOG_POPUP("[%p] NativeMoveResizeCallback flipped_x %d flipped_y %d\n",
1834             aWindow, flipped_x, flipped_y);
1835   LOG_POPUP("[%p]   new position [%d, %d] -> [%d x %d]", aWindow, final_rect->x,
1836             final_rect->y, final_rect->width, final_rect->height);
1837   nsWindow* wnd = get_window_for_gdk_window(window);
1838 
1839   wnd->NativeMoveResizeWaylandPopupCallback(final_rect, flipped_x, flipped_y);
1840 }
1841 
NativeMoveResizeWaylandPopupCallback(const GdkRectangle * aFinalSize,bool aFlippedX,bool aFlippedY)1842 void nsWindow::NativeMoveResizeWaylandPopupCallback(
1843     const GdkRectangle* aFinalSize, bool aFlippedX, bool aFlippedY) {
1844   mWaitingForMoveToRectCallback = false;
1845 
1846   bool moved = mNewBoundsAfterMoveToRect.x || mNewBoundsAfterMoveToRect.y;
1847   bool resized =
1848       mNewBoundsAfterMoveToRect.width || mNewBoundsAfterMoveToRect.height;
1849 
1850   if (moved || resized) {
1851     LOG("  Another move/resize called during waiting for callback\n");
1852 
1853     // Set the preferred size to zero to avoid wrong size of popup because the
1854     // mPreferredPopupRect is used in nsMenuPopupFrame to set dimensions
1855     mPreferredPopupRect = LayoutDeviceIntRect();
1856     if (moved) {
1857       mBounds.x = mNewBoundsAfterMoveToRect.x;
1858       mBounds.y = mNewBoundsAfterMoveToRect.y;
1859     }
1860     if (resized) {
1861       mBounds.width = mNewBoundsAfterMoveToRect.width;
1862       mBounds.height = mNewBoundsAfterMoveToRect.height;
1863     }
1864     mNewBoundsAfterMoveToRect = LayoutDeviceIntRect(0, 0, 0, 0);
1865     NativeMoveResize(moved, resized);
1866     return;
1867   }
1868 
1869   LOG("  orig mBounds [%d, %d] -> [%d x %d]\n", mBounds.x, mBounds.y,
1870       mBounds.width, mBounds.height);
1871 
1872   LayoutDeviceIntRect newBounds = [&] {
1873     GdkRectangle finalRect = *aFinalSize;
1874     GdkPoint parent = WaylandGetParentPosition();
1875     finalRect.x += parent.x;
1876     finalRect.y += parent.y;
1877     return GdkRectToDevicePixels(finalRect);
1878   }();
1879 
1880   LOG("  new mBounds [%d, %d] -> [%d x %d]", newBounds.x, newBounds.y,
1881       newBounds.width, newBounds.height);
1882 
1883   const bool needsPositionUpdate = newBounds.TopLeft() != mBounds.TopLeft();
1884   const bool needsSizeUpdate = newBounds.Size() != mBounds.Size();
1885 
1886   // Update view
1887   if (needsSizeUpdate) {
1888     LOG("  needSizeUpdate\n");
1889     mPreferredPopupRect = newBounds;
1890     mPreferredPopupRectFlushed = false;
1891 
1892     Resize(aFinalSize->width, aFinalSize->height, true);
1893 
1894     if (nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame())) {
1895       RefPtr<PresShell> presShell = popupFrame->PresShell();
1896       presShell->FrameNeedsReflow(popupFrame, IntrinsicDirty::Resize,
1897                                   NS_FRAME_IS_DIRTY);
1898       // Force to trigger popup crop to fit the screen
1899       popupFrame->SetPopupPosition(nullptr, true, false);
1900     }
1901   }
1902   if (needsPositionUpdate) {
1903     LOG("  needPositionUpdate, new bounds [%d, %d]", newBounds.x, newBounds.y);
1904     mBounds.x = newBounds.x;
1905     mBounds.y = newBounds.y;
1906     NotifyWindowMoved(mBounds.x, mBounds.y);
1907   }
1908   mUpdatedByMoveToRectCallback = true;
1909 }
1910 
PopupAlignmentToGdkGravity(int8_t aAlignment)1911 static GdkGravity PopupAlignmentToGdkGravity(int8_t aAlignment) {
1912   switch (aAlignment) {
1913     case POPUPALIGNMENT_NONE:
1914       return GDK_GRAVITY_NORTH_WEST;
1915     case POPUPALIGNMENT_TOPLEFT:
1916       return GDK_GRAVITY_NORTH_WEST;
1917     case POPUPALIGNMENT_TOPRIGHT:
1918       return GDK_GRAVITY_NORTH_EAST;
1919     case POPUPALIGNMENT_BOTTOMLEFT:
1920       return GDK_GRAVITY_SOUTH_WEST;
1921     case POPUPALIGNMENT_BOTTOMRIGHT:
1922       return GDK_GRAVITY_SOUTH_EAST;
1923     case POPUPALIGNMENT_LEFTCENTER:
1924       return GDK_GRAVITY_WEST;
1925     case POPUPALIGNMENT_RIGHTCENTER:
1926       return GDK_GRAVITY_EAST;
1927     case POPUPALIGNMENT_TOPCENTER:
1928       return GDK_GRAVITY_NORTH;
1929     case POPUPALIGNMENT_BOTTOMCENTER:
1930       return GDK_GRAVITY_SOUTH;
1931   }
1932   return GDK_GRAVITY_STATIC;
1933 }
1934 
IsPopupDirectionRTL()1935 bool nsWindow::IsPopupDirectionRTL() {
1936   nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
1937   return popupFrame && popupFrame->IsDirectionRTL();
1938 }
1939 
1940 // Position the popup directly by gtk_window_move() and try to keep it
1941 // on screen by just moving it in scope of it's parent window.
1942 //
1943 // It's used when we position noautihode popup and we don't use xdg_positioner.
1944 // See Bug 1718867
WaylandPopupSetDirectPosition()1945 void nsWindow::WaylandPopupSetDirectPosition() {
1946   GdkRectangle rect = DevicePixelsToGdkRectRoundOut(mBounds);
1947 
1948   LOG("nsWindow::WaylandPopupSetDirectPosition %d,%d -> %d x %d\n", rect.x,
1949       rect.y, rect.width, rect.height);
1950 
1951   mPopupPosition = {rect.x, rect.y};
1952 
1953   if (mIsDragPopup) {
1954     gtk_window_move(GTK_WINDOW(mShell), rect.x, rect.y);
1955     gtk_window_resize(GTK_WINDOW(mShell), rect.width, rect.height);
1956     // DND window is placed inside container so we need to make hard size
1957     // request to ensure parent container is resized too.
1958     gtk_widget_set_size_request(GTK_WIDGET(mShell), rect.width, rect.height);
1959     return;
1960   }
1961 
1962   GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
1963   nsWindow* window = get_window_for_gtk_widget(GTK_WIDGET(parentGtkWindow));
1964   GdkWindow* gdkWindow =
1965       gtk_widget_get_window(GTK_WIDGET(window->GetMozContainer()));
1966 
1967   int parentWidth = gdk_window_get_width(gdkWindow);
1968   int popupWidth = rect.width;
1969 
1970   int x;
1971   gdk_window_get_position(gdkWindow, &x, nullptr);
1972 
1973   // If popup is bigger than main window just center it.
1974   if (popupWidth > parentWidth) {
1975     mPopupPosition.x = -(parentWidth - popupWidth) / 2 + x;
1976   } else {
1977     if (IsPopupDirectionRTL()) {
1978       // Stick with right window edge
1979       if (mPopupPosition.x < x) {
1980         mPopupPosition.x = x;
1981       }
1982     } else {
1983       // Stick with left window edge
1984       if (mPopupPosition.x + popupWidth > parentWidth + x) {
1985         mPopupPosition.x = parentWidth + x - popupWidth;
1986       }
1987     }
1988   }
1989 
1990   LOG("  set position [%d, %d]\n", mPopupPosition.x, mPopupPosition.y);
1991   gtk_window_move(GTK_WINDOW(mShell), mPopupPosition.x, mPopupPosition.y);
1992 
1993   LOG("  set size [%d, %d]\n", rect.width, rect.height);
1994   gtk_window_resize(GTK_WINDOW(mShell), rect.width, rect.height);
1995 
1996   if (mPopupPosition.x != rect.x) {
1997     mBounds = LayoutDeviceIntRect(GdkPointToDevicePixels(mPopupPosition),
1998                                   mBounds.Size());
1999     LOG("  setting new bounds [%d, %d]\n", mBounds.x, mBounds.y);
2000     NotifyWindowMoved(mBounds.x, mBounds.y);
2001     if (nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame())) {
2002       auto p = CSSIntPoint::Round(
2003           mBounds.TopLeft() / popupFrame->PresContext()->CSSToDevPixelScale());
2004       popupFrame->MoveTo(p, true);
2005     }
2006   }
2007 }
2008 
WaylandPopupFitsParentWindow(const GdkRectangle & aSize)2009 bool nsWindow::WaylandPopupFitsParentWindow(const GdkRectangle& aSize) {
2010   GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
2011   nsWindow* parentWindow =
2012       get_window_for_gtk_widget(GTK_WIDGET(parentGtkWindow));
2013 
2014   // Don't try to fiddle with popup position when our parent is also popup.
2015   // Use layout setup in such case.
2016   if (parentWindow->IsPopup()) {
2017     return false;
2018   }
2019 
2020   // Use MozContainer to get visible area without decorations.
2021   GdkWindow* parentGdkWindow =
2022       gtk_widget_get_window(GTK_WIDGET(parentWindow->GetMozContainer()));
2023 
2024   // x,y are offsets of mozcontainer, i.e. size of CSD decorations size.
2025   int x, y;
2026   gdk_window_get_position(parentGdkWindow, &x, &y);
2027 
2028   // We use parent mozcontainer size plus left/top CSD decorations sizes as
2029   // this coordinates are used by mBounds.
2030   int parentWidth = gdk_window_get_width(parentGdkWindow) + x;
2031   int parentHeight = gdk_window_get_width(parentGdkWindow) + y;
2032   int popupWidth = aSize.width;
2033   int popupHeight = aSize.height;
2034 
2035   auto popupBounds = DevicePixelsToGdkRectRoundOut(mBounds);
2036 
2037   return popupBounds.x + popupWidth <= parentWidth &&
2038          popupBounds.y + popupHeight <= parentHeight;
2039 }
2040 
NativeMoveResizeWaylandPopup(bool aMove,bool aResize)2041 void nsWindow::NativeMoveResizeWaylandPopup(bool aMove, bool aResize) {
2042   GdkRectangle rect = DevicePixelsToGdkRectRoundOut(mBounds);
2043 
2044   LOG("nsWindow::NativeMoveResizeWaylandPopup %d,%d -> %d x %d\n", rect.x,
2045       rect.y, rect.width, rect.height);
2046 
2047   // Compositor may be confused by windows with width/height = 0
2048   // and positioning such windows leads to Bug 1555866.
2049   if (!AreBoundsSane()) {
2050     LOG("  Bounds are not sane (width: %d height: %d)\n", mBounds.width,
2051         mBounds.height);
2052     return;
2053   }
2054 
2055   if (mWaitingForMoveToRectCallback) {
2056     LOG("  waiting for move to rect, schedulling");
2057     if (aMove) {
2058       mNewBoundsAfterMoveToRect.x = mBounds.x;
2059       mNewBoundsAfterMoveToRect.y = mBounds.y;
2060     }
2061     if (aResize) {
2062       mNewBoundsAfterMoveToRect.width = mBounds.width;
2063       mNewBoundsAfterMoveToRect.height = mBounds.height;
2064     }
2065     return;
2066   }
2067 
2068   mNewBoundsAfterMoveToRect = LayoutDeviceIntRect(0, 0, 0, 0);
2069 
2070   if (!WaylandPopupNeedsTrackInHierarchy()) {
2071     WaylandPopupSetDirectPosition();
2072     return;
2073   }
2074 
2075   if (aResize) {
2076     LOG("  set size [%d, %d]\n", rect.width, rect.height);
2077     gtk_window_resize(GTK_WINDOW(mShell), rect.width, rect.height);
2078   }
2079 
2080   auto clearUpdateFlag =
2081       MakeScopeExit([&] { mUpdatedByMoveToRectCallback = false; });
2082 
2083   if (!aMove && WaylandPopupFitsParentWindow(rect)) {
2084     // Popup position has not been changed and its position/size fits
2085     // parent window so no need to reposition the window.
2086     LOG("  fits parent window size, just resize\n");
2087     return;
2088   }
2089 
2090   // Mark popup as changed as we're updating position/size.
2091   mPopupChanged = true;
2092 
2093   // Save popup position for former re-calculations when popup hierarchy
2094   // is changed.
2095   LOG("  popup position changed from [%d, %d] to [%d, %d]\n", mPopupPosition.x,
2096       mPopupPosition.y, rect.x, rect.y);
2097   mPopupPosition = {rect.x, rect.y};
2098 
2099   UpdateWaylandPopupHierarchy();
2100 }
2101 
2102 struct PopupSides {
2103   Maybe<Side> mVertical;
2104   Maybe<Side> mHorizontal;
2105 };
2106 
SidesForPopupAlignment(int8_t aAlignment)2107 static PopupSides SidesForPopupAlignment(int8_t aAlignment) {
2108   switch (aAlignment) {
2109     case POPUPALIGNMENT_NONE:
2110       break;
2111     case POPUPALIGNMENT_TOPLEFT:
2112       return {Some(eSideTop), Some(eSideLeft)};
2113     case POPUPALIGNMENT_TOPRIGHT:
2114       return {Some(eSideTop), Some(eSideRight)};
2115     case POPUPALIGNMENT_BOTTOMLEFT:
2116       return {Some(eSideBottom), Some(eSideLeft)};
2117     case POPUPALIGNMENT_BOTTOMRIGHT:
2118       return {Some(eSideBottom), Some(eSideRight)};
2119     case POPUPALIGNMENT_LEFTCENTER:
2120       return {Nothing(), Some(eSideLeft)};
2121     case POPUPALIGNMENT_RIGHTCENTER:
2122       return {Nothing(), Some(eSideRight)};
2123     case POPUPALIGNMENT_TOPCENTER:
2124       return {Some(eSideTop), Nothing()};
2125     case POPUPALIGNMENT_BOTTOMCENTER:
2126       return {Some(eSideBottom), Nothing()};
2127   }
2128   return {};
2129 }
2130 
2131 // We want to apply margins based on popup alignment (which would generally be
2132 // just an offset to apply to the popup). However, to deal with flipping
2133 // correctly, we apply the margin to the anchor when possible.
2134 struct ResolvedPopupMargin {
2135   // A margin to be applied to the anchor.
2136   nsMargin mAnchorMargin;
2137   // An offset in app units to be applied to the popup for when we need to tell
2138   // GTK to center inside the anchor precisely (so we can't really do better in
2139   // presence of flips).
2140   nsPoint mPopupOffset;
2141 };
2142 
ResolveMargin(nsMenuPopupFrame * aFrame,int8_t aPopupAlign,int8_t aAnchorAlign,bool aAnchoredToPoint,bool aIsContextMenu)2143 static ResolvedPopupMargin ResolveMargin(nsMenuPopupFrame* aFrame,
2144                                          int8_t aPopupAlign,
2145                                          int8_t aAnchorAlign,
2146                                          bool aAnchoredToPoint,
2147                                          bool aIsContextMenu) {
2148   nsMargin margin = aFrame->GetMargin();
2149   nsPoint offset;
2150 
2151   if (aAnchoredToPoint) {
2152     // Since GTK doesn't allow us to specify margins itself, when anchored to a
2153     // point we can just assume we'll be aligned correctly... This is kind of
2154     // annoying but alas.
2155     //
2156     // This calculation must match the relevant unanchored popup calculation in
2157     // nsMenuPopupFrame::SetPopupPosition(), which should itself be the inverse
2158     // inverse of nsMenuPopupFrame::MoveTo().
2159     if (aIsContextMenu && aFrame->IsDirectionRTL()) {
2160       offset.x = -margin.right;
2161     } else {
2162       offset.x = margin.left;
2163     }
2164     offset.y = margin.top;
2165     return {nsMargin(), offset};
2166   }
2167 
2168   auto popupSides = SidesForPopupAlignment(aPopupAlign);
2169   auto anchorSides = SidesForPopupAlignment(aAnchorAlign);
2170   // Matched sides: Invert the margin, so that we pull in the right direction.
2171   // Popup not aligned to any anchor side: We give up and use the offset,
2172   // applying the margin from the popup side.
2173   // Mismatched sides: We swap the margins so that we pull in the right
2174   // direction, e.g. margin-left: -10px should shrink 10px the _right_ of the
2175   // box, not the left of the box.
2176   if (popupSides.mHorizontal == anchorSides.mHorizontal) {
2177     margin.left = -margin.left;
2178     margin.right = -margin.right;
2179   } else if (!anchorSides.mHorizontal) {
2180     auto popupSide = *popupSides.mHorizontal;
2181     offset.x += popupSide == eSideRight ? -margin.Side(popupSide)
2182                                         : margin.Side(popupSide);
2183     margin.left = margin.right = 0;
2184   } else {
2185     std::swap(margin.left, margin.right);
2186   }
2187 
2188   // Same logic as above, but in the vertical direction.
2189   if (popupSides.mVertical == anchorSides.mVertical) {
2190     margin.top = -margin.top;
2191     margin.bottom = -margin.bottom;
2192   } else if (!anchorSides.mVertical) {
2193     auto popupSide = *popupSides.mVertical;
2194     offset.y += popupSide == eSideBottom ? -margin.Side(popupSide)
2195                                          : margin.Side(popupSide);
2196     margin.top = margin.bottom = 0;
2197   } else {
2198     std::swap(margin.top, margin.bottom);
2199   }
2200 
2201   return {margin, offset};
2202 }
2203 
WaylandPopupMove()2204 void nsWindow::WaylandPopupMove() {
2205   LOG("nsWindow::WaylandPopupMove\n");
2206 
2207   // Available as of GTK 3.24+
2208   static auto sGdkWindowMoveToRect = (void (*)(
2209       GdkWindow*, const GdkRectangle*, GdkGravity, GdkGravity, GdkAnchorHints,
2210       gint, gint))dlsym(RTLD_DEFAULT, "gdk_window_move_to_rect");
2211 
2212   GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(mShell));
2213   nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
2214 
2215   LOG("  original widget popup position [%d, %d]\n", mPopupPosition.x,
2216       mPopupPosition.y);
2217   LOG("  relative widget popup position [%d, %d]\n", mRelativePopupPosition.x,
2218       mRelativePopupPosition.y);
2219 
2220   if (mPopupUseMoveToRect) {
2221     mPopupUseMoveToRect = sGdkWindowMoveToRect && gdkWindow && popupFrame;
2222   }
2223 
2224   LOG(" popup use move to rect %d\n", mPopupUseMoveToRect);
2225 
2226   if (!mPopupUseMoveToRect) {
2227     if (mNeedsShow && mPopupType != ePopupTypeTooltip) {
2228       // Workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/4308
2229       // Tooltips are created as subsurfaces with relative position.
2230       LOG("  use gtk_window_move(%d, %d) for hidden widget\n", mPopupPosition.x,
2231           mPopupPosition.y);
2232       gtk_window_move(GTK_WINDOW(mShell), mPopupPosition.x, mPopupPosition.y);
2233     } else {
2234       LOG("  use gtk_window_move(%d, %d) for visible widget\n",
2235           mRelativePopupPosition.x, mRelativePopupPosition.y);
2236       gtk_window_move(GTK_WINDOW(mShell), mRelativePopupPosition.x,
2237                       mRelativePopupPosition.y);
2238     }
2239     return;
2240   }
2241 
2242   const bool isTopContextMenu = mPopupContextMenu && !mPopupAnchored;
2243   const bool isRTL = IsPopupDirectionRTL();
2244   int8_t popupAlign(popupFrame->GetPopupAlignment());
2245   int8_t anchorAlign(popupFrame->GetPopupAnchor());
2246   if (isTopContextMenu) {
2247     anchorAlign = POPUPALIGNMENT_BOTTOMRIGHT;
2248     popupAlign = POPUPALIGNMENT_TOPLEFT;
2249   }
2250   if (isRTL) {
2251     popupAlign = -popupAlign;
2252     anchorAlign = -anchorAlign;
2253   }
2254 
2255   // Although we have mPopupPosition / mRelativePopupPosition here
2256   // we can't use it. move-to-rect needs anchor rectangle to position a popup
2257   // but we have only a point from Resize().
2258   //
2259   // So we need to extract popup position from nsMenuPopupFrame() and duplicate
2260   // the layout work here.
2261   LayoutDeviceIntRect anchorRect;
2262   ResolvedPopupMargin popupMargin;
2263   {
2264     nsRect anchorRectAppUnits = popupFrame->GetUntransformedAnchorRect();
2265     // This is a somewhat hacky way of applying the popup margin. We don't know
2266     // if GTK will end up flipping the popup, in which case the offset we
2267     // compute is just wrong / applied to the wrong side.
2268     //
2269     // Instead, we tell it to anchor us at a smaller or bigger rect depending on
2270     // the margin, which achieves the same result if the popup is positioned
2271     // correctly, but doesn't misposition the popup when flipped across the
2272     // anchor.
2273     popupMargin = ResolveMargin(popupFrame, popupAlign, anchorAlign,
2274                                 anchorRectAppUnits.IsEmpty(), isTopContextMenu);
2275     LOG("  layout popup CSS anchor (%d, %d) %s, margin %s offset %s\n",
2276         popupAlign, anchorAlign, ToString(anchorRectAppUnits).c_str(),
2277         ToString(popupMargin.mAnchorMargin).c_str(),
2278         ToString(popupMargin.mPopupOffset).c_str());
2279     anchorRectAppUnits.Inflate(popupMargin.mAnchorMargin);
2280     LOG("    after margins %s\n", ToString(anchorRectAppUnits).c_str());
2281     nscoord auPerDev = popupFrame->PresContext()->AppUnitsPerDevPixel();
2282     if (anchorRect.width < 0) {
2283       auto w = -anchorRect.width;
2284       anchorRect.width += w + 1;
2285       anchorRect.x += w;
2286     }
2287     anchorRect = LayoutDeviceIntRect::FromAppUnitsToNearest(anchorRectAppUnits,
2288                                                             auPerDev);
2289     LOG("    final %s\n", ToString(anchorRect).c_str());
2290   }
2291 
2292   // Get gravity and flip type
2293   GdkGravity rectAnchor = PopupAlignmentToGdkGravity(anchorAlign);
2294   GdkGravity menuAnchor = PopupAlignmentToGdkGravity(popupAlign);
2295   FlipType flipType = popupFrame->GetFlipType();
2296   int8_t position = popupFrame->GetAlignmentPosition();
2297   if (mWaylandPopupPrev->mWaylandToplevel) {
2298     GdkPoint parent = WaylandGetParentPosition();
2299     LOG("  subtract parent position [%d, %d]\n", parent.x, parent.y);
2300     anchorRect.MoveBy(-GdkPointToDevicePixels(parent));
2301   }
2302 
2303   LOG("  final popup rect position [%d, %d] -> [%d x %d]\n", anchorRect.x,
2304       anchorRect.y, anchorRect.width, anchorRect.height);
2305 
2306   LOG("  parentRect gravity: %d anchor gravity: %d\n", rectAnchor, menuAnchor);
2307 
2308   // Gtk default is: GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE | GDK_ANCHOR_RESIZE.
2309   // We want to SLIDE_X menu on the dual monitor setup rather than resize it
2310   // on the other monitor.
2311   GdkAnchorHints hints =
2312       GdkAnchorHints(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_RESIZE);
2313 
2314   // slideHorizontal from nsMenuPopupFrame::SetPopupPosition
2315   if (position >= POPUPPOSITION_BEFORESTART &&
2316       position <= POPUPPOSITION_AFTEREND) {
2317     hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE_X);
2318   }
2319   // slideVertical from nsMenuPopupFrame::SetPopupPosition
2320   if (position >= POPUPPOSITION_STARTBEFORE &&
2321       position <= POPUPPOSITION_ENDAFTER) {
2322     hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE_Y);
2323   }
2324 
2325   if (rectAnchor == GDK_GRAVITY_CENTER && menuAnchor == GDK_GRAVITY_CENTER) {
2326     // only slide
2327     hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
2328   } else {
2329     switch (flipType) {
2330       case FlipType_Both:
2331         hints = GdkAnchorHints(hints | GDK_ANCHOR_FLIP);
2332         break;
2333       case FlipType_Slide:
2334         hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
2335         break;
2336       case FlipType_Default:
2337         hints = GdkAnchorHints(hints | GDK_ANCHOR_FLIP);
2338         break;
2339       default:
2340         break;
2341     }
2342   }
2343   if (!WaylandPopupIsMenu()) {
2344     // we don't want to slide menus to fit the screen rather resize them
2345     hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
2346   }
2347   if (!g_signal_handler_find(gdkWindow, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
2348                              FuncToGpointer(NativeMoveResizeCallback), this)) {
2349     g_signal_connect(gdkWindow, "moved-to-rect",
2350                      G_CALLBACK(NativeMoveResizeCallback), this);
2351   }
2352 
2353   GdkRectangle rect = DevicePixelsToGdkRectRoundOut(anchorRect);
2354   mWaitingForMoveToRectCallback = true;
2355 
2356   if (gtk_widget_is_visible(mShell)) {
2357     NS_WARNING(
2358         "Positioning visible popup under Wayland, position may be wrong!");
2359   }
2360 
2361   // Correct popup position now. It will be updated by gdk_window_move_to_rect()
2362   // anyway but we need to set it now to avoid a race condition here.
2363   WaylandPopupRemoveNegativePosition();
2364 
2365   LOG("  move-to-rect call");
2366 
2367   auto offset =
2368       DevicePixelsToGdkPointRoundDown(LayoutDevicePoint::FromAppUnitsToNearest(
2369           popupMargin.mPopupOffset,
2370           popupFrame->PresContext()->AppUnitsPerDevPixel()));
2371   sGdkWindowMoveToRect(gdkWindow, &rect, rectAnchor, menuAnchor, hints,
2372                        offset.x, offset.y);
2373 }
2374 
SetZIndex(int32_t aZIndex)2375 void nsWindow::SetZIndex(int32_t aZIndex) {
2376   nsIWidget* oldPrev = GetPrevSibling();
2377 
2378   nsBaseWidget::SetZIndex(aZIndex);
2379 
2380   if (GetPrevSibling() == oldPrev) {
2381     return;
2382   }
2383 
2384   NS_ASSERTION(!mContainer, "Expected Mozilla child widget");
2385 
2386   // We skip the nsWindows that don't have mGdkWindows.
2387   // These are probably in the process of being destroyed.
2388   if (!mGdkWindow) {
2389     return;
2390   }
2391 
2392   if (!GetNextSibling()) {
2393     // We're to be on top.
2394     if (mGdkWindow) {
2395       gdk_window_raise(mGdkWindow);
2396     }
2397   } else {
2398     // All the siblings before us need to be below our widget.
2399     for (nsWindow* w = this; w;
2400          w = static_cast<nsWindow*>(w->GetPrevSibling())) {
2401       if (w->mGdkWindow) {
2402         gdk_window_lower(w->mGdkWindow);
2403       }
2404     }
2405   }
2406 }
2407 
SetSizeMode(nsSizeMode aMode)2408 void nsWindow::SetSizeMode(nsSizeMode aMode) {
2409   LOG("nsWindow::SetSizeMode %d\n", aMode);
2410 
2411   // Save the requested state.
2412   nsBaseWidget::SetSizeMode(aMode);
2413 
2414   // return if there's no shell or our current state is the same as
2415   // the mode we were just set to.
2416   if (!mShell || mSizeState == mSizeMode) {
2417     LOG("    already set");
2418     return;
2419   }
2420 
2421   switch (aMode) {
2422     case nsSizeMode_Maximized:
2423       LOG("    set maximized");
2424       gtk_window_maximize(GTK_WINDOW(mShell));
2425       break;
2426     case nsSizeMode_Minimized:
2427       LOG("    set minimized");
2428       gtk_window_iconify(GTK_WINDOW(mShell));
2429       break;
2430     case nsSizeMode_Fullscreen:
2431       LOG("    set fullscreen");
2432       MakeFullScreen(true);
2433       break;
2434 
2435     default:
2436       LOG("    set normal");
2437       // nsSizeMode_Normal, really.
2438       if (mSizeState == nsSizeMode_Minimized) {
2439         gtk_window_deiconify(GTK_WINDOW(mShell));
2440       } else if (mSizeState == nsSizeMode_Maximized) {
2441         gtk_window_unmaximize(GTK_WINDOW(mShell));
2442       } else if (mSizeState == nsSizeMode_Fullscreen) {
2443         MakeFullScreen(false);
2444       }
2445       break;
2446   }
2447 
2448   // Request mBounds update from configure event as we may not get
2449   // OnSizeAllocate for size state changes (Bug 1489463).
2450   mBoundsAreValid = false;
2451 
2452   mSizeState = mSizeMode;
2453 }
2454 
GetWindowManagerName(GdkWindow * gdk_window,nsACString & wmName)2455 static bool GetWindowManagerName(GdkWindow* gdk_window, nsACString& wmName) {
2456   if (!GdkIsX11Display()) {
2457     return false;
2458   }
2459 
2460   Display* xdisplay = gdk_x11_get_default_xdisplay();
2461   GdkScreen* screen = gdk_window_get_screen(gdk_window);
2462   Window root_win = GDK_WINDOW_XID(gdk_screen_get_root_window(screen));
2463 
2464   int actual_format_return;
2465   Atom actual_type_return;
2466   unsigned long nitems_return;
2467   unsigned long bytes_after_return;
2468   unsigned char* prop_return = nullptr;
2469   auto releaseXProperty = MakeScopeExit([&] {
2470     if (prop_return) {
2471       XFree(prop_return);
2472     }
2473   });
2474 
2475   Atom property = XInternAtom(xdisplay, "_NET_SUPPORTING_WM_CHECK", true);
2476   Atom req_type = XInternAtom(xdisplay, "WINDOW", true);
2477   if (!property || !req_type) {
2478     return false;
2479   }
2480   int result =
2481       XGetWindowProperty(xdisplay, root_win, property,
2482                          0L,                  // offset
2483                          sizeof(Window) / 4,  // length
2484                          false,               // delete
2485                          req_type, &actual_type_return, &actual_format_return,
2486                          &nitems_return, &bytes_after_return, &prop_return);
2487 
2488   if (result != Success || bytes_after_return != 0 || nitems_return != 1) {
2489     return false;
2490   }
2491 
2492   Window wmWindow = reinterpret_cast<Window*>(prop_return)[0];
2493   if (!wmWindow) {
2494     return false;
2495   }
2496 
2497   XFree(prop_return);
2498   prop_return = nullptr;
2499 
2500   property = XInternAtom(xdisplay, "_NET_WM_NAME", true);
2501   req_type = XInternAtom(xdisplay, "UTF8_STRING", true);
2502   if (!property || !req_type) {
2503     return false;
2504   }
2505   {
2506     // Suppress fatal errors for a missing window.
2507     ScopedXErrorHandler handler;
2508     result =
2509         XGetWindowProperty(xdisplay, wmWindow, property,
2510                            0L,         // offset
2511                            INT32_MAX,  // length
2512                            false,      // delete
2513                            req_type, &actual_type_return, &actual_format_return,
2514                            &nitems_return, &bytes_after_return, &prop_return);
2515   }
2516 
2517   if (result != Success || bytes_after_return != 0) {
2518     return false;
2519   }
2520 
2521   wmName = reinterpret_cast<const char*>(prop_return);
2522   return true;
2523 }
2524 
2525 #define kDesktopMutterSchema "org.gnome.mutter"
2526 #define kDesktopDynamicWorkspacesKey "dynamic-workspaces"
2527 
WorkspaceManagementDisabled(GdkWindow * gdk_window)2528 static bool WorkspaceManagementDisabled(GdkWindow* gdk_window) {
2529   if (Preferences::GetBool("widget.disable-workspace-management", false)) {
2530     return true;
2531   }
2532   if (Preferences::HasUserValue("widget.workspace-management")) {
2533     return Preferences::GetBool("widget.workspace-management");
2534   }
2535 
2536   static const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
2537   if (currentDesktop && strstr(currentDesktop, "GNOME")) {
2538     // Gnome uses dynamic workspaces by default so disable workspace management
2539     // in that case.
2540     bool usesDynamicWorkspaces = true;
2541     nsCOMPtr<nsIGSettingsService> gsettings =
2542         do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
2543     if (gsettings) {
2544       nsCOMPtr<nsIGSettingsCollection> mutterSettings;
2545       gsettings->GetCollectionForSchema(nsLiteralCString(kDesktopMutterSchema),
2546                                         getter_AddRefs(mutterSettings));
2547       if (mutterSettings) {
2548         if (NS_SUCCEEDED(mutterSettings->GetBoolean(
2549                 nsLiteralCString(kDesktopDynamicWorkspacesKey),
2550                 &usesDynamicWorkspaces))) {
2551         }
2552       }
2553     }
2554     return usesDynamicWorkspaces;
2555   }
2556 
2557   // When XDG_CURRENT_DESKTOP is missing, try to get window manager name.
2558   if (!currentDesktop) {
2559     nsAutoCString wmName;
2560     if (GetWindowManagerName(gdk_window, wmName)) {
2561       if (wmName.EqualsLiteral("bspwm")) {
2562         return true;
2563       }
2564       if (wmName.EqualsLiteral("i3")) {
2565         return true;
2566       }
2567     }
2568   }
2569 
2570   return false;
2571 }
2572 
GetWorkspaceID(nsAString & workspaceID)2573 void nsWindow::GetWorkspaceID(nsAString& workspaceID) {
2574   workspaceID.Truncate();
2575 
2576   if (!GdkIsX11Display() || !mShell) {
2577     return;
2578   }
2579 
2580   LOG("nsWindow::GetWorkspaceID()\n");
2581 
2582   // Get the gdk window for this widget.
2583   GdkWindow* gdk_window = gtk_widget_get_window(mShell);
2584   if (!gdk_window) {
2585     LOG("  missing Gdk window, quit.");
2586     return;
2587   }
2588 
2589   if (WorkspaceManagementDisabled(gdk_window)) {
2590     LOG("  WorkspaceManagementDisabled, quit.");
2591     return;
2592   }
2593 
2594   GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
2595   GdkAtom type_returned;
2596   int format_returned;
2597   int length_returned;
2598   long* wm_desktop;
2599 
2600   if (!gdk_property_get(gdk_window, gdk_atom_intern("_NET_WM_DESKTOP", FALSE),
2601                         cardinal_atom,
2602                         0,          // offset
2603                         INT32_MAX,  // length
2604                         FALSE,      // delete
2605                         &type_returned, &format_returned, &length_returned,
2606                         (guchar**)&wm_desktop)) {
2607     LOG("  gdk_property_get() failed, quit.");
2608     return;
2609   }
2610 
2611   LOG("  got workspace ID %d", (int32_t)wm_desktop[0]);
2612   workspaceID.AppendInt((int32_t)wm_desktop[0]);
2613   g_free(wm_desktop);
2614 }
2615 
MoveToWorkspace(const nsAString & workspaceIDStr)2616 void nsWindow::MoveToWorkspace(const nsAString& workspaceIDStr) {
2617   nsresult rv = NS_OK;
2618   int32_t workspaceID = workspaceIDStr.ToInteger(&rv);
2619 
2620   LOG("nsWindow::MoveToWorkspace() ID %d", workspaceID);
2621   if (NS_FAILED(rv) || !workspaceID || !GdkIsX11Display() || !mShell) {
2622     LOG("  MoveToWorkspace disabled, quit");
2623     return;
2624   }
2625 
2626   // Get the gdk window for this widget.
2627   GdkWindow* gdk_window = gtk_widget_get_window(mShell);
2628   if (!gdk_window) {
2629     LOG("  failed to get GdkWindow, quit.");
2630     return;
2631   }
2632 
2633   // This code is inspired by some found in the 'gxtuner' project.
2634   // https://github.com/brummer10/gxtuner/blob/792d453da0f3a599408008f0f1107823939d730d/deskpager.cpp#L50
2635   XEvent xevent;
2636   Display* xdisplay = gdk_x11_get_default_xdisplay();
2637   GdkScreen* screen = gdk_window_get_screen(gdk_window);
2638   Window root_win = GDK_WINDOW_XID(gdk_screen_get_root_window(screen));
2639   GdkDisplay* display = gdk_window_get_display(gdk_window);
2640   Atom type = gdk_x11_get_xatom_by_name_for_display(display, "_NET_WM_DESKTOP");
2641 
2642   xevent.type = ClientMessage;
2643   xevent.xclient.type = ClientMessage;
2644   xevent.xclient.serial = 0;
2645   xevent.xclient.send_event = TRUE;
2646   xevent.xclient.display = xdisplay;
2647   xevent.xclient.window = GDK_WINDOW_XID(gdk_window);
2648   xevent.xclient.message_type = type;
2649   xevent.xclient.format = 32;
2650   xevent.xclient.data.l[0] = workspaceID;
2651   xevent.xclient.data.l[1] = X11CurrentTime;
2652   xevent.xclient.data.l[2] = 0;
2653   xevent.xclient.data.l[3] = 0;
2654   xevent.xclient.data.l[4] = 0;
2655 
2656   XSendEvent(xdisplay, root_win, FALSE,
2657              SubstructureNotifyMask | SubstructureRedirectMask, &xevent);
2658 
2659   XFlush(xdisplay);
2660   LOG("  moved to workspace");
2661 }
2662 
2663 using SetUserTimeFunc = void (*)(GdkWindow*, guint32);
2664 
SetUserTimeAndStartupIDForActivatedWindow(GtkWidget * aWindow)2665 static void SetUserTimeAndStartupIDForActivatedWindow(GtkWidget* aWindow) {
2666   nsGTKToolkit* GTKToolkit = nsGTKToolkit::GetToolkit();
2667   if (!GTKToolkit) return;
2668 
2669   nsAutoCString desktopStartupID;
2670   GTKToolkit->GetDesktopStartupID(&desktopStartupID);
2671   if (desktopStartupID.IsEmpty()) {
2672     // We don't have the data we need. Fall back to an
2673     // approximation ... using the timestamp of the remote command
2674     // being received as a guess for the timestamp of the user event
2675     // that triggered it.
2676     uint32_t timestamp = GTKToolkit->GetFocusTimestamp();
2677     if (timestamp) {
2678       gdk_window_focus(gtk_widget_get_window(aWindow), timestamp);
2679       GTKToolkit->SetFocusTimestamp(0);
2680     }
2681     return;
2682   }
2683 
2684   gtk_window_set_startup_id(GTK_WINDOW(aWindow), desktopStartupID.get());
2685 
2686   // If we used the startup ID, that already contains the focus timestamp;
2687   // we don't want to reuse the timestamp next time we raise the window
2688   GTKToolkit->SetFocusTimestamp(0);
2689   GTKToolkit->SetDesktopStartupID(""_ns);
2690 }
2691 
2692 /* static */
GetLastUserInputTime()2693 guint32 nsWindow::GetLastUserInputTime() {
2694   // gdk_x11_display_get_user_time/gtk_get_current_event_time tracks
2695   // button and key presses, DESKTOP_STARTUP_ID used to start the app,
2696   // drop events from external drags,
2697   // WM_DELETE_WINDOW delete events, but not usually mouse motion nor
2698   // button and key releases.  Therefore use the most recent of
2699   // gdk_x11_display_get_user_time and the last time that we have seen.
2700   GdkDisplay* gdkDisplay = gdk_display_get_default();
2701   guint32 timestamp = GdkIsX11Display(gdkDisplay)
2702                           ? gdk_x11_display_get_user_time(gdkDisplay)
2703                           : gtk_get_current_event_time();
2704 
2705   if (sLastUserInputTime != GDK_CURRENT_TIME &&
2706       TimestampIsNewerThan(sLastUserInputTime, timestamp)) {
2707     return sLastUserInputTime;
2708   }
2709 
2710   return timestamp;
2711 }
2712 
2713 #ifdef MOZ_WAYLAND
FocusWaylandWindow(const char * aTokenID)2714 void nsWindow::FocusWaylandWindow(const char* aTokenID) {
2715   auto releaseToken = mozilla::MakeScopeExit(
2716       [&]() { g_clear_pointer(&mXdgToken, xdg_activation_token_v1_destroy); });
2717 
2718   LOG("nsWindow::SetFocusWayland");
2719   if (IsDestroyed()) {
2720     LOG("  already destroyed, quit.");
2721     return;
2722   }
2723   wl_surface* surface =
2724       mGdkWindow ? gdk_wayland_window_get_wl_surface(mGdkWindow) : nullptr;
2725   if (!surface) {
2726     LOG("  mGdkWindow is not visible, quit.");
2727     return;
2728   }
2729 
2730   LOG("  requesting xdg-activation, surface ID %d",
2731       wl_proxy_get_id((struct wl_proxy*)surface));
2732 
2733   xdg_activation_v1* xdg_activation = WaylandDisplayGet()->GetXdgActivation();
2734   xdg_activation_v1_activate(xdg_activation, aTokenID, surface);
2735 }
2736 
2737 // We've got activation token from Wayland compositor so it's time to use it.
token_done(gpointer data,struct xdg_activation_token_v1 * provider,const char * token)2738 static void token_done(gpointer data, struct xdg_activation_token_v1* provider,
2739                        const char* token) {
2740   // Compensate aWindow->AddRef() from nsWindow::RequestFocusWaylandWindow().
2741   RefPtr<nsWindow> window = dont_AddRef(static_cast<nsWindow*>(data));
2742   window->FocusWaylandWindow(token);
2743 }
2744 
2745 static const struct xdg_activation_token_v1_listener token_listener = {
2746     token_done,
2747 };
2748 
RequestFocusWaylandWindow(RefPtr<nsWindow> aWindow)2749 void nsWindow::RequestFocusWaylandWindow(RefPtr<nsWindow> aWindow) {
2750   LOGW("nsWindow::RequestFocusWaylandWindow(%p) gFocusWindow %p",
2751        (void*)aWindow, gFocusWindow);
2752 
2753   if (!gFocusWindow || gFocusWindow->IsDestroyed()) {
2754     LOGW("  missing gFocusWindow, quit.");
2755     return;
2756   }
2757 
2758   RefPtr<nsWaylandDisplay> display = WaylandDisplayGet();
2759   xdg_activation_v1* xdg_activation = display->GetXdgActivation();
2760   if (!xdg_activation) {
2761     LOGW("  xdg-activation is missing, quit.");
2762     return;
2763   }
2764 
2765   wl_surface* focusSurface;
2766   uint32_t focusSerial;
2767   KeymapWrapper::GetFocusInfo(&focusSurface, &focusSerial);
2768   if (!focusSurface) {
2769     LOGW("  We're missing KeymapWrapper focused window, quit.");
2770     return;
2771   }
2772 
2773   GdkWindow* gdkWindow = gtk_widget_get_window(gFocusWindow->mShell);
2774   if (!gdkWindow) {
2775     LOGW("  gFocusWindow is not mapped, quit.");
2776     return;
2777   }
2778   wl_surface* surface = gdk_wayland_window_get_wl_surface(gdkWindow);
2779   if (focusSurface != surface) {
2780     LOGW("  focused surface %p and gFocusWindow surface %p don't match, quit.",
2781          focusSurface, surface);
2782     return;
2783   }
2784 
2785   LOGW(
2786       "  requesting xdg-activation token, surface %p ID %d serial %d seat ID "
2787       "%d",
2788       focusSurface,
2789       focusSurface ? wl_proxy_get_id((struct wl_proxy*)focusSurface) : 0,
2790       focusSerial, wl_proxy_get_id((struct wl_proxy*)KeymapWrapper::GetSeat()));
2791 
2792   // Store activation token at activated window for further release.
2793   g_clear_pointer(&aWindow->mXdgToken, xdg_activation_token_v1_destroy);
2794   aWindow->mXdgToken = xdg_activation_v1_get_activation_token(xdg_activation);
2795 
2796   // Addref aWindow to avoid potential release untill we get token_done
2797   // callback.
2798   xdg_activation_token_v1_add_listener(aWindow->mXdgToken, &token_listener,
2799                                        do_AddRef(aWindow).take());
2800   xdg_activation_token_v1_set_serial(aWindow->mXdgToken, focusSerial,
2801                                      KeymapWrapper::GetSeat());
2802   xdg_activation_token_v1_set_surface(aWindow->mXdgToken, focusSurface);
2803   xdg_activation_token_v1_commit(aWindow->mXdgToken);
2804 }
2805 #endif
2806 
2807 // Request activation of this window or give focus to this widget.
2808 // aRaise means whether we should request activation of this widget's
2809 // toplevel window.
2810 //
2811 // nsWindow::SetFocus(Raise::Yes) - Raise and give focus to toplevel window.
2812 // nsWindow::SetFocus(Raise::No) - Give focus to this window.
SetFocus(Raise aRaise,mozilla::dom::CallerType aCallerType)2813 void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
2814   LOG("nsWindow::SetFocus Raise %d\n", aRaise == Raise::Yes);
2815 
2816   // Raise the window if someone passed in true and the prefs are
2817   // set properly.
2818   GtkWidget* toplevelWidget = gtk_widget_get_toplevel(GTK_WIDGET(mContainer));
2819 
2820   LOG("  gFocusWindow [%p]\n", gFocusWindow);
2821   LOG("  mContainer [%p]\n", GTK_WIDGET(mContainer));
2822   LOG("  Toplevel widget [%p]\n", toplevelWidget);
2823 
2824   // Make sure that our owning widget has focus.  If it doesn't try to
2825   // grab it.  Note that we don't set our focus flag in this case.
2826   if (gRaiseWindows && aRaise == Raise::Yes && toplevelWidget &&
2827       !gtk_widget_has_focus(toplevelWidget)) {
2828     if (gtk_widget_get_visible(mShell)) {
2829       gdk_window_show_unraised(gtk_widget_get_window(mShell));
2830       // Unset the urgency hint if possible.
2831       SetUrgencyHint(mShell, false);
2832     }
2833   }
2834 
2835   RefPtr<nsWindow> toplevelWindow = get_window_for_gtk_widget(toplevelWidget);
2836   if (!toplevelWindow) {
2837     LOG("  missing toplevel nsWindow, quit\n");
2838     return;
2839   }
2840 
2841   if (aRaise == Raise::Yes) {
2842     // means request toplevel activation.
2843 
2844     // This is asynchronous.
2845     // If and when the window manager accepts the request, then the focus
2846     // widget will get a focus-in-event signal.
2847     if (gRaiseWindows && toplevelWindow->mIsShown && toplevelWindow->mShell &&
2848         !gtk_window_is_active(GTK_WINDOW(toplevelWindow->mShell))) {
2849       uint32_t timestamp = GDK_CURRENT_TIME;
2850 
2851       nsGTKToolkit* GTKToolkit = nsGTKToolkit::GetToolkit();
2852       if (GTKToolkit) {
2853         timestamp = GTKToolkit->GetFocusTimestamp();
2854         GTKToolkit->SetFocusTimestamp(0);
2855       }
2856       if (!timestamp) {
2857         timestamp = GetLastUserInputTime();
2858       }
2859 
2860       LOG("  requesting toplevel activation [%p]\n", (void*)toplevelWindow);
2861       gtk_window_present_with_time(GTK_WINDOW(toplevelWindow->mShell),
2862                                    timestamp);
2863 
2864 #ifdef MOZ_WAYLAND
2865       if (GdkIsWaylandDisplay()) {
2866         RequestFocusWaylandWindow(toplevelWindow);
2867       }
2868 #endif
2869     }
2870     return;
2871   }
2872 
2873   // aRaise == No means that keyboard events should be dispatched from this
2874   // widget.
2875 
2876   // Ensure GTK_WIDGET(mContainer) is the focused GtkWidget within its toplevel
2877   // window.
2878   //
2879   // For eWindowType_popup, this GtkWidget may not actually be the one that
2880   // receives the key events as it may be the parent window that is active.
2881   if (!gtk_widget_is_focus(GTK_WIDGET(mContainer))) {
2882     // This is synchronous.  It takes focus from a plugin or from a widget
2883     // in an embedder.  The focus manager already knows that this window
2884     // is active so gBlockActivateEvent avoids another (unnecessary)
2885     // activate notification.
2886     gBlockActivateEvent = true;
2887     gtk_widget_grab_focus(GTK_WIDGET(mContainer));
2888     gBlockActivateEvent = false;
2889   }
2890 
2891   // If this is the widget that already has focus, return.
2892   if (gFocusWindow == this) {
2893     LOG("  already have focus");
2894     return;
2895   }
2896 
2897   // Set this window to be the focused child window
2898   gFocusWindow = this;
2899 
2900   if (mIMContext) {
2901     mIMContext->OnFocusWindow(this);
2902   }
2903 
2904   LOG("  widget now has focus in SetFocus()");
2905 }
2906 
GetScreenBounds()2907 LayoutDeviceIntRect nsWindow::GetScreenBounds() {
2908   LayoutDeviceIntRect rect;
2909   if (mContainer) {
2910     // use the point including window decorations
2911     gint x, y;
2912     gdk_window_get_root_origin(gtk_widget_get_window(GTK_WIDGET(mContainer)),
2913                                &x, &y);
2914     rect.MoveTo(GdkPointToDevicePixels({x, y}));
2915   } else {
2916     rect.MoveTo(WidgetToScreenOffset());
2917   }
2918   // mBounds.Size() is the window bounds, not the window-manager frame
2919   // bounds (bug 581863).  gdk_window_get_frame_extents would give the
2920   // frame bounds, but mBounds.Size() is returned here for consistency
2921   // with Resize.
2922   rect.SizeTo(mBounds.Size());
2923 #if MOZ_LOGGING
2924   gint scale = GdkCeiledScaleFactor();
2925   LOG("GetScreenBounds %d,%d -> %d x %d, unscaled %d,%d -> %d x %d\n", rect.x,
2926       rect.y, rect.width, rect.height, rect.x / scale, rect.y / scale,
2927       rect.width / scale, rect.height / scale);
2928 #endif
2929   return rect;
2930 }
2931 
GetClientSize()2932 LayoutDeviceIntSize nsWindow::GetClientSize() {
2933   return LayoutDeviceIntSize(mBounds.width, mBounds.height);
2934 }
2935 
GetClientBounds()2936 LayoutDeviceIntRect nsWindow::GetClientBounds() {
2937   // GetBounds returns a rect whose top left represents the top left of the
2938   // outer bounds, but whose width/height represent the size of the inner
2939   // bounds (which is messed up).
2940   LayoutDeviceIntRect rect = GetBounds();
2941   rect.MoveBy(GetClientOffset());
2942   return rect;
2943 }
2944 
UpdateClientOffsetFromFrameExtents()2945 void nsWindow::UpdateClientOffsetFromFrameExtents() {
2946   AUTO_PROFILER_LABEL("nsWindow::UpdateClientOffsetFromFrameExtents", OTHER);
2947 
2948   if (mGtkWindowDecoration == GTK_DECORATION_CLIENT && mDrawInTitlebar) {
2949     return;
2950   }
2951 
2952   if (!mShell ||
2953       gtk_window_get_window_type(GTK_WINDOW(mShell)) == GTK_WINDOW_POPUP) {
2954     mClientOffset = nsIntPoint(0, 0);
2955     return;
2956   }
2957 
2958   GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
2959 
2960   GdkAtom type_returned;
2961   int format_returned;
2962   int length_returned;
2963   long* frame_extents;
2964 
2965   if (!gdk_property_get(gtk_widget_get_window(mShell),
2966                         gdk_atom_intern("_NET_FRAME_EXTENTS", FALSE),
2967                         cardinal_atom,
2968                         0,      // offset
2969                         4 * 4,  // length
2970                         FALSE,  // delete
2971                         &type_returned, &format_returned, &length_returned,
2972                         (guchar**)&frame_extents) ||
2973       length_returned / sizeof(glong) != 4) {
2974     mClientOffset = nsIntPoint(0, 0);
2975   } else {
2976     // data returned is in the order left, right, top, bottom
2977     auto left = int32_t(frame_extents[0]);
2978     auto top = int32_t(frame_extents[2]);
2979     g_free(frame_extents);
2980 
2981     mClientOffset = nsIntPoint(left, top);
2982   }
2983 
2984   // Send a WindowMoved notification. This ensures that BrowserParent
2985   // picks up the new client offset and sends it to the child process
2986   // if appropriate.
2987   NotifyWindowMoved(mBounds.x, mBounds.y);
2988 
2989   LOG("nsWindow::UpdateClientOffsetFromFrameExtents %d,%d\n", mClientOffset.x,
2990       mClientOffset.y);
2991 }
2992 
GetClientOffset()2993 LayoutDeviceIntPoint nsWindow::GetClientOffset() {
2994   return GdkIsX11Display()
2995              ? LayoutDeviceIntPoint::FromUnknownPoint(mClientOffset)
2996              : LayoutDeviceIntPoint(0, 0);
2997 }
2998 
OnPropertyNotifyEvent(GtkWidget * aWidget,GdkEventProperty * aEvent)2999 gboolean nsWindow::OnPropertyNotifyEvent(GtkWidget* aWidget,
3000                                          GdkEventProperty* aEvent) {
3001   if (aEvent->atom == gdk_atom_intern("_NET_FRAME_EXTENTS", FALSE)) {
3002     UpdateClientOffsetFromFrameExtents();
3003     return FALSE;
3004   }
3005   if (!mGdkWindow) {
3006     return FALSE;
3007   }
3008   if (GetCurrentTimeGetter()->PropertyNotifyHandler(aWidget, aEvent)) {
3009     return TRUE;
3010   }
3011   return FALSE;
3012 }
3013 
GetCursorForImage(const nsIWidget::Cursor & aCursor,int32_t aWidgetScaleFactor)3014 static GdkCursor* GetCursorForImage(const nsIWidget::Cursor& aCursor,
3015                                     int32_t aWidgetScaleFactor) {
3016   if (!aCursor.IsCustom()) {
3017     return nullptr;
3018   }
3019   nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
3020 
3021   // NOTE: GTK only allows integer scale factors, so we ceil to the larger scale
3022   // factor and then tell gtk to scale it down. We ensure to scale at least to
3023   // the GDK scale factor, so that cursors aren't downsized in HiDPI on wayland,
3024   // see bug 1707533.
3025   int32_t gtkScale = std::max(
3026       aWidgetScaleFactor, int32_t(std::ceil(std::max(aCursor.mResolution.mX,
3027                                                      aCursor.mResolution.mY))));
3028 
3029   // Reject cursors greater than 128 pixels in some direction, to prevent
3030   // spoofing.
3031   // XXX ideally we should rescale. Also, we could modify the API to
3032   // allow trusted content to set larger cursors.
3033   //
3034   // TODO(emilio, bug 1445844): Unify the solution for this with other
3035   // platforms.
3036   if (size.width > 128 || size.height > 128) {
3037     return nullptr;
3038   }
3039 
3040   nsIntSize rasterSize = size * gtkScale;
3041   GdkPixbuf* pixbuf =
3042       nsImageToPixbuf::ImageToPixbuf(aCursor.mContainer, Some(rasterSize));
3043   if (!pixbuf) {
3044     return nullptr;
3045   }
3046 
3047   // Looks like all cursors need an alpha channel (tested on Gtk 2.4.4). This
3048   // is of course not documented anywhere...
3049   // So add one if there isn't one yet
3050   if (!gdk_pixbuf_get_has_alpha(pixbuf)) {
3051     GdkPixbuf* alphaBuf = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
3052     g_object_unref(pixbuf);
3053     pixbuf = alphaBuf;
3054     if (!alphaBuf) {
3055       return nullptr;
3056     }
3057   }
3058 
3059   auto CleanupPixBuf =
3060       mozilla::MakeScopeExit([&]() { g_object_unref(pixbuf); });
3061 
3062   cairo_surface_t* surface =
3063       gdk_cairo_surface_create_from_pixbuf(pixbuf, gtkScale, nullptr);
3064   if (!surface) {
3065     return nullptr;
3066   }
3067 
3068   auto CleanupSurface =
3069       mozilla::MakeScopeExit([&]() { cairo_surface_destroy(surface); });
3070 
3071   return gdk_cursor_new_from_surface(gdk_display_get_default(), surface,
3072                                      aCursor.mHotspotX, aCursor.mHotspotY);
3073 }
3074 
SetCursor(const Cursor & aCursor)3075 void nsWindow::SetCursor(const Cursor& aCursor) {
3076   // if we're not the toplevel window pass up the cursor request to
3077   // the toplevel window to handle it.
3078   if (!mContainer && mGdkWindow) {
3079     if (nsWindow* window = GetContainerWindow()) {
3080       window->SetCursor(aCursor);
3081     }
3082     return;
3083   }
3084 
3085   // Only change cursor if it's actually been changed
3086   if (!mUpdateCursor && mCursor == aCursor) {
3087     return;
3088   }
3089 
3090   mUpdateCursor = false;
3091   mCursor = aCursor;
3092 
3093   // Try to set the cursor image first, and fall back to the numeric cursor.
3094   bool fromImage = true;
3095   GdkCursor* newCursor = GetCursorForImage(aCursor, GdkCeiledScaleFactor());
3096   if (!newCursor) {
3097     fromImage = false;
3098     newCursor = get_gtk_cursor(aCursor.mDefaultCursor);
3099   }
3100 
3101   auto CleanupCursor = mozilla::MakeScopeExit([&]() {
3102     // get_gtk_cursor returns a weak reference, which we shouldn't unref.
3103     if (fromImage) {
3104       g_object_unref(newCursor);
3105     }
3106   });
3107 
3108   if (!newCursor || !mContainer) {
3109     return;
3110   }
3111 
3112   gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)),
3113                         newCursor);
3114 }
3115 
Invalidate(const LayoutDeviceIntRect & aRect)3116 void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
3117   if (!mGdkWindow) return;
3118 
3119   GdkRectangle rect = DevicePixelsToGdkRectRoundOut(aRect);
3120   gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
3121 
3122   LOG("Invalidate (rect): %d %d %d %d\n", rect.x, rect.y, rect.width,
3123       rect.height);
3124 }
3125 
GetNativeData(uint32_t aDataType)3126 void* nsWindow::GetNativeData(uint32_t aDataType) {
3127   switch (aDataType) {
3128     case NS_NATIVE_WINDOW:
3129     case NS_NATIVE_WIDGET: {
3130       return mGdkWindow;
3131     }
3132 
3133     case NS_NATIVE_DISPLAY: {
3134 #ifdef MOZ_X11
3135       GdkDisplay* gdkDisplay = gdk_display_get_default();
3136       if (GdkIsX11Display(gdkDisplay)) {
3137         return GDK_DISPLAY_XDISPLAY(gdkDisplay);
3138       }
3139 #endif /* MOZ_X11 */
3140       // Don't bother to return native display on Wayland as it's for
3141       // X11 only NPAPI plugins.
3142       return nullptr;
3143     }
3144     case NS_NATIVE_SHELLWIDGET:
3145       return GetToplevelWidget();
3146 
3147     case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID:
3148       if (GdkIsX11Display()) {
3149         return (void*)GDK_WINDOW_XID(gdk_window_get_toplevel(mGdkWindow));
3150       }
3151       NS_WARNING(
3152           "nsWindow::GetNativeData(): NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID is not "
3153           "handled on Wayland!");
3154       return nullptr;
3155     case NS_RAW_NATIVE_IME_CONTEXT: {
3156       void* pseudoIMEContext = GetPseudoIMEContext();
3157       if (pseudoIMEContext) {
3158         return pseudoIMEContext;
3159       }
3160       // If IME context isn't available on this widget, we should set |this|
3161       // instead of nullptr.
3162       if (!mIMContext) {
3163         return this;
3164       }
3165       return mIMContext.get();
3166     }
3167     case NS_NATIVE_OPENGL_CONTEXT:
3168       return nullptr;
3169     case NS_NATIVE_EGL_WINDOW: {
3170       void* eglWindow = nullptr;
3171       if (GdkIsX11Display()) {
3172         eglWindow = mGdkWindow ? (void*)GDK_WINDOW_XID(mGdkWindow) : nullptr;
3173       }
3174 #ifdef MOZ_WAYLAND
3175       else {
3176         eglWindow = moz_container_wayland_get_egl_window(
3177             mContainer, FractionalScaleFactor());
3178       }
3179 #endif
3180       LOG("Get NS_NATIVE_EGL_WINDOW window %p", eglWindow);
3181       return eglWindow;
3182     }
3183     default:
3184       NS_WARNING("nsWindow::GetNativeData called with bad value");
3185       return nullptr;
3186   }
3187 }
3188 
SetTitle(const nsAString & aTitle)3189 nsresult nsWindow::SetTitle(const nsAString& aTitle) {
3190   if (!mShell) return NS_OK;
3191 
3192     // convert the string into utf8 and set the title.
3193 #define UTF8_FOLLOWBYTE(ch) (((ch)&0xC0) == 0x80)
3194   NS_ConvertUTF16toUTF8 titleUTF8(aTitle);
3195   if (titleUTF8.Length() > NS_WINDOW_TITLE_MAX_LENGTH) {
3196     // Truncate overlong titles (bug 167315). Make sure we chop after a
3197     // complete sequence by making sure the next char isn't a follow-byte.
3198     uint32_t len = NS_WINDOW_TITLE_MAX_LENGTH;
3199     while (UTF8_FOLLOWBYTE(titleUTF8[len])) --len;
3200     titleUTF8.Truncate(len);
3201   }
3202   gtk_window_set_title(GTK_WINDOW(mShell), (const char*)titleUTF8.get());
3203 
3204   return NS_OK;
3205 }
3206 
SetIcon(const nsAString & aIconSpec)3207 void nsWindow::SetIcon(const nsAString& aIconSpec) {
3208   if (!mShell) return;
3209 
3210   nsAutoCString iconName;
3211 
3212   if (aIconSpec.EqualsLiteral("default")) {
3213     nsAutoString brandName;
3214     WidgetUtils::GetBrandShortName(brandName);
3215     if (brandName.IsEmpty()) {
3216       brandName.AssignLiteral(u"Mozilla");
3217     }
3218     AppendUTF16toUTF8(brandName, iconName);
3219     ToLowerCase(iconName);
3220   } else {
3221     AppendUTF16toUTF8(aIconSpec, iconName);
3222   }
3223 
3224   nsCOMPtr<nsIFile> iconFile;
3225   nsAutoCString path;
3226 
3227   gint* iconSizes = gtk_icon_theme_get_icon_sizes(gtk_icon_theme_get_default(),
3228                                                   iconName.get());
3229   bool foundIcon = (iconSizes[0] != 0);
3230   g_free(iconSizes);
3231 
3232   if (!foundIcon) {
3233     // Look for icons with the following suffixes appended to the base name
3234     // The last two entries (for the old XPM format) will be ignored unless
3235     // no icons are found using other suffixes. XPM icons are deprecated.
3236 
3237     const char16_t extensions[9][8] = {u".png",    u"16.png", u"32.png",
3238                                        u"48.png",  u"64.png", u"128.png",
3239                                        u"256.png", u".xpm",   u"16.xpm"};
3240 
3241     for (uint32_t i = 0; i < ArrayLength(extensions); i++) {
3242       // Don't bother looking for XPM versions if we found a PNG.
3243       if (i == ArrayLength(extensions) - 2 && foundIcon) break;
3244 
3245       ResolveIconName(aIconSpec, nsDependentString(extensions[i]),
3246                       getter_AddRefs(iconFile));
3247       if (iconFile) {
3248         iconFile->GetNativePath(path);
3249         GdkPixbuf* icon = gdk_pixbuf_new_from_file(path.get(), nullptr);
3250         if (icon) {
3251           gtk_icon_theme_add_builtin_icon(iconName.get(),
3252                                           gdk_pixbuf_get_height(icon), icon);
3253           g_object_unref(icon);
3254           foundIcon = true;
3255         }
3256       }
3257     }
3258   }
3259 
3260   // leave the default icon intact if no matching icons were found
3261   if (foundIcon) {
3262     gtk_window_set_icon_name(GTK_WINDOW(mShell), iconName.get());
3263   }
3264 }
3265 
3266 /* TODO(bug 1655924): gdk_window_get_origin is can block waiting for the X
3267    server for a long time, we would like to use the implementation below
3268    instead. However, removing the synchronous x server queries causes a race
3269    condition to surface, causing issues such as bug 1652743 and bug 1653711.
3270 
3271 
3272    This code can be used instead of gdk_window_get_origin() but it cuases
3273    such issues:
3274 
3275     *aX = 0;
3276     *aY = 0;
3277     if (!aWindow) {
3278       return;
3279     }
3280 
3281     GdkWindow* current = aWindow;
3282     while (GdkWindow* parent = gdk_window_get_parent(current)) {
3283       if (parent == current) {
3284         break;
3285       }
3286 
3287       int x = 0;
3288       int y = 0;
3289       gdk_window_get_position(current, &x, &y);
3290       *aX += x;
3291       *aY += y;
3292 
3293       current = parent;
3294     }
3295 */
WidgetToScreenOffset()3296 LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
3297   nsIntPoint origin(0, 0);
3298   if (mGdkWindow) {
3299     gdk_window_get_origin(mGdkWindow, &origin.x, &origin.y);
3300   }
3301   return GdkPointToDevicePixels({origin.x, origin.y});
3302 }
3303 
CaptureMouse(bool aCapture)3304 void nsWindow::CaptureMouse(bool aCapture) {
3305   LOG("nsWindow::CaptureMouse()");
3306 
3307   if (mIsDestroyed) {
3308     return;
3309   }
3310 
3311   if (aCapture) {
3312     gtk_grab_add(GTK_WIDGET(mContainer));
3313     GrabPointer(GetLastUserInputTime());
3314   } else {
3315     ReleaseGrabs();
3316     gtk_grab_remove(GTK_WIDGET(mContainer));
3317   }
3318 }
3319 
CaptureRollupEvents(nsIRollupListener * aListener,bool aDoCapture)3320 void nsWindow::CaptureRollupEvents(nsIRollupListener* aListener,
3321                                    bool aDoCapture) {
3322   LOG("CaptureRollupEvents() %i\n", int(aDoCapture));
3323 
3324   if (mIsDestroyed) {
3325     return;
3326   }
3327 
3328   if (aDoCapture) {
3329     gRollupListener = aListener;
3330     // Don't add a grab if a drag is in progress, or if the widget is a drag
3331     // feedback popup. (panels with type="drag").
3332     if (!GdkIsWaylandDisplay() && !mIsDragPopup &&
3333         !nsWindow::DragInProgress()) {
3334       gtk_grab_add(GTK_WIDGET(mContainer));
3335       GrabPointer(GetLastUserInputTime());
3336     }
3337   } else {
3338     if (!nsWindow::DragInProgress()) {
3339       ReleaseGrabs();
3340     }
3341     // There may not have been a drag in process when aDoCapture was set,
3342     // so make sure to remove any added grab.  This is a no-op if the grab
3343     // was not added to this widget.
3344     LOG("  remove mContainer grab [%p]\n", this);
3345     gtk_grab_remove(GTK_WIDGET(mContainer));
3346     gRollupListener = nullptr;
3347   }
3348 }
3349 
GetAttention(int32_t aCycleCount)3350 nsresult nsWindow::GetAttention(int32_t aCycleCount) {
3351   LOG("nsWindow::GetAttention");
3352 
3353   GtkWidget* top_window = GetToplevelWidget();
3354   GtkWidget* top_focused_window =
3355       gFocusWindow ? gFocusWindow->GetToplevelWidget() : nullptr;
3356 
3357   // Don't get attention if the window is focused anyway.
3358   if (top_window && (gtk_widget_get_visible(top_window)) &&
3359       top_window != top_focused_window) {
3360     SetUrgencyHint(top_window, true);
3361   }
3362 
3363   return NS_OK;
3364 }
3365 
HasPendingInputEvent()3366 bool nsWindow::HasPendingInputEvent() {
3367   // This sucks, but gtk/gdk has no way to answer the question we want while
3368   // excluding paint events, and there's no X API that will let us peek
3369   // without blocking or removing.  To prevent event reordering, peek
3370   // anything except expose events.  Reordering expose and others should be
3371   // ok, hopefully.
3372   bool haveEvent = false;
3373 #ifdef MOZ_X11
3374   XEvent ev;
3375   if (GdkIsX11Display()) {
3376     Display* display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
3377     haveEvent = XCheckMaskEvent(
3378         display,
3379         KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
3380             EnterWindowMask | LeaveWindowMask | PointerMotionMask |
3381             PointerMotionHintMask | Button1MotionMask | Button2MotionMask |
3382             Button3MotionMask | Button4MotionMask | Button5MotionMask |
3383             ButtonMotionMask | KeymapStateMask | VisibilityChangeMask |
3384             StructureNotifyMask | ResizeRedirectMask | SubstructureNotifyMask |
3385             SubstructureRedirectMask | FocusChangeMask | PropertyChangeMask |
3386             ColormapChangeMask | OwnerGrabButtonMask,
3387         &ev);
3388     if (haveEvent) {
3389       XPutBackEvent(display, &ev);
3390     }
3391   }
3392 #endif
3393   return haveEvent;
3394 }
3395 
3396 #ifdef cairo_copy_clip_rectangle_list
3397 #  error "Looks like we're including Mozilla's cairo instead of system cairo"
3398 #endif
ExtractExposeRegion(LayoutDeviceIntRegion & aRegion,cairo_t * cr)3399 static bool ExtractExposeRegion(LayoutDeviceIntRegion& aRegion, cairo_t* cr) {
3400   cairo_rectangle_list_t* rects = cairo_copy_clip_rectangle_list(cr);
3401   if (rects->status != CAIRO_STATUS_SUCCESS) {
3402     NS_WARNING("Failed to obtain cairo rectangle list.");
3403     return false;
3404   }
3405 
3406   for (int i = 0; i < rects->num_rectangles; i++) {
3407     const cairo_rectangle_t& r = rects->rectangles[i];
3408     aRegion.Or(aRegion,
3409                LayoutDeviceIntRect::Truncate((float)r.x, (float)r.y,
3410                                              (float)r.width, (float)r.height));
3411   }
3412 
3413   cairo_rectangle_list_destroy(rects);
3414   return true;
3415 }
3416 
3417 #ifdef MOZ_WAYLAND
CreateCompositorVsyncDispatcher()3418 void nsWindow::CreateCompositorVsyncDispatcher() {
3419   LOG_VSYNC("nsWindow::CreateCompositorVsyncDispatcher()");
3420   if (!mWaylandVsyncSource) {
3421     LOG_VSYNC(
3422         "  mWaylandVsyncSource is missing, create "
3423         "nsBaseWidget::CompositorVsyncDispatcher()");
3424     nsBaseWidget::CreateCompositorVsyncDispatcher();
3425     return;
3426   }
3427 
3428   if (XRE_IsParentProcess()) {
3429     if (!mCompositorVsyncDispatcherLock) {
3430       mCompositorVsyncDispatcherLock =
3431           MakeUnique<Mutex>("mCompositorVsyncDispatcherLock");
3432     }
3433     MutexAutoLock lock(*mCompositorVsyncDispatcherLock);
3434     if (!mCompositorVsyncDispatcher) {
3435       LOG_VSYNC("  create CompositorVsyncDispatcher()");
3436       mCompositorVsyncDispatcher =
3437           new CompositorVsyncDispatcher(mWaylandVsyncSource);
3438     }
3439   }
3440 }
3441 #endif
3442 
OnExposeEvent(cairo_t * cr)3443 gboolean nsWindow::OnExposeEvent(cairo_t* cr) {
3444   // Send any pending resize events so that layout can update.
3445   // May run event loop.
3446   MaybeDispatchResized();
3447 
3448   if (mIsDestroyed) {
3449     return FALSE;
3450   }
3451 
3452   // Windows that are not visible will be painted after they become visible.
3453   if (!mGdkWindow || !mHasMappedToplevel) {
3454     return FALSE;
3455   }
3456 #ifdef MOZ_WAYLAND
3457   if (GdkIsWaylandDisplay() && !moz_container_wayland_can_draw(mContainer)) {
3458     return FALSE;
3459   }
3460 #endif
3461 
3462   nsIWidgetListener* listener = GetListener();
3463   if (!listener) return FALSE;
3464 
3465   LOG("received expose event %p 0x%lx (rects follow):\n", mGdkWindow,
3466       GdkIsX11Display() ? gdk_x11_window_get_xid(mGdkWindow) : 0);
3467   LayoutDeviceIntRegion exposeRegion;
3468   if (!ExtractExposeRegion(exposeRegion, cr)) {
3469     return FALSE;
3470   }
3471 
3472   gint scale = GdkCeiledScaleFactor();
3473   LayoutDeviceIntRegion region = exposeRegion;
3474   region.ScaleRoundOut(scale, scale);
3475 
3476   WindowRenderer* renderer = GetWindowRenderer();
3477   WebRenderLayerManager* layerManager = renderer->AsWebRender();
3478   KnowsCompositor* knowsCompositor = renderer->AsKnowsCompositor();
3479 
3480   if (knowsCompositor && layerManager && mCompositorSession) {
3481     if (!mConfiguredClearColor && !IsPopup()) {
3482       layerManager->WrBridge()->SendSetDefaultClearColor(LookAndFeel::Color(
3483           LookAndFeel::ColorID::Window, LookAndFeel::ColorSchemeForChrome(),
3484           LookAndFeel::UseStandins::No));
3485       mConfiguredClearColor = true;
3486     }
3487 
3488     // We need to paint to the screen even if nothing changed, since if we
3489     // don't have a compositing window manager, our pixels could be stale.
3490     layerManager->SetNeedsComposite(true);
3491     layerManager->SendInvalidRegion(region.ToUnknownRegion());
3492   }
3493 
3494   RefPtr<nsWindow> strongThis(this);
3495 
3496   // Dispatch WillPaintWindow notification to allow scripts etc. to run
3497   // before we paint
3498   {
3499     listener->WillPaintWindow(this);
3500 
3501     // If the window has been destroyed during the will paint notification,
3502     // there is nothing left to do.
3503     if (!mGdkWindow) return TRUE;
3504 
3505     // Re-get the listener since the will paint notification might have
3506     // killed it.
3507     listener = GetListener();
3508     if (!listener) return FALSE;
3509   }
3510 
3511   if (knowsCompositor && layerManager && layerManager->NeedsComposite()) {
3512     layerManager->ScheduleComposite(wr::RenderReasons::WIDGET);
3513     layerManager->SetNeedsComposite(false);
3514   }
3515 
3516   // Our bounds may have changed after calling WillPaintWindow.  Clip
3517   // to the new bounds here.  The region is relative to this
3518   // window.
3519   region.And(region, LayoutDeviceIntRect(0, 0, mBounds.width, mBounds.height));
3520 
3521   bool shaped = false;
3522   if (eTransparencyTransparent == GetTransparencyMode()) {
3523     auto* window = static_cast<nsWindow*>(GetTopLevelWidget());
3524     if (mTransparencyBitmapForTitlebar) {
3525       if (mSizeState == nsSizeMode_Normal) {
3526         window->UpdateTitlebarTransparencyBitmap();
3527       } else {
3528         window->ClearTransparencyBitmap();
3529       }
3530     } else {
3531       if (mHasAlphaVisual) {
3532         // Remove possible shape mask from when window manger was not
3533         // previously compositing.
3534         window->ClearTransparencyBitmap();
3535       } else {
3536         shaped = true;
3537       }
3538     }
3539   }
3540 
3541   if (region.IsEmpty()) {
3542     return TRUE;
3543   }
3544 
3545   // If this widget uses OMTC...
3546   if (renderer->GetBackendType() == LayersBackend::LAYERS_WR) {
3547     listener->PaintWindow(this, region);
3548 
3549     // Re-get the listener since the will paint notification might have
3550     // killed it.
3551     listener = GetListener();
3552     if (!listener) return TRUE;
3553 
3554     listener->DidPaintWindow();
3555     return TRUE;
3556   }
3557 
3558   BufferMode layerBuffering = BufferMode::BUFFERED;
3559   RefPtr<DrawTarget> dt = StartRemoteDrawingInRegion(region, &layerBuffering);
3560   if (!dt || !dt->IsValid()) {
3561     return FALSE;
3562   }
3563   RefPtr<gfxContext> ctx;
3564   IntRect boundsRect = region.GetBounds().ToUnknownRect();
3565   IntPoint offset(0, 0);
3566   if (dt->GetSize() == boundsRect.Size()) {
3567     offset = boundsRect.TopLeft();
3568     dt->SetTransform(Matrix::Translation(-offset));
3569   }
3570 
3571 #ifdef MOZ_X11
3572   if (shaped) {
3573     // Collapse update area to the bounding box. This is so we only have to
3574     // call UpdateTranslucentWindowAlpha once. After we have dropped
3575     // support for non-Thebes graphics, UpdateTranslucentWindowAlpha will be
3576     // our private interface so we can rework things to avoid this.
3577     dt->PushClipRect(Rect(boundsRect));
3578 
3579     // The double buffering is done here to extract the shape mask.
3580     // (The shape mask won't be necessary when a visual with an alpha
3581     // channel is used on compositing window managers.)
3582     layerBuffering = BufferMode::BUFFER_NONE;
3583     RefPtr<DrawTarget> destDT =
3584         dt->CreateSimilarDrawTarget(boundsRect.Size(), SurfaceFormat::B8G8R8A8);
3585     if (!destDT || !destDT->IsValid()) {
3586       return FALSE;
3587     }
3588     destDT->SetTransform(Matrix::Translation(-boundsRect.TopLeft()));
3589     ctx = gfxContext::CreatePreservingTransformOrNull(destDT);
3590   } else {
3591     gfxUtils::ClipToRegion(dt, region.ToUnknownRegion());
3592     ctx = gfxContext::CreatePreservingTransformOrNull(dt);
3593   }
3594   MOZ_ASSERT(ctx);  // checked both dt and destDT valid draw target above
3595 
3596 #  if 0
3597     // NOTE: Paint flashing region would be wrong for cairo, since
3598     // cairo inflates the update region, etc.  So don't paint flash
3599     // for cairo.
3600 #    ifdef DEBUG
3601     // XXX aEvent->region may refer to a newly-invalid area.  FIXME
3602     if (0 && WANT_PAINT_FLASHING && gtk_widget_get_window(aEvent))
3603         gdk_window_flash(mGdkWindow, 1, 100, aEvent->region);
3604 #    endif
3605 #  endif
3606 
3607 #endif  // MOZ_X11
3608 
3609   bool painted = false;
3610   {
3611     if (renderer->GetBackendType() == LayersBackend::LAYERS_NONE) {
3612       if (GetTransparencyMode() == eTransparencyTransparent &&
3613           layerBuffering == BufferMode::BUFFER_NONE && mHasAlphaVisual) {
3614         // If our draw target is unbuffered and we use an alpha channel,
3615         // clear the image beforehand to ensure we don't get artifacts from a
3616         // reused SHM image. See bug 1258086.
3617         dt->ClearRect(Rect(boundsRect));
3618       }
3619       AutoLayerManagerSetup setupLayerManager(this, ctx, layerBuffering);
3620       painted = listener->PaintWindow(this, region);
3621 
3622       // Re-get the listener since the will paint notification might have
3623       // killed it.
3624       listener = GetListener();
3625       if (!listener) return TRUE;
3626     }
3627   }
3628 
3629 #ifdef MOZ_X11
3630   // PaintWindow can Destroy us (bug 378273), avoid doing any paint
3631   // operations below if that happened - it will lead to XError and exit().
3632   if (shaped) {
3633     if (MOZ_LIKELY(!mIsDestroyed)) {
3634       if (painted) {
3635         RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
3636 
3637         UpdateAlpha(surf, boundsRect);
3638 
3639         dt->DrawSurface(surf, Rect(boundsRect),
3640                         Rect(0, 0, boundsRect.width, boundsRect.height),
3641                         DrawSurfaceOptions(SamplingFilter::POINT),
3642                         DrawOptions(1.0f, CompositionOp::OP_SOURCE));
3643       }
3644     }
3645   }
3646 
3647   ctx = nullptr;
3648   dt->PopClip();
3649 
3650 #endif  // MOZ_X11
3651 
3652   EndRemoteDrawingInRegion(dt, region);
3653 
3654   listener->DidPaintWindow();
3655 
3656   // Synchronously flush any new dirty areas
3657   cairo_region_t* dirtyArea = gdk_window_get_update_area(mGdkWindow);
3658 
3659   if (dirtyArea) {
3660     gdk_window_invalidate_region(mGdkWindow, dirtyArea, false);
3661     cairo_region_destroy(dirtyArea);
3662     gdk_window_process_updates(mGdkWindow, false);
3663   }
3664 
3665   // check the return value!
3666   return TRUE;
3667 }
3668 
UpdateAlpha(SourceSurface * aSourceSurface,nsIntRect aBoundsRect)3669 void nsWindow::UpdateAlpha(SourceSurface* aSourceSurface,
3670                            nsIntRect aBoundsRect) {
3671   // We need to create our own buffer to force the stride to match the
3672   // expected stride.
3673   int32_t stride =
3674       GetAlignedStride<4>(aBoundsRect.width, BytesPerPixel(SurfaceFormat::A8));
3675   if (stride == 0) {
3676     return;
3677   }
3678   int32_t bufferSize = stride * aBoundsRect.height;
3679   auto imageBuffer = MakeUniqueFallible<uint8_t[]>(bufferSize);
3680   {
3681     RefPtr<DrawTarget> drawTarget = gfxPlatform::CreateDrawTargetForData(
3682         imageBuffer.get(), aBoundsRect.Size(), stride, SurfaceFormat::A8);
3683 
3684     if (drawTarget) {
3685       drawTarget->DrawSurface(aSourceSurface,
3686                               Rect(0, 0, aBoundsRect.width, aBoundsRect.height),
3687                               Rect(0, 0, aSourceSurface->GetSize().width,
3688                                    aSourceSurface->GetSize().height),
3689                               DrawSurfaceOptions(SamplingFilter::POINT),
3690                               DrawOptions(1.0f, CompositionOp::OP_SOURCE));
3691     }
3692   }
3693   UpdateTranslucentWindowAlphaInternal(aBoundsRect, imageBuffer.get(), stride);
3694 }
3695 
OnConfigureEvent(GtkWidget * aWidget,GdkEventConfigure * aEvent)3696 gboolean nsWindow::OnConfigureEvent(GtkWidget* aWidget,
3697                                     GdkEventConfigure* aEvent) {
3698   // These events are only received on toplevel windows.
3699   //
3700   // GDK ensures that the coordinates are the client window top-left wrt the
3701   // root window.
3702   //
3703   //   GDK calculates the cordinates for real ConfigureNotify events on
3704   //   managed windows (that would normally be relative to the parent
3705   //   window).
3706   //
3707   //   Synthetic ConfigureNotify events are from the window manager and
3708   //   already relative to the root window.  GDK creates all X windows with
3709   //   border_width = 0, so synthetic events also indicate the top-left of
3710   //   the client window.
3711   //
3712   //   Override-redirect windows are children of the root window so parent
3713   //   coordinates are root coordinates.
3714 
3715   LOG("configure event %d,%d -> %d x %d scale %d\n", aEvent->x, aEvent->y,
3716       aEvent->width, aEvent->height,
3717       mGdkWindow ? gdk_window_get_scale_factor(mGdkWindow) : -1);
3718 
3719   if (mPendingConfigures > 0) {
3720     mPendingConfigures--;
3721   }
3722 
3723   // Don't fire configure event for scale changes, we handle that
3724   // OnScaleChanged event. Skip that for toplevel windows only.
3725   if (mWindowType == eWindowType_toplevel) {
3726     MOZ_DIAGNOSTIC_ASSERT(mGdkWindow,
3727                           "Getting configure for invisible window?");
3728     if (mWindowScaleFactor != gdk_window_get_scale_factor(mGdkWindow)) {
3729       LOG("  scale factor changed to %d,return early",
3730           gdk_window_get_scale_factor(mGdkWindow));
3731       return FALSE;
3732     }
3733   }
3734 
3735   LayoutDeviceIntRect screenBounds = GetScreenBounds();
3736 
3737   if (mWindowType == eWindowType_toplevel ||
3738       mWindowType == eWindowType_dialog) {
3739     // This check avoids unwanted rollup on spurious configure events from
3740     // Cygwin/X (bug 672103).
3741     if (mBounds.x != screenBounds.x || mBounds.y != screenBounds.y) {
3742       CheckForRollup(0, 0, false, true);
3743     }
3744   }
3745 
3746   NS_ASSERTION(GTK_IS_WINDOW(aWidget),
3747                "Configure event on widget that is not a GtkWindow");
3748   if (gtk_window_get_window_type(GTK_WINDOW(aWidget)) == GTK_WINDOW_POPUP) {
3749     // Override-redirect window
3750     //
3751     // These windows should not be moved by the window manager, and so any
3752     // change in position is a result of our direction.  mBounds has
3753     // already been set in std::move() or Resize(), and that is more
3754     // up-to-date than the position in the ConfigureNotify event if the
3755     // event is from an earlier window move.
3756     //
3757     // Skipping the WindowMoved call saves context menus from an infinite
3758     // loop when nsXULPopupManager::PopupMoved moves the window to the new
3759     // position and nsMenuPopupFrame::SetPopupPosition adds
3760     // offsetForContextMenu on each iteration.
3761 
3762     // Our back buffer might have been invalidated while we drew the last
3763     // frame, and its contents might be incorrect. See bug 1280653 comment 7
3764     // and comment 10. Specifically we must ensure we recomposite the frame
3765     // as soon as possible to avoid the corrupted frame being displayed.
3766     GetWindowRenderer()->FlushRendering(wr::RenderReasons::WIDGET);
3767     return FALSE;
3768   }
3769 
3770   mBounds.MoveTo(screenBounds.TopLeft());
3771 
3772   // XXX mozilla will invalidate the entire window after this move
3773   // complete.  wtf?
3774   NotifyWindowMoved(mBounds.x, mBounds.y);
3775 
3776   // A GTK app would usually update its client area size in response to
3777   // a "size-allocate" signal.
3778   // However, we need to set mBounds in advance at Resize()
3779   // as JS code expects immediate window size change.
3780   // If Gecko requests a resize from GTK, but subsequently,
3781   // before a corresponding "size-allocate" signal is emitted, the window is
3782   // resized to its former size via other means, such as maximizing,
3783   // then there is no "size-allocate" signal from which to update
3784   // the value of mBounds. Similarly, if Gecko's resize request is refused
3785   // by the window manager, then there will be no "size-allocate" signal.
3786   // In the refused request case, the window manager is required to dispatch
3787   // a ConfigureNotify event. mBounds can then be updated here.
3788   // This seems to also be sufficient to update mBounds when Gecko resizes
3789   // the window from maximized size and then immediately maximizes again.
3790   if (!mBoundsAreValid) {
3791     GtkAllocation allocation = {-1, -1, 0, 0};
3792     gtk_window_get_size(GTK_WINDOW(mShell), &allocation.width,
3793                         &allocation.height);
3794     OnSizeAllocate(&allocation);
3795   }
3796 
3797   return FALSE;
3798 }
3799 
OnMap()3800 void nsWindow::OnMap() {
3801   LOG("nsWindow::OnMap");
3802   // Gtk mapped out widget to screen. Configure underlying GdkWindow properly
3803   // as our rendering target.
3804   // This call means we have X11 (or Wayland) window we can render to by GL
3805   // so we need to notify compositor about it.
3806   mIsMapped = true;
3807   ConfigureGdkWindow();
3808 }
3809 
OnUnrealize()3810 void nsWindow::OnUnrealize() {
3811   // The GdkWindows are about to be destroyed (but not deleted), so remove
3812   // their references back to their container widget while the GdkWindow
3813   // hierarchy is still available.
3814   // This call means we *don't* have X11 (or Wayland) window we can render to.
3815   LOG("nsWindow::OnUnrealize GdkWindow %p", mGdkWindow);
3816   mIsMapped = false;
3817   ReleaseGdkWindow();
3818 }
3819 
OnSizeAllocate(GtkAllocation * aAllocation)3820 void nsWindow::OnSizeAllocate(GtkAllocation* aAllocation) {
3821   LOG("nsWindow::OnSizeAllocate %d,%d -> %d x %d\n", aAllocation->x,
3822       aAllocation->y, aAllocation->width, aAllocation->height);
3823 
3824   // Client offset are updated by _NET_FRAME_EXTENTS on X11 when system titlebar
3825   // is enabled. In either cases (Wayland or system titlebar is off on X11)
3826   // we don't get _NET_FRAME_EXTENTS X11 property notification so we derive
3827   // it from mContainer position.
3828   if (mGtkWindowDecoration == GTK_DECORATION_CLIENT) {
3829     if (GdkIsWaylandDisplay() || (GdkIsX11Display() && mDrawInTitlebar)) {
3830       UpdateClientOffsetFromCSDWindow();
3831     }
3832   }
3833 
3834   mBoundsAreValid = true;
3835 
3836   LayoutDeviceIntSize size = GdkRectToDevicePixels(*aAllocation).Size();
3837   if (mBounds.Size() == size) {
3838     LOG("  Already the same size");
3839     // We were already resized at nsWindow::OnConfigureEvent() so skip it.
3840     return;
3841   }
3842 
3843   // Invalidate the new part of the window now for the pending paint to
3844   // minimize background flashes (GDK does not do this for external resizes
3845   // of toplevels.)
3846   if (mGdkWindow) {
3847     if (mBounds.width < size.width) {
3848       GdkRectangle rect = DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect(
3849           mBounds.width, 0, size.width - mBounds.width, size.height));
3850       gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
3851     }
3852     if (mBounds.height < size.height) {
3853       GdkRectangle rect = DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect(
3854           0, mBounds.height, size.width, size.height - mBounds.height));
3855       gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
3856     }
3857   }
3858 
3859   mBounds.SizeTo(size);
3860 
3861   // Notify the GtkCompositorWidget of a ClientSizeChange
3862   if (mCompositorWidgetDelegate) {
3863     mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
3864   }
3865 
3866   // Gecko permits running nested event loops during processing of events,
3867   // GtkWindow callers of gtk_widget_size_allocate expect the signal
3868   // handlers to return sometime in the near future.
3869   mNeedsDispatchResized = true;
3870   NS_DispatchToCurrentThread(NewRunnableMethod(
3871       "nsWindow::MaybeDispatchResized", this, &nsWindow::MaybeDispatchResized));
3872 }
3873 
OnDeleteEvent()3874 void nsWindow::OnDeleteEvent() {
3875   if (mWidgetListener) mWidgetListener->RequestWindowClose(this);
3876 }
3877 
OnEnterNotifyEvent(GdkEventCrossing * aEvent)3878 void nsWindow::OnEnterNotifyEvent(GdkEventCrossing* aEvent) {
3879   // This skips NotifyVirtual and NotifyNonlinearVirtual enter notify events
3880   // when the pointer enters a child window.  If the destination window is a
3881   // Gecko window then we'll catch the corresponding event on that window,
3882   // but we won't notice when the pointer directly enters a foreign (plugin)
3883   // child window without passing over a visible portion of a Gecko window.
3884   if (aEvent->subwindow != nullptr) return;
3885 
3886   // Check before is_parent_ungrab_enter() as the button state may have
3887   // changed while a non-Gecko ancestor window had a pointer grab.
3888   DispatchMissedButtonReleases(aEvent);
3889 
3890   if (is_parent_ungrab_enter(aEvent)) {
3891     return;
3892   }
3893 
3894   WidgetMouseEvent event(true, eMouseEnterIntoWidget, this,
3895                          WidgetMouseEvent::eReal);
3896 
3897   event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
3898   event.AssignEventTime(GetWidgetEventTime(aEvent->time));
3899 
3900   LOG("OnEnterNotify");
3901 
3902   DispatchInputEvent(&event);
3903 }
3904 
3905 // XXX Is this the right test for embedding cases?
is_top_level_mouse_exit(GdkWindow * aWindow,GdkEventCrossing * aEvent)3906 static bool is_top_level_mouse_exit(GdkWindow* aWindow,
3907                                     GdkEventCrossing* aEvent) {
3908   auto x = gint(aEvent->x_root);
3909   auto y = gint(aEvent->y_root);
3910   GdkDevice* pointer = GdkGetPointer();
3911   GdkWindow* winAtPt = gdk_device_get_window_at_position(pointer, &x, &y);
3912   if (!winAtPt) return true;
3913   GdkWindow* topLevelAtPt = gdk_window_get_toplevel(winAtPt);
3914   GdkWindow* topLevelWidget = gdk_window_get_toplevel(aWindow);
3915   return topLevelAtPt != topLevelWidget;
3916 }
3917 
OnLeaveNotifyEvent(GdkEventCrossing * aEvent)3918 void nsWindow::OnLeaveNotifyEvent(GdkEventCrossing* aEvent) {
3919   // This ignores NotifyVirtual and NotifyNonlinearVirtual leave notify
3920   // events when the pointer leaves a child window.  If the destination
3921   // window is a Gecko window then we'll catch the corresponding event on
3922   // that window.
3923   //
3924   // XXXkt However, we will miss toplevel exits when the pointer directly
3925   // leaves a foreign (plugin) child window without passing over a visible
3926   // portion of a Gecko window.
3927   if (!mGdkWindow || aEvent->subwindow != nullptr) {
3928     return;
3929   }
3930 
3931   WidgetMouseEvent event(true, eMouseExitFromWidget, this,
3932                          WidgetMouseEvent::eReal);
3933 
3934   event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
3935   event.AssignEventTime(GetWidgetEventTime(aEvent->time));
3936 
3937   event.mExitFrom = Some(is_top_level_mouse_exit(mGdkWindow, aEvent)
3938                              ? WidgetMouseEvent::ePlatformTopLevel
3939                              : WidgetMouseEvent::ePlatformChild);
3940 
3941   LOG("OnLeaveNotify");
3942 
3943   DispatchInputEvent(&event);
3944 }
3945 
CheckResizerEdge(LayoutDeviceIntPoint aPoint,GdkWindowEdge & aOutEdge)3946 bool nsWindow::CheckResizerEdge(LayoutDeviceIntPoint aPoint,
3947                                 GdkWindowEdge& aOutEdge) {
3948   // We only need to handle resizers for PIP window.
3949   if (!mIsPIPWindow) {
3950     return false;
3951   }
3952 
3953   // Don't allow resizing maximized windows.
3954   if (mSizeState != nsSizeMode_Normal) {
3955     return false;
3956   }
3957 
3958 #define RESIZER_SIZE 15
3959   int resizerSize = RESIZER_SIZE * GdkCeiledScaleFactor();
3960   int topDist = aPoint.y;
3961   int leftDist = aPoint.x;
3962   int rightDist = mBounds.width - aPoint.x;
3963   int bottomDist = mBounds.height - aPoint.y;
3964 
3965   if (leftDist <= resizerSize && topDist <= resizerSize) {
3966     aOutEdge = GDK_WINDOW_EDGE_NORTH_WEST;
3967   } else if (rightDist <= resizerSize && topDist <= resizerSize) {
3968     aOutEdge = GDK_WINDOW_EDGE_NORTH_EAST;
3969   } else if (leftDist <= resizerSize && bottomDist <= resizerSize) {
3970     aOutEdge = GDK_WINDOW_EDGE_SOUTH_WEST;
3971   } else if (rightDist <= resizerSize && bottomDist <= resizerSize) {
3972     aOutEdge = GDK_WINDOW_EDGE_SOUTH_EAST;
3973   } else if (topDist <= resizerSize) {
3974     aOutEdge = GDK_WINDOW_EDGE_NORTH;
3975   } else if (leftDist <= resizerSize) {
3976     aOutEdge = GDK_WINDOW_EDGE_WEST;
3977   } else if (rightDist <= resizerSize) {
3978     aOutEdge = GDK_WINDOW_EDGE_EAST;
3979   } else if (bottomDist <= resizerSize) {
3980     aOutEdge = GDK_WINDOW_EDGE_SOUTH;
3981   } else {
3982     return false;
3983   }
3984   return true;
3985 }
3986 
3987 template <typename Event>
GetRefPoint(nsWindow * aWindow,Event * aEvent)3988 static LayoutDeviceIntPoint GetRefPoint(nsWindow* aWindow, Event* aEvent) {
3989   if (aEvent->window == aWindow->GetGdkWindow()) {
3990     // we are the window that the event happened on so no need for expensive
3991     // WidgetToScreenOffset
3992     return aWindow->GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
3993   }
3994   // XXX we're never quite sure which GdkWindow the event came from due to our
3995   // custom bubbling in scroll_event_cb(), so use ScreenToWidget to translate
3996   // the screen root coordinates into coordinates relative to this widget.
3997   return aWindow->GdkEventCoordsToDevicePixels(aEvent->x_root, aEvent->y_root) -
3998          aWindow->WidgetToScreenOffset();
3999 }
4000 
OnMotionNotifyEvent(GdkEventMotion * aEvent)4001 void nsWindow::OnMotionNotifyEvent(GdkEventMotion* aEvent) {
4002   if (!mGdkWindow) {
4003     return;
4004   }
4005 
4006   if (mWindowShouldStartDragging) {
4007     mWindowShouldStartDragging = false;
4008     // find the top-level window
4009     GdkWindow* gdk_window = gdk_window_get_toplevel(mGdkWindow);
4010     MOZ_ASSERT(gdk_window, "gdk_window_get_toplevel should not return null");
4011 
4012     bool canDrag = true;
4013     if (GdkIsX11Display()) {
4014       // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=789054
4015       // To avoid crashes disable double-click on WM without _NET_WM_MOVERESIZE.
4016       // See _should_perform_ewmh_drag() at gdkwindow-x11.c
4017       GdkScreen* screen = gdk_window_get_screen(gdk_window);
4018       GdkAtom atom = gdk_atom_intern("_NET_WM_MOVERESIZE", FALSE);
4019       if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
4020         canDrag = false;
4021       }
4022     }
4023 
4024     if (canDrag) {
4025       gdk_window_begin_move_drag(gdk_window, 1, aEvent->x_root, aEvent->y_root,
4026                                  aEvent->time);
4027       return;
4028     }
4029   }
4030 
4031   GdkWindowEdge edge;
4032   if (CheckResizerEdge(GetRefPoint(this, aEvent), edge)) {
4033     nsCursor cursor = eCursor_none;
4034     switch (edge) {
4035       case GDK_WINDOW_EDGE_NORTH:
4036         cursor = eCursor_n_resize;
4037         break;
4038       case GDK_WINDOW_EDGE_NORTH_WEST:
4039         cursor = eCursor_nw_resize;
4040         break;
4041       case GDK_WINDOW_EDGE_NORTH_EAST:
4042         cursor = eCursor_ne_resize;
4043         break;
4044       case GDK_WINDOW_EDGE_WEST:
4045         cursor = eCursor_w_resize;
4046         break;
4047       case GDK_WINDOW_EDGE_EAST:
4048         cursor = eCursor_e_resize;
4049         break;
4050       case GDK_WINDOW_EDGE_SOUTH:
4051         cursor = eCursor_s_resize;
4052         break;
4053       case GDK_WINDOW_EDGE_SOUTH_WEST:
4054         cursor = eCursor_sw_resize;
4055         break;
4056       case GDK_WINDOW_EDGE_SOUTH_EAST:
4057         cursor = eCursor_se_resize;
4058         break;
4059     }
4060     SetCursor(Cursor{cursor});
4061     return;
4062   }
4063 
4064   WidgetMouseEvent event(true, eMouseMove, this, WidgetMouseEvent::eReal);
4065 
4066   gdouble pressure = 0;
4067   gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
4068   // Sometime gdk generate 0 pressure value between normal values
4069   // We have to ignore that and use last valid value
4070   if (pressure) mLastMotionPressure = pressure;
4071   event.mPressure = mLastMotionPressure;
4072   event.mRefPoint = GetRefPoint(this, aEvent);
4073   event.AssignEventTime(GetWidgetEventTime(aEvent->time));
4074 
4075   KeymapWrapper::InitInputEvent(event, aEvent->state);
4076 
4077   DispatchInputEvent(&event);
4078 }
4079 
4080 // If the automatic pointer grab on ButtonPress has deactivated before
4081 // ButtonRelease, and the mouse button is released while the pointer is not
4082 // over any a Gecko window, then the ButtonRelease event will not be received.
4083 // (A similar situation exists when the pointer is grabbed with owner_events
4084 // True as the ButtonRelease may be received on a foreign [plugin] window).
4085 // Use this method to check for released buttons when the pointer returns to a
4086 // Gecko window.
DispatchMissedButtonReleases(GdkEventCrossing * aGdkEvent)4087 void nsWindow::DispatchMissedButtonReleases(GdkEventCrossing* aGdkEvent) {
4088   guint changed = aGdkEvent->state ^ gButtonState;
4089   // Only consider button releases.
4090   // (Ignore button presses that occurred outside Gecko.)
4091   guint released = changed & gButtonState;
4092   gButtonState = aGdkEvent->state;
4093 
4094   // Loop over each button, excluding mouse wheel buttons 4 and 5 for which
4095   // GDK ignores releases.
4096   for (guint buttonMask = GDK_BUTTON1_MASK; buttonMask <= GDK_BUTTON3_MASK;
4097        buttonMask <<= 1) {
4098     if (released & buttonMask) {
4099       int16_t buttonType;
4100       switch (buttonMask) {
4101         case GDK_BUTTON1_MASK:
4102           buttonType = MouseButton::ePrimary;
4103           break;
4104         case GDK_BUTTON2_MASK:
4105           buttonType = MouseButton::eMiddle;
4106           break;
4107         default:
4108           NS_ASSERTION(buttonMask == GDK_BUTTON3_MASK,
4109                        "Unexpected button mask");
4110           buttonType = MouseButton::eSecondary;
4111       }
4112 
4113       LOG("Synthesized button %u release", guint(buttonType + 1));
4114 
4115       // Dispatch a synthesized button up event to tell Gecko about the
4116       // change in state.  This event is marked as synthesized so that
4117       // it is not dispatched as a DOM event, because we don't know the
4118       // position, widget, modifiers, or time/order.
4119       WidgetMouseEvent synthEvent(true, eMouseUp, this,
4120                                   WidgetMouseEvent::eSynthesized);
4121       synthEvent.mButton = buttonType;
4122       DispatchInputEvent(&synthEvent);
4123     }
4124   }
4125 }
4126 
InitButtonEvent(WidgetMouseEvent & aEvent,GdkEventButton * aGdkEvent)4127 void nsWindow::InitButtonEvent(WidgetMouseEvent& aEvent,
4128                                GdkEventButton* aGdkEvent) {
4129   aEvent.mRefPoint = GetRefPoint(this, aGdkEvent);
4130 
4131   guint modifierState = aGdkEvent->state;
4132   // aEvent's state includes the button state from immediately before this
4133   // event.  If aEvent is a mousedown or mouseup event, we need to update
4134   // the button state.
4135   guint buttonMask = 0;
4136   switch (aGdkEvent->button) {
4137     case 1:
4138       buttonMask = GDK_BUTTON1_MASK;
4139       break;
4140     case 2:
4141       buttonMask = GDK_BUTTON2_MASK;
4142       break;
4143     case 3:
4144       buttonMask = GDK_BUTTON3_MASK;
4145       break;
4146   }
4147   if (aGdkEvent->type == GDK_BUTTON_RELEASE) {
4148     modifierState &= ~buttonMask;
4149   } else {
4150     modifierState |= buttonMask;
4151   }
4152 
4153   KeymapWrapper::InitInputEvent(aEvent, modifierState);
4154 
4155   aEvent.AssignEventTime(GetWidgetEventTime(aGdkEvent->time));
4156 
4157   switch (aGdkEvent->type) {
4158     case GDK_2BUTTON_PRESS:
4159       aEvent.mClickCount = 2;
4160       break;
4161     case GDK_3BUTTON_PRESS:
4162       aEvent.mClickCount = 3;
4163       break;
4164       // default is one click
4165     default:
4166       aEvent.mClickCount = 1;
4167   }
4168 }
4169 
ButtonMaskFromGDKButton(guint button)4170 static guint ButtonMaskFromGDKButton(guint button) {
4171   return GDK_BUTTON1_MASK << (button - 1);
4172 }
4173 
DispatchContextMenuEventFromMouseEvent(uint16_t domButton,GdkEventButton * aEvent)4174 void nsWindow::DispatchContextMenuEventFromMouseEvent(uint16_t domButton,
4175                                                       GdkEventButton* aEvent) {
4176   if (domButton == MouseButton::eSecondary && MOZ_LIKELY(!mIsDestroyed)) {
4177     WidgetMouseEvent contextMenuEvent(true, eContextMenu, this,
4178                                       WidgetMouseEvent::eReal);
4179     InitButtonEvent(contextMenuEvent, aEvent);
4180     contextMenuEvent.mPressure = mLastMotionPressure;
4181     DispatchInputEvent(&contextMenuEvent);
4182   }
4183 }
4184 
OnButtonPressEvent(GdkEventButton * aEvent)4185 void nsWindow::OnButtonPressEvent(GdkEventButton* aEvent) {
4186   LOG("Button %u press\n", aEvent->button);
4187 
4188   // If you double click in GDK, it will actually generate a second
4189   // GDK_BUTTON_PRESS before sending the GDK_2BUTTON_PRESS, and this is
4190   // different than the DOM spec.  GDK puts this in the queue
4191   // programatically, so it's safe to assume that if there's a
4192   // double click in the queue, it was generated so we can just drop
4193   // this click.
4194   GdkEvent* peekedEvent = gdk_event_peek();
4195   if (peekedEvent) {
4196     GdkEventType type = peekedEvent->any.type;
4197     gdk_event_free(peekedEvent);
4198     if (type == GDK_2BUTTON_PRESS || type == GDK_3BUTTON_PRESS) return;
4199   }
4200 
4201   nsWindow* containerWindow = GetContainerWindow();
4202   if (!gFocusWindow && containerWindow) {
4203     containerWindow->DispatchActivateEvent();
4204   }
4205 
4206   // check to see if we should rollup
4207   if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false)) return;
4208 
4209   // Check to see if the event is within our window's resize region
4210   GdkWindowEdge edge;
4211   if (CheckResizerEdge(GetRefPoint(this, aEvent), edge)) {
4212     gdk_window_begin_resize_drag(gtk_widget_get_window(mShell), edge,
4213                                  aEvent->button, aEvent->x_root, aEvent->y_root,
4214                                  aEvent->time);
4215     return;
4216   }
4217 
4218   gdouble pressure = 0;
4219   gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
4220   mLastMotionPressure = pressure;
4221 
4222   uint16_t domButton;
4223   switch (aEvent->button) {
4224     case 1:
4225       domButton = MouseButton::ePrimary;
4226       break;
4227     case 2:
4228       domButton = MouseButton::eMiddle;
4229       break;
4230     case 3:
4231       domButton = MouseButton::eSecondary;
4232       break;
4233     // These are mapped to horizontal scroll
4234     case 6:
4235     case 7:
4236       NS_WARNING("We're not supporting legacy horizontal scroll event");
4237       return;
4238     // Map buttons 8-9 to back/forward
4239     case 8:
4240       if (!Preferences::GetBool("mousebutton.4th.enabled", true)) {
4241         return;
4242       }
4243       DispatchCommandEvent(nsGkAtoms::Back);
4244       return;
4245     case 9:
4246       if (!Preferences::GetBool("mousebutton.5th.enabled", true)) {
4247         return;
4248       }
4249       DispatchCommandEvent(nsGkAtoms::Forward);
4250       return;
4251     default:
4252       return;
4253   }
4254 
4255   gButtonState |= ButtonMaskFromGDKButton(aEvent->button);
4256 
4257   WidgetMouseEvent event(true, eMouseDown, this, WidgetMouseEvent::eReal);
4258   event.mButton = domButton;
4259   InitButtonEvent(event, aEvent);
4260   event.mPressure = mLastMotionPressure;
4261 
4262   nsIWidget::ContentAndAPZEventStatus eventStatus = DispatchInputEvent(&event);
4263 
4264   LayoutDeviceIntPoint refPoint =
4265       GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
4266   if ((mIsWaylandPanelWindow ||
4267        mDraggableRegion.Contains(refPoint.x, refPoint.y)) &&
4268       domButton == MouseButton::ePrimary &&
4269       eventStatus.mContentStatus != nsEventStatus_eConsumeNoDefault) {
4270     mWindowShouldStartDragging = true;
4271   }
4272 
4273   // right menu click on linux should also pop up a context menu
4274   if (!StaticPrefs::ui_context_menus_after_mouseup() &&
4275       eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
4276     DispatchContextMenuEventFromMouseEvent(domButton, aEvent);
4277   }
4278 }
4279 
OnButtonReleaseEvent(GdkEventButton * aEvent)4280 void nsWindow::OnButtonReleaseEvent(GdkEventButton* aEvent) {
4281   LOG("Button %u release\n", aEvent->button);
4282   if (!mGdkWindow) {
4283     return;
4284   }
4285 
4286   if (mWindowShouldStartDragging) {
4287     mWindowShouldStartDragging = false;
4288   }
4289 
4290   uint16_t domButton;
4291   switch (aEvent->button) {
4292     case 1:
4293       domButton = MouseButton::ePrimary;
4294       break;
4295     case 2:
4296       domButton = MouseButton::eMiddle;
4297       break;
4298     case 3:
4299       domButton = MouseButton::eSecondary;
4300       break;
4301     default:
4302       return;
4303   }
4304 
4305   gButtonState &= ~ButtonMaskFromGDKButton(aEvent->button);
4306 
4307   WidgetMouseEvent event(true, eMouseUp, this, WidgetMouseEvent::eReal);
4308   event.mButton = domButton;
4309   InitButtonEvent(event, aEvent);
4310   gdouble pressure = 0;
4311   gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
4312   event.mPressure = pressure ? (float)pressure : (float)mLastMotionPressure;
4313 
4314   // The mRefPoint is manipulated in DispatchInputEvent, we're saving it
4315   // to use it for the doubleclick position check.
4316   LayoutDeviceIntPoint pos = event.mRefPoint;
4317 
4318   nsIWidget::ContentAndAPZEventStatus eventStatus = DispatchInputEvent(&event);
4319 
4320   bool defaultPrevented =
4321       (eventStatus.mContentStatus == nsEventStatus_eConsumeNoDefault);
4322   // Check if mouse position in titlebar and doubleclick happened to
4323   // trigger restore/maximize.
4324   if (!defaultPrevented && mDrawInTitlebar &&
4325       event.mButton == MouseButton::ePrimary && event.mClickCount == 2 &&
4326       mDraggableRegion.Contains(pos.x, pos.y)) {
4327     if (mSizeState == nsSizeMode_Maximized) {
4328       SetSizeMode(nsSizeMode_Normal);
4329     } else {
4330       SetSizeMode(nsSizeMode_Maximized);
4331     }
4332   }
4333   mLastMotionPressure = pressure;
4334 
4335   // right menu click on linux should also pop up a context menu
4336   if (StaticPrefs::ui_context_menus_after_mouseup() &&
4337       eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
4338     DispatchContextMenuEventFromMouseEvent(domButton, aEvent);
4339   }
4340 
4341   // Open window manager menu on PIP window to allow user
4342   // to place it on top / all workspaces.
4343   if (mIsPIPWindow && aEvent->button == 3) {
4344     static auto sGdkWindowShowWindowMenu =
4345         (gboolean(*)(GdkWindow * window, GdkEvent*))
4346             dlsym(RTLD_DEFAULT, "gdk_window_show_window_menu");
4347     if (sGdkWindowShowWindowMenu) {
4348       sGdkWindowShowWindowMenu(mGdkWindow, (GdkEvent*)aEvent);
4349     }
4350   }
4351 }
4352 
OnContainerFocusInEvent(GdkEventFocus * aEvent)4353 void nsWindow::OnContainerFocusInEvent(GdkEventFocus* aEvent) {
4354   LOG("OnContainerFocusInEvent");
4355 
4356   // Unset the urgency hint, if possible
4357   GtkWidget* top_window = GetToplevelWidget();
4358   if (top_window && (gtk_widget_get_visible(top_window))) {
4359     SetUrgencyHint(top_window, false);
4360   }
4361 
4362   // Return if being called within SetFocus because the focus manager
4363   // already knows that the window is active.
4364   if (gBlockActivateEvent) {
4365     LOG("activated notification is blocked");
4366     return;
4367   }
4368 
4369   // If keyboard input will be accepted, the focus manager will call
4370   // SetFocus to set the correct window.
4371   gFocusWindow = nullptr;
4372 
4373   DispatchActivateEvent();
4374 
4375   if (!gFocusWindow) {
4376     // We don't really have a window for dispatching key events, but
4377     // setting a non-nullptr value here prevents OnButtonPressEvent() from
4378     // dispatching an activation notification if the widget is already
4379     // active.
4380     gFocusWindow = this;
4381   }
4382 
4383   LOG("Events sent from focus in event");
4384 }
4385 
OnContainerFocusOutEvent(GdkEventFocus * aEvent)4386 void nsWindow::OnContainerFocusOutEvent(GdkEventFocus* aEvent) {
4387   LOG("OnContainerFocusOutEvent");
4388 
4389   if (mWindowType == eWindowType_toplevel ||
4390       mWindowType == eWindowType_dialog) {
4391     nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID);
4392     nsCOMPtr<nsIDragSession> dragSession;
4393     dragService->GetCurrentSession(getter_AddRefs(dragSession));
4394 
4395     // Rollup popups when a window is focused out unless a drag is occurring.
4396     // This check is because drags grab the keyboard and cause a focus out on
4397     // versions of GTK before 2.18.
4398     bool shouldRollup = !dragSession;
4399     if (!shouldRollup) {
4400       // we also roll up when a drag is from a different application
4401       nsCOMPtr<nsINode> sourceNode;
4402       dragSession->GetSourceNode(getter_AddRefs(sourceNode));
4403       shouldRollup = (sourceNode == nullptr);
4404     }
4405 
4406     if (shouldRollup) {
4407       CheckForRollup(0, 0, false, true);
4408     }
4409   }
4410 
4411   if (gFocusWindow) {
4412     RefPtr<nsWindow> kungFuDeathGrip = gFocusWindow;
4413     if (gFocusWindow->mIMContext) {
4414       gFocusWindow->mIMContext->OnBlurWindow(gFocusWindow);
4415     }
4416     gFocusWindow = nullptr;
4417   }
4418 
4419   DispatchDeactivateEvent();
4420 
4421   if (IsChromeWindowTitlebar()) {
4422     // DispatchDeactivateEvent() ultimately results in a call to
4423     // BrowsingContext::SetIsActiveBrowserWindow(), which resets
4424     // the state.  We call UpdateMozWindowActive() to keep it in
4425     // sync with GDK_WINDOW_STATE_FOCUSED.
4426     UpdateMozWindowActive();
4427   }
4428 
4429   LOG("Done with container focus out");
4430 }
4431 
DispatchCommandEvent(nsAtom * aCommand)4432 bool nsWindow::DispatchCommandEvent(nsAtom* aCommand) {
4433   nsEventStatus status;
4434   WidgetCommandEvent appCommandEvent(true, aCommand, this);
4435   DispatchEvent(&appCommandEvent, status);
4436   return TRUE;
4437 }
4438 
DispatchContentCommandEvent(EventMessage aMsg)4439 bool nsWindow::DispatchContentCommandEvent(EventMessage aMsg) {
4440   nsEventStatus status;
4441   WidgetContentCommandEvent event(true, aMsg, this);
4442   DispatchEvent(&event, status);
4443   return TRUE;
4444 }
4445 
GetWidgetEventTime(guint32 aEventTime)4446 WidgetEventTime nsWindow::GetWidgetEventTime(guint32 aEventTime) {
4447   return WidgetEventTime(aEventTime, GetEventTimeStamp(aEventTime));
4448 }
4449 
GetEventTimeStamp(guint32 aEventTime)4450 TimeStamp nsWindow::GetEventTimeStamp(guint32 aEventTime) {
4451   if (MOZ_UNLIKELY(!mGdkWindow)) {
4452     // nsWindow has been Destroy()ed.
4453     return TimeStamp::Now();
4454   }
4455   if (aEventTime == 0) {
4456     // Some X11 and GDK events may be received with a time of 0 to indicate
4457     // that they are synthetic events. Some input method editors do this.
4458     // In this case too, just return the current timestamp.
4459     return TimeStamp::Now();
4460   }
4461 
4462   TimeStamp eventTimeStamp;
4463 
4464   if (GdkIsWaylandDisplay()) {
4465     // Wayland compositors use monotonic time to set timestamps.
4466     int64_t timestampTime = g_get_monotonic_time() / 1000;
4467     guint32 refTimeTruncated = guint32(timestampTime);
4468 
4469     timestampTime -= refTimeTruncated - aEventTime;
4470     int64_t tick =
4471         BaseTimeDurationPlatformUtils::TicksFromMilliseconds(timestampTime);
4472     eventTimeStamp = TimeStamp::FromSystemTime(tick);
4473   } else {
4474     CurrentX11TimeGetter* getCurrentTime = GetCurrentTimeGetter();
4475     MOZ_ASSERT(getCurrentTime,
4476                "Null current time getter despite having a window");
4477     eventTimeStamp =
4478         TimeConverter().GetTimeStampFromSystemTime(aEventTime, *getCurrentTime);
4479   }
4480   return eventTimeStamp;
4481 }
4482 
GetCurrentTimeGetter()4483 mozilla::CurrentX11TimeGetter* nsWindow::GetCurrentTimeGetter() {
4484   MOZ_ASSERT(mGdkWindow, "Expected mGdkWindow to be set");
4485   if (MOZ_UNLIKELY(!mCurrentTimeGetter)) {
4486     mCurrentTimeGetter = MakeUnique<CurrentX11TimeGetter>(mGdkWindow);
4487   }
4488   return mCurrentTimeGetter.get();
4489 }
4490 
OnKeyPressEvent(GdkEventKey * aEvent)4491 gboolean nsWindow::OnKeyPressEvent(GdkEventKey* aEvent) {
4492   LOG("OnKeyPressEvent");
4493 
4494   RefPtr<nsWindow> self(this);
4495   KeymapWrapper::HandleKeyPressEvent(self, aEvent);
4496   return TRUE;
4497 }
4498 
OnKeyReleaseEvent(GdkEventKey * aEvent)4499 gboolean nsWindow::OnKeyReleaseEvent(GdkEventKey* aEvent) {
4500   LOG("OnKeyReleaseEvent");
4501 
4502   RefPtr<nsWindow> self(this);
4503   if (NS_WARN_IF(!KeymapWrapper::HandleKeyReleaseEvent(self, aEvent))) {
4504     return FALSE;
4505   }
4506   return TRUE;
4507 }
4508 
OnScrollEvent(GdkEventScroll * aEvent)4509 void nsWindow::OnScrollEvent(GdkEventScroll* aEvent) {
4510   // check to see if we should rollup
4511   if (CheckForRollup(aEvent->x_root, aEvent->y_root, true, false)) {
4512     return;
4513   }
4514 
4515   // check for duplicate legacy scroll event, see GNOME bug 726878
4516   if (aEvent->direction != GDK_SCROLL_SMOOTH &&
4517       mLastScrollEventTime == aEvent->time) {
4518     LOG("[%d] duplicate legacy scroll event %d\n", aEvent->time,
4519         aEvent->direction);
4520     return;
4521   }
4522   WidgetWheelEvent wheelEvent(true, eWheel, this);
4523   wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_LINE;
4524   switch (aEvent->direction) {
4525     case GDK_SCROLL_SMOOTH: {
4526       // As of GTK 3.4, all directional scroll events are provided by
4527       // the GDK_SCROLL_SMOOTH direction on XInput2 and Wayland devices.
4528       mLastScrollEventTime = aEvent->time;
4529 
4530       // Special handling for touchpads to support flings
4531       // (also known as kinetic/inertial/momentum scrolling)
4532       GdkDevice* device = gdk_event_get_source_device((GdkEvent*)aEvent);
4533       GdkInputSource source = gdk_device_get_source(device);
4534       if (source == GDK_SOURCE_TOUCHSCREEN || source == GDK_SOURCE_TOUCHPAD) {
4535         if (StaticPrefs::apz_gtk_kinetic_scroll_enabled() &&
4536             gtk_check_version(3, 20, 0) == nullptr) {
4537           static auto sGdkEventIsScrollStopEvent =
4538               (gboolean(*)(const GdkEvent*))dlsym(
4539                   RTLD_DEFAULT, "gdk_event_is_scroll_stop_event");
4540 
4541           LOG("[%d] pan smooth event dx=%f dy=%f inprogress=%d\n", aEvent->time,
4542               aEvent->delta_x, aEvent->delta_y, mPanInProgress);
4543           PanGestureInput::PanGestureType eventType =
4544               PanGestureInput::PANGESTURE_PAN;
4545           if (sGdkEventIsScrollStopEvent((GdkEvent*)aEvent)) {
4546             eventType = PanGestureInput::PANGESTURE_END;
4547             mPanInProgress = false;
4548           } else if (!mPanInProgress) {
4549             eventType = PanGestureInput::PANGESTURE_START;
4550             mPanInProgress = true;
4551           }
4552 
4553           const bool isPageMode =
4554               StaticPrefs::apz_gtk_kinetic_scroll_delta_mode() != 2;
4555           const double multiplier =
4556               isPageMode
4557                   ? StaticPrefs::
4558                         apz_gtk_kinetic_scroll_page_delta_mode_multiplier()
4559                   : StaticPrefs::
4560                             apz_gtk_kinetic_scroll_pixel_delta_mode_multiplier() *
4561                         FractionalScaleFactor();
4562           ScreenPoint deltas(float(aEvent->delta_x * multiplier),
4563                              float(aEvent->delta_y * multiplier));
4564 
4565           LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent);
4566           PanGestureInput panEvent(
4567               eventType, aEvent->time, GetEventTimeStamp(aEvent->time),
4568               ScreenPoint(touchPoint.x, touchPoint.y), deltas,
4569               KeymapWrapper::ComputeKeyModifiers(aEvent->state));
4570           panEvent.mDeltaType = isPageMode ? PanGestureInput::PANDELTA_PAGE
4571                                            : PanGestureInput::PANDELTA_PIXEL;
4572           panEvent.mSimulateMomentum = true;
4573 
4574           DispatchPanGestureInput(panEvent);
4575 
4576           return;
4577         }
4578 
4579         // Older GTK doesn't support stop events, so we can't support fling
4580         wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSLY;
4581       }
4582 
4583       // TODO - use a more appropriate scrolling unit than lines.
4584       // Multiply event deltas by 3 to emulate legacy behaviour.
4585       wheelEvent.mDeltaX = aEvent->delta_x * 3;
4586       wheelEvent.mDeltaY = aEvent->delta_y * 3;
4587       wheelEvent.mWheelTicksX = aEvent->delta_x;
4588       wheelEvent.mWheelTicksY = aEvent->delta_y;
4589       wheelEvent.mIsNoLineOrPageDelta = true;
4590 
4591       break;
4592     }
4593     case GDK_SCROLL_UP:
4594       wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = -3;
4595       wheelEvent.mWheelTicksY = -1;
4596       break;
4597     case GDK_SCROLL_DOWN:
4598       wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = 3;
4599       wheelEvent.mWheelTicksY = 1;
4600       break;
4601     case GDK_SCROLL_LEFT:
4602       wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = -1;
4603       wheelEvent.mWheelTicksX = -1;
4604       break;
4605     case GDK_SCROLL_RIGHT:
4606       wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = 1;
4607       wheelEvent.mWheelTicksX = 1;
4608       break;
4609   }
4610 
4611   wheelEvent.mRefPoint = GetRefPoint(this, aEvent);
4612 
4613   KeymapWrapper::InitInputEvent(wheelEvent, aEvent->state);
4614 
4615   wheelEvent.AssignEventTime(GetWidgetEventTime(aEvent->time));
4616 
4617   DispatchInputEvent(&wheelEvent);
4618 }
4619 
OnWindowStateEvent(GtkWidget * aWidget,GdkEventWindowState * aEvent)4620 void nsWindow::OnWindowStateEvent(GtkWidget* aWidget,
4621                                   GdkEventWindowState* aEvent) {
4622   LOG("nsWindow::OnWindowStateEvent for %p changed 0x%x new_window_state "
4623       "0x%x\n",
4624       aWidget, aEvent->changed_mask, aEvent->new_window_state);
4625 
4626   if (IS_MOZ_CONTAINER(aWidget)) {
4627     // This event is notifying the container widget of changes to the
4628     // toplevel window.  Just detect changes affecting whether windows are
4629     // viewable.
4630     //
4631     // (A visibility notify event is sent to each window that becomes
4632     // viewable when the toplevel is mapped, but we can't rely on that for
4633     // setting mHasMappedToplevel because these toplevel window state
4634     // events are asynchronous.  The windows in the hierarchy now may not
4635     // be the same windows as when the toplevel was mapped, so they may
4636     // not get VisibilityNotify events.)
4637     bool mapped = !(aEvent->new_window_state &
4638                     (GDK_WINDOW_STATE_ICONIFIED | GDK_WINDOW_STATE_WITHDRAWN));
4639     if (mHasMappedToplevel != mapped) {
4640       SetHasMappedToplevel(mapped);
4641     }
4642     LOG("\tquick return because IS_MOZ_CONTAINER(aWidget) is true\n");
4643     return;
4644   }
4645   // else the widget is a shell widget.
4646 
4647   // The block below is a bit evil.
4648   //
4649   // When a window is resized before it is shown, gtk_window_resize() delays
4650   // resizes until the window is shown.  If gtk_window_state_event() sees a
4651   // GDK_WINDOW_STATE_MAXIMIZED change [1] before the window is shown, then
4652   // gtk_window_compute_configure_request_size() ignores the values from the
4653   // resize [2].  See bug 1449166 for an example of how this could happen.
4654   //
4655   // [1] https://gitlab.gnome.org/GNOME/gtk/blob/3.22.30/gtk/gtkwindow.c#L7967
4656   // [2] https://gitlab.gnome.org/GNOME/gtk/blob/3.22.30/gtk/gtkwindow.c#L9377
4657   //
4658   // In order to provide a sensible size for the window when the user exits
4659   // maximized state, we hide the GDK_WINDOW_STATE_MAXIMIZED change from
4660   // gtk_window_state_event() so as to trick GTK into using the values from
4661   // gtk_window_resize() in its configure request.
4662   //
4663   // We instead notify gtk_window_state_event() of the maximized state change
4664   // once the window is shown.
4665   //
4666   // See https://gitlab.gnome.org/GNOME/gtk/issues/1044
4667   //
4668   // This may be fixed in Gtk 3.24+ but some DE still have this issue
4669   // (Bug 1624199) so let's remove it for Wayland only.
4670   if (GdkIsX11Display()) {
4671     if (!mIsShown) {
4672       aEvent->changed_mask = static_cast<GdkWindowState>(
4673           aEvent->changed_mask & ~GDK_WINDOW_STATE_MAXIMIZED);
4674     } else if (aEvent->changed_mask & GDK_WINDOW_STATE_WITHDRAWN &&
4675                aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
4676       aEvent->changed_mask = static_cast<GdkWindowState>(
4677           aEvent->changed_mask | GDK_WINDOW_STATE_MAXIMIZED);
4678     }
4679   }
4680 
4681   // This is a workaround for https://gitlab.gnome.org/GNOME/gtk/issues/1395
4682   // Gtk+ controls window active appearance by window-state-event signal.
4683   if (IsChromeWindowTitlebar() &&
4684       (aEvent->changed_mask & GDK_WINDOW_STATE_FOCUSED)) {
4685     // Emulate what Gtk+ does at gtk_window_state_event().
4686     // We can't check GTK_STATE_FLAG_BACKDROP directly as it's set by Gtk+
4687     // *after* this window-state-event handler.
4688     mTitlebarBackdropState =
4689         !(aEvent->new_window_state & GDK_WINDOW_STATE_FOCUSED);
4690 
4691     // keep IsActiveBrowserWindow in sync with GDK_WINDOW_STATE_FOCUSED
4692     UpdateMozWindowActive();
4693 
4694     ForceTitlebarRedraw();
4695   }
4696 
4697   // We don't care about anything but changes in the maximized/icon/fullscreen
4698   // states but we need a workaround for bug in Wayland:
4699   // https://gitlab.gnome.org/GNOME/gtk/issues/67
4700   // Under wayland the gtk_window_iconify implementation does NOT synthetize
4701   // window_state_event where the GDK_WINDOW_STATE_ICONIFIED is set.
4702   // During restore we  won't get aEvent->changed_mask with
4703   // the GDK_WINDOW_STATE_ICONIFIED so to detect that change we use the stored
4704   // mSizeState and obtaining a focus.
4705   bool waylandWasIconified =
4706       (GdkIsWaylandDisplay() &&
4707        aEvent->changed_mask & GDK_WINDOW_STATE_FOCUSED &&
4708        aEvent->new_window_state & GDK_WINDOW_STATE_FOCUSED &&
4709        mSizeState == nsSizeMode_Minimized);
4710   if (!waylandWasIconified &&
4711       (aEvent->changed_mask &
4712        (GDK_WINDOW_STATE_ICONIFIED | GDK_WINDOW_STATE_MAXIMIZED |
4713         GDK_WINDOW_STATE_TILED | GDK_WINDOW_STATE_FULLSCREEN)) == 0) {
4714     LOG("\tearly return because no interesting bits changed\n");
4715     return;
4716   }
4717 
4718   if (aEvent->new_window_state & GDK_WINDOW_STATE_ICONIFIED) {
4719     LOG("\tIconified\n");
4720     mSizeState = nsSizeMode_Minimized;
4721 #ifdef ACCESSIBILITY
4722     DispatchMinimizeEventAccessible();
4723 #endif  // ACCESSIBILITY
4724   } else if (aEvent->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) {
4725     LOG("\tFullscreen\n");
4726     mSizeState = nsSizeMode_Fullscreen;
4727   } else if (aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
4728     LOG("\tMaximized\n");
4729     mSizeState = nsSizeMode_Maximized;
4730 #ifdef ACCESSIBILITY
4731     DispatchMaximizeEventAccessible();
4732 #endif  // ACCESSIBILITY
4733   } else {
4734     LOG("\tNormal\n");
4735     mSizeState = nsSizeMode_Normal;
4736 #ifdef ACCESSIBILITY
4737     DispatchRestoreEventAccessible();
4738 #endif  // ACCESSIBILITY
4739   }
4740 
4741   if (aEvent->new_window_state & GDK_WINDOW_STATE_TILED) {
4742     LOG("\tTiled\n");
4743     mIsTiled = true;
4744   } else {
4745     LOG("\tNot tiled\n");
4746     mIsTiled = false;
4747   }
4748 
4749   if (mWidgetListener) {
4750     mWidgetListener->SizeModeChanged(mSizeState);
4751     if (aEvent->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
4752       mWidgetListener->FullscreenChanged(aEvent->new_window_state &
4753                                          GDK_WINDOW_STATE_FULLSCREEN);
4754     }
4755   }
4756 
4757   if (mDrawInTitlebar && mTransparencyBitmapForTitlebar) {
4758     if (mSizeState == nsSizeMode_Normal && !mIsTiled) {
4759       UpdateTitlebarTransparencyBitmap();
4760     } else {
4761       ClearTransparencyBitmap();
4762     }
4763   }
4764 }
4765 
OnDPIChanged()4766 void nsWindow::OnDPIChanged() {
4767   // Update menu's font size etc.
4768   // This affects style / layout because it affects system font sizes.
4769   if (mWidgetListener) {
4770     if (PresShell* presShell = mWidgetListener->GetPresShell()) {
4771       presShell->BackingScaleFactorChanged();
4772     }
4773     mWidgetListener->UIResolutionChanged();
4774   }
4775   NotifyThemeChanged(ThemeChangeKind::StyleAndLayout);
4776 }
4777 
OnCheckResize()4778 void nsWindow::OnCheckResize() { mPendingConfigures++; }
4779 
OnCompositedChanged()4780 void nsWindow::OnCompositedChanged() {
4781   // Update CSD after the change in alpha visibility. This only affects
4782   // system metrics, not other theme shenanigans.
4783   NotifyThemeChanged(ThemeChangeKind::MediaQueriesOnly);
4784   mCompositedScreen = gdk_screen_is_composited(gdk_screen_get_default());
4785 }
4786 
OnScaleChanged()4787 void nsWindow::OnScaleChanged() {
4788   // Force scale factor recalculation
4789   if (!mGdkWindow) {
4790     mWindowScaleFactorChanged = true;
4791     return;
4792   }
4793 
4794   // Gtk supply us sometimes with doubled events so stay calm in such case.
4795   if (gdk_window_get_scale_factor(mGdkWindow) == mWindowScaleFactor) {
4796     return;
4797   }
4798 
4799   // We pause compositor to avoid rendering of obsoleted remote content which
4800   // produces flickering.
4801   // Re-enable compositor again when remote content is updated or
4802   // timeout happens.
4803   PauseCompositor();
4804 
4805   // Force scale factor recalculation
4806   mWindowScaleFactorChanged = true;
4807 
4808   GtkAllocation allocation;
4809   gtk_widget_get_allocation(GTK_WIDGET(mContainer), &allocation);
4810   LayoutDeviceIntSize size = GdkRectToDevicePixels(allocation).Size();
4811   mBoundsAreValid = true;
4812   mBounds.SizeTo(size);
4813 
4814   if (mWidgetListener) {
4815     if (PresShell* presShell = mWidgetListener->GetPresShell()) {
4816       presShell->BackingScaleFactorChanged();
4817     }
4818   }
4819   // This affects style / layout because it affects system font sizes.
4820   // Update menu's font size etc.
4821   NotifyThemeChanged(ThemeChangeKind::StyleAndLayout);
4822 
4823   DispatchResized();
4824 
4825   if (mCompositorWidgetDelegate) {
4826     mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
4827   }
4828 
4829   if (mCursor.IsCustom()) {
4830     mUpdateCursor = true;
4831     SetCursor(Cursor{mCursor});
4832   }
4833 }
4834 
DispatchDragEvent(EventMessage aMsg,const LayoutDeviceIntPoint & aRefPoint,guint aTime)4835 void nsWindow::DispatchDragEvent(EventMessage aMsg,
4836                                  const LayoutDeviceIntPoint& aRefPoint,
4837                                  guint aTime) {
4838   WidgetDragEvent event(true, aMsg, this);
4839 
4840   InitDragEvent(event);
4841 
4842   event.mRefPoint = aRefPoint;
4843   event.AssignEventTime(GetWidgetEventTime(aTime));
4844 
4845   DispatchInputEvent(&event);
4846 }
4847 
OnDragDataReceivedEvent(GtkWidget * aWidget,GdkDragContext * aDragContext,gint aX,gint aY,GtkSelectionData * aSelectionData,guint aInfo,guint aTime,gpointer aData)4848 void nsWindow::OnDragDataReceivedEvent(GtkWidget* aWidget,
4849                                        GdkDragContext* aDragContext, gint aX,
4850                                        gint aY,
4851                                        GtkSelectionData* aSelectionData,
4852                                        guint aInfo, guint aTime,
4853                                        gpointer aData) {
4854   LOGDRAG("nsWindow::OnDragDataReceived(%p)\n", (void*)this);
4855 
4856   RefPtr<nsDragService> dragService = nsDragService::GetInstance();
4857   dragService->TargetDataReceived(aWidget, aDragContext, aX, aY, aSelectionData,
4858                                   aInfo, aTime);
4859 }
4860 
GetTransientForWindowIfPopup()4861 nsWindow* nsWindow::GetTransientForWindowIfPopup() {
4862   if (mWindowType != eWindowType_popup) {
4863     return nullptr;
4864   }
4865   GtkWindow* toplevel = gtk_window_get_transient_for(GTK_WINDOW(mShell));
4866   if (toplevel) {
4867     return get_window_for_gtk_widget(GTK_WIDGET(toplevel));
4868   }
4869   return nullptr;
4870 }
4871 
IsHandlingTouchSequence(GdkEventSequence * aSequence)4872 bool nsWindow::IsHandlingTouchSequence(GdkEventSequence* aSequence) {
4873   return mHandleTouchEvent && mTouches.Contains(aSequence);
4874 }
4875 
OnTouchpadPinchEvent(GdkEventTouchpadPinch * aEvent)4876 gboolean nsWindow::OnTouchpadPinchEvent(GdkEventTouchpadPinch* aEvent) {
4877   if (!StaticPrefs::apz_gtk_touchpad_pinch_enabled()) {
4878     return TRUE;
4879   }
4880   // Do not respond to pinch gestures involving more than two fingers
4881   // unless specifically preffed on. These are sometimes hooked up to other
4882   // actions at the desktop environment level and having the browser also
4883   // pinch can be undesirable.
4884   if (aEvent->n_fingers > 2 &&
4885       !StaticPrefs::apz_gtk_touchpad_pinch_three_fingers_enabled()) {
4886     return FALSE;
4887   }
4888   auto pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
4889   ScreenCoord currentSpan;
4890   ScreenCoord previousSpan;
4891 
4892   switch (aEvent->phase) {
4893     case GDK_TOUCHPAD_GESTURE_PHASE_BEGIN:
4894       pinchGestureType = PinchGestureInput::PINCHGESTURE_START;
4895       currentSpan = aEvent->scale;
4896 
4897       // Assign PreviousSpan --> 0.999 to make mDeltaY field of the
4898       // WidgetWheelEvent that this PinchGestureInput event will be converted
4899       // to not equal Zero as our discussion because we observed that the
4900       // scale of the PHASE_BEGIN event is 1.
4901       previousSpan = 0.999;
4902       break;
4903 
4904     case GDK_TOUCHPAD_GESTURE_PHASE_UPDATE:
4905       pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
4906       if (aEvent->scale == mLastPinchEventSpan) {
4907         return FALSE;
4908       }
4909       currentSpan = aEvent->scale;
4910       previousSpan = mLastPinchEventSpan;
4911       break;
4912 
4913     case GDK_TOUCHPAD_GESTURE_PHASE_END:
4914       pinchGestureType = PinchGestureInput::PINCHGESTURE_END;
4915       currentSpan = aEvent->scale;
4916       previousSpan = mLastPinchEventSpan;
4917       break;
4918 
4919     default:
4920       return FALSE;
4921   }
4922 
4923   LayoutDeviceIntPoint touchpadPoint = GetRefPoint(this, aEvent);
4924   PinchGestureInput event(
4925       pinchGestureType, PinchGestureInput::TRACKPAD, aEvent->time,
4926       GetEventTimeStamp(aEvent->time), ExternalPoint(0, 0),
4927       ScreenPoint(touchpadPoint.x, touchpadPoint.y),
4928       100.0 * ((aEvent->phase == GDK_TOUCHPAD_GESTURE_PHASE_END)
4929                    ? ScreenCoord(1.f)
4930                    : currentSpan),
4931       100.0 * ((aEvent->phase == GDK_TOUCHPAD_GESTURE_PHASE_END)
4932                    ? ScreenCoord(1.f)
4933                    : previousSpan),
4934       KeymapWrapper::ComputeKeyModifiers(aEvent->state));
4935 
4936   if (!event.SetLineOrPageDeltaY(this)) {
4937     return FALSE;
4938   }
4939 
4940   mLastPinchEventSpan = aEvent->scale;
4941   DispatchPinchGestureInput(event);
4942   return TRUE;
4943 }
4944 
OnTouchEvent(GdkEventTouch * aEvent)4945 gboolean nsWindow::OnTouchEvent(GdkEventTouch* aEvent) {
4946   LOG("OnTouchEvent: x=%f y=%f type=%d\n", aEvent->x, aEvent->y, aEvent->type);
4947   if (!mHandleTouchEvent) {
4948     // If a popup window was spawned (e.g. as the result of a long-press)
4949     // and touch events got diverted to that window within a touch sequence,
4950     // ensure the touch event gets sent to the original window instead. We
4951     // keep the checks here very conservative so that we only redirect
4952     // events in this specific scenario.
4953     nsWindow* targetWindow = GetTransientForWindowIfPopup();
4954     if (targetWindow &&
4955         targetWindow->IsHandlingTouchSequence(aEvent->sequence)) {
4956       return targetWindow->OnTouchEvent(aEvent);
4957     }
4958 
4959     return FALSE;
4960   }
4961 
4962   EventMessage msg;
4963   switch (aEvent->type) {
4964     case GDK_TOUCH_BEGIN:
4965       // check to see if we should rollup
4966       if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false)) {
4967         return FALSE;
4968       }
4969       msg = eTouchStart;
4970       break;
4971     case GDK_TOUCH_UPDATE:
4972       msg = eTouchMove;
4973       // Start dragging when motion events happens in the dragging area
4974       if (mWindowShouldStartDragging) {
4975         mWindowShouldStartDragging = false;
4976         GdkWindow* gdk_window = gdk_window_get_toplevel(mGdkWindow);
4977         MOZ_ASSERT(gdk_window,
4978                    "gdk_window_get_toplevel should not return null");
4979 
4980         LOG("  start window dragging window\n");
4981         gdk_window_begin_move_drag(gdk_window, 1, aEvent->x_root,
4982                                    aEvent->y_root, aEvent->time);
4983 
4984         // Cancel the event sequence. gdk will steal all subsequent events
4985         // (including TOUCH_END).
4986         msg = eTouchCancel;
4987       }
4988       break;
4989     case GDK_TOUCH_END:
4990       msg = eTouchEnd;
4991       if (mWindowShouldStartDragging) {
4992         LOG("  end of window dragging window\n");
4993         mWindowShouldStartDragging = false;
4994       }
4995       break;
4996     case GDK_TOUCH_CANCEL:
4997       msg = eTouchCancel;
4998       break;
4999     default:
5000       return FALSE;
5001   }
5002 
5003   LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent);
5004 
5005   int32_t id;
5006   RefPtr<dom::Touch> touch;
5007   if (mTouches.Remove(aEvent->sequence, getter_AddRefs(touch))) {
5008     id = touch->mIdentifier;
5009   } else {
5010     id = ++gLastTouchID & 0x7FFFFFFF;
5011   }
5012 
5013   touch =
5014       new dom::Touch(id, touchPoint, LayoutDeviceIntPoint(1, 1), 0.0f, 0.0f);
5015 
5016   WidgetTouchEvent event(true, msg, this);
5017   KeymapWrapper::InitInputEvent(event, aEvent->state);
5018   event.mTime = aEvent->time;
5019 
5020   if (msg == eTouchStart || msg == eTouchMove) {
5021     mTouches.InsertOrUpdate(aEvent->sequence, std::move(touch));
5022     // add all touch points to event object
5023     for (const auto& data : mTouches.Values()) {
5024       event.mTouches.AppendElement(new dom::Touch(*data));
5025     }
5026   } else if (msg == eTouchEnd || msg == eTouchCancel) {
5027     *event.mTouches.AppendElement() = std::move(touch);
5028   }
5029 
5030   nsIWidget::ContentAndAPZEventStatus eventStatus = DispatchInputEvent(&event);
5031 
5032   // There's a chance that we are in drag area and the event is not consumed
5033   // by something on it.
5034   LayoutDeviceIntPoint refPoint =
5035       GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
5036   if (msg == eTouchStart && mDraggableRegion.Contains(refPoint.x, refPoint.y) &&
5037       eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
5038     mWindowShouldStartDragging = true;
5039   }
5040   return TRUE;
5041 }
5042 
5043 // Return true if toplevel window is transparent.
5044 // It's transparent when we're running on composited screens
5045 // and we can draw main window without system titlebar.
IsToplevelWindowTransparent()5046 bool nsWindow::IsToplevelWindowTransparent() {
5047   static bool transparencyConfigured = false;
5048 
5049   if (!transparencyConfigured) {
5050     if (gdk_screen_is_composited(gdk_screen_get_default())) {
5051       // Some Gtk+ themes use non-rectangular toplevel windows. To fully
5052       // support such themes we need to make toplevel window transparent
5053       // with ARGB visual.
5054       // It may cause performanance issue so make it configurable
5055       // and enable it by default for selected window managers.
5056       if (Preferences::HasUserValue("mozilla.widget.use-argb-visuals")) {
5057         // argb visual is explicitly required so use it
5058         sTransparentMainWindow =
5059             Preferences::GetBool("mozilla.widget.use-argb-visuals");
5060       } else {
5061         // Enable transparent toplevel window if we can draw main window
5062         // without system titlebar as Gtk+ themes use titlebar round corners.
5063         sTransparentMainWindow =
5064             GetSystemGtkWindowDecoration() != GTK_DECORATION_NONE;
5065       }
5066     }
5067     transparencyConfigured = true;
5068   }
5069 
5070   return sTransparentMainWindow;
5071 }
5072 
5073 #ifdef MOZ_X11
5074 // Configure GL visual on X11.
ConfigureX11GLVisual()5075 bool nsWindow::ConfigureX11GLVisual() {
5076   auto* screen = gtk_widget_get_screen(mShell);
5077   int visualId = 0;
5078   bool haveVisual = false;
5079 
5080   if (gfxVars::UseEGL()) {
5081     haveVisual = GLContextEGL::FindVisual(&visualId);
5082   }
5083 
5084   // We are on GLX or use it as a fallback on Mesa, see
5085   // https://gitlab.freedesktop.org/mesa/mesa/-/issues/149
5086   if (!haveVisual) {
5087     auto* display = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(mShell));
5088     int screenNumber = GDK_SCREEN_XNUMBER(screen);
5089     haveVisual = GLContextGLX::FindVisual(display, screenNumber, &visualId);
5090   }
5091 
5092   GdkVisual* gdkVisual = nullptr;
5093   if (haveVisual) {
5094     // If we're using CSD, rendering will go through mContainer, but
5095     // it will inherit this visual as it is a child of mShell.
5096     gdkVisual = gdk_x11_screen_lookup_visual(screen, visualId);
5097   }
5098   if (!gdkVisual) {
5099     NS_WARNING("We're missing X11 Visual!");
5100     // We try to use a fallback alpha visual
5101     GdkScreen* screen = gtk_widget_get_screen(mShell);
5102     gdkVisual = gdk_screen_get_rgba_visual(screen);
5103   }
5104   if (gdkVisual) {
5105     gtk_widget_set_visual(mShell, gdkVisual);
5106     mHasAlphaVisual = true;
5107     return true;
5108   }
5109 
5110   return false;
5111 }
5112 #endif
5113 
GetFrameTag() const5114 nsAutoCString nsWindow::GetFrameTag() const {
5115   if (nsIFrame* frame = GetFrame()) {
5116 #ifdef DEBUG_FRAME_DUMP
5117     return frame->ListTag();
5118 #else
5119     nsAutoCString buf;
5120     buf.AppendPrintf("Frame(%p)", frame);
5121     if (nsIContent* content = frame->GetContent()) {
5122       buf.Append(' ');
5123       AppendUTF16toUTF8(content->NodeName(), buf);
5124     }
5125     return buf;
5126 #endif
5127   }
5128   return nsAutoCString("(no frame)");
5129 }
5130 
GetPopupTypeName()5131 nsCString nsWindow::GetPopupTypeName() {
5132   switch (mPopupHint) {
5133     case ePopupTypeMenu:
5134       return nsCString("Menu");
5135     case ePopupTypeTooltip:
5136       return nsCString("Tooltip");
5137     case ePopupTypePanel:
5138       return nsCString("Panel/Utility");
5139     default:
5140       return nsCString("Unknown");
5141   }
5142 }
5143 
5144 // Disables all rendering of GtkWidget from Gtk side.
5145 // We do our best to persuade Gtk/Gdk to ignore all painting
5146 // to the widget.
GtkWidgetDisableUpdates(GtkWidget * aWidget)5147 static void GtkWidgetDisableUpdates(GtkWidget* aWidget) {
5148   // Clear exposure mask - it disabled synthesized events.
5149   GdkWindow* window = gtk_widget_get_window(aWidget);
5150   gdk_window_set_events(window, (GdkEventMask)(gdk_window_get_events(window) &
5151                                                (~GDK_EXPOSURE_MASK)));
5152 
5153   // Remove before/after paint handles from frame clock.
5154   // It disables widget content updates.
5155   GdkFrameClock* frame_clock = gdk_window_get_frame_clock(window);
5156   g_signal_handlers_disconnect_by_data(frame_clock, window);
5157 }
5158 
EnableRenderingToWindow()5159 void nsWindow::EnableRenderingToWindow() {
5160   LOG("nsWindow::EnableRenderingToWindow()");
5161 
5162   if (mCompositorWidgetDelegate) {
5163     mCompositorWidgetDelegate->EnableRendering(GetX11Window(),
5164                                                GetShapedState());
5165   }
5166 
5167   if (GdkIsWaylandDisplay()) {
5168 #ifdef MOZ_WAYLAND
5169     moz_container_wayland_add_initial_draw_callback(
5170         mContainer, [self = RefPtr{this}, this]() -> void {
5171           LOG("moz_container_wayland initial create "
5172               "ResumeCompositorHiddenWindow()");
5173           self->ResumeCompositorHiddenWindow();
5174           self->WaylandStartVsync();
5175         });
5176 #endif
5177   } else {
5178     ResumeCompositorHiddenWindow();
5179     WaylandStartVsync();
5180   }
5181 }
5182 
DisableRenderingToWindow()5183 void nsWindow::DisableRenderingToWindow() {
5184   LOG("nsWindow::DisableRenderingToWindow()");
5185 
5186   PauseCompositorHiddenWindow();
5187   WaylandStopVsync();
5188   if (mCompositorWidgetDelegate) {
5189     mCompositorWidgetDelegate->DisableRendering();
5190   }
5191 }
5192 
GetX11Window()5193 Window nsWindow::GetX11Window() {
5194   return GdkIsX11Display() && mGdkWindow ? gdk_x11_window_get_xid(mGdkWindow)
5195                                          : X11None;
5196 }
5197 
EnsureGdkWindow()5198 void nsWindow::EnsureGdkWindow() {
5199   if (!mGdkWindow) {
5200     mGdkWindow = gtk_widget_get_window(mDrawToContainer ? GTK_WIDGET(mContainer)
5201                                                         : mShell);
5202     g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", this);
5203   }
5204 }
5205 
GetShapedState()5206 bool nsWindow::GetShapedState() {
5207   return mIsTransparent && !mHasAlphaVisual && !mTransparencyBitmapForTitlebar;
5208 }
5209 
ConfigureGdkWindow()5210 void nsWindow::ConfigureGdkWindow() {
5211   LOG("nsWindow::ConfigureGdkWindow() [%p]", this);
5212 
5213   EnsureGdkWindow();
5214 
5215 #ifdef MOZ_X11
5216   if (GdkIsX11Display()) {
5217     GdkVisual* gdkVisual = gdk_window_get_visual(mGdkWindow);
5218     Visual* visual = gdk_x11_visual_get_xvisual(gdkVisual);
5219     int depth = gdk_visual_get_depth(gdkVisual);
5220     mSurfaceProvider.Initialize(GetX11Window(), visual, depth,
5221                                 GetShapedState());
5222 
5223     // Set window manager hint to keep fullscreen windows composited.
5224     //
5225     // If the window were to get unredirected, there could be visible
5226     // tearing because Gecko does not align its framebuffer updates with
5227     // vblank.
5228     SetCompositorHint(GTK_WIDGET_COMPOSIDED_ENABLED);
5229 
5230     // Dummy call to a function in mozgtk to prevent the linker from removing
5231     // the dependency with --as-needed.
5232     XShmQueryExtension(DefaultXDisplay());
5233   }
5234 #endif
5235 #ifdef MOZ_WAYLAND
5236   if (GdkIsWaylandDisplay()) {
5237     mSurfaceProvider.Initialize(this);
5238   }
5239 #endif
5240 
5241   if (mIsDragPopup) {
5242     if (GdkIsWaylandDisplay()) {
5243       // Disable painting to the widget on Wayland as we paint directly to the
5244       // widget. Wayland compositors does not paint wl_subsurface
5245       // of D&D widget.
5246       if (GtkWidget* parent = gtk_widget_get_parent(mShell)) {
5247         GtkWidgetDisableUpdates(parent);
5248       }
5249       GtkWidgetDisableUpdates(mShell);
5250       GtkWidgetDisableUpdates(GTK_WIDGET(mContainer));
5251     } else {
5252       // Disable rendering of parent container on X11 to avoid flickering.
5253       if (GtkWidget* parent = gtk_widget_get_parent(mShell)) {
5254         gtk_widget_set_opacity(parent, 0.0);
5255       }
5256     }
5257   }
5258 
5259   if (mWindowType == eWindowType_popup) {
5260     if (mNoAutoHide) {
5261       gint wmd = ConvertBorderStyles(mBorderStyle);
5262       if (wmd != -1) {
5263         gdk_window_set_decorations(mGdkWindow, (GdkWMDecoration)wmd);
5264       }
5265     }
5266     // If the popup ignores mouse events, set an empty input shape.
5267     SetWindowMouseTransparent(mMouseTransparent);
5268   }
5269 
5270   RefreshWindowClass();
5271 
5272   if (mCompositorState == COMPOSITOR_PAUSED_INITIALLY) {
5273     mCompositorState = COMPOSITOR_PAUSED_MISSING_WINDOW;
5274   }
5275 
5276   EnableRenderingToWindow();
5277 
5278   if (mHasMappedToplevel) {
5279     EnsureGrabs();
5280   }
5281 
5282   LOG("  finished, new GdkWindow %p XID 0x%lx\n", mGdkWindow,
5283       GdkIsX11Display() ? gdk_x11_window_get_xid(mGdkWindow) : 0);
5284 }
5285 
ReleaseGdkWindow()5286 void nsWindow::ReleaseGdkWindow() {
5287   LOG("nsWindow::ReleaseGdkWindow() [%p]", this);
5288 
5289   DestroyChildWindows();
5290   DisableRenderingToWindow();
5291 
5292   if (mGdkWindow) {
5293     g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", nullptr);
5294     mGdkWindow = nullptr;
5295   }
5296 
5297   mSurfaceProvider.CleanupResources();
5298 }
5299 
Create(nsIWidget * aParent,nsNativeWidget aNativeParent,const LayoutDeviceIntRect & aRect,nsWidgetInitData * aInitData)5300 nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
5301                           const LayoutDeviceIntRect& aRect,
5302                           nsWidgetInitData* aInitData) {
5303   LOG("nsWindow::Create\n");
5304 
5305   // only set the base parent if we're going to be a dialog or a
5306   // toplevel
5307   nsIWidget* baseParent =
5308       aInitData && (aInitData->mWindowType == eWindowType_dialog ||
5309                     aInitData->mWindowType == eWindowType_toplevel ||
5310                     aInitData->mWindowType == eWindowType_invisible)
5311           ? nullptr
5312           : aParent;
5313 
5314 #ifdef ACCESSIBILITY
5315   // Send a DBus message to check whether a11y is enabled
5316   a11y::PreInit();
5317 #endif
5318 
5319 #ifdef MOZ_WAYLAND
5320   // Ensure that KeymapWrapper is created on Wayland as we need it for
5321   // keyboard focus tracking.
5322   if (GdkIsWaylandDisplay()) {
5323     KeymapWrapper::EnsureInstance();
5324   }
5325 #endif
5326 
5327   // Ensure that the toolkit is created.
5328   nsGTKToolkit::GetToolkit();
5329 
5330   // initialize all the common bits of this class
5331   BaseCreate(baseParent, aInitData);
5332 
5333   // and do our common creation
5334   mParent = aParent;
5335   mCreated = true;
5336   // save our bounds
5337   mBounds = aRect;
5338   LOG("  mBounds: x:%d y:%d w:%d h:%d\n", mBounds.x, mBounds.y, mBounds.width,
5339       mBounds.height);
5340 
5341   mPreferredPopupRectFlushed = false;
5342 
5343   ConstrainSize(&mBounds.width, &mBounds.height);
5344 
5345   GtkWidget* eventWidget = nullptr;
5346   bool popupNeedsAlphaVisual = (mWindowType == eWindowType_popup &&
5347                                 (aInitData && aInitData->mSupportTranslucency));
5348 
5349   // Figure out our parent window - only used for eWindowType_child
5350   GdkWindow* parentGdkWindow = nullptr;
5351   nsWindow* parentnsWindow = nullptr;
5352 
5353   if (aParent) {
5354     parentnsWindow = static_cast<nsWindow*>(aParent);
5355     parentGdkWindow = parentnsWindow->mGdkWindow;
5356   } else if (aNativeParent && GDK_IS_WINDOW(aNativeParent)) {
5357     parentGdkWindow = GDK_WINDOW(aNativeParent);
5358     parentnsWindow = get_window_for_gdk_window(parentGdkWindow);
5359     if (!parentnsWindow) {
5360       return NS_ERROR_FAILURE;
5361     }
5362   }
5363 
5364   if (mWindowType == eWindowType_child) {
5365     // We don't support eWindowType_child directly but emulate it by popup
5366     // windows.
5367     mWindowType = eWindowType_popup;
5368     if (!parentnsWindow) {
5369       if (aNativeParent && GTK_IS_CONTAINER(aNativeParent)) {
5370         parentnsWindow = get_window_for_gtk_widget(GTK_WIDGET(aNativeParent));
5371       }
5372     }
5373     mIsChildWindow = true;
5374     LOG("  child widget, switch to popup. parent nsWindow %p", parentnsWindow);
5375   }
5376 
5377   if (mWindowType == eWindowType_popup && !parentnsWindow) {
5378     LOG("  popup window without parent!");
5379     if (GdkIsWaylandDisplay()) {
5380       LOG("  switch to toplevel on Wayland.");
5381       // Wayland does not allow to create popup without parent so switch to
5382       // toplevel and mark as wayland panel.
5383       mIsWaylandPanelWindow = true;
5384       mWindowType = eWindowType_toplevel;
5385     }
5386   }
5387 
5388   if (mWindowType != eWindowType_dialog && mWindowType != eWindowType_popup &&
5389       mWindowType != eWindowType_toplevel &&
5390       mWindowType != eWindowType_invisible) {
5391     MOZ_ASSERT_UNREACHABLE("Unexpected eWindowType");
5392     return NS_ERROR_FAILURE;
5393   }
5394 
5395   mAlwaysOnTop = aInitData && aInitData->mAlwaysOnTop;
5396   mIsPIPWindow = aInitData && aInitData->mPIPWindow;
5397   // mNoAutoHide seems to be always false here.
5398   // The mNoAutoHide state is set later on nsMenuPopupFrame level
5399   // and can be changed so we use WaylandPopupIsPermanent() to get
5400   // recent popup config (Bug 1728952).
5401   mNoAutoHide = aInitData && aInitData->mNoAutoHide;
5402   mMouseTransparent = aInitData && aInitData->mMouseTransparent;
5403 
5404   // Popups that are not noautohide are only temporary. The are used
5405   // for menus and the like and disappear when another window is used.
5406   // For most popups, use the standard GtkWindowType GTK_WINDOW_POPUP,
5407   // which will use a Window with the override-redirect attribute
5408   // (for temporary windows).
5409   // For long-lived windows, their stacking order is managed by the
5410   // window manager, as indicated by GTK_WINDOW_TOPLEVEL.
5411   // For Wayland we have to always use GTK_WINDOW_POPUP to control
5412   // popup window position.
5413   GtkWindowType type = GTK_WINDOW_TOPLEVEL;
5414   if (mWindowType == eWindowType_popup) {
5415     MOZ_ASSERT(aInitData);
5416     type = GTK_WINDOW_POPUP;
5417     if (GdkIsX11Display() && mNoAutoHide) {
5418       type = GTK_WINDOW_TOPLEVEL;
5419     }
5420   }
5421   mShell = gtk_window_new(type);
5422 
5423   // Ensure gfxPlatform is initialized, since that is what initializes
5424   // gfxVars, used below.
5425   Unused << gfxPlatform::GetPlatform();
5426 
5427   if (mWindowType == eWindowType_toplevel ||
5428       mWindowType == eWindowType_dialog) {
5429     mGtkWindowDecoration = GetSystemGtkWindowDecoration();
5430   }
5431 
5432   // Don't use transparency for PictureInPicture windows.
5433   bool toplevelNeedsAlphaVisual = false;
5434   if (mWindowType == eWindowType_toplevel && !mIsPIPWindow) {
5435     toplevelNeedsAlphaVisual = IsToplevelWindowTransparent();
5436   }
5437 
5438   bool isGLVisualSet = false;
5439   mIsAccelerated = ComputeShouldAccelerate();
5440 #ifdef MOZ_X11
5441   if (GdkIsX11Display() && mIsAccelerated) {
5442     isGLVisualSet = ConfigureX11GLVisual();
5443   }
5444 #endif
5445   if (!isGLVisualSet && (popupNeedsAlphaVisual || toplevelNeedsAlphaVisual)) {
5446     // We're running on composited screen so we can use alpha visual
5447     // for both toplevel and popups.
5448     if (mCompositedScreen) {
5449       GdkVisual* visual =
5450           gdk_screen_get_rgba_visual(gtk_widget_get_screen(mShell));
5451       if (visual) {
5452         gtk_widget_set_visual(mShell, visual);
5453         mHasAlphaVisual = true;
5454       }
5455     }
5456   }
5457 
5458   // Use X shape mask to draw round corners of Firefox titlebar.
5459   // We don't use shape masks any more as we switched to ARGB visual
5460   // by default and non-compositing screens use solid-csd decorations
5461   // without round corners.
5462   // Leave the shape mask code here as it can be used to draw round
5463   // corners on EGL (https://gitlab.freedesktop.org/mesa/mesa/-/issues/149)
5464   // or when custom titlebar theme is used.
5465   mTransparencyBitmapForTitlebar = TitlebarUseShapeMask();
5466 
5467   // We have a toplevel window with transparency.
5468   // Calls to UpdateTitlebarTransparencyBitmap() from OnExposeEvent()
5469   // occur before SetTransparencyMode() receives eTransparencyTransparent
5470   // from layout, so set mIsTransparent here.
5471   if (mWindowType == eWindowType_toplevel &&
5472       (mHasAlphaVisual || mTransparencyBitmapForTitlebar)) {
5473     mIsTransparent = true;
5474   }
5475 
5476   // We only move a general managed toplevel window if someone has
5477   // actually placed the window somewhere.  If no placement has taken
5478   // place, we just let the window manager Do The Right Thing.
5479   if (AreBoundsSane()) {
5480     GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
5481     LOG("nsWindow::Create() Initial resize to %d x %d\n", size.width,
5482         size.height);
5483     gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
5484   }
5485 
5486   if (mWindowType == eWindowType_dialog) {
5487     mGtkWindowRoleName = "Dialog";
5488 
5489     SetDefaultIcon();
5490     gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_DIALOG);
5491     LOG("nsWindow::Create(): dialog");
5492     if (parentnsWindow) {
5493       gtk_window_set_transient_for(GTK_WINDOW(mShell),
5494                                    GTK_WINDOW(parentnsWindow->GetGtkWidget()));
5495       LOG("    set parent window [%p]\n", parentnsWindow);
5496     }
5497   } else if (mWindowType == eWindowType_popup) {
5498     MOZ_ASSERT(aInitData);
5499     mGtkWindowRoleName = "Popup";
5500     mPopupHint = aInitData->mPopupHint;
5501 
5502     LOG("nsWindow::Create() Popup");
5503 
5504     if (mNoAutoHide) {
5505       // ... but the window manager does not decorate this window,
5506       // nor provide a separate taskbar icon.
5507       if (mBorderStyle == eBorderStyle_default) {
5508         gtk_window_set_decorated(GTK_WINDOW(mShell), FALSE);
5509       } else {
5510         bool decorate = mBorderStyle & eBorderStyle_title;
5511         gtk_window_set_decorated(GTK_WINDOW(mShell), decorate);
5512         if (decorate) {
5513           gtk_window_set_deletable(GTK_WINDOW(mShell),
5514                                    mBorderStyle & eBorderStyle_close);
5515         }
5516       }
5517       gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mShell), TRUE);
5518       // Element focus is managed by the parent window so the
5519       // WM_HINTS input field is set to False to tell the window
5520       // manager not to set input focus to this window ...
5521       gtk_window_set_accept_focus(GTK_WINDOW(mShell), FALSE);
5522 #ifdef MOZ_X11
5523       // ... but when the window manager offers focus through
5524       // WM_TAKE_FOCUS, focus is requested on the parent window.
5525       if (GdkIsX11Display()) {
5526         gtk_widget_realize(mShell);
5527         gdk_window_add_filter(gtk_widget_get_window(mShell),
5528                               popup_take_focus_filter, nullptr);
5529       }
5530 #endif
5531     }
5532 
5533     if (aInitData->mIsDragPopup) {
5534       gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_DND);
5535       mIsDragPopup = true;
5536       LOG("nsWindow::Create() Drag popup\n");
5537     } else if (GdkIsX11Display()) {
5538       // Set the window hints on X11 only. Wayland popups are configured
5539       // at WaylandPopupNeedsTrackInHierarchy().
5540       GdkWindowTypeHint gtkTypeHint;
5541       switch (mPopupHint) {
5542         case ePopupTypeMenu:
5543           gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
5544           break;
5545         case ePopupTypeTooltip:
5546           gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
5547           break;
5548         default:
5549           gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
5550           break;
5551       }
5552       gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
5553       LOG("nsWindow::Create() popup type %s", GetPopupTypeName().get());
5554     }
5555     if (parentnsWindow) {
5556       LOG("    set parent window [%p] %s", parentnsWindow,
5557           parentnsWindow->mGtkWindowRoleName.get());
5558       GtkWindow* parentWidget = GTK_WINDOW(parentnsWindow->GetGtkWidget());
5559       gtk_window_set_transient_for(GTK_WINDOW(mShell), parentWidget);
5560       if (GdkIsWaylandDisplay() && gtk_window_get_modal(parentWidget)) {
5561         gtk_window_set_modal(GTK_WINDOW(mShell), true);
5562       }
5563     }
5564 
5565     // We need realized mShell at NativeMoveResize().
5566     gtk_widget_realize(mShell);
5567 
5568     if (GdkIsX11Display()) {
5569       // With popup windows, we want to control their position, so don't
5570       // wait for the window manager to place them (which wouldn't
5571       // happen with override-redirect windows anyway).
5572       NativeMoveResize(/* move */ true, /* resize */ false);
5573     }
5574   } else {  // must be eWindowType_toplevel
5575     mGtkWindowRoleName = "Toplevel";
5576     SetDefaultIcon();
5577 
5578     LOG("nsWindow::Create() Toplevel\n");
5579 
5580     if (mIsPIPWindow) {
5581       LOG("    Is PIP Window\n");
5582       gtk_window_set_type_hint(GTK_WINDOW(mShell),
5583                                GDK_WINDOW_TYPE_HINT_UTILITY);
5584     }
5585 
5586     // each toplevel window gets its own window group
5587     GtkWindowGroup* group = gtk_window_group_new();
5588     gtk_window_group_add_window(group, GTK_WINDOW(mShell));
5589     g_object_unref(group);
5590   }
5591 
5592   if (mAlwaysOnTop) {
5593     gtk_window_set_keep_above(GTK_WINDOW(mShell), TRUE);
5594   }
5595 
5596   // Create a container to hold child windows and child GtkWidgets.
5597   GtkWidget* container = moz_container_new();
5598   mContainer = MOZ_CONTAINER(container);
5599 
5600   // Don't render to invisible window.
5601   mCompositorState = COMPOSITOR_PAUSED_INITIALLY;
5602 
5603   // "csd" style is set when widget is realized so we need to call
5604   // it explicitly now.
5605   gtk_widget_realize(mShell);
5606 
5607   /* There are several cases here:
5608    *
5609    * 1) We're running on Gtk+ without client side decorations.
5610    *    Content is rendered to mShell window and we listen
5611    *    to the Gtk+ events on mShell
5612    * 2) We're running on Gtk+ and client side decorations
5613    *    are drawn by Gtk+ to mShell. Content is rendered to mContainer
5614    *    and we listen to the Gtk+ events on mContainer.
5615    * 3) We're running on Wayland. All gecko content is rendered
5616    *    to mContainer and we listen to the Gtk+ events on mContainer.
5617    */
5618   GtkStyleContext* style = gtk_widget_get_style_context(mShell);
5619   mDrawToContainer = GdkIsWaylandDisplay() ||
5620                      (mGtkWindowDecoration == GTK_DECORATION_CLIENT) ||
5621                      gtk_style_context_has_class(style, "csd");
5622   eventWidget = mDrawToContainer ? container : mShell;
5623 
5624   // Prevent GtkWindow from painting a background to avoid flickering.
5625   gtk_widget_set_app_paintable(eventWidget, gTransparentWindows);
5626 
5627   gtk_widget_add_events(eventWidget, kEvents);
5628 
5629   if (mDrawToContainer) {
5630     gtk_widget_add_events(mShell, GDK_PROPERTY_CHANGE_MASK);
5631     gtk_widget_set_app_paintable(mShell, gTransparentWindows);
5632   }
5633   if (mTransparencyBitmapForTitlebar) {
5634     moz_container_force_default_visual(mContainer);
5635   }
5636 
5637   // If we draw to mContainer window then configure it now because
5638   // gtk_container_add() realizes the child widget.
5639   gtk_widget_set_has_window(container, mDrawToContainer);
5640 
5641   gtk_container_add(GTK_CONTAINER(mShell), container);
5642 
5643   // alwaysontop windows are generally used for peripheral indicators,
5644   // so we don't focus them by default.
5645   if (mAlwaysOnTop) {
5646     gtk_window_set_focus_on_map(GTK_WINDOW(mShell), FALSE);
5647   }
5648 
5649   gtk_widget_realize(container);
5650 
5651   // make sure this is the focus widget in the container
5652   gtk_widget_show(container);
5653 
5654   if (!mAlwaysOnTop) {
5655     gtk_widget_grab_focus(container);
5656   }
5657 
5658   if (mIsWaylandPanelWindow) {
5659     gtk_window_set_decorated(GTK_WINDOW(mShell), false);
5660   }
5661 
5662 #ifdef MOZ_WAYLAND
5663   if (mIsDragPopup && GdkIsWaylandDisplay()) {
5664     LOG("  set commit to parent");
5665     moz_container_wayland_set_commit_to_parent(mContainer);
5666   }
5667 #endif
5668 
5669   if (mWindowType == eWindowType_popup) {
5670     MOZ_ASSERT(aInitData);
5671     // gdk does not automatically set the cursor for "temporary"
5672     // windows, which are what gtk uses for popups.
5673 
5674     // force SetCursor to actually set the cursor, even though our internal
5675     // state indicates that we already have the standard cursor.
5676     mUpdateCursor = true;
5677     SetCursor(Cursor{eCursor_standard});
5678   }
5679 
5680   if (mIsChildWindow && parentnsWindow) {
5681     GdkWindow* window = GetToplevelGdkWindow();
5682     GdkWindow* parentWindow = parentnsWindow->GetToplevelGdkWindow();
5683     LOG("  child GdkWindow %p set parent GdkWindow %p", window, parentWindow);
5684     gdk_window_reparent(window, parentWindow,
5685                         DevicePixelsToGdkCoordRoundDown(mBounds.x),
5686                         DevicePixelsToGdkCoordRoundDown(mBounds.y));
5687   }
5688 
5689   if (mDrawToContainer) {
5690     // Also label mShell toplevel window,
5691     // property_notify_event_cb callback also needs to find its way home
5692     g_object_set_data(G_OBJECT(gtk_widget_get_window(mShell)), "nsWindow",
5693                       this);
5694   }
5695 
5696   g_object_set_data(G_OBJECT(mContainer), "nsWindow", this);
5697   g_object_set_data(G_OBJECT(mShell), "nsWindow", this);
5698 
5699   // attach listeners for events
5700   g_signal_connect(mShell, "configure_event", G_CALLBACK(configure_event_cb),
5701                    nullptr);
5702   g_signal_connect(mShell, "delete_event", G_CALLBACK(delete_event_cb),
5703                    nullptr);
5704   g_signal_connect(mShell, "window_state_event",
5705                    G_CALLBACK(window_state_event_cb), nullptr);
5706   g_signal_connect(mShell, "check-resize", G_CALLBACK(check_resize_cb),
5707                    nullptr);
5708   g_signal_connect(mShell, "composited-changed",
5709                    G_CALLBACK(widget_composited_changed_cb), nullptr);
5710   g_signal_connect(mShell, "property-notify-event",
5711                    G_CALLBACK(property_notify_event_cb), nullptr);
5712   g_signal_connect(mShell, "map", G_CALLBACK(widget_map_cb), nullptr);
5713   g_signal_connect(mShell, "unrealize", G_CALLBACK(widget_unrealize_cb),
5714                    nullptr);
5715 
5716   if (mWindowType == eWindowType_toplevel) {
5717     g_signal_connect_after(mShell, "size_allocate",
5718                            G_CALLBACK(toplevel_window_size_allocate_cb),
5719                            nullptr);
5720   }
5721 
5722   GdkScreen* screen = gtk_widget_get_screen(mShell);
5723   if (!g_signal_handler_find(screen, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
5724                              FuncToGpointer(screen_composited_changed_cb),
5725                              nullptr)) {
5726     g_signal_connect(screen, "composited-changed",
5727                      G_CALLBACK(screen_composited_changed_cb), nullptr);
5728   }
5729 
5730   gtk_drag_dest_set((GtkWidget*)mShell, (GtkDestDefaults)0, nullptr, 0,
5731                     (GdkDragAction)0);
5732   g_signal_connect(mShell, "drag_motion", G_CALLBACK(drag_motion_event_cb),
5733                    nullptr);
5734   g_signal_connect(mShell, "drag_leave", G_CALLBACK(drag_leave_event_cb),
5735                    nullptr);
5736   g_signal_connect(mShell, "drag_drop", G_CALLBACK(drag_drop_event_cb),
5737                    nullptr);
5738   g_signal_connect(mShell, "drag_data_received",
5739                    G_CALLBACK(drag_data_received_event_cb), nullptr);
5740 
5741   GtkSettings* default_settings = gtk_settings_get_default();
5742   g_signal_connect_after(default_settings, "notify::gtk-xft-dpi",
5743                          G_CALLBACK(settings_xft_dpi_changed_cb), this);
5744 
5745   // Widget signals
5746   g_signal_connect_after(mContainer, "size_allocate",
5747                          G_CALLBACK(size_allocate_cb), nullptr);
5748   g_signal_connect(mContainer, "hierarchy-changed",
5749                    G_CALLBACK(hierarchy_changed_cb), nullptr);
5750   g_signal_connect(mContainer, "notify::scale-factor",
5751                    G_CALLBACK(scale_changed_cb), nullptr);
5752   // Initialize mHasMappedToplevel.
5753   hierarchy_changed_cb(GTK_WIDGET(mContainer), nullptr);
5754   // Expose, focus, key, and drag events are sent even to GTK_NO_WINDOW
5755   // widgets.
5756   g_signal_connect(G_OBJECT(mContainer), "draw", G_CALLBACK(expose_event_cb),
5757                    nullptr);
5758   g_signal_connect(mContainer, "focus_in_event", G_CALLBACK(focus_in_event_cb),
5759                    nullptr);
5760   g_signal_connect(mContainer, "focus_out_event",
5761                    G_CALLBACK(focus_out_event_cb), nullptr);
5762   g_signal_connect(mContainer, "key_press_event",
5763                    G_CALLBACK(key_press_event_cb), nullptr);
5764   g_signal_connect(mContainer, "key_release_event",
5765                    G_CALLBACK(key_release_event_cb), nullptr);
5766 
5767 #ifdef MOZ_X11
5768   if (GdkIsX11Display()) {
5769     GtkWidget* widgets[] = {GTK_WIDGET(mContainer),
5770                             !mDrawToContainer ? mShell : nullptr};
5771     for (size_t i = 0; i < ArrayLength(widgets) && widgets[i]; ++i) {
5772       // Double buffering is controlled by the window's owning
5773       // widget. Disable double buffering for painting directly to the
5774       // X Window.
5775       gtk_widget_set_double_buffered(widgets[i], FALSE);
5776     }
5777   }
5778 #endif
5779 #ifdef MOZ_WAYLAND
5780   // Initialize the window specific VsyncSource early in order to avoid races
5781   // with BrowserParent::UpdateVsyncParentVsyncSource().
5782   // Only use for toplevel windows for now, see bug 1619246.
5783   if (GdkIsWaylandDisplay() &&
5784       StaticPrefs::widget_wayland_vsync_enabled_AtStartup() &&
5785       mWindowType == eWindowType_toplevel) {
5786     mWaylandVsyncSource = new WaylandVsyncSource();
5787     LOG_VSYNC("  created WaylandVsyncSource)");
5788     MOZ_RELEASE_ASSERT(mWaylandVsyncSource);
5789   }
5790 #endif
5791 
5792   // We create input contexts for all containers, except for
5793   // toplevel popup windows
5794   if (mWindowType != eWindowType_popup) {
5795     mIMContext = new IMContextWrapper(this);
5796   }
5797 
5798   // These events are sent to the owning widget of the relevant window
5799   // and propagate up to the first widget that handles the events, so we
5800   // need only connect on mShell, if it exists, to catch events on its
5801   // window and windows of mContainer.
5802   g_signal_connect(eventWidget, "enter-notify-event",
5803                    G_CALLBACK(enter_notify_event_cb), nullptr);
5804   g_signal_connect(eventWidget, "leave-notify-event",
5805                    G_CALLBACK(leave_notify_event_cb), nullptr);
5806   g_signal_connect(eventWidget, "motion-notify-event",
5807                    G_CALLBACK(motion_notify_event_cb), nullptr);
5808   g_signal_connect(eventWidget, "button-press-event",
5809                    G_CALLBACK(button_press_event_cb), nullptr);
5810   g_signal_connect(eventWidget, "button-release-event",
5811                    G_CALLBACK(button_release_event_cb), nullptr);
5812   g_signal_connect(eventWidget, "scroll-event", G_CALLBACK(scroll_event_cb),
5813                    nullptr);
5814   if (gtk_check_version(3, 18, 0) == nullptr) {
5815     g_signal_connect(eventWidget, "event", G_CALLBACK(generic_event_cb),
5816                      nullptr);
5817   }
5818   g_signal_connect(eventWidget, "touch-event", G_CALLBACK(touch_event_cb),
5819                    nullptr);
5820 
5821   LOG("  nsWindow type %d %s\n", mWindowType, mIsPIPWindow ? "PIP window" : "");
5822   LOG("  mShell %p mContainer %p mGdkWindow %p XID 0x%lx\n", mShell, mContainer,
5823       mGdkWindow,
5824       (GdkIsX11Display() && mGdkWindow) ? gdk_x11_window_get_xid(mGdkWindow)
5825                                         : 0);
5826 
5827   // Set default application name when it's empty.
5828   if (mGtkWindowAppName.IsEmpty()) {
5829     mGtkWindowAppName = gAppData->name;
5830   }
5831 
5832   return NS_OK;
5833 }
5834 
RefreshWindowClass(void)5835 void nsWindow::RefreshWindowClass(void) {
5836   GdkWindow* gdkWindow = gtk_widget_get_window(mShell);
5837   if (!gdkWindow) {
5838     return;
5839   }
5840 
5841   if (!mGtkWindowRoleName.IsEmpty()) {
5842     gdk_window_set_role(gdkWindow, mGtkWindowRoleName.get());
5843   }
5844 
5845 #ifdef MOZ_X11
5846   if (!mGtkWindowAppName.IsEmpty() && GdkIsX11Display()) {
5847     XClassHint* class_hint = XAllocClassHint();
5848     if (!class_hint) {
5849       return;
5850     }
5851     const char* res_class = gdk_get_program_class();
5852     if (!res_class) return;
5853 
5854     class_hint->res_name = const_cast<char*>(mGtkWindowAppName.get());
5855     class_hint->res_class = const_cast<char*>(res_class);
5856 
5857     // Can't use gtk_window_set_wmclass() for this; it prints
5858     // a warning & refuses to make the change.
5859     GdkDisplay* display = gdk_display_get_default();
5860     XSetClassHint(GDK_DISPLAY_XDISPLAY(display),
5861                   gdk_x11_window_get_xid(gdkWindow), class_hint);
5862     XFree(class_hint);
5863   }
5864 #endif /* MOZ_X11 */
5865 }
5866 
SetWindowClass(const nsAString & xulWinType)5867 void nsWindow::SetWindowClass(const nsAString& xulWinType) {
5868   if (!mShell) return;
5869 
5870   char* res_name = ToNewCString(xulWinType, mozilla::fallible);
5871   if (!res_name) return;
5872 
5873   const char* role = nullptr;
5874 
5875   // Parse res_name into a name and role. Characters other than
5876   // [A-Za-z0-9_-] are converted to '_'. Anything after the first
5877   // colon is assigned to role; if there's no colon, assign the
5878   // whole thing to both role and res_name.
5879   for (char* c = res_name; *c; c++) {
5880     if (':' == *c) {
5881       *c = 0;
5882       role = c + 1;
5883     } else if (!isascii(*c) || (!isalnum(*c) && ('_' != *c) && ('-' != *c))) {
5884       *c = '_';
5885     }
5886   }
5887   res_name[0] = (char)toupper(res_name[0]);
5888   if (!role) role = res_name;
5889 
5890   mGtkWindowAppName = res_name;
5891   mGtkWindowRoleName = role;
5892   free(res_name);
5893 
5894   RefreshWindowClass();
5895 }
5896 
GetDebugTag() const5897 nsAutoCString nsWindow::GetDebugTag() const {
5898   nsAutoCString tag;
5899   tag.AppendPrintf("[%p]", this);
5900   return tag;
5901 }
5902 
NativeMoveResize(bool aMoved,bool aResized)5903 void nsWindow::NativeMoveResize(bool aMoved, bool aResized) {
5904   GdkRectangle rect = DevicePixelsToGdkRectRoundOut(mBounds);
5905 
5906   LOG("nsWindow::NativeMoveResize move %d resize %d to %d,%d -> %d x %d\n",
5907       aMoved, aResized, rect.x, rect.y, rect.width, rect.height);
5908 
5909   if (aResized && !AreBoundsSane()) {
5910     LOG("  bounds are insane, hidding the window");
5911     // We have been resized but to incorrect size.
5912     // If someone has set this so that the needs show flag is false
5913     // and it needs to be hidden, update the flag and hide the
5914     // window.  This flag will be cleared the next time someone
5915     // hides the window or shows it.  It also prevents us from
5916     // calling NativeShow(false) excessively on the window which
5917     // causes unneeded X traffic.
5918     if (!mNeedsShow && mIsShown) {
5919       mNeedsShow = true;
5920       NativeShow(false);
5921     }
5922     if (aMoved) {
5923       LOG("  moving to %d x %d", rect.x, rect.y);
5924       gtk_window_move(GTK_WINDOW(mShell), rect.x, rect.y);
5925     }
5926     mUpdatedByMoveToRectCallback = false;
5927     return;
5928   }
5929 
5930   // Set position to hidden window on X11 may fail, so save the position
5931   // and move it when it's shown.
5932   if (aMoved && GdkIsX11Display() && IsPopup() &&
5933       !gtk_widget_get_visible(GTK_WIDGET(mShell))) {
5934     LOG("  store position of hidden popup window");
5935     mHiddenPopupPositioned = true;
5936     mPopupPosition = {rect.x, rect.y};
5937   }
5938 
5939   if (IsWaylandPopup()) {
5940     NativeMoveResizeWaylandPopup(aMoved, aResized);
5941   } else {
5942     // x and y give the position of the window manager frame top-left.
5943     if (aMoved) {
5944       gtk_window_move(GTK_WINDOW(mShell), rect.x, rect.y);
5945     }
5946     if (aResized) {
5947       gtk_window_resize(GTK_WINDOW(mShell), rect.width, rect.height);
5948       if (mIsDragPopup) {
5949         // DND window is placed inside container so we need to make hard size
5950         // request to ensure parent container is resized too.
5951         gtk_widget_set_size_request(GTK_WIDGET(mShell), rect.width,
5952                                     rect.height);
5953       }
5954     }
5955   }
5956 
5957   // Notify the GtkCompositorWidget of a ClientSizeChange
5958   // This is different than OnSizeAllocate to catch initial sizing
5959   if (mCompositorWidgetDelegate && aResized) {
5960     mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
5961   }
5962 
5963   // Does it need to be shown because bounds were previously insane?
5964   if (mNeedsShow && mIsShown && aResized) {
5965     NativeShow(true);
5966   }
5967 }
5968 
ResumeCompositorHiddenWindow()5969 void nsWindow::ResumeCompositorHiddenWindow() {
5970   MOZ_RELEASE_ASSERT(NS_IsMainThread());
5971 
5972   LOG("nsWindow::ResumeCompositorHiddenWindow\n");
5973   if (mIsDestroyed || mCompositorState == COMPOSITOR_ENABLED) {
5974     LOG("  early quit\n");
5975     return;
5976   }
5977 
5978   if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) {
5979     LOG("  resume\n");
5980     MOZ_ASSERT(mCompositorWidgetDelegate);
5981     if (mCompositorWidgetDelegate) {
5982       mCompositorState = COMPOSITOR_ENABLED;
5983       remoteRenderer->SendResumeAsync();
5984     }
5985     remoteRenderer->SendForcePresent(wr::RenderReasons::WIDGET);
5986   } else {
5987     LOG("  quit, failed to get remote renderer.\n");
5988   }
5989 }
5990 
5991 // Because wl_egl_window is destroyed on moz_container_unmap(),
5992 // the current compositor cannot use it anymore. To avoid crash,
5993 // pause the compositor and destroy EGLSurface & resume the compositor
5994 // and re-create EGLSurface on next expose event.
PauseCompositorHiddenWindow()5995 void nsWindow::PauseCompositorHiddenWindow() {
5996   LOG("nsWindow::PauseCompositorHiddenWindow");
5997 
5998   if (mCompositorState != COMPOSITOR_ENABLED) {
5999     LOG("  quit early, compositor is disabled");
6000     return;
6001   }
6002 
6003   mCompositorState = COMPOSITOR_PAUSED_MISSING_WINDOW;
6004 
6005   // Without remote widget / renderer we can't pause compositor.
6006   // So delete LayerManager to avoid EGLSurface access.
6007   CompositorBridgeChild* remoteRenderer = GetRemoteRenderer();
6008   if (!remoteRenderer || !mCompositorWidgetDelegate) {
6009     LOG("  deleted layer manager");
6010     DestroyLayerManager();
6011     return;
6012   }
6013 
6014   // XXX slow sync IPC
6015   LOG("  paused compositor");
6016   remoteRenderer->SendPause();
6017 }
6018 
WindowResumeCompositor(void * data)6019 static int WindowResumeCompositor(void* data) {
6020   nsWindow* window = static_cast<nsWindow*>(data);
6021   window->ResumeCompositor();
6022   return true;
6023 }
6024 
6025 // We pause compositor to avoid rendering of obsoleted remote content which
6026 // produces flickering.
6027 // Re-enable compositor again when remote content is updated or
6028 // timeout happens.
6029 
6030 // Define maximal compositor pause when it's paused to avoid flickering,
6031 // in milliseconds.
6032 #define COMPOSITOR_PAUSE_TIMEOUT (1000)
6033 
PauseCompositor()6034 void nsWindow::PauseCompositor() {
6035   bool pauseCompositor = (mWindowType == eWindowType_toplevel) &&
6036                          mCompositorState == COMPOSITOR_ENABLED &&
6037                          mCompositorWidgetDelegate && !mIsDestroyed;
6038   if (!pauseCompositor) {
6039     return;
6040   }
6041 
6042   LOG("nsWindow::PauseCompositor()");
6043 
6044   if (mCompositorPauseTimeoutID) {
6045     g_source_remove(mCompositorPauseTimeoutID);
6046     mCompositorPauseTimeoutID = 0;
6047   }
6048 
6049   CompositorBridgeChild* remoteRenderer = GetRemoteRenderer();
6050   if (remoteRenderer) {
6051     remoteRenderer->SendPause();
6052     mCompositorState = COMPOSITOR_PAUSED_FLICKERING;
6053     mCompositorPauseTimeoutID = (int)g_timeout_add(
6054         COMPOSITOR_PAUSE_TIMEOUT, &WindowResumeCompositor, this);
6055   }
6056 }
6057 
IsWaitingForCompositorResume()6058 bool nsWindow::IsWaitingForCompositorResume() {
6059   return mCompositorState == COMPOSITOR_PAUSED_FLICKERING;
6060 }
6061 
ResumeCompositor()6062 void nsWindow::ResumeCompositor() {
6063   MOZ_RELEASE_ASSERT(NS_IsMainThread());
6064 
6065   LOG("nsWindow::ResumeCompositor()\n");
6066 
6067   if (mIsDestroyed || !IsWaitingForCompositorResume()) {
6068     LOG("  early quit\n");
6069     return;
6070   }
6071 
6072   if (mCompositorPauseTimeoutID) {
6073     g_source_remove(mCompositorPauseTimeoutID);
6074     mCompositorPauseTimeoutID = 0;
6075   }
6076 
6077   // We're expected to have mCompositorWidgetDelegate present
6078   // as we don't delete LayerManager (in PauseCompositor())
6079   // to avoid flickering.
6080   MOZ_RELEASE_ASSERT(mCompositorWidgetDelegate);
6081 
6082   CompositorBridgeChild* remoteRenderer = GetRemoteRenderer();
6083   if (remoteRenderer) {
6084     mCompositorState = COMPOSITOR_ENABLED;
6085     remoteRenderer->SendResumeAsync();
6086     remoteRenderer->SendForcePresent(wr::RenderReasons::WIDGET);
6087   }
6088 }
6089 
ResumeCompositorFromCompositorThread()6090 void nsWindow::ResumeCompositorFromCompositorThread() {
6091   nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
6092       "nsWindow::ResumeCompositor", this, &nsWindow::ResumeCompositor);
6093   NS_DispatchToMainThread(event.forget());
6094 }
6095 
WaylandStartVsync()6096 void nsWindow::WaylandStartVsync() {
6097 #ifdef MOZ_WAYLAND
6098   if (!mWaylandVsyncSource) {
6099     return;
6100   }
6101 
6102   LOG_VSYNC("nsWindow::WaylandStartVsync");
6103 
6104   WaylandVsyncSource::WaylandDisplay& display =
6105       static_cast<WaylandVsyncSource::WaylandDisplay&>(
6106           mWaylandVsyncSource->GetGlobalDisplay());
6107 
6108   if (mCompositorWidgetDelegate) {
6109     if (RefPtr<layers::NativeLayerRoot> nativeLayerRoot =
6110             mCompositorWidgetDelegate->AsGtkCompositorWidget()
6111                 ->GetNativeLayerRoot()) {
6112       LOG_VSYNC("  use source NativeLayerRootWayland");
6113       display.MaybeUpdateSource(nativeLayerRoot->AsNativeLayerRootWayland());
6114     } else {
6115       LOG_VSYNC("  use source mContainer");
6116       display.MaybeUpdateSource(mContainer);
6117     }
6118   }
6119   display.EnableMonitor();
6120 #endif
6121 }
6122 
WaylandStopVsync()6123 void nsWindow::WaylandStopVsync() {
6124 #ifdef MOZ_WAYLAND
6125   if (!mWaylandVsyncSource) {
6126     return;
6127   }
6128 
6129   LOG_VSYNC("nsWindow::WaylandStopVsync");
6130 
6131   // The widget is going to be hidden, so clear the surface of our
6132   // vsync source.
6133   WaylandVsyncSource::WaylandDisplay& display =
6134       static_cast<WaylandVsyncSource::WaylandDisplay&>(
6135           mWaylandVsyncSource->GetGlobalDisplay());
6136   display.DisableMonitor();
6137   display.MaybeUpdateSource(nullptr);
6138 #endif
6139 }
6140 
NativeShow(bool aAction)6141 void nsWindow::NativeShow(bool aAction) {
6142   if (aAction) {
6143     // unset our flag now that our window has been shown
6144     mNeedsShow = true;
6145     auto removeShow = MakeScopeExit([&] { mNeedsShow = false; });
6146 
6147     LOG("nsWindow::NativeShow show\n");
6148 
6149     if (IsWaylandPopup()) {
6150       mPopupClosed = false;
6151       if (WaylandPopupNeedsTrackInHierarchy()) {
6152         AddWindowToPopupHierarchy();
6153         UpdateWaylandPopupHierarchy();
6154         if (mPopupClosed) {
6155           return;
6156         }
6157       }
6158     }
6159     // Set up usertime/startupID metadata for the created window.
6160     if (mWindowType != eWindowType_invisible) {
6161       SetUserTimeAndStartupIDForActivatedWindow(mShell);
6162     }
6163     if (GdkIsWaylandDisplay()) {
6164       ShowWaylandWindow();
6165     } else {
6166       LOG("  calling gtk_widget_show(mShell)\n");
6167       gtk_widget_show(mShell);
6168     }
6169 
6170     if (mHiddenPopupPositioned && IsPopup()) {
6171       LOG("  re-position hidden popup window");
6172       gtk_window_move(GTK_WINDOW(mShell), mPopupPosition.x, mPopupPosition.y);
6173       mHiddenPopupPositioned = false;
6174     }
6175   } else {
6176     // There's a chance that when the popup will be shown again it might be
6177     // resized because parent could be moved meanwhile.
6178     mPreferredPopupRect = LayoutDeviceIntRect();
6179     mPreferredPopupRectFlushed = false;
6180     LOG("nsWindow::NativeShow hide\n");
6181     if (GdkIsWaylandDisplay()) {
6182       if (IsWaylandPopup()) {
6183         // We can't close tracked popups directly as they may have visible
6184         // child popups. Just mark is as closed and let
6185         // UpdateWaylandPopupHierarchy() do the job.
6186         if (IsInPopupHierarchy()) {
6187           WaylandPopupMarkAsClosed();
6188           UpdateWaylandPopupHierarchy();
6189         } else {
6190           // Close untracked popups directly.
6191           HideWaylandPopupWindow(/* aTemporaryHide */ false,
6192                                  /* aRemoveFromPopupList */ true);
6193         }
6194       } else {
6195         HideWaylandToplevelWindow();
6196       }
6197     } else {
6198       // Workaround window freezes on GTK versions before 3.21.2 by
6199       // ensuring that configure events get dispatched to windows before
6200       // they are unmapped. See bug 1225044.
6201       if (gtk_check_version(3, 21, 2) != nullptr && mPendingConfigures > 0) {
6202         GtkAllocation allocation;
6203         gtk_widget_get_allocation(GTK_WIDGET(mShell), &allocation);
6204 
6205         GdkEventConfigure event;
6206         PodZero(&event);
6207         event.type = GDK_CONFIGURE;
6208         event.window = mGdkWindow;
6209         event.send_event = TRUE;
6210         event.x = allocation.x;
6211         event.y = allocation.y;
6212         event.width = allocation.width;
6213         event.height = allocation.height;
6214 
6215         auto* shellClass = GTK_WIDGET_GET_CLASS(mShell);
6216         for (unsigned int i = 0; i < mPendingConfigures; i++) {
6217           Unused << shellClass->configure_event(mShell, &event);
6218         }
6219         mPendingConfigures = 0;
6220       }
6221       gtk_widget_hide(mShell);
6222 
6223       ClearTransparencyBitmap();  // Release some resources
6224     }
6225   }
6226 }
6227 
SetHasMappedToplevel(bool aState)6228 void nsWindow::SetHasMappedToplevel(bool aState) {
6229   LOG("nsWindow::SetHasMappedToplevel() state %d", aState);
6230 
6231   // Even when aState == mHasMappedToplevel (as when this method is called
6232   // from Show()), child windows need to have their state checked, so don't
6233   // return early.
6234   bool oldState = mHasMappedToplevel;
6235   mHasMappedToplevel = aState;
6236 
6237   // mHasMappedToplevel is not updated for children of windows that are
6238   // hidden; GDK knows not to send expose events for these windows.  The
6239   // state is recorded on the hidden window itself, but, for child trees of
6240   // hidden windows, their state essentially becomes disconnected from their
6241   // hidden parent.  When the hidden parent gets shown, the child trees are
6242   // reconnected, and the state of the window being shown can be easily
6243   // propagated.
6244   if (!mIsShown || !mGdkWindow) {
6245     LOG("  hidden, quit.\n");
6246     return;
6247   }
6248 
6249   if (aState && !oldState) {
6250     // Check that a grab didn't fail due to the window not being
6251     // viewable.
6252     EnsureGrabs();
6253   }
6254 }
6255 
GetSafeWindowSize(LayoutDeviceIntSize aSize)6256 LayoutDeviceIntSize nsWindow::GetSafeWindowSize(LayoutDeviceIntSize aSize) {
6257   // The X protocol uses CARD32 for window sizes, but the server (1.11.3)
6258   // reads it as CARD16.  Sizes of pixmaps, used for drawing, are (unsigned)
6259   // CARD16 in the protocol, but the server's ProcCreatePixmap returns
6260   // BadAlloc if dimensions cannot be represented by signed shorts.
6261   // Because we are creating Cairo surfaces to represent window buffers,
6262   // we also must ensure that the window can fit in a Cairo surface.
6263   LayoutDeviceIntSize result = aSize;
6264   int32_t maxSize = 32767;
6265   if (mWindowRenderer && mWindowRenderer->AsKnowsCompositor()) {
6266     maxSize = std::min(
6267         maxSize, mWindowRenderer->AsKnowsCompositor()->GetMaxTextureSize());
6268   }
6269   if (result.width > maxSize) {
6270     result.width = maxSize;
6271   }
6272   if (result.height > maxSize) {
6273     result.height = maxSize;
6274   }
6275   return result;
6276 }
6277 
EnsureGrabs(void)6278 void nsWindow::EnsureGrabs(void) {
6279   if (mRetryPointerGrab) {
6280     GrabPointer(sRetryGrabTime);
6281   }
6282 }
6283 
CleanLayerManagerRecursive(void)6284 void nsWindow::CleanLayerManagerRecursive(void) {
6285   if (mWindowRenderer) {
6286     mWindowRenderer->Destroy();
6287     mWindowRenderer = nullptr;
6288   }
6289 
6290   DestroyCompositor();
6291 }
6292 
SetTransparencyMode(nsTransparencyMode aMode)6293 void nsWindow::SetTransparencyMode(nsTransparencyMode aMode) {
6294   bool isTransparent = aMode == eTransparencyTransparent;
6295 
6296   if (mIsTransparent == isTransparent) {
6297     return;
6298   }
6299 
6300   if (mWindowType != eWindowType_popup) {
6301     // https://bugzilla.mozilla.org/show_bug.cgi?id=1344839 reported
6302     // problems cleaning the layer manager for toplevel windows.
6303     // Ignore the request so as to workaround that.
6304     // mIsTransparent is set in Create() if transparency may be required.
6305     if (isTransparent) {
6306       NS_WARNING("Transparent mode not supported on non-popup windows.");
6307     }
6308     return;
6309   }
6310 
6311   if (!isTransparent) {
6312     ClearTransparencyBitmap();
6313   }  // else the new default alpha values are "all 1", so we don't
6314   // need to change anything yet
6315 
6316   mIsTransparent = isTransparent;
6317 
6318   if (!mHasAlphaVisual) {
6319     // The choice of layer manager depends on
6320     // GtkCompositorWidgetInitData::Shaped(), which will need to change, so
6321     // clean out the old layer manager.
6322     CleanLayerManagerRecursive();
6323   }
6324 }
6325 
GetTransparencyMode()6326 nsTransparencyMode nsWindow::GetTransparencyMode() {
6327   return mIsTransparent ? eTransparencyTransparent : eTransparencyOpaque;
6328 }
6329 
SetWindowMouseTransparent(bool aIsTransparent)6330 void nsWindow::SetWindowMouseTransparent(bool aIsTransparent) {
6331   mMouseTransparent = aIsTransparent;
6332 
6333   GdkWindow* window =
6334       mDrawToContainer ? gtk_widget_get_window(mShell) : mGdkWindow;
6335   if (!window) {
6336     return;
6337   }
6338 
6339   LOG("nsWindow::SetWindowMouseTransparent(%d)", aIsTransparent);
6340 
6341   cairo_rectangle_int_t emptyRect = {0, 0, 0, 0};
6342   cairo_region_t* region =
6343       aIsTransparent ? cairo_region_create_rectangle(&emptyRect) : nullptr;
6344   gdk_window_input_shape_combine_region(window, region, 0, 0);
6345   if (region) {
6346     cairo_region_destroy(region);
6347   }
6348 
6349   // On Wayland gdk_window_input_shape_combine_region() call is cached and
6350   // applied to underlying wl_surface when GdkWindow is repainted.
6351   // Force repaint of GdkWindow to apply the change immediately.
6352   if (GdkIsWaylandDisplay()) {
6353     gdk_window_invalidate_rect(window, nullptr, false);
6354   }
6355 }
6356 
6357 // For setting the draggable titlebar region from CSS
6358 // with -moz-window-dragging: drag.
UpdateWindowDraggingRegion(const LayoutDeviceIntRegion & aRegion)6359 void nsWindow::UpdateWindowDraggingRegion(
6360     const LayoutDeviceIntRegion& aRegion) {
6361   if (mDraggableRegion != aRegion) {
6362     mDraggableRegion = aRegion;
6363   }
6364 }
6365 
GetTitlebarRadius()6366 LayoutDeviceIntCoord nsWindow::GetTitlebarRadius() {
6367   MOZ_RELEASE_ASSERT(NS_IsMainThread());
6368   int32_t cssCoord = LookAndFeel::GetInt(LookAndFeel::IntID::TitlebarRadius);
6369   return GdkCoordToDevicePixels(cssCoord);
6370 }
6371 
6372 // See subtract_corners_from_region() at gtk/gtkwindow.c
6373 // We need to subtract corners from toplevel window opaque region
6374 // to draw transparent corners of default Gtk titlebar.
6375 // Both implementations (cairo_region_t and wl_region) needs to be synced.
SubtractTitlebarCorners(cairo_region_t * aRegion,int aX,int aY,int aWindowWidth,int aTitlebarRadius)6376 static void SubtractTitlebarCorners(cairo_region_t* aRegion, int aX, int aY,
6377                                     int aWindowWidth, int aTitlebarRadius) {
6378   if (!aTitlebarRadius) {
6379     return;
6380   }
6381   cairo_rectangle_int_t rect = {aX, aY, aTitlebarRadius, aTitlebarRadius};
6382   cairo_region_subtract_rectangle(aRegion, &rect);
6383   rect = {
6384       aX + aWindowWidth - aTitlebarRadius,
6385       aY,
6386       aTitlebarRadius,
6387       aTitlebarRadius,
6388   };
6389   cairo_region_subtract_rectangle(aRegion, &rect);
6390 }
6391 
UpdateTopLevelOpaqueRegion(void)6392 void nsWindow::UpdateTopLevelOpaqueRegion(void) {
6393   if (!mCompositedScreen) {
6394     return;
6395   }
6396 
6397   GdkWindow* window =
6398       mDrawToContainer ? gtk_widget_get_window(mShell) : mGdkWindow;
6399   if (!window) {
6400     return;
6401   }
6402   MOZ_ASSERT(gdk_window_get_window_type(window) == GDK_WINDOW_TOPLEVEL);
6403 
6404   int x = 0;
6405   int y = 0;
6406 
6407   if (mDrawToContainer) {
6408     gdk_window_get_position(mGdkWindow, &x, &y);
6409   }
6410 
6411   int width = DevicePixelsToGdkCoordRoundDown(mBounds.width);
6412   int height = DevicePixelsToGdkCoordRoundDown(mBounds.height);
6413 
6414   cairo_region_t* region = cairo_region_create();
6415   cairo_rectangle_int_t rect = {x, y, width, height};
6416   cairo_region_union_rectangle(region, &rect);
6417 
6418   int radius = DoDrawTilebarCorners() ? int(GetTitlebarRadius()) : 0;
6419   SubtractTitlebarCorners(region, x, y, width, radius);
6420 
6421   gdk_window_set_opaque_region(window, region);
6422 
6423   cairo_region_destroy(region);
6424 
6425 #ifdef MOZ_WAYLAND
6426   if (GdkIsWaylandDisplay()) {
6427     moz_container_wayland_update_opaque_region(mContainer, radius);
6428   }
6429 #endif
6430 }
6431 
IsChromeWindowTitlebar()6432 bool nsWindow::IsChromeWindowTitlebar() {
6433   return mDrawInTitlebar && !mIsPIPWindow &&
6434          mWindowType == eWindowType_toplevel;
6435 }
6436 
DoDrawTilebarCorners()6437 bool nsWindow::DoDrawTilebarCorners() {
6438   return IsChromeWindowTitlebar() && mSizeState == nsSizeMode_Normal &&
6439          !mIsTiled;
6440 }
6441 
ResizeTransparencyBitmap()6442 void nsWindow::ResizeTransparencyBitmap() {
6443   if (!mTransparencyBitmap) return;
6444 
6445   if (mBounds.width == mTransparencyBitmapWidth &&
6446       mBounds.height == mTransparencyBitmapHeight) {
6447     return;
6448   }
6449 
6450   int32_t newRowBytes = GetBitmapStride(mBounds.width);
6451   int32_t newSize = newRowBytes * mBounds.height;
6452   auto* newBits = new gchar[newSize];
6453   // fill new mask with "transparent", first
6454   memset(newBits, 0, newSize);
6455 
6456   // Now copy the intersection of the old and new areas into the new mask
6457   int32_t copyWidth = std::min(mBounds.width, mTransparencyBitmapWidth);
6458   int32_t copyHeight = std::min(mBounds.height, mTransparencyBitmapHeight);
6459   int32_t oldRowBytes = GetBitmapStride(mTransparencyBitmapWidth);
6460   int32_t copyBytes = GetBitmapStride(copyWidth);
6461 
6462   int32_t i;
6463   gchar* fromPtr = mTransparencyBitmap;
6464   gchar* toPtr = newBits;
6465   for (i = 0; i < copyHeight; i++) {
6466     memcpy(toPtr, fromPtr, copyBytes);
6467     fromPtr += oldRowBytes;
6468     toPtr += newRowBytes;
6469   }
6470 
6471   delete[] mTransparencyBitmap;
6472   mTransparencyBitmap = newBits;
6473   mTransparencyBitmapWidth = mBounds.width;
6474   mTransparencyBitmapHeight = mBounds.height;
6475 }
6476 
ChangedMaskBits(gchar * aMaskBits,int32_t aMaskWidth,int32_t aMaskHeight,const nsIntRect & aRect,uint8_t * aAlphas,int32_t aStride)6477 static bool ChangedMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
6478                             int32_t aMaskHeight, const nsIntRect& aRect,
6479                             uint8_t* aAlphas, int32_t aStride) {
6480   int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
6481   int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
6482   for (y = aRect.y; y < yMax; y++) {
6483     gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
6484     uint8_t* alphas = aAlphas;
6485     for (x = aRect.x; x < xMax; x++) {
6486       bool newBit = *alphas > 0x7f;
6487       alphas++;
6488 
6489       gchar maskByte = maskBytes[x >> 3];
6490       bool maskBit = (maskByte & (1 << (x & 7))) != 0;
6491 
6492       if (maskBit != newBit) {
6493         return true;
6494       }
6495     }
6496     aAlphas += aStride;
6497   }
6498 
6499   return false;
6500 }
6501 
UpdateMaskBits(gchar * aMaskBits,int32_t aMaskWidth,int32_t aMaskHeight,const nsIntRect & aRect,uint8_t * aAlphas,int32_t aStride)6502 static void UpdateMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
6503                            int32_t aMaskHeight, const nsIntRect& aRect,
6504                            uint8_t* aAlphas, int32_t aStride) {
6505   int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
6506   int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
6507   for (y = aRect.y; y < yMax; y++) {
6508     gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
6509     uint8_t* alphas = aAlphas;
6510     for (x = aRect.x; x < xMax; x++) {
6511       bool newBit = *alphas > 0x7f;
6512       alphas++;
6513 
6514       gchar mask = 1 << (x & 7);
6515       gchar maskByte = maskBytes[x >> 3];
6516       // Note: '-newBit' turns 0 into 00...00 and 1 into 11...11
6517       maskBytes[x >> 3] = (maskByte & ~mask) | (-newBit & mask);
6518     }
6519     aAlphas += aStride;
6520   }
6521 }
6522 
ApplyTransparencyBitmap()6523 void nsWindow::ApplyTransparencyBitmap() {
6524 #ifdef MOZ_X11
6525   // We use X11 calls where possible, because GDK handles expose events
6526   // for shaped windows in a way that's incompatible with us (Bug 635903).
6527   // It doesn't occur when the shapes are set through X.
6528   Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
6529   Window xDrawable = GDK_WINDOW_XID(mGdkWindow);
6530   Pixmap maskPixmap = XCreateBitmapFromData(
6531       xDisplay, xDrawable, mTransparencyBitmap, mTransparencyBitmapWidth,
6532       mTransparencyBitmapHeight);
6533   XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
6534                     ShapeSet);
6535   XFreePixmap(xDisplay, maskPixmap);
6536 #else
6537   cairo_surface_t* maskBitmap;
6538   maskBitmap = cairo_image_surface_create_for_data(
6539       (unsigned char*)mTransparencyBitmap, CAIRO_FORMAT_A1,
6540       mTransparencyBitmapWidth, mTransparencyBitmapHeight,
6541       GetBitmapStride(mTransparencyBitmapWidth));
6542   if (!maskBitmap) return;
6543 
6544   cairo_region_t* maskRegion = gdk_cairo_region_create_from_surface(maskBitmap);
6545   gtk_widget_shape_combine_region(mShell, maskRegion);
6546   cairo_region_destroy(maskRegion);
6547   cairo_surface_destroy(maskBitmap);
6548 #endif  // MOZ_X11
6549 }
6550 
ClearTransparencyBitmap()6551 void nsWindow::ClearTransparencyBitmap() {
6552   if (!mTransparencyBitmap) return;
6553 
6554   delete[] mTransparencyBitmap;
6555   mTransparencyBitmap = nullptr;
6556   mTransparencyBitmapWidth = 0;
6557   mTransparencyBitmapHeight = 0;
6558 
6559   if (!mShell) return;
6560 
6561 #ifdef MOZ_X11
6562   if (MOZ_UNLIKELY(!mGdkWindow)) {
6563     return;
6564   }
6565 
6566   Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
6567   Window xWindow = gdk_x11_window_get_xid(mGdkWindow);
6568 
6569   XShapeCombineMask(xDisplay, xWindow, ShapeBounding, 0, 0, X11None, ShapeSet);
6570 #endif
6571 }
6572 
UpdateTranslucentWindowAlphaInternal(const nsIntRect & aRect,uint8_t * aAlphas,int32_t aStride)6573 nsresult nsWindow::UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
6574                                                         uint8_t* aAlphas,
6575                                                         int32_t aStride) {
6576   NS_ASSERTION(mIsTransparent, "Window is not transparent");
6577   NS_ASSERTION(!mTransparencyBitmapForTitlebar,
6578                "Transparency bitmap is already used for titlebar rendering");
6579 
6580   if (mTransparencyBitmap == nullptr) {
6581     int32_t size = GetBitmapStride(mBounds.width) * mBounds.height;
6582     mTransparencyBitmap = new gchar[size];
6583     memset(mTransparencyBitmap, 255, size);
6584     mTransparencyBitmapWidth = mBounds.width;
6585     mTransparencyBitmapHeight = mBounds.height;
6586   } else {
6587     ResizeTransparencyBitmap();
6588   }
6589 
6590   nsIntRect rect;
6591   rect.IntersectRect(aRect, nsIntRect(0, 0, mBounds.width, mBounds.height));
6592 
6593   if (!ChangedMaskBits(mTransparencyBitmap, mBounds.width, mBounds.height, rect,
6594                        aAlphas, aStride)) {
6595     // skip the expensive stuff if the mask bits haven't changed; hopefully
6596     // this is the common case
6597     return NS_OK;
6598   }
6599 
6600   UpdateMaskBits(mTransparencyBitmap, mBounds.width, mBounds.height, rect,
6601                  aAlphas, aStride);
6602 
6603   if (!mNeedsShow) {
6604     ApplyTransparencyBitmap();
6605   }
6606   return NS_OK;
6607 }
6608 
6609 #define TITLEBAR_HEIGHT 10
6610 
GetTitlebarRect()6611 LayoutDeviceIntRect nsWindow::GetTitlebarRect() {
6612   if (!mGdkWindow || !mDrawInTitlebar) {
6613     return LayoutDeviceIntRect();
6614   }
6615 
6616   int height = 0;
6617   if (DoDrawTilebarCorners()) {
6618     height = GdkCeiledScaleFactor() * TITLEBAR_HEIGHT;
6619   }
6620   return LayoutDeviceIntRect(0, 0, mBounds.width, height);
6621 }
6622 
UpdateTitlebarTransparencyBitmap()6623 void nsWindow::UpdateTitlebarTransparencyBitmap() {
6624   NS_ASSERTION(mTransparencyBitmapForTitlebar,
6625                "Transparency bitmap is already used to draw window shape");
6626 
6627   if (!mGdkWindow || !mDrawInTitlebar ||
6628       (mBounds.width == mTransparencyBitmapWidth &&
6629        mBounds.height == mTransparencyBitmapHeight)) {
6630     return;
6631   }
6632 
6633   bool maskCreate =
6634       !mTransparencyBitmap || mBounds.width > mTransparencyBitmapWidth;
6635 
6636   bool maskUpdate =
6637       !mTransparencyBitmap || mBounds.width != mTransparencyBitmapWidth;
6638 
6639   LayoutDeviceIntCoord radius = GetTitlebarRadius();
6640   if (maskCreate) {
6641     delete[] mTransparencyBitmap;
6642     int32_t size = GetBitmapStride(mBounds.width) * radius;
6643     mTransparencyBitmap = new gchar[size];
6644     mTransparencyBitmapWidth = mBounds.width;
6645   } else {
6646     mTransparencyBitmapWidth = mBounds.width;
6647   }
6648   mTransparencyBitmapHeight = mBounds.height;
6649 
6650   if (maskUpdate) {
6651     cairo_surface_t* surface = cairo_image_surface_create(
6652         CAIRO_FORMAT_A8, mTransparencyBitmapWidth, radius);
6653     if (!surface) return;
6654 
6655     cairo_t* cr = cairo_create(surface);
6656 
6657     GtkWidgetState state;
6658     memset((void*)&state, 0, sizeof(state));
6659     GdkRectangle rect = {0, 0, mTransparencyBitmapWidth, radius};
6660 
6661     moz_gtk_widget_paint(MOZ_GTK_HEADER_BAR, cr, &rect, &state, 0,
6662                          GTK_TEXT_DIR_NONE);
6663 
6664     cairo_destroy(cr);
6665     cairo_surface_mark_dirty(surface);
6666     cairo_surface_flush(surface);
6667 
6668     UpdateMaskBits(mTransparencyBitmap, mTransparencyBitmapWidth, radius,
6669                    nsIntRect(0, 0, mTransparencyBitmapWidth, radius),
6670                    cairo_image_surface_get_data(surface),
6671                    cairo_format_stride_for_width(CAIRO_FORMAT_A8,
6672                                                  mTransparencyBitmapWidth));
6673 
6674     cairo_surface_destroy(surface);
6675   }
6676 
6677   if (!mNeedsShow) {
6678     Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
6679     Window xDrawable = GDK_WINDOW_XID(mGdkWindow);
6680 
6681     Pixmap maskPixmap =
6682         XCreateBitmapFromData(xDisplay, xDrawable, mTransparencyBitmap,
6683                               mTransparencyBitmapWidth, radius);
6684 
6685     XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
6686                       ShapeSet);
6687 
6688     if (mTransparencyBitmapHeight > radius) {
6689       XRectangle rect = {0, 0, (unsigned short)mTransparencyBitmapWidth,
6690                          (unsigned short)(mTransparencyBitmapHeight - radius)};
6691       XShapeCombineRectangles(xDisplay, xDrawable, ShapeBounding, 0, radius,
6692                               &rect, 1, ShapeUnion, 0);
6693     }
6694 
6695     XFreePixmap(xDisplay, maskPixmap);
6696   }
6697 }
6698 
GrabPointer(guint32 aTime)6699 void nsWindow::GrabPointer(guint32 aTime) {
6700   LOG("GrabPointer time=0x%08x retry=%d\n", (unsigned int)aTime,
6701       mRetryPointerGrab);
6702 
6703   // Don't to the grab on Wayland as it causes a regression
6704   // from Bug 1377084.
6705   if (mIsDestroyed || GdkIsWaylandDisplay()) {
6706     return;
6707   }
6708 
6709   mRetryPointerGrab = false;
6710   sRetryGrabTime = aTime;
6711 
6712   // If the window isn't visible, just set the flag to retry the
6713   // grab.  When this window becomes visible, the grab will be
6714   // retried.
6715   if (!mHasMappedToplevel || !mGdkWindow) {
6716     LOG("  quit, window not visible, mHasMappedToplevel = %d, mGdkWindow = %p",
6717         mHasMappedToplevel, mGdkWindow);
6718     mRetryPointerGrab = true;
6719     return;
6720   }
6721 
6722   gint retval;
6723   // Note that we need GDK_TOUCH_MASK below to work around a GDK/X11 bug that
6724   // causes touch events that would normally be received by this client on
6725   // other windows to be discarded during the grab.
6726   retval = gdk_pointer_grab(
6727       mGdkWindow, TRUE,
6728       (GdkEventMask)(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
6729                      GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
6730                      GDK_POINTER_MOTION_MASK | GDK_TOUCH_MASK),
6731       (GdkWindow*)nullptr, nullptr, aTime);
6732 
6733   if (retval == GDK_GRAB_NOT_VIEWABLE) {
6734     LOG("  failed: window not viewable; will retry\n");
6735     mRetryPointerGrab = true;
6736   } else if (retval != GDK_GRAB_SUCCESS) {
6737     LOG("  pointer grab failed: %i\n", retval);
6738     // A failed grab indicates that another app has grabbed the pointer.
6739     // Check for rollup now, because, without the grab, we likely won't
6740     // get subsequent button press events. Do this with an event so that
6741     // popups don't rollup while potentially adjusting the grab for
6742     // this popup.
6743     nsCOMPtr<nsIRunnable> event =
6744         NewRunnableMethod("nsWindow::CheckForRollupDuringGrab", this,
6745                           &nsWindow::CheckForRollupDuringGrab);
6746     NS_DispatchToCurrentThread(event.forget());
6747   }
6748 }
6749 
ReleaseGrabs(void)6750 void nsWindow::ReleaseGrabs(void) {
6751   LOG("ReleaseGrabs\n");
6752 
6753   mRetryPointerGrab = false;
6754 
6755   if (GdkIsWaylandDisplay()) {
6756     // Don't to the ungrab on Wayland as it causes a regression
6757     // from Bug 1377084.
6758     return;
6759   }
6760 
6761   gdk_pointer_ungrab(GDK_CURRENT_TIME);
6762 }
6763 
GetToplevelWidget()6764 GtkWidget* nsWindow::GetToplevelWidget() { return mShell; }
6765 
GetToplevelGdkWindow()6766 GdkWindow* nsWindow::GetToplevelGdkWindow() {
6767   return gtk_widget_get_window(mShell);
6768 }
6769 
GetContainerWindow()6770 nsWindow* nsWindow::GetContainerWindow() {
6771   GtkWidget* owningWidget = GTK_WIDGET(mContainer);
6772   if (!owningWidget) return nullptr;
6773 
6774   nsWindow* window = get_window_for_gtk_widget(owningWidget);
6775   NS_ASSERTION(window, "No nsWindow for container widget");
6776   return window;
6777 }
6778 
SetUrgencyHint(GtkWidget * top_window,bool state)6779 void nsWindow::SetUrgencyHint(GtkWidget* top_window, bool state) {
6780   LOG("  nsWindow::SetUrgencyHint widget %p\n", top_window);
6781 
6782   if (!top_window) return;
6783 
6784   // TODO: Use xdg-activation on Wayland?
6785   gdk_window_set_urgency_hint(gtk_widget_get_window(top_window), state);
6786 }
6787 
SetDefaultIcon(void)6788 void nsWindow::SetDefaultIcon(void) { SetIcon(u"default"_ns); }
6789 
ConvertBorderStyles(nsBorderStyle aStyle)6790 gint nsWindow::ConvertBorderStyles(nsBorderStyle aStyle) {
6791   gint w = 0;
6792 
6793   if (aStyle == eBorderStyle_default) return -1;
6794 
6795   // note that we don't handle eBorderStyle_close yet
6796   if (aStyle & eBorderStyle_all) w |= GDK_DECOR_ALL;
6797   if (aStyle & eBorderStyle_border) w |= GDK_DECOR_BORDER;
6798   if (aStyle & eBorderStyle_resizeh) w |= GDK_DECOR_RESIZEH;
6799   if (aStyle & eBorderStyle_title) w |= GDK_DECOR_TITLE;
6800   if (aStyle & eBorderStyle_menu) w |= GDK_DECOR_MENU;
6801   if (aStyle & eBorderStyle_minimize) w |= GDK_DECOR_MINIMIZE;
6802   if (aStyle & eBorderStyle_maximize) w |= GDK_DECOR_MAXIMIZE;
6803 
6804   return w;
6805 }
6806 
6807 class FullscreenTransitionWindow final : public nsISupports {
6808  public:
6809   NS_DECL_ISUPPORTS
6810 
6811   explicit FullscreenTransitionWindow(GtkWidget* aWidget);
6812 
6813   GtkWidget* mWindow;
6814 
6815  private:
6816   ~FullscreenTransitionWindow();
6817 };
6818 
NS_IMPL_ISUPPORTS0(FullscreenTransitionWindow)6819 NS_IMPL_ISUPPORTS0(FullscreenTransitionWindow)
6820 
6821 FullscreenTransitionWindow::FullscreenTransitionWindow(GtkWidget* aWidget) {
6822   mWindow = gtk_window_new(GTK_WINDOW_POPUP);
6823   GtkWindow* gtkWin = GTK_WINDOW(mWindow);
6824 
6825   gtk_window_set_type_hint(gtkWin, GDK_WINDOW_TYPE_HINT_SPLASHSCREEN);
6826   gtk_window_set_transient_for(gtkWin, GTK_WINDOW(aWidget));
6827   gtk_window_set_decorated(gtkWin, false);
6828 
6829   GdkWindow* gdkWin = gtk_widget_get_window(aWidget);
6830   GdkScreen* screen = gtk_widget_get_screen(aWidget);
6831   gint monitorNum = gdk_screen_get_monitor_at_window(screen, gdkWin);
6832   GdkRectangle monitorRect;
6833   gdk_screen_get_monitor_geometry(screen, monitorNum, &monitorRect);
6834   gtk_window_set_screen(gtkWin, screen);
6835   gtk_window_move(gtkWin, monitorRect.x, monitorRect.y);
6836   MOZ_ASSERT(monitorRect.width > 0 && monitorRect.height > 0,
6837              "Can't resize window smaller than 1x1.");
6838   gtk_window_resize(gtkWin, monitorRect.width, monitorRect.height);
6839 
6840   GdkRGBA bgColor;
6841   bgColor.red = bgColor.green = bgColor.blue = 0.0;
6842   bgColor.alpha = 1.0;
6843   gtk_widget_override_background_color(mWindow, GTK_STATE_FLAG_NORMAL,
6844                                        &bgColor);
6845 
6846   gtk_widget_set_opacity(mWindow, 0.0);
6847   gtk_widget_show(mWindow);
6848 }
6849 
~FullscreenTransitionWindow()6850 FullscreenTransitionWindow::~FullscreenTransitionWindow() {
6851   gtk_widget_destroy(mWindow);
6852 }
6853 
6854 class FullscreenTransitionData {
6855  public:
FullscreenTransitionData(nsIWidget::FullscreenTransitionStage aStage,uint16_t aDuration,nsIRunnable * aCallback,FullscreenTransitionWindow * aWindow)6856   FullscreenTransitionData(nsIWidget::FullscreenTransitionStage aStage,
6857                            uint16_t aDuration, nsIRunnable* aCallback,
6858                            FullscreenTransitionWindow* aWindow)
6859       : mStage(aStage),
6860         mStartTime(TimeStamp::Now()),
6861         mDuration(TimeDuration::FromMilliseconds(aDuration)),
6862         mCallback(aCallback),
6863         mWindow(aWindow) {}
6864 
6865   static const guint sInterval = 1000 / 30;  // 30fps
6866   static gboolean TimeoutCallback(gpointer aData);
6867 
6868  private:
6869   nsIWidget::FullscreenTransitionStage mStage;
6870   TimeStamp mStartTime;
6871   TimeDuration mDuration;
6872   nsCOMPtr<nsIRunnable> mCallback;
6873   RefPtr<FullscreenTransitionWindow> mWindow;
6874 };
6875 
6876 /* static */
TimeoutCallback(gpointer aData)6877 gboolean FullscreenTransitionData::TimeoutCallback(gpointer aData) {
6878   bool finishing = false;
6879   auto* data = static_cast<FullscreenTransitionData*>(aData);
6880   gdouble opacity = (TimeStamp::Now() - data->mStartTime) / data->mDuration;
6881   if (opacity >= 1.0) {
6882     opacity = 1.0;
6883     finishing = true;
6884   }
6885   if (data->mStage == nsIWidget::eAfterFullscreenToggle) {
6886     opacity = 1.0 - opacity;
6887   }
6888   gtk_widget_set_opacity(data->mWindow->mWindow, opacity);
6889 
6890   if (!finishing) {
6891     return TRUE;
6892   }
6893   NS_DispatchToMainThread(data->mCallback.forget());
6894   delete data;
6895   return FALSE;
6896 }
6897 
6898 /* virtual */
PrepareForFullscreenTransition(nsISupports ** aData)6899 bool nsWindow::PrepareForFullscreenTransition(nsISupports** aData) {
6900   if (!mCompositedScreen) {
6901     return false;
6902   }
6903   *aData = do_AddRef(new FullscreenTransitionWindow(mShell)).take();
6904   return true;
6905 }
6906 
6907 /* virtual */
PerformFullscreenTransition(FullscreenTransitionStage aStage,uint16_t aDuration,nsISupports * aData,nsIRunnable * aCallback)6908 void nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
6909                                            uint16_t aDuration,
6910                                            nsISupports* aData,
6911                                            nsIRunnable* aCallback) {
6912   auto* data = static_cast<FullscreenTransitionWindow*>(aData);
6913   // This will be released at the end of the last timeout callback for it.
6914   auto* transitionData =
6915       new FullscreenTransitionData(aStage, aDuration, aCallback, data);
6916   g_timeout_add_full(G_PRIORITY_HIGH, FullscreenTransitionData::sInterval,
6917                      FullscreenTransitionData::TimeoutCallback, transitionData,
6918                      nullptr);
6919 }
6920 
GetWidgetScreen()6921 already_AddRefed<nsIScreen> nsWindow::GetWidgetScreen() {
6922   // Wayland can read screen directly
6923   if (GdkIsWaylandDisplay()) {
6924     RefPtr<nsIScreen> screen = ScreenHelperGTK::GetScreenForWindow(this);
6925     if (screen) {
6926       return screen.forget();
6927     }
6928   }
6929 
6930   nsCOMPtr<nsIScreenManager> screenManager;
6931   screenManager = do_GetService("@mozilla.org/gfx/screenmanager;1");
6932   if (!screenManager) {
6933     return nullptr;
6934   }
6935 
6936   // GetScreenBounds() is slow for the GTK port so we override and use
6937   // mBounds directly.
6938   LayoutDeviceIntRect bounds = mBounds;
6939   DesktopIntRect deskBounds = RoundedToInt(bounds / GetDesktopToDeviceScale());
6940   nsCOMPtr<nsIScreen> screen;
6941   screenManager->ScreenForRect(deskBounds.x, deskBounds.y, deskBounds.width,
6942                                deskBounds.height, getter_AddRefs(screen));
6943   return screen.forget();
6944 }
6945 
GetVsyncSource()6946 RefPtr<VsyncSource> nsWindow::GetVsyncSource() {
6947 #ifdef MOZ_WAYLAND
6948   if (mWaylandVsyncSource) {
6949     return mWaylandVsyncSource;
6950   }
6951 #endif
6952   return nullptr;
6953 }
6954 
SynchronouslyRepaintOnResize()6955 bool nsWindow::SynchronouslyRepaintOnResize() {
6956   if (GdkIsWaylandDisplay()) {
6957     // See Bug 1734368
6958     // Don't request synchronous repaint on HW accelerated backend - mesa can be
6959     // deadlocked when it's missing back buffer and main event loop is blocked.
6960     return false;
6961   }
6962 
6963   // default is synced repaint.
6964   return true;
6965 }
6966 
IsFullscreenSupported(GtkWidget * aShell)6967 static bool IsFullscreenSupported(GtkWidget* aShell) {
6968 #ifdef MOZ_X11
6969   GdkScreen* screen = gtk_widget_get_screen(aShell);
6970   GdkAtom atom = gdk_atom_intern("_NET_WM_STATE_FULLSCREEN", FALSE);
6971   return gdk_x11_screen_supports_net_wm_hint(screen, atom);
6972 #elif
6973   return true;
6974 #endif
6975 }
6976 
MakeFullScreen(bool aFullScreen)6977 nsresult nsWindow::MakeFullScreen(bool aFullScreen) {
6978   LOG("nsWindow::MakeFullScreen aFullScreen %d\n", aFullScreen);
6979 
6980   if (GdkIsX11Display() && !IsFullscreenSupported(mShell)) {
6981     return NS_ERROR_NOT_AVAILABLE;
6982   }
6983 
6984   bool wasFullscreen = mSizeState == nsSizeMode_Fullscreen;
6985   if (aFullScreen != wasFullscreen && mWidgetListener) {
6986     mWidgetListener->FullscreenWillChange(aFullScreen);
6987   }
6988 
6989   if (aFullScreen) {
6990     if (mSizeMode != nsSizeMode_Fullscreen) mLastSizeMode = mSizeMode;
6991 
6992     mSizeMode = nsSizeMode_Fullscreen;
6993 
6994     if (mIsPIPWindow) {
6995       gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_NORMAL);
6996       if (gUseAspectRatio) {
6997         mAspectRatioSaved = mAspectRatio;
6998         mAspectRatio = 0.0f;
6999         ApplySizeConstraints();
7000       }
7001     }
7002 
7003     gtk_window_fullscreen(GTK_WINDOW(mShell));
7004   } else {
7005     mSizeMode = mLastSizeMode;
7006     gtk_window_unfullscreen(GTK_WINDOW(mShell));
7007 
7008     if (mIsPIPWindow) {
7009       gtk_window_set_type_hint(GTK_WINDOW(mShell),
7010                                GDK_WINDOW_TYPE_HINT_UTILITY);
7011       if (gUseAspectRatio) {
7012         mAspectRatio = mAspectRatioSaved;
7013         // ApplySizeConstraints();
7014       }
7015     }
7016   }
7017 
7018   NS_ASSERTION(mLastSizeMode != nsSizeMode_Fullscreen,
7019                "mLastSizeMode should never be fullscreen");
7020   return NS_OK;
7021 }
7022 
SetWindowDecoration(nsBorderStyle aStyle)7023 void nsWindow::SetWindowDecoration(nsBorderStyle aStyle) {
7024   LOG("nsWindow::SetWindowDecoration() Border style %x\n", aStyle);
7025 
7026   // We can't use mGdkWindow directly here as it can be
7027   // derived from mContainer which is not a top-level GdkWindow.
7028   GdkWindow* window = gtk_widget_get_window(mShell);
7029 
7030   // Sawfish, metacity, and presumably other window managers get
7031   // confused if we change the window decorations while the window
7032   // is visible.
7033   bool wasVisible = false;
7034   if (gdk_window_is_visible(window)) {
7035     gdk_window_hide(window);
7036     wasVisible = true;
7037   }
7038 
7039   gint wmd = ConvertBorderStyles(aStyle);
7040   if (wmd != -1) gdk_window_set_decorations(window, (GdkWMDecoration)wmd);
7041 
7042   if (wasVisible) gdk_window_show(window);
7043 
7044     // For some window managers, adding or removing window decorations
7045     // requires unmapping and remapping our toplevel window.  Go ahead
7046     // and flush the queue here so that we don't end up with a BadWindow
7047     // error later when this happens (when the persistence timer fires
7048     // and GetWindowPos is called)
7049 #ifdef MOZ_X11
7050   if (GdkIsX11Display()) {
7051     XSync(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), X11False);
7052   } else
7053 #endif /* MOZ_X11 */
7054   {
7055     gdk_flush();
7056   }
7057 }
7058 
HideWindowChrome(bool aShouldHide)7059 void nsWindow::HideWindowChrome(bool aShouldHide) {
7060   SetWindowDecoration(aShouldHide ? eBorderStyle_none : mBorderStyle);
7061 }
7062 
CheckForRollup(gdouble aMouseX,gdouble aMouseY,bool aIsWheel,bool aAlwaysRollup)7063 bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
7064                               bool aAlwaysRollup) {
7065   nsIRollupListener* rollupListener = GetActiveRollupListener();
7066   nsCOMPtr<nsIWidget> rollupWidget;
7067   if (rollupListener) {
7068     rollupWidget = rollupListener->GetRollupWidget();
7069   }
7070   if (!rollupWidget) {
7071     nsBaseWidget::gRollupListener = nullptr;
7072     return false;
7073   }
7074 
7075   bool retVal = false;
7076   auto* currentPopup =
7077       (GdkWindow*)rollupWidget->GetNativeData(NS_NATIVE_WINDOW);
7078   if (aAlwaysRollup || !is_mouse_in_window(currentPopup, aMouseX, aMouseY)) {
7079     bool rollup = true;
7080     if (aIsWheel) {
7081       rollup = rollupListener->ShouldRollupOnMouseWheelEvent();
7082       retVal = rollupListener->ShouldConsumeOnMouseWheelEvent();
7083     }
7084     // if we're dealing with menus, we probably have submenus and
7085     // we don't want to rollup if the click is in a parent menu of
7086     // the current submenu
7087     uint32_t popupsToRollup = UINT32_MAX;
7088     if (!aAlwaysRollup) {
7089       AutoTArray<nsIWidget*, 5> widgetChain;
7090       uint32_t sameTypeCount =
7091           rollupListener->GetSubmenuWidgetChain(&widgetChain);
7092       for (unsigned long i = 0; i < widgetChain.Length(); ++i) {
7093         nsIWidget* widget = widgetChain[i];
7094         auto* currWindow = (GdkWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
7095         if (is_mouse_in_window(currWindow, aMouseX, aMouseY)) {
7096           // don't roll up if the mouse event occurred within a
7097           // menu of the same type. If the mouse event occurred
7098           // in a menu higher than that, roll up, but pass the
7099           // number of popups to Rollup so that only those of the
7100           // same type close up.
7101           if (i < sameTypeCount) {
7102             rollup = false;
7103           } else {
7104             popupsToRollup = sameTypeCount;
7105           }
7106           break;
7107         }
7108       }  // foreach parent menu widget
7109     }    // if rollup listener knows about menus
7110 
7111     // if we've determined that we should still rollup, do it.
7112     bool usePoint = !aIsWheel && !aAlwaysRollup;
7113     LayoutDeviceIntPoint point;
7114     if (usePoint) {
7115       point = GdkEventCoordsToDevicePixels(aMouseX, aMouseY);
7116     }
7117     if (rollup &&
7118         rollupListener->Rollup(popupsToRollup, true,
7119                                usePoint ? &point : nullptr, nullptr)) {
7120       retVal = true;
7121     }
7122   }
7123   return retVal;
7124 }
7125 
7126 /* static */
DragInProgress(void)7127 bool nsWindow::DragInProgress(void) {
7128   nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID);
7129   if (!dragService) {
7130     return false;
7131   }
7132 
7133   nsCOMPtr<nsIDragSession> currentDragSession;
7134   dragService->GetCurrentSession(getter_AddRefs(currentDragSession));
7135 
7136   return currentDragSession != nullptr;
7137 }
7138 
7139 // This is an ugly workaround for
7140 // https://bugzilla.mozilla.org/show_bug.cgi?id=1622107
7141 // We try to detect when Wayland compositor / gtk fails to deliver
7142 // info about finished D&D operations and cancel it on our own.
WaylandDragWorkaround(GdkEventButton * aEvent)7143 MOZ_CAN_RUN_SCRIPT static void WaylandDragWorkaround(GdkEventButton* aEvent) {
7144   static int buttonPressCountWithDrag = 0;
7145 
7146   // We track only left button state as Firefox performs D&D on left
7147   // button only.
7148   if (aEvent->button != 1 || aEvent->type != GDK_BUTTON_PRESS) {
7149     return;
7150   }
7151 
7152   nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID);
7153   if (!dragService) {
7154     return;
7155   }
7156   nsCOMPtr<nsIDragSession> currentDragSession;
7157   dragService->GetCurrentSession(getter_AddRefs(currentDragSession));
7158 
7159   if (currentDragSession != nullptr) {
7160     buttonPressCountWithDrag++;
7161     if (buttonPressCountWithDrag > 1) {
7162       NS_WARNING(
7163           "Quit unfinished Wayland Drag and Drop operation. Buggy Wayland "
7164           "compositor?");
7165       buttonPressCountWithDrag = 0;
7166       dragService->EndDragSession(false, 0);
7167     }
7168   }
7169 }
7170 
is_mouse_in_window(GdkWindow * aWindow,gdouble aMouseX,gdouble aMouseY)7171 static bool is_mouse_in_window(GdkWindow* aWindow, gdouble aMouseX,
7172                                gdouble aMouseY) {
7173   GdkWindow* window = aWindow;
7174   if (!window) {
7175     return false;
7176   }
7177 
7178   gint x = 0;
7179   gint y = 0;
7180   gint w, h;
7181 
7182   gint offsetX = 0;
7183   gint offsetY = 0;
7184 
7185   while (window) {
7186     gint tmpX = 0;
7187     gint tmpY = 0;
7188 
7189     gdk_window_get_position(window, &tmpX, &tmpY);
7190     GtkWidget* widget = get_gtk_widget_for_gdk_window(window);
7191 
7192     // if this is a window, compute x and y given its origin and our
7193     // offset
7194     if (GTK_IS_WINDOW(widget)) {
7195       x = tmpX + offsetX;
7196       y = tmpY + offsetY;
7197       break;
7198     }
7199 
7200     offsetX += tmpX;
7201     offsetY += tmpY;
7202     window = gdk_window_get_parent(window);
7203   }
7204 
7205   w = gdk_window_get_width(aWindow);
7206   h = gdk_window_get_height(aWindow);
7207 
7208   return (aMouseX > x && aMouseX < x + w && aMouseY > y && aMouseY < y + h);
7209 }
7210 
get_window_for_gtk_widget(GtkWidget * widget)7211 static nsWindow* get_window_for_gtk_widget(GtkWidget* widget) {
7212   gpointer user_data = g_object_get_data(G_OBJECT(widget), "nsWindow");
7213 
7214   return static_cast<nsWindow*>(user_data);
7215 }
7216 
get_window_for_gdk_window(GdkWindow * window)7217 static nsWindow* get_window_for_gdk_window(GdkWindow* window) {
7218   gpointer user_data = g_object_get_data(G_OBJECT(window), "nsWindow");
7219 
7220   return static_cast<nsWindow*>(user_data);
7221 }
7222 
get_gtk_widget_for_gdk_window(GdkWindow * window)7223 static GtkWidget* get_gtk_widget_for_gdk_window(GdkWindow* window) {
7224   gpointer user_data = nullptr;
7225   gdk_window_get_user_data(window, &user_data);
7226 
7227   return GTK_WIDGET(user_data);
7228 }
7229 
get_gtk_cursor(nsCursor aCursor)7230 static GdkCursor* get_gtk_cursor(nsCursor aCursor) {
7231   GdkCursor* gdkcursor = nullptr;
7232   uint8_t newType = 0xff;
7233 
7234   if ((gdkcursor = gCursorCache[aCursor])) {
7235     return gdkcursor;
7236   }
7237 
7238   GdkDisplay* defaultDisplay = gdk_display_get_default();
7239 
7240   // The strategy here is to use standard GDK cursors, and, if not available,
7241   // load by standard name with gdk_cursor_new_from_name.
7242   // Spec is here: http://www.freedesktop.org/wiki/Specifications/cursor-spec/
7243   switch (aCursor) {
7244     case eCursor_standard:
7245       gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_PTR);
7246       break;
7247     case eCursor_wait:
7248       gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_WATCH);
7249       break;
7250     case eCursor_select:
7251       gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_XTERM);
7252       break;
7253     case eCursor_hyperlink:
7254       gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_HAND2);
7255       break;
7256     case eCursor_n_resize:
7257       gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_SIDE);
7258       break;
7259     case eCursor_s_resize:
7260       gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_SIDE);
7261       break;
7262     case eCursor_w_resize:
7263       gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_SIDE);
7264       break;
7265     case eCursor_e_resize:
7266       gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_RIGHT_SIDE);
7267       break;
7268     case eCursor_nw_resize:
7269       gdkcursor =
7270           gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_LEFT_CORNER);
7271       break;
7272     case eCursor_se_resize:
7273       gdkcursor =
7274           gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_RIGHT_CORNER);
7275       break;
7276     case eCursor_ne_resize:
7277       gdkcursor =
7278           gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_RIGHT_CORNER);
7279       break;
7280     case eCursor_sw_resize:
7281       gdkcursor =
7282           gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_LEFT_CORNER);
7283       break;
7284     case eCursor_crosshair:
7285       gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_CROSSHAIR);
7286       break;
7287     case eCursor_move:
7288       gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_FLEUR);
7289       break;
7290     case eCursor_help:
7291       gdkcursor =
7292           gdk_cursor_new_for_display(defaultDisplay, GDK_QUESTION_ARROW);
7293       break;
7294     case eCursor_copy:  // CSS3
7295       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "copy");
7296       if (!gdkcursor) newType = MOZ_CURSOR_COPY;
7297       break;
7298     case eCursor_alias:
7299       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "alias");
7300       if (!gdkcursor) newType = MOZ_CURSOR_ALIAS;
7301       break;
7302     case eCursor_context_menu:
7303       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "context-menu");
7304       if (!gdkcursor) newType = MOZ_CURSOR_CONTEXT_MENU;
7305       break;
7306     case eCursor_cell:
7307       gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_PLUS);
7308       break;
7309     // Those two aren’t standardized. Trying both KDE’s and GNOME’s names
7310     case eCursor_grab:
7311       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "openhand");
7312       if (!gdkcursor) newType = MOZ_CURSOR_HAND_GRAB;
7313       break;
7314     case eCursor_grabbing:
7315       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "closedhand");
7316       if (!gdkcursor) {
7317         gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "grabbing");
7318       }
7319       if (!gdkcursor) newType = MOZ_CURSOR_HAND_GRABBING;
7320       break;
7321     case eCursor_spinning:
7322       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "progress");
7323       if (!gdkcursor) newType = MOZ_CURSOR_SPINNING;
7324       break;
7325     case eCursor_zoom_in:
7326       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "zoom-in");
7327       if (!gdkcursor) newType = MOZ_CURSOR_ZOOM_IN;
7328       break;
7329     case eCursor_zoom_out:
7330       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "zoom-out");
7331       if (!gdkcursor) newType = MOZ_CURSOR_ZOOM_OUT;
7332       break;
7333     case eCursor_not_allowed:
7334       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "not-allowed");
7335       if (!gdkcursor) {  // nonstandard, yet common
7336         gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "crossed_circle");
7337       }
7338       if (!gdkcursor) newType = MOZ_CURSOR_NOT_ALLOWED;
7339       break;
7340     case eCursor_no_drop:
7341       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "no-drop");
7342       if (!gdkcursor) {  // this nonstandard sequence makes it work on KDE and
7343                          // GNOME
7344         gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "forbidden");
7345       }
7346       if (!gdkcursor) {
7347         gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "circle");
7348       }
7349       if (!gdkcursor) newType = MOZ_CURSOR_NOT_ALLOWED;
7350       break;
7351     case eCursor_vertical_text:
7352       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "vertical-text");
7353       if (!gdkcursor) {
7354         newType = MOZ_CURSOR_VERTICAL_TEXT;
7355       }
7356       break;
7357     case eCursor_all_scroll:
7358       gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_FLEUR);
7359       break;
7360     case eCursor_nesw_resize:
7361       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "size_bdiag");
7362       if (!gdkcursor) newType = MOZ_CURSOR_NESW_RESIZE;
7363       break;
7364     case eCursor_nwse_resize:
7365       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "size_fdiag");
7366       if (!gdkcursor) newType = MOZ_CURSOR_NWSE_RESIZE;
7367       break;
7368     case eCursor_ns_resize:
7369       gdkcursor =
7370           gdk_cursor_new_for_display(defaultDisplay, GDK_SB_V_DOUBLE_ARROW);
7371       break;
7372     case eCursor_ew_resize:
7373       gdkcursor =
7374           gdk_cursor_new_for_display(defaultDisplay, GDK_SB_H_DOUBLE_ARROW);
7375       break;
7376     // Here, two better fitting cursors exist in some cursor themes. Try those
7377     // first
7378     case eCursor_row_resize:
7379       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "split_v");
7380       if (!gdkcursor) {
7381         gdkcursor =
7382             gdk_cursor_new_for_display(defaultDisplay, GDK_SB_V_DOUBLE_ARROW);
7383       }
7384       break;
7385     case eCursor_col_resize:
7386       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "split_h");
7387       if (!gdkcursor) {
7388         gdkcursor =
7389             gdk_cursor_new_for_display(defaultDisplay, GDK_SB_H_DOUBLE_ARROW);
7390       }
7391       break;
7392     case eCursor_none:
7393       newType = MOZ_CURSOR_NONE;
7394       break;
7395     default:
7396       NS_ASSERTION(aCursor, "Invalid cursor type");
7397       gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_PTR);
7398       break;
7399   }
7400 
7401   // If by now we don't have a xcursor, this means we have to make a custom
7402   // one. First, we try creating a named cursor based on the hash of our
7403   // custom bitmap, as libXcursor has some magic to convert bitmapped cursors
7404   // to themed cursors
7405   if (newType != 0xFF && GtkCursors[newType].hash) {
7406     gdkcursor =
7407         gdk_cursor_new_from_name(defaultDisplay, GtkCursors[newType].hash);
7408   }
7409 
7410   // If we still don't have a xcursor, we now really create a bitmap cursor
7411   if (newType != 0xff && !gdkcursor) {
7412     GdkPixbuf* cursor_pixbuf =
7413         gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32);
7414     if (!cursor_pixbuf) return nullptr;
7415 
7416     guchar* data = gdk_pixbuf_get_pixels(cursor_pixbuf);
7417 
7418     // Read data from GtkCursors and compose RGBA surface from 1bit bitmap and
7419     // mask GtkCursors bits and mask are 32x32 monochrome bitmaps (1 bit for
7420     // each pixel) so it's 128 byte array (4 bytes for are one bitmap row and
7421     // there are 32 rows here).
7422     const unsigned char* bits = GtkCursors[newType].bits;
7423     const unsigned char* mask_bits = GtkCursors[newType].mask_bits;
7424 
7425     for (int i = 0; i < 128; i++) {
7426       char bit = (char)*bits++;
7427       char mask = (char)*mask_bits++;
7428       for (int j = 0; j < 8; j++) {
7429         unsigned char pix = ~(((bit >> j) & 0x01) * 0xff);
7430         *data++ = pix;
7431         *data++ = pix;
7432         *data++ = pix;
7433         *data++ = (((mask >> j) & 0x01) * 0xff);
7434       }
7435     }
7436 
7437     gdkcursor = gdk_cursor_new_from_pixbuf(
7438         gdk_display_get_default(), cursor_pixbuf, GtkCursors[newType].hot_x,
7439         GtkCursors[newType].hot_y);
7440 
7441     g_object_unref(cursor_pixbuf);
7442   }
7443 
7444   gCursorCache[aCursor] = gdkcursor;
7445 
7446   return gdkcursor;
7447 }
7448 
7449 // gtk callbacks
7450 
draw_window_of_widget(GtkWidget * widget,GdkWindow * aWindow,cairo_t * cr)7451 void draw_window_of_widget(GtkWidget* widget, GdkWindow* aWindow, cairo_t* cr) {
7452   if (gtk_cairo_should_draw_window(cr, aWindow)) {
7453     RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
7454     if (!window) {
7455       NS_WARNING("Cannot get nsWindow from GtkWidget");
7456     } else {
7457       cairo_save(cr);
7458       gtk_cairo_transform_to_window(cr, widget, aWindow);
7459       // TODO - window->OnExposeEvent() can destroy this or other windows,
7460       // do we need to handle it somehow?
7461       window->OnExposeEvent(cr);
7462       cairo_restore(cr);
7463     }
7464   }
7465 }
7466 
7467 /* static */
expose_event_cb(GtkWidget * widget,cairo_t * cr)7468 gboolean expose_event_cb(GtkWidget* widget, cairo_t* cr) {
7469   draw_window_of_widget(widget, gtk_widget_get_window(widget), cr);
7470 
7471   // A strong reference is already held during "draw" signal emission,
7472   // but GTK+ 3.4 wants the object to live a little longer than that
7473   // (bug 1225970).
7474   g_object_ref(widget);
7475   g_idle_add(
7476       [](gpointer data) -> gboolean {
7477         g_object_unref(data);
7478         return G_SOURCE_REMOVE;
7479       },
7480       widget);
7481 
7482   return FALSE;
7483 }
7484 
configure_event_cb(GtkWidget * widget,GdkEventConfigure * event)7485 static gboolean configure_event_cb(GtkWidget* widget,
7486                                    GdkEventConfigure* event) {
7487   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
7488   if (!window) {
7489     return FALSE;
7490   }
7491 
7492   return window->OnConfigureEvent(widget, event);
7493 }
7494 
7495 // Some Gtk widget code may call gtk_widget_unrealize() which destroys
7496 // mGdkWindow. We need to listen on this signal and re-create
7497 // mGdkWindow when we're already mapped.
widget_map_cb(GtkWidget * widget)7498 static void widget_map_cb(GtkWidget* widget) {
7499   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
7500   if (!window) {
7501     return;
7502   }
7503   window->OnMap();
7504 }
7505 
widget_unrealize_cb(GtkWidget * widget)7506 static void widget_unrealize_cb(GtkWidget* widget) {
7507   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
7508   if (!window) {
7509     return;
7510   }
7511   window->OnUnrealize();
7512 }
7513 
size_allocate_cb(GtkWidget * widget,GtkAllocation * allocation)7514 static void size_allocate_cb(GtkWidget* widget, GtkAllocation* allocation) {
7515   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
7516   if (!window) {
7517     return;
7518   }
7519 
7520   window->OnSizeAllocate(allocation);
7521 }
7522 
toplevel_window_size_allocate_cb(GtkWidget * widget,GtkAllocation * allocation)7523 static void toplevel_window_size_allocate_cb(GtkWidget* widget,
7524                                              GtkAllocation* allocation) {
7525   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
7526   if (!window) {
7527     return;
7528   }
7529 
7530   window->UpdateTopLevelOpaqueRegion();
7531 }
7532 
delete_event_cb(GtkWidget * widget,GdkEventAny * event)7533 static gboolean delete_event_cb(GtkWidget* widget, GdkEventAny* event) {
7534   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
7535   if (!window) {
7536     return FALSE;
7537   }
7538 
7539   window->OnDeleteEvent();
7540 
7541   return TRUE;
7542 }
7543 
enter_notify_event_cb(GtkWidget * widget,GdkEventCrossing * event)7544 static gboolean enter_notify_event_cb(GtkWidget* widget,
7545                                       GdkEventCrossing* event) {
7546   RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
7547   if (!window) {
7548     return TRUE;
7549   }
7550 
7551   window->OnEnterNotifyEvent(event);
7552 
7553   return TRUE;
7554 }
7555 
leave_notify_event_cb(GtkWidget * widget,GdkEventCrossing * event)7556 static gboolean leave_notify_event_cb(GtkWidget* widget,
7557                                       GdkEventCrossing* event) {
7558   if (is_parent_grab_leave(event)) {
7559     return TRUE;
7560   }
7561 
7562   // bug 369599: Suppress LeaveNotify events caused by pointer grabs to
7563   // avoid generating spurious mouse exit events.
7564   auto x = gint(event->x_root);
7565   auto y = gint(event->y_root);
7566   GdkDevice* pointer = GdkGetPointer();
7567   GdkWindow* winAtPt = gdk_device_get_window_at_position(pointer, &x, &y);
7568   if (winAtPt == event->window) {
7569     return TRUE;
7570   }
7571 
7572   RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
7573   if (!window) return TRUE;
7574 
7575   window->OnLeaveNotifyEvent(event);
7576 
7577   return TRUE;
7578 }
7579 
GetFirstNSWindowForGDKWindow(GdkWindow * aGdkWindow)7580 static nsWindow* GetFirstNSWindowForGDKWindow(GdkWindow* aGdkWindow) {
7581   nsWindow* window;
7582   while (!(window = get_window_for_gdk_window(aGdkWindow))) {
7583     // The event has bubbled to the moz_container widget as passed into each
7584     // caller's *widget parameter, but its corresponding nsWindow is an ancestor
7585     // of the window that we need.  Instead, look at event->window and find the
7586     // first ancestor nsWindow of it because event->window may be in a plugin.
7587     aGdkWindow = gdk_window_get_parent(aGdkWindow);
7588     if (!aGdkWindow) {
7589       window = nullptr;
7590       break;
7591     }
7592   }
7593   return window;
7594 }
7595 
motion_notify_event_cb(GtkWidget * widget,GdkEventMotion * event)7596 static gboolean motion_notify_event_cb(GtkWidget* widget,
7597                                        GdkEventMotion* event) {
7598   UpdateLastInputEventTime(event);
7599 
7600   nsWindow* window = GetFirstNSWindowForGDKWindow(event->window);
7601   if (!window) return FALSE;
7602 
7603   window->OnMotionNotifyEvent(event);
7604 
7605   return TRUE;
7606 }
7607 
button_press_event_cb(GtkWidget * widget,GdkEventButton * event)7608 static gboolean button_press_event_cb(GtkWidget* widget,
7609                                       GdkEventButton* event) {
7610   UpdateLastInputEventTime(event);
7611 
7612   nsWindow* window = GetFirstNSWindowForGDKWindow(event->window);
7613   if (!window) return FALSE;
7614 
7615   window->OnButtonPressEvent(event);
7616 
7617   if (GdkIsWaylandDisplay()) {
7618     WaylandDragWorkaround(event);
7619   }
7620 
7621   return TRUE;
7622 }
7623 
button_release_event_cb(GtkWidget * widget,GdkEventButton * event)7624 static gboolean button_release_event_cb(GtkWidget* widget,
7625                                         GdkEventButton* event) {
7626   UpdateLastInputEventTime(event);
7627 
7628   nsWindow* window = GetFirstNSWindowForGDKWindow(event->window);
7629   if (!window) return FALSE;
7630 
7631   window->OnButtonReleaseEvent(event);
7632 
7633   return TRUE;
7634 }
7635 
focus_in_event_cb(GtkWidget * widget,GdkEventFocus * event)7636 static gboolean focus_in_event_cb(GtkWidget* widget, GdkEventFocus* event) {
7637   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
7638   if (!window) return FALSE;
7639 
7640   window->OnContainerFocusInEvent(event);
7641 
7642   return FALSE;
7643 }
7644 
focus_out_event_cb(GtkWidget * widget,GdkEventFocus * event)7645 static gboolean focus_out_event_cb(GtkWidget* widget, GdkEventFocus* event) {
7646   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
7647   if (!window) return FALSE;
7648 
7649   window->OnContainerFocusOutEvent(event);
7650 
7651   return FALSE;
7652 }
7653 
7654 #ifdef MOZ_X11
7655 // For long-lived popup windows that don't really take focus themselves but
7656 // may have elements that accept keyboard input when the parent window is
7657 // active, focus is handled specially.  These windows include noautohide
7658 // panels.  (This special handling is not necessary for temporary popups where
7659 // the keyboard is grabbed.)
7660 //
7661 // Mousing over or clicking on these windows should not cause them to steal
7662 // focus from their parent windows, so, the input field of WM_HINTS is set to
7663 // False to request that the window manager not set the input focus to this
7664 // window.  http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7
7665 //
7666 // However, these windows can still receive WM_TAKE_FOCUS messages from the
7667 // window manager, so they can still detect when the user has indicated that
7668 // they wish to direct keyboard input at these windows.  When the window
7669 // manager offers focus to these windows (after a mouse over or click, for
7670 // example), a request to make the parent window active is issued.  When the
7671 // parent window becomes active, keyboard events will be received.
7672 
popup_take_focus_filter(GdkXEvent * gdk_xevent,GdkEvent * event,gpointer data)7673 static GdkFilterReturn popup_take_focus_filter(GdkXEvent* gdk_xevent,
7674                                                GdkEvent* event, gpointer data) {
7675   auto* xevent = static_cast<XEvent*>(gdk_xevent);
7676   if (xevent->type != ClientMessage) return GDK_FILTER_CONTINUE;
7677 
7678   XClientMessageEvent& xclient = xevent->xclient;
7679   if (xclient.message_type != gdk_x11_get_xatom_by_name("WM_PROTOCOLS")) {
7680     return GDK_FILTER_CONTINUE;
7681   }
7682 
7683   Atom atom = xclient.data.l[0];
7684   if (atom != gdk_x11_get_xatom_by_name("WM_TAKE_FOCUS")) {
7685     return GDK_FILTER_CONTINUE;
7686   }
7687 
7688   guint32 timestamp = xclient.data.l[1];
7689 
7690   GtkWidget* widget = get_gtk_widget_for_gdk_window(event->any.window);
7691   if (!widget) return GDK_FILTER_CONTINUE;
7692 
7693   GtkWindow* parent = gtk_window_get_transient_for(GTK_WINDOW(widget));
7694   if (!parent) return GDK_FILTER_CONTINUE;
7695 
7696   if (gtk_window_is_active(parent)) {
7697     return GDK_FILTER_REMOVE;  // leave input focus on the parent
7698   }
7699 
7700   GdkWindow* parent_window = gtk_widget_get_window(GTK_WIDGET(parent));
7701   if (!parent_window) return GDK_FILTER_CONTINUE;
7702 
7703   // In case the parent has not been deconified.
7704   gdk_window_show_unraised(parent_window);
7705 
7706   // Request focus on the parent window.
7707   // Use gdk_window_focus rather than gtk_window_present to avoid
7708   // raising the parent window.
7709   gdk_window_focus(parent_window, timestamp);
7710   return GDK_FILTER_REMOVE;
7711 }
7712 #endif /* MOZ_X11 */
7713 
key_press_event_cb(GtkWidget * widget,GdkEventKey * event)7714 static gboolean key_press_event_cb(GtkWidget* widget, GdkEventKey* event) {
7715   LOGW("key_press_event_cb\n");
7716 
7717   UpdateLastInputEventTime(event);
7718 
7719   // find the window with focus and dispatch this event to that widget
7720   nsWindow* window = get_window_for_gtk_widget(widget);
7721   if (!window) return FALSE;
7722 
7723   RefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
7724 
7725 #ifdef MOZ_X11
7726   // Keyboard repeat can cause key press events to queue up when there are
7727   // slow event handlers (bug 301029).  Throttle these events by removing
7728   // consecutive pending duplicate KeyPress events to the same window.
7729   // We use the event time of the last one.
7730   // Note: GDK calls XkbSetDetectableAutorepeat so that KeyRelease events
7731   // are generated only when the key is physically released.
7732 #  define NS_GDKEVENT_MATCH_MASK 0x1FFF  // GDK_SHIFT_MASK .. GDK_BUTTON5_MASK
7733   // Our headers undefine X11 KeyPress - let's redefine it here.
7734 #  ifndef KeyPress
7735 #    define KeyPress 2
7736 #  endif
7737   GdkDisplay* gdkDisplay = gtk_widget_get_display(widget);
7738   if (GdkIsX11Display(gdkDisplay)) {
7739     Display* dpy = GDK_DISPLAY_XDISPLAY(gdkDisplay);
7740     while (XPending(dpy)) {
7741       XEvent next_event;
7742       XPeekEvent(dpy, &next_event);
7743       GdkWindow* nextGdkWindow =
7744           gdk_x11_window_lookup_for_display(gdkDisplay, next_event.xany.window);
7745       if (nextGdkWindow != event->window || next_event.type != KeyPress ||
7746           next_event.xkey.keycode != event->hardware_keycode ||
7747           next_event.xkey.state != (event->state & NS_GDKEVENT_MATCH_MASK)) {
7748         break;
7749       }
7750       XNextEvent(dpy, &next_event);
7751       event->time = next_event.xkey.time;
7752     }
7753   }
7754 #endif
7755 
7756   return focusWindow->OnKeyPressEvent(event);
7757 }
7758 
key_release_event_cb(GtkWidget * widget,GdkEventKey * event)7759 static gboolean key_release_event_cb(GtkWidget* widget, GdkEventKey* event) {
7760   LOGW("key_release_event_cb\n");
7761 
7762   UpdateLastInputEventTime(event);
7763 
7764   // find the window with focus and dispatch this event to that widget
7765   nsWindow* window = get_window_for_gtk_widget(widget);
7766   if (!window) return FALSE;
7767 
7768   RefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
7769 
7770   return focusWindow->OnKeyReleaseEvent(event);
7771 }
7772 
property_notify_event_cb(GtkWidget * aWidget,GdkEventProperty * aEvent)7773 static gboolean property_notify_event_cb(GtkWidget* aWidget,
7774                                          GdkEventProperty* aEvent) {
7775   RefPtr<nsWindow> window = get_window_for_gdk_window(aEvent->window);
7776   if (!window) return FALSE;
7777 
7778   return window->OnPropertyNotifyEvent(aWidget, aEvent);
7779 }
7780 
scroll_event_cb(GtkWidget * widget,GdkEventScroll * event)7781 static gboolean scroll_event_cb(GtkWidget* widget, GdkEventScroll* event) {
7782   nsWindow* window = GetFirstNSWindowForGDKWindow(event->window);
7783   if (!window) return FALSE;
7784 
7785   window->OnScrollEvent(event);
7786 
7787   return TRUE;
7788 }
7789 
hierarchy_changed_cb(GtkWidget * widget,GtkWidget * previous_toplevel)7790 static void hierarchy_changed_cb(GtkWidget* widget,
7791                                  GtkWidget* previous_toplevel) {
7792   GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
7793   GdkWindowState old_window_state = GDK_WINDOW_STATE_WITHDRAWN;
7794   GdkEventWindowState event;
7795 
7796   event.new_window_state = GDK_WINDOW_STATE_WITHDRAWN;
7797 
7798   if (GTK_IS_WINDOW(previous_toplevel)) {
7799     g_signal_handlers_disconnect_by_func(
7800         previous_toplevel, FuncToGpointer(window_state_event_cb), widget);
7801     GdkWindow* win = gtk_widget_get_window(previous_toplevel);
7802     if (win) {
7803       old_window_state = gdk_window_get_state(win);
7804     }
7805   }
7806 
7807   if (GTK_IS_WINDOW(toplevel)) {
7808     g_signal_connect_swapped(toplevel, "window-state-event",
7809                              G_CALLBACK(window_state_event_cb), widget);
7810     GdkWindow* win = gtk_widget_get_window(toplevel);
7811     if (win) {
7812       event.new_window_state = gdk_window_get_state(win);
7813     }
7814   }
7815 
7816   event.changed_mask =
7817       static_cast<GdkWindowState>(old_window_state ^ event.new_window_state);
7818 
7819   if (event.changed_mask) {
7820     event.type = GDK_WINDOW_STATE;
7821     event.window = nullptr;
7822     event.send_event = TRUE;
7823     window_state_event_cb(widget, &event);
7824   }
7825 }
7826 
window_state_event_cb(GtkWidget * widget,GdkEventWindowState * event)7827 static gboolean window_state_event_cb(GtkWidget* widget,
7828                                       GdkEventWindowState* event) {
7829   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
7830   if (!window) return FALSE;
7831 
7832   window->OnWindowStateEvent(widget, event);
7833 
7834   return FALSE;
7835 }
7836 
settings_xft_dpi_changed_cb(GtkSettings * gtk_settings,GParamSpec * pspec,nsWindow * data)7837 static void settings_xft_dpi_changed_cb(GtkSettings* gtk_settings,
7838                                         GParamSpec* pspec, nsWindow* data) {
7839   RefPtr<nsWindow> window = data;
7840   window->OnDPIChanged();
7841   // Even though the window size in screen pixels has not changed,
7842   // nsViewManager stores the dimensions in app units.
7843   // DispatchResized() updates those.
7844   window->DispatchResized();
7845 }
7846 
check_resize_cb(GtkContainer * container,gpointer user_data)7847 static void check_resize_cb(GtkContainer* container, gpointer user_data) {
7848   RefPtr<nsWindow> window = get_window_for_gtk_widget(GTK_WIDGET(container));
7849   if (!window) {
7850     return;
7851   }
7852   window->OnCheckResize();
7853 }
7854 
screen_composited_changed_cb(GdkScreen * screen,gpointer user_data)7855 static void screen_composited_changed_cb(GdkScreen* screen,
7856                                          gpointer user_data) {
7857   // This callback can run before gfxPlatform::Init() in rare
7858   // cases involving the profile manager. When this happens,
7859   // we have no reason to reset any compositors as graphics
7860   // hasn't been initialized yet.
7861   if (GPUProcessManager::Get()) {
7862     GPUProcessManager::Get()->ResetCompositors();
7863   }
7864 }
7865 
widget_composited_changed_cb(GtkWidget * widget,gpointer user_data)7866 static void widget_composited_changed_cb(GtkWidget* widget,
7867                                          gpointer user_data) {
7868   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
7869   if (!window) {
7870     return;
7871   }
7872   window->OnCompositedChanged();
7873 }
7874 
scale_changed_cb(GtkWidget * widget,GParamSpec * aPSpec,gpointer aPointer)7875 static void scale_changed_cb(GtkWidget* widget, GParamSpec* aPSpec,
7876                              gpointer aPointer) {
7877   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
7878   if (!window) {
7879     return;
7880   }
7881 
7882   window->OnScaleChanged();
7883 }
7884 
touch_event_cb(GtkWidget * aWidget,GdkEventTouch * aEvent)7885 static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent) {
7886   UpdateLastInputEventTime(aEvent);
7887 
7888   nsWindow* window = GetFirstNSWindowForGDKWindow(aEvent->window);
7889   if (!window) {
7890     return FALSE;
7891   }
7892 
7893   return window->OnTouchEvent(aEvent);
7894 }
7895 
7896 // This function called generic because there is no signal specific to touchpad
7897 // pinch events.
generic_event_cb(GtkWidget * widget,GdkEvent * aEvent)7898 static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent) {
7899   if (aEvent->type != GDK_TOUCHPAD_PINCH) {
7900     return FALSE;
7901   }
7902   // Using reinterpret_cast because the touchpad_pinch field of GdkEvent is not
7903   // available in GTK+ versions lower than v3.18
7904   GdkEventTouchpadPinch* event =
7905       reinterpret_cast<GdkEventTouchpadPinch*>(aEvent);
7906 
7907   nsWindow* window = GetFirstNSWindowForGDKWindow(event->window);
7908 
7909   if (!window) {
7910     return FALSE;
7911   }
7912   return window->OnTouchpadPinchEvent(event);
7913 }
7914 
7915 //////////////////////////////////////////////////////////////////////
7916 // These are all of our drag and drop operations
7917 
InitDragEvent(WidgetDragEvent & aEvent)7918 void nsWindow::InitDragEvent(WidgetDragEvent& aEvent) {
7919   // set the keyboard modifiers
7920   guint modifierState = KeymapWrapper::GetCurrentModifierState();
7921   KeymapWrapper::InitInputEvent(aEvent, modifierState);
7922 }
7923 
WindowDragMotionHandler(GtkWidget * aWidget,GdkDragContext * aDragContext,gint aX,gint aY,guint aTime)7924 gboolean WindowDragMotionHandler(GtkWidget* aWidget,
7925                                  GdkDragContext* aDragContext, gint aX, gint aY,
7926                                  guint aTime) {
7927   RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
7928   if (!window) {
7929     return FALSE;
7930   }
7931 
7932   // figure out which internal widget this drag motion actually happened on
7933   nscoord retx = 0;
7934   nscoord rety = 0;
7935 
7936   GdkWindow* innerWindow = get_inner_gdk_window(gtk_widget_get_window(aWidget),
7937                                                 aX, aY, &retx, &rety);
7938   RefPtr<nsWindow> innerMostWindow = get_window_for_gdk_window(innerWindow);
7939 
7940   if (!innerMostWindow) {
7941     innerMostWindow = window;
7942   }
7943 
7944   int tx = 0, ty = 0;
7945   // Workaround for Bug 1710344
7946   // Caused by Gtk issue https://gitlab.gnome.org/GNOME/gtk/-/issues/4437
7947   if (innerMostWindow->IsWaylandPopup()) {
7948     gdk_window_get_position(innerWindow, &tx, &ty);
7949   }
7950 
7951   LayoutDeviceIntPoint point =
7952       innerMostWindow->GdkPointToDevicePixels({retx + tx, rety + ty});
7953   LOGDRAG("WindowDragMotionHandler nsWindow %p coords [%d, %d]\n",
7954           innerMostWindow.get(), retx, rety);
7955 
7956   RefPtr<nsDragService> dragService = nsDragService::GetInstance();
7957   if (!dragService->ScheduleMotionEvent(innerMostWindow, aDragContext, point,
7958                                         aTime)) {
7959     return FALSE;
7960   }
7961   // We need to reply to drag_motion event on Wayland immediately,
7962   // see Bug 1730203.
7963   if (GdkIsWaylandDisplay()) {
7964     dragService->ReplyToDragMotion();
7965   }
7966   return TRUE;
7967 }
7968 
drag_motion_event_cb(GtkWidget * aWidget,GdkDragContext * aDragContext,gint aX,gint aY,guint aTime,gpointer aData)7969 static gboolean drag_motion_event_cb(GtkWidget* aWidget,
7970                                      GdkDragContext* aDragContext, gint aX,
7971                                      gint aY, guint aTime, gpointer aData) {
7972   return WindowDragMotionHandler(aWidget, aDragContext, aX, aY, aTime);
7973 }
7974 
WindowDragLeaveHandler(GtkWidget * aWidget)7975 void WindowDragLeaveHandler(GtkWidget* aWidget) {
7976   LOGDRAG("WindowDragLeaveHandler()\n");
7977 
7978   RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
7979   if (!window) {
7980     LOGDRAG("    Failed - can't find nsWindow!\n");
7981     return;
7982   }
7983 
7984   RefPtr<nsDragService> dragService = nsDragService::GetInstance();
7985   nsWindow* mostRecentDragWindow = dragService->GetMostRecentDestWindow();
7986   if (!mostRecentDragWindow) {
7987     // This can happen when the target will not accept a drop.  A GTK drag
7988     // source sends the leave message to the destination before the
7989     // drag-failed signal on the source widget, but the leave message goes
7990     // via the X server, and so doesn't get processed at least until the
7991     // event loop runs again.
7992     LOGDRAG("    Failed - GetMostRecentDestWindow()!\n");
7993     return;
7994   }
7995 
7996   if (aWidget != window->GetGtkWidget()) {
7997     // When the drag moves between widgets, GTK can send leave signal for
7998     // the old widget after the motion or drop signal for the new widget.
7999     // We'll send the leave event when the motion or drop event is run.
8000     LOGDRAG("    Failed - GtkWidget mismatch!\n");
8001     return;
8002   }
8003 
8004   LOGDRAG("WindowDragLeaveHandler nsWindow %p\n", (void*)mostRecentDragWindow);
8005   dragService->ScheduleLeaveEvent();
8006 }
8007 
drag_leave_event_cb(GtkWidget * aWidget,GdkDragContext * aDragContext,guint aTime,gpointer aData)8008 static void drag_leave_event_cb(GtkWidget* aWidget,
8009                                 GdkDragContext* aDragContext, guint aTime,
8010                                 gpointer aData) {
8011   WindowDragLeaveHandler(aWidget);
8012 }
8013 
WindowDragDropHandler(GtkWidget * aWidget,GdkDragContext * aDragContext,gint aX,gint aY,guint aTime)8014 gboolean WindowDragDropHandler(GtkWidget* aWidget, GdkDragContext* aDragContext,
8015                                gint aX, gint aY, guint aTime) {
8016   RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
8017   if (!window) return FALSE;
8018 
8019   // figure out which internal widget this drag motion actually happened on
8020   nscoord retx = 0;
8021   nscoord rety = 0;
8022 
8023   GdkWindow* innerWindow = get_inner_gdk_window(gtk_widget_get_window(aWidget),
8024                                                 aX, aY, &retx, &rety);
8025   RefPtr<nsWindow> innerMostWindow = get_window_for_gdk_window(innerWindow);
8026 
8027   if (!innerMostWindow) {
8028     innerMostWindow = window;
8029   }
8030 
8031   int tx = 0, ty = 0;
8032   // Workaround for Bug 1710344
8033   // Caused by Gtk issue https://gitlab.gnome.org/GNOME/gtk/-/issues/4437
8034   if (innerMostWindow->IsWaylandPopup()) {
8035     gdk_window_get_position(innerWindow, &tx, &ty);
8036   }
8037 
8038   LayoutDeviceIntPoint point =
8039       window->GdkPointToDevicePixels({retx + tx, rety + ty});
8040   LOGDRAG("WindowDragDropHandler nsWindow %p coords [%d,%d]\n",
8041           innerMostWindow.get(), retx, rety);
8042 
8043   RefPtr<nsDragService> dragService = nsDragService::GetInstance();
8044   return dragService->ScheduleDropEvent(innerMostWindow, aDragContext, point,
8045                                         aTime);
8046 }
8047 
drag_drop_event_cb(GtkWidget * aWidget,GdkDragContext * aDragContext,gint aX,gint aY,guint aTime,gpointer aData)8048 static gboolean drag_drop_event_cb(GtkWidget* aWidget,
8049                                    GdkDragContext* aDragContext, gint aX,
8050                                    gint aY, guint aTime, gpointer aData) {
8051   return WindowDragDropHandler(aWidget, aDragContext, aX, aY, aTime);
8052 }
8053 
drag_data_received_event_cb(GtkWidget * aWidget,GdkDragContext * aDragContext,gint aX,gint aY,GtkSelectionData * aSelectionData,guint aInfo,guint aTime,gpointer aData)8054 static void drag_data_received_event_cb(GtkWidget* aWidget,
8055                                         GdkDragContext* aDragContext, gint aX,
8056                                         gint aY,
8057                                         GtkSelectionData* aSelectionData,
8058                                         guint aInfo, guint aTime,
8059                                         gpointer aData) {
8060   RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
8061   if (!window) return;
8062 
8063   window->OnDragDataReceivedEvent(aWidget, aDragContext, aX, aY, aSelectionData,
8064                                   aInfo, aTime, aData);
8065 }
8066 
initialize_prefs(void)8067 static nsresult initialize_prefs(void) {
8068   gRaiseWindows =
8069       Preferences::GetBool("mozilla.widget.raise-on-setfocus", true);
8070   gTransparentWindows =
8071       Preferences::GetBool("widget.transparent-windows", true);
8072   gUseMoveToRect =
8073       Preferences::GetBool("widget.wayland.use-move-to-rect", true);
8074 
8075   if (Preferences::HasUserValue("widget.use-aspect-ratio")) {
8076     gUseAspectRatio = Preferences::GetBool("widget.use-aspect-ratio", true);
8077   } else {
8078     static const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
8079     gUseAspectRatio =
8080         currentDesktop ? (strstr(currentDesktop, "GNOME") != nullptr) : false;
8081   }
8082 
8083   return NS_OK;
8084 }
8085 
8086 // TODO: Can we simplify it for mShell/mContainer only scenario?
get_inner_gdk_window(GdkWindow * aWindow,gint x,gint y,gint * retx,gint * rety)8087 static GdkWindow* get_inner_gdk_window(GdkWindow* aWindow, gint x, gint y,
8088                                        gint* retx, gint* rety) {
8089   gint cx, cy, cw, ch;
8090   GList* children = gdk_window_peek_children(aWindow);
8091   for (GList* child = g_list_last(children); child;
8092        child = g_list_previous(child)) {
8093     auto* childWindow = (GdkWindow*)child->data;
8094     if (get_window_for_gdk_window(childWindow)) {
8095       gdk_window_get_geometry(childWindow, &cx, &cy, &cw, &ch);
8096       if ((cx < x) && (x < (cx + cw)) && (cy < y) && (y < (cy + ch)) &&
8097           gdk_window_is_visible(childWindow)) {
8098         return get_inner_gdk_window(childWindow, x - cx, y - cy, retx, rety);
8099       }
8100     }
8101   }
8102   *retx = x;
8103   *rety = y;
8104   return aWindow;
8105 }
8106 
is_parent_ungrab_enter(GdkEventCrossing * aEvent)8107 static int is_parent_ungrab_enter(GdkEventCrossing* aEvent) {
8108   return (GDK_CROSSING_UNGRAB == aEvent->mode) &&
8109          ((GDK_NOTIFY_ANCESTOR == aEvent->detail) ||
8110           (GDK_NOTIFY_VIRTUAL == aEvent->detail));
8111 }
8112 
is_parent_grab_leave(GdkEventCrossing * aEvent)8113 static int is_parent_grab_leave(GdkEventCrossing* aEvent) {
8114   return (GDK_CROSSING_GRAB == aEvent->mode) &&
8115          ((GDK_NOTIFY_ANCESTOR == aEvent->detail) ||
8116           (GDK_NOTIFY_VIRTUAL == aEvent->detail));
8117 }
8118 
8119 #ifdef ACCESSIBILITY
CreateRootAccessible()8120 void nsWindow::CreateRootAccessible() {
8121   if (!mRootAccessible) {
8122     LOG("nsWindow:: Create Toplevel Accessibility\n");
8123     mRootAccessible = GetRootAccessible();
8124   }
8125 }
8126 
DispatchEventToRootAccessible(uint32_t aEventType)8127 void nsWindow::DispatchEventToRootAccessible(uint32_t aEventType) {
8128   if (!a11y::ShouldA11yBeEnabled()) {
8129     return;
8130   }
8131 
8132   nsAccessibilityService* accService = GetOrCreateAccService();
8133   if (!accService) {
8134     return;
8135   }
8136 
8137   // Get the root document accessible and fire event to it.
8138   a11y::LocalAccessible* acc = GetRootAccessible();
8139   if (acc) {
8140     accService->FireAccessibleEvent(aEventType, acc);
8141   }
8142 }
8143 
DispatchActivateEventAccessible(void)8144 void nsWindow::DispatchActivateEventAccessible(void) {
8145   DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_ACTIVATE);
8146 }
8147 
DispatchDeactivateEventAccessible(void)8148 void nsWindow::DispatchDeactivateEventAccessible(void) {
8149   DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_DEACTIVATE);
8150 }
8151 
DispatchMaximizeEventAccessible(void)8152 void nsWindow::DispatchMaximizeEventAccessible(void) {
8153   DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_MAXIMIZE);
8154 }
8155 
DispatchMinimizeEventAccessible(void)8156 void nsWindow::DispatchMinimizeEventAccessible(void) {
8157   DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_MINIMIZE);
8158 }
8159 
DispatchRestoreEventAccessible(void)8160 void nsWindow::DispatchRestoreEventAccessible(void) {
8161   DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_RESTORE);
8162 }
8163 
8164 #endif /* #ifdef ACCESSIBILITY */
8165 
SetInputContext(const InputContext & aContext,const InputContextAction & aAction)8166 void nsWindow::SetInputContext(const InputContext& aContext,
8167                                const InputContextAction& aAction) {
8168   if (!mIMContext) {
8169     return;
8170   }
8171   mIMContext->SetInputContext(this, &aContext, &aAction);
8172 }
8173 
GetInputContext()8174 InputContext nsWindow::GetInputContext() {
8175   InputContext context;
8176   if (!mIMContext) {
8177     context.mIMEState.mEnabled = IMEEnabled::Disabled;
8178     context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
8179   } else {
8180     context = mIMContext->GetInputContext();
8181   }
8182   return context;
8183 }
8184 
GetNativeTextEventDispatcherListener()8185 TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() {
8186   if (NS_WARN_IF(!mIMContext)) {
8187     return nullptr;
8188   }
8189   return mIMContext;
8190 }
8191 
GetEditCommands(NativeKeyBindingsType aType,const WidgetKeyboardEvent & aEvent,nsTArray<CommandInt> & aCommands)8192 bool nsWindow::GetEditCommands(NativeKeyBindingsType aType,
8193                                const WidgetKeyboardEvent& aEvent,
8194                                nsTArray<CommandInt>& aCommands) {
8195   // Validate the arguments.
8196   if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
8197     return false;
8198   }
8199 
8200   Maybe<WritingMode> writingMode;
8201   if (aEvent.NeedsToRemapNavigationKey()) {
8202     if (RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher()) {
8203       writingMode = dispatcher->MaybeQueryWritingModeAtSelection();
8204     }
8205   }
8206 
8207   NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
8208   keyBindings->GetEditCommands(aEvent, writingMode, aCommands);
8209   return true;
8210 }
8211 
StartRemoteDrawingInRegion(const LayoutDeviceIntRegion & aInvalidRegion,BufferMode * aBufferMode)8212 already_AddRefed<DrawTarget> nsWindow::StartRemoteDrawingInRegion(
8213     const LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode) {
8214   return mSurfaceProvider.StartRemoteDrawingInRegion(aInvalidRegion,
8215                                                      aBufferMode);
8216 }
8217 
EndRemoteDrawingInRegion(DrawTarget * aDrawTarget,const LayoutDeviceIntRegion & aInvalidRegion)8218 void nsWindow::EndRemoteDrawingInRegion(
8219     DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
8220   mSurfaceProvider.EndRemoteDrawingInRegion(aDrawTarget, aInvalidRegion);
8221 }
8222 
8223 // Code shared begin BeginMoveDrag and BeginResizeDrag
GetDragInfo(WidgetMouseEvent * aMouseEvent,GdkWindow ** aWindow,gint * aButton,gint * aRootX,gint * aRootY)8224 bool nsWindow::GetDragInfo(WidgetMouseEvent* aMouseEvent, GdkWindow** aWindow,
8225                            gint* aButton, gint* aRootX, gint* aRootY) {
8226   if (aMouseEvent->mButton != MouseButton::ePrimary) {
8227     // we can only begin a move drag with the left mouse button
8228     return false;
8229   }
8230   *aButton = 1;
8231 
8232   // get the gdk window for this widget
8233   GdkWindow* gdk_window = mGdkWindow;
8234   if (!gdk_window) {
8235     return false;
8236   }
8237 #ifdef DEBUG
8238   // GDK_IS_WINDOW(...) expands to a statement-expression, and
8239   // statement-expressions are not allowed in template-argument lists. So we
8240   // have to make the MOZ_ASSERT condition indirect.
8241   if (!GDK_IS_WINDOW(gdk_window)) {
8242     MOZ_ASSERT(false, "must really be window");
8243   }
8244 #endif
8245 
8246   // find the top-level window
8247   gdk_window = gdk_window_get_toplevel(gdk_window);
8248   MOZ_ASSERT(gdk_window, "gdk_window_get_toplevel should not return null");
8249   *aWindow = gdk_window;
8250 
8251   if (!aMouseEvent->mWidget) {
8252     return false;
8253   }
8254 
8255   if (GdkIsX11Display()) {
8256     // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=789054
8257     // To avoid crashes disable double-click on WM without _NET_WM_MOVERESIZE.
8258     // See _should_perform_ewmh_drag() at gdkwindow-x11.c
8259     GdkScreen* screen = gdk_window_get_screen(gdk_window);
8260     GdkAtom atom = gdk_atom_intern("_NET_WM_MOVERESIZE", FALSE);
8261     if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
8262       static unsigned int lastTimeStamp = 0;
8263       if (lastTimeStamp != aMouseEvent->mTime) {
8264         lastTimeStamp = aMouseEvent->mTime;
8265       } else {
8266         return false;
8267       }
8268     }
8269   }
8270 
8271   // FIXME: It would be nice to have the widget position at the time
8272   // of the event, but it's relatively unlikely that the widget has
8273   // moved since the mousedown.  (On the other hand, it's quite likely
8274   // that the mouse has moved, which is why we use the mouse position
8275   // from the event.)
8276   LayoutDeviceIntPoint offset = aMouseEvent->mWidget->WidgetToScreenOffset();
8277   *aRootX = aMouseEvent->mRefPoint.x + offset.x;
8278   *aRootY = aMouseEvent->mRefPoint.y + offset.y;
8279 
8280   return true;
8281 }
8282 
BeginResizeDrag(WidgetGUIEvent * aEvent,int32_t aHorizontal,int32_t aVertical)8283 nsresult nsWindow::BeginResizeDrag(WidgetGUIEvent* aEvent, int32_t aHorizontal,
8284                                    int32_t aVertical) {
8285   NS_ENSURE_ARG_POINTER(aEvent);
8286 
8287   if (aEvent->mClass != eMouseEventClass) {
8288     // you can only begin a resize drag with a mouse event
8289     return NS_ERROR_INVALID_ARG;
8290   }
8291 
8292   GdkWindow* gdk_window;
8293   gint button, screenX, screenY;
8294   if (!GetDragInfo(aEvent->AsMouseEvent(), &gdk_window, &button, &screenX,
8295                    &screenY)) {
8296     return NS_ERROR_FAILURE;
8297   }
8298 
8299   // work out what GdkWindowEdge we're talking about
8300   GdkWindowEdge window_edge;
8301   if (aVertical < 0) {
8302     if (aHorizontal < 0) {
8303       window_edge = GDK_WINDOW_EDGE_NORTH_WEST;
8304     } else if (aHorizontal == 0) {
8305       window_edge = GDK_WINDOW_EDGE_NORTH;
8306     } else {
8307       window_edge = GDK_WINDOW_EDGE_NORTH_EAST;
8308     }
8309   } else if (aVertical == 0) {
8310     if (aHorizontal < 0) {
8311       window_edge = GDK_WINDOW_EDGE_WEST;
8312     } else if (aHorizontal == 0) {
8313       return NS_ERROR_INVALID_ARG;
8314     } else {
8315       window_edge = GDK_WINDOW_EDGE_EAST;
8316     }
8317   } else {
8318     if (aHorizontal < 0) {
8319       window_edge = GDK_WINDOW_EDGE_SOUTH_WEST;
8320     } else if (aHorizontal == 0) {
8321       window_edge = GDK_WINDOW_EDGE_SOUTH;
8322     } else {
8323       window_edge = GDK_WINDOW_EDGE_SOUTH_EAST;
8324     }
8325   }
8326 
8327   // tell the window manager to start the resize
8328   gdk_window_begin_resize_drag(gdk_window, window_edge, button, screenX,
8329                                screenY, aEvent->mTime);
8330 
8331   return NS_OK;
8332 }
8333 
GetWindowRenderer()8334 nsIWidget::WindowRenderer* nsWindow::GetWindowRenderer() {
8335   if (mIsDestroyed) {
8336     // Prevent external code from triggering the re-creation of the
8337     // LayerManager/Compositor during shutdown. Just return what we currently
8338     // have, which is most likely null.
8339     return mWindowRenderer;
8340   }
8341 
8342   return nsBaseWidget::GetWindowRenderer();
8343 }
8344 
DidGetNonBlankPaint()8345 void nsWindow::DidGetNonBlankPaint() {
8346   if (mGotNonBlankPaint) {
8347     return;
8348   }
8349   mGotNonBlankPaint = true;
8350   if (!mConfiguredClearColor) {
8351     // Nothing to do, we hadn't overridden the clear color.
8352     mConfiguredClearColor = true;
8353     return;
8354   }
8355   // Reset the clear color set in the expose event to transparent.
8356   GetWindowRenderer()->AsWebRender()->WrBridge()->SendSetDefaultClearColor(
8357       NS_TRANSPARENT);
8358 }
8359 
8360 /* nsWindow::SetCompositorWidgetDelegate() sets remote GtkCompositorWidget
8361  * to render into with compositor.
8362  *
8363  * If we're already visible we need to recreate compositor/vsync state.
8364  */
SetCompositorWidgetDelegate(CompositorWidgetDelegate * delegate)8365 void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
8366   LOG("nsWindow::SetCompositorWidgetDelegate %p\n", delegate);
8367 
8368   // There's a change of remote widget - stop compositor and VSync as
8369   // we're going re-init it.
8370   if (mCompositorWidgetDelegate && mIsMapped) {
8371     DisableRenderingToWindow();
8372   }
8373 
8374   if (delegate) {
8375     mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate();
8376     MOZ_ASSERT(mCompositorWidgetDelegate,
8377                "nsWindow::SetCompositorWidgetDelegate called with a "
8378                "non-PlatformCompositorWidgetDelegate");
8379     // This is called from nsBaseWidget::CreateCompositor() in which case
8380     // we need to create a new EGL surface in RenderCompositorEGL on X11
8381     if (mIsMapped) {
8382       EnableRenderingToWindow();
8383     }
8384   } else {
8385     mCompositorWidgetDelegate = nullptr;
8386   }
8387 }
8388 
8389 /* nsWindow::UpdateClientOffsetFromCSDWindow() is designed to be called from
8390  * nsWindow::OnConfigureEvent() when mContainer window is already positioned.
8391  *
8392  * It works only for CSD decorated GtkWindow.
8393  */
UpdateClientOffsetFromCSDWindow()8394 void nsWindow::UpdateClientOffsetFromCSDWindow() {
8395   int x = 0, y = 0;
8396 
8397   if (mGdkWindow) {
8398     gdk_window_get_position(mGdkWindow, &x, &y);
8399   }
8400 
8401   x = GdkCoordToDevicePixels(x);
8402   y = GdkCoordToDevicePixels(y);
8403 
8404   if (mClientOffset.x != x || mClientOffset.y != y) {
8405     mClientOffset = nsIntPoint(x, y);
8406 
8407     LOG("nsWindow::UpdateClientOffsetFromCSDWindow %d, %d\n", mClientOffset.x,
8408         mClientOffset.y);
8409 
8410     // Send a WindowMoved notification. This ensures that BrowserParent
8411     // picks up the new client offset and sends it to the child process
8412     // if appropriate.
8413     NotifyWindowMoved(mBounds.x, mBounds.y);
8414   }
8415 }
8416 
SetNonClientMargins(LayoutDeviceIntMargin & aMargins)8417 nsresult nsWindow::SetNonClientMargins(LayoutDeviceIntMargin& aMargins) {
8418   SetDrawsInTitlebar(aMargins.top == 0);
8419   return NS_OK;
8420 }
8421 
SetDrawsInTitlebar(bool aState)8422 void nsWindow::SetDrawsInTitlebar(bool aState) {
8423   LOG("nsWindow::SetDrawsInTitlebar() State %d mGtkWindowDecoration %d\n",
8424       aState, (int)mGtkWindowDecoration);
8425 
8426   if (mGtkWindowDecoration == GTK_DECORATION_NONE ||
8427       aState == mDrawInTitlebar) {
8428     LOG("  already set, quit");
8429     return;
8430   }
8431 
8432   if (mIsPIPWindow) {
8433     gtk_window_set_decorated(GTK_WINDOW(mShell), !aState);
8434     LOG("  set decoration for PIP %d", aState);
8435     return;
8436   }
8437 
8438   if (mGtkWindowDecoration == GTK_DECORATION_SYSTEM) {
8439     SetWindowDecoration(aState ? eBorderStyle_border : mBorderStyle);
8440   } else if (mGtkWindowDecoration == GTK_DECORATION_CLIENT) {
8441     LOG("    Using CSD mode\n");
8442 
8443     /* Window manager does not support GDK_DECOR_BORDER,
8444      * emulate it by CSD.
8445      *
8446      * gtk_window_set_titlebar() works on unrealized widgets only,
8447      * we need to handle mShell carefully here.
8448      * When CSD is enabled mGdkWindow is owned by mContainer which is good
8449      * as we can't delete our mGdkWindow. To make mShell unrealized while
8450      * mContainer is preserved we temporary reparent mContainer to an
8451      * invisible GtkWindow.
8452      */
8453     bool visible = !mNeedsShow && mIsShown;
8454     if (visible) {
8455       NativeShow(false);
8456     }
8457 
8458     // Using GTK_WINDOW_POPUP rather than
8459     // GTK_WINDOW_TOPLEVEL in the hope that POPUP results in less
8460     // initialization and window manager interaction.
8461     GtkWidget* tmpWindow = gtk_window_new(GTK_WINDOW_POPUP);
8462     gtk_widget_realize(tmpWindow);
8463 
8464     gtk_widget_reparent(GTK_WIDGET(mContainer), tmpWindow);
8465     gtk_widget_unrealize(GTK_WIDGET(mShell));
8466 
8467     if (aState) {
8468       // Add a hidden titlebar widget to trigger CSD, but disable the default
8469       // titlebar.  GtkFixed is a somewhat random choice for a simple unused
8470       // widget. gtk_window_set_titlebar() takes ownership of the titlebar
8471       // widget.
8472       gtk_window_set_titlebar(GTK_WINDOW(mShell), gtk_fixed_new());
8473     } else {
8474       gtk_window_set_titlebar(GTK_WINDOW(mShell), nullptr);
8475     }
8476 
8477     /* A workaround for https://bugzilla.gnome.org/show_bug.cgi?id=791081
8478      * gtk_widget_realize() throws:
8479      * "In pixman_region32_init_rect: Invalid rectangle passed"
8480      * when mShell has default 1x1 size.
8481      */
8482     GtkAllocation allocation = {0, 0, 0, 0};
8483     gtk_widget_get_preferred_width(GTK_WIDGET(mShell), nullptr,
8484                                    &allocation.width);
8485     gtk_widget_get_preferred_height(GTK_WIDGET(mShell), nullptr,
8486                                     &allocation.height);
8487     gtk_widget_size_allocate(GTK_WIDGET(mShell), &allocation);
8488 
8489     gtk_widget_realize(GTK_WIDGET(mShell));
8490     gtk_widget_reparent(GTK_WIDGET(mContainer), GTK_WIDGET(mShell));
8491 
8492     // Label mShell toplevel window so property_notify_event_cb callback
8493     // can find its way home.
8494     g_object_set_data(G_OBJECT(gtk_widget_get_window(mShell)), "nsWindow",
8495                       this);
8496 
8497     if (AreBoundsSane()) {
8498       GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
8499       LOG("    resize to %d x %d\n", size.width, size.height);
8500       gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
8501     }
8502 
8503     if (visible) {
8504       mNeedsShow = true;
8505       NativeShow(true);
8506     }
8507 
8508     gtk_widget_destroy(tmpWindow);
8509   }
8510 
8511   mDrawInTitlebar = aState;
8512 
8513   if (mTransparencyBitmapForTitlebar) {
8514     if (mDrawInTitlebar && mSizeState == nsSizeMode_Normal && !mIsTiled) {
8515       UpdateTitlebarTransparencyBitmap();
8516     } else {
8517       ClearTransparencyBitmap();
8518     }
8519   }
8520 }
8521 
GetCurrentTopmostWindow()8522 GtkWindow* nsWindow::GetCurrentTopmostWindow() {
8523   GtkWindow* parentWindow = GTK_WINDOW(GetGtkWidget());
8524   GtkWindow* topmostParentWindow = nullptr;
8525   while (parentWindow) {
8526     topmostParentWindow = parentWindow;
8527     parentWindow = gtk_window_get_transient_for(parentWindow);
8528   }
8529   return topmostParentWindow;
8530 }
8531 
GdkCeiledScaleFactor()8532 gint nsWindow::GdkCeiledScaleFactor() {
8533   // We depend on notify::scale-factor callback which is reliable for toplevel
8534   // windows only, so don't use scale cache for popup windows.
8535   if (mWindowType == eWindowType_toplevel && !mWindowScaleFactorChanged) {
8536     return mWindowScaleFactor;
8537   }
8538 
8539   GdkWindow* scaledGdkWindow = nullptr;
8540   if (GdkIsWaylandDisplay()) {
8541     // For popup windows/dialogs with parent window we need to get scale factor
8542     // of the topmost window. Otherwise the scale factor of the popup is
8543     // not updated during it's hidden.
8544     if (mWindowType == eWindowType_popup || mWindowType == eWindowType_dialog) {
8545       // Get toplevel window for scale factor:
8546       GtkWindow* topmostParentWindow = GetCurrentTopmostWindow();
8547       if (topmostParentWindow) {
8548         scaledGdkWindow =
8549             gtk_widget_get_window(GTK_WIDGET(topmostParentWindow));
8550       } else {
8551         NS_WARNING("Popup/Dialog has no parent.");
8552       }
8553     }
8554   }
8555   // Fallback for windows which parent has been unrealized.
8556   if (!scaledGdkWindow) {
8557     scaledGdkWindow = mGdkWindow;
8558   }
8559   if (scaledGdkWindow) {
8560     mWindowScaleFactor = gdk_window_get_scale_factor(scaledGdkWindow);
8561     mWindowScaleFactorChanged = false;
8562   } else {
8563     mWindowScaleFactor = ScreenHelperGTK::GetGTKMonitorScaleFactor();
8564   }
8565   return mWindowScaleFactor;
8566 }
8567 
UseFractionalScale()8568 bool nsWindow::UseFractionalScale() {
8569 #ifdef MOZ_WAYLAND
8570   return (GdkIsWaylandDisplay() &&
8571           StaticPrefs::widget_wayland_fractional_buffer_scale_AtStartup() > 0 &&
8572           WaylandDisplayGet()->GetViewporter());
8573 #else
8574   return false;
8575 #endif
8576 }
8577 
FractionalScaleFactor()8578 double nsWindow::FractionalScaleFactor() {
8579 #ifdef MOZ_WAYLAND
8580   if (UseFractionalScale()) {
8581     double scale =
8582         StaticPrefs::widget_wayland_fractional_buffer_scale_AtStartup();
8583     scale = std::max(scale, 0.5);
8584     scale = std::min(scale, 8.0);
8585     return scale;
8586   }
8587 #endif
8588   return GdkCeiledScaleFactor();
8589 }
8590 
DevicePixelsToGdkCoordRoundUp(int pixels)8591 gint nsWindow::DevicePixelsToGdkCoordRoundUp(int pixels) {
8592   double scale = FractionalScaleFactor();
8593   return ceil(pixels / scale);
8594 }
8595 
DevicePixelsToGdkCoordRoundDown(int pixels)8596 gint nsWindow::DevicePixelsToGdkCoordRoundDown(int pixels) {
8597   double scale = FractionalScaleFactor();
8598   return floor(pixels / scale);
8599 }
8600 
DevicePixelsToGdkPointRoundDown(LayoutDeviceIntPoint point)8601 GdkPoint nsWindow::DevicePixelsToGdkPointRoundDown(LayoutDeviceIntPoint point) {
8602   double scale = FractionalScaleFactor();
8603   return {int(point.x / scale), int(point.y / scale)};
8604 }
8605 
DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect rect)8606 GdkRectangle nsWindow::DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect rect) {
8607   double scale = FractionalScaleFactor();
8608   int x = floor(rect.x / scale);
8609   int y = floor(rect.y / scale);
8610   int right = ceil((rect.x + rect.width) / scale);
8611   int bottom = ceil((rect.y + rect.height) / scale);
8612   return {x, y, right - x, bottom - y};
8613 }
8614 
DevicePixelsToGdkSizeRoundUp(LayoutDeviceIntSize pixelSize)8615 GdkRectangle nsWindow::DevicePixelsToGdkSizeRoundUp(
8616     LayoutDeviceIntSize pixelSize) {
8617   double scale = FractionalScaleFactor();
8618   gint width = ceil(pixelSize.width / scale);
8619   gint height = ceil(pixelSize.height / scale);
8620   return {0, 0, width, height};
8621 }
8622 
GdkCoordToDevicePixels(gint coord)8623 int nsWindow::GdkCoordToDevicePixels(gint coord) {
8624   return (int)(coord * FractionalScaleFactor());
8625 }
8626 
GdkEventCoordsToDevicePixels(gdouble x,gdouble y)8627 LayoutDeviceIntPoint nsWindow::GdkEventCoordsToDevicePixels(gdouble x,
8628                                                             gdouble y) {
8629   double scale = FractionalScaleFactor();
8630   return LayoutDeviceIntPoint::Floor((float)(x * scale), (float)(y * scale));
8631 }
8632 
GdkPointToDevicePixels(GdkPoint point)8633 LayoutDeviceIntPoint nsWindow::GdkPointToDevicePixels(GdkPoint point) {
8634   double scale = FractionalScaleFactor();
8635   return LayoutDeviceIntPoint::Floor((float)(point.x * scale),
8636                                      (float)(point.y * scale));
8637 }
8638 
GdkRectToDevicePixels(GdkRectangle rect)8639 LayoutDeviceIntRect nsWindow::GdkRectToDevicePixels(GdkRectangle rect) {
8640   double scale = FractionalScaleFactor();
8641   return LayoutDeviceIntRect::RoundIn(
8642       (float)(rect.x * scale), (float)(rect.y * scale),
8643       (float)(rect.width * scale), (float)(rect.height * scale));
8644 }
8645 
SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,NativeMouseMessage aNativeMessage,MouseButton aButton,nsIWidget::Modifiers aModifierFlags,nsIObserver * aObserver)8646 nsresult nsWindow::SynthesizeNativeMouseEvent(
8647     LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
8648     MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
8649     nsIObserver* aObserver) {
8650   AutoObserverNotifier notifier(aObserver, "mouseevent");
8651 
8652   if (!mGdkWindow) {
8653     return NS_OK;
8654   }
8655 
8656   // When a button-press/release event is requested, create it here and put it
8657   // in the event queue. This will not emit a motion event - this needs to be
8658   // done explicitly *before* requesting a button-press/release. You will also
8659   // need to wait for the motion event to be dispatched before requesting a
8660   // button-press/release event to maintain the desired event order.
8661   switch (aNativeMessage) {
8662     case NativeMouseMessage::ButtonDown:
8663     case NativeMouseMessage::ButtonUp: {
8664       GdkEvent event;
8665       memset(&event, 0, sizeof(GdkEvent));
8666       event.type = aNativeMessage == NativeMouseMessage::ButtonDown
8667                        ? GDK_BUTTON_PRESS
8668                        : GDK_BUTTON_RELEASE;
8669       switch (aButton) {
8670         case MouseButton::ePrimary:
8671         case MouseButton::eMiddle:
8672         case MouseButton::eSecondary:
8673         case MouseButton::eX1:
8674         case MouseButton::eX2:
8675           event.button.button = aButton + 1;
8676           break;
8677         default:
8678           return NS_ERROR_INVALID_ARG;
8679       }
8680       event.button.state =
8681           KeymapWrapper::ConvertWidgetModifierToGdkState(aModifierFlags);
8682       event.button.window = mGdkWindow;
8683       event.button.time = GDK_CURRENT_TIME;
8684 
8685       // Get device for event source
8686       event.button.device = GdkGetPointer();
8687 
8688       event.button.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
8689       event.button.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
8690 
8691       LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
8692       event.button.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
8693       event.button.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
8694 
8695       gdk_event_put(&event);
8696       return NS_OK;
8697     }
8698     case NativeMouseMessage::Move: {
8699       // We don't support specific events other than button-press/release. In
8700       // all other cases we'll synthesize a motion event that will be emitted by
8701       // gdk_display_warp_pointer().
8702       // XXX How to activate native modifier for the other events?
8703 #ifdef MOZ_WAYLAND
8704       // Impossible to warp the pointer on Wayland.
8705       // For pointer lock, pointer-constraints and relative-pointer are used.
8706       if (GdkIsWaylandDisplay()) {
8707         return NS_OK;
8708       }
8709 #endif
8710       GdkScreen* screen = gdk_window_get_screen(mGdkWindow);
8711       GdkPoint point = DevicePixelsToGdkPointRoundDown(aPoint);
8712       gdk_device_warp(GdkGetPointer(), screen, point.x, point.y);
8713       return NS_OK;
8714     }
8715     case NativeMouseMessage::EnterWindow:
8716     case NativeMouseMessage::LeaveWindow:
8717       MOZ_ASSERT_UNREACHABLE("Non supported mouse event on Linux");
8718       return NS_ERROR_INVALID_ARG;
8719   }
8720   return NS_ERROR_UNEXPECTED;
8721 }
8722 
SynthesizeNativeMouseScrollEvent(mozilla::LayoutDeviceIntPoint aPoint,uint32_t aNativeMessage,double aDeltaX,double aDeltaY,double aDeltaZ,uint32_t aModifierFlags,uint32_t aAdditionalFlags,nsIObserver * aObserver)8723 nsresult nsWindow::SynthesizeNativeMouseScrollEvent(
8724     mozilla::LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage,
8725     double aDeltaX, double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
8726     uint32_t aAdditionalFlags, nsIObserver* aObserver) {
8727   AutoObserverNotifier notifier(aObserver, "mousescrollevent");
8728 
8729   if (!mGdkWindow) {
8730     return NS_OK;
8731   }
8732 
8733   GdkEvent event;
8734   memset(&event, 0, sizeof(GdkEvent));
8735   event.type = GDK_SCROLL;
8736   event.scroll.window = mGdkWindow;
8737   event.scroll.time = GDK_CURRENT_TIME;
8738   // Get device for event source
8739   GdkDisplay* display = gdk_window_get_display(mGdkWindow);
8740   GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
8741   event.scroll.device = gdk_device_manager_get_client_pointer(device_manager);
8742   event.scroll.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
8743   event.scroll.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
8744 
8745   LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
8746   event.scroll.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
8747   event.scroll.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
8748 
8749   // The delta values are backwards on Linux compared to Windows and Cocoa,
8750   // hence the negation.
8751   event.scroll.direction = GDK_SCROLL_SMOOTH;
8752   event.scroll.delta_x = -aDeltaX;
8753   event.scroll.delta_y = -aDeltaY;
8754 
8755   gdk_event_put(&event);
8756 
8757   return NS_OK;
8758 }
8759 
SynthesizeNativeTouchPoint(uint32_t aPointerId,TouchPointerState aPointerState,LayoutDeviceIntPoint aPoint,double aPointerPressure,uint32_t aPointerOrientation,nsIObserver * aObserver)8760 nsresult nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId,
8761                                               TouchPointerState aPointerState,
8762                                               LayoutDeviceIntPoint aPoint,
8763                                               double aPointerPressure,
8764                                               uint32_t aPointerOrientation,
8765                                               nsIObserver* aObserver) {
8766   AutoObserverNotifier notifier(aObserver, "touchpoint");
8767 
8768   if (!mGdkWindow) {
8769     return NS_OK;
8770   }
8771 
8772   GdkEvent event;
8773   memset(&event, 0, sizeof(GdkEvent));
8774 
8775   static std::map<uint32_t, GdkEventSequence*> sKnownPointers;
8776 
8777   auto result = sKnownPointers.find(aPointerId);
8778   switch (aPointerState) {
8779     case TOUCH_CONTACT:
8780       if (result == sKnownPointers.end()) {
8781         // GdkEventSequence isn't a thing we can instantiate, and never gets
8782         // dereferenced in the gtk code. It's an opaque pointer, the only
8783         // requirement is that it be distinct from other instances of
8784         // GdkEventSequence*.
8785         event.touch.sequence = (GdkEventSequence*)((uintptr_t)aPointerId);
8786         sKnownPointers[aPointerId] = event.touch.sequence;
8787         event.type = GDK_TOUCH_BEGIN;
8788       } else {
8789         event.touch.sequence = result->second;
8790         event.type = GDK_TOUCH_UPDATE;
8791       }
8792       break;
8793     case TOUCH_REMOVE:
8794       event.type = GDK_TOUCH_END;
8795       if (result == sKnownPointers.end()) {
8796         NS_WARNING("Tried to synthesize touch-end for unknown pointer!");
8797         return NS_ERROR_UNEXPECTED;
8798       }
8799       event.touch.sequence = result->second;
8800       sKnownPointers.erase(result);
8801       break;
8802     case TOUCH_CANCEL:
8803       event.type = GDK_TOUCH_CANCEL;
8804       if (result == sKnownPointers.end()) {
8805         NS_WARNING("Tried to synthesize touch-cancel for unknown pointer!");
8806         return NS_ERROR_UNEXPECTED;
8807       }
8808       event.touch.sequence = result->second;
8809       sKnownPointers.erase(result);
8810       break;
8811     case TOUCH_HOVER:
8812     default:
8813       return NS_ERROR_NOT_IMPLEMENTED;
8814   }
8815 
8816   event.touch.window = mGdkWindow;
8817   event.touch.time = GDK_CURRENT_TIME;
8818 
8819   GdkDisplay* display = gdk_window_get_display(mGdkWindow);
8820   GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
8821   event.touch.device = gdk_device_manager_get_client_pointer(device_manager);
8822 
8823   event.touch.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
8824   event.touch.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
8825 
8826   LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
8827   event.touch.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
8828   event.touch.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
8829 
8830   gdk_event_put(&event);
8831 
8832   return NS_OK;
8833 }
8834 
SynthesizeNativeTouchPadPinch(TouchpadGesturePhase aEventPhase,float aScale,LayoutDeviceIntPoint aPoint,int32_t aModifierFlags)8835 nsresult nsWindow::SynthesizeNativeTouchPadPinch(
8836     TouchpadGesturePhase aEventPhase, float aScale, LayoutDeviceIntPoint aPoint,
8837     int32_t aModifierFlags) {
8838   if (!mGdkWindow) {
8839     return NS_OK;
8840   }
8841   GdkEvent event;
8842   memset(&event, 0, sizeof(GdkEvent));
8843 
8844   GdkEventTouchpadPinch* touchpad_event =
8845       reinterpret_cast<GdkEventTouchpadPinch*>(&event);
8846   touchpad_event->type = GDK_TOUCHPAD_PINCH;
8847 
8848   switch (aEventPhase) {
8849     case PHASE_BEGIN:
8850       touchpad_event->phase = GDK_TOUCHPAD_GESTURE_PHASE_BEGIN;
8851       break;
8852     case PHASE_UPDATE:
8853       touchpad_event->phase = GDK_TOUCHPAD_GESTURE_PHASE_UPDATE;
8854       break;
8855     case PHASE_END:
8856       touchpad_event->phase = GDK_TOUCHPAD_GESTURE_PHASE_END;
8857       break;
8858 
8859     default:
8860       return NS_ERROR_NOT_IMPLEMENTED;
8861   }
8862 
8863   touchpad_event->window = mGdkWindow;
8864   // We only set the fields of GdkEventTouchpadPinch which are
8865   // actually used in OnTouchpadPinchEvent().
8866   // GdkEventTouchpadPinch has additional fields (for example, `dx` and `dy`).
8867   // If OnTouchpadPinchEvent() is changed to use other fields, this function
8868   // will need to change to set them as well.
8869   touchpad_event->time = GDK_CURRENT_TIME;
8870   touchpad_event->scale = aScale;
8871   touchpad_event->x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
8872   touchpad_event->y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
8873 
8874   LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
8875   touchpad_event->x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
8876   touchpad_event->y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
8877   touchpad_event->state = aModifierFlags;
8878 
8879   gdk_event_put(&event);
8880 
8881   return NS_OK;
8882 }
8883 
GetSystemGtkWindowDecoration()8884 nsWindow::GtkWindowDecoration nsWindow::GetSystemGtkWindowDecoration() {
8885   static GtkWindowDecoration sGtkWindowDecoration = [] {
8886     // Allow MOZ_GTK_TITLEBAR_DECORATION to override our heuristics
8887     if (const char* decorationOverride =
8888             getenv("MOZ_GTK_TITLEBAR_DECORATION")) {
8889       if (strcmp(decorationOverride, "none") == 0) {
8890         return GTK_DECORATION_NONE;
8891       }
8892       if (strcmp(decorationOverride, "client") == 0) {
8893         return GTK_DECORATION_CLIENT;
8894       }
8895       if (strcmp(decorationOverride, "system") == 0) {
8896         return GTK_DECORATION_SYSTEM;
8897       }
8898     }
8899 
8900     // nsWindow::GetSystemGtkWindowDecoration can be called from various
8901     // threads so we can't use gfxPlatformGtk here.
8902     if (GdkIsWaylandDisplay()) {
8903       return GTK_DECORATION_CLIENT;
8904     }
8905 
8906     // GTK_CSD forces CSD mode - use also CSD because window manager
8907     // decorations does not work with CSD.
8908     // We check GTK_CSD as well as gtk_window_should_use_csd() does.
8909     const char* csdOverride = getenv("GTK_CSD");
8910     if (csdOverride && *csdOverride == '1') {
8911       return GTK_DECORATION_CLIENT;
8912     }
8913 
8914     const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
8915     if (!currentDesktop) {
8916       return GTK_DECORATION_NONE;
8917     }
8918     // clang-format off
8919     if (strstr(currentDesktop, "pop:GNOME") || // Bug 1629198
8920         strstr(currentDesktop, "KDE") ||
8921         strstr(currentDesktop, "Enlightenment") ||
8922         strstr(currentDesktop, "LXDE") ||
8923         strstr(currentDesktop, "openbox") ||
8924         strstr(currentDesktop, "MATE") ||
8925         strstr(currentDesktop, "X-Cinnamon") ||
8926         strstr(currentDesktop, "Pantheon") ||
8927         strstr(currentDesktop, "Deepin")) {
8928       return GTK_DECORATION_CLIENT;
8929     }
8930     if (strstr(currentDesktop, "GNOME") ||
8931         strstr(currentDesktop, "LXQt") ||
8932         strstr(currentDesktop, "Unity")) {
8933       return GTK_DECORATION_SYSTEM;
8934     }
8935     if (strstr(currentDesktop, "i3")) {
8936       return GTK_DECORATION_NONE;
8937     }
8938     // clang-format on
8939 
8940     return GTK_DECORATION_CLIENT;
8941   }();
8942   return sGtkWindowDecoration;
8943 }
8944 
TitlebarUseShapeMask()8945 bool nsWindow::TitlebarUseShapeMask() {
8946   static int useShapeMask = []() {
8947     // Don't use titlebar shape mask on Wayland
8948     if (!GdkIsX11Display()) {
8949       return false;
8950     }
8951 
8952     // We can't use shape masks on Mutter/X.org as we can't resize Firefox
8953     // window there (Bug 1530252).
8954     const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
8955     if (currentDesktop) {
8956       if (strstr(currentDesktop, "GNOME") != nullptr) {
8957         const char* sessionType = getenv("XDG_SESSION_TYPE");
8958         if (sessionType && strstr(sessionType, "x11") != nullptr) {
8959           return false;
8960         }
8961       }
8962     }
8963 
8964     return Preferences::GetBool("widget.titlebar-x11-use-shape-mask", false);
8965   }();
8966   return useShapeMask;
8967 }
8968 
RoundsWidgetCoordinatesTo()8969 int32_t nsWindow::RoundsWidgetCoordinatesTo() { return GdkCeiledScaleFactor(); }
8970 
GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData * aInitData)8971 void nsWindow::GetCompositorWidgetInitData(
8972     mozilla::widget::CompositorWidgetInitData* aInitData) {
8973   nsCString displayName;
8974 
8975   LOG("nsWindow::GetCompositorWidgetInitData");
8976   EnsureGdkWindow();
8977 
8978   *aInitData = mozilla::widget::GtkCompositorWidgetInitData(
8979       GetX11Window(), displayName, GetShapedState(), GdkIsX11Display(),
8980       GetClientSize());
8981 
8982 #ifdef MOZ_X11
8983   if (GdkIsX11Display()) {
8984     // Make sure the window XID is propagated to X server, we can fail otherwise
8985     // in GPU process (Bug 1401634).
8986     Display* display = DefaultXDisplay();
8987     XFlush(display);
8988     displayName = nsCString(XDisplayString(display));
8989   }
8990 #endif
8991 }
8992 
8993 #ifdef MOZ_X11
8994 /* XApp progress support currently works by setting a property
8995  * on a window with this Atom name.  A supporting window manager
8996  * will notice this and pass it along to whatever handling has
8997  * been implemented on that end (e.g. passing it on to a taskbar
8998  * widget.)  There is no issue if WM support is lacking, this is
8999  * simply ignored in that case.
9000  *
9001  * See https://github.com/linuxmint/xapps/blob/master/libxapp/xapp-gtk-window.c
9002  * for further details.
9003  */
9004 
9005 #  define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS"
9006 
set_window_hint_cardinal(Window xid,const gchar * atom_name,gulong cardinal)9007 static void set_window_hint_cardinal(Window xid, const gchar* atom_name,
9008                                      gulong cardinal) {
9009   GdkDisplay* display;
9010 
9011   display = gdk_display_get_default();
9012 
9013   if (cardinal > 0) {
9014     XChangeProperty(GDK_DISPLAY_XDISPLAY(display), xid,
9015                     gdk_x11_get_xatom_by_name_for_display(display, atom_name),
9016                     XA_CARDINAL, 32, PropModeReplace, (guchar*)&cardinal, 1);
9017   } else {
9018     XDeleteProperty(GDK_DISPLAY_XDISPLAY(display), xid,
9019                     gdk_x11_get_xatom_by_name_for_display(display, atom_name));
9020   }
9021 }
9022 #endif  // MOZ_X11
9023 
SetProgress(unsigned long progressPercent)9024 void nsWindow::SetProgress(unsigned long progressPercent) {
9025 #ifdef MOZ_X11
9026 
9027   if (!GdkIsX11Display()) {
9028     return;
9029   }
9030 
9031   if (!mShell) {
9032     return;
9033   }
9034 
9035   progressPercent = MIN(progressPercent, 100);
9036 
9037   set_window_hint_cardinal(GDK_WINDOW_XID(gtk_widget_get_window(mShell)),
9038                            PROGRESS_HINT, progressPercent);
9039 #endif  // MOZ_X11
9040 }
9041 
9042 #ifdef MOZ_X11
SetCompositorHint(WindowComposeRequest aState)9043 void nsWindow::SetCompositorHint(WindowComposeRequest aState) {
9044   if (!GdkIsX11Display()) {
9045     return;
9046   }
9047 
9048   gulong value = aState;
9049   GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
9050   gdk_property_change(gtk_widget_get_window(mShell),
9051                       gdk_atom_intern("_NET_WM_BYPASS_COMPOSITOR", FALSE),
9052                       cardinal_atom,
9053                       32,  // format
9054                       GDK_PROP_MODE_REPLACE, (guchar*)&value, 1);
9055 }
9056 #endif
9057 
SetSystemFont(const nsCString & aFontName)9058 nsresult nsWindow::SetSystemFont(const nsCString& aFontName) {
9059   GtkSettings* settings = gtk_settings_get_default();
9060   g_object_set(settings, "gtk-font-name", aFontName.get(), nullptr);
9061   return NS_OK;
9062 }
9063 
GetSystemFont(nsCString & aFontName)9064 nsresult nsWindow::GetSystemFont(nsCString& aFontName) {
9065   GtkSettings* settings = gtk_settings_get_default();
9066   gchar* fontName = nullptr;
9067   g_object_get(settings, "gtk-font-name", &fontName, nullptr);
9068   if (fontName) {
9069     aFontName.Assign(fontName);
9070     g_free(fontName);
9071   }
9072   return NS_OK;
9073 }
9074 
CreateTopLevelWindow()9075 already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
9076   nsCOMPtr<nsIWidget> window = new nsWindow();
9077   return window.forget();
9078 }
9079 
CreateChildWindow()9080 already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
9081   nsCOMPtr<nsIWidget> window = new nsWindow();
9082   return window.forget();
9083 }
9084 
9085 #ifdef MOZ_WAYLAND
relative_pointer_handle_relative_motion(void * data,struct zwp_relative_pointer_v1 * pointer,uint32_t time_hi,uint32_t time_lo,wl_fixed_t dx_w,wl_fixed_t dy_w,wl_fixed_t dx_unaccel_w,wl_fixed_t dy_unaccel_w)9086 static void relative_pointer_handle_relative_motion(
9087     void* data, struct zwp_relative_pointer_v1* pointer, uint32_t time_hi,
9088     uint32_t time_lo, wl_fixed_t dx_w, wl_fixed_t dy_w, wl_fixed_t dx_unaccel_w,
9089     wl_fixed_t dy_unaccel_w) {
9090   RefPtr<nsWindow> window(reinterpret_cast<nsWindow*>(data));
9091 
9092   WidgetMouseEvent event(true, eMouseMove, window, WidgetMouseEvent::eReal);
9093 
9094   event.mRefPoint = window->GetNativePointerLockCenter();
9095   event.mRefPoint.x += wl_fixed_to_int(dx_w);
9096   event.mRefPoint.y += wl_fixed_to_int(dy_w);
9097 
9098   event.AssignEventTime(window->GetWidgetEventTime(time_lo));
9099   window->DispatchInputEvent(&event);
9100 }
9101 
9102 static const struct zwp_relative_pointer_v1_listener relative_pointer_listener =
9103     {
9104         relative_pointer_handle_relative_motion,
9105 };
9106 
SetNativePointerLockCenter(const LayoutDeviceIntPoint & aLockCenter)9107 void nsWindow::SetNativePointerLockCenter(
9108     const LayoutDeviceIntPoint& aLockCenter) {
9109   mNativePointerLockCenter = aLockCenter;
9110 }
9111 
LockNativePointer()9112 void nsWindow::LockNativePointer() {
9113   if (!GdkIsWaylandDisplay()) {
9114     return;
9115   }
9116 
9117   auto waylandDisplay = WaylandDisplayGet();
9118 
9119   auto* pointerConstraints = waylandDisplay->GetPointerConstraints();
9120   if (!pointerConstraints) {
9121     return;
9122   }
9123 
9124   auto* relativePointerMgr = waylandDisplay->GetRelativePointerManager();
9125   if (!relativePointerMgr) {
9126     return;
9127   }
9128 
9129   GdkDisplay* display = gdk_display_get_default();
9130 
9131   GdkDeviceManager* manager = gdk_display_get_device_manager(display);
9132   MOZ_ASSERT(manager);
9133 
9134   GdkDevice* device = gdk_device_manager_get_client_pointer(manager);
9135   if (!device) {
9136     NS_WARNING("Could not find Wayland pointer to lock");
9137     return;
9138   }
9139   wl_pointer* pointer = gdk_wayland_device_get_wl_pointer(device);
9140   MOZ_ASSERT(pointer);
9141 
9142   wl_surface* surface =
9143       gdk_wayland_window_get_wl_surface(gtk_widget_get_window(GetGtkWidget()));
9144   if (!surface) {
9145     /* Can be null when the window is hidden.
9146      * Though it's unlikely that a lock request comes in that case, be
9147      * defensive. */
9148     return;
9149   }
9150 
9151   mLockedPointer = zwp_pointer_constraints_v1_lock_pointer(
9152       pointerConstraints, surface, pointer, nullptr,
9153       ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
9154   if (!mLockedPointer) {
9155     NS_WARNING("Could not lock Wayland pointer");
9156     return;
9157   }
9158 
9159   mRelativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer(
9160       relativePointerMgr, pointer);
9161   if (!mRelativePointer) {
9162     NS_WARNING("Could not create relative Wayland pointer");
9163     zwp_locked_pointer_v1_destroy(mLockedPointer);
9164     mLockedPointer = nullptr;
9165     return;
9166   }
9167 
9168   zwp_relative_pointer_v1_add_listener(mRelativePointer,
9169                                        &relative_pointer_listener, this);
9170 }
9171 
UnlockNativePointer()9172 void nsWindow::UnlockNativePointer() {
9173   if (!GdkIsWaylandDisplay()) {
9174     return;
9175   }
9176   if (mRelativePointer) {
9177     zwp_relative_pointer_v1_destroy(mRelativePointer);
9178     mRelativePointer = nullptr;
9179   }
9180   if (mLockedPointer) {
9181     zwp_locked_pointer_v1_destroy(mLockedPointer);
9182     mLockedPointer = nullptr;
9183   }
9184 }
9185 #endif
9186 
GetTopLevelWindowActiveState(nsIFrame * aFrame)9187 bool nsWindow::GetTopLevelWindowActiveState(nsIFrame* aFrame) {
9188   // Used by window frame and button box rendering. We can end up in here in
9189   // the content process when rendering one of these moz styles freely in a
9190   // page. Fail in this case, there is no applicable window focus state.
9191   if (!XRE_IsParentProcess()) {
9192     return false;
9193   }
9194   // All headless windows are considered active so they are painted.
9195   if (gfxPlatform::IsHeadless()) {
9196     return true;
9197   }
9198   // Get the widget. nsIFrame's GetNearestWidget walks up the view chain
9199   // until it finds a real window.
9200   nsWindow* window = static_cast<nsWindow*>(aFrame->GetNearestWidget());
9201   if (!window) {
9202     return false;
9203   }
9204   return !window->mTitlebarBackdropState;
9205 }
9206 
FindTitlebarFrame(nsIFrame * aFrame)9207 static nsIFrame* FindTitlebarFrame(nsIFrame* aFrame) {
9208   for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
9209     StyleAppearance appearance =
9210         childFrame->StyleDisplay()->EffectiveAppearance();
9211     if (appearance == StyleAppearance::MozWindowTitlebar ||
9212         appearance == StyleAppearance::MozWindowTitlebarMaximized) {
9213       return childFrame;
9214     }
9215 
9216     if (nsIFrame* foundFrame = FindTitlebarFrame(childFrame)) {
9217       return foundFrame;
9218     }
9219   }
9220   return nullptr;
9221 }
9222 
GetFrame() const9223 nsIFrame* nsWindow::GetFrame() const {
9224   nsView* view = nsView::GetViewFor(this);
9225   if (!view) {
9226     return nullptr;
9227   }
9228   return view->GetFrame();
9229 }
9230 
UpdateMozWindowActive()9231 void nsWindow::UpdateMozWindowActive() {
9232   // Update activation state for the :-moz-window-inactive pseudoclass.
9233   // Normally, this follows focus; we override it here to follow
9234   // GDK_WINDOW_STATE_FOCUSED.
9235   if (mozilla::dom::Document* document = GetDocument()) {
9236     if (nsPIDOMWindowOuter* window = document->GetWindow()) {
9237       if (RefPtr<mozilla::dom::BrowsingContext> bc =
9238               window->GetBrowsingContext()) {
9239         bc->SetIsActiveBrowserWindow(!mTitlebarBackdropState);
9240       }
9241     }
9242   }
9243 }
9244 
ForceTitlebarRedraw(void)9245 void nsWindow::ForceTitlebarRedraw(void) {
9246   MOZ_ASSERT(mDrawInTitlebar, "We should not redraw invisible titlebar.");
9247 
9248   if (!mWidgetListener || !mWidgetListener->GetPresShell()) {
9249     return;
9250   }
9251 
9252   nsIFrame* frame = GetFrame();
9253   if (!frame) {
9254     return;
9255   }
9256 
9257   frame = FindTitlebarFrame(frame);
9258   if (frame) {
9259     nsIContent* content = frame->GetContent();
9260     if (content) {
9261       nsLayoutUtils::PostRestyleEvent(content->AsElement(), RestyleHint{0},
9262                                       nsChangeHint_RepaintFrame);
9263     }
9264   }
9265 }
9266 
LockAspectRatio(bool aShouldLock)9267 void nsWindow::LockAspectRatio(bool aShouldLock) {
9268   if (!gUseAspectRatio) {
9269     return;
9270   }
9271 
9272   if (aShouldLock) {
9273     int decWidth = 0, decHeight = 0;
9274     AddCSDDecorationSize(&decWidth, &decHeight);
9275 
9276     float width =
9277         (float)DevicePixelsToGdkCoordRoundDown(mBounds.width) + (float)decWidth;
9278     float height = (float)DevicePixelsToGdkCoordRoundDown(mBounds.height) +
9279                    (float)decHeight;
9280 
9281     mAspectRatio = width / height;
9282     LOG("nsWindow::LockAspectRatio() width %f height %f aspect %f", width,
9283         height, mAspectRatio);
9284   } else {
9285     mAspectRatio = 0.0;
9286     LOG("nsWindow::LockAspectRatio() removed aspect ratio");
9287   }
9288 
9289   ApplySizeConstraints();
9290 }
9291 
GetFocusedWindow()9292 nsWindow* nsWindow::GetFocusedWindow() { return gFocusWindow; }
9293 
9294 #ifdef MOZ_WAYLAND
SetEGLNativeWindowSize(const LayoutDeviceIntSize & aEGLWindowSize)9295 void nsWindow::SetEGLNativeWindowSize(
9296     const LayoutDeviceIntSize& aEGLWindowSize) {
9297   if (!mContainer || !GdkIsWaylandDisplay()) {
9298     return;
9299   }
9300   moz_container_wayland_egl_window_set_size(mContainer, aEGLWindowSize.width,
9301                                             aEGLWindowSize.height);
9302   moz_container_wayland_set_scale_factor(mContainer);
9303 }
9304 #endif
9305 
GetMozContainerSize()9306 LayoutDeviceIntSize nsWindow::GetMozContainerSize() {
9307   LayoutDeviceIntSize size(0, 0);
9308   if (mContainer) {
9309     GtkAllocation allocation;
9310     gtk_widget_get_allocation(GTK_WIDGET(mContainer), &allocation);
9311     double scale = FractionalScaleFactor();
9312     size.width = round(allocation.width * scale);
9313     size.height = round(allocation.height * scale);
9314   }
9315   return size;
9316 }
9317