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