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