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