forked from mirrors/gecko-dev
		
	 ec695c5797
			
		
	
	
		ec695c5797
		
	
	
	
	
		
			
			nsBaseWidget already stores these / exposes IsPIPWindow(). Differential Revision: https://phabricator.services.mozilla.com/D197866
		
			
				
	
	
		
			9899 lines
		
	
	
	
		
			330 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			9899 lines
		
	
	
	
		
			330 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim:expandtab:shiftwidth=2:tabstop=2:
 | |
|  */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "nsWindow.h"
 | |
| 
 | |
| #include <algorithm>
 | |
| #include <cstdint>
 | |
| #include <dlfcn.h>
 | |
| #include <gdk/gdkkeysyms.h>
 | |
| #include <wchar.h>
 | |
| 
 | |
| #include "VsyncSource.h"
 | |
| #include "gfx2DGlue.h"
 | |
| #include "gfxContext.h"
 | |
| #include "gfxImageSurface.h"
 | |
| #include "gfxPlatformGtk.h"
 | |
| #include "gfxUtils.h"
 | |
| #include "GLContextProvider.h"
 | |
| #include "GLContext.h"
 | |
| #include "GtkCompositorWidget.h"
 | |
| #include "gtkdrawing.h"
 | |
| #include "imgIContainer.h"
 | |
| #include "InputData.h"
 | |
| #include "mozilla/ArrayUtils.h"
 | |
| #include "mozilla/Assertions.h"
 | |
| #include "mozilla/Components.h"
 | |
| #include "mozilla/GRefPtr.h"
 | |
| #include "mozilla/dom/Document.h"
 | |
| #include "mozilla/dom/WheelEventBinding.h"
 | |
| #include "mozilla/gfx/2D.h"
 | |
| #include "mozilla/gfx/gfxVars.h"
 | |
| #include "mozilla/gfx/GPUProcessManager.h"
 | |
| #include "mozilla/gfx/HelpersCairo.h"
 | |
| #include "mozilla/layers/APZThreadUtils.h"
 | |
| #include "mozilla/layers/LayersTypes.h"
 | |
| #include "mozilla/layers/CompositorBridgeChild.h"
 | |
| #include "mozilla/layers/CompositorBridgeParent.h"
 | |
| #include "mozilla/layers/CompositorThread.h"
 | |
| #include "mozilla/layers/KnowsCompositor.h"
 | |
| #include "mozilla/layers/WebRenderBridgeChild.h"
 | |
| #include "mozilla/layers/WebRenderLayerManager.h"
 | |
| #include "mozilla/layers/APZInputBridge.h"
 | |
| #include "mozilla/layers/IAPZCTreeManager.h"
 | |
| #include "mozilla/Likely.h"
 | |
| #include "mozilla/MiscEvents.h"
 | |
| #include "mozilla/MouseEvents.h"
 | |
| #include "mozilla/NativeKeyBindingsType.h"
 | |
| #include "mozilla/Preferences.h"
 | |
| #include "mozilla/PresShell.h"
 | |
| #include "mozilla/ProfilerLabels.h"
 | |
| #include "mozilla/ScopeExit.h"
 | |
| #include "mozilla/StaticPrefs_apz.h"
 | |
| #include "mozilla/StaticPrefs_layout.h"
 | |
| #include "mozilla/StaticPrefs_mozilla.h"
 | |
| #include "mozilla/StaticPrefs_ui.h"
 | |
| #include "mozilla/StaticPrefs_widget.h"
 | |
| #include "mozilla/SwipeTracker.h"
 | |
| #include "mozilla/TextEventDispatcher.h"
 | |
| #include "mozilla/TextEvents.h"
 | |
| #include "mozilla/TimeStamp.h"
 | |
| #include "mozilla/UniquePtrExtensions.h"
 | |
| #include "mozilla/WidgetUtils.h"
 | |
| #include "mozilla/WritingModes.h"
 | |
| #ifdef MOZ_X11
 | |
| #  include "mozilla/X11Util.h"
 | |
| #endif
 | |
| #include "mozilla/XREAppData.h"
 | |
| #include "NativeKeyBindings.h"
 | |
| #include "nsAppDirectoryServiceDefs.h"
 | |
| #include "nsAppRunner.h"
 | |
| #include "nsDragService.h"
 | |
| #include "nsGTKToolkit.h"
 | |
| #include "nsGtkKeyUtils.h"
 | |
| #include "nsGtkCursors.h"
 | |
| #include "nsGfxCIID.h"
 | |
| #include "nsGtkUtils.h"
 | |
| #include "nsIFile.h"
 | |
| #include "nsIGSettingsService.h"
 | |
| #include "nsIInterfaceRequestorUtils.h"
 | |
| #include "nsImageToPixbuf.h"
 | |
| #include "nsINode.h"
 | |
| #include "nsIRollupListener.h"
 | |
| #include "nsIScreenManager.h"
 | |
| #include "nsIUserIdleServiceInternal.h"
 | |
| #include "nsIWidgetListener.h"
 | |
| #include "nsLayoutUtils.h"
 | |
| #include "nsMenuPopupFrame.h"
 | |
| #include "nsPresContext.h"
 | |
| #include "nsShmImage.h"
 | |
| #include "nsString.h"
 | |
| #include "nsWidgetsCID.h"
 | |
| #include "nsViewManager.h"
 | |
| #include "nsXPLookAndFeel.h"
 | |
| #include "prlink.h"
 | |
| #include "Screen.h"
 | |
| #include "ScreenHelperGTK.h"
 | |
| #include "SystemTimeConverter.h"
 | |
| #include "WidgetUtilsGtk.h"
 | |
| 
 | |
| #ifdef ACCESSIBILITY
 | |
| #  include "mozilla/a11y/LocalAccessible.h"
 | |
| #  include "mozilla/a11y/Platform.h"
 | |
| #  include "nsAccessibilityService.h"
 | |
| #endif
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
| #  include <gdk/gdkkeysyms-compat.h>
 | |
| #  include <X11/Xatom.h>
 | |
| #  include <X11/extensions/XShm.h>
 | |
| #  include <X11/extensions/shape.h>
 | |
| #  include "gfxXlibSurface.h"
 | |
| #  include "GLContextGLX.h"  // for GLContextGLX::FindVisual()
 | |
| #  include "GLContextEGL.h"  // for GLContextEGL::FindVisual()
 | |
| #  include "WindowSurfaceX11Image.h"
 | |
| #  include "WindowSurfaceX11SHM.h"
 | |
| #endif
 | |
| #ifdef MOZ_WAYLAND
 | |
| #  include <gdk/gdkkeysyms-compat.h>
 | |
| #  include "nsIClipboard.h"
 | |
| #  include "nsView.h"
 | |
| #endif
 | |
| 
 | |
| using namespace mozilla;
 | |
| using namespace mozilla::gfx;
 | |
| using namespace mozilla::layers;
 | |
| using namespace mozilla::widget;
 | |
| #ifdef MOZ_X11
 | |
| using mozilla::gl::GLContextEGL;
 | |
| using mozilla::gl::GLContextGLX;
 | |
| #endif
 | |
| 
 | |
| // Don't put more than this many rects in the dirty region, just fluff
 | |
| // out to the bounding-box if there are more
 | |
| #define MAX_RECTS_IN_REGION 100
 | |
| 
 | |
| #if !GTK_CHECK_VERSION(3, 18, 0)
 | |
| 
 | |
| struct _GdkEventTouchpadPinch {
 | |
|   GdkEventType type;
 | |
|   GdkWindow* window;
 | |
|   gint8 send_event;
 | |
|   gint8 phase;
 | |
|   gint8 n_fingers;
 | |
|   guint32 time;
 | |
|   gdouble x;
 | |
|   gdouble y;
 | |
|   gdouble dx;
 | |
|   gdouble dy;
 | |
|   gdouble angle_delta;
 | |
|   gdouble scale;
 | |
|   gdouble x_root, y_root;
 | |
|   guint state;
 | |
| };
 | |
| 
 | |
| typedef enum {
 | |
|   GDK_TOUCHPAD_GESTURE_PHASE_BEGIN,
 | |
|   GDK_TOUCHPAD_GESTURE_PHASE_UPDATE,
 | |
|   GDK_TOUCHPAD_GESTURE_PHASE_END,
 | |
|   GDK_TOUCHPAD_GESTURE_PHASE_CANCEL
 | |
| } GdkTouchpadGesturePhase;
 | |
| 
 | |
| gint GDK_TOUCHPAD_GESTURE_MASK = 1 << 24;
 | |
| GdkEventType GDK_TOUCHPAD_PINCH = static_cast<GdkEventType>(42);
 | |
| 
 | |
| #endif
 | |
| 
 | |
| const gint kEvents = GDK_TOUCHPAD_GESTURE_MASK | GDK_EXPOSURE_MASK |
 | |
|                      GDK_STRUCTURE_MASK | GDK_VISIBILITY_NOTIFY_MASK |
 | |
|                      GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
 | |
|                      GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
 | |
|                      GDK_SMOOTH_SCROLL_MASK | GDK_TOUCH_MASK | GDK_SCROLL_MASK |
 | |
|                      GDK_POINTER_MOTION_MASK | GDK_PROPERTY_CHANGE_MASK;
 | |
| 
 | |
| /* utility functions */
 | |
| static bool is_mouse_in_window(GdkWindow* aWindow, gdouble aMouseX,
 | |
|                                gdouble aMouseY);
 | |
| static nsWindow* get_window_for_gtk_widget(GtkWidget* widget);
 | |
| static nsWindow* get_window_for_gdk_window(GdkWindow* window);
 | |
| static GtkWidget* get_gtk_widget_for_gdk_window(GdkWindow* window);
 | |
| static GdkCursor* get_gtk_cursor(nsCursor aCursor);
 | |
| static GdkWindow* get_inner_gdk_window(GdkWindow* aWindow, gint x, gint y,
 | |
|                                        gint* retx, gint* rety);
 | |
| 
 | |
| /* callbacks from widgets */
 | |
| static gboolean expose_event_cb(GtkWidget* widget, cairo_t* cr);
 | |
| static gboolean configure_event_cb(GtkWidget* widget, GdkEventConfigure* event);
 | |
| static void widget_map_cb(GtkWidget* widget);
 | |
| static void widget_unmap_cb(GtkWidget* widget);
 | |
| static void widget_unrealize_cb(GtkWidget* widget);
 | |
| static void size_allocate_cb(GtkWidget* widget, GtkAllocation* allocation);
 | |
| static void toplevel_window_size_allocate_cb(GtkWidget* widget,
 | |
|                                              GtkAllocation* allocation);
 | |
| static gboolean delete_event_cb(GtkWidget* widget, GdkEventAny* event);
 | |
| static gboolean enter_notify_event_cb(GtkWidget* widget,
 | |
|                                       GdkEventCrossing* event);
 | |
| static gboolean leave_notify_event_cb(GtkWidget* widget,
 | |
|                                       GdkEventCrossing* event);
 | |
| static gboolean motion_notify_event_cb(GtkWidget* widget,
 | |
|                                        GdkEventMotion* event);
 | |
| MOZ_CAN_RUN_SCRIPT static gboolean button_press_event_cb(GtkWidget* widget,
 | |
|                                                          GdkEventButton* event);
 | |
| static gboolean button_release_event_cb(GtkWidget* widget,
 | |
|                                         GdkEventButton* event);
 | |
| static gboolean focus_in_event_cb(GtkWidget* widget, GdkEventFocus* event);
 | |
| static gboolean focus_out_event_cb(GtkWidget* widget, GdkEventFocus* event);
 | |
| static gboolean key_press_event_cb(GtkWidget* widget, GdkEventKey* event);
 | |
| static gboolean key_release_event_cb(GtkWidget* widget, GdkEventKey* event);
 | |
| static gboolean property_notify_event_cb(GtkWidget* widget,
 | |
|                                          GdkEventProperty* event);
 | |
| static gboolean scroll_event_cb(GtkWidget* widget, GdkEventScroll* event);
 | |
| static gboolean visibility_notify_event_cb(GtkWidget* widget,
 | |
|                                            GdkEventVisibility* event);
 | |
| static void hierarchy_changed_cb(GtkWidget* widget,
 | |
|                                  GtkWidget* previous_toplevel);
 | |
| static gboolean window_state_event_cb(GtkWidget* widget,
 | |
|                                       GdkEventWindowState* event);
 | |
| static void settings_xft_dpi_changed_cb(GtkSettings* settings,
 | |
|                                         GParamSpec* pspec, nsWindow* data);
 | |
| static void check_resize_cb(GtkContainer* container, gpointer user_data);
 | |
| static void screen_composited_changed_cb(GdkScreen* screen, gpointer user_data);
 | |
| static void widget_composited_changed_cb(GtkWidget* widget, gpointer user_data);
 | |
| 
 | |
| static void scale_changed_cb(GtkWidget* widget, GParamSpec* aPSpec,
 | |
|                              gpointer aPointer);
 | |
| static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent);
 | |
| static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent);
 | |
| 
 | |
| static nsWindow* GetFirstNSWindowForGDKWindow(GdkWindow* aGdkWindow);
 | |
| 
 | |
| #ifdef __cplusplus
 | |
| extern "C" {
 | |
| #endif /* __cplusplus */
 | |
| #ifdef MOZ_X11
 | |
| static GdkFilterReturn popup_take_focus_filter(GdkXEvent* gdk_xevent,
 | |
|                                                GdkEvent* event, gpointer data);
 | |
| #endif /* MOZ_X11 */
 | |
| #ifdef __cplusplus
 | |
| }
 | |
| #endif /* __cplusplus */
 | |
| 
 | |
| static gboolean drag_motion_event_cb(GtkWidget* aWidget,
 | |
|                                      GdkDragContext* aDragContext, gint aX,
 | |
|                                      gint aY, guint aTime, gpointer aData);
 | |
| static void drag_leave_event_cb(GtkWidget* aWidget,
 | |
|                                 GdkDragContext* aDragContext, guint aTime,
 | |
|                                 gpointer aData);
 | |
| static gboolean drag_drop_event_cb(GtkWidget* aWidget,
 | |
|                                    GdkDragContext* aDragContext, gint aX,
 | |
|                                    gint aY, guint aTime, gpointer aData);
 | |
| static void drag_data_received_event_cb(GtkWidget* aWidget,
 | |
|                                         GdkDragContext* aDragContext, gint aX,
 | |
|                                         gint aY,
 | |
|                                         GtkSelectionData* aSelectionData,
 | |
|                                         guint aInfo, guint32 aTime,
 | |
|                                         gpointer aData);
 | |
| 
 | |
| /* initialization static functions */
 | |
| static nsresult initialize_prefs(void);
 | |
| 
 | |
| static guint32 sLastUserInputTime = GDK_CURRENT_TIME;
 | |
| 
 | |
| static SystemTimeConverter<guint32>& TimeConverter() {
 | |
|   static SystemTimeConverter<guint32> sTimeConverterSingleton;
 | |
|   return sTimeConverterSingleton;
 | |
| }
 | |
| 
 | |
| bool nsWindow::sTransparentMainWindow = false;
 | |
| 
 | |
| // forward declare from mozgtk
 | |
| extern "C" MOZ_EXPORT void mozgtk_linker_holder();
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
| class CurrentX11TimeGetter {
 | |
|  public:
 | |
|   explicit CurrentX11TimeGetter(GdkWindow* aWindow) : mWindow(aWindow) {}
 | |
| 
 | |
|   guint32 GetCurrentTime() const { return gdk_x11_get_server_time(mWindow); }
 | |
| 
 | |
|   void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
 | |
|     // Check for in-flight request
 | |
|     if (!mAsyncUpdateStart.IsNull()) {
 | |
|       return;
 | |
|     }
 | |
|     mAsyncUpdateStart = aNow;
 | |
| 
 | |
|     Display* xDisplay = GDK_WINDOW_XDISPLAY(mWindow);
 | |
|     Window xWindow = GDK_WINDOW_XID(mWindow);
 | |
|     unsigned char c = 'a';
 | |
|     Atom timeStampPropAtom = TimeStampPropAtom();
 | |
|     XChangeProperty(xDisplay, xWindow, timeStampPropAtom, timeStampPropAtom, 8,
 | |
|                     PropModeReplace, &c, 1);
 | |
|     XFlush(xDisplay);
 | |
|   }
 | |
| 
 | |
|   gboolean PropertyNotifyHandler(GtkWidget* aWidget, GdkEventProperty* aEvent) {
 | |
|     if (aEvent->atom != gdk_x11_xatom_to_atom(TimeStampPropAtom())) {
 | |
|       return FALSE;
 | |
|     }
 | |
| 
 | |
|     guint32 eventTime = aEvent->time;
 | |
|     TimeStamp lowerBound = mAsyncUpdateStart;
 | |
| 
 | |
|     TimeConverter().CompensateForBackwardsSkew(eventTime, lowerBound);
 | |
|     mAsyncUpdateStart = TimeStamp();
 | |
|     return TRUE;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   static Atom TimeStampPropAtom() {
 | |
|     return gdk_x11_get_xatom_by_name_for_display(gdk_display_get_default(),
 | |
|                                                  "GDK_TIMESTAMP_PROP");
 | |
|   }
 | |
| 
 | |
|   // This is safe because this class is stored as a member of mWindow and
 | |
|   // won't outlive it.
 | |
|   GdkWindow* mWindow;
 | |
|   TimeStamp mAsyncUpdateStart;
 | |
| };
 | |
| #endif
 | |
| 
 | |
| }  // namespace mozilla
 | |
| 
 | |
| // The window from which the focus manager asks us to dispatch key events.
 | |
| static nsWindow* gFocusWindow = nullptr;
 | |
| static bool gBlockActivateEvent = false;
 | |
| static bool gGlobalsInitialized = false;
 | |
| static bool gUseAspectRatio = true;
 | |
| static uint32_t gLastTouchID = 0;
 | |
| // See Bug 1777269 for details. We don't know if the suspected leave notify
 | |
| // event is a correct one when we get it.
 | |
| // Store it and issue it later from enter notify event if it's correct,
 | |
| // throw it away otherwise.
 | |
| static GUniquePtr<GdkEventCrossing> sStoredLeaveNotifyEvent;
 | |
| 
 | |
| #define NS_WINDOW_TITLE_MAX_LENGTH 4095
 | |
| 
 | |
| // cursor cache
 | |
| static GdkCursor* gCursorCache[eCursorCount];
 | |
| 
 | |
| // Sometimes this actually also includes the state of the modifier keys, but
 | |
| // only the button state bits are used.
 | |
| static guint gButtonState;
 | |
| 
 | |
| static inline int32_t GetBitmapStride(int32_t width) {
 | |
| #if defined(MOZ_X11)
 | |
|   return (width + 7) / 8;
 | |
| #else
 | |
|   return cairo_format_stride_for_width(CAIRO_FORMAT_A1, width);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| static inline bool TimestampIsNewerThan(guint32 a, guint32 b) {
 | |
|   // Timestamps are just the least significant bits of a monotonically
 | |
|   // increasing function, and so the use of unsigned overflow arithmetic.
 | |
|   return a - b <= G_MAXUINT32 / 2;
 | |
| }
 | |
| 
 | |
| static void UpdateLastInputEventTime(void* aGdkEvent) {
 | |
|   nsCOMPtr<nsIUserIdleServiceInternal> idleService =
 | |
|       do_GetService("@mozilla.org/widget/useridleservice;1");
 | |
|   if (idleService) {
 | |
|     idleService->ResetIdleTimeOut(0);
 | |
|   }
 | |
| 
 | |
|   guint timestamp = gdk_event_get_time(static_cast<GdkEvent*>(aGdkEvent));
 | |
|   if (timestamp == GDK_CURRENT_TIME) return;
 | |
| 
 | |
|   sLastUserInputTime = timestamp;
 | |
| }
 | |
| 
 | |
| // Don't set parent (transient for) if nothing changes.
 | |
| // gtk_window_set_transient_for() blows up wl_subsurfaces used by aWindow
 | |
| // even if aParent is the same.
 | |
| static void GtkWindowSetTransientFor(GtkWindow* aWindow, GtkWindow* aParent) {
 | |
|   GtkWindow* parent = gtk_window_get_transient_for(aWindow);
 | |
|   if (parent != aParent) {
 | |
|     gtk_window_set_transient_for(aWindow, aParent);
 | |
|   }
 | |
| }
 | |
| 
 | |
| #define gtk_window_set_transient_for(a, b)                         \
 | |
|   {                                                                \
 | |
|     MOZ_ASSERT_UNREACHABLE(                                        \
 | |
|         "gtk_window_set_transient_for() can't be used directly."); \
 | |
|   }
 | |
| 
 | |
| nsWindow::nsWindow()
 | |
|     : mDestroyMutex("nsWindow::mDestroyMutex"),
 | |
|       mIsDestroyed(false),
 | |
|       mIsShown(false),
 | |
|       mNeedsShow(false),
 | |
|       mIsMapped(false),
 | |
|       mEnabled(true),
 | |
|       mCreated(false),
 | |
|       mHandleTouchEvent(false),
 | |
|       mIsDragPopup(false),
 | |
|       mCompositedScreen(gdk_screen_is_composited(gdk_screen_get_default())),
 | |
|       mIsAccelerated(false),
 | |
|       mWindowShouldStartDragging(false),
 | |
|       mHasMappedToplevel(false),
 | |
|       mRetryPointerGrab(false),
 | |
|       mPanInProgress(false),
 | |
|       mTitlebarBackdropState(false),
 | |
|       mIsChildWindow(false),
 | |
|       mAlwaysOnTop(false),
 | |
|       mNoAutoHide(false),
 | |
|       mIsTransparent(false),
 | |
|       mHasReceivedSizeAllocate(false),
 | |
|       mWidgetCursorLocked(false),
 | |
|       mUndecorated(false),
 | |
|       mPopupTrackInHierarchy(false),
 | |
|       mPopupTrackInHierarchyConfigured(false),
 | |
|       mHiddenPopupPositioned(false),
 | |
|       mTransparencyBitmapForTitlebar(false),
 | |
|       mHasAlphaVisual(false),
 | |
|       mPopupAnchored(false),
 | |
|       mPopupContextMenu(false),
 | |
|       mPopupMatchesLayout(false),
 | |
|       mPopupChanged(false),
 | |
|       mPopupTemporaryHidden(false),
 | |
|       mPopupClosed(false),
 | |
|       mPopupUseMoveToRect(false),
 | |
|       mWaitingForMoveToRectCallback(false),
 | |
|       mMovedAfterMoveToRect(false),
 | |
|       mResizedAfterMoveToRect(false),
 | |
|       mConfiguredClearColor(false),
 | |
|       mGotNonBlankPaint(false),
 | |
|       mNeedsToRetryCapturingMouse(false) {
 | |
|   mWindowType = WindowType::Child;
 | |
|   mSizeConstraints.mMaxSize = GetSafeWindowSize(mSizeConstraints.mMaxSize);
 | |
| 
 | |
|   if (!gGlobalsInitialized) {
 | |
|     gGlobalsInitialized = true;
 | |
| 
 | |
|     // It's OK if either of these fail, but it may not be one day.
 | |
|     initialize_prefs();
 | |
| 
 | |
| #ifdef MOZ_WAYLAND
 | |
|     // Wayland provides clipboard data to application on focus-in event
 | |
|     // so we need to init our clipboard hooks before we create window
 | |
|     // and get focus.
 | |
|     if (GdkIsWaylandDisplay()) {
 | |
|       nsCOMPtr<nsIClipboard> clipboard =
 | |
|           do_GetService("@mozilla.org/widget/clipboard;1");
 | |
|       NS_ASSERTION(clipboard, "Failed to init clipboard!");
 | |
|     }
 | |
| #endif
 | |
|   }
 | |
|   // Dummy call to mozgtk to prevent the linker from removing
 | |
|   // the dependency with --as-needed.
 | |
|   // see toolkit/library/moz.build for details.
 | |
|   mozgtk_linker_holder();
 | |
| }
 | |
| 
 | |
| nsWindow::~nsWindow() {
 | |
|   LOG("nsWindow::~nsWindow()");
 | |
|   Destroy();
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void nsWindow::ReleaseGlobals() {
 | |
|   for (auto& cursor : gCursorCache) {
 | |
|     if (cursor) {
 | |
|       g_object_unref(cursor);
 | |
|       cursor = nullptr;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::DispatchActivateEvent(void) {
 | |
| #ifdef ACCESSIBILITY
 | |
|   DispatchActivateEventAccessible();
 | |
| #endif  // ACCESSIBILITY
 | |
| 
 | |
|   if (mWidgetListener) mWidgetListener->WindowActivated();
 | |
| }
 | |
| 
 | |
| void nsWindow::DispatchDeactivateEvent() {
 | |
|   if (mWidgetListener) {
 | |
|     mWidgetListener->WindowDeactivated();
 | |
|   }
 | |
| 
 | |
| #ifdef ACCESSIBILITY
 | |
|   DispatchDeactivateEventAccessible();
 | |
| #endif  // ACCESSIBILITY
 | |
| }
 | |
| 
 | |
| void nsWindow::DispatchResized() {
 | |
|   LOG("nsWindow::DispatchResized() size [%d, %d]", (int)(mBounds.width),
 | |
|       (int)(mBounds.height));
 | |
| 
 | |
|   mNeedsDispatchSize = LayoutDeviceIntSize(-1, -1);
 | |
|   if (mWidgetListener) {
 | |
|     mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
 | |
|   }
 | |
|   if (mAttachedWidgetListener) {
 | |
|     mAttachedWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::MaybeDispatchResized() {
 | |
|   if (mNeedsDispatchSize != LayoutDeviceIntSize(-1, -1) && !mIsDestroyed) {
 | |
|     mBounds.SizeTo(mNeedsDispatchSize);
 | |
|     // Check mBounds size
 | |
|     if (mCompositorSession &&
 | |
|         !wr::WindowSizeSanityCheck(mBounds.width, mBounds.height)) {
 | |
|       gfxCriticalNoteOnce << "Invalid mBounds in MaybeDispatchResized "
 | |
|                           << mBounds << " size state " << mSizeMode;
 | |
|     }
 | |
| 
 | |
|     if (IsTopLevelWindowType()) {
 | |
|       UpdateTopLevelOpaqueRegion();
 | |
|     }
 | |
| 
 | |
|     // Notify the GtkCompositorWidget of a ClientSizeChange
 | |
|     if (mCompositorWidgetDelegate) {
 | |
|       mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
 | |
|     }
 | |
| 
 | |
|     DispatchResized();
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsIWidgetListener* nsWindow::GetListener() {
 | |
|   return mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener;
 | |
| }
 | |
| 
 | |
| nsresult nsWindow::DispatchEvent(WidgetGUIEvent* aEvent,
 | |
|                                  nsEventStatus& aStatus) {
 | |
| #ifdef DEBUG
 | |
|   debug_DumpEvent(stdout, aEvent->mWidget, aEvent, "something", 0);
 | |
| #endif
 | |
|   aStatus = nsEventStatus_eIgnore;
 | |
|   nsIWidgetListener* listener = GetListener();
 | |
|   if (listener) {
 | |
|     aStatus = listener->HandleEvent(aEvent, mUseAttachedEvents);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsWindow::OnDestroy(void) {
 | |
|   if (mOnDestroyCalled) return;
 | |
| 
 | |
|   mOnDestroyCalled = true;
 | |
| 
 | |
|   // Prevent deletion.
 | |
|   nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
 | |
| 
 | |
|   // release references to children, device context, toolkit + app shell
 | |
|   nsBaseWidget::OnDestroy();
 | |
| 
 | |
|   // Remove association between this object and its parent and siblings.
 | |
|   nsBaseWidget::Destroy();
 | |
|   mParent = nullptr;
 | |
| 
 | |
|   NotifyWindowDestroyed();
 | |
| }
 | |
| 
 | |
| bool nsWindow::AreBoundsSane() {
 | |
|   // Check requested size, as mBounds might not have been updated.
 | |
|   return !mLastSizeRequest.IsEmpty();
 | |
| }
 | |
| 
 | |
| // Walk the list of child windows and call destroy on them.
 | |
| void nsWindow::DestroyChildWindows() {
 | |
|   LOG("nsWindow::DestroyChildWindows()");
 | |
|   if (!mGdkWindow) {
 | |
|     return;
 | |
|   }
 | |
|   while (GList* children = gdk_window_peek_children(mGdkWindow)) {
 | |
|     GdkWindow* child = GDK_WINDOW(children->data);
 | |
|     nsWindow* kid = get_window_for_gdk_window(child);
 | |
|     if (kid) {
 | |
|       kid->Destroy();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::Destroy() {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (mIsDestroyed || !mCreated) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LOG("nsWindow::Destroy\n");
 | |
| 
 | |
|   MutexAutoLock lock(mDestroyMutex);
 | |
|   mIsDestroyed = true;
 | |
|   mCreated = false;
 | |
| 
 | |
|   MozClearHandleID(mCompositorPauseTimeoutID, g_source_remove);
 | |
| 
 | |
|   ClearTransparencyBitmap();
 | |
| 
 | |
| #ifdef MOZ_WAYLAND
 | |
|   // Shut down our local vsync source
 | |
|   if (mWaylandVsyncSource) {
 | |
|     mWaylandVsyncSource->Shutdown();
 | |
|     mWaylandVsyncSource = nullptr;
 | |
|   }
 | |
|   mWaylandVsyncDispatcher = nullptr;
 | |
| #endif
 | |
| 
 | |
|   /** Need to clean our LayerManager up while still alive */
 | |
|   DestroyLayerManager();
 | |
| 
 | |
|   // Ensure any resources assigned to the window get cleaned up first
 | |
|   // to avoid double-freeing.
 | |
|   mSurfaceProvider.CleanupResources();
 | |
| 
 | |
|   g_signal_handlers_disconnect_by_data(gtk_settings_get_default(), this);
 | |
| 
 | |
|   nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
 | |
|   if (rollupListener) {
 | |
|     nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
 | |
|     if (static_cast<nsIWidget*>(this) == rollupWidget) {
 | |
|       rollupListener->Rollup({});
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // dragService will be null after shutdown of the service manager.
 | |
|   RefPtr<nsDragService> dragService = nsDragService::GetInstance();
 | |
|   if (dragService && this == dragService->GetMostRecentDestWindow()) {
 | |
|     dragService->ScheduleLeaveEvent();
 | |
|   }
 | |
| 
 | |
|   NativeShow(false);
 | |
| 
 | |
|   if (mIMContext) {
 | |
|     mIMContext->OnDestroyWindow(this);
 | |
|   }
 | |
| 
 | |
|   // make sure that we remove ourself as the focus window
 | |
|   if (gFocusWindow == this) {
 | |
|     LOG("automatically losing focus...\n");
 | |
|     gFocusWindow = nullptr;
 | |
|   }
 | |
| 
 | |
|   if (sStoredLeaveNotifyEvent) {
 | |
|     nsWindow* window =
 | |
|         get_window_for_gdk_window(sStoredLeaveNotifyEvent->window);
 | |
|     if (window == this) {
 | |
|       sStoredLeaveNotifyEvent = nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // We need to detach accessible object here because mContainer is a custom
 | |
|   // widget and doesn't call gtk_widget_real_destroy() from destroy handler
 | |
|   // as regular widgets.
 | |
|   if (AtkObject* ac = gtk_widget_get_accessible(GTK_WIDGET(mContainer))) {
 | |
|     gtk_accessible_set_widget(GTK_ACCESSIBLE(ac), nullptr);
 | |
|   }
 | |
| 
 | |
|   gtk_widget_destroy(mShell);
 | |
|   mShell = nullptr;
 | |
|   mContainer = nullptr;
 | |
| 
 | |
|   MOZ_ASSERT(!mGdkWindow,
 | |
|              "mGdkWindow should be NULL when mContainer is destroyed");
 | |
| 
 | |
| #ifdef ACCESSIBILITY
 | |
|   if (mRootAccessible) {
 | |
|     mRootAccessible = nullptr;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   // Save until last because OnDestroy() may cause us to be deleted.
 | |
|   OnDestroy();
 | |
| }
 | |
| 
 | |
| nsIWidget* nsWindow::GetParent() { return mParent; }
 | |
| 
 | |
| float nsWindow::GetDPI() {
 | |
|   float dpi = 96.0f;
 | |
|   nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
 | |
|   if (screen) {
 | |
|     screen->GetDpi(&dpi);
 | |
|   }
 | |
|   return dpi;
 | |
| }
 | |
| 
 | |
| double nsWindow::GetDefaultScaleInternal() { return FractionalScaleFactor(); }
 | |
| 
 | |
| DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScale() {
 | |
| #ifdef MOZ_WAYLAND
 | |
|   if (GdkIsWaylandDisplay()) {
 | |
|     return DesktopToLayoutDeviceScale(FractionalScaleFactor());
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   // In Gtk/X11, we manage windows using device pixels.
 | |
|   return DesktopToLayoutDeviceScale(1.0);
 | |
| }
 | |
| 
 | |
| DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScaleByScreen() {
 | |
| #ifdef MOZ_WAYLAND
 | |
|   // In Wayland there's no way to get absolute position of the window and use it
 | |
|   // to determine the screen factor of the monitor on which the window is
 | |
|   // placed. The window is notified of the current scale factor but not at this
 | |
|   // point, so the GdkScaleFactor can return wrong value which can lead to wrong
 | |
|   // popup placement. We need to use parent's window scale factor for the new
 | |
|   // one.
 | |
|   if (GdkIsWaylandDisplay()) {
 | |
|     nsView* view = nsView::GetViewFor(this);
 | |
|     if (view) {
 | |
|       nsView* parentView = view->GetParent();
 | |
|       if (parentView) {
 | |
|         nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr);
 | |
|         if (parentWidget) {
 | |
|           return DesktopToLayoutDeviceScale(
 | |
|               parentWidget->RoundsWidgetCoordinatesTo());
 | |
|         }
 | |
|         NS_WARNING("Widget has no parent");
 | |
|       }
 | |
|     } else {
 | |
|       NS_WARNING("Cannot find widget view");
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
|   return nsBaseWidget::GetDesktopToDeviceScale();
 | |
| }
 | |
| 
 | |
| // Reparent a child window to a new parent.
 | |
| void nsWindow::SetParent(nsIWidget* aNewParent) {
 | |
|   LOG("nsWindow::SetParent() new parent %p", aNewParent);
 | |
|   if (!mIsChildWindow) {
 | |
|     NS_WARNING("Used by child widgets only");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
 | |
|   if (mParent) {
 | |
|     mParent->RemoveChild(this);
 | |
|   }
 | |
|   mParent = aNewParent;
 | |
| 
 | |
|   // We're already deleted, quit.
 | |
|   if (!mGdkWindow || mIsDestroyed || !aNewParent) {
 | |
|     return;
 | |
|   }
 | |
|   aNewParent->AddChild(this);
 | |
| 
 | |
|   auto* newParent = static_cast<nsWindow*>(aNewParent);
 | |
| 
 | |
|   // New parent is deleted, quit.
 | |
|   if (newParent->mIsDestroyed) {
 | |
|     Destroy();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   GdkWindow* window = GetToplevelGdkWindow();
 | |
|   GdkWindow* parentWindow = newParent->GetToplevelGdkWindow();
 | |
|   LOG("  child GdkWindow %p set parent GdkWindow %p", window, parentWindow);
 | |
|   gdk_window_reparent(window, parentWindow, 0, 0);
 | |
|   SetHasMappedToplevel(newParent && newParent->mHasMappedToplevel);
 | |
| }
 | |
| 
 | |
| bool nsWindow::WidgetTypeSupportsAcceleration() {
 | |
|   if (mWindowType == WindowType::Invisible) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (IsSmallPopup()) {
 | |
|     return false;
 | |
|   }
 | |
|   // Workaround for Bug 1479135
 | |
|   // We draw transparent popups on non-compositing screens by SW as we don't
 | |
|   // implement X shape masks in WebRender.
 | |
|   if (mWindowType == WindowType::Popup) {
 | |
|     return HasRemoteContent() && mCompositedScreen;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) {
 | |
|   MOZ_ASSERT(aNewParent, "null widget");
 | |
|   MOZ_ASSERT(!mIsDestroyed, "");
 | |
|   MOZ_ASSERT(!static_cast<nsWindow*>(aNewParent)->mIsDestroyed, "");
 | |
|   MOZ_ASSERT(
 | |
|       !mParent,
 | |
|       "nsWindow::ReparentNativeWidget() works on toplevel windows only.");
 | |
| 
 | |
|   auto* newParent = static_cast<nsWindow*>(aNewParent);
 | |
|   GtkWindow* newParentWidget = GTK_WINDOW(newParent->GetGtkWidget());
 | |
| 
 | |
|   LOG("nsWindow::ReparentNativeWidget new parent %p\n", newParent);
 | |
|   GtkWindowSetTransientFor(GTK_WINDOW(mShell), newParentWidget);
 | |
| }
 | |
| 
 | |
| void nsWindow::SetModal(bool aModal) {
 | |
|   LOG("nsWindow::SetModal %d\n", aModal);
 | |
|   if (mIsDestroyed) return;
 | |
|   gtk_window_set_modal(GTK_WINDOW(mShell), aModal ? TRUE : FALSE);
 | |
| }
 | |
| 
 | |
| // nsIWidget method, which means IsShown.
 | |
| bool nsWindow::IsVisible() const { return mIsShown; }
 | |
| 
 | |
| bool nsWindow::IsMapped() const {
 | |
|   // TODO: Enable for X11 when Mozilla testsuite is moved to new
 | |
|   // testing environment from Ubuntu 18.04 which is broken.
 | |
|   return GdkIsWaylandDisplay() ? mIsMapped : true;
 | |
| }
 | |
| 
 | |
| void nsWindow::RegisterTouchWindow() {
 | |
|   mHandleTouchEvent = true;
 | |
|   mTouches.Clear();
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntPoint nsWindow::GetScreenEdgeSlop() {
 | |
|   if (DrawsToCSDTitlebar()) {
 | |
|     return GetClientOffset();
 | |
|   }
 | |
|   return {};
 | |
| }
 | |
| 
 | |
| void nsWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
 | |
|   if (!mShell || GdkIsWaylandDisplay()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   double dpiScale = GetDefaultScale().scale;
 | |
| 
 | |
|   // we need to use the window size in logical screen pixels
 | |
|   int32_t logWidth = std::max(NSToIntRound(mBounds.width / dpiScale), 1);
 | |
|   int32_t logHeight = std::max(NSToIntRound(mBounds.height / dpiScale), 1);
 | |
| 
 | |
|   /* get our playing field. use the current screen, or failing that
 | |
|     for any reason, use device caps for the default screen. */
 | |
|   nsCOMPtr<nsIScreenManager> screenmgr =
 | |
|       do_GetService("@mozilla.org/gfx/screenmanager;1");
 | |
|   if (!screenmgr) {
 | |
|     return;
 | |
|   }
 | |
|   nsCOMPtr<nsIScreen> screen;
 | |
|   screenmgr->ScreenForRect(aPoint.x, aPoint.y, logWidth, logHeight,
 | |
|                            getter_AddRefs(screen));
 | |
|   // We don't have any screen so leave the coordinates as is
 | |
|   if (!screen) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // For normalized windows, use the desktop work area.
 | |
|   // For full screen windows, use the desktop.
 | |
|   DesktopIntRect screenRect = mSizeMode == nsSizeMode_Fullscreen
 | |
|                                   ? screen->GetRectDisplayPix()
 | |
|                                   : screen->GetAvailRectDisplayPix();
 | |
| 
 | |
|   // Expand for the decoration size if needed.
 | |
|   auto slop =
 | |
|       DesktopIntPoint::Round(GetScreenEdgeSlop() / GetDesktopToDeviceScale());
 | |
|   screenRect.Inflate(slop.x, slop.y);
 | |
| 
 | |
|   if (aPoint.x < screenRect.x) {
 | |
|     aPoint.x = screenRect.x;
 | |
|   } else if (aPoint.x >= screenRect.XMost() - logWidth) {
 | |
|     aPoint.x = screenRect.XMost() - logWidth;
 | |
|   }
 | |
| 
 | |
|   if (aPoint.y < screenRect.y) {
 | |
|     aPoint.y = screenRect.y;
 | |
|   } else if (aPoint.y >= screenRect.YMost() - logHeight) {
 | |
|     aPoint.y = screenRect.YMost() - logHeight;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
 | |
|   mSizeConstraints.mMinSize = GetSafeWindowSize(aConstraints.mMinSize);
 | |
|   mSizeConstraints.mMaxSize = GetSafeWindowSize(aConstraints.mMaxSize);
 | |
| 
 | |
|   ApplySizeConstraints();
 | |
| }
 | |
| 
 | |
| bool nsWindow::DrawsToCSDTitlebar() const {
 | |
|   return mSizeMode == nsSizeMode_Normal &&
 | |
|          mGtkWindowDecoration == GTK_DECORATION_CLIENT && mDrawInTitlebar;
 | |
| }
 | |
| 
 | |
| void nsWindow::AddCSDDecorationSize(int* aWidth, int* aHeight) {
 | |
|   if (mSizeMode != nsSizeMode_Normal || mUndecorated ||
 | |
|       mGtkWindowDecoration != GTK_DECORATION_CLIENT || !GdkIsWaylandDisplay() ||
 | |
|       !IsGnomeDesktopEnvironment()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   GtkBorder decorationSize = GetCSDDecorationSize(IsPopup());
 | |
|   *aWidth += decorationSize.left + decorationSize.right;
 | |
|   *aHeight += decorationSize.top + decorationSize.bottom;
 | |
| }
 | |
| 
 | |
| #ifdef MOZ_WAYLAND
 | |
| bool nsWindow::GetCSDDecorationOffset(int* aDx, int* aDy) {
 | |
|   if (!DrawsToCSDTitlebar()) {
 | |
|     return false;
 | |
|   }
 | |
|   GtkBorder decorationSize = GetCSDDecorationSize(IsPopup());
 | |
|   *aDx = decorationSize.left;
 | |
|   *aDy = decorationSize.top;
 | |
|   return true;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| void nsWindow::ApplySizeConstraints() {
 | |
|   if (mShell) {
 | |
|     GdkGeometry geometry;
 | |
|     geometry.min_width =
 | |
|         DevicePixelsToGdkCoordRoundUp(mSizeConstraints.mMinSize.width);
 | |
|     geometry.min_height =
 | |
|         DevicePixelsToGdkCoordRoundUp(mSizeConstraints.mMinSize.height);
 | |
|     geometry.max_width =
 | |
|         DevicePixelsToGdkCoordRoundDown(mSizeConstraints.mMaxSize.width);
 | |
|     geometry.max_height =
 | |
|         DevicePixelsToGdkCoordRoundDown(mSizeConstraints.mMaxSize.height);
 | |
| 
 | |
|     uint32_t hints = 0;
 | |
|     if (mSizeConstraints.mMinSize != LayoutDeviceIntSize()) {
 | |
|       if (GdkIsWaylandDisplay()) {
 | |
|         gtk_widget_set_size_request(GTK_WIDGET(mContainer), geometry.min_width,
 | |
|                                     geometry.min_height);
 | |
|       }
 | |
|       AddCSDDecorationSize(&geometry.min_width, &geometry.min_height);
 | |
|       hints |= GDK_HINT_MIN_SIZE;
 | |
|     }
 | |
|     if (mSizeConstraints.mMaxSize !=
 | |
|         LayoutDeviceIntSize(NS_MAXSIZE, NS_MAXSIZE)) {
 | |
|       AddCSDDecorationSize(&geometry.max_width, &geometry.max_height);
 | |
|       hints |= GDK_HINT_MAX_SIZE;
 | |
|     }
 | |
| 
 | |
|     if (mAspectRatio != 0.0f && !mAspectResizer) {
 | |
|       geometry.min_aspect = mAspectRatio;
 | |
|       geometry.max_aspect = mAspectRatio;
 | |
|       hints |= GDK_HINT_ASPECT;
 | |
|     }
 | |
| 
 | |
|     gtk_window_set_geometry_hints(GTK_WINDOW(mShell), nullptr, &geometry,
 | |
|                                   GdkWindowHints(hints));
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::Show(bool aState) {
 | |
|   if (aState == mIsShown) return;
 | |
| 
 | |
|   mIsShown = aState;
 | |
| 
 | |
| #ifdef MOZ_LOGGING
 | |
|   LOG("nsWindow::Show state %d frame %s\n", aState, GetFrameTag().get());
 | |
|   if (!aState && mSourceDragContext && GdkIsWaylandDisplay()) {
 | |
|     LOG("  closing Drag&Drop source window, D&D will be canceled!");
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   // Ok, someone called show on a window that isn't sized to a sane
 | |
|   // value.  Mark this window as needing to have Show() called on it
 | |
|   // and return.
 | |
|   if ((aState && !AreBoundsSane()) || !mCreated) {
 | |
|     LOG("\tbounds are insane or window hasn't been created yet\n");
 | |
|     mNeedsShow = true;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If someone is hiding this widget, clear any needing show flag.
 | |
|   if (!aState) mNeedsShow = false;
 | |
| 
 | |
| #ifdef ACCESSIBILITY
 | |
|   if (aState && a11y::ShouldA11yBeEnabled()) CreateRootAccessible();
 | |
| #endif
 | |
| 
 | |
|   NativeShow(aState);
 | |
|   RefreshWindowClass();
 | |
| }
 | |
| 
 | |
| void nsWindow::ResizeInt(const Maybe<LayoutDeviceIntPoint>& aMove,
 | |
|                          LayoutDeviceIntSize aSize) {
 | |
|   LOG("nsWindow::ResizeInt w:%d h:%d\n", aSize.width, aSize.height);
 | |
|   const bool moved = aMove && *aMove != mBounds.TopLeft();
 | |
|   if (moved) {
 | |
|     mBounds.MoveTo(*aMove);
 | |
|     LOG("  with move to left:%d top:%d", aMove->x.value, aMove->y.value);
 | |
|   }
 | |
| 
 | |
|   ConstrainSize(&aSize.width, &aSize.height);
 | |
|   LOG("  ConstrainSize: w:%d h;%d\n", aSize.width, aSize.height);
 | |
| 
 | |
|   const bool resized = aSize != mLastSizeRequest || mBounds.Size() != aSize;
 | |
| #if MOZ_LOGGING
 | |
|   LOG("  resized %d aSize [%d, %d] mLastSizeRequest [%d, %d] mBounds [%d, %d]",
 | |
|       resized, aSize.width, aSize.height, mLastSizeRequest.width,
 | |
|       mLastSizeRequest.height, mBounds.width, mBounds.height);
 | |
| #endif
 | |
| 
 | |
|   // For top-level windows, aSize should possibly be
 | |
|   // interpreted as frame bounds, but NativeMoveResize treats these as window
 | |
|   // bounds (Bug 581866).
 | |
|   mLastSizeRequest = aSize;
 | |
|   // Check size
 | |
|   if (mCompositorSession &&
 | |
|       !wr::WindowSizeSanityCheck(aSize.width, aSize.height)) {
 | |
|     gfxCriticalNoteOnce << "Invalid aSize in ResizeInt " << aSize
 | |
|                         << " size state " << mSizeMode;
 | |
|   }
 | |
| 
 | |
|   // Recalculate aspect ratio when resized from DOM
 | |
|   if (mAspectRatio != 0.0) {
 | |
|     LockAspectRatio(true);
 | |
|   }
 | |
| 
 | |
|   if (!mCreated) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!moved && !resized) {
 | |
|     LOG("  not moved or resized, quit");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   NativeMoveResize(moved, resized);
 | |
| 
 | |
|   // We optimistically assume size changes immediately in two cases:
 | |
|   // 1. Override-redirect window: Size is controlled by only us.
 | |
|   // 2. Managed window that has not not yet received a size-allocate event:
 | |
|   //    Resize() Callers expect initial sizes to be applied synchronously.
 | |
|   //    If the size request is not honored, then we'll correct in
 | |
|   //    OnSizeAllocate().
 | |
|   //
 | |
|   // When a managed window has already received a size-allocate, we cannot
 | |
|   // assume we'll always get a notification if our request does not get
 | |
|   // honored: "If the configure request has not changed, we don't ever resend
 | |
|   // it, because it could mean fighting the user or window manager."
 | |
|   // https://gitlab.gnome.org/GNOME/gtk/-/blob/3.24.31/gtk/gtkwindow.c#L9782
 | |
|   // So we don't update mBounds until OnSizeAllocate() when we know the
 | |
|   // request is granted.
 | |
|   bool isOrWillBeVisible = mHasReceivedSizeAllocate || mNeedsShow || mIsShown;
 | |
|   if (!isOrWillBeVisible ||
 | |
|       gtk_window_get_window_type(GTK_WINDOW(mShell)) == GTK_WINDOW_POPUP) {
 | |
|     mBounds.SizeTo(aSize);
 | |
|     if (mCompositorWidgetDelegate) {
 | |
|       mCompositorWidgetDelegate->NotifyClientSizeChanged(aSize);
 | |
|     }
 | |
|     DispatchResized();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
 | |
|   LOG("nsWindow::Resize %f %f\n", aWidth, aHeight);
 | |
| 
 | |
|   double scale =
 | |
|       BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
 | |
|   auto size = LayoutDeviceIntSize::Round(scale * aWidth, scale * aHeight);
 | |
| 
 | |
|   ResizeInt(Nothing(), size);
 | |
| }
 | |
| 
 | |
| void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
 | |
|                       bool aRepaint) {
 | |
|   LOG("nsWindow::Resize [%f,%f] -> [%f x %f] repaint %d\n", aX, aY, aWidth,
 | |
|       aHeight, aRepaint);
 | |
| 
 | |
|   double scale =
 | |
|       BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
 | |
|   auto size = LayoutDeviceIntSize::Round(scale * aWidth, scale * aHeight);
 | |
|   auto topLeft = LayoutDeviceIntPoint::Round(scale * aX, scale * aY);
 | |
| 
 | |
|   ResizeInt(Some(topLeft), size);
 | |
| }
 | |
| 
 | |
| void nsWindow::Enable(bool aState) { mEnabled = aState; }
 | |
| 
 | |
| bool nsWindow::IsEnabled() const { return mEnabled; }
 | |
| 
 | |
| void nsWindow::Move(double aX, double aY) {
 | |
|   double scale =
 | |
|       BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
 | |
|   int32_t x = NSToIntRound(aX * scale);
 | |
|   int32_t y = NSToIntRound(aY * scale);
 | |
| 
 | |
|   LOG("nsWindow::Move to %d x %d\n", x, y);
 | |
| 
 | |
|   if (mSizeMode != nsSizeMode_Normal && IsTopLevelWindowType()) {
 | |
|     LOG("  size state is not normal, bailing");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Since a popup window's x/y coordinates are in relation to to
 | |
|   // the parent, the parent might have moved so we always move a
 | |
|   // popup window.
 | |
|   LOG("  bounds %d x %d\n", mBounds.x, mBounds.y);
 | |
|   if (x == mBounds.x && y == mBounds.y && mWindowType != WindowType::Popup) {
 | |
|     LOG("  position is the same, return\n");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // XXX Should we do some AreBoundsSane check here?
 | |
| 
 | |
|   mBounds.x = x;
 | |
|   mBounds.y = y;
 | |
| 
 | |
|   if (!mCreated) {
 | |
|     LOG("  is not created, return.\n");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   NativeMoveResize(/* move */ true, /* resize */ false);
 | |
| }
 | |
| 
 | |
| bool nsWindow::IsPopup() const { return mWindowType == WindowType::Popup; }
 | |
| 
 | |
| bool nsWindow::IsWaylandPopup() const {
 | |
|   return GdkIsWaylandDisplay() && IsPopup();
 | |
| }
 | |
| 
 | |
| static nsMenuPopupFrame* GetMenuPopupFrame(nsIFrame* aFrame) {
 | |
|   return do_QueryFrame(aFrame);
 | |
| }
 | |
| 
 | |
| void nsWindow::AppendPopupToHierarchyList(nsWindow* aToplevelWindow) {
 | |
|   mWaylandToplevel = aToplevelWindow;
 | |
| 
 | |
|   nsWindow* popup = aToplevelWindow;
 | |
|   while (popup && popup->mWaylandPopupNext) {
 | |
|     popup = popup->mWaylandPopupNext;
 | |
|   }
 | |
|   popup->mWaylandPopupNext = this;
 | |
| 
 | |
|   mWaylandPopupPrev = popup;
 | |
|   mWaylandPopupNext = nullptr;
 | |
|   mPopupChanged = true;
 | |
|   mPopupClosed = false;
 | |
| }
 | |
| 
 | |
| void nsWindow::RemovePopupFromHierarchyList() {
 | |
|   // We're already removed from the popup hierarchy
 | |
|   if (!IsInPopupHierarchy()) {
 | |
|     return;
 | |
|   }
 | |
|   mWaylandPopupPrev->mWaylandPopupNext = mWaylandPopupNext;
 | |
|   if (mWaylandPopupNext) {
 | |
|     mWaylandPopupNext->mWaylandPopupPrev = mWaylandPopupPrev;
 | |
|     mWaylandPopupNext->mPopupChanged = true;
 | |
|   }
 | |
|   mWaylandPopupNext = mWaylandPopupPrev = nullptr;
 | |
| }
 | |
| 
 | |
| // Gtk refuses to map popup window with x < 0 && y < 0 relative coordinates
 | |
| // see https://gitlab.gnome.org/GNOME/gtk/-/issues/4071
 | |
| // as a workaround just fool around and place the popup temporary to 0,0.
 | |
| bool nsWindow::WaylandPopupRemoveNegativePosition(int* aX, int* aY) {
 | |
|   // https://gitlab.gnome.org/GNOME/gtk/-/issues/4071 applies to temporary
 | |
|   // windows only
 | |
|   GdkWindow* window = GetToplevelGdkWindow();
 | |
|   if (!window || gdk_window_get_window_type(window) != GDK_WINDOW_TEMP) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   LOG("nsWindow::WaylandPopupRemoveNegativePosition()");
 | |
| 
 | |
|   int x, y;
 | |
|   gtk_window_get_position(GTK_WINDOW(mShell), &x, &y);
 | |
|   bool moveBack = (x < 0 && y < 0);
 | |
|   if (moveBack) {
 | |
|     gtk_window_move(GTK_WINDOW(mShell), 0, 0);
 | |
|     if (aX) {
 | |
|       *aX = x;
 | |
|     }
 | |
|     if (aY) {
 | |
|       *aY = y;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   gdk_window_get_geometry(window, &x, &y, nullptr, nullptr);
 | |
|   if (x < 0 && y < 0) {
 | |
|     gdk_window_move(window, 0, 0);
 | |
|   }
 | |
| 
 | |
|   return moveBack;
 | |
| }
 | |
| 
 | |
| void nsWindow::ShowWaylandPopupWindow() {
 | |
|   LOG("nsWindow::ShowWaylandPopupWindow. Expected to see visible.");
 | |
|   MOZ_ASSERT(IsWaylandPopup());
 | |
| 
 | |
|   if (!mPopupTrackInHierarchy) {
 | |
|     LOG("  popup is not tracked in popup hierarchy, show it now");
 | |
|     gtk_widget_show(mShell);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Popup position was checked before gdk_window_move_to_rect() callback
 | |
|   // so just show it.
 | |
|   if (mPopupUseMoveToRect && mWaitingForMoveToRectCallback) {
 | |
|     LOG("  active move-to-rect callback, show it as is");
 | |
|     gtk_widget_show(mShell);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (gtk_widget_is_visible(mShell)) {
 | |
|     LOG("  is already visible, quit");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   int x, y;
 | |
|   bool moved = WaylandPopupRemoveNegativePosition(&x, &y);
 | |
|   gtk_widget_show(mShell);
 | |
|   if (moved) {
 | |
|     LOG("  move back to (%d, %d) and show", x, y);
 | |
|     gtk_window_move(GTK_WINDOW(mShell), x, y);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::WaylandPopupMarkAsClosed() {
 | |
|   LOG("nsWindow::WaylandPopupMarkAsClosed: [%p]\n", this);
 | |
|   mPopupClosed = true;
 | |
|   // If we have any child popup window notify it about
 | |
|   // parent switch.
 | |
|   if (mWaylandPopupNext) {
 | |
|     mWaylandPopupNext->mPopupChanged = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsWindow* nsWindow::WaylandPopupFindLast(nsWindow* aPopup) {
 | |
|   while (aPopup && aPopup->mWaylandPopupNext) {
 | |
|     aPopup = aPopup->mWaylandPopupNext;
 | |
|   }
 | |
|   return aPopup;
 | |
| }
 | |
| 
 | |
| // Hide and potentially removes popup from popup hierarchy.
 | |
| void nsWindow::HideWaylandPopupWindow(bool aTemporaryHide,
 | |
|                                       bool aRemoveFromPopupList) {
 | |
|   LOG("nsWindow::HideWaylandPopupWindow: remove from list %d\n",
 | |
|       aRemoveFromPopupList);
 | |
|   if (aRemoveFromPopupList) {
 | |
|     RemovePopupFromHierarchyList();
 | |
|   }
 | |
| 
 | |
|   if (!mPopupClosed) {
 | |
|     mPopupClosed = !aTemporaryHide;
 | |
|   }
 | |
| 
 | |
|   bool visible = gtk_widget_is_visible(mShell);
 | |
|   LOG("  gtk_widget_is_visible() = %d\n", visible);
 | |
| 
 | |
|   // Restore only popups which are really visible
 | |
|   mPopupTemporaryHidden = aTemporaryHide && visible;
 | |
| 
 | |
|   // Hide only visible popups or popups closed pernamently.
 | |
|   if (visible) {
 | |
|     gtk_widget_hide(mShell);
 | |
| 
 | |
|     // If there's pending Move-To-Rect callback and we hide the popup
 | |
|     // the callback won't be called any more.
 | |
|     mWaitingForMoveToRectCallback = false;
 | |
|   }
 | |
| 
 | |
|   if (mPopupClosed) {
 | |
|     LOG("  Clearing mMoveToRectPopupSize\n");
 | |
|     mMoveToRectPopupSize = {};
 | |
| #ifdef MOZ_WAYLAND
 | |
|     if (moz_container_wayland_is_waiting_to_show(mContainer)) {
 | |
|       // We need to clear rendering queue, see Bug 1782948.
 | |
|       LOG("  popup failed to show by Wayland compositor, clear rendering "
 | |
|           "queue.");
 | |
|       moz_container_wayland_clear_waiting_to_show_flag(mContainer);
 | |
|       ClearRenderingQueue();
 | |
|     }
 | |
| #endif
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::HideWaylandToplevelWindow() {
 | |
|   LOG("nsWindow::HideWaylandToplevelWindow: [%p]\n", this);
 | |
|   if (mWaylandPopupNext) {
 | |
|     nsWindow* popup = WaylandPopupFindLast(mWaylandPopupNext);
 | |
|     while (popup->mWaylandToplevel != nullptr) {
 | |
|       nsWindow* prev = popup->mWaylandPopupPrev;
 | |
|       popup->HideWaylandPopupWindow(/* aTemporaryHide */ false,
 | |
|                                     /* aRemoveFromPopupList */ true);
 | |
|       popup = prev;
 | |
|     }
 | |
|   }
 | |
|   WaylandStopVsync();
 | |
|   gtk_widget_hide(mShell);
 | |
| }
 | |
| 
 | |
| void nsWindow::ShowWaylandToplevelWindow() {
 | |
|   MOZ_ASSERT(!IsWaylandPopup());
 | |
|   LOG("nsWindow::ShowWaylandToplevelWindow");
 | |
|   gtk_widget_show(mShell);
 | |
| }
 | |
| 
 | |
| void nsWindow::WaylandPopupRemoveClosedPopups() {
 | |
|   LOG("nsWindow::WaylandPopupRemoveClosedPopups()");
 | |
|   nsWindow* popup = this;
 | |
|   while (popup) {
 | |
|     nsWindow* next = popup->mWaylandPopupNext;
 | |
|     if (popup->mPopupClosed) {
 | |
|       popup->HideWaylandPopupWindow(/* aTemporaryHide */ false,
 | |
|                                     /* aRemoveFromPopupList */ true);
 | |
|     }
 | |
|     popup = next;
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Hide all tooltips except the latest one.
 | |
| void nsWindow::WaylandPopupHideTooltips() {
 | |
|   LOG("nsWindow::WaylandPopupHideTooltips");
 | |
|   MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
 | |
| 
 | |
|   nsWindow* popup = mWaylandPopupNext;
 | |
|   while (popup && popup->mWaylandPopupNext) {
 | |
|     if (popup->mPopupType == PopupType::Tooltip) {
 | |
|       LOG("  hidding tooltip [%p]", popup);
 | |
|       popup->WaylandPopupMarkAsClosed();
 | |
|     }
 | |
|     popup = popup->mWaylandPopupNext;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::WaylandPopupCloseOrphanedPopups() {
 | |
| #ifdef MOZ_WAYLAND
 | |
|   LOG("nsWindow::WaylandPopupCloseOrphanedPopups");
 | |
|   MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
 | |
| 
 | |
|   nsWindow* popup = mWaylandPopupNext;
 | |
|   bool dangling = false;
 | |
|   while (popup) {
 | |
|     if (!dangling &&
 | |
|         moz_container_wayland_is_waiting_to_show(popup->GetMozContainer())) {
 | |
|       LOG("  popup [%p] is waiting to show, close all child popups", popup);
 | |
|       dangling = true;
 | |
|     } else if (dangling) {
 | |
|       popup->WaylandPopupMarkAsClosed();
 | |
|     }
 | |
|     popup = popup->mWaylandPopupNext;
 | |
|   }
 | |
| #endif
 | |
| }
 | |
| 
 | |
| // We can't show popups with remote content or overflow popups
 | |
| // on top of regular ones.
 | |
| // If there's any remote popup opened, close all parent popups of it.
 | |
| void nsWindow::CloseAllPopupsBeforeRemotePopup() {
 | |
|   LOG("nsWindow::CloseAllPopupsBeforeRemotePopup");
 | |
|   MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
 | |
| 
 | |
|   // Don't waste time when there's only one popup opened.
 | |
|   if (!mWaylandPopupNext || mWaylandPopupNext->mWaylandPopupNext == nullptr) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Find the first opened remote content popup
 | |
|   nsWindow* remotePopup = mWaylandPopupNext;
 | |
|   while (remotePopup) {
 | |
|     if (remotePopup->HasRemoteContent() ||
 | |
|         remotePopup->IsWidgetOverflowWindow()) {
 | |
|       LOG("  remote popup [%p]", remotePopup);
 | |
|       break;
 | |
|     }
 | |
|     remotePopup = remotePopup->mWaylandPopupNext;
 | |
|   }
 | |
| 
 | |
|   if (!remotePopup) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // ...hide opened popups before the remote one.
 | |
|   nsWindow* popup = mWaylandPopupNext;
 | |
|   while (popup && popup != remotePopup) {
 | |
|     LOG("  hidding popup [%p]", popup);
 | |
|     popup->WaylandPopupMarkAsClosed();
 | |
|     popup = popup->mWaylandPopupNext;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void GetLayoutPopupWidgetChain(
 | |
|     nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
 | |
|   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
 | |
|   pm->GetSubmenuWidgetChain(aLayoutWidgetHierarchy);
 | |
|   aLayoutWidgetHierarchy->Reverse();
 | |
| }
 | |
| 
 | |
| // Compare 'this' popup position in Wayland widget hierarchy
 | |
| // (mWaylandPopupPrev/mWaylandPopupNext) with
 | |
| // 'this' popup position in layout hierarchy.
 | |
| //
 | |
| // When aMustMatchParent is true we also request
 | |
| // 'this' parents match, i.e. 'this' has the same parent in
 | |
| // both layout and widget hierarchy.
 | |
| bool nsWindow::IsPopupInLayoutPopupChain(
 | |
|     nsTArray<nsIWidget*>* aLayoutWidgetHierarchy, bool aMustMatchParent) {
 | |
|   int len = (int)aLayoutWidgetHierarchy->Length();
 | |
|   for (int i = 0; i < len; i++) {
 | |
|     if (this == (*aLayoutWidgetHierarchy)[i]) {
 | |
|       if (!aMustMatchParent) {
 | |
|         return true;
 | |
|       }
 | |
| 
 | |
|       // Find correct parent popup for 'this' according to widget
 | |
|       // hierarchy. That means we need to skip closed popups.
 | |
|       nsWindow* parentPopup = nullptr;
 | |
|       if (mWaylandPopupPrev != mWaylandToplevel) {
 | |
|         parentPopup = mWaylandPopupPrev;
 | |
|         while (parentPopup != mWaylandToplevel && parentPopup->mPopupClosed) {
 | |
|           parentPopup = parentPopup->mWaylandPopupPrev;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (i == 0) {
 | |
|         // We found 'this' popups as a first popup in layout hierarchy.
 | |
|         // It matches layout hierarchy if it's first widget also in
 | |
|         // wayland widget hierarchy (i.e. parent is null).
 | |
|         return parentPopup == nullptr;
 | |
|       }
 | |
| 
 | |
|       return parentPopup == (*aLayoutWidgetHierarchy)[i - 1];
 | |
|     }
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| // Hide popups which are not in popup chain.
 | |
| void nsWindow::WaylandPopupHierarchyHideByLayout(
 | |
|     nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
 | |
|   LOG("nsWindow::WaylandPopupHierarchyHideByLayout");
 | |
|   MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
 | |
| 
 | |
|   // Hide all popups which are not in layout popup chain
 | |
|   nsWindow* popup = mWaylandPopupNext;
 | |
|   while (popup) {
 | |
|     // Don't check closed popups and drag source popups and tooltips.
 | |
|     if (!popup->mPopupClosed && popup->mPopupType != PopupType::Tooltip &&
 | |
|         !popup->mSourceDragContext) {
 | |
|       if (!popup->IsPopupInLayoutPopupChain(aLayoutWidgetHierarchy,
 | |
|                                             /* aMustMatchParent */ false)) {
 | |
|         LOG("  hidding popup [%p]", popup);
 | |
|         popup->WaylandPopupMarkAsClosed();
 | |
|       }
 | |
|     }
 | |
|     popup = popup->mWaylandPopupNext;
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Mark popups outside of layout hierarchy
 | |
| void nsWindow::WaylandPopupHierarchyValidateByLayout(
 | |
|     nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
 | |
|   LOG("nsWindow::WaylandPopupHierarchyValidateByLayout");
 | |
|   nsWindow* popup = mWaylandPopupNext;
 | |
|   while (popup) {
 | |
|     if (popup->mPopupType == PopupType::Tooltip) {
 | |
|       popup->mPopupMatchesLayout = true;
 | |
|     } else if (!popup->mPopupClosed) {
 | |
|       popup->mPopupMatchesLayout = popup->IsPopupInLayoutPopupChain(
 | |
|           aLayoutWidgetHierarchy, /* aMustMatchParent */ true);
 | |
|       LOG("  popup [%p] parent window [%p] matches layout %d\n", (void*)popup,
 | |
|           (void*)popup->mWaylandPopupPrev, popup->mPopupMatchesLayout);
 | |
|     }
 | |
|     popup = popup->mWaylandPopupNext;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::WaylandPopupHierarchyHideTemporary() {
 | |
|   LOG("nsWindow::WaylandPopupHierarchyHideTemporary()");
 | |
|   nsWindow* popup = WaylandPopupFindLast(this);
 | |
|   while (popup && popup != this) {
 | |
|     LOG("  temporary hidding popup [%p]", popup);
 | |
|     nsWindow* prev = popup->mWaylandPopupPrev;
 | |
|     popup->HideWaylandPopupWindow(/* aTemporaryHide */ true,
 | |
|                                   /* aRemoveFromPopupList */ false);
 | |
|     popup = prev;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::WaylandPopupHierarchyShowTemporaryHidden() {
 | |
|   LOG("nsWindow::WaylandPopupHierarchyShowTemporaryHidden()");
 | |
|   nsWindow* popup = this;
 | |
|   while (popup) {
 | |
|     if (popup->mPopupTemporaryHidden) {
 | |
|       popup->mPopupTemporaryHidden = false;
 | |
|       LOG("  showing temporary hidden popup [%p]", popup);
 | |
|       popup->ShowWaylandPopupWindow();
 | |
|     }
 | |
|     popup = popup->mWaylandPopupNext;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::WaylandPopupHierarchyCalculatePositions() {
 | |
|   LOG("nsWindow::WaylandPopupHierarchyCalculatePositions()");
 | |
| 
 | |
|   // Set widget hierarchy in Gtk
 | |
|   nsWindow* popup = mWaylandToplevel->mWaylandPopupNext;
 | |
|   while (popup) {
 | |
|     LOG("  popup [%p] set parent window [%p]", (void*)popup,
 | |
|         (void*)popup->mWaylandPopupPrev);
 | |
|     GtkWindowSetTransientFor(GTK_WINDOW(popup->mShell),
 | |
|                              GTK_WINDOW(popup->mWaylandPopupPrev->mShell));
 | |
|     popup = popup->mWaylandPopupNext;
 | |
|   }
 | |
| 
 | |
|   popup = this;
 | |
|   while (popup) {
 | |
|     // Anchored window has mPopupPosition already calculated against
 | |
|     // its parent, no need to recalculate.
 | |
|     LOG("  popup [%p] bounds [%d, %d] -> [%d x %d]", popup,
 | |
|         (int)(popup->mBounds.x / FractionalScaleFactor()),
 | |
|         (int)(popup->mBounds.y / FractionalScaleFactor()),
 | |
|         (int)(popup->mBounds.width / FractionalScaleFactor()),
 | |
|         (int)(popup->mBounds.height / FractionalScaleFactor()));
 | |
| #ifdef MOZ_LOGGING
 | |
|     if (LOG_ENABLED()) {
 | |
|       if (nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame())) {
 | |
|         auto r = LayoutDeviceRect::FromAppUnitsRounded(
 | |
|             popupFrame->GetRect(),
 | |
|             popupFrame->PresContext()->AppUnitsPerDevPixel());
 | |
|         LOG("  popup [%p] layout [%d, %d] -> [%d x %d]", popup, r.x, r.y,
 | |
|             r.width, r.height);
 | |
|       }
 | |
|     }
 | |
| #endif
 | |
|     if (popup->WaylandPopupIsFirst()) {
 | |
|       LOG("  popup [%p] has toplevel as parent", popup);
 | |
|       popup->mRelativePopupPosition = popup->mPopupPosition;
 | |
|     } else {
 | |
|       if (popup->mPopupAnchored) {
 | |
|         LOG("  popup [%p] is anchored", popup);
 | |
|         if (!popup->mPopupMatchesLayout) {
 | |
|           NS_WARNING("Anchored popup does not match layout!");
 | |
|         }
 | |
|       }
 | |
|       GdkPoint parent = popup->WaylandGetParentPosition();
 | |
| 
 | |
|       LOG("  popup [%p] uses transformed coordinates\n", popup);
 | |
|       LOG("    parent position [%d, %d]\n", parent.x, parent.y);
 | |
|       LOG("    popup position [%d, %d]\n", popup->mPopupPosition.x,
 | |
|           popup->mPopupPosition.y);
 | |
| 
 | |
|       popup->mRelativePopupPosition.x = popup->mPopupPosition.x - parent.x;
 | |
|       popup->mRelativePopupPosition.y = popup->mPopupPosition.y - parent.y;
 | |
|     }
 | |
|     LOG("  popup [%p] transformed popup coordinates from [%d, %d] to [%d, %d]",
 | |
|         popup, popup->mPopupPosition.x, popup->mPopupPosition.y,
 | |
|         popup->mRelativePopupPosition.x, popup->mRelativePopupPosition.y);
 | |
|     popup = popup->mWaylandPopupNext;
 | |
|   }
 | |
| }
 | |
| 
 | |
| // The MenuList popups are used as dropdown menus for example in WebRTC
 | |
| // microphone/camera chooser or autocomplete widgets.
 | |
| bool nsWindow::WaylandPopupIsMenu() {
 | |
|   nsMenuPopupFrame* menuPopupFrame = GetMenuPopupFrame(GetFrame());
 | |
|   if (menuPopupFrame) {
 | |
|     return mPopupType == PopupType::Menu && !menuPopupFrame->IsMenuList();
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool nsWindow::WaylandPopupIsContextMenu() {
 | |
|   nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
 | |
|   if (!popupFrame) {
 | |
|     return false;
 | |
|   }
 | |
|   return popupFrame->IsContextMenu();
 | |
| }
 | |
| 
 | |
| bool nsWindow::WaylandPopupIsPermanent() {
 | |
|   nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
 | |
|   if (!popupFrame) {
 | |
|     // We can always hide popups without frames.
 | |
|     return false;
 | |
|   }
 | |
|   return popupFrame->IsNoAutoHide();
 | |
| }
 | |
| 
 | |
| bool nsWindow::WaylandPopupIsAnchored() {
 | |
|   nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
 | |
|   if (!popupFrame) {
 | |
|     // We can always hide popups without frames.
 | |
|     return false;
 | |
|   }
 | |
|   return !!popupFrame->GetAnchor();
 | |
| }
 | |
| 
 | |
| bool nsWindow::IsWidgetOverflowWindow() {
 | |
|   if (this->GetFrame() && this->GetFrame()->GetContent()->GetID()) {
 | |
|     nsCString nodeId;
 | |
|     this->GetFrame()->GetContent()->GetID()->ToUTF8String(nodeId);
 | |
|     return nodeId.Equals("widget-overflow");
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool nsWindow::WaylandPopupIsFirst() {
 | |
|   return !mWaylandPopupPrev || !mWaylandPopupPrev->mWaylandToplevel;
 | |
| }
 | |
| 
 | |
| nsWindow* nsWindow::GetEffectiveParent() {
 | |
|   GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
 | |
|   if (!parentGtkWindow || !GTK_IS_WIDGET(parentGtkWindow)) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   return get_window_for_gtk_widget(GTK_WIDGET(parentGtkWindow));
 | |
| }
 | |
| 
 | |
| GdkPoint nsWindow::WaylandGetParentPosition() {
 | |
|   GdkPoint topLeft = {0, 0};
 | |
|   nsWindow* window = GetEffectiveParent();
 | |
|   if (window->IsPopup()) {
 | |
|     topLeft = DevicePixelsToGdkPointRoundDown(window->mBounds.TopLeft());
 | |
|   }
 | |
|   LOG("nsWindow::WaylandGetParentPosition() [%d, %d]\n", topLeft.x, topLeft.y);
 | |
|   return topLeft;
 | |
| }
 | |
| 
 | |
| #ifdef MOZ_LOGGING
 | |
| void nsWindow::LogPopupHierarchy() {
 | |
|   if (!LOG_ENABLED()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LOG("Widget Popup Hierarchy:\n");
 | |
|   if (!mWaylandToplevel->mWaylandPopupNext) {
 | |
|     LOG("    Empty\n");
 | |
|   } else {
 | |
|     int indent = 4;
 | |
|     nsWindow* popup = mWaylandToplevel->mWaylandPopupNext;
 | |
|     while (popup) {
 | |
|       nsPrintfCString indentString("%*s", indent, " ");
 | |
|       LOG("%s %s %s nsWindow [%p] Menu %d Permanent %d ContextMenu %d "
 | |
|           "Anchored %d Visible %d MovedByRect %d\n",
 | |
|           indentString.get(), popup->GetFrameTag().get(),
 | |
|           popup->GetPopupTypeName().get(), popup, popup->WaylandPopupIsMenu(),
 | |
|           popup->WaylandPopupIsPermanent(), popup->mPopupContextMenu,
 | |
|           popup->mPopupAnchored, gtk_widget_is_visible(popup->mShell),
 | |
|           popup->mPopupUseMoveToRect);
 | |
|       indent += 4;
 | |
|       popup = popup->mWaylandPopupNext;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   LOG("Layout Popup Hierarchy:\n");
 | |
|   AutoTArray<nsIWidget*, 5> widgetChain;
 | |
|   GetLayoutPopupWidgetChain(&widgetChain);
 | |
|   if (widgetChain.Length() == 0) {
 | |
|     LOG("    Empty\n");
 | |
|   } else {
 | |
|     for (unsigned long i = 0; i < widgetChain.Length(); i++) {
 | |
|       nsWindow* window = static_cast<nsWindow*>(widgetChain[i]);
 | |
|       nsPrintfCString indentString("%*s", (int)(i + 1) * 4, " ");
 | |
|       if (window) {
 | |
|         LOG("%s %s %s nsWindow [%p] Menu %d Permanent %d ContextMenu %d "
 | |
|             "Anchored %d Visible %d MovedByRect %d\n",
 | |
|             indentString.get(), window->GetFrameTag().get(),
 | |
|             window->GetPopupTypeName().get(), window,
 | |
|             window->WaylandPopupIsMenu(), window->WaylandPopupIsPermanent(),
 | |
|             window->mPopupContextMenu, window->mPopupAnchored,
 | |
|             gtk_widget_is_visible(window->mShell), window->mPopupUseMoveToRect);
 | |
|       } else {
 | |
|         LOG("%s null window\n", indentString.get());
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| #endif
 | |
| 
 | |
| nsWindow* nsWindow::GetTopmostWindow() {
 | |
|   if (nsView* view = nsView::GetViewFor(this)) {
 | |
|     if (nsView* parentView = view->GetParent()) {
 | |
|       if (nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr)) {
 | |
|         return static_cast<nsWindow*>(parentWidget);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| // Configure Wayland popup. If true is returned we need to track popup
 | |
| // in popup hierarchy. Otherwise we just show it as is.
 | |
| bool nsWindow::WaylandPopupConfigure() {
 | |
|   if (mIsDragPopup) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Don't track popups without frame
 | |
|   nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
 | |
|   if (!popupFrame) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Popup state can be changed, see Bug 1728952.
 | |
|   bool permanentStateMatches =
 | |
|       mPopupTrackInHierarchy == !WaylandPopupIsPermanent();
 | |
| 
 | |
|   // Popup permanent state (noautohide attribute) can change during popup life.
 | |
|   if (mPopupTrackInHierarchyConfigured && permanentStateMatches) {
 | |
|     return mPopupTrackInHierarchy;
 | |
|   }
 | |
| 
 | |
|   // Configure persistent popup params only once.
 | |
|   // WaylandPopupIsAnchored() can give it wrong value after
 | |
|   // nsMenuPopupFrame::MoveTo() call which we use in move-to-rect callback
 | |
|   // to position popup after wayland position change.
 | |
|   if (!mPopupTrackInHierarchyConfigured) {
 | |
|     mPopupAnchored = WaylandPopupIsAnchored();
 | |
|     mPopupContextMenu = WaylandPopupIsContextMenu();
 | |
|   }
 | |
| 
 | |
|   LOG("nsWindow::WaylandPopupConfigure tracked %d anchored %d hint %d\n",
 | |
|       mPopupTrackInHierarchy, mPopupAnchored, int(mPopupType));
 | |
| 
 | |
|   // Permanent state changed and popup is mapped.
 | |
|   // We need to switch popup type but that's done when popup is mapped
 | |
|   // by Gtk so we need to unmap the popup here.
 | |
|   // It will be mapped again by gtk_widget_show().
 | |
|   if (!permanentStateMatches && mIsMapped) {
 | |
|     LOG("  permanent state change from %d to %d, unmapping",
 | |
|         mPopupTrackInHierarchy, !WaylandPopupIsPermanent());
 | |
|     gtk_widget_unmap(mShell);
 | |
|   }
 | |
| 
 | |
|   mPopupTrackInHierarchy = !WaylandPopupIsPermanent();
 | |
|   LOG("  tracked in hierarchy %d\n", mPopupTrackInHierarchy);
 | |
| 
 | |
|   // See gdkwindow-wayland.c and
 | |
|   // should_map_as_popup()/should_map_as_subsurface()
 | |
|   GdkWindowTypeHint gtkTypeHint;
 | |
|   switch (mPopupType) {
 | |
|     case PopupType::Menu:
 | |
|       // GDK_WINDOW_TYPE_HINT_POPUP_MENU is mapped as xdg_popup by default.
 | |
|       // We use this type for all menu popups.
 | |
|       gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
 | |
|       LOG("  popup type Menu");
 | |
|       break;
 | |
|     case PopupType::Tooltip:
 | |
|       gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
 | |
|       LOG("  popup type Tooltip");
 | |
|       break;
 | |
|     default:
 | |
|       gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
 | |
|       LOG("  popup type Utility");
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   if (!mPopupTrackInHierarchy) {
 | |
|     // GDK_WINDOW_TYPE_HINT_UTILITY is mapped as wl_subsurface
 | |
|     // by default.
 | |
|     LOG("  not tracked in popup hierarchy, switch to Utility");
 | |
|     gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
 | |
|   }
 | |
|   gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
 | |
| 
 | |
|   mPopupTrackInHierarchyConfigured = true;
 | |
|   return mPopupTrackInHierarchy;
 | |
| }
 | |
| 
 | |
| bool nsWindow::IsInPopupHierarchy() {
 | |
|   return mPopupTrackInHierarchy && mWaylandToplevel && mWaylandPopupPrev;
 | |
| }
 | |
| 
 | |
| void nsWindow::AddWindowToPopupHierarchy() {
 | |
|   LOG("nsWindow::AddWindowToPopupHierarchy\n");
 | |
|   if (!GetFrame()) {
 | |
|     LOG("  Window without frame cannot be added as popup!\n");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Check if we're already in the hierarchy
 | |
|   if (!IsInPopupHierarchy()) {
 | |
|     mWaylandToplevel = GetTopmostWindow();
 | |
|     AppendPopupToHierarchyList(mWaylandToplevel);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Wayland keeps strong popup window hierarchy. We need to track active
 | |
| // (visible) popup windows and make sure we hide popup on the same level
 | |
| // before we open another one on that level. It means that every open
 | |
| // popup needs to have an unique parent.
 | |
| void nsWindow::UpdateWaylandPopupHierarchy() {
 | |
|   LOG("nsWindow::UpdateWaylandPopupHierarchy\n");
 | |
| 
 | |
|   // This popup hasn't been added to popup hierarchy yet so no need to
 | |
|   // do any configurations.
 | |
|   if (!IsInPopupHierarchy()) {
 | |
|     LOG("  popup isn't in hierarchy\n");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_LOGGING
 | |
|   LogPopupHierarchy();
 | |
|   auto printPopupHierarchy = MakeScopeExit([&] { LogPopupHierarchy(); });
 | |
| #endif
 | |
| 
 | |
|   // Hide all tooltips without the last one. Tooltip can't be popup parent.
 | |
|   mWaylandToplevel->WaylandPopupHideTooltips();
 | |
| 
 | |
|   // See Bug 1709254 / https://gitlab.gnome.org/GNOME/gtk/-/issues/5092
 | |
|   // It's possible that Wayland compositor refuses to show
 | |
|   // a popup although Gtk claims it's visible.
 | |
|   // We don't know if the popup is shown or not.
 | |
|   // To avoid application crash refuse to create any child of such invisible
 | |
|   // popup and close any child of it now.
 | |
|   mWaylandToplevel->WaylandPopupCloseOrphanedPopups();
 | |
| 
 | |
|   // Check if we have any remote content / overflow window in hierarchy.
 | |
|   // We can't attach such widget on top of other popup.
 | |
|   mWaylandToplevel->CloseAllPopupsBeforeRemotePopup();
 | |
| 
 | |
|   // Check if your popup hierarchy matches layout hierarchy.
 | |
|   // For instance we should not connect hamburger menu on top
 | |
|   // of context menu.
 | |
|   // Close all popups from different layout chains if possible.
 | |
|   AutoTArray<nsIWidget*, 5> layoutPopupWidgetChain;
 | |
|   GetLayoutPopupWidgetChain(&layoutPopupWidgetChain);
 | |
| 
 | |
|   mWaylandToplevel->WaylandPopupHierarchyHideByLayout(&layoutPopupWidgetChain);
 | |
|   mWaylandToplevel->WaylandPopupHierarchyValidateByLayout(
 | |
|       &layoutPopupWidgetChain);
 | |
| 
 | |
|   // Now we have Popup hierarchy complete.
 | |
|   // Find first unchanged (and still open) popup to start with hierarchy
 | |
|   // changes.
 | |
|   nsWindow* changedPopup = mWaylandToplevel->mWaylandPopupNext;
 | |
|   while (changedPopup) {
 | |
|     // Stop when parent of this popup was changed and we need to recalc
 | |
|     // popup position.
 | |
|     if (changedPopup->mPopupChanged) {
 | |
|       break;
 | |
|     }
 | |
|     // Stop when this popup is closed.
 | |
|     if (changedPopup->mPopupClosed) {
 | |
|       break;
 | |
|     }
 | |
|     changedPopup = changedPopup->mWaylandPopupNext;
 | |
|   }
 | |
| 
 | |
|   // We don't need to recompute popup positions, quit now.
 | |
|   if (!changedPopup) {
 | |
|     LOG("  changed Popup is null, quit.\n");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LOG("  first changed popup [%p]\n", (void*)changedPopup);
 | |
| 
 | |
|   // Hide parent popups if necessary (there are layout discontinuity)
 | |
|   // reposition the popup and show them again.
 | |
|   changedPopup->WaylandPopupHierarchyHideTemporary();
 | |
| 
 | |
|   nsWindow* parentOfchangedPopup = nullptr;
 | |
|   if (changedPopup->mPopupClosed) {
 | |
|     parentOfchangedPopup = changedPopup->mWaylandPopupPrev;
 | |
|   }
 | |
|   changedPopup->WaylandPopupRemoveClosedPopups();
 | |
| 
 | |
|   // It's possible that changedPopup was removed from widget hierarchy,
 | |
|   // in such case use child popup of the removed one if there's any.
 | |
|   if (!changedPopup->IsInPopupHierarchy()) {
 | |
|     if (!parentOfchangedPopup || !parentOfchangedPopup->mWaylandPopupNext) {
 | |
|       LOG("  last popup was removed, quit.\n");
 | |
|       return;
 | |
|     }
 | |
|     changedPopup = parentOfchangedPopup->mWaylandPopupNext;
 | |
|   }
 | |
| 
 | |
|   GetLayoutPopupWidgetChain(&layoutPopupWidgetChain);
 | |
|   mWaylandToplevel->WaylandPopupHierarchyValidateByLayout(
 | |
|       &layoutPopupWidgetChain);
 | |
| 
 | |
|   changedPopup->WaylandPopupHierarchyCalculatePositions();
 | |
| 
 | |
|   nsWindow* popup = changedPopup;
 | |
|   while (popup) {
 | |
|     const bool useMoveToRect = [&] {
 | |
|       if (!StaticPrefs::widget_wayland_use_move_to_rect_AtStartup()) {
 | |
|         return false;  // Not available.
 | |
|       }
 | |
|       if (!popup->mPopupMatchesLayout) {
 | |
|         // We can use move_to_rect only when popups in popup hierarchy matches
 | |
|         // layout hierarchy as move_to_rect request that parent/child
 | |
|         // popups are adjacent.
 | |
|         return false;
 | |
|       }
 | |
|       if (popup->mPopupType == PopupType::Panel &&
 | |
|           popup->WaylandPopupIsFirst() &&
 | |
|           popup->WaylandPopupFitsToplevelWindow(/* aMove */ true)) {
 | |
|         // Workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/1986
 | |
|         //
 | |
|         // PopupType::Panel types are used for extension popups which may be
 | |
|         // resized. If such popup uses move-to-rect, we need to hide it before
 | |
|         // resize and show it again. That leads to massive flickering
 | |
|         // so use plain move if possible to avoid it.
 | |
|         //
 | |
|         // Bug 1760276 - don't use move-to-rect when popup is inside main
 | |
|         // Firefox window.
 | |
|         //
 | |
|         // Use it for first popups only due to another mutter bug
 | |
|         // https://gitlab.gnome.org/GNOME/gtk/-/issues/5089
 | |
|         // https://bugzilla.mozilla.org/show_bug.cgi?id=1784873
 | |
|         return false;
 | |
|       }
 | |
|       if (!popup->WaylandPopupIsFirst() &&
 | |
|           !popup->mWaylandPopupPrev->WaylandPopupIsFirst() &&
 | |
|           !popup->mWaylandPopupPrev->mPopupUseMoveToRect) {
 | |
|         // We can't use move-to-rect if there are more parents of
 | |
|         // wl_subsurface popups types.
 | |
|         //
 | |
|         // It's because wl_subsurface is ignored by xgd_popup
 | |
|         // (created by move-to-rect) so our popup scenario:
 | |
|         //
 | |
|         // toplevel -> xgd_popup(1) -> wl_subsurface(2) -> xgd_popup(3)
 | |
|         //
 | |
|         // looks for Wayland compositor as:
 | |
|         //
 | |
|         // toplevel -> xgd_popup(1) -> xgd_popup(3)
 | |
|         //
 | |
|         // If xgd_popup(1) and xgd_popup(3) are not connected
 | |
|         // move-to-rect applied to xgd_popup(3) fails and we get missing popup.
 | |
|         return false;
 | |
|       }
 | |
|       return true;
 | |
|     }();
 | |
| 
 | |
|     LOG("  popup [%p] matches layout [%d] anchored [%d] first popup [%d] use "
 | |
|         "move-to-rect %d\n",
 | |
|         popup, popup->mPopupMatchesLayout, popup->mPopupAnchored,
 | |
|         popup->WaylandPopupIsFirst(), useMoveToRect);
 | |
| 
 | |
|     popup->mPopupUseMoveToRect = useMoveToRect;
 | |
|     popup->WaylandPopupMoveImpl();
 | |
|     popup->mPopupChanged = false;
 | |
|     popup = popup->mWaylandPopupNext;
 | |
|   }
 | |
| 
 | |
|   changedPopup->WaylandPopupHierarchyShowTemporaryHidden();
 | |
| }
 | |
| 
 | |
| static void NativeMoveResizeCallback(GdkWindow* window,
 | |
|                                      const GdkRectangle* flipped_rect,
 | |
|                                      const GdkRectangle* final_rect,
 | |
|                                      gboolean flipped_x, gboolean flipped_y,
 | |
|                                      void* aWindow) {
 | |
|   LOG_POPUP("[%p] NativeMoveResizeCallback flipped_x %d flipped_y %d\n",
 | |
|             aWindow, flipped_x, flipped_y);
 | |
|   LOG_POPUP("[%p]    new position [%d, %d] -> [%d x %d]", aWindow,
 | |
|             final_rect->x, final_rect->y, final_rect->width,
 | |
|             final_rect->height);
 | |
|   nsWindow* wnd = get_window_for_gdk_window(window);
 | |
| 
 | |
|   wnd->NativeMoveResizeWaylandPopupCallback(final_rect, flipped_x, flipped_y);
 | |
| }
 | |
| 
 | |
| // When popup is repositioned by widget code, we need to notify
 | |
| // layout about it. It's because we control popup placement
 | |
| // on widget on Wayland so layout may have old popup size/coordinates.
 | |
| void nsWindow::WaylandPopupPropagateChangesToLayout(bool aMove, bool aResize) {
 | |
|   LOG("nsWindow::WaylandPopupPropagateChangesToLayout()");
 | |
| 
 | |
|   if (aResize) {
 | |
|     LOG("  needSizeUpdate\n");
 | |
|     if (nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame())) {
 | |
|       RefPtr<PresShell> presShell = popupFrame->PresShell();
 | |
|       presShell->FrameNeedsReflow(popupFrame, IntrinsicDirty::None,
 | |
|                                   NS_FRAME_IS_DIRTY);
 | |
|     }
 | |
|   }
 | |
|   if (aMove) {
 | |
|     LOG("  needPositionUpdate, bounds [%d, %d]", mBounds.x, mBounds.y);
 | |
|     NotifyWindowMoved(mBounds.x, mBounds.y, ByMoveToRect::Yes);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::NativeMoveResizeWaylandPopupCallback(
 | |
|     const GdkRectangle* aFinalSize, bool aFlippedX, bool aFlippedY) {
 | |
|   // We're getting move-to-rect callback without move-to-rect call.
 | |
|   // That indicates a compositor bug. It happens when a window is hidden and
 | |
|   // shown again before move-to-rect callback is fired.
 | |
|   // It may lead to incorrect popup placement as we may call
 | |
|   // gtk_window_move() between hide & show.
 | |
|   // See Bug 1777919, 1789581.
 | |
| #if MOZ_LOGGING
 | |
|   if (!mWaitingForMoveToRectCallback) {
 | |
|     LOG("  Bogus move-to-rect callback! Expect wrong popup coordinates.");
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   mWaitingForMoveToRectCallback = false;
 | |
| 
 | |
|   bool movedByLayout = mMovedAfterMoveToRect;
 | |
|   bool resizedByLayout = mResizedAfterMoveToRect;
 | |
| 
 | |
|   // Popup was moved between move-to-rect call and move-to-rect callback
 | |
|   // and the coordinates from move-to-rect callback are outdated.
 | |
|   if (movedByLayout || resizedByLayout) {
 | |
|     LOG("  Another move/resize called during waiting for callback\n");
 | |
|     mMovedAfterMoveToRect = false;
 | |
|     mResizedAfterMoveToRect = false;
 | |
|     // Fire another round of move/resize to reflect latest request
 | |
|     // from layout.
 | |
|     NativeMoveResize(movedByLayout, resizedByLayout);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LOG("  orig mBounds [%d, %d] -> [%d x %d]\n", mBounds.x, mBounds.y,
 | |
|       mBounds.width, mBounds.height);
 | |
| 
 | |
|   LayoutDeviceIntRect newBounds = [&] {
 | |
|     GdkRectangle finalRect = *aFinalSize;
 | |
|     GdkPoint parent = WaylandGetParentPosition();
 | |
|     finalRect.x += parent.x;
 | |
|     finalRect.y += parent.y;
 | |
|     return GdkRectToDevicePixels(finalRect);
 | |
|   }();
 | |
| 
 | |
|   LOG("  new mBounds [%d, %d] -> [%d x %d]", newBounds.x, newBounds.y,
 | |
|       newBounds.width, newBounds.height);
 | |
| 
 | |
|   bool needsPositionUpdate = newBounds.TopLeft() != mBounds.TopLeft();
 | |
|   bool needsSizeUpdate = newBounds.Size() != mLastSizeRequest;
 | |
| 
 | |
|   if (needsSizeUpdate) {
 | |
|     // Wayland compositor changed popup size request from layout.
 | |
|     // Set the constraints to use them in nsMenuPopupFrame::SetPopupPosition().
 | |
|     // Beware that gtk_window_resize() requests sizes asynchronously and so
 | |
|     // newBounds might not have the size from the most recent
 | |
|     // gtk_window_resize().
 | |
|     if (newBounds.width < mLastSizeRequest.width) {
 | |
|       mMoveToRectPopupSize.width = newBounds.width;
 | |
|     }
 | |
|     if (newBounds.height < mLastSizeRequest.height) {
 | |
|       mMoveToRectPopupSize.height = newBounds.height;
 | |
|     }
 | |
|     LOG("  mMoveToRectPopupSize set to [%d, %d]", mMoveToRectPopupSize.width,
 | |
|         mMoveToRectPopupSize.height);
 | |
|   }
 | |
|   mBounds = newBounds;
 | |
|   // Check mBounds size
 | |
|   if (mCompositorSession &&
 | |
|       !wr::WindowSizeSanityCheck(mBounds.width, mBounds.height)) {
 | |
|     gfxCriticalNoteOnce << "Invalid mBounds in PopupCallback " << mBounds
 | |
|                         << " size state " << mSizeMode;
 | |
|   }
 | |
|   WaylandPopupPropagateChangesToLayout(needsPositionUpdate, needsSizeUpdate);
 | |
| }
 | |
| 
 | |
| static GdkGravity PopupAlignmentToGdkGravity(int8_t aAlignment) {
 | |
|   switch (aAlignment) {
 | |
|     case POPUPALIGNMENT_NONE:
 | |
|       return GDK_GRAVITY_NORTH_WEST;
 | |
|     case POPUPALIGNMENT_TOPLEFT:
 | |
|       return GDK_GRAVITY_NORTH_WEST;
 | |
|     case POPUPALIGNMENT_TOPRIGHT:
 | |
|       return GDK_GRAVITY_NORTH_EAST;
 | |
|     case POPUPALIGNMENT_BOTTOMLEFT:
 | |
|       return GDK_GRAVITY_SOUTH_WEST;
 | |
|     case POPUPALIGNMENT_BOTTOMRIGHT:
 | |
|       return GDK_GRAVITY_SOUTH_EAST;
 | |
|     case POPUPALIGNMENT_LEFTCENTER:
 | |
|       return GDK_GRAVITY_WEST;
 | |
|     case POPUPALIGNMENT_RIGHTCENTER:
 | |
|       return GDK_GRAVITY_EAST;
 | |
|     case POPUPALIGNMENT_TOPCENTER:
 | |
|       return GDK_GRAVITY_NORTH;
 | |
|     case POPUPALIGNMENT_BOTTOMCENTER:
 | |
|       return GDK_GRAVITY_SOUTH;
 | |
|   }
 | |
|   return GDK_GRAVITY_STATIC;
 | |
| }
 | |
| 
 | |
| bool nsWindow::IsPopupDirectionRTL() {
 | |
|   nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
 | |
|   return popupFrame && popupFrame->IsDirectionRTL();
 | |
| }
 | |
| 
 | |
| // Position the popup directly by gtk_window_move() and try to keep it
 | |
| // on screen by just moving it in scope of it's parent window.
 | |
| //
 | |
| // It's used when we position noautihode popup and we don't use xdg_positioner.
 | |
| // See Bug 1718867
 | |
| void nsWindow::WaylandPopupSetDirectPosition() {
 | |
|   GdkPoint topLeft = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
 | |
|   GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mLastSizeRequest);
 | |
| 
 | |
|   LOG("nsWindow::WaylandPopupSetDirectPosition %d,%d -> %d x %d\n", topLeft.x,
 | |
|       topLeft.y, size.width, size.height);
 | |
| 
 | |
|   mPopupPosition = {topLeft.x, topLeft.y};
 | |
| 
 | |
|   if (mIsDragPopup) {
 | |
|     gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y);
 | |
|     gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
 | |
|     // DND window is placed inside container so we need to make hard size
 | |
|     // request to ensure parent container is resized too.
 | |
|     gtk_widget_set_size_request(GTK_WIDGET(mShell), size.width, size.height);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
 | |
|   nsWindow* window = get_window_for_gtk_widget(GTK_WIDGET(parentGtkWindow));
 | |
|   if (!window) {
 | |
|     return;
 | |
|   }
 | |
|   GdkWindow* gdkWindow =
 | |
|       gtk_widget_get_window(GTK_WIDGET(window->GetMozContainer()));
 | |
| 
 | |
|   int parentWidth = gdk_window_get_width(gdkWindow);
 | |
|   int popupWidth = size.width;
 | |
| 
 | |
|   int x;
 | |
|   gdk_window_get_position(gdkWindow, &x, nullptr);
 | |
| 
 | |
|   // If popup is bigger than main window just center it.
 | |
|   if (popupWidth > parentWidth) {
 | |
|     mPopupPosition.x = -(parentWidth - popupWidth) / 2 + x;
 | |
|   } else {
 | |
|     if (IsPopupDirectionRTL()) {
 | |
|       // Stick with right window edge
 | |
|       if (mPopupPosition.x < x) {
 | |
|         mPopupPosition.x = x;
 | |
|       }
 | |
|     } else {
 | |
|       // Stick with left window edge
 | |
|       if (mPopupPosition.x + popupWidth > parentWidth + x) {
 | |
|         mPopupPosition.x = parentWidth + x - popupWidth;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   LOG("  set position [%d, %d]\n", mPopupPosition.x, mPopupPosition.y);
 | |
|   gtk_window_move(GTK_WINDOW(mShell), mPopupPosition.x, mPopupPosition.y);
 | |
| 
 | |
|   LOG("  set size [%d, %d]\n", size.width, size.height);
 | |
|   gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
 | |
| 
 | |
|   if (mPopupPosition.x != topLeft.x) {
 | |
|     mBounds.MoveTo(GdkPointToDevicePixels(mPopupPosition));
 | |
|     LOG("  setting new bounds [%d, %d]\n", mBounds.x, mBounds.y);
 | |
|     WaylandPopupPropagateChangesToLayout(/* move */ true, /* resize */ false);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool nsWindow::WaylandPopupFitsToplevelWindow(bool aMove) {
 | |
|   LOG("nsWindow::WaylandPopupFitsToplevelWindow() move %d", aMove);
 | |
| 
 | |
|   GtkWindow* parent = gtk_window_get_transient_for(GTK_WINDOW(mShell));
 | |
|   GtkWindow* tmp = parent;
 | |
|   while ((tmp = gtk_window_get_transient_for(GTK_WINDOW(parent)))) {
 | |
|     parent = tmp;
 | |
|   }
 | |
|   GdkWindow* toplevelGdkWindow = gtk_widget_get_window(GTK_WIDGET(parent));
 | |
| 
 | |
|   if (!toplevelGdkWindow) {
 | |
|     NS_WARNING("Toplevel widget without GdkWindow?");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   int parentWidth = gdk_window_get_width(toplevelGdkWindow);
 | |
|   int parentHeight = gdk_window_get_height(toplevelGdkWindow);
 | |
|   LOG("  parent size %d x %d", parentWidth, parentHeight);
 | |
| 
 | |
|   GdkPoint topLeft = aMove ? mPopupPosition
 | |
|                            : DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
 | |
|   GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mLastSizeRequest);
 | |
|   LOG("  popup topleft %d, %d size %d x %d", topLeft.x, topLeft.y, size.width,
 | |
|       size.height);
 | |
|   int fits = topLeft.x >= 0 && topLeft.y >= 0 &&
 | |
|              topLeft.x + size.width <= parentWidth &&
 | |
|              topLeft.y + size.height <= parentHeight;
 | |
| 
 | |
|   LOG("  fits %d", fits);
 | |
|   return fits;
 | |
| }
 | |
| 
 | |
| void nsWindow::NativeMoveResizeWaylandPopup(bool aMove, bool aResize) {
 | |
|   GdkPoint topLeft = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
 | |
|   GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mLastSizeRequest);
 | |
| 
 | |
|   LOG("nsWindow::NativeMoveResizeWaylandPopup Bounds %d,%d -> %d x %d move %d "
 | |
|       "resize %d\n",
 | |
|       topLeft.x, topLeft.y, size.width, size.height, aMove, aResize);
 | |
| 
 | |
|   // Compositor may be confused by windows with width/height = 0
 | |
|   // and positioning such windows leads to Bug 1555866.
 | |
|   if (!AreBoundsSane()) {
 | |
|     LOG("  Bounds are not sane (width: %d height: %d)\n",
 | |
|         mLastSizeRequest.width, mLastSizeRequest.height);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mWaitingForMoveToRectCallback) {
 | |
|     LOG("  waiting for move to rect, scheduling");
 | |
|     // mBounds position must not be overwritten before it is applied.
 | |
|     // OnConfigureEvent() will not set mBounds to an old position for
 | |
|     // GTK_WINDOW_POPUP.
 | |
|     MOZ_ASSERT(gtk_window_get_window_type(GTK_WINDOW(mShell)) ==
 | |
|                GTK_WINDOW_POPUP);
 | |
|     mMovedAfterMoveToRect = aMove;
 | |
|     mResizedAfterMoveToRect = aResize;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mMovedAfterMoveToRect = false;
 | |
|   mResizedAfterMoveToRect = false;
 | |
| 
 | |
|   bool trackedInHierarchy = WaylandPopupConfigure();
 | |
| 
 | |
|   // Read popup position from layout if it was moved or newly created.
 | |
|   // This position is used by move-to-rect method as we need anchor and other
 | |
|   // info to place popup correctly.
 | |
|   // We need WaylandPopupConfigure() to be called before to have all needed
 | |
|   // popup info in place (mainly the anchored flag).
 | |
|   if (aMove) {
 | |
|     mPopupMoveToRectParams = WaylandPopupGetPositionFromLayout();
 | |
|   }
 | |
| 
 | |
|   if (!trackedInHierarchy) {
 | |
|     WaylandPopupSetDirectPosition();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aResize) {
 | |
|     LOG("  set size [%d, %d]\n", size.width, size.height);
 | |
|     gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
 | |
|   }
 | |
| 
 | |
|   if (!aMove && WaylandPopupFitsToplevelWindow(aMove)) {
 | |
|     // Popup position has not been changed and its position/size fits
 | |
|     // parent window so no need to reposition the window.
 | |
|     LOG("  fits parent window size, just resize\n");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Mark popup as changed as we're updating position/size.
 | |
|   mPopupChanged = true;
 | |
| 
 | |
|   // Save popup position for former re-calculations when popup hierarchy
 | |
|   // is changed.
 | |
|   LOG("  popup position changed from [%d, %d] to [%d, %d]\n", mPopupPosition.x,
 | |
|       mPopupPosition.y, topLeft.x, topLeft.y);
 | |
|   mPopupPosition = {topLeft.x, topLeft.y};
 | |
| 
 | |
|   UpdateWaylandPopupHierarchy();
 | |
| }
 | |
| 
 | |
| struct PopupSides {
 | |
|   Maybe<Side> mVertical;
 | |
|   Maybe<Side> mHorizontal;
 | |
| };
 | |
| 
 | |
| static PopupSides SidesForPopupAlignment(int8_t aAlignment) {
 | |
|   switch (aAlignment) {
 | |
|     case POPUPALIGNMENT_NONE:
 | |
|       break;
 | |
|     case POPUPALIGNMENT_TOPLEFT:
 | |
|       return {Some(eSideTop), Some(eSideLeft)};
 | |
|     case POPUPALIGNMENT_TOPRIGHT:
 | |
|       return {Some(eSideTop), Some(eSideRight)};
 | |
|     case POPUPALIGNMENT_BOTTOMLEFT:
 | |
|       return {Some(eSideBottom), Some(eSideLeft)};
 | |
|     case POPUPALIGNMENT_BOTTOMRIGHT:
 | |
|       return {Some(eSideBottom), Some(eSideRight)};
 | |
|     case POPUPALIGNMENT_LEFTCENTER:
 | |
|       return {Nothing(), Some(eSideLeft)};
 | |
|     case POPUPALIGNMENT_RIGHTCENTER:
 | |
|       return {Nothing(), Some(eSideRight)};
 | |
|     case POPUPALIGNMENT_TOPCENTER:
 | |
|       return {Some(eSideTop), Nothing()};
 | |
|     case POPUPALIGNMENT_BOTTOMCENTER:
 | |
|       return {Some(eSideBottom), Nothing()};
 | |
|   }
 | |
|   return {};
 | |
| }
 | |
| 
 | |
| // We want to apply margins based on popup alignment (which would generally be
 | |
| // just an offset to apply to the popup). However, to deal with flipping
 | |
| // correctly, we apply the margin to the anchor when possible.
 | |
| struct ResolvedPopupMargin {
 | |
|   // A margin to be applied to the anchor.
 | |
|   nsMargin mAnchorMargin;
 | |
|   // An offset in app units to be applied to the popup for when we need to tell
 | |
|   // GTK to center inside the anchor precisely (so we can't really do better in
 | |
|   // presence of flips).
 | |
|   nsPoint mPopupOffset;
 | |
| };
 | |
| 
 | |
| static ResolvedPopupMargin ResolveMargin(nsMenuPopupFrame* aFrame,
 | |
|                                          int8_t aPopupAlign,
 | |
|                                          int8_t aAnchorAlign,
 | |
|                                          bool aAnchoredToPoint,
 | |
|                                          bool aIsContextMenu) {
 | |
|   nsMargin margin = aFrame->GetMargin();
 | |
|   nsPoint offset;
 | |
| 
 | |
|   if (aAnchoredToPoint) {
 | |
|     // Since GTK doesn't allow us to specify margins itself, when anchored to a
 | |
|     // point we can just assume we'll be aligned correctly... This is kind of
 | |
|     // annoying but alas.
 | |
|     //
 | |
|     // This calculation must match the relevant unanchored popup calculation in
 | |
|     // nsMenuPopupFrame::SetPopupPosition(), which should itself be the inverse
 | |
|     // inverse of nsMenuPopupFrame::MoveTo().
 | |
|     if (aIsContextMenu && aFrame->IsDirectionRTL()) {
 | |
|       offset.x = -margin.right;
 | |
|     } else {
 | |
|       offset.x = margin.left;
 | |
|     }
 | |
|     offset.y = margin.top;
 | |
|     return {nsMargin(), offset};
 | |
|   }
 | |
| 
 | |
|   auto popupSides = SidesForPopupAlignment(aPopupAlign);
 | |
|   auto anchorSides = SidesForPopupAlignment(aAnchorAlign);
 | |
|   // Matched sides: Invert the margin, so that we pull in the right direction.
 | |
|   // Popup not aligned to any anchor side: We give up and use the offset,
 | |
|   // applying the margin from the popup side.
 | |
|   // Mismatched sides: We swap the margins so that we pull in the right
 | |
|   // direction, e.g. margin-left: -10px should shrink 10px the _right_ of the
 | |
|   // box, not the left of the box.
 | |
|   if (popupSides.mHorizontal == anchorSides.mHorizontal) {
 | |
|     margin.left = -margin.left;
 | |
|     margin.right = -margin.right;
 | |
|   } else if (!anchorSides.mHorizontal) {
 | |
|     auto popupSide = *popupSides.mHorizontal;
 | |
|     offset.x += popupSide == eSideRight ? -margin.Side(popupSide)
 | |
|                                         : margin.Side(popupSide);
 | |
|     margin.left = margin.right = 0;
 | |
|   } else {
 | |
|     std::swap(margin.left, margin.right);
 | |
|   }
 | |
| 
 | |
|   // Same logic as above, but in the vertical direction.
 | |
|   if (popupSides.mVertical == anchorSides.mVertical) {
 | |
|     margin.top = -margin.top;
 | |
|     margin.bottom = -margin.bottom;
 | |
|   } else if (!anchorSides.mVertical) {
 | |
|     auto popupSide = *popupSides.mVertical;
 | |
|     offset.y += popupSide == eSideBottom ? -margin.Side(popupSide)
 | |
|                                          : margin.Side(popupSide);
 | |
|     margin.top = margin.bottom = 0;
 | |
|   } else {
 | |
|     std::swap(margin.top, margin.bottom);
 | |
|   }
 | |
| 
 | |
|   return {margin, offset};
 | |
| }
 | |
| 
 | |
| #ifdef MOZ_LOGGING
 | |
| void nsWindow::LogPopupAnchorHints(int aHints) {
 | |
|   static struct hints_ {
 | |
|     int hint;
 | |
|     char name[100];
 | |
|   } hints[] = {
 | |
|       {GDK_ANCHOR_FLIP_X, "GDK_ANCHOR_FLIP_X"},
 | |
|       {GDK_ANCHOR_FLIP_Y, "GDK_ANCHOR_FLIP_Y"},
 | |
|       {GDK_ANCHOR_SLIDE_X, "GDK_ANCHOR_SLIDE_X"},
 | |
|       {GDK_ANCHOR_SLIDE_Y, "GDK_ANCHOR_SLIDE_Y"},
 | |
|       {GDK_ANCHOR_RESIZE_X, "GDK_ANCHOR_RESIZE_X"},
 | |
|       {GDK_ANCHOR_RESIZE_Y, "GDK_ANCHOR_RESIZE_X"},
 | |
|   };
 | |
| 
 | |
|   LOG("  PopupAnchorHints");
 | |
|   for (const auto& hint : hints) {
 | |
|     if (hint.hint & aHints) {
 | |
|       LOG("    %s", hint.name);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::LogPopupGravity(GdkGravity aGravity) {
 | |
|   static char gravity[][100]{"NONE",
 | |
|                              "GDK_GRAVITY_NORTH_WEST",
 | |
|                              "GDK_GRAVITY_NORTH",
 | |
|                              "GDK_GRAVITY_NORTH_EAST",
 | |
|                              "GDK_GRAVITY_WEST",
 | |
|                              "GDK_GRAVITY_CENTER",
 | |
|                              "GDK_GRAVITY_EAST",
 | |
|                              "GDK_GRAVITY_SOUTH_WEST",
 | |
|                              "GDK_GRAVITY_SOUTH",
 | |
|                              "GDK_GRAVITY_SOUTH_EAST",
 | |
|                              "GDK_GRAVITY_STATIC"};
 | |
|   LOG("    %s", gravity[aGravity]);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| const nsWindow::WaylandPopupMoveToRectParams
 | |
| nsWindow::WaylandPopupGetPositionFromLayout() {
 | |
|   LOG("nsWindow::WaylandPopupGetPositionFromLayout\n");
 | |
| 
 | |
|   nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
 | |
| 
 | |
|   const bool isTopContextMenu = mPopupContextMenu && !mPopupAnchored;
 | |
|   const bool isRTL = IsPopupDirectionRTL();
 | |
|   const bool anchored = popupFrame->IsAnchored();
 | |
|   int8_t popupAlign = POPUPALIGNMENT_TOPLEFT;
 | |
|   int8_t anchorAlign = POPUPALIGNMENT_BOTTOMRIGHT;
 | |
|   if (anchored) {
 | |
|     // See nsMenuPopupFrame::AdjustPositionForAnchorAlign.
 | |
|     popupAlign = popupFrame->GetPopupAlignment();
 | |
|     anchorAlign = popupFrame->GetPopupAnchor();
 | |
|   }
 | |
|   if (isRTL && (anchored || isTopContextMenu)) {
 | |
|     popupAlign = -popupAlign;
 | |
|     anchorAlign = -anchorAlign;
 | |
|   }
 | |
| 
 | |
|   // Although we have mPopupPosition / mRelativePopupPosition here
 | |
|   // we can't use it. move-to-rect needs anchor rectangle to position a popup
 | |
|   // but we have only a point from Resize().
 | |
|   //
 | |
|   // So we need to extract popup position from nsMenuPopupFrame() and duplicate
 | |
|   // the layout work here.
 | |
|   LayoutDeviceIntRect anchorRect;
 | |
|   ResolvedPopupMargin popupMargin;
 | |
|   {
 | |
|     nsRect anchorRectAppUnits = popupFrame->GetUntransformedAnchorRect();
 | |
|     // This is a somewhat hacky way of applying the popup margin. We don't know
 | |
|     // if GTK will end up flipping the popup, in which case the offset we
 | |
|     // compute is just wrong / applied to the wrong side.
 | |
|     //
 | |
|     // Instead, we tell it to anchor us at a smaller or bigger rect depending on
 | |
|     // the margin, which achieves the same result if the popup is positioned
 | |
|     // correctly, but doesn't misposition the popup when flipped across the
 | |
|     // anchor.
 | |
|     popupMargin = ResolveMargin(popupFrame, popupAlign, anchorAlign,
 | |
|                                 anchorRectAppUnits.IsEmpty(), isTopContextMenu);
 | |
|     LOG("  layout popup CSS anchor (%d, %d) %s, margin %s offset %s\n",
 | |
|         popupAlign, anchorAlign, ToString(anchorRectAppUnits).c_str(),
 | |
|         ToString(popupMargin.mAnchorMargin).c_str(),
 | |
|         ToString(popupMargin.mPopupOffset).c_str());
 | |
|     anchorRectAppUnits.Inflate(popupMargin.mAnchorMargin);
 | |
|     LOG("    after margins %s\n", ToString(anchorRectAppUnits).c_str());
 | |
|     nscoord auPerDev = popupFrame->PresContext()->AppUnitsPerDevPixel();
 | |
|     anchorRect = LayoutDeviceIntRect::FromAppUnitsToNearest(anchorRectAppUnits,
 | |
|                                                             auPerDev);
 | |
|     if (anchorRect.width < 0) {
 | |
|       auto w = -anchorRect.width;
 | |
|       anchorRect.width += w + 1;
 | |
|       anchorRect.x += w;
 | |
|     }
 | |
|     LOG("    final %s\n", ToString(anchorRect).c_str());
 | |
|   }
 | |
| 
 | |
|   LOG("  relative popup rect position [%d, %d] -> [%d x %d]\n", anchorRect.x,
 | |
|       anchorRect.y, anchorRect.width, anchorRect.height);
 | |
| 
 | |
|   // Get gravity and flip type
 | |
|   GdkGravity rectAnchor = PopupAlignmentToGdkGravity(anchorAlign);
 | |
|   GdkGravity menuAnchor = PopupAlignmentToGdkGravity(popupAlign);
 | |
| 
 | |
|   LOG("  parentRect gravity: %d anchor gravity: %d\n", rectAnchor, menuAnchor);
 | |
| 
 | |
|   // Gtk default is: GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE | GDK_ANCHOR_RESIZE.
 | |
|   // We want to SLIDE_X menu on the dual monitor setup rather than resize it
 | |
|   // on the other monitor.
 | |
|   GdkAnchorHints hints =
 | |
|       GdkAnchorHints(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_RESIZE);
 | |
| 
 | |
|   // slideHorizontal from nsMenuPopupFrame::SetPopupPosition
 | |
|   int8_t position = popupFrame->GetAlignmentPosition();
 | |
|   if (position >= POPUPPOSITION_BEFORESTART &&
 | |
|       position <= POPUPPOSITION_AFTEREND) {
 | |
|     hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE_X);
 | |
|   }
 | |
|   // slideVertical from nsMenuPopupFrame::SetPopupPosition
 | |
|   if (position >= POPUPPOSITION_STARTBEFORE &&
 | |
|       position <= POPUPPOSITION_ENDAFTER) {
 | |
|     hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE_Y);
 | |
|   }
 | |
| 
 | |
|   FlipType flipType = popupFrame->GetFlipType();
 | |
|   if (rectAnchor == GDK_GRAVITY_CENTER && menuAnchor == GDK_GRAVITY_CENTER) {
 | |
|     // only slide
 | |
|     hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
 | |
|   } else {
 | |
|     switch (flipType) {
 | |
|       case FlipType_Both:
 | |
|         hints = GdkAnchorHints(hints | GDK_ANCHOR_FLIP);
 | |
|         break;
 | |
|       case FlipType_Slide:
 | |
|         hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
 | |
|         break;
 | |
|       case FlipType_Default:
 | |
|         hints = GdkAnchorHints(hints | GDK_ANCHOR_FLIP);
 | |
|         break;
 | |
|       default:
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
|   if (!WaylandPopupIsMenu()) {
 | |
|     // we don't want to slide menus to fit the screen rather resize them
 | |
|     hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
 | |
|   }
 | |
| 
 | |
|   // We want tooltips to flip verticaly or slide only.
 | |
|   // See nsMenuPopupFrame::SetPopupPosition().
 | |
|   // https://searchfox.org/mozilla-central/rev/d0f5bc50aff3462c9d1546b88d60c5cb020eb15c/layout/xul/nsMenuPopupFrame.cpp#1603
 | |
|   if (mPopupType == PopupType::Tooltip) {
 | |
|     hints = GdkAnchorHints(GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_SLIDE);
 | |
|   }
 | |
| 
 | |
|   return {
 | |
|       anchorRect,
 | |
|       rectAnchor,
 | |
|       menuAnchor,
 | |
|       hints,
 | |
|       DevicePixelsToGdkPointRoundDown(LayoutDevicePoint::FromAppUnitsToNearest(
 | |
|           popupMargin.mPopupOffset,
 | |
|           popupFrame->PresContext()->AppUnitsPerDevPixel())),
 | |
|       true};
 | |
| }
 | |
| 
 | |
| bool nsWindow::WaylandPopupAnchorAdjustForParentPopup(
 | |
|     GdkRectangle* aPopupAnchor, GdkPoint* aOffset) {
 | |
|   LOG("nsWindow::WaylandPopupAnchorAdjustForParentPopup");
 | |
| 
 | |
|   GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
 | |
|   if (!parentGtkWindow || !GTK_IS_WIDGET(parentGtkWindow)) {
 | |
|     NS_WARNING("Popup has no parent!");
 | |
|     return false;
 | |
|   }
 | |
|   GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(parentGtkWindow));
 | |
|   if (!window) {
 | |
|     NS_WARNING("Popup parrent is not mapped!");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   GdkRectangle parentWindowRect = {0, 0, gdk_window_get_width(window),
 | |
|                                    gdk_window_get_height(window)};
 | |
|   LOG("  parent window size %d x %d", parentWindowRect.width,
 | |
|       parentWindowRect.height);
 | |
| 
 | |
|   // We can't have rectangle anchor with zero width/height.
 | |
|   if (!aPopupAnchor->width) {
 | |
|     aPopupAnchor->width = 1;
 | |
|   }
 | |
|   if (!aPopupAnchor->height) {
 | |
|     aPopupAnchor->height = 1;
 | |
|   }
 | |
| 
 | |
|   GdkRectangle finalRect;
 | |
|   if (!gdk_rectangle_intersect(aPopupAnchor, &parentWindowRect, &finalRect)) {
 | |
|     return false;
 | |
|   }
 | |
|   *aPopupAnchor = finalRect;
 | |
|   LOG("  anchor is correct %d,%d -> %d x %d", finalRect.x, finalRect.y,
 | |
|       finalRect.width, finalRect.height);
 | |
| 
 | |
|   *aOffset = mPopupMoveToRectParams.mOffset;
 | |
|   LOG("  anchor offset %d, %d", aOffset->x, aOffset->y);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool nsWindow::WaylandPopupCheckAndGetAnchor(GdkRectangle* aPopupAnchor,
 | |
|                                              GdkPoint* aOffset) {
 | |
|   LOG("nsWindow::WaylandPopupCheckAndGetAnchor");
 | |
| 
 | |
|   GdkWindow* gdkWindow = GetToplevelGdkWindow();
 | |
|   nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
 | |
|   if (!gdkWindow || !popupFrame) {
 | |
|     LOG("  can't use move-to-rect due missing gdkWindow or popupFrame");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (popupFrame->IsConstrainedByLayout()) {
 | |
|     LOG("  can't use move-to-rect, flipped / constrained by layout");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (!mPopupMoveToRectParams.mAnchorSet) {
 | |
|     LOG("  can't use move-to-rect due missing anchor");
 | |
|     return false;
 | |
|   }
 | |
|   // Update popup layout coordinates from layout by recent popup hierarchy
 | |
|   // (calculate correct position according to parent window)
 | |
|   // and convert to Gtk coordinates.
 | |
|   LayoutDeviceIntRect anchorRect = mPopupMoveToRectParams.mAnchorRect;
 | |
|   if (!WaylandPopupIsFirst()) {
 | |
|     GdkPoint parent = WaylandGetParentPosition();
 | |
|     LOG("  subtract parent position from anchor [%d, %d]\n", parent.x,
 | |
|         parent.y);
 | |
|     anchorRect.MoveBy(-GdkPointToDevicePixels(parent));
 | |
|   }
 | |
| 
 | |
|   *aPopupAnchor = DevicePixelsToGdkRectRoundOut(anchorRect);
 | |
|   LOG("  anchored to rectangle [%d, %d] -> [%d x %d]", aPopupAnchor->x,
 | |
|       aPopupAnchor->y, aPopupAnchor->width, aPopupAnchor->height);
 | |
| 
 | |
|   if (!WaylandPopupAnchorAdjustForParentPopup(aPopupAnchor, aOffset)) {
 | |
|     LOG("  can't use move-to-rect, anchor is not placed inside of parent "
 | |
|         "window");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void nsWindow::WaylandPopupPrepareForMove() {
 | |
|   LOG("nsWindow::WaylandPopupPrepareForMove()");
 | |
| 
 | |
|   if (mPopupType == PopupType::Tooltip) {
 | |
|     // Don't fiddle with tooltips type, just hide it before move-to-rect
 | |
|     if (mPopupUseMoveToRect && gtk_widget_is_visible(mShell)) {
 | |
|       HideWaylandPopupWindow(/* aTemporaryHide */ true,
 | |
|                              /* aRemoveFromPopupList */ false);
 | |
|     }
 | |
|     LOG("  it's tooltip, quit");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // See https://bugzilla.mozilla.org/show_bug.cgi?id=1785185#c8
 | |
|   // gtk_window_move() needs GDK_WINDOW_TYPE_HINT_UTILITY popup type.
 | |
|   // move-to-rect requires GDK_WINDOW_TYPE_HINT_POPUP_MENU popups type.
 | |
|   // We need to set it before map event when popup is hidden.
 | |
|   const GdkWindowTypeHint currentType =
 | |
|       gtk_window_get_type_hint(GTK_WINDOW(mShell));
 | |
|   const GdkWindowTypeHint requiredType = mPopupUseMoveToRect
 | |
|                                              ? GDK_WINDOW_TYPE_HINT_POPUP_MENU
 | |
|                                              : GDK_WINDOW_TYPE_HINT_UTILITY;
 | |
| 
 | |
|   if (!mPopupUseMoveToRect && currentType == requiredType) {
 | |
|     LOG("  type matches and we're not forced to hide it, quit.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (gtk_widget_is_visible(mShell)) {
 | |
|     HideWaylandPopupWindow(/* aTemporaryHide */ true,
 | |
|                            /* aRemoveFromPopupList */ false);
 | |
|   }
 | |
| 
 | |
|   if (currentType != requiredType) {
 | |
|     LOG("  set type %s",
 | |
|         requiredType == GDK_WINDOW_TYPE_HINT_POPUP_MENU ? "MENU" : "UTILITY");
 | |
|     gtk_window_set_type_hint(GTK_WINDOW(mShell), requiredType);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Plain popup move on Wayland - simply place popup on given location.
 | |
| // We can't just call gtk_window_move() as it's not effective on visible
 | |
| // popups.
 | |
| void nsWindow::WaylandPopupMovePlain(int aX, int aY) {
 | |
|   LOG("nsWindow::WaylandPopupMovePlain(%d, %d)", aX, aY);
 | |
| 
 | |
|   // We can directly move only popups based on wl_subsurface type.
 | |
|   MOZ_DIAGNOSTIC_ASSERT(gtk_window_get_type_hint(GTK_WINDOW(mShell)) ==
 | |
|                             GDK_WINDOW_TYPE_HINT_UTILITY ||
 | |
|                         gtk_window_get_type_hint(GTK_WINDOW(mShell)) ==
 | |
|                             GDK_WINDOW_TYPE_HINT_TOOLTIP);
 | |
| 
 | |
|   gtk_window_move(GTK_WINDOW(mShell), aX, aY);
 | |
| 
 | |
|   // gtk_window_move() can trick us. When widget is hidden gtk_window_move()
 | |
|   // does not move the widget but sets new widget coordinates when widget
 | |
|   // is mapped again.
 | |
|   //
 | |
|   // If popup used move-to-rect before
 | |
|   // (GdkWindow has POSITION_METHOD_MOVE_TO_RECT set), popup will use
 | |
|   // move-to-rect again when it's mapped and we'll get bogus move-to-rect
 | |
|   // callback.
 | |
|   //
 | |
|   // gdk_window_move() sets position_method to POSITION_METHOD_MOVE_RESIZE
 | |
|   // so we'll use plain move when popup is shown.
 | |
|   if (!gtk_widget_get_mapped(mShell)) {
 | |
|     if (GdkWindow* window = GetToplevelGdkWindow()) {
 | |
|       gdk_window_move(window, aX, aY);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::WaylandPopupMoveImpl() {
 | |
|   // Available as of GTK 3.24+
 | |
|   static auto sGdkWindowMoveToRect = (void (*)(
 | |
|       GdkWindow*, const GdkRectangle*, GdkGravity, GdkGravity, GdkAnchorHints,
 | |
|       gint, gint))dlsym(RTLD_DEFAULT, "gdk_window_move_to_rect");
 | |
| 
 | |
|   if (mPopupUseMoveToRect && !sGdkWindowMoveToRect) {
 | |
|     LOG("can't use move-to-rect due missing gdk_window_move_to_rect()");
 | |
|     mPopupUseMoveToRect = false;
 | |
|   }
 | |
| 
 | |
|   GdkRectangle gtkAnchorRect;
 | |
|   GdkPoint offset;
 | |
|   if (mPopupUseMoveToRect) {
 | |
|     mPopupUseMoveToRect =
 | |
|         WaylandPopupCheckAndGetAnchor(>kAnchorRect, &offset);
 | |
|   }
 | |
| 
 | |
|   LOG("nsWindow::WaylandPopupMove");
 | |
|   LOG("  original widget popup position [%d, %d]\n", mPopupPosition.x,
 | |
|       mPopupPosition.y);
 | |
|   LOG("  relative widget popup position [%d, %d]\n", mRelativePopupPosition.x,
 | |
|       mRelativePopupPosition.y);
 | |
|   LOG("  popup use move to rect %d", mPopupUseMoveToRect);
 | |
| 
 | |
|   WaylandPopupPrepareForMove();
 | |
| 
 | |
|   if (!mPopupUseMoveToRect) {
 | |
|     WaylandPopupMovePlain(mRelativePopupPosition.x, mRelativePopupPosition.y);
 | |
|     // Layout already should be aware of our bounds, since we didn't change it
 | |
|     // from the widget side for flipping or so.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Correct popup position now. It will be updated by gdk_window_move_to_rect()
 | |
|   // anyway but we need to set it now to avoid a race condition here.
 | |
|   WaylandPopupRemoveNegativePosition();
 | |
| 
 | |
|   GdkWindow* gdkWindow = GetToplevelGdkWindow();
 | |
|   if (!g_signal_handler_find(gdkWindow, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
 | |
|                              FuncToGpointer(NativeMoveResizeCallback), this)) {
 | |
|     g_signal_connect(gdkWindow, "moved-to-rect",
 | |
|                      G_CALLBACK(NativeMoveResizeCallback), this);
 | |
|   }
 | |
|   mWaitingForMoveToRectCallback = true;
 | |
| 
 | |
| #ifdef MOZ_LOGGING
 | |
|   if (LOG_ENABLED()) {
 | |
|     LOG("  Call move-to-rect");
 | |
|     LOG("  Anchor rect [%d, %d] -> [%d x %d]", gtkAnchorRect.x, gtkAnchorRect.y,
 | |
|         gtkAnchorRect.width, gtkAnchorRect.height);
 | |
|     LOG("  Offset [%d, %d]", offset.x, offset.y);
 | |
|     LOG("  AnchorType");
 | |
|     LogPopupGravity(mPopupMoveToRectParams.mAnchorRectType);
 | |
|     LOG("  PopupAnchorType");
 | |
|     LogPopupGravity(mPopupMoveToRectParams.mPopupAnchorType);
 | |
|     LogPopupAnchorHints(mPopupMoveToRectParams.mHints);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   sGdkWindowMoveToRect(gdkWindow, >kAnchorRect,
 | |
|                        mPopupMoveToRectParams.mAnchorRectType,
 | |
|                        mPopupMoveToRectParams.mPopupAnchorType,
 | |
|                        mPopupMoveToRectParams.mHints, offset.x, offset.y);
 | |
| }
 | |
| 
 | |
| void nsWindow::SetZIndex(int32_t aZIndex) {
 | |
|   nsIWidget* oldPrev = GetPrevSibling();
 | |
| 
 | |
|   nsBaseWidget::SetZIndex(aZIndex);
 | |
| 
 | |
|   if (GetPrevSibling() == oldPrev) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // We skip the nsWindows that don't have mGdkWindows.
 | |
|   // These are probably in the process of being destroyed.
 | |
|   if (!mGdkWindow) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!GetNextSibling()) {
 | |
|     // We're to be on top.
 | |
|     if (mGdkWindow) {
 | |
|       gdk_window_raise(mGdkWindow);
 | |
|     }
 | |
|   } else {
 | |
|     // All the siblings before us need to be below our widget.
 | |
|     for (nsWindow* w = this; w;
 | |
|          w = static_cast<nsWindow*>(w->GetPrevSibling())) {
 | |
|       if (w->mGdkWindow) {
 | |
|         gdk_window_lower(w->mGdkWindow);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::SetSizeMode(nsSizeMode aMode) {
 | |
|   LOG("nsWindow::SetSizeMode %d\n", aMode);
 | |
| 
 | |
|   // Return if there's no shell or our current state is the same as the mode we
 | |
|   // were just set to.
 | |
|   if (!mShell) {
 | |
|     LOG("    no shell");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mSizeMode == aMode && mLastSizeModeRequest == aMode) {
 | |
|     LOG("    already set");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // It is tempting to try to optimize calls below based only on current
 | |
|   // mSizeMode, but that wouldn't work if there's a size-request in flight
 | |
|   // (specially before show). See bug 1789823.
 | |
|   const auto SizeModeMightBe = [&](nsSizeMode aModeToTest) {
 | |
|     if (mSizeMode != mLastSizeModeRequest) {
 | |
|       // Arbitrary size mode requests might be ongoing.
 | |
|       return true;
 | |
|     }
 | |
|     return mSizeMode == aModeToTest;
 | |
|   };
 | |
| 
 | |
|   if (aMode != nsSizeMode_Fullscreen && aMode != nsSizeMode_Minimized) {
 | |
|     // Fullscreen and minimized are compatible.
 | |
|     if (SizeModeMightBe(nsSizeMode_Fullscreen)) {
 | |
|       MakeFullScreen(false);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   switch (aMode) {
 | |
|     case nsSizeMode_Maximized:
 | |
|       LOG("    set maximized");
 | |
|       gtk_window_maximize(GTK_WINDOW(mShell));
 | |
|       break;
 | |
|     case nsSizeMode_Minimized:
 | |
|       LOG("    set minimized");
 | |
|       gtk_window_iconify(GTK_WINDOW(mShell));
 | |
|       break;
 | |
|     case nsSizeMode_Fullscreen:
 | |
|       LOG("    set fullscreen");
 | |
|       MakeFullScreen(true);
 | |
|       break;
 | |
|     default:
 | |
|       MOZ_FALLTHROUGH_ASSERT("Unknown size mode");
 | |
|     case nsSizeMode_Normal:
 | |
|       LOG("    set normal");
 | |
|       if (SizeModeMightBe(nsSizeMode_Maximized)) {
 | |
|         gtk_window_unmaximize(GTK_WINDOW(mShell));
 | |
|       }
 | |
|       if (SizeModeMightBe(nsSizeMode_Minimized)) {
 | |
|         gtk_window_deiconify(GTK_WINDOW(mShell));
 | |
|         // We need this for actual deiconification on mutter.
 | |
|         gtk_window_present(GTK_WINDOW(mShell));
 | |
|       }
 | |
|       break;
 | |
|   }
 | |
|   mLastSizeModeRequest = aMode;
 | |
| }
 | |
| 
 | |
| #define kDesktopMutterSchema "org.gnome.mutter"_ns
 | |
| #define kDesktopDynamicWorkspacesKey "dynamic-workspaces"_ns
 | |
| 
 | |
| static bool WorkspaceManagementDisabled(GdkScreen* screen) {
 | |
|   if (Preferences::GetBool("widget.disable-workspace-management", false)) {
 | |
|     return true;
 | |
|   }
 | |
|   if (Preferences::HasUserValue("widget.workspace-management")) {
 | |
|     return Preferences::GetBool("widget.workspace-management");
 | |
|   }
 | |
| 
 | |
|   if (IsGnomeDesktopEnvironment()) {
 | |
|     // Gnome uses dynamic workspaces by default so disable workspace management
 | |
|     // in that case.
 | |
|     bool usesDynamicWorkspaces = true;
 | |
|     nsCOMPtr<nsIGSettingsService> gsettings =
 | |
|         do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
 | |
|     if (gsettings) {
 | |
|       nsCOMPtr<nsIGSettingsCollection> mutterSettings;
 | |
|       gsettings->GetCollectionForSchema(kDesktopMutterSchema,
 | |
|                                         getter_AddRefs(mutterSettings));
 | |
|       if (mutterSettings) {
 | |
|         mutterSettings->GetBoolean(kDesktopDynamicWorkspacesKey,
 | |
|                                    &usesDynamicWorkspaces);
 | |
|       }
 | |
|     }
 | |
|     return usesDynamicWorkspaces;
 | |
|   }
 | |
| 
 | |
|   const auto& desktop = GetDesktopEnvironmentIdentifier();
 | |
|   return desktop.EqualsLiteral("bspwm") || desktop.EqualsLiteral("i3");
 | |
| }
 | |
| 
 | |
| void nsWindow::GetWorkspaceID(nsAString& workspaceID) {
 | |
|   workspaceID.Truncate();
 | |
| 
 | |
|   if (!GdkIsX11Display() || !mShell) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
|   LOG("nsWindow::GetWorkspaceID()\n");
 | |
| 
 | |
|   // Get the gdk window for this widget.
 | |
|   GdkWindow* gdk_window = GetToplevelGdkWindow();
 | |
|   if (!gdk_window) {
 | |
|     LOG("  missing Gdk window, quit.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (WorkspaceManagementDisabled(gdk_window_get_screen(gdk_window))) {
 | |
|     LOG("  WorkspaceManagementDisabled, quit.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
 | |
|   GdkAtom type_returned;
 | |
|   int format_returned;
 | |
|   int length_returned;
 | |
|   long* wm_desktop;
 | |
| 
 | |
|   if (!gdk_property_get(gdk_window, gdk_atom_intern("_NET_WM_DESKTOP", FALSE),
 | |
|                         cardinal_atom,
 | |
|                         0,          // offset
 | |
|                         INT32_MAX,  // length
 | |
|                         FALSE,      // delete
 | |
|                         &type_returned, &format_returned, &length_returned,
 | |
|                         (guchar**)&wm_desktop)) {
 | |
|     LOG("  gdk_property_get() failed, quit.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LOG("  got workspace ID %d", (int32_t)wm_desktop[0]);
 | |
|   workspaceID.AppendInt((int32_t)wm_desktop[0]);
 | |
|   g_free(wm_desktop);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| void nsWindow::MoveToWorkspace(const nsAString& workspaceIDStr) {
 | |
|   nsresult rv = NS_OK;
 | |
|   int32_t workspaceID = workspaceIDStr.ToInteger(&rv);
 | |
| 
 | |
|   LOG("nsWindow::MoveToWorkspace() ID %d", workspaceID);
 | |
|   if (NS_FAILED(rv) || !workspaceID || !GdkIsX11Display() || !mShell) {
 | |
|     LOG("  MoveToWorkspace disabled, quit");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
|   // Get the gdk window for this widget.
 | |
|   GdkWindow* gdk_window = GetToplevelGdkWindow();
 | |
|   if (!gdk_window) {
 | |
|     LOG("  failed to get GdkWindow, quit.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // This code is inspired by some found in the 'gxtuner' project.
 | |
|   // https://github.com/brummer10/gxtuner/blob/792d453da0f3a599408008f0f1107823939d730d/deskpager.cpp#L50
 | |
|   XEvent xevent;
 | |
|   Display* xdisplay = gdk_x11_get_default_xdisplay();
 | |
|   GdkScreen* screen = gdk_window_get_screen(gdk_window);
 | |
|   Window root_win = GDK_WINDOW_XID(gdk_screen_get_root_window(screen));
 | |
|   GdkDisplay* display = gdk_window_get_display(gdk_window);
 | |
|   Atom type = gdk_x11_get_xatom_by_name_for_display(display, "_NET_WM_DESKTOP");
 | |
| 
 | |
|   xevent.type = ClientMessage;
 | |
|   xevent.xclient.type = ClientMessage;
 | |
|   xevent.xclient.serial = 0;
 | |
|   xevent.xclient.send_event = TRUE;
 | |
|   xevent.xclient.display = xdisplay;
 | |
|   xevent.xclient.window = GDK_WINDOW_XID(gdk_window);
 | |
|   xevent.xclient.message_type = type;
 | |
|   xevent.xclient.format = 32;
 | |
|   xevent.xclient.data.l[0] = workspaceID;
 | |
|   xevent.xclient.data.l[1] = X11CurrentTime;
 | |
|   xevent.xclient.data.l[2] = 0;
 | |
|   xevent.xclient.data.l[3] = 0;
 | |
|   xevent.xclient.data.l[4] = 0;
 | |
| 
 | |
|   XSendEvent(xdisplay, root_win, FALSE,
 | |
|              SubstructureNotifyMask | SubstructureRedirectMask, &xevent);
 | |
| 
 | |
|   XFlush(xdisplay);
 | |
|   LOG("  moved to workspace");
 | |
| #endif
 | |
| }
 | |
| 
 | |
| void nsWindow::SetUserTimeAndStartupTokenForActivatedWindow() {
 | |
|   nsGTKToolkit* toolkit = nsGTKToolkit::GetToolkit();
 | |
|   if (!toolkit || MOZ_UNLIKELY(mWindowType == WindowType::Invisible)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mWindowActivationTokenFromEnv = toolkit->GetStartupToken();
 | |
|   if (!mWindowActivationTokenFromEnv.IsEmpty()) {
 | |
|     if (!GdkIsWaylandDisplay()) {
 | |
|       gtk_window_set_startup_id(GTK_WINDOW(mShell),
 | |
|                                 mWindowActivationTokenFromEnv.get());
 | |
|       // In the case of X11, the above call is all we need. For wayland we need
 | |
|       // to keep the token around until we take it in
 | |
|       // TransferFocusToWaylandWindow.
 | |
|       mWindowActivationTokenFromEnv.Truncate();
 | |
|     }
 | |
|   } else if (uint32_t timestamp = toolkit->GetFocusTimestamp()) {
 | |
|     // We don't have the data we need. Fall back to an
 | |
|     // approximation ... using the timestamp of the remote command
 | |
|     // being received as a guess for the timestamp of the user event
 | |
|     // that triggered it.
 | |
|     gdk_window_focus(GetToplevelGdkWindow(), timestamp);
 | |
|   }
 | |
| 
 | |
|   // If we used the startup ID, that already contains the focus timestamp;
 | |
|   // we don't want to reuse the timestamp next time we raise the window
 | |
|   toolkit->SetFocusTimestamp(0);
 | |
|   toolkit->SetStartupToken(""_ns);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| guint32 nsWindow::GetLastUserInputTime() {
 | |
|   // gdk_x11_display_get_user_time/gtk_get_current_event_time tracks
 | |
|   // button and key presses, DESKTOP_STARTUP_ID used to start the app,
 | |
|   // drop events from external drags,
 | |
|   // WM_DELETE_WINDOW delete events, but not usually mouse motion nor
 | |
|   // button and key releases.  Therefore use the most recent of
 | |
|   // gdk_x11_display_get_user_time and the last time that we have seen.
 | |
| #ifdef MOZ_X11
 | |
|   GdkDisplay* gdkDisplay = gdk_display_get_default();
 | |
|   guint32 timestamp = GdkIsX11Display(gdkDisplay)
 | |
|                           ? gdk_x11_display_get_user_time(gdkDisplay)
 | |
|                           : gtk_get_current_event_time();
 | |
| #else
 | |
|   guint32 timestamp = gtk_get_current_event_time();
 | |
| #endif
 | |
| 
 | |
|   if (sLastUserInputTime != GDK_CURRENT_TIME &&
 | |
|       TimestampIsNewerThan(sLastUserInputTime, timestamp)) {
 | |
|     return sLastUserInputTime;
 | |
|   }
 | |
| 
 | |
|   return timestamp;
 | |
| }
 | |
| 
 | |
| #ifdef MOZ_WAYLAND
 | |
| void nsWindow::FocusWaylandWindow(const char* aTokenID) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aTokenID);
 | |
| 
 | |
|   LOG("nsWindow::FocusWaylandWindow(%s)", aTokenID);
 | |
|   if (IsDestroyed()) {
 | |
|     LOG("  already destroyed, quit.");
 | |
|     return;
 | |
|   }
 | |
|   wl_surface* surface =
 | |
|       mGdkWindow ? gdk_wayland_window_get_wl_surface(mGdkWindow) : nullptr;
 | |
|   if (!surface) {
 | |
|     LOG("  mGdkWindow is not visible, quit.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LOG("  requesting xdg-activation, surface ID %d",
 | |
|       wl_proxy_get_id((struct wl_proxy*)surface));
 | |
|   xdg_activation_v1* xdg_activation = WaylandDisplayGet()->GetXdgActivation();
 | |
|   if (!xdg_activation) {
 | |
|     return;
 | |
|   }
 | |
|   xdg_activation_v1_activate(xdg_activation, aTokenID, surface);
 | |
| }
 | |
| 
 | |
| // Transfer focus from gFocusWindow to aWindow and use xdg_activation
 | |
| // protocol for it.
 | |
| void nsWindow::TransferFocusToWaylandWindow(nsWindow* aWindow) {
 | |
|   LOGW("nsWindow::TransferFocusToWaylandWindow(%p) gFocusWindow %p", aWindow,
 | |
|        gFocusWindow);
 | |
|   auto promise = mozilla::widget::RequestWaylandFocusPromise();
 | |
|   if (NS_WARN_IF(!promise)) {
 | |
|     LOGW("  quit, failed to create TransferFocusToWaylandWindow [%p]", aWindow);
 | |
|     return;
 | |
|   }
 | |
|   promise->Then(
 | |
|       GetMainThreadSerialEventTarget(), __func__,
 | |
|       /* resolve */
 | |
|       [window = RefPtr{aWindow}](nsCString token) {
 | |
|         window->FocusWaylandWindow(token.get());
 | |
|       },
 | |
|       /* reject */
 | |
|       [window = RefPtr{aWindow}](bool state) {
 | |
|         LOGW("TransferFocusToWaylandWindow [%p] failed", window.get());
 | |
|       });
 | |
| }
 | |
| #endif
 | |
| 
 | |
| // Request activation of this window or give focus to this widget.
 | |
| // aRaise means whether we should request activation of this widget's
 | |
| // toplevel window.
 | |
| //
 | |
| // nsWindow::SetFocus(Raise::Yes) - Raise and give focus to toplevel window.
 | |
| // nsWindow::SetFocus(Raise::No) - Give focus to this window.
 | |
| void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
 | |
|   LOG("nsWindow::SetFocus Raise %d\n", aRaise == Raise::Yes);
 | |
| 
 | |
|   // Raise the window if someone passed in true and the prefs are
 | |
|   // set properly.
 | |
|   GtkWidget* toplevelWidget = gtk_widget_get_toplevel(GTK_WIDGET(mContainer));
 | |
| 
 | |
|   LOG("  gFocusWindow [%p]\n", gFocusWindow);
 | |
|   LOG("  mContainer [%p]\n", GTK_WIDGET(mContainer));
 | |
|   LOG("  Toplevel widget [%p]\n", toplevelWidget);
 | |
| 
 | |
|   // Make sure that our owning widget has focus.  If it doesn't try to
 | |
|   // grab it.  Note that we don't set our focus flag in this case.
 | |
|   if (StaticPrefs::mozilla_widget_raise_on_setfocus_AtStartup() &&
 | |
|       aRaise == Raise::Yes && toplevelWidget &&
 | |
|       !gtk_widget_has_focus(toplevelWidget)) {
 | |
|     if (gtk_widget_get_visible(mShell)) {
 | |
|       LOG("  toplevel is not focused");
 | |
|       gdk_window_show_unraised(GetToplevelGdkWindow());
 | |
|       // Unset the urgency hint if possible.
 | |
|       SetUrgencyHint(mShell, false);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   RefPtr<nsWindow> toplevelWindow = get_window_for_gtk_widget(toplevelWidget);
 | |
|   if (!toplevelWindow) {
 | |
|     LOG("  missing toplevel nsWindow, quit\n");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aRaise == Raise::Yes) {
 | |
|     // means request toplevel activation.
 | |
| 
 | |
|     // This is asynchronous. If and when the window manager accepts the request,
 | |
|     // then the focus widget will get a focus-in-event signal.
 | |
|     if (StaticPrefs::mozilla_widget_raise_on_setfocus_AtStartup() &&
 | |
|         toplevelWindow->mIsShown && toplevelWindow->mShell &&
 | |
|         !gtk_window_is_active(GTK_WINDOW(toplevelWindow->mShell))) {
 | |
|       LOG("  toplevel is visible but not active, requesting activation [%p]",
 | |
|           toplevelWindow.get());
 | |
| 
 | |
|       // Take the time here explicitly for the call below.
 | |
|       const uint32_t timestamp = [&] {
 | |
|         if (nsGTKToolkit* toolkit = nsGTKToolkit::GetToolkit()) {
 | |
|           if (uint32_t t = toolkit->GetFocusTimestamp()) {
 | |
|             toolkit->SetFocusTimestamp(0);
 | |
|             return t;
 | |
|           }
 | |
|         }
 | |
|         return GetLastUserInputTime();
 | |
|       }();
 | |
| 
 | |
|       toplevelWindow->SetUserTimeAndStartupTokenForActivatedWindow();
 | |
|       gtk_window_present_with_time(GTK_WINDOW(toplevelWindow->mShell),
 | |
|                                    timestamp);
 | |
| 
 | |
| #ifdef MOZ_WAYLAND
 | |
|       if (GdkIsWaylandDisplay()) {
 | |
|         auto existingToken =
 | |
|             std::move(toplevelWindow->mWindowActivationTokenFromEnv);
 | |
|         if (!existingToken.IsEmpty()) {
 | |
|           LOG("  has existing activation token.");
 | |
|           toplevelWindow->FocusWaylandWindow(existingToken.get());
 | |
|         } else {
 | |
|           LOG("  missing activation token, try to transfer from focused "
 | |
|               "window");
 | |
|           TransferFocusToWaylandWindow(toplevelWindow);
 | |
|         }
 | |
|       }
 | |
| #endif
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // aRaise == No means that keyboard events should be dispatched from this
 | |
|   // widget.
 | |
| 
 | |
|   // Ensure GTK_WIDGET(mContainer) is the focused GtkWidget within its toplevel
 | |
|   // window.
 | |
|   //
 | |
|   // For WindowType::Popup, this GtkWidget may not actually be the one that
 | |
|   // receives the key events as it may be the parent window that is active.
 | |
|   if (!gtk_widget_is_focus(GTK_WIDGET(mContainer))) {
 | |
|     // This is synchronous.  It takes focus from a plugin or from a widget
 | |
|     // in an embedder.  The focus manager already knows that this window
 | |
|     // is active so gBlockActivateEvent avoids another (unnecessary)
 | |
|     // activate notification.
 | |
|     gBlockActivateEvent = true;
 | |
|     gtk_widget_grab_focus(GTK_WIDGET(mContainer));
 | |
|     gBlockActivateEvent = false;
 | |
|   }
 | |
| 
 | |
|   // If this is the widget that already has focus, return.
 | |
|   if (gFocusWindow == this) {
 | |
|     LOG("  already have focus");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Set this window to be the focused child window
 | |
|   gFocusWindow = this;
 | |
| 
 | |
|   if (mIMContext) {
 | |
|     mIMContext->OnFocusWindow(this);
 | |
|   }
 | |
| 
 | |
|   LOG("  widget now has focus in SetFocus()");
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntRect nsWindow::GetScreenBounds() {
 | |
|   const LayoutDeviceIntPoint origin = [&] {
 | |
|     if (mIsDestroyed || !mGdkWindow) {
 | |
|       return LayoutDeviceIntPoint(0, 0);
 | |
|     }
 | |
|     gint x, y;
 | |
|     gdk_window_get_root_origin(mGdkWindow, &x, &y);
 | |
| 
 | |
|     // Workaround for https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4820
 | |
|     // Bug 1775017 Gtk < 3.24.35 returns scaled values for
 | |
|     // override redirected window on X11.
 | |
|     if (gtk_check_version(3, 24, 35) != nullptr && GdkIsX11Display() &&
 | |
|         gdk_window_get_window_type(mGdkWindow) == GDK_WINDOW_TEMP) {
 | |
|       return LayoutDeviceIntPoint(x, y);
 | |
|     }
 | |
|     return GdkPointToDevicePixels({x, y});
 | |
|   }();
 | |
| 
 | |
|   // mBounds.Size() is the window bounds, not the window-manager frame
 | |
|   // bounds (bug 581863).  gdk_window_get_frame_extents would give the
 | |
|   // frame bounds, but mBounds.Size() is returned here for consistency
 | |
|   // with Resize.
 | |
|   const LayoutDeviceIntRect rect(origin, mBounds.Size());
 | |
| #if MOZ_LOGGING
 | |
|   if (MOZ_LOG_TEST(IsPopup() ? gWidgetPopupLog : gWidgetLog,
 | |
|                    LogLevel::Verbose)) {
 | |
|     gint scale = GdkCeiledScaleFactor();
 | |
|     if (mLastLoggedScale != scale || !(mLastLoggedBoundSize == rect)) {
 | |
|       mLastLoggedScale = scale;
 | |
|       mLastLoggedBoundSize = rect;
 | |
|       LOG("GetScreenBounds %d,%d -> %d x %d, unscaled %d,%d -> %d x %d\n",
 | |
|           rect.x, rect.y, rect.width, rect.height, rect.x / scale,
 | |
|           rect.y / scale, rect.width / scale, rect.height / scale);
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
|   return rect;
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntSize nsWindow::GetClientSize() {
 | |
|   return LayoutDeviceIntSize(mBounds.width, mBounds.height);
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntRect nsWindow::GetClientBounds() {
 | |
|   // GetBounds returns a rect whose top left represents the top left of the
 | |
|   // outer bounds, but whose width/height represent the size of the inner
 | |
|   // bounds (which is messed up).
 | |
|   LayoutDeviceIntRect rect = GetBounds();
 | |
|   rect.MoveBy(GetClientOffset());
 | |
|   return rect;
 | |
| }
 | |
| 
 | |
| void nsWindow::RecomputeClientOffset(bool aNotify) {
 | |
|   if (!IsTopLevelWindowType()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   auto oldOffset = mClientOffset;
 | |
| 
 | |
|   mClientOffset = WidgetToScreenOffset() - mBounds.TopLeft();
 | |
| 
 | |
|   if (aNotify && mClientOffset != oldOffset) {
 | |
|     // Send a WindowMoved notification. This ensures that BrowserParent picks up
 | |
|     // the new client offset and sends it to the child process if appropriate.
 | |
|     NotifyWindowMoved(mBounds.x, mBounds.y);
 | |
|   }
 | |
| }
 | |
| 
 | |
| gboolean nsWindow::OnPropertyNotifyEvent(GtkWidget* aWidget,
 | |
|                                          GdkEventProperty* aEvent) {
 | |
|   if (aEvent->atom == gdk_atom_intern("_NET_FRAME_EXTENTS", FALSE)) {
 | |
|     RecomputeClientOffset(/* aNotify = */ true);
 | |
|     return FALSE;
 | |
|   }
 | |
|   if (!mGdkWindow) {
 | |
|     return FALSE;
 | |
|   }
 | |
| #ifdef MOZ_X11
 | |
|   if (GetCurrentTimeGetter()->PropertyNotifyHandler(aWidget, aEvent)) {
 | |
|     return TRUE;
 | |
|   }
 | |
| #endif
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static GdkCursor* GetCursorForImage(const nsIWidget::Cursor& aCursor,
 | |
|                                     int32_t aWidgetScaleFactor) {
 | |
|   if (!aCursor.IsCustom()) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
 | |
| 
 | |
|   // NOTE: GTK only allows integer scale factors, so we ceil to the larger scale
 | |
|   // factor and then tell gtk to scale it down. We ensure to scale at least to
 | |
|   // the GDK scale factor, so that cursors aren't downsized in HiDPI on wayland,
 | |
|   // see bug 1707533.
 | |
|   int32_t gtkScale = std::max(
 | |
|       aWidgetScaleFactor, int32_t(std::ceil(std::max(aCursor.mResolution.mX,
 | |
|                                                      aCursor.mResolution.mY))));
 | |
| 
 | |
|   // Reject cursors greater than 128 pixels in some direction, to prevent
 | |
|   // spoofing.
 | |
|   // XXX ideally we should rescale. Also, we could modify the API to
 | |
|   // allow trusted content to set larger cursors.
 | |
|   //
 | |
|   // TODO(emilio, bug 1445844): Unify the solution for this with other
 | |
|   // platforms.
 | |
|   if (size.width > 128 || size.height > 128) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsIntSize rasterSize = size * gtkScale;
 | |
|   RefPtr<GdkPixbuf> pixbuf =
 | |
|       nsImageToPixbuf::ImageToPixbuf(aCursor.mContainer, Some(rasterSize));
 | |
|   if (!pixbuf) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // Looks like all cursors need an alpha channel (tested on Gtk 2.4.4). This
 | |
|   // is of course not documented anywhere...
 | |
|   // So add one if there isn't one yet
 | |
|   if (!gdk_pixbuf_get_has_alpha(pixbuf)) {
 | |
|     RefPtr<GdkPixbuf> alphaBuf =
 | |
|         dont_AddRef(gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0));
 | |
|     pixbuf = std::move(alphaBuf);
 | |
|     if (!pixbuf) {
 | |
|       return nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   cairo_surface_t* surface =
 | |
|       gdk_cairo_surface_create_from_pixbuf(pixbuf, gtkScale, nullptr);
 | |
|   if (!surface) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   auto CleanupSurface =
 | |
|       MakeScopeExit([&]() { cairo_surface_destroy(surface); });
 | |
| 
 | |
|   return gdk_cursor_new_from_surface(gdk_display_get_default(), surface,
 | |
|                                      aCursor.mHotspotX, aCursor.mHotspotY);
 | |
| }
 | |
| 
 | |
| void nsWindow::SetCursor(const Cursor& aCursor) {
 | |
|   if (mWidgetCursorLocked) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Only change cursor if it's actually been changed
 | |
|   if (!mUpdateCursor && mCursor == aCursor) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mUpdateCursor = false;
 | |
|   mCursor = aCursor;
 | |
| 
 | |
|   // Try to set the cursor image first, and fall back to the numeric cursor.
 | |
|   GdkCursor* imageCursor = GetCursorForImage(aCursor, GdkCeiledScaleFactor());
 | |
| 
 | |
|   // When using a custom cursor, clear the cursor first using eCursor_none, in
 | |
|   // order to work around https://gitlab.gnome.org/GNOME/gtk/-/issues/6242
 | |
|   GdkCursor* nonImageCursor =
 | |
|       get_gtk_cursor(imageCursor ? eCursor_none : aCursor.mDefaultCursor);
 | |
|   auto CleanupCursor = mozilla::MakeScopeExit([&]() {
 | |
|     // get_gtk_cursor returns a weak reference, which we shouldn't unref.
 | |
|     if (imageCursor) {
 | |
|       g_object_unref(imageCursor);
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)),
 | |
|                         nonImageCursor);
 | |
|   if (imageCursor) {
 | |
|     gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)),
 | |
|                           imageCursor);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
 | |
|   if (!mGdkWindow) return;
 | |
| 
 | |
|   GdkRectangle rect = DevicePixelsToGdkRectRoundOut(aRect);
 | |
|   gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
 | |
| 
 | |
|   LOG("Invalidate (rect): %d %d %d %d\n", rect.x, rect.y, rect.width,
 | |
|       rect.height);
 | |
| }
 | |
| 
 | |
| void* nsWindow::GetNativeData(uint32_t aDataType) {
 | |
|   switch (aDataType) {
 | |
|     case NS_NATIVE_WINDOW:
 | |
|     case NS_NATIVE_WIDGET: {
 | |
|       return mGdkWindow;
 | |
|     }
 | |
| 
 | |
|     case NS_NATIVE_SHELLWIDGET:
 | |
|       return GetToplevelWidget();
 | |
| 
 | |
|     case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID:
 | |
| #ifdef MOZ_X11
 | |
|       if (GdkIsX11Display()) {
 | |
|         return (void*)GDK_WINDOW_XID(gdk_window_get_toplevel(mGdkWindow));
 | |
|       }
 | |
| #endif
 | |
|       NS_WARNING(
 | |
|           "nsWindow::GetNativeData(): NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID is not "
 | |
|           "handled on Wayland!");
 | |
|       return nullptr;
 | |
|     case NS_RAW_NATIVE_IME_CONTEXT: {
 | |
|       void* pseudoIMEContext = GetPseudoIMEContext();
 | |
|       if (pseudoIMEContext) {
 | |
|         return pseudoIMEContext;
 | |
|       }
 | |
|       // If IME context isn't available on this widget, we should set |this|
 | |
|       // instead of nullptr.
 | |
|       if (!mIMContext) {
 | |
|         return this;
 | |
|       }
 | |
|       return mIMContext.get();
 | |
|     }
 | |
|     case NS_NATIVE_OPENGL_CONTEXT:
 | |
|       return nullptr;
 | |
|     case NS_NATIVE_EGL_WINDOW: {
 | |
|       void* eglWindow = nullptr;
 | |
| 
 | |
|       // We can't block on mutex here as it leads to a deadlock:
 | |
|       // 1) mutex is taken at nsWindow::Destroy()
 | |
|       // 2) NS_NATIVE_EGL_WINDOW is called from compositor/rendering thread,
 | |
|       //    blocking on mutex.
 | |
|       // 3) DestroyCompositor() is called by nsWindow::Destroy(). As a sync
 | |
|       //    call it waits to compositor/rendering threads,
 | |
|       //    but they're blocked at 2).
 | |
|       //    It's fine if we return null EGL window during DestroyCompositor(),
 | |
|       //    in such case compositor painting is skipped.
 | |
|       if (mDestroyMutex.TryLock()) {
 | |
|         if (mGdkWindow && !mIsDestroyed) {
 | |
| #ifdef MOZ_X11
 | |
|           if (GdkIsX11Display()) {
 | |
|             eglWindow = (void*)GDK_WINDOW_XID(mGdkWindow);
 | |
|           }
 | |
| #endif
 | |
| #ifdef MOZ_WAYLAND
 | |
|           if (GdkIsWaylandDisplay()) {
 | |
|             bool hiddenWindow =
 | |
|                 mCompositorWidgetDelegate &&
 | |
|                 mCompositorWidgetDelegate->AsGtkCompositorWidget() &&
 | |
|                 mCompositorWidgetDelegate->AsGtkCompositorWidget()->IsHidden();
 | |
|             if (!hiddenWindow) {
 | |
|               eglWindow = moz_container_wayland_get_egl_window(
 | |
|                   mContainer, FractionalScaleFactor());
 | |
|             }
 | |
|           }
 | |
| #endif
 | |
|         }
 | |
|         mDestroyMutex.Unlock();
 | |
|       }
 | |
| 
 | |
|       LOG("Get NS_NATIVE_EGL_WINDOW mGdkWindow %p returned eglWindow %p",
 | |
|           mGdkWindow, eglWindow);
 | |
|       return eglWindow;
 | |
|     }
 | |
|     default:
 | |
|       NS_WARNING("nsWindow::GetNativeData called with bad value");
 | |
|       return nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult nsWindow::SetTitle(const nsAString& aTitle) {
 | |
|   if (!mShell) return NS_OK;
 | |
| 
 | |
|     // convert the string into utf8 and set the title.
 | |
| #define UTF8_FOLLOWBYTE(ch) (((ch) & 0xC0) == 0x80)
 | |
|   NS_ConvertUTF16toUTF8 titleUTF8(aTitle);
 | |
|   if (titleUTF8.Length() > NS_WINDOW_TITLE_MAX_LENGTH) {
 | |
|     // Truncate overlong titles (bug 167315). Make sure we chop after a
 | |
|     // complete sequence by making sure the next char isn't a follow-byte.
 | |
|     uint32_t len = NS_WINDOW_TITLE_MAX_LENGTH;
 | |
|     while (UTF8_FOLLOWBYTE(titleUTF8[len])) --len;
 | |
|     titleUTF8.Truncate(len);
 | |
|   }
 | |
|   gtk_window_set_title(GTK_WINDOW(mShell), (const char*)titleUTF8.get());
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsWindow::SetIcon(const nsAString& aIconSpec) {
 | |
|   if (!mShell) return;
 | |
| 
 | |
|   nsAutoCString iconName;
 | |
| 
 | |
|   if (aIconSpec.EqualsLiteral("default")) {
 | |
|     nsAutoString brandName;
 | |
|     WidgetUtils::GetBrandShortName(brandName);
 | |
|     if (brandName.IsEmpty()) {
 | |
|       brandName.AssignLiteral(u"Mozilla");
 | |
|     }
 | |
|     AppendUTF16toUTF8(brandName, iconName);
 | |
|     ToLowerCase(iconName);
 | |
|   } else {
 | |
|     AppendUTF16toUTF8(aIconSpec, iconName);
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIFile> iconFile;
 | |
|   nsAutoCString path;
 | |
| 
 | |
|   gint* iconSizes = gtk_icon_theme_get_icon_sizes(gtk_icon_theme_get_default(),
 | |
|                                                   iconName.get());
 | |
|   bool foundIcon = (iconSizes[0] != 0);
 | |
|   g_free(iconSizes);
 | |
| 
 | |
|   if (!foundIcon) {
 | |
|     // Look for icons with the following suffixes appended to the base name
 | |
|     // The last two entries (for the old XPM format) will be ignored unless
 | |
|     // no icons are found using other suffixes. XPM icons are deprecated.
 | |
| 
 | |
|     const char16_t extensions[9][8] = {u".png",    u"16.png", u"32.png",
 | |
|                                        u"48.png",  u"64.png", u"128.png",
 | |
|                                        u"256.png", u".xpm",   u"16.xpm"};
 | |
| 
 | |
|     for (uint32_t i = 0; i < ArrayLength(extensions); i++) {
 | |
|       // Don't bother looking for XPM versions if we found a PNG.
 | |
|       if (i == ArrayLength(extensions) - 2 && foundIcon) break;
 | |
| 
 | |
|       ResolveIconName(aIconSpec, nsDependentString(extensions[i]),
 | |
|                       getter_AddRefs(iconFile));
 | |
|       if (iconFile) {
 | |
|         iconFile->GetNativePath(path);
 | |
|         GdkPixbuf* icon = gdk_pixbuf_new_from_file(path.get(), nullptr);
 | |
|         if (icon) {
 | |
|           gtk_icon_theme_add_builtin_icon(iconName.get(),
 | |
|                                           gdk_pixbuf_get_height(icon), icon);
 | |
|           g_object_unref(icon);
 | |
|           foundIcon = true;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // leave the default icon intact if no matching icons were found
 | |
|   if (foundIcon) {
 | |
|     gtk_window_set_icon_name(GTK_WINDOW(mShell), iconName.get());
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* TODO(bug 1655924): gdk_window_get_origin is can block waiting for the X
 | |
|    server for a long time, we would like to use the implementation below
 | |
|    instead. However, removing the synchronous x server queries causes a race
 | |
|    condition to surface, causing issues such as bug 1652743 and bug 1653711.
 | |
| 
 | |
| 
 | |
|    This code can be used instead of gdk_window_get_origin() but it cuases
 | |
|    such issues:
 | |
| 
 | |
|     *aX = 0;
 | |
|     *aY = 0;
 | |
|     if (!aWindow) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     GdkWindow* current = aWindow;
 | |
|     while (GdkWindow* parent = gdk_window_get_parent(current)) {
 | |
|       if (parent == current) {
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       int x = 0;
 | |
|       int y = 0;
 | |
|       gdk_window_get_position(current, &x, &y);
 | |
|       *aX += x;
 | |
|       *aY += y;
 | |
| 
 | |
|       current = parent;
 | |
|     }
 | |
| */
 | |
| LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
 | |
|   // Don't use gdk_window_get_origin() on wl_subsurface Wayland popups
 | |
|   // https://gitlab.gnome.org/GNOME/gtk/-/issues/5287
 | |
|   if (IsWaylandPopup() && !mPopupUseMoveToRect) {
 | |
|     return mBounds.TopLeft();
 | |
|   }
 | |
|   nsIntPoint origin(0, 0);
 | |
|   if (mGdkWindow) {
 | |
|     gdk_window_get_origin(mGdkWindow, &origin.x.value, &origin.y.value);
 | |
|   }
 | |
|   return GdkPointToDevicePixels({origin.x, origin.y});
 | |
| }
 | |
| 
 | |
| void nsWindow::CaptureRollupEvents(bool aDoCapture) {
 | |
|   LOG("CaptureRollupEvents(%d)\n", aDoCapture);
 | |
|   if (mIsDestroyed) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   static constexpr auto kCaptureEventsMask =
 | |
|       GdkEventMask(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
 | |
|                    GDK_POINTER_MOTION_MASK | GDK_TOUCH_MASK);
 | |
| 
 | |
|   static bool sSystemNeedsPointerGrab = [&] {
 | |
|     if (GdkIsWaylandDisplay()) {
 | |
|       return false;
 | |
|     }
 | |
|     // We only need to grab the pointer for X servers that move the focus with
 | |
|     // the pointer (like twm, sawfish...). Since we roll up popups on focus out,
 | |
|     // not grabbing the pointer triggers rollup when the mouse enters the popup
 | |
|     // and leaves the main window, see bug 1807482.
 | |
|     //
 | |
|     // FVWM is also affected but less severely: the pointer can enter the
 | |
|     // popup, but if it briefly moves out of the popup and over the main window
 | |
|     // then we see a focus change and roll up the popup.
 | |
|     //
 | |
|     // We don't do it for most common desktops, if only because it causes X11
 | |
|     // crashes like bug 1607713.
 | |
|     const auto& desktop = GetDesktopEnvironmentIdentifier();
 | |
|     return desktop.EqualsLiteral("twm") || desktop.EqualsLiteral("sawfish") ||
 | |
|            StringBeginsWith(desktop, "fvwm"_ns);
 | |
|   }();
 | |
| 
 | |
|   const bool grabPointer = [] {
 | |
|     switch (StaticPrefs::widget_gtk_grab_pointer()) {
 | |
|       case 0:
 | |
|         return false;
 | |
|       case 1:
 | |
|         return true;
 | |
|       default:
 | |
|         return sSystemNeedsPointerGrab;
 | |
|     }
 | |
|   }();
 | |
| 
 | |
|   if (!grabPointer) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mNeedsToRetryCapturingMouse = false;
 | |
|   if (aDoCapture) {
 | |
|     if (mIsDragPopup || DragInProgress()) {
 | |
|       // Don't add a grab if a drag is in progress, or if the widget is a drag
 | |
|       // feedback popup. (panels with type="drag").
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (!mHasMappedToplevel) {
 | |
|       // On X, capturing an unmapped window is pointless (returns
 | |
|       // GDK_GRAB_NOT_VIEWABLE). Avoid the X server round-trip and just retry
 | |
|       // when we're mapped.
 | |
|       mNeedsToRetryCapturingMouse = true;
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // gdk_pointer_grab is deprecated in favor of gdk_device_grab, but that
 | |
|     // causes a strange bug on X11, most obviously with nested popup menus:
 | |
|     // we somehow take the pointer position relative to the top left of the
 | |
|     // outer menu and use it as if it were relative to the submenu.  This
 | |
|     // doesn't happen with gdk_pointer_grab even though the code is very
 | |
|     // similar.  See the video attached to bug 1750721 for a demonstration,
 | |
|     // and see also bug 1820542 for when the same thing happened with
 | |
|     // another attempt to use gdk_device_grab.
 | |
|     //
 | |
|     // (gdk_device_grab is deprecated in favor of gdk_seat_grab as of 3.20,
 | |
|     // but at the time of this writing we still support older versions of
 | |
|     // GTK 3.)
 | |
|     GdkGrabStatus status =
 | |
|         gdk_pointer_grab(GetToplevelGdkWindow(),
 | |
|                          /* owner_events = */ true, kCaptureEventsMask,
 | |
|                          /* confine_to = */ nullptr,
 | |
|                          /* cursor = */ nullptr, GetLastUserInputTime());
 | |
|     Unused << NS_WARN_IF(status != GDK_GRAB_SUCCESS);
 | |
|     LOG(" > pointer grab with status %d", int(status));
 | |
|     gtk_grab_add(GTK_WIDGET(mContainer));
 | |
|   } else {
 | |
|     // There may not have been a drag in process when aDoCapture was set,
 | |
|     // so make sure to remove any added grab.  This is a no-op if the grab
 | |
|     // was not added to this widget.
 | |
|     gtk_grab_remove(GTK_WIDGET(mContainer));
 | |
|     gdk_pointer_ungrab(GetLastUserInputTime());
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult nsWindow::GetAttention(int32_t aCycleCount) {
 | |
|   LOG("nsWindow::GetAttention");
 | |
| 
 | |
|   GtkWidget* top_window = GetToplevelWidget();
 | |
|   GtkWidget* top_focused_window =
 | |
|       gFocusWindow ? gFocusWindow->GetToplevelWidget() : nullptr;
 | |
| 
 | |
|   // Don't get attention if the window is focused anyway.
 | |
|   if (top_window && (gtk_widget_get_visible(top_window)) &&
 | |
|       top_window != top_focused_window) {
 | |
|     SetUrgencyHint(top_window, true);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| bool nsWindow::HasPendingInputEvent() {
 | |
|   // This sucks, but gtk/gdk has no way to answer the question we want while
 | |
|   // excluding paint events, and there's no X API that will let us peek
 | |
|   // without blocking or removing.  To prevent event reordering, peek
 | |
|   // anything except expose events.  Reordering expose and others should be
 | |
|   // ok, hopefully.
 | |
|   bool haveEvent = false;
 | |
| #ifdef MOZ_X11
 | |
|   XEvent ev;
 | |
|   if (GdkIsX11Display()) {
 | |
|     Display* display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
 | |
|     haveEvent = XCheckMaskEvent(
 | |
|         display,
 | |
|         KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
 | |
|             EnterWindowMask | LeaveWindowMask | PointerMotionMask |
 | |
|             PointerMotionHintMask | Button1MotionMask | Button2MotionMask |
 | |
|             Button3MotionMask | Button4MotionMask | Button5MotionMask |
 | |
|             ButtonMotionMask | KeymapStateMask | VisibilityChangeMask |
 | |
|             StructureNotifyMask | ResizeRedirectMask | SubstructureNotifyMask |
 | |
|             SubstructureRedirectMask | FocusChangeMask | PropertyChangeMask |
 | |
|             ColormapChangeMask | OwnerGrabButtonMask,
 | |
|         &ev);
 | |
|     if (haveEvent) {
 | |
|       XPutBackEvent(display, &ev);
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
|   return haveEvent;
 | |
| }
 | |
| 
 | |
| #ifdef cairo_copy_clip_rectangle_list
 | |
| #  error "Looks like we're including Mozilla's cairo instead of system cairo"
 | |
| #endif
 | |
| static bool ExtractExposeRegion(LayoutDeviceIntRegion& aRegion, cairo_t* cr) {
 | |
|   cairo_rectangle_list_t* rects = cairo_copy_clip_rectangle_list(cr);
 | |
|   if (rects->status != CAIRO_STATUS_SUCCESS) {
 | |
|     NS_WARNING("Failed to obtain cairo rectangle list.");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   for (int i = 0; i < rects->num_rectangles; i++) {
 | |
|     const cairo_rectangle_t& r = rects->rectangles[i];
 | |
|     aRegion.Or(aRegion,
 | |
|                LayoutDeviceIntRect::Truncate((float)r.x, (float)r.y,
 | |
|                                              (float)r.width, (float)r.height));
 | |
|   }
 | |
| 
 | |
|   cairo_rectangle_list_destroy(rects);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| #ifdef MOZ_WAYLAND
 | |
| void nsWindow::CreateCompositorVsyncDispatcher() {
 | |
|   LOG_VSYNC("nsWindow::CreateCompositorVsyncDispatcher()");
 | |
|   if (!mWaylandVsyncSource) {
 | |
|     LOG_VSYNC(
 | |
|         "  mWaylandVsyncSource is missing, create "
 | |
|         "nsBaseWidget::CompositorVsyncDispatcher()");
 | |
|     nsBaseWidget::CreateCompositorVsyncDispatcher();
 | |
|     return;
 | |
|   }
 | |
|   if (!mCompositorVsyncDispatcherLock) {
 | |
|     mCompositorVsyncDispatcherLock =
 | |
|         MakeUnique<Mutex>("mCompositorVsyncDispatcherLock");
 | |
|   }
 | |
|   MutexAutoLock lock(*mCompositorVsyncDispatcherLock);
 | |
|   if (!mCompositorVsyncDispatcher) {
 | |
|     LOG_VSYNC("  create CompositorVsyncDispatcher()");
 | |
|     mCompositorVsyncDispatcher =
 | |
|         new CompositorVsyncDispatcher(mWaylandVsyncDispatcher);
 | |
|   }
 | |
| }
 | |
| #endif
 | |
| 
 | |
| gboolean nsWindow::OnExposeEvent(cairo_t* cr) {
 | |
|   // This might destroy us.
 | |
|   NotifyOcclusionState(OcclusionState::VISIBLE);
 | |
|   if (mIsDestroyed) {
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   // Send any pending resize events so that layout can update.
 | |
|   // May run event loop and destroy us.
 | |
|   MaybeDispatchResized();
 | |
|   if (mIsDestroyed) {
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   // Windows that are not visible will be painted after they become visible.
 | |
|   if (!mGdkWindow || !mHasMappedToplevel) {
 | |
|     return FALSE;
 | |
|   }
 | |
| #ifdef MOZ_WAYLAND
 | |
|   if (GdkIsWaylandDisplay() && !moz_container_wayland_can_draw(mContainer)) {
 | |
|     return FALSE;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   nsIWidgetListener* listener = GetListener();
 | |
|   if (!listener) return FALSE;
 | |
| 
 | |
|   LOG("nsWindow::OnExposeEvent GdkWindow [%p] XID [0x%lx]", mGdkWindow,
 | |
|       GetX11Window());
 | |
| 
 | |
|   LayoutDeviceIntRegion exposeRegion;
 | |
|   if (!ExtractExposeRegion(exposeRegion, cr)) {
 | |
|     LOG("  no rects, quit");
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   gint scale = GdkCeiledScaleFactor();
 | |
|   LayoutDeviceIntRegion region = exposeRegion;
 | |
|   region.ScaleRoundOut(scale, scale);
 | |
| 
 | |
|   WindowRenderer* renderer = GetWindowRenderer();
 | |
|   WebRenderLayerManager* layerManager = renderer->AsWebRender();
 | |
|   KnowsCompositor* knowsCompositor = renderer->AsKnowsCompositor();
 | |
| 
 | |
|   if (knowsCompositor && layerManager && mCompositorSession) {
 | |
|     if (!mConfiguredClearColor && !IsPopup()) {
 | |
|       layerManager->WrBridge()->SendSetDefaultClearColor(LookAndFeel::Color(
 | |
|           LookAndFeel::ColorID::Window, PreferenceSheet::ColorSchemeForChrome(),
 | |
|           LookAndFeel::UseStandins::No));
 | |
|       mConfiguredClearColor = true;
 | |
|     }
 | |
| 
 | |
|     // We need to paint to the screen even if nothing changed, since if we
 | |
|     // don't have a compositing window manager, our pixels could be stale.
 | |
|     layerManager->SetNeedsComposite(true);
 | |
|     layerManager->SendInvalidRegion(region.ToUnknownRegion());
 | |
|   }
 | |
| 
 | |
|   RefPtr<nsWindow> strongThis(this);
 | |
| 
 | |
|   // Dispatch WillPaintWindow notification to allow scripts etc. to run
 | |
|   // before we paint
 | |
|   {
 | |
|     listener->WillPaintWindow(this);
 | |
| 
 | |
|     // If the window has been destroyed during the will paint notification,
 | |
|     // there is nothing left to do.
 | |
|     if (!mGdkWindow) return TRUE;
 | |
| 
 | |
|     // Re-get the listener since the will paint notification might have
 | |
|     // killed it.
 | |
|     listener = GetListener();
 | |
|     if (!listener) return FALSE;
 | |
|   }
 | |
| 
 | |
|   if (knowsCompositor && layerManager && layerManager->NeedsComposite()) {
 | |
|     layerManager->ScheduleComposite(wr::RenderReasons::WIDGET);
 | |
|     layerManager->SetNeedsComposite(false);
 | |
|   }
 | |
| 
 | |
|   // Our bounds may have changed after calling WillPaintWindow.  Clip
 | |
|   // to the new bounds here.  The region is relative to this
 | |
|   // window.
 | |
|   region.And(region, LayoutDeviceIntRect(0, 0, mBounds.width, mBounds.height));
 | |
| 
 | |
|   bool shaped = false;
 | |
|   if (TransparencyMode::Transparent == GetTransparencyMode()) {
 | |
|     auto* window = static_cast<nsWindow*>(GetTopLevelWidget());
 | |
|     if (mTransparencyBitmapForTitlebar) {
 | |
|       if (mSizeMode == nsSizeMode_Normal) {
 | |
|         window->UpdateTitlebarTransparencyBitmap();
 | |
|       } else {
 | |
|         window->ClearTransparencyBitmap();
 | |
|       }
 | |
|     } else {
 | |
|       if (mHasAlphaVisual) {
 | |
|         // Remove possible shape mask from when window manger was not
 | |
|         // previously compositing.
 | |
|         window->ClearTransparencyBitmap();
 | |
|       } else {
 | |
|         shaped = true;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (region.IsEmpty()) {
 | |
|     return TRUE;
 | |
|   }
 | |
| 
 | |
|   // If this widget uses OMTC...
 | |
|   if (renderer->GetBackendType() == LayersBackend::LAYERS_WR) {
 | |
|     listener->PaintWindow(this, region);
 | |
| 
 | |
|     // Re-get the listener since the will paint notification might have
 | |
|     // killed it.
 | |
|     listener = GetListener();
 | |
|     if (!listener) return TRUE;
 | |
| 
 | |
|     listener->DidPaintWindow();
 | |
|     return TRUE;
 | |
|   }
 | |
| 
 | |
|   BufferMode layerBuffering = BufferMode::BUFFERED;
 | |
|   RefPtr<DrawTarget> dt = StartRemoteDrawingInRegion(region, &layerBuffering);
 | |
|   if (!dt || !dt->IsValid()) {
 | |
|     return FALSE;
 | |
|   }
 | |
|   Maybe<gfxContext> ctx;
 | |
|   IntRect boundsRect = region.GetBounds().ToUnknownRect();
 | |
|   IntPoint offset(0, 0);
 | |
|   if (dt->GetSize() == boundsRect.Size()) {
 | |
|     offset = boundsRect.TopLeft();
 | |
|     dt->SetTransform(Matrix::Translation(-offset));
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
|   if (shaped) {
 | |
|     // Collapse update area to the bounding box. This is so we only have to
 | |
|     // call UpdateTranslucentWindowAlpha once. After we have dropped
 | |
|     // support for non-Thebes graphics, UpdateTranslucentWindowAlpha will be
 | |
|     // our private interface so we can rework things to avoid this.
 | |
|     dt->PushClipRect(Rect(boundsRect));
 | |
| 
 | |
|     // The double buffering is done here to extract the shape mask.
 | |
|     // (The shape mask won't be necessary when a visual with an alpha
 | |
|     // channel is used on compositing window managers.)
 | |
|     layerBuffering = BufferMode::BUFFER_NONE;
 | |
|     RefPtr<DrawTarget> destDT =
 | |
|         dt->CreateSimilarDrawTarget(boundsRect.Size(), SurfaceFormat::B8G8R8A8);
 | |
|     if (!destDT || !destDT->IsValid()) {
 | |
|       return FALSE;
 | |
|     }
 | |
|     destDT->SetTransform(Matrix::Translation(-boundsRect.TopLeft()));
 | |
|     ctx.emplace(destDT, /* aPreserveTransform */ true);
 | |
|   } else {
 | |
|     gfxUtils::ClipToRegion(dt, region.ToUnknownRegion());
 | |
|     ctx.emplace(dt, /* aPreserveTransform */ true);
 | |
|   }
 | |
| 
 | |
| #  if 0
 | |
|     // NOTE: Paint flashing region would be wrong for cairo, since
 | |
|     // cairo inflates the update region, etc.  So don't paint flash
 | |
|     // for cairo.
 | |
| #    ifdef DEBUG
 | |
|     // XXX aEvent->region may refer to a newly-invalid area.  FIXME
 | |
|     if (0 && WANT_PAINT_FLASHING && gtk_widget_get_window(aEvent))
 | |
|         gdk_window_flash(mGdkWindow, 1, 100, aEvent->region);
 | |
| #    endif
 | |
| #  endif
 | |
| 
 | |
| #endif  // MOZ_X11
 | |
| 
 | |
|   bool painted = false;
 | |
|   {
 | |
|     if (renderer->GetBackendType() == LayersBackend::LAYERS_NONE) {
 | |
|       if (GetTransparencyMode() == TransparencyMode::Transparent &&
 | |
|           layerBuffering == BufferMode::BUFFER_NONE && mHasAlphaVisual) {
 | |
|         // If our draw target is unbuffered and we use an alpha channel,
 | |
|         // clear the image beforehand to ensure we don't get artifacts from a
 | |
|         // reused SHM image. See bug 1258086.
 | |
|         dt->ClearRect(Rect(boundsRect));
 | |
|       }
 | |
|       AutoLayerManagerSetup setupLayerManager(
 | |
|           this, ctx.isNothing() ? nullptr : &ctx.ref(), layerBuffering);
 | |
|       painted = listener->PaintWindow(this, region);
 | |
| 
 | |
|       // Re-get the listener since the will paint notification might have
 | |
|       // killed it.
 | |
|       listener = GetListener();
 | |
|       if (!listener) return TRUE;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
|   // PaintWindow can Destroy us (bug 378273), avoid doing any paint
 | |
|   // operations below if that happened - it will lead to XError and exit().
 | |
|   if (shaped) {
 | |
|     if (MOZ_LIKELY(!mIsDestroyed)) {
 | |
|       if (painted) {
 | |
|         RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
 | |
| 
 | |
|         UpdateAlpha(surf, boundsRect);
 | |
| 
 | |
|         dt->DrawSurface(surf, Rect(boundsRect),
 | |
|                         Rect(0, 0, boundsRect.width, boundsRect.height),
 | |
|                         DrawSurfaceOptions(SamplingFilter::POINT),
 | |
|                         DrawOptions(1.0f, CompositionOp::OP_SOURCE));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   ctx.reset();
 | |
|   dt->PopClip();
 | |
| 
 | |
| #endif  // MOZ_X11
 | |
| 
 | |
|   EndRemoteDrawingInRegion(dt, region);
 | |
| 
 | |
|   listener->DidPaintWindow();
 | |
| 
 | |
|   // Synchronously flush any new dirty areas
 | |
|   cairo_region_t* dirtyArea = gdk_window_get_update_area(mGdkWindow);
 | |
| 
 | |
|   if (dirtyArea) {
 | |
|     gdk_window_invalidate_region(mGdkWindow, dirtyArea, false);
 | |
|     cairo_region_destroy(dirtyArea);
 | |
|     gdk_window_process_updates(mGdkWindow, false);
 | |
|   }
 | |
| 
 | |
|   // check the return value!
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| void nsWindow::UpdateAlpha(SourceSurface* aSourceSurface,
 | |
|                            nsIntRect aBoundsRect) {
 | |
|   // We need to create our own buffer to force the stride to match the
 | |
|   // expected stride.
 | |
|   int32_t stride =
 | |
|       GetAlignedStride<4>(aBoundsRect.width, BytesPerPixel(SurfaceFormat::A8));
 | |
|   if (stride == 0) {
 | |
|     return;
 | |
|   }
 | |
|   int32_t bufferSize = stride * aBoundsRect.height;
 | |
|   auto imageBuffer = MakeUniqueFallible<uint8_t[]>(bufferSize);
 | |
|   {
 | |
|     RefPtr<DrawTarget> drawTarget = gfxPlatform::CreateDrawTargetForData(
 | |
|         imageBuffer.get(), aBoundsRect.Size(), stride, SurfaceFormat::A8);
 | |
| 
 | |
|     if (drawTarget) {
 | |
|       drawTarget->DrawSurface(aSourceSurface,
 | |
|                               Rect(0, 0, aBoundsRect.width, aBoundsRect.height),
 | |
|                               Rect(0, 0, aSourceSurface->GetSize().width,
 | |
|                                    aSourceSurface->GetSize().height),
 | |
|                               DrawSurfaceOptions(SamplingFilter::POINT),
 | |
|                               DrawOptions(1.0f, CompositionOp::OP_SOURCE));
 | |
|     }
 | |
|   }
 | |
|   UpdateTranslucentWindowAlphaInternal(aBoundsRect, imageBuffer.get(), stride);
 | |
| }
 | |
| 
 | |
| gboolean nsWindow::OnConfigureEvent(GtkWidget* aWidget,
 | |
|                                     GdkEventConfigure* aEvent) {
 | |
|   // These events are only received on toplevel windows.
 | |
|   //
 | |
|   // GDK ensures that the coordinates are the client window top-left wrt the
 | |
|   // root window.
 | |
|   //
 | |
|   //   GDK calculates the cordinates for real ConfigureNotify events on
 | |
|   //   managed windows (that would normally be relative to the parent
 | |
|   //   window).
 | |
|   //
 | |
|   //   Synthetic ConfigureNotify events are from the window manager and
 | |
|   //   already relative to the root window.  GDK creates all X windows with
 | |
|   //   border_width = 0, so synthetic events also indicate the top-left of
 | |
|   //   the client window.
 | |
|   //
 | |
|   //   Override-redirect windows are children of the root window so parent
 | |
|   //   coordinates are root coordinates.
 | |
| 
 | |
| #ifdef MOZ_LOGGING
 | |
|   int scale = mGdkWindow ? gdk_window_get_scale_factor(mGdkWindow) : -1;
 | |
|   LOG("configure event %d,%d -> %d x %d direct mGdkWindow scale %d (scaled "
 | |
|       "size %d x %d)\n",
 | |
|       aEvent->x, aEvent->y, aEvent->width, aEvent->height, scale,
 | |
|       aEvent->width * scale, aEvent->height * scale);
 | |
| #endif
 | |
| 
 | |
|   if (mPendingConfigures > 0) {
 | |
|     mPendingConfigures--;
 | |
|   }
 | |
| 
 | |
|   // Don't fire configure event for scale changes, we handle that
 | |
|   // OnScaleChanged event. Skip that for toplevel windows only.
 | |
|   if (mGdkWindow && IsTopLevelWindowType()) {
 | |
|     if (mCeiledScaleFactor != gdk_window_get_scale_factor(mGdkWindow)) {
 | |
|       LOG("  scale factor changed to %d,return early",
 | |
|           gdk_window_get_scale_factor(mGdkWindow));
 | |
|       return FALSE;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   LayoutDeviceIntRect screenBounds = GetScreenBounds();
 | |
| 
 | |
|   if (IsTopLevelWindowType()) {
 | |
|     // This check avoids unwanted rollup on spurious configure events from
 | |
|     // Cygwin/X (bug 672103).
 | |
|     if (mBounds.x != screenBounds.x || mBounds.y != screenBounds.y) {
 | |
|       RollupAllMenus();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   NS_ASSERTION(GTK_IS_WINDOW(aWidget),
 | |
|                "Configure event on widget that is not a GtkWindow");
 | |
|   if (mGdkWindow &&
 | |
|       gtk_window_get_window_type(GTK_WINDOW(aWidget)) == GTK_WINDOW_POPUP) {
 | |
|     // Override-redirect window
 | |
|     //
 | |
|     // These windows should not be moved by the window manager, and so any
 | |
|     // change in position is a result of our direction.  mBounds has
 | |
|     // already been set in std::move() or Resize(), and that is more
 | |
|     // up-to-date than the position in the ConfigureNotify event if the
 | |
|     // event is from an earlier window move.
 | |
|     //
 | |
|     // Skipping the WindowMoved call saves context menus from an infinite
 | |
|     // loop when nsXULPopupManager::PopupMoved moves the window to the new
 | |
|     // position and nsMenuPopupFrame::SetPopupPosition adds
 | |
|     // offsetForContextMenu on each iteration.
 | |
| 
 | |
|     // Our back buffer might have been invalidated while we drew the last
 | |
|     // frame, and its contents might be incorrect. See bug 1280653 comment 7
 | |
|     // and comment 10. Specifically we must ensure we recomposite the frame
 | |
|     // as soon as possible to avoid the corrupted frame being displayed.
 | |
|     GetWindowRenderer()->FlushRendering(wr::RenderReasons::WIDGET);
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   mBounds.MoveTo(screenBounds.TopLeft());
 | |
|   RecomputeClientOffset(/* aNotify = */ false);
 | |
| 
 | |
|   // XXX mozilla will invalidate the entire window after this move
 | |
|   // complete.  wtf?
 | |
|   NotifyWindowMoved(mBounds.x, mBounds.y);
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| void nsWindow::OnMap() {
 | |
|   LOG("nsWindow::OnMap");
 | |
|   // Gtk mapped widget to screen. Configure underlying GdkWindow properly
 | |
|   // as our rendering target.
 | |
|   // This call means we have X11 (or Wayland) window we can render to by GL
 | |
|   // so we need to notify compositor about it.
 | |
|   mIsMapped = true;
 | |
|   ConfigureGdkWindow();
 | |
| }
 | |
| 
 | |
| void nsWindow::OnUnmap() {
 | |
|   LOG("nsWindow::OnUnmap");
 | |
| 
 | |
|   // Mark window as unmapped. It can be still used for rendering on X11
 | |
|   // untill OnUnrealize is called.
 | |
|   mIsMapped = false;
 | |
| 
 | |
|   if (mSourceDragContext) {
 | |
|     static auto sGtkDragCancel =
 | |
|         (void (*)(GdkDragContext*))dlsym(RTLD_DEFAULT, "gtk_drag_cancel");
 | |
|     if (sGtkDragCancel) {
 | |
|       sGtkDragCancel(mSourceDragContext);
 | |
|       mSourceDragContext = nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_WAYLAND
 | |
|   // wl_surface owned by mContainer is going to be deleted.
 | |
|   // Make sure we don't paint to it on Wayland.
 | |
|   if (GdkIsWaylandDisplay()) {
 | |
|     if (mCompositorWidgetDelegate) {
 | |
|       mCompositorWidgetDelegate->DisableRendering();
 | |
|     }
 | |
|     if (moz_container_wayland_has_egl_window(mContainer)) {
 | |
|       // Widget is backed by OpenGL EGLSurface created over wl_surface
 | |
|       // owned by mContainer.
 | |
|       // RenderCompositorEGL::Resume() deletes recent EGLSurface based on
 | |
|       // wl_surface owned by mContainer and creates a new fallback EGLSurface.
 | |
|       // Then we can delete wl_surface in moz_container_wayland_unmap().
 | |
|       // We don't want to pause compositor as it may lead to whole
 | |
|       // browser freeze (Bug 1777664).
 | |
|       if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) {
 | |
|         remoteRenderer->SendResume();
 | |
|       }
 | |
|     }
 | |
|     if (GdkIsWaylandDisplay()) {
 | |
|       moz_container_wayland_unmap(GTK_WIDGET(mContainer));
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
|   moz_container_unmap(GTK_WIDGET(mContainer));
 | |
| }
 | |
| 
 | |
| void nsWindow::OnUnrealize() {
 | |
|   // The GdkWindows are about to be destroyed (but not deleted), so remove
 | |
|   // their references back to their container widget while the GdkWindow
 | |
|   // hierarchy is still available.
 | |
|   // This call means we *don't* have X11 (or Wayland) window we can render to.
 | |
|   LOG("nsWindow::OnUnrealize GdkWindow %p", mGdkWindow);
 | |
|   ReleaseGdkWindow();
 | |
| }
 | |
| 
 | |
| void nsWindow::OnSizeAllocate(GtkAllocation* aAllocation) {
 | |
|   LOG("nsWindow::OnSizeAllocate %d,%d -> %d x %d\n", aAllocation->x,
 | |
|       aAllocation->y, aAllocation->width, aAllocation->height);
 | |
| 
 | |
|   // Client offset are updated by _NET_FRAME_EXTENTS on X11 when system titlebar
 | |
|   // is enabled. In either cases (Wayland or system titlebar is off on X11)
 | |
|   // we don't get _NET_FRAME_EXTENTS X11 property notification so we derive
 | |
|   // it from mContainer position.
 | |
|   RecomputeClientOffset(/* aNotify = */ true);
 | |
| 
 | |
|   mHasReceivedSizeAllocate = true;
 | |
| 
 | |
|   LayoutDeviceIntSize size = GdkRectToDevicePixels(*aAllocation).Size();
 | |
| 
 | |
|   // Sometimes the window manager gives us garbage sizes (way past the maximum
 | |
|   // texture size) causing crashes if we don't enforce size constraints again
 | |
|   // here.
 | |
|   ConstrainSize(&size.width, &size.height);
 | |
| 
 | |
|   if (mBounds.Size() == size) {
 | |
|     LOG("  Already the same size");
 | |
|     // mBounds was set at Create() or Resize().
 | |
|     if (mNeedsDispatchSize != LayoutDeviceIntSize(-1, -1)) {
 | |
|       LOG("  No longer needs to dispatch %dx%d", mNeedsDispatchSize.width,
 | |
|           mNeedsDispatchSize.height);
 | |
|       mNeedsDispatchSize = LayoutDeviceIntSize(-1, -1);
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Invalidate the new part of the window now for the pending paint to
 | |
|   // minimize background flashes (GDK does not do this for external resizes
 | |
|   // of toplevels.)
 | |
|   if (mGdkWindow) {
 | |
|     if (mBounds.width < size.width) {
 | |
|       GdkRectangle rect = DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect(
 | |
|           mBounds.width, 0, size.width - mBounds.width, size.height));
 | |
|       gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
 | |
|     }
 | |
|     if (mBounds.height < size.height) {
 | |
|       GdkRectangle rect = DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect(
 | |
|           0, mBounds.height, size.width, size.height - mBounds.height));
 | |
|       gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If we update mBounds here, then inner/outerHeight are out of sync until
 | |
|   // we call WindowResized.
 | |
|   mNeedsDispatchSize = size;
 | |
| 
 | |
|   // Gecko permits running nested event loops during processing of events,
 | |
|   // GtkWindow callers of gtk_widget_size_allocate expect the signal
 | |
|   // handlers to return sometime in the near future.
 | |
|   NS_DispatchToCurrentThread(NewRunnableMethod(
 | |
|       "nsWindow::MaybeDispatchResized", this, &nsWindow::MaybeDispatchResized));
 | |
| }
 | |
| 
 | |
| void nsWindow::OnDeleteEvent() {
 | |
|   if (mWidgetListener) mWidgetListener->RequestWindowClose(this);
 | |
| }
 | |
| 
 | |
| void nsWindow::OnEnterNotifyEvent(GdkEventCrossing* aEvent) {
 | |
|   LOG("enter notify (win=%p, sub=%p): %f, %f mode %d, detail %d\n",
 | |
|       aEvent->window, aEvent->subwindow, aEvent->x, aEvent->y, aEvent->mode,
 | |
|       aEvent->detail);
 | |
|   // This skips NotifyVirtual and NotifyNonlinearVirtual enter notify events
 | |
|   // when the pointer enters a child window.  If the destination window is a
 | |
|   // Gecko window then we'll catch the corresponding event on that window,
 | |
|   // but we won't notice when the pointer directly enters a foreign (plugin)
 | |
|   // child window without passing over a visible portion of a Gecko window.
 | |
|   if (aEvent->subwindow) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Check before checking for ungrab as the button state may have
 | |
|   // changed while a non-Gecko ancestor window had a pointer grab.
 | |
|   DispatchMissedButtonReleases(aEvent);
 | |
| 
 | |
|   WidgetMouseEvent event(true, eMouseEnterIntoWidget, this,
 | |
|                          WidgetMouseEvent::eReal);
 | |
| 
 | |
|   event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
 | |
|   event.AssignEventTime(GetWidgetEventTime(aEvent->time));
 | |
| 
 | |
|   LOG("OnEnterNotify");
 | |
| 
 | |
|   DispatchInputEvent(&event);
 | |
| }
 | |
| 
 | |
| // Some window managers send a bogus top-level leave-notify event on every
 | |
| // click. That confuses our event handling code in ways that can break websites,
 | |
| // see bug 1805939 for details.
 | |
| //
 | |
| // Make sure to only check this on bogus environments, since for environments
 | |
| // with CSD, gdk_device_get_window_at_position could return the window even when
 | |
| // the pointer is in the decoration area.
 | |
| static bool IsBogusLeaveNotifyEvent(GdkWindow* aWindow,
 | |
|                                     GdkEventCrossing* aEvent) {
 | |
|   static bool sBogusWm = [] {
 | |
|     if (GdkIsWaylandDisplay()) {
 | |
|       return false;
 | |
|     }
 | |
|     const auto& desktopEnv = GetDesktopEnvironmentIdentifier();
 | |
|     return desktopEnv.EqualsLiteral("fluxbox") ||   // Bug 1805939 comment 0.
 | |
|            desktopEnv.EqualsLiteral("blackbox") ||  // Bug 1805939 comment 32.
 | |
|            desktopEnv.EqualsLiteral("lg3d") ||      // Bug 1820405.
 | |
|            desktopEnv.EqualsLiteral("pekwm") ||     // Bug 1822911.
 | |
|            StringBeginsWith(desktopEnv, "fvwm"_ns);
 | |
|   }();
 | |
| 
 | |
|   const bool shouldCheck = [] {
 | |
|     switch (StaticPrefs::widget_gtk_ignore_bogus_leave_notify()) {
 | |
|       case 0:
 | |
|         return false;
 | |
|       case 1:
 | |
|         return true;
 | |
|       default:
 | |
|         return sBogusWm;
 | |
|     }
 | |
|   }();
 | |
| 
 | |
|   if (!shouldCheck) {
 | |
|     return false;
 | |
|   }
 | |
|   GdkDevice* pointer = GdkGetPointer();
 | |
|   GdkWindow* winAtPt =
 | |
|       gdk_device_get_window_at_position(pointer, nullptr, nullptr);
 | |
|   if (!winAtPt) {
 | |
|     return false;
 | |
|   }
 | |
|   // We're still in the same top level window, ignore this leave notify event.
 | |
|   GdkWindow* topLevelAtPt = gdk_window_get_toplevel(winAtPt);
 | |
|   GdkWindow* topLevelWidget = gdk_window_get_toplevel(aWindow);
 | |
|   return topLevelAtPt == topLevelWidget;
 | |
| }
 | |
| 
 | |
| void nsWindow::OnLeaveNotifyEvent(GdkEventCrossing* aEvent) {
 | |
|   LOG("leave notify (win=%p, sub=%p): %f, %f mode %d, detail %d\n",
 | |
|       aEvent->window, aEvent->subwindow, aEvent->x, aEvent->y, aEvent->mode,
 | |
|       aEvent->detail);
 | |
| 
 | |
|   // This ignores NotifyVirtual and NotifyNonlinearVirtual leave notify
 | |
|   // events when the pointer leaves a child window.  If the destination
 | |
|   // window is a Gecko window then we'll catch the corresponding event on
 | |
|   // that window.
 | |
|   //
 | |
|   // XXXkt However, we will miss toplevel exits when the pointer directly
 | |
|   // leaves a foreign (plugin) child window without passing over a visible
 | |
|   // portion of a Gecko window.
 | |
|   if (aEvent->subwindow) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // The filter out for subwindows should make sure that this is targeted to
 | |
|   // this nsWindow.
 | |
|   const bool leavingTopLevel = IsTopLevelWindowType();
 | |
|   if (leavingTopLevel && IsBogusLeaveNotifyEvent(mGdkWindow, aEvent)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   WidgetMouseEvent event(true, eMouseExitFromWidget, this,
 | |
|                          WidgetMouseEvent::eReal);
 | |
| 
 | |
|   event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
 | |
|   event.AssignEventTime(GetWidgetEventTime(aEvent->time));
 | |
|   event.mExitFrom = Some(leavingTopLevel ? WidgetMouseEvent::ePlatformTopLevel
 | |
|                                          : WidgetMouseEvent::ePlatformChild);
 | |
| 
 | |
|   LOG("OnLeaveNotify");
 | |
| 
 | |
|   DispatchInputEvent(&event);
 | |
| }
 | |
| 
 | |
| Maybe<GdkWindowEdge> nsWindow::CheckResizerEdge(
 | |
|     const LayoutDeviceIntPoint& aPoint) {
 | |
|   const bool canResize = [&] {
 | |
|     // Don't allow resizing maximized/fullscreen windows.
 | |
|     if (mSizeMode != nsSizeMode_Normal) {
 | |
|       return false;
 | |
|     }
 | |
|     if (mIsPIPWindow) {
 | |
|       // Note that since we do show resizers on left/right sides on PIP windows,
 | |
|       // we still want the resizers there, even when tiled.
 | |
|       return true;
 | |
|     }
 | |
|     if (!mDrawInTitlebar) {
 | |
|       return false;
 | |
|     }
 | |
|     // On KDE, allow for 1 extra pixel at the top of regular windows when
 | |
|     // drawing to the titlebar. This matches the native titlebar behavior on
 | |
|     // that environment. See bug 1813554.
 | |
|     //
 | |
|     // Don't do that on GNOME (see bug 1822764). If we wanted to do this on
 | |
|     // GNOME we'd need an extra check for mIsTiled, since the window is "stuck"
 | |
|     // to the top and bottom.
 | |
|     //
 | |
|     // Other DEs are untested.
 | |
|     return mDrawInTitlebar && IsKdeDesktopEnvironment();
 | |
|   }();
 | |
| 
 | |
|   if (!canResize) {
 | |
|     return Nothing();
 | |
|   }
 | |
| 
 | |
|   // If we're not in a PiP window, allow 1px resizer edge from the top edge,
 | |
|   // and nothing else.
 | |
|   // This is to allow resizes of tiled windows on KDE, see bug 1813554.
 | |
|   const int resizerHeight = (mIsPIPWindow ? 15 : 1) * GdkCeiledScaleFactor();
 | |
|   const int resizerWidth = resizerHeight * 4;
 | |
| 
 | |
|   const int topDist = aPoint.y;
 | |
|   const int leftDist = aPoint.x;
 | |
|   const int rightDist = mBounds.width - aPoint.x;
 | |
|   const int bottomDist = mBounds.height - aPoint.y;
 | |
| 
 | |
|   // We can't emulate resize of North/West edges on Wayland as we can't shift
 | |
|   // toplevel window.
 | |
|   bool waylandLimitedResize = mAspectRatio != 0.0f && GdkIsWaylandDisplay();
 | |
| 
 | |
|   if (topDist <= resizerHeight) {
 | |
|     if (rightDist <= resizerWidth) {
 | |
|       return Some(GDK_WINDOW_EDGE_NORTH_EAST);
 | |
|     }
 | |
|     if (leftDist <= resizerWidth) {
 | |
|       return Some(GDK_WINDOW_EDGE_NORTH_WEST);
 | |
|     }
 | |
|     return waylandLimitedResize ? Nothing() : Some(GDK_WINDOW_EDGE_NORTH);
 | |
|   }
 | |
| 
 | |
|   if (!mIsPIPWindow) {
 | |
|     return Nothing();
 | |
|   }
 | |
| 
 | |
|   if (bottomDist <= resizerHeight) {
 | |
|     if (leftDist <= resizerWidth) {
 | |
|       return Some(GDK_WINDOW_EDGE_SOUTH_WEST);
 | |
|     }
 | |
|     if (rightDist <= resizerWidth) {
 | |
|       return Some(GDK_WINDOW_EDGE_SOUTH_EAST);
 | |
|     }
 | |
|     return Some(GDK_WINDOW_EDGE_SOUTH);
 | |
|   }
 | |
| 
 | |
|   if (leftDist <= resizerHeight) {
 | |
|     if (topDist <= resizerWidth) {
 | |
|       return Some(GDK_WINDOW_EDGE_NORTH_WEST);
 | |
|     }
 | |
|     if (bottomDist <= resizerWidth) {
 | |
|       return Some(GDK_WINDOW_EDGE_SOUTH_WEST);
 | |
|     }
 | |
|     return waylandLimitedResize ? Nothing() : Some(GDK_WINDOW_EDGE_WEST);
 | |
|   }
 | |
| 
 | |
|   if (rightDist <= resizerHeight) {
 | |
|     if (topDist <= resizerWidth) {
 | |
|       return Some(GDK_WINDOW_EDGE_NORTH_EAST);
 | |
|     }
 | |
|     if (bottomDist <= resizerWidth) {
 | |
|       return Some(GDK_WINDOW_EDGE_SOUTH_EAST);
 | |
|     }
 | |
|     return Some(GDK_WINDOW_EDGE_EAST);
 | |
|   }
 | |
|   return Nothing();
 | |
| }
 | |
| 
 | |
| template <typename Event>
 | |
| static LayoutDeviceIntPoint GetRefPoint(nsWindow* aWindow, Event* aEvent) {
 | |
|   if (aEvent->window == aWindow->GetGdkWindow()) {
 | |
|     // we are the window that the event happened on so no need for expensive
 | |
|     // WidgetToScreenOffset
 | |
|     return aWindow->GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
 | |
|   }
 | |
|   // XXX we're never quite sure which GdkWindow the event came from due to our
 | |
|   // custom bubbling in scroll_event_cb(), so use ScreenToWidget to translate
 | |
|   // the screen root coordinates into coordinates relative to this widget.
 | |
|   return aWindow->GdkEventCoordsToDevicePixels(aEvent->x_root, aEvent->y_root) -
 | |
|          aWindow->WidgetToScreenOffset();
 | |
| }
 | |
| 
 | |
| void nsWindow::EmulateResizeDrag(GdkEventMotion* aEvent) {
 | |
|   auto newPoint = LayoutDeviceIntPoint::Floor(aEvent->x, aEvent->y);
 | |
|   LayoutDeviceIntPoint diff = newPoint - mLastResizePoint;
 | |
|   mLastResizePoint = newPoint;
 | |
| 
 | |
|   GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
 | |
|   LayoutDeviceIntSize newSize(size.width + diff.x, size.height + diff.y);
 | |
| 
 | |
|   if (mAspectResizer.value() == GTK_ORIENTATION_VERTICAL) {
 | |
|     newSize.width = int(newSize.height * mAspectRatio);
 | |
|   } else {  // GTK_ORIENTATION_HORIZONTAL
 | |
|     newSize.height = int(newSize.width / mAspectRatio);
 | |
|   }
 | |
|   LOG("  aspect ratio correction %d x %d aspect %f\n", newSize.width,
 | |
|       newSize.height, mAspectRatio);
 | |
|   gtk_window_resize(GTK_WINDOW(mShell), newSize.width, newSize.height);
 | |
| }
 | |
| 
 | |
| void nsWindow::OnMotionNotifyEvent(GdkEventMotion* aEvent) {
 | |
|   if (!mGdkWindow) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Emulate gdk_window_begin_resize_drag() for windows
 | |
|   // with fixed aspect ratio on Wayland.
 | |
|   if (mAspectResizer && mAspectRatio != 0.0f) {
 | |
|     EmulateResizeDrag(aEvent);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mWindowShouldStartDragging) {
 | |
|     mWindowShouldStartDragging = false;
 | |
|     // find the top-level window
 | |
|     GdkWindow* gdk_window = gdk_window_get_toplevel(mGdkWindow);
 | |
|     MOZ_ASSERT(gdk_window, "gdk_window_get_toplevel should not return null");
 | |
| 
 | |
|     bool canDrag = true;
 | |
| #ifdef MOZ_X11
 | |
|     if (GdkIsX11Display()) {
 | |
|       // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=789054
 | |
|       // To avoid crashes disable double-click on WM without _NET_WM_MOVERESIZE.
 | |
|       // See _should_perform_ewmh_drag() at gdkwindow-x11.c
 | |
|       GdkScreen* screen = gdk_window_get_screen(gdk_window);
 | |
|       GdkAtom atom = gdk_atom_intern("_NET_WM_MOVERESIZE", FALSE);
 | |
|       if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
 | |
|         canDrag = false;
 | |
|       }
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     if (canDrag) {
 | |
|       gdk_window_begin_move_drag(gdk_window, 1, aEvent->x_root, aEvent->y_root,
 | |
|                                  aEvent->time);
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mWidgetCursorLocked = false;
 | |
|   const auto refPoint = GetRefPoint(this, aEvent);
 | |
|   if (auto edge = CheckResizerEdge(refPoint)) {
 | |
|     nsCursor cursor = eCursor_none;
 | |
|     switch (*edge) {
 | |
|       case GDK_WINDOW_EDGE_SOUTH:
 | |
|       case GDK_WINDOW_EDGE_NORTH:
 | |
|         cursor = eCursor_ns_resize;
 | |
|         break;
 | |
|       case GDK_WINDOW_EDGE_WEST:
 | |
|       case GDK_WINDOW_EDGE_EAST:
 | |
|         cursor = eCursor_ew_resize;
 | |
|         break;
 | |
|       case GDK_WINDOW_EDGE_NORTH_WEST:
 | |
|       case GDK_WINDOW_EDGE_SOUTH_EAST:
 | |
|         cursor = eCursor_nwse_resize;
 | |
|         break;
 | |
|       case GDK_WINDOW_EDGE_NORTH_EAST:
 | |
|       case GDK_WINDOW_EDGE_SOUTH_WEST:
 | |
|         cursor = eCursor_nesw_resize;
 | |
|         break;
 | |
|     }
 | |
|     SetCursor(Cursor{cursor});
 | |
|     // If we set resize cursor on widget level keep it locked and prevent layout
 | |
|     // to switch it back to default (by synthetic mouse events for instance)
 | |
|     // until resize is finished. This affects PIP windows only.
 | |
|     if (mIsPIPWindow) {
 | |
|       mWidgetCursorLocked = true;
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   WidgetMouseEvent event(true, eMouseMove, this, WidgetMouseEvent::eReal);
 | |
| 
 | |
|   gdouble pressure = 0;
 | |
|   gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
 | |
|   // Sometime gdk generate 0 pressure value between normal values
 | |
|   // We have to ignore that and use last valid value
 | |
|   if (pressure) {
 | |
|     mLastMotionPressure = pressure;
 | |
|   }
 | |
|   event.mPressure = mLastMotionPressure;
 | |
|   event.mRefPoint = refPoint;
 | |
|   event.AssignEventTime(GetWidgetEventTime(aEvent->time));
 | |
| 
 | |
|   KeymapWrapper::InitInputEvent(event, aEvent->state);
 | |
| 
 | |
|   DispatchInputEvent(&event);
 | |
| }
 | |
| 
 | |
| // If the automatic pointer grab on ButtonPress has deactivated before
 | |
| // ButtonRelease, and the mouse button is released while the pointer is not
 | |
| // over any a Gecko window, then the ButtonRelease event will not be received.
 | |
| // (A similar situation exists when the pointer is grabbed with owner_events
 | |
| // True as the ButtonRelease may be received on a foreign [plugin] window).
 | |
| // Use this method to check for released buttons when the pointer returns to a
 | |
| // Gecko window.
 | |
| void nsWindow::DispatchMissedButtonReleases(GdkEventCrossing* aGdkEvent) {
 | |
|   guint changed = aGdkEvent->state ^ gButtonState;
 | |
|   // Only consider button releases.
 | |
|   // (Ignore button presses that occurred outside Gecko.)
 | |
|   guint released = changed & gButtonState;
 | |
|   gButtonState = aGdkEvent->state;
 | |
| 
 | |
|   // Loop over each button, excluding mouse wheel buttons 4 and 5 for which
 | |
|   // GDK ignores releases.
 | |
|   for (guint buttonMask = GDK_BUTTON1_MASK; buttonMask <= GDK_BUTTON3_MASK;
 | |
|        buttonMask <<= 1) {
 | |
|     if (released & buttonMask) {
 | |
|       int16_t buttonType;
 | |
|       switch (buttonMask) {
 | |
|         case GDK_BUTTON1_MASK:
 | |
|           buttonType = MouseButton::ePrimary;
 | |
|           break;
 | |
|         case GDK_BUTTON2_MASK:
 | |
|           buttonType = MouseButton::eMiddle;
 | |
|           break;
 | |
|         default:
 | |
|           NS_ASSERTION(buttonMask == GDK_BUTTON3_MASK,
 | |
|                        "Unexpected button mask");
 | |
|           buttonType = MouseButton::eSecondary;
 | |
|       }
 | |
| 
 | |
|       LOG("Synthesized button %u release", guint(buttonType + 1));
 | |
| 
 | |
|       // Dispatch a synthesized button up event to tell Gecko about the
 | |
|       // change in state.  This event is marked as synthesized so that
 | |
|       // it is not dispatched as a DOM event, because we don't know the
 | |
|       // position, widget, modifiers, or time/order.
 | |
|       WidgetMouseEvent synthEvent(true, eMouseUp, this,
 | |
|                                   WidgetMouseEvent::eSynthesized);
 | |
|       synthEvent.mButton = buttonType;
 | |
|       DispatchInputEvent(&synthEvent);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::InitButtonEvent(WidgetMouseEvent& aEvent,
 | |
|                                GdkEventButton* aGdkEvent,
 | |
|                                const LayoutDeviceIntPoint& aRefPoint) {
 | |
|   aEvent.mRefPoint = aRefPoint;
 | |
| 
 | |
|   guint modifierState = aGdkEvent->state;
 | |
|   // aEvent's state includes the button state from immediately before this
 | |
|   // event.  If aEvent is a mousedown or mouseup event, we need to update
 | |
|   // the button state.
 | |
|   guint buttonMask = 0;
 | |
|   switch (aGdkEvent->button) {
 | |
|     case 1:
 | |
|       buttonMask = GDK_BUTTON1_MASK;
 | |
|       break;
 | |
|     case 2:
 | |
|       buttonMask = GDK_BUTTON2_MASK;
 | |
|       break;
 | |
|     case 3:
 | |
|       buttonMask = GDK_BUTTON3_MASK;
 | |
|       break;
 | |
|   }
 | |
|   if (aGdkEvent->type == GDK_BUTTON_RELEASE) {
 | |
|     modifierState &= ~buttonMask;
 | |
|   } else {
 | |
|     modifierState |= buttonMask;
 | |
|   }
 | |
| 
 | |
|   KeymapWrapper::InitInputEvent(aEvent, modifierState);
 | |
| 
 | |
|   aEvent.AssignEventTime(GetWidgetEventTime(aGdkEvent->time));
 | |
| 
 | |
|   switch (aGdkEvent->type) {
 | |
|     case GDK_2BUTTON_PRESS:
 | |
|       aEvent.mClickCount = 2;
 | |
|       break;
 | |
|     case GDK_3BUTTON_PRESS:
 | |
|       aEvent.mClickCount = 3;
 | |
|       break;
 | |
|       // default is one click
 | |
|     default:
 | |
|       aEvent.mClickCount = 1;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static guint ButtonMaskFromGDKButton(guint button) {
 | |
|   return GDK_BUTTON1_MASK << (button - 1);
 | |
| }
 | |
| 
 | |
| void nsWindow::DispatchContextMenuEventFromMouseEvent(
 | |
|     uint16_t domButton, GdkEventButton* aEvent,
 | |
|     const LayoutDeviceIntPoint& aRefPoint) {
 | |
|   if (domButton == MouseButton::eSecondary && MOZ_LIKELY(!mIsDestroyed)) {
 | |
|     WidgetMouseEvent contextMenuEvent(true, eContextMenu, this,
 | |
|                                       WidgetMouseEvent::eReal);
 | |
|     InitButtonEvent(contextMenuEvent, aEvent, aRefPoint);
 | |
|     contextMenuEvent.mPressure = mLastMotionPressure;
 | |
|     DispatchInputEvent(&contextMenuEvent);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::TryToShowNativeWindowMenu(GdkEventButton* aEvent) {
 | |
|   if (!gdk_window_show_window_menu(GetToplevelGdkWindow(), (GdkEvent*)aEvent)) {
 | |
|     NS_WARNING("Native context menu wasn't shown");
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::OnButtonPressEvent(GdkEventButton* aEvent) {
 | |
|   LOG("Button %u press\n", aEvent->button);
 | |
| 
 | |
|   SetLastMousePressEvent((GdkEvent*)aEvent);
 | |
| 
 | |
|   // If you double click in GDK, it will actually generate a second
 | |
|   // GDK_BUTTON_PRESS before sending the GDK_2BUTTON_PRESS, and this is
 | |
|   // different than the DOM spec.  GDK puts this in the queue
 | |
|   // programatically, so it's safe to assume that if there's a
 | |
|   // double click in the queue, it was generated so we can just drop
 | |
|   // this click.
 | |
|   GUniquePtr<GdkEvent> peekedEvent(gdk_event_peek());
 | |
|   if (peekedEvent) {
 | |
|     GdkEventType type = peekedEvent->any.type;
 | |
|     if (type == GDK_2BUTTON_PRESS || type == GDK_3BUTTON_PRESS) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsWindow* containerWindow = GetContainerWindow();
 | |
|   if (!gFocusWindow && containerWindow) {
 | |
|     containerWindow->DispatchActivateEvent();
 | |
|   }
 | |
| 
 | |
|   const auto refPoint = GetRefPoint(this, aEvent);
 | |
| 
 | |
|   // check to see if we should rollup
 | |
|   if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false)) {
 | |
|     if (aEvent->button == 3 && mDraggableRegion.Contains(refPoint)) {
 | |
|       GUniquePtr<GdkEvent> eventCopy;
 | |
|       if (aEvent->type != GDK_BUTTON_PRESS) {
 | |
|         // If the user double-clicks too fast we'll get a 2BUTTON_PRESS event
 | |
|         // instead, and that isn't handled by open_window_menu, so coerce it
 | |
|         // into a regular press.
 | |
|         eventCopy.reset(gdk_event_copy((GdkEvent*)aEvent));
 | |
|         eventCopy->type = GDK_BUTTON_PRESS;
 | |
|       }
 | |
|       TryToShowNativeWindowMenu(eventCopy ? &eventCopy->button : aEvent);
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Check to see if the event is within our window's resize region
 | |
|   if (auto edge = CheckResizerEdge(refPoint)) {
 | |
|     // On Wayland Gtk fails to vertically/horizontally resize windows
 | |
|     // with fixed aspect ratio. We need to emulate
 | |
|     // gdk_window_begin_resize_drag() at OnMotionNotifyEvent().
 | |
|     if (mAspectRatio != 0.0f && GdkIsWaylandDisplay()) {
 | |
|       mLastResizePoint = LayoutDeviceIntPoint::Floor(aEvent->x, aEvent->y);
 | |
|       switch (*edge) {
 | |
|         case GDK_WINDOW_EDGE_SOUTH:
 | |
|           mAspectResizer = Some(GTK_ORIENTATION_VERTICAL);
 | |
|           break;
 | |
|         case GDK_WINDOW_EDGE_EAST:
 | |
|           mAspectResizer = Some(GTK_ORIENTATION_HORIZONTAL);
 | |
|           break;
 | |
|         default:
 | |
|           mAspectResizer.reset();
 | |
|           break;
 | |
|       }
 | |
|       ApplySizeConstraints();
 | |
|     }
 | |
|     if (!mAspectResizer) {
 | |
|       gdk_window_begin_resize_drag(GetToplevelGdkWindow(), *edge,
 | |
|                                    aEvent->button, aEvent->x_root,
 | |
|                                    aEvent->y_root, aEvent->time);
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   gdouble pressure = 0;
 | |
|   gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
 | |
|   mLastMotionPressure = pressure;
 | |
| 
 | |
|   uint16_t domButton;
 | |
|   switch (aEvent->button) {
 | |
|     case 1:
 | |
|       domButton = MouseButton::ePrimary;
 | |
|       break;
 | |
|     case 2:
 | |
|       domButton = MouseButton::eMiddle;
 | |
|       break;
 | |
|     case 3:
 | |
|       domButton = MouseButton::eSecondary;
 | |
|       break;
 | |
|     // These are mapped to horizontal scroll
 | |
|     case 6:
 | |
|     case 7:
 | |
|       NS_WARNING("We're not supporting legacy horizontal scroll event");
 | |
|       return;
 | |
|     // Map buttons 8-9(10) to back/forward
 | |
|     case 8:
 | |
|       if (!Preferences::GetBool("mousebutton.4th.enabled", true)) {
 | |
|         return;
 | |
|       }
 | |
|       DispatchCommandEvent(nsGkAtoms::Back);
 | |
|       return;
 | |
|     case 9:
 | |
|     case 10:
 | |
|       if (!Preferences::GetBool("mousebutton.5th.enabled", true)) {
 | |
|         return;
 | |
|       }
 | |
|       DispatchCommandEvent(nsGkAtoms::Forward);
 | |
|       return;
 | |
|     default:
 | |
|       return;
 | |
|   }
 | |
| 
 | |
|   gButtonState |= ButtonMaskFromGDKButton(aEvent->button);
 | |
| 
 | |
|   WidgetMouseEvent event(true, eMouseDown, this, WidgetMouseEvent::eReal);
 | |
|   event.mButton = domButton;
 | |
|   InitButtonEvent(event, aEvent, refPoint);
 | |
|   event.mPressure = mLastMotionPressure;
 | |
| 
 | |
|   nsIWidget::ContentAndAPZEventStatus eventStatus = DispatchInputEvent(&event);
 | |
| 
 | |
|   if (mDraggableRegion.Contains(refPoint) &&
 | |
|       domButton == MouseButton::ePrimary &&
 | |
|       eventStatus.mContentStatus != nsEventStatus_eConsumeNoDefault) {
 | |
|     mWindowShouldStartDragging = true;
 | |
|   }
 | |
| 
 | |
|   // right menu click on linux should also pop up a context menu
 | |
|   if (!StaticPrefs::ui_context_menus_after_mouseup() &&
 | |
|       eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
 | |
|     DispatchContextMenuEventFromMouseEvent(domButton, aEvent, refPoint);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::OnButtonReleaseEvent(GdkEventButton* aEvent) {
 | |
|   LOG("Button %u release\n", aEvent->button);
 | |
| 
 | |
|   SetLastMousePressEvent(nullptr);
 | |
| 
 | |
|   if (!mGdkWindow) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mAspectResizer) {
 | |
|     mAspectResizer = Nothing();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mWindowShouldStartDragging) {
 | |
|     mWindowShouldStartDragging = false;
 | |
|   }
 | |
| 
 | |
|   uint16_t domButton;
 | |
|   switch (aEvent->button) {
 | |
|     case 1:
 | |
|       domButton = MouseButton::ePrimary;
 | |
|       break;
 | |
|     case 2:
 | |
|       domButton = MouseButton::eMiddle;
 | |
|       break;
 | |
|     case 3:
 | |
|       domButton = MouseButton::eSecondary;
 | |
|       break;
 | |
|     default:
 | |
|       return;
 | |
|   }
 | |
| 
 | |
|   gButtonState &= ~ButtonMaskFromGDKButton(aEvent->button);
 | |
| 
 | |
|   const auto refPoint = GetRefPoint(this, aEvent);
 | |
| 
 | |
|   WidgetMouseEvent event(true, eMouseUp, this, WidgetMouseEvent::eReal);
 | |
|   event.mButton = domButton;
 | |
|   InitButtonEvent(event, aEvent, refPoint);
 | |
|   gdouble pressure = 0;
 | |
|   gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
 | |
|   event.mPressure = pressure ? (float)pressure : (float)mLastMotionPressure;
 | |
| 
 | |
|   // The mRefPoint is manipulated in DispatchInputEvent, we're saving it
 | |
|   // to use it for the doubleclick position check.
 | |
|   const LayoutDeviceIntPoint pos = event.mRefPoint;
 | |
| 
 | |
|   nsIWidget::ContentAndAPZEventStatus eventStatus = DispatchInputEvent(&event);
 | |
| 
 | |
|   const bool defaultPrevented =
 | |
|       eventStatus.mContentStatus == nsEventStatus_eConsumeNoDefault;
 | |
|   // Check if mouse position in titlebar and doubleclick happened to
 | |
|   // trigger restore/maximize.
 | |
|   if (!defaultPrevented && mDrawInTitlebar &&
 | |
|       event.mButton == MouseButton::ePrimary && event.mClickCount == 2 &&
 | |
|       mDraggableRegion.Contains(pos)) {
 | |
|     if (mSizeMode == nsSizeMode_Maximized) {
 | |
|       SetSizeMode(nsSizeMode_Normal);
 | |
|     } else if (mSizeMode == nsSizeMode_Normal) {
 | |
|       SetSizeMode(nsSizeMode_Maximized);
 | |
|     }
 | |
|   }
 | |
|   mLastMotionPressure = pressure;
 | |
| 
 | |
|   // right menu click on linux should also pop up a context menu
 | |
|   if (StaticPrefs::ui_context_menus_after_mouseup() &&
 | |
|       eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
 | |
|     DispatchContextMenuEventFromMouseEvent(domButton, aEvent, refPoint);
 | |
|   }
 | |
| 
 | |
|   // Open window manager menu on PIP window to allow user
 | |
|   // to place it on top / all workspaces.
 | |
|   if (mAlwaysOnTop && aEvent->button == 3) {
 | |
|     TryToShowNativeWindowMenu(aEvent);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::OnContainerFocusInEvent(GdkEventFocus* aEvent) {
 | |
|   LOG("OnContainerFocusInEvent");
 | |
| 
 | |
|   // Unset the urgency hint, if possible
 | |
|   GtkWidget* top_window = GetToplevelWidget();
 | |
|   if (top_window && (gtk_widget_get_visible(top_window))) {
 | |
|     SetUrgencyHint(top_window, false);
 | |
|   }
 | |
| 
 | |
|   // Return if being called within SetFocus because the focus manager
 | |
|   // already knows that the window is active.
 | |
|   if (gBlockActivateEvent) {
 | |
|     LOG("activated notification is blocked");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If keyboard input will be accepted, the focus manager will call
 | |
|   // SetFocus to set the correct window.
 | |
|   gFocusWindow = nullptr;
 | |
| 
 | |
|   DispatchActivateEvent();
 | |
| 
 | |
|   if (!gFocusWindow) {
 | |
|     // We don't really have a window for dispatching key events, but
 | |
|     // setting a non-nullptr value here prevents OnButtonPressEvent() from
 | |
|     // dispatching an activation notification if the widget is already
 | |
|     // active.
 | |
|     gFocusWindow = this;
 | |
|   }
 | |
| 
 | |
|   LOG("Events sent from focus in event");
 | |
| }
 | |
| 
 | |
| void nsWindow::OnContainerFocusOutEvent(GdkEventFocus* aEvent) {
 | |
|   LOG("OnContainerFocusOutEvent");
 | |
| 
 | |
|   if (IsTopLevelWindowType()) {
 | |
|     // Rollup menus when a window is focused out unless a drag is occurring.
 | |
|     // This check is because drags grab the keyboard and cause a focus out on
 | |
|     // versions of GTK before 2.18.
 | |
|     const bool shouldRollupMenus = [&] {
 | |
|       nsCOMPtr<nsIDragService> dragService =
 | |
|           do_GetService("@mozilla.org/widget/dragservice;1");
 | |
|       nsCOMPtr<nsIDragSession> dragSession;
 | |
|       dragService->GetCurrentSession(getter_AddRefs(dragSession));
 | |
|       if (!dragSession) {
 | |
|         return true;
 | |
|       }
 | |
|       // We also roll up when a drag is from a different application
 | |
|       nsCOMPtr<nsINode> sourceNode;
 | |
|       dragSession->GetSourceNode(getter_AddRefs(sourceNode));
 | |
|       return !sourceNode;
 | |
|     }();
 | |
| 
 | |
|     if (shouldRollupMenus) {
 | |
|       RollupAllMenus();
 | |
|     }
 | |
| 
 | |
|     if (RefPtr pm = nsXULPopupManager::GetInstance()) {
 | |
|       pm->RollupTooltips();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (gFocusWindow) {
 | |
|     RefPtr<nsWindow> kungFuDeathGrip = gFocusWindow;
 | |
|     if (gFocusWindow->mIMContext) {
 | |
|       gFocusWindow->mIMContext->OnBlurWindow(gFocusWindow);
 | |
|     }
 | |
|     gFocusWindow = nullptr;
 | |
|   }
 | |
| 
 | |
|   DispatchDeactivateEvent();
 | |
| 
 | |
|   if (IsChromeWindowTitlebar()) {
 | |
|     // DispatchDeactivateEvent() ultimately results in a call to
 | |
|     // BrowsingContext::SetIsActiveBrowserWindow(), which resets
 | |
|     // the state.  We call UpdateMozWindowActive() to keep it in
 | |
|     // sync with GDK_WINDOW_STATE_FOCUSED.
 | |
|     UpdateMozWindowActive();
 | |
|   }
 | |
| 
 | |
|   LOG("Done with container focus out");
 | |
| }
 | |
| 
 | |
| bool nsWindow::DispatchCommandEvent(nsAtom* aCommand) {
 | |
|   nsEventStatus status;
 | |
|   WidgetCommandEvent appCommandEvent(true, aCommand, this);
 | |
|   DispatchEvent(&appCommandEvent, status);
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| bool nsWindow::DispatchContentCommandEvent(EventMessage aMsg) {
 | |
|   nsEventStatus status;
 | |
|   WidgetContentCommandEvent event(true, aMsg, this);
 | |
|   DispatchEvent(&event, status);
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| WidgetEventTime nsWindow::GetWidgetEventTime(guint32 aEventTime) {
 | |
|   return WidgetEventTime(GetEventTimeStamp(aEventTime));
 | |
| }
 | |
| 
 | |
| TimeStamp nsWindow::GetEventTimeStamp(guint32 aEventTime) {
 | |
|   if (MOZ_UNLIKELY(!mGdkWindow)) {
 | |
|     // nsWindow has been Destroy()ed.
 | |
|     return TimeStamp::Now();
 | |
|   }
 | |
|   if (aEventTime == 0) {
 | |
|     // Some X11 and GDK events may be received with a time of 0 to indicate
 | |
|     // that they are synthetic events. Some input method editors do this.
 | |
|     // In this case too, just return the current timestamp.
 | |
|     return TimeStamp::Now();
 | |
|   }
 | |
| 
 | |
|   TimeStamp eventTimeStamp;
 | |
| 
 | |
|   if (GdkIsWaylandDisplay()) {
 | |
|     // Wayland compositors use monotonic time to set timestamps.
 | |
|     int64_t timestampTime = g_get_monotonic_time() / 1000;
 | |
|     guint32 refTimeTruncated = guint32(timestampTime);
 | |
| 
 | |
|     timestampTime -= refTimeTruncated - aEventTime;
 | |
|     int64_t tick =
 | |
|         BaseTimeDurationPlatformUtils::TicksFromMilliseconds(timestampTime);
 | |
|     eventTimeStamp = TimeStamp::FromSystemTime(tick);
 | |
|   } else {
 | |
| #ifdef MOZ_X11
 | |
|     CurrentX11TimeGetter* getCurrentTime = GetCurrentTimeGetter();
 | |
|     MOZ_ASSERT(getCurrentTime,
 | |
|                "Null current time getter despite having a window");
 | |
|     eventTimeStamp =
 | |
|         TimeConverter().GetTimeStampFromSystemTime(aEventTime, *getCurrentTime);
 | |
| #endif
 | |
|   }
 | |
|   return eventTimeStamp;
 | |
| }
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
| mozilla::CurrentX11TimeGetter* nsWindow::GetCurrentTimeGetter() {
 | |
|   MOZ_ASSERT(mGdkWindow, "Expected mGdkWindow to be set");
 | |
|   if (MOZ_UNLIKELY(!mCurrentTimeGetter)) {
 | |
|     mCurrentTimeGetter = MakeUnique<CurrentX11TimeGetter>(mGdkWindow);
 | |
|   }
 | |
|   return mCurrentTimeGetter.get();
 | |
| }
 | |
| #endif
 | |
| 
 | |
| gboolean nsWindow::OnKeyPressEvent(GdkEventKey* aEvent) {
 | |
|   LOG("OnKeyPressEvent");
 | |
| 
 | |
|   KeymapWrapper::HandleKeyPressEvent(this, aEvent);
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| gboolean nsWindow::OnKeyReleaseEvent(GdkEventKey* aEvent) {
 | |
|   LOG("OnKeyReleaseEvent");
 | |
|   if (NS_WARN_IF(!KeymapWrapper::HandleKeyReleaseEvent(this, aEvent))) {
 | |
|     return FALSE;
 | |
|   }
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| void nsWindow::OnScrollEvent(GdkEventScroll* aEvent) {
 | |
|   // check to see if we should rollup
 | |
|   if (CheckForRollup(aEvent->x_root, aEvent->y_root, true, false)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // check for duplicate legacy scroll event, see GNOME bug 726878
 | |
|   if (aEvent->direction != GDK_SCROLL_SMOOTH &&
 | |
|       mLastScrollEventTime == aEvent->time) {
 | |
|     LOG("[%d] duplicate legacy scroll event %d\n", aEvent->time,
 | |
|         aEvent->direction);
 | |
|     return;
 | |
|   }
 | |
|   WidgetWheelEvent wheelEvent(true, eWheel, this);
 | |
|   wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_LINE;
 | |
|   switch (aEvent->direction) {
 | |
|     case GDK_SCROLL_SMOOTH: {
 | |
|       // As of GTK 3.4, all directional scroll events are provided by
 | |
|       // the GDK_SCROLL_SMOOTH direction on XInput2 and Wayland devices.
 | |
|       mLastScrollEventTime = aEvent->time;
 | |
| 
 | |
|       // Special handling for touchpads to support flings
 | |
|       // (also known as kinetic/inertial/momentum scrolling)
 | |
|       GdkDevice* device = gdk_event_get_source_device((GdkEvent*)aEvent);
 | |
|       GdkInputSource source = gdk_device_get_source(device);
 | |
|       if (source == GDK_SOURCE_TOUCHSCREEN || source == GDK_SOURCE_TOUCHPAD ||
 | |
|           mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase.isSome()) {
 | |
|         if (StaticPrefs::apz_gtk_pangesture_enabled() &&
 | |
|             gtk_check_version(3, 20, 0) == nullptr) {
 | |
|           static auto sGdkEventIsScrollStopEvent =
 | |
|               (gboolean(*)(const GdkEvent*))dlsym(
 | |
|                   RTLD_DEFAULT, "gdk_event_is_scroll_stop_event");
 | |
| 
 | |
|           LOG("[%d] pan smooth event dx=%f dy=%f inprogress=%d\n", aEvent->time,
 | |
|               aEvent->delta_x, aEvent->delta_y, mPanInProgress);
 | |
|           auto eventType = PanGestureInput::PANGESTURE_PAN;
 | |
|           if (sGdkEventIsScrollStopEvent((GdkEvent*)aEvent)) {
 | |
|             eventType = PanGestureInput::PANGESTURE_END;
 | |
|             mPanInProgress = false;
 | |
|           } else if (!mPanInProgress) {
 | |
|             eventType = PanGestureInput::PANGESTURE_START;
 | |
|             mPanInProgress = true;
 | |
|           } else if (mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase
 | |
|                          .isSome()) {
 | |
|             switch (*mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase) {
 | |
|               case PHASE_BEGIN:
 | |
|                 // we should never hit this because we'll hit the above case
 | |
|                 // before this.
 | |
|                 MOZ_ASSERT_UNREACHABLE();
 | |
|                 eventType = PanGestureInput::PANGESTURE_START;
 | |
|                 mPanInProgress = true;
 | |
|                 break;
 | |
|               case PHASE_UPDATE:
 | |
|                 // nothing to do here, eventtype should already be set
 | |
|                 MOZ_ASSERT(mPanInProgress);
 | |
|                 MOZ_ASSERT(eventType == PanGestureInput::PANGESTURE_PAN);
 | |
|                 eventType = PanGestureInput::PANGESTURE_PAN;
 | |
|                 break;
 | |
|               case PHASE_END:
 | |
|                 MOZ_ASSERT(mPanInProgress);
 | |
|                 eventType = PanGestureInput::PANGESTURE_END;
 | |
|                 mPanInProgress = false;
 | |
|                 break;
 | |
|               default:
 | |
|                 MOZ_ASSERT_UNREACHABLE();
 | |
|                 break;
 | |
|             }
 | |
|           }
 | |
| 
 | |
|           mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase.reset();
 | |
| 
 | |
|           const bool isPageMode =
 | |
| #ifdef NIGHTLY_BUILD
 | |
|               StaticPrefs::apz_gtk_pangesture_delta_mode() == 1;
 | |
| #else
 | |
|               StaticPrefs::apz_gtk_pangesture_delta_mode() != 2;
 | |
| #endif
 | |
|           const double multiplier =
 | |
|               isPageMode
 | |
|                   ? StaticPrefs::apz_gtk_pangesture_page_delta_mode_multiplier()
 | |
|                   : StaticPrefs::
 | |
|                             apz_gtk_pangesture_pixel_delta_mode_multiplier() *
 | |
|                         FractionalScaleFactor();
 | |
| 
 | |
|           ScreenPoint deltas(float(aEvent->delta_x * multiplier),
 | |
|                              float(aEvent->delta_y * multiplier));
 | |
| 
 | |
|           LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent);
 | |
|           PanGestureInput panEvent(
 | |
|               eventType, GetEventTimeStamp(aEvent->time),
 | |
|               ScreenPoint(touchPoint.x, touchPoint.y), deltas,
 | |
|               KeymapWrapper::ComputeKeyModifiers(aEvent->state));
 | |
|           panEvent.mDeltaType = isPageMode ? PanGestureInput::PANDELTA_PAGE
 | |
|                                            : PanGestureInput::PANDELTA_PIXEL;
 | |
|           panEvent.mSimulateMomentum =
 | |
|               StaticPrefs::apz_gtk_kinetic_scroll_enabled();
 | |
| 
 | |
|           DispatchPanGesture(panEvent);
 | |
| 
 | |
|           if (mCurrentSynthesizedTouchpadPan.mSavedObserver != 0) {
 | |
|             mozilla::widget::AutoObserverNotifier::NotifySavedObserver(
 | |
|                 mCurrentSynthesizedTouchpadPan.mSavedObserver,
 | |
|                 "touchpadpanevent");
 | |
|             mCurrentSynthesizedTouchpadPan.mSavedObserver = 0;
 | |
|           }
 | |
| 
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         // Older GTK doesn't support stop events, so we can't support fling
 | |
|         wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSLY;
 | |
|       }
 | |
| 
 | |
|       // TODO - use a more appropriate scrolling unit than lines.
 | |
|       // Multiply event deltas by 3 to emulate legacy behaviour.
 | |
|       wheelEvent.mDeltaX = aEvent->delta_x * 3;
 | |
|       wheelEvent.mDeltaY = aEvent->delta_y * 3;
 | |
|       wheelEvent.mWheelTicksX = aEvent->delta_x;
 | |
|       wheelEvent.mWheelTicksY = aEvent->delta_y;
 | |
|       wheelEvent.mIsNoLineOrPageDelta = true;
 | |
| 
 | |
|       break;
 | |
|     }
 | |
|     case GDK_SCROLL_UP:
 | |
|       wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = -3;
 | |
|       wheelEvent.mWheelTicksY = -1;
 | |
|       break;
 | |
|     case GDK_SCROLL_DOWN:
 | |
|       wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = 3;
 | |
|       wheelEvent.mWheelTicksY = 1;
 | |
|       break;
 | |
|     case GDK_SCROLL_LEFT:
 | |
|       wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = -1;
 | |
|       wheelEvent.mWheelTicksX = -1;
 | |
|       break;
 | |
|     case GDK_SCROLL_RIGHT:
 | |
|       wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = 1;
 | |
|       wheelEvent.mWheelTicksX = 1;
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   wheelEvent.mRefPoint = GetRefPoint(this, aEvent);
 | |
| 
 | |
|   KeymapWrapper::InitInputEvent(wheelEvent, aEvent->state);
 | |
| 
 | |
|   wheelEvent.AssignEventTime(GetWidgetEventTime(aEvent->time));
 | |
| 
 | |
|   DispatchInputEvent(&wheelEvent);
 | |
| }
 | |
| 
 | |
| void nsWindow::DispatchPanGesture(PanGestureInput& aPanInput) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (mSwipeTracker) {
 | |
|     // Give the swipe tracker a first pass at the event. If a new pan gesture
 | |
|     // has been started since the beginning of the swipe, the swipe tracker
 | |
|     // will know to ignore the event.
 | |
|     nsEventStatus status = mSwipeTracker->ProcessEvent(aPanInput);
 | |
|     if (status == nsEventStatus_eConsumeNoDefault) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   APZEventResult result;
 | |
|   if (mAPZC) {
 | |
|     MOZ_ASSERT(APZThreadUtils::IsControllerThread());
 | |
| 
 | |
|     result = mAPZC->InputBridge()->ReceiveInputEvent(aPanInput);
 | |
|     if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   WidgetWheelEvent event = aPanInput.ToWidgetEvent(this);
 | |
|   if (!mAPZC) {
 | |
|     if (MayStartSwipeForNonAPZ(aPanInput)) {
 | |
|       return;
 | |
|     }
 | |
|   } else {
 | |
|     event = MayStartSwipeForAPZ(aPanInput, result);
 | |
|   }
 | |
| 
 | |
|   ProcessUntransformedAPZEvent(&event, result);
 | |
| }
 | |
| 
 | |
| void nsWindow::OnVisibilityNotifyEvent(GdkVisibilityState aState) {
 | |
|   LOG("nsWindow::OnVisibilityNotifyEvent [%p] state 0x%x\n", this, aState);
 | |
|   auto state = aState == GDK_VISIBILITY_FULLY_OBSCURED
 | |
|                    ? OcclusionState::OCCLUDED
 | |
|                    : OcclusionState::UNKNOWN;
 | |
|   NotifyOcclusionState(state);
 | |
| }
 | |
| 
 | |
| void nsWindow::OnWindowStateEvent(GtkWidget* aWidget,
 | |
|                                   GdkEventWindowState* aEvent) {
 | |
|   LOG("nsWindow::OnWindowStateEvent for %p changed 0x%x new_window_state "
 | |
|       "0x%x\n",
 | |
|       aWidget, aEvent->changed_mask, aEvent->new_window_state);
 | |
| 
 | |
|   if (IS_MOZ_CONTAINER(aWidget)) {
 | |
|     // This event is notifying the container widget of changes to the
 | |
|     // toplevel window.  Just detect changes affecting whether windows are
 | |
|     // viewable.
 | |
|     //
 | |
|     // (A visibility notify event is sent to each window that becomes
 | |
|     // viewable when the toplevel is mapped, but we can't rely on that for
 | |
|     // setting mHasMappedToplevel because these toplevel window state
 | |
|     // events are asynchronous.  The windows in the hierarchy now may not
 | |
|     // be the same windows as when the toplevel was mapped, so they may
 | |
|     // not get VisibilityNotify events.)
 | |
|     bool mapped = !(aEvent->new_window_state &
 | |
|                     (GDK_WINDOW_STATE_ICONIFIED | GDK_WINDOW_STATE_WITHDRAWN));
 | |
|     SetHasMappedToplevel(mapped);
 | |
|     LOG("\tquick return because IS_MOZ_CONTAINER(aWidget) is true\n");
 | |
|     return;
 | |
|   }
 | |
|   // else the widget is a shell widget.
 | |
| 
 | |
|   // The block below is a bit evil.
 | |
|   //
 | |
|   // When a window is resized before it is shown, gtk_window_resize() delays
 | |
|   // resizes until the window is shown.  If gtk_window_state_event() sees a
 | |
|   // GDK_WINDOW_STATE_MAXIMIZED change [1] before the window is shown, then
 | |
|   // gtk_window_compute_configure_request_size() ignores the values from the
 | |
|   // resize [2].  See bug 1449166 for an example of how this could happen.
 | |
|   //
 | |
|   // [1] https://gitlab.gnome.org/GNOME/gtk/blob/3.22.30/gtk/gtkwindow.c#L7967
 | |
|   // [2] https://gitlab.gnome.org/GNOME/gtk/blob/3.22.30/gtk/gtkwindow.c#L9377
 | |
|   //
 | |
|   // In order to provide a sensible size for the window when the user exits
 | |
|   // maximized state, we hide the GDK_WINDOW_STATE_MAXIMIZED change from
 | |
|   // gtk_window_state_event() so as to trick GTK into using the values from
 | |
|   // gtk_window_resize() in its configure request.
 | |
|   //
 | |
|   // We instead notify gtk_window_state_event() of the maximized state change
 | |
|   // once the window is shown.
 | |
|   //
 | |
|   // See https://gitlab.gnome.org/GNOME/gtk/issues/1044
 | |
|   //
 | |
|   // This may be fixed in Gtk 3.24+ but it's still live and kicking
 | |
|   // (Bug 1791779).
 | |
|   if (!mIsShown) {
 | |
|     aEvent->changed_mask = static_cast<GdkWindowState>(
 | |
|         aEvent->changed_mask & ~GDK_WINDOW_STATE_MAXIMIZED);
 | |
|   } else if (aEvent->changed_mask & GDK_WINDOW_STATE_WITHDRAWN &&
 | |
|              aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
 | |
|     aEvent->changed_mask = static_cast<GdkWindowState>(
 | |
|         aEvent->changed_mask | GDK_WINDOW_STATE_MAXIMIZED);
 | |
|   }
 | |
| 
 | |
|   // This is a workaround for https://gitlab.gnome.org/GNOME/gtk/issues/1395
 | |
|   // Gtk+ controls window active appearance by window-state-event signal.
 | |
|   if (IsChromeWindowTitlebar() &&
 | |
|       (aEvent->changed_mask & GDK_WINDOW_STATE_FOCUSED)) {
 | |
|     // Emulate what Gtk+ does at gtk_window_state_event().
 | |
|     // We can't check GTK_STATE_FLAG_BACKDROP directly as it's set by Gtk+
 | |
|     // *after* this window-state-event handler.
 | |
|     mTitlebarBackdropState =
 | |
|         !(aEvent->new_window_state & GDK_WINDOW_STATE_FOCUSED);
 | |
| 
 | |
|     // keep IsActiveBrowserWindow in sync with GDK_WINDOW_STATE_FOCUSED
 | |
|     UpdateMozWindowActive();
 | |
| 
 | |
|     ForceTitlebarRedraw();
 | |
|   }
 | |
| 
 | |
|   // We don't care about anything but changes in the maximized/icon/fullscreen
 | |
|   // states but we need a workaround for bug in Wayland:
 | |
|   // https://gitlab.gnome.org/GNOME/gtk/issues/67
 | |
|   // Under wayland the gtk_window_iconify implementation does NOT synthetize
 | |
|   // window_state_event where the GDK_WINDOW_STATE_ICONIFIED is set.
 | |
|   // During restore we  won't get aEvent->changed_mask with
 | |
|   // the GDK_WINDOW_STATE_ICONIFIED so to detect that change we use the stored
 | |
|   // mSizeMode and obtaining a focus.
 | |
|   bool waylandWasIconified =
 | |
|       (GdkIsWaylandDisplay() &&
 | |
|        aEvent->changed_mask & GDK_WINDOW_STATE_FOCUSED &&
 | |
|        aEvent->new_window_state & GDK_WINDOW_STATE_FOCUSED &&
 | |
|        mSizeMode == nsSizeMode_Minimized);
 | |
|   if (!waylandWasIconified &&
 | |
|       (aEvent->changed_mask &
 | |
|        (GDK_WINDOW_STATE_ICONIFIED | GDK_WINDOW_STATE_MAXIMIZED |
 | |
|         GDK_WINDOW_STATE_TILED | GDK_WINDOW_STATE_FULLSCREEN)) == 0) {
 | |
|     LOG("\tearly return because no interesting bits changed\n");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   auto oldSizeMode = mSizeMode;
 | |
|   if (aEvent->new_window_state & GDK_WINDOW_STATE_ICONIFIED) {
 | |
|     LOG("\tIconified\n");
 | |
|     mSizeMode = nsSizeMode_Minimized;
 | |
| #ifdef ACCESSIBILITY
 | |
|     DispatchMinimizeEventAccessible();
 | |
| #endif  // ACCESSIBILITY
 | |
|   } else if (aEvent->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) {
 | |
|     LOG("\tFullscreen\n");
 | |
|     mSizeMode = nsSizeMode_Fullscreen;
 | |
|   } else if (aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
 | |
|     LOG("\tMaximized\n");
 | |
|     mSizeMode = nsSizeMode_Maximized;
 | |
| #ifdef ACCESSIBILITY
 | |
|     DispatchMaximizeEventAccessible();
 | |
| #endif  // ACCESSIBILITY
 | |
|   } else {
 | |
|     LOG("\tNormal\n");
 | |
|     mSizeMode = nsSizeMode_Normal;
 | |
| #ifdef ACCESSIBILITY
 | |
|     DispatchRestoreEventAccessible();
 | |
| #endif  // ACCESSIBILITY
 | |
|   }
 | |
| 
 | |
|   mIsTiled = aEvent->new_window_state & GDK_WINDOW_STATE_TILED;
 | |
|   LOG("\tTiled: %d\n", int(mIsTiled));
 | |
| 
 | |
|   if (mWidgetListener && mSizeMode != oldSizeMode) {
 | |
|     mWidgetListener->SizeModeChanged(mSizeMode);
 | |
|   }
 | |
| 
 | |
|   if (mDrawInTitlebar && mTransparencyBitmapForTitlebar) {
 | |
|     if (mSizeMode == nsSizeMode_Normal && !mIsTiled) {
 | |
|       UpdateTitlebarTransparencyBitmap();
 | |
|     } else {
 | |
|       ClearTransparencyBitmap();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::OnDPIChanged() {
 | |
|   // Update menu's font size etc.
 | |
|   // This affects style / layout because it affects system font sizes.
 | |
|   if (mWidgetListener) {
 | |
|     if (PresShell* presShell = mWidgetListener->GetPresShell()) {
 | |
|       presShell->BackingScaleFactorChanged();
 | |
|     }
 | |
|     mWidgetListener->UIResolutionChanged();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::OnCheckResize() { mPendingConfigures++; }
 | |
| 
 | |
| void nsWindow::OnCompositedChanged() {
 | |
|   // Update CSD after the change in alpha visibility. This only affects
 | |
|   // system metrics, not other theme shenanigans.
 | |
|   NotifyThemeChanged(ThemeChangeKind::MediaQueriesOnly);
 | |
|   mCompositedScreen = gdk_screen_is_composited(gdk_screen_get_default());
 | |
| }
 | |
| 
 | |
| void nsWindow::OnScaleChanged(bool aNotify) {
 | |
|   if (!IsTopLevelWindowType()) {
 | |
|     return;
 | |
|   }
 | |
|   if (!mGdkWindow) {
 | |
|     return;  // We'll get there again when we configure the window.
 | |
|   }
 | |
|   gint newCeiled = gdk_window_get_scale_factor(mGdkWindow);
 | |
|   double newFractional = [&] {
 | |
| #ifdef MOZ_WAYLAND
 | |
|     if (GdkIsWaylandDisplay()) {
 | |
|       return moz_container_wayland_get_fractional_scale(mContainer);
 | |
|     }
 | |
| #endif
 | |
|     return 0.0;
 | |
|   }();
 | |
| 
 | |
|   if (mCeiledScaleFactor == newCeiled &&
 | |
|       mFractionalScaleFactor == newFractional) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LOG("OnScaleChanged %d, %f -> %d, %f\n", int(mCeiledScaleFactor),
 | |
|       mFractionalScaleFactor, newCeiled, newFractional);
 | |
| 
 | |
|   mCeiledScaleFactor = newCeiled;
 | |
|   mFractionalScaleFactor = newFractional;
 | |
| 
 | |
|   if (!aNotify) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // We pause compositor to avoid rendering of obsoleted remote content which
 | |
|   // produces flickering.
 | |
|   // Re-enable compositor again when remote content is updated or timeout
 | |
|   // happens.
 | |
|   PauseCompositorFlickering();
 | |
| 
 | |
|   GtkAllocation allocation;
 | |
|   gtk_widget_get_allocation(GTK_WIDGET(mContainer), &allocation);
 | |
|   LayoutDeviceIntSize size = GdkRectToDevicePixels(allocation).Size();
 | |
|   mBounds.SizeTo(size);
 | |
|   // Check mBounds size
 | |
|   if (mCompositorSession &&
 | |
|       !wr::WindowSizeSanityCheck(mBounds.width, mBounds.height)) {
 | |
|     gfxCriticalNoteOnce << "Invalid mBounds in OnScaleChanged " << mBounds;
 | |
|   }
 | |
| 
 | |
|   if (mWidgetListener) {
 | |
|     if (PresShell* presShell = mWidgetListener->GetPresShell()) {
 | |
|       presShell->BackingScaleFactorChanged();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DispatchResized();
 | |
| 
 | |
|   if (mCompositorWidgetDelegate) {
 | |
|     mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
 | |
|   }
 | |
| 
 | |
|   if (mCursor.IsCustom()) {
 | |
|     mUpdateCursor = true;
 | |
|     SetCursor(Cursor{mCursor});
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::DispatchDragEvent(EventMessage aMsg,
 | |
|                                  const LayoutDeviceIntPoint& aRefPoint,
 | |
|                                  guint aTime) {
 | |
|   LOGDRAG("nsWindow::DispatchDragEvent");
 | |
|   WidgetDragEvent event(true, aMsg, this);
 | |
| 
 | |
|   InitDragEvent(event);
 | |
| 
 | |
|   event.mRefPoint = aRefPoint;
 | |
|   event.AssignEventTime(GetWidgetEventTime(aTime));
 | |
| 
 | |
|   DispatchInputEvent(&event);
 | |
| }
 | |
| 
 | |
| void nsWindow::OnDragDataReceivedEvent(GtkWidget* aWidget,
 | |
|                                        GdkDragContext* aDragContext, gint aX,
 | |
|                                        gint aY,
 | |
|                                        GtkSelectionData* aSelectionData,
 | |
|                                        guint aInfo, guint aTime,
 | |
|                                        gpointer aData) {
 | |
|   LOGDRAG("nsWindow::OnDragDataReceived");
 | |
| 
 | |
|   RefPtr<nsDragService> dragService = nsDragService::GetInstance();
 | |
|   nsDragService::AutoEventLoop loop(dragService);
 | |
|   dragService->TargetDataReceived(aWidget, aDragContext, aX, aY, aSelectionData,
 | |
|                                   aInfo, aTime);
 | |
| }
 | |
| 
 | |
| nsWindow* nsWindow::GetTransientForWindowIfPopup() {
 | |
|   if (mWindowType != WindowType::Popup) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   GtkWindow* toplevel = gtk_window_get_transient_for(GTK_WINDOW(mShell));
 | |
|   if (toplevel) {
 | |
|     return get_window_for_gtk_widget(GTK_WIDGET(toplevel));
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| bool nsWindow::IsHandlingTouchSequence(GdkEventSequence* aSequence) {
 | |
|   return mHandleTouchEvent && mTouches.Contains(aSequence);
 | |
| }
 | |
| 
 | |
| gboolean nsWindow::OnTouchpadPinchEvent(GdkEventTouchpadPinch* aEvent) {
 | |
|   if (!StaticPrefs::apz_gtk_touchpad_pinch_enabled()) {
 | |
|     return TRUE;
 | |
|   }
 | |
|   // Do not respond to pinch gestures involving more than two fingers
 | |
|   // unless specifically preffed on. These are sometimes hooked up to other
 | |
|   // actions at the desktop environment level and having the browser also
 | |
|   // pinch can be undesirable.
 | |
|   if (aEvent->n_fingers > 2 &&
 | |
|       !StaticPrefs::apz_gtk_touchpad_pinch_three_fingers_enabled()) {
 | |
|     return FALSE;
 | |
|   }
 | |
|   auto pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
 | |
|   ScreenCoord currentSpan;
 | |
|   ScreenCoord previousSpan;
 | |
| 
 | |
|   switch (aEvent->phase) {
 | |
|     case GDK_TOUCHPAD_GESTURE_PHASE_BEGIN:
 | |
|       pinchGestureType = PinchGestureInput::PINCHGESTURE_START;
 | |
|       currentSpan = aEvent->scale;
 | |
|       mCurrentTouchpadFocus = ViewAs<ScreenPixel>(
 | |
|           GetRefPoint(this, aEvent),
 | |
|           PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
 | |
| 
 | |
|       // Assign PreviousSpan --> 0.999 to make mDeltaY field of the
 | |
|       // WidgetWheelEvent that this PinchGestureInput event will be converted
 | |
|       // to not equal Zero as our discussion because we observed that the
 | |
|       // scale of the PHASE_BEGIN event is 1.
 | |
|       previousSpan = 0.999;
 | |
|       break;
 | |
| 
 | |
|     case GDK_TOUCHPAD_GESTURE_PHASE_UPDATE:
 | |
|       pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
 | |
|       mCurrentTouchpadFocus += ScreenPoint(aEvent->dx, aEvent->dy);
 | |
|       if (aEvent->scale == mLastPinchEventSpan) {
 | |
|         return FALSE;
 | |
|       }
 | |
|       currentSpan = aEvent->scale;
 | |
|       previousSpan = mLastPinchEventSpan;
 | |
|       break;
 | |
| 
 | |
|     case GDK_TOUCHPAD_GESTURE_PHASE_END:
 | |
|       pinchGestureType = PinchGestureInput::PINCHGESTURE_END;
 | |
|       currentSpan = aEvent->scale;
 | |
|       previousSpan = mLastPinchEventSpan;
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       return FALSE;
 | |
|   }
 | |
| 
 | |
|   PinchGestureInput event(
 | |
|       pinchGestureType, PinchGestureInput::TRACKPAD,
 | |
|       GetEventTimeStamp(aEvent->time), ExternalPoint(0, 0),
 | |
|       mCurrentTouchpadFocus,
 | |
|       100.0 * ((aEvent->phase == GDK_TOUCHPAD_GESTURE_PHASE_END)
 | |
|                    ? ScreenCoord(1.f)
 | |
|                    : currentSpan),
 | |
|       100.0 * ((aEvent->phase == GDK_TOUCHPAD_GESTURE_PHASE_END)
 | |
|                    ? ScreenCoord(1.f)
 | |
|                    : previousSpan),
 | |
|       KeymapWrapper::ComputeKeyModifiers(aEvent->state));
 | |
| 
 | |
|   if (!event.SetLineOrPageDeltaY(this)) {
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   mLastPinchEventSpan = aEvent->scale;
 | |
|   DispatchPinchGestureInput(event);
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| gboolean nsWindow::OnTouchEvent(GdkEventTouch* aEvent) {
 | |
|   LOG("OnTouchEvent: x=%f y=%f type=%d\n", aEvent->x, aEvent->y, aEvent->type);
 | |
|   if (!mHandleTouchEvent) {
 | |
|     // If a popup window was spawned (e.g. as the result of a long-press)
 | |
|     // and touch events got diverted to that window within a touch sequence,
 | |
|     // ensure the touch event gets sent to the original window instead. We
 | |
|     // keep the checks here very conservative so that we only redirect
 | |
|     // events in this specific scenario.
 | |
|     nsWindow* targetWindow = GetTransientForWindowIfPopup();
 | |
|     if (targetWindow &&
 | |
|         targetWindow->IsHandlingTouchSequence(aEvent->sequence)) {
 | |
|       return targetWindow->OnTouchEvent(aEvent);
 | |
|     }
 | |
| 
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   EventMessage msg;
 | |
|   switch (aEvent->type) {
 | |
|     case GDK_TOUCH_BEGIN:
 | |
|       // check to see if we should rollup
 | |
|       if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false)) {
 | |
|         return FALSE;
 | |
|       }
 | |
|       msg = eTouchStart;
 | |
|       break;
 | |
|     case GDK_TOUCH_UPDATE:
 | |
|       msg = eTouchMove;
 | |
|       // Start dragging when motion events happens in the dragging area
 | |
|       if (mWindowShouldStartDragging) {
 | |
|         mWindowShouldStartDragging = false;
 | |
|         GdkWindow* gdk_window = gdk_window_get_toplevel(mGdkWindow);
 | |
|         MOZ_ASSERT(gdk_window,
 | |
|                    "gdk_window_get_toplevel should not return null");
 | |
| 
 | |
|         LOG("  start window dragging window\n");
 | |
|         gdk_window_begin_move_drag(gdk_window, 1, aEvent->x_root,
 | |
|                                    aEvent->y_root, aEvent->time);
 | |
| 
 | |
|         // Cancel the event sequence. gdk will steal all subsequent events
 | |
|         // (including TOUCH_END).
 | |
|         msg = eTouchCancel;
 | |
|       }
 | |
|       break;
 | |
|     case GDK_TOUCH_END:
 | |
|       msg = eTouchEnd;
 | |
|       if (mWindowShouldStartDragging) {
 | |
|         LOG("  end of window dragging window\n");
 | |
|         mWindowShouldStartDragging = false;
 | |
|       }
 | |
|       break;
 | |
|     case GDK_TOUCH_CANCEL:
 | |
|       msg = eTouchCancel;
 | |
|       break;
 | |
|     default:
 | |
|       return FALSE;
 | |
|   }
 | |
| 
 | |
|   const LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent);
 | |
| 
 | |
|   int32_t id;
 | |
|   RefPtr<dom::Touch> touch;
 | |
|   if (mTouches.Remove(aEvent->sequence, getter_AddRefs(touch))) {
 | |
|     id = touch->mIdentifier;
 | |
|   } else {
 | |
|     id = ++gLastTouchID & 0x7FFFFFFF;
 | |
|   }
 | |
| 
 | |
|   touch =
 | |
|       new dom::Touch(id, touchPoint, LayoutDeviceIntPoint(1, 1), 0.0f, 0.0f);
 | |
| 
 | |
|   WidgetTouchEvent event(true, msg, this);
 | |
|   KeymapWrapper::InitInputEvent(event, aEvent->state);
 | |
| 
 | |
|   if (msg == eTouchStart || msg == eTouchMove) {
 | |
|     mTouches.InsertOrUpdate(aEvent->sequence, std::move(touch));
 | |
|     // add all touch points to event object
 | |
|     for (const auto& data : mTouches.Values()) {
 | |
|       event.mTouches.AppendElement(new dom::Touch(*data));
 | |
|     }
 | |
|   } else if (msg == eTouchEnd || msg == eTouchCancel) {
 | |
|     *event.mTouches.AppendElement() = std::move(touch);
 | |
|   }
 | |
| 
 | |
|   nsIWidget::ContentAndAPZEventStatus eventStatus = DispatchInputEvent(&event);
 | |
| 
 | |
|   // There's a chance that we are in drag area and the event is not consumed
 | |
|   // by something on it.
 | |
|   if (msg == eTouchStart && mDraggableRegion.Contains(touchPoint) &&
 | |
|       eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
 | |
|     mWindowShouldStartDragging = true;
 | |
|   }
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| // Return true if toplevel window is transparent.
 | |
| // It's transparent when we're running on composited screens
 | |
| // and we can draw main window without system titlebar.
 | |
| bool nsWindow::IsToplevelWindowTransparent() {
 | |
|   static bool transparencyConfigured = false;
 | |
| 
 | |
|   if (!transparencyConfigured) {
 | |
|     if (gdk_screen_is_composited(gdk_screen_get_default())) {
 | |
|       // Some Gtk+ themes use non-rectangular toplevel windows. To fully
 | |
|       // support such themes we need to make toplevel window transparent
 | |
|       // with ARGB visual.
 | |
|       // It may cause performanance issue so make it configurable
 | |
|       // and enable it by default for selected window managers.
 | |
|       if (Preferences::HasUserValue("mozilla.widget.use-argb-visuals")) {
 | |
|         // argb visual is explicitly required so use it
 | |
|         sTransparentMainWindow =
 | |
|             Preferences::GetBool("mozilla.widget.use-argb-visuals");
 | |
|       } else {
 | |
|         // Enable transparent toplevel window if we can draw main window
 | |
|         // without system titlebar as Gtk+ themes use titlebar round corners.
 | |
|         sTransparentMainWindow =
 | |
|             GetSystemGtkWindowDecoration() != GTK_DECORATION_NONE;
 | |
|       }
 | |
|     }
 | |
|     transparencyConfigured = true;
 | |
|   }
 | |
| 
 | |
|   return sTransparentMainWindow;
 | |
| }
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
| // Configure GL visual on X11.
 | |
| bool nsWindow::ConfigureX11GLVisual() {
 | |
|   auto* screen = gtk_widget_get_screen(mShell);
 | |
|   int visualId = 0;
 | |
|   bool haveVisual = false;
 | |
| 
 | |
|   if (gfxVars::UseEGL()) {
 | |
|     haveVisual = GLContextEGL::FindVisual(&visualId);
 | |
|   }
 | |
| 
 | |
|   // We are on GLX or use it as a fallback on Mesa, see
 | |
|   // https://gitlab.freedesktop.org/mesa/mesa/-/issues/149
 | |
|   if (!haveVisual) {
 | |
|     auto* display = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(mShell));
 | |
|     int screenNumber = GDK_SCREEN_XNUMBER(screen);
 | |
|     haveVisual = GLContextGLX::FindVisual(display, screenNumber, &visualId);
 | |
|   }
 | |
| 
 | |
|   GdkVisual* gdkVisual = nullptr;
 | |
|   if (haveVisual) {
 | |
|     // If we're using CSD, rendering will go through mContainer, but
 | |
|     // it will inherit this visual as it is a child of mShell.
 | |
|     gdkVisual = gdk_x11_screen_lookup_visual(screen, visualId);
 | |
|   }
 | |
|   if (!gdkVisual) {
 | |
|     NS_WARNING("We're missing X11 Visual!");
 | |
|     // We try to use a fallback alpha visual
 | |
|     GdkScreen* screen = gtk_widget_get_screen(mShell);
 | |
|     gdkVisual = gdk_screen_get_rgba_visual(screen);
 | |
|   }
 | |
|   if (gdkVisual) {
 | |
|     gtk_widget_set_visual(mShell, gdkVisual);
 | |
|     mHasAlphaVisual = true;
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| nsAutoCString nsWindow::GetFrameTag() const {
 | |
|   if (nsIFrame* frame = GetFrame()) {
 | |
| #ifdef DEBUG_FRAME_DUMP
 | |
|     return frame->ListTag();
 | |
| #else
 | |
|     nsAutoCString buf;
 | |
|     buf.AppendPrintf("Frame(%p)", frame);
 | |
|     if (nsIContent* content = frame->GetContent()) {
 | |
|       buf.Append(' ');
 | |
|       AppendUTF16toUTF8(content->NodeName(), buf);
 | |
|     }
 | |
|     return buf;
 | |
| #endif
 | |
|   }
 | |
|   return nsAutoCString("(no frame)");
 | |
| }
 | |
| 
 | |
| nsCString nsWindow::GetPopupTypeName() {
 | |
|   switch (mPopupType) {
 | |
|     case PopupType::Menu:
 | |
|       return nsCString("Menu");
 | |
|     case PopupType::Tooltip:
 | |
|       return nsCString("Tooltip");
 | |
|     case PopupType::Panel:
 | |
|       return nsCString("Panel/Utility");
 | |
|     default:
 | |
|       return nsCString("Unknown");
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Disables all rendering of GtkWidget from Gtk side.
 | |
| // We do our best to persuade Gtk/Gdk to ignore all painting
 | |
| // to the widget.
 | |
| static void GtkWidgetDisableUpdates(GtkWidget* aWidget) {
 | |
|   // Clear exposure mask - it disabled synthesized events.
 | |
|   GdkWindow* window = gtk_widget_get_window(aWidget);
 | |
|   gdk_window_set_events(window, (GdkEventMask)(gdk_window_get_events(window) &
 | |
|                                                (~GDK_EXPOSURE_MASK)));
 | |
| 
 | |
|   // Remove before/after paint handles from frame clock.
 | |
|   // It disables widget content updates.
 | |
|   GdkFrameClock* frame_clock = gdk_window_get_frame_clock(window);
 | |
|   g_signal_handlers_disconnect_by_data(frame_clock, window);
 | |
| }
 | |
| 
 | |
| Window nsWindow::GetX11Window() {
 | |
| #ifdef MOZ_X11
 | |
|   if (GdkIsX11Display()) {
 | |
|     return mGdkWindow ? gdk_x11_window_get_xid(mGdkWindow) : X11None;
 | |
|   }
 | |
| #endif
 | |
|   return (Window) nullptr;
 | |
| }
 | |
| 
 | |
| void nsWindow::EnsureGdkWindow() {
 | |
|   if (!mGdkWindow) {
 | |
|     mGdkWindow = gtk_widget_get_window(GTK_WIDGET(mContainer));
 | |
|     g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", this);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool nsWindow::GetShapedState() {
 | |
|   return mIsTransparent && !mHasAlphaVisual && !mTransparencyBitmapForTitlebar;
 | |
| }
 | |
| 
 | |
| void nsWindow::ConfigureCompositor() {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mCompositorState == COMPOSITOR_ENABLED);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mIsMapped);
 | |
| 
 | |
|   LOG("nsWindow::ConfigureCompositor()");
 | |
|   auto startCompositing = [self = RefPtr{this}, this]() -> void {
 | |
|     LOG("  moz_container_wayland_add_or_fire_initial_draw_callback "
 | |
|         "ConfigureCompositor");
 | |
| 
 | |
|     // too late
 | |
|     if (mIsDestroyed || !mIsMapped) {
 | |
|       LOG("  quit, mIsDestroyed = %d mIsMapped = %d", !!mIsDestroyed,
 | |
|           mIsMapped);
 | |
|       return;
 | |
|     }
 | |
|     // Compositor will be resumed later by ResumeCompositorFlickering().
 | |
|     if (mCompositorState == COMPOSITOR_PAUSED_FLICKERING) {
 | |
|       LOG("  quit, will be resumed by ResumeCompositorFlickering.");
 | |
|       return;
 | |
|     }
 | |
|     // Compositor will be resumed at nsWindow::SetCompositorWidgetDelegate().
 | |
|     if (!mCompositorWidgetDelegate) {
 | |
|       LOG("  quit, missing mCompositorWidgetDelegate");
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     ResumeCompositorImpl();
 | |
|   };
 | |
| 
 | |
|   if (GdkIsWaylandDisplay()) {
 | |
| #ifdef MOZ_WAYLAND
 | |
|     moz_container_wayland_add_or_fire_initial_draw_callback(mContainer,
 | |
|                                                             startCompositing);
 | |
| #endif
 | |
|   } else {
 | |
|     startCompositing();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::ConfigureGdkWindow() {
 | |
|   LOG("nsWindow::ConfigureGdkWindow()");
 | |
| 
 | |
|   EnsureGdkWindow();
 | |
|   OnScaleChanged(/* aNotify = */ false);
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
|   if (GdkIsX11Display()) {
 | |
|     GdkVisual* gdkVisual = gdk_window_get_visual(mGdkWindow);
 | |
|     Visual* visual = gdk_x11_visual_get_xvisual(gdkVisual);
 | |
|     int depth = gdk_visual_get_depth(gdkVisual);
 | |
|     mSurfaceProvider.Initialize(GetX11Window(), visual, depth,
 | |
|                                 GetShapedState());
 | |
| 
 | |
|     // Set window manager hint to keep fullscreen windows composited.
 | |
|     //
 | |
|     // If the window were to get unredirected, there could be visible
 | |
|     // tearing because Gecko does not align its framebuffer updates with
 | |
|     // vblank.
 | |
|     SetCompositorHint(GTK_WIDGET_COMPOSIDED_ENABLED);
 | |
|   }
 | |
| #endif
 | |
| #ifdef MOZ_WAYLAND
 | |
|   if (GdkIsWaylandDisplay()) {
 | |
|     mSurfaceProvider.Initialize(this);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   if (mIsDragPopup) {
 | |
|     if (GdkIsWaylandDisplay()) {
 | |
|       // Disable painting to the widget on Wayland as we paint directly to the
 | |
|       // widget. Wayland compositors does not paint wl_subsurface
 | |
|       // of D&D widget.
 | |
|       if (GtkWidget* parent = gtk_widget_get_parent(mShell)) {
 | |
|         GtkWidgetDisableUpdates(parent);
 | |
|       }
 | |
|       GtkWidgetDisableUpdates(mShell);
 | |
|       GtkWidgetDisableUpdates(GTK_WIDGET(mContainer));
 | |
|     } else {
 | |
|       // Disable rendering of parent container on X11 to avoid flickering.
 | |
|       if (GtkWidget* parent = gtk_widget_get_parent(mShell)) {
 | |
|         gtk_widget_set_opacity(parent, 0.0);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (mWindowType == WindowType::Popup) {
 | |
|     if (mNoAutoHide) {
 | |
|       gint wmd = ConvertBorderStyles(mBorderStyle);
 | |
|       if (wmd != -1) {
 | |
|         gdk_window_set_decorations(mGdkWindow, (GdkWMDecoration)wmd);
 | |
|       }
 | |
|     }
 | |
|     // If the popup ignores mouse events, set an empty input shape.
 | |
|     SetInputRegion(mInputRegion);
 | |
|   }
 | |
| 
 | |
|   RefreshWindowClass();
 | |
| 
 | |
|   // We're not mapped yet but we have already created compositor.
 | |
|   if (mCompositorWidgetDelegate) {
 | |
|     ConfigureCompositor();
 | |
|   }
 | |
| 
 | |
|   LOG("  finished, new GdkWindow %p XID 0x%lx\n", mGdkWindow, GetX11Window());
 | |
| }
 | |
| 
 | |
| void nsWindow::ReleaseGdkWindow() {
 | |
|   LOG("nsWindow::ReleaseGdkWindow()");
 | |
| 
 | |
|   DestroyChildWindows();
 | |
| 
 | |
|   if (mCompositorWidgetDelegate) {
 | |
|     mCompositorWidgetDelegate->DisableRendering();
 | |
|   }
 | |
| 
 | |
|   if (mGdkWindow) {
 | |
|     g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", nullptr);
 | |
|     mGdkWindow = nullptr;
 | |
|   }
 | |
| 
 | |
|   mSurfaceProvider.CleanupResources();
 | |
| }
 | |
| 
 | |
| nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
 | |
|                           const LayoutDeviceIntRect& aRect,
 | |
|                           widget::InitData* aInitData) {
 | |
|   LOG("nsWindow::Create\n");
 | |
| 
 | |
|   // only set the base parent if we're going to be a dialog or a
 | |
|   // toplevel
 | |
|   nsIWidget* baseParent =
 | |
|       aInitData && (aInitData->mWindowType == WindowType::Dialog ||
 | |
|                     aInitData->mWindowType == WindowType::TopLevel ||
 | |
|                     aInitData->mWindowType == WindowType::Invisible)
 | |
|           ? nullptr
 | |
|           : aParent;
 | |
| 
 | |
| #ifdef ACCESSIBILITY
 | |
|   // Send a DBus message to check whether a11y is enabled
 | |
|   a11y::PreInit();
 | |
| #endif
 | |
| 
 | |
| #ifdef MOZ_WAYLAND
 | |
|   // Ensure that KeymapWrapper is created on Wayland as we need it for
 | |
|   // keyboard focus tracking.
 | |
|   if (GdkIsWaylandDisplay()) {
 | |
|     KeymapWrapper::EnsureInstance();
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   // Ensure that the toolkit is created.
 | |
|   nsGTKToolkit::GetToolkit();
 | |
| 
 | |
|   // initialize all the common bits of this class
 | |
|   BaseCreate(baseParent, aInitData);
 | |
| 
 | |
|   // and do our common creation
 | |
|   mParent = aParent;
 | |
|   // save our bounds
 | |
|   mBounds = aRect;
 | |
|   LOG("  mBounds: x:%d y:%d w:%d h:%d\n", mBounds.x, mBounds.y, mBounds.width,
 | |
|       mBounds.height);
 | |
| 
 | |
|   ConstrainSize(&mBounds.width, &mBounds.height);
 | |
|   mLastSizeRequest = mBounds.Size();
 | |
| 
 | |
|   bool popupNeedsAlphaVisual = mWindowType == WindowType::Popup &&
 | |
|                                (aInitData && aInitData->mTransparencyMode ==
 | |
|                                                  TransparencyMode::Transparent);
 | |
| 
 | |
|   // Figure out our parent window - only used for WindowType::Child
 | |
|   nsWindow* parentnsWindow = nullptr;
 | |
| 
 | |
|   if (aParent) {
 | |
|     parentnsWindow = static_cast<nsWindow*>(aParent);
 | |
|   } else if (aNativeParent && GDK_IS_WINDOW(aNativeParent)) {
 | |
|     parentnsWindow = get_window_for_gdk_window(GDK_WINDOW(aNativeParent));
 | |
|     if (!parentnsWindow) {
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (mWindowType == WindowType::Child) {
 | |
|     // We don't support WindowType::Child directly but emulate it by popup
 | |
|     // windows.
 | |
|     mWindowType = WindowType::Popup;
 | |
|     if (!parentnsWindow) {
 | |
|       if (aNativeParent && GTK_IS_CONTAINER(aNativeParent)) {
 | |
|         parentnsWindow = get_window_for_gtk_widget(GTK_WIDGET(aNativeParent));
 | |
|       }
 | |
|     }
 | |
|     mIsChildWindow = true;
 | |
|     LOG("  child widget, switch to popup. parent nsWindow %p", parentnsWindow);
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT_IF(mWindowType == WindowType::Popup, parentnsWindow);
 | |
| 
 | |
|   if (mWindowType != WindowType::Dialog && mWindowType != WindowType::Popup &&
 | |
|       mWindowType != WindowType::TopLevel &&
 | |
|       mWindowType != WindowType::Invisible) {
 | |
|     MOZ_ASSERT_UNREACHABLE("Unexpected eWindowType");
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   mAlwaysOnTop = aInitData && aInitData->mAlwaysOnTop;
 | |
|   // mNoAutoHide seems to be always false here.
 | |
|   // The mNoAutoHide state is set later on nsMenuPopupFrame level
 | |
|   // and can be changed so we use WaylandPopupIsPermanent() to get
 | |
|   // recent popup config (Bug 1728952).
 | |
|   mNoAutoHide = aInitData && aInitData->mNoAutoHide;
 | |
| 
 | |
|   // Popups that are not noautohide are only temporary. The are used
 | |
|   // for menus and the like and disappear when another window is used.
 | |
|   // For most popups, use the standard GtkWindowType GTK_WINDOW_POPUP,
 | |
|   // which will use a Window with the override-redirect attribute
 | |
|   // (for temporary windows).
 | |
|   // For long-lived windows, their stacking order is managed by the
 | |
|   // window manager, as indicated by GTK_WINDOW_TOPLEVEL.
 | |
|   // For Wayland we have to always use GTK_WINDOW_POPUP to control
 | |
|   // popup window position.
 | |
|   GtkWindowType type = GTK_WINDOW_TOPLEVEL;
 | |
|   if (mWindowType == WindowType::Popup) {
 | |
|     MOZ_ASSERT(aInitData);
 | |
|     type = GTK_WINDOW_POPUP;
 | |
|     if (GdkIsX11Display() && mNoAutoHide) {
 | |
|       type = GTK_WINDOW_TOPLEVEL;
 | |
|     }
 | |
|   }
 | |
|   mShell = gtk_window_new(type);
 | |
| 
 | |
|   // Ensure gfxPlatform is initialized, since that is what initializes
 | |
|   // gfxVars, used below.
 | |
|   Unused << gfxPlatform::GetPlatform();
 | |
| 
 | |
|   if (IsTopLevelWindowType()) {
 | |
|     mGtkWindowDecoration = GetSystemGtkWindowDecoration();
 | |
|     // Inherit initial scale from our parent, or use the default monitor scale
 | |
|     // otherwise.
 | |
|     mCeiledScaleFactor = parentnsWindow
 | |
|                              ? int32_t(parentnsWindow->mCeiledScaleFactor)
 | |
|                              : ScreenHelperGTK::GetGTKMonitorScaleFactor();
 | |
|   }
 | |
| 
 | |
|   // Don't use transparency for PictureInPicture windows.
 | |
|   bool toplevelNeedsAlphaVisual = false;
 | |
|   if (mWindowType == WindowType::TopLevel && !mIsPIPWindow) {
 | |
|     toplevelNeedsAlphaVisual = IsToplevelWindowTransparent();
 | |
|   }
 | |
| 
 | |
|   bool isGLVisualSet = false;
 | |
|   mIsAccelerated = ComputeShouldAccelerate();
 | |
| #ifdef MOZ_X11
 | |
|   if (GdkIsX11Display() && mIsAccelerated) {
 | |
|     isGLVisualSet = ConfigureX11GLVisual();
 | |
|   }
 | |
| #endif
 | |
|   if (!isGLVisualSet && (popupNeedsAlphaVisual || toplevelNeedsAlphaVisual)) {
 | |
|     // We're running on composited screen so we can use alpha visual
 | |
|     // for both toplevel and popups.
 | |
|     if (mCompositedScreen) {
 | |
|       GdkVisual* visual =
 | |
|           gdk_screen_get_rgba_visual(gtk_widget_get_screen(mShell));
 | |
|       if (visual) {
 | |
|         gtk_widget_set_visual(mShell, visual);
 | |
|         mHasAlphaVisual = true;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Use X shape mask to draw round corners of Firefox titlebar.
 | |
|   // We don't use shape masks any more as we switched to ARGB visual
 | |
|   // by default and non-compositing screens use solid-csd decorations
 | |
|   // without round corners.
 | |
|   // Leave the shape mask code here as it can be used to draw round
 | |
|   // corners on EGL (https://gitlab.freedesktop.org/mesa/mesa/-/issues/149)
 | |
|   // or when custom titlebar theme is used.
 | |
|   mTransparencyBitmapForTitlebar = TitlebarUseShapeMask();
 | |
| 
 | |
|   // We have a toplevel window with transparency.
 | |
|   // Calls to UpdateTitlebarTransparencyBitmap() from OnExposeEvent()
 | |
|   // occur before SetTransparencyMode() receives TransparencyMode::Transparent
 | |
|   // from layout, so set mIsTransparent here.
 | |
|   if (mWindowType == WindowType::TopLevel &&
 | |
|       (mHasAlphaVisual || mTransparencyBitmapForTitlebar)) {
 | |
|     mIsTransparent = true;
 | |
|   }
 | |
| 
 | |
|   // We only move a general managed toplevel window if someone has
 | |
|   // actually placed the window somewhere.  If no placement has taken
 | |
|   // place, we just let the window manager Do The Right Thing.
 | |
|   if (AreBoundsSane()) {
 | |
|     GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
 | |
|     LOG("nsWindow::Create() Initial resize to %d x %d\n", size.width,
 | |
|         size.height);
 | |
|     gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
 | |
|   }
 | |
|   if (mIsPIPWindow) {
 | |
|     LOG("    Is PIP Window\n");
 | |
|     gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_UTILITY);
 | |
|   } else if (mWindowType == WindowType::Dialog) {
 | |
|     mGtkWindowRoleName = "Dialog";
 | |
| 
 | |
|     SetDefaultIcon();
 | |
|     gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_DIALOG);
 | |
|     LOG("nsWindow::Create(): dialog");
 | |
|     if (parentnsWindow) {
 | |
|       GtkWindowSetTransientFor(GTK_WINDOW(mShell),
 | |
|                                GTK_WINDOW(parentnsWindow->GetGtkWidget()));
 | |
|       LOG("    set parent window [%p]\n", parentnsWindow);
 | |
|     }
 | |
|   } else if (mWindowType == WindowType::Popup) {
 | |
|     MOZ_ASSERT(aInitData);
 | |
|     mGtkWindowRoleName = "Popup";
 | |
| 
 | |
|     LOG("nsWindow::Create() Popup");
 | |
| 
 | |
|     if (mNoAutoHide) {
 | |
|       // ... but the window manager does not decorate this window,
 | |
|       // nor provide a separate taskbar icon.
 | |
|       if (mBorderStyle == BorderStyle::Default) {
 | |
|         gtk_window_set_decorated(GTK_WINDOW(mShell), FALSE);
 | |
|       } else {
 | |
|         bool decorate = bool(mBorderStyle & BorderStyle::Title);
 | |
|         gtk_window_set_decorated(GTK_WINDOW(mShell), decorate);
 | |
|         if (decorate) {
 | |
|           gtk_window_set_deletable(GTK_WINDOW(mShell),
 | |
|                                    bool(mBorderStyle & BorderStyle::Close));
 | |
|         }
 | |
|       }
 | |
|       gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mShell), TRUE);
 | |
|       // Element focus is managed by the parent window so the
 | |
|       // WM_HINTS input field is set to False to tell the window
 | |
|       // manager not to set input focus to this window ...
 | |
|       gtk_window_set_accept_focus(GTK_WINDOW(mShell), FALSE);
 | |
| #ifdef MOZ_X11
 | |
|       // ... but when the window manager offers focus through
 | |
|       // WM_TAKE_FOCUS, focus is requested on the parent window.
 | |
|       if (GdkIsX11Display()) {
 | |
|         gtk_widget_realize(mShell);
 | |
|         gdk_window_add_filter(GetToplevelGdkWindow(), popup_take_focus_filter,
 | |
|                               nullptr);
 | |
|       }
 | |
| #endif
 | |
|     }
 | |
| 
 | |
|     if (aInitData->mIsDragPopup) {
 | |
|       gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_DND);
 | |
|       mIsDragPopup = true;
 | |
|       LOG("nsWindow::Create() Drag popup\n");
 | |
|     } else if (GdkIsX11Display()) {
 | |
|       // Set the window hints on X11 only. Wayland popups are configured
 | |
|       // at WaylandPopupConfigure().
 | |
|       GdkWindowTypeHint gtkTypeHint;
 | |
|       switch (mPopupType) {
 | |
|         case PopupType::Menu:
 | |
|           gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
 | |
|           break;
 | |
|         case PopupType::Tooltip:
 | |
|           gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
 | |
|           break;
 | |
|         default:
 | |
|           gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
 | |
|           break;
 | |
|       }
 | |
|       gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
 | |
|       LOG("nsWindow::Create() popup type %s", GetPopupTypeName().get());
 | |
|     }
 | |
|     if (parentnsWindow) {
 | |
|       LOG("    set parent window [%p] %s", parentnsWindow,
 | |
|           parentnsWindow->mGtkWindowRoleName.get());
 | |
|       GtkWindow* parentWidget = GTK_WINDOW(parentnsWindow->GetGtkWidget());
 | |
|       GtkWindowSetTransientFor(GTK_WINDOW(mShell), parentWidget);
 | |
| 
 | |
|       // If popup parent is modal, we need to make popup modal too.
 | |
|       if (mPopupType != PopupType::Tooltip &&
 | |
|           gtk_window_get_modal(parentWidget)) {
 | |
|         gtk_window_set_modal(GTK_WINDOW(mShell), true);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // We need realized mShell at NativeMoveResize().
 | |
|     gtk_widget_realize(mShell);
 | |
| 
 | |
|     // With popup windows, we want to set their position.
 | |
|     // Place them immediately on X11 and save initial popup position
 | |
|     // on Wayland as we place Wayland popup on show.
 | |
|     if (GdkIsX11Display()) {
 | |
|       NativeMoveResize(/* move */ true, /* resize */ false);
 | |
|     } else if (AreBoundsSane()) {
 | |
|       GdkRectangle rect = DevicePixelsToGdkRectRoundOut(mBounds);
 | |
|       mPopupPosition = {rect.x, rect.y};
 | |
|     }
 | |
|   } else {  // must be WindowType::TopLevel
 | |
|     mGtkWindowRoleName = "Toplevel";
 | |
|     SetDefaultIcon();
 | |
| 
 | |
|     LOG("nsWindow::Create() Toplevel\n");
 | |
| 
 | |
|     // each toplevel window gets its own window group
 | |
|     GtkWindowGroup* group = gtk_window_group_new();
 | |
|     gtk_window_group_add_window(group, GTK_WINDOW(mShell));
 | |
|     g_object_unref(group);
 | |
|   }
 | |
| 
 | |
|   if (mAlwaysOnTop) {
 | |
|     gtk_window_set_keep_above(GTK_WINDOW(mShell), TRUE);
 | |
|   }
 | |
| 
 | |
|   // It is important that this happens before the realize() call below, so that
 | |
|   // we don't get bogus CSD margins on Wayland, see bug 1794577.
 | |
|   mUndecorated = IsAlwaysUndecoratedWindow();
 | |
|   if (mUndecorated) {
 | |
|     LOG("    Is undecorated Window\n");
 | |
|     gtk_window_set_titlebar(GTK_WINDOW(mShell), gtk_fixed_new());
 | |
|     gtk_window_set_decorated(GTK_WINDOW(mShell), false);
 | |
|   }
 | |
| 
 | |
|   // Create a container to hold child windows and child GtkWidgets.
 | |
|   GtkWidget* container = moz_container_new();
 | |
|   mContainer = MOZ_CONTAINER(container);
 | |
| 
 | |
|   // Prevent GtkWindow from painting a background to avoid flickering.
 | |
|   gtk_widget_set_app_paintable(
 | |
|       GTK_WIDGET(mContainer),
 | |
|       StaticPrefs::widget_transparent_windows_AtStartup());
 | |
| 
 | |
|   gtk_widget_add_events(GTK_WIDGET(mContainer), kEvents);
 | |
|   gtk_widget_add_events(mShell, GDK_PROPERTY_CHANGE_MASK);
 | |
|   gtk_widget_set_app_paintable(
 | |
|       mShell, StaticPrefs::widget_transparent_windows_AtStartup());
 | |
| 
 | |
|   if (mTransparencyBitmapForTitlebar) {
 | |
|     moz_container_force_default_visual(mContainer);
 | |
|   }
 | |
| 
 | |
|   // If we draw to mContainer window then configure it now because
 | |
|   // gtk_container_add() realizes the child widget.
 | |
|   gtk_widget_set_has_window(container, true);
 | |
|   gtk_container_add(GTK_CONTAINER(mShell), container);
 | |
| 
 | |
|   // alwaysontop windows are generally used for peripheral indicators,
 | |
|   // so we don't focus them by default.
 | |
|   if (mAlwaysOnTop) {
 | |
|     gtk_window_set_focus_on_map(GTK_WINDOW(mShell), FALSE);
 | |
|   }
 | |
| 
 | |
|   gtk_widget_realize(container);
 | |
| 
 | |
|   // make sure this is the focus widget in the container
 | |
|   gtk_widget_show(container);
 | |
| 
 | |
|   if (!mAlwaysOnTop) {
 | |
|     gtk_widget_grab_focus(container);
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_WAYLAND
 | |
|   if (mIsDragPopup && GdkIsWaylandDisplay()) {
 | |
|     LOG("  set commit to parent");
 | |
|     moz_container_wayland_set_commit_to_parent(mContainer);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   if (mWindowType == WindowType::TopLevel && gKioskMode) {
 | |
|     if (gKioskMonitor != -1) {
 | |
|       mKioskMonitor = Some(gKioskMonitor);
 | |
|       LOG("  set kiosk mode monitor %d", mKioskMonitor.value());
 | |
|     } else {
 | |
|       LOG("  set kiosk mode");
 | |
|     }
 | |
|     // Kiosk mode always use fullscreen.
 | |
|     MakeFullScreen(/* aFullScreen */ true);
 | |
|   }
 | |
| 
 | |
|   if (mWindowType == WindowType::Popup) {
 | |
|     MOZ_ASSERT(aInitData);
 | |
|     // gdk does not automatically set the cursor for "temporary"
 | |
|     // windows, which are what gtk uses for popups.
 | |
| 
 | |
|     // force SetCursor to actually set the cursor, even though our internal
 | |
|     // state indicates that we already have the standard cursor.
 | |
|     mUpdateCursor = true;
 | |
|     SetCursor(Cursor{eCursor_standard});
 | |
|   }
 | |
| 
 | |
|   if (mIsChildWindow && parentnsWindow) {
 | |
|     GdkWindow* window = GetToplevelGdkWindow();
 | |
|     GdkWindow* parentWindow = parentnsWindow->GetToplevelGdkWindow();
 | |
|     LOG("  child GdkWindow %p set parent GdkWindow %p", window, parentWindow);
 | |
|     gdk_window_reparent(window, parentWindow,
 | |
|                         DevicePixelsToGdkCoordRoundDown(mBounds.x),
 | |
|                         DevicePixelsToGdkCoordRoundDown(mBounds.y));
 | |
|   }
 | |
| 
 | |
|   // Also label mShell toplevel window,
 | |
|   // property_notify_event_cb callback also needs to find its way home
 | |
|   g_object_set_data(G_OBJECT(GetToplevelGdkWindow()), "nsWindow", this);
 | |
|   g_object_set_data(G_OBJECT(mContainer), "nsWindow", this);
 | |
|   g_object_set_data(G_OBJECT(mShell), "nsWindow", this);
 | |
| 
 | |
|   // attach listeners for events
 | |
|   g_signal_connect(mShell, "configure_event", G_CALLBACK(configure_event_cb),
 | |
|                    nullptr);
 | |
|   g_signal_connect(mShell, "delete_event", G_CALLBACK(delete_event_cb),
 | |
|                    nullptr);
 | |
|   g_signal_connect(mShell, "window_state_event",
 | |
|                    G_CALLBACK(window_state_event_cb), nullptr);
 | |
|   g_signal_connect(mShell, "visibility-notify-event",
 | |
|                    G_CALLBACK(visibility_notify_event_cb), nullptr);
 | |
|   g_signal_connect(mShell, "check-resize", G_CALLBACK(check_resize_cb),
 | |
|                    nullptr);
 | |
|   g_signal_connect(mShell, "composited-changed",
 | |
|                    G_CALLBACK(widget_composited_changed_cb), nullptr);
 | |
|   g_signal_connect(mShell, "property-notify-event",
 | |
|                    G_CALLBACK(property_notify_event_cb), nullptr);
 | |
| 
 | |
|   if (mWindowType == WindowType::TopLevel) {
 | |
|     g_signal_connect_after(mShell, "size_allocate",
 | |
|                            G_CALLBACK(toplevel_window_size_allocate_cb),
 | |
|                            nullptr);
 | |
|   }
 | |
| 
 | |
|   GdkScreen* screen = gtk_widget_get_screen(mShell);
 | |
|   if (!g_signal_handler_find(screen, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
 | |
|                              FuncToGpointer(screen_composited_changed_cb),
 | |
|                              nullptr)) {
 | |
|     g_signal_connect(screen, "composited-changed",
 | |
|                      G_CALLBACK(screen_composited_changed_cb), nullptr);
 | |
|   }
 | |
| 
 | |
|   gtk_drag_dest_set((GtkWidget*)mShell, (GtkDestDefaults)0, nullptr, 0,
 | |
|                     (GdkDragAction)0);
 | |
|   g_signal_connect(mShell, "drag_motion", G_CALLBACK(drag_motion_event_cb),
 | |
|                    nullptr);
 | |
|   g_signal_connect(mShell, "drag_leave", G_CALLBACK(drag_leave_event_cb),
 | |
|                    nullptr);
 | |
|   g_signal_connect(mShell, "drag_drop", G_CALLBACK(drag_drop_event_cb),
 | |
|                    nullptr);
 | |
|   g_signal_connect(mShell, "drag_data_received",
 | |
|                    G_CALLBACK(drag_data_received_event_cb), nullptr);
 | |
| 
 | |
|   GtkSettings* default_settings = gtk_settings_get_default();
 | |
|   g_signal_connect_after(default_settings, "notify::gtk-xft-dpi",
 | |
|                          G_CALLBACK(settings_xft_dpi_changed_cb), this);
 | |
| 
 | |
|   // Widget signals
 | |
|   g_signal_connect(mContainer, "map", G_CALLBACK(widget_map_cb), nullptr);
 | |
|   g_signal_connect(mContainer, "unmap", G_CALLBACK(widget_unmap_cb), nullptr);
 | |
|   g_signal_connect(mContainer, "unrealize", G_CALLBACK(widget_unrealize_cb),
 | |
|                    nullptr);
 | |
|   g_signal_connect_after(mContainer, "size_allocate",
 | |
|                          G_CALLBACK(size_allocate_cb), nullptr);
 | |
|   g_signal_connect(mContainer, "hierarchy-changed",
 | |
|                    G_CALLBACK(hierarchy_changed_cb), nullptr);
 | |
|   g_signal_connect(mContainer, "notify::scale-factor",
 | |
|                    G_CALLBACK(scale_changed_cb), nullptr);
 | |
|   // Initialize mHasMappedToplevel.
 | |
|   hierarchy_changed_cb(GTK_WIDGET(mContainer), nullptr);
 | |
|   // Expose, focus, key, and drag events are sent even to GTK_NO_WINDOW
 | |
|   // widgets.
 | |
|   g_signal_connect(G_OBJECT(mContainer), "draw", G_CALLBACK(expose_event_cb),
 | |
|                    nullptr);
 | |
|   g_signal_connect(mContainer, "focus_in_event", G_CALLBACK(focus_in_event_cb),
 | |
|                    nullptr);
 | |
|   g_signal_connect(mContainer, "focus_out_event",
 | |
|                    G_CALLBACK(focus_out_event_cb), nullptr);
 | |
|   g_signal_connect(mContainer, "key_press_event",
 | |
|                    G_CALLBACK(key_press_event_cb), nullptr);
 | |
|   g_signal_connect(mContainer, "key_release_event",
 | |
|                    G_CALLBACK(key_release_event_cb), nullptr);
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
|   if (GdkIsX11Display()) {
 | |
|     gtk_widget_set_double_buffered(GTK_WIDGET(mContainer), FALSE);
 | |
|   }
 | |
| #endif
 | |
| #ifdef MOZ_WAYLAND
 | |
|   // Initialize the window specific VsyncSource early in order to avoid races
 | |
|   // with BrowserParent::UpdateVsyncParentVsyncDispatcher().
 | |
|   // Only use for toplevel windows for now, see bug 1619246.
 | |
|   if (GdkIsWaylandDisplay() &&
 | |
|       StaticPrefs::widget_wayland_vsync_enabled_AtStartup() &&
 | |
|       IsTopLevelWindowType()) {
 | |
|     mWaylandVsyncSource = new WaylandVsyncSource(this);
 | |
|     mWaylandVsyncDispatcher = new VsyncDispatcher(mWaylandVsyncSource);
 | |
|     LOG_VSYNC("  created WaylandVsyncSource");
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   // We create input contexts for all containers, except for
 | |
|   // toplevel popup windows
 | |
|   if (mWindowType != WindowType::Popup) {
 | |
|     mIMContext = new IMContextWrapper(this);
 | |
|   }
 | |
| 
 | |
|   // These events are sent to the owning widget of the relevant window
 | |
|   // and propagate up to the first widget that handles the events, so we
 | |
|   // need only connect on mShell, if it exists, to catch events on its
 | |
|   // window and windows of mContainer.
 | |
|   g_signal_connect(mContainer, "enter-notify-event",
 | |
|                    G_CALLBACK(enter_notify_event_cb), nullptr);
 | |
|   g_signal_connect(mContainer, "leave-notify-event",
 | |
|                    G_CALLBACK(leave_notify_event_cb), nullptr);
 | |
|   g_signal_connect(mContainer, "motion-notify-event",
 | |
|                    G_CALLBACK(motion_notify_event_cb), nullptr);
 | |
|   g_signal_connect(mContainer, "button-press-event",
 | |
|                    G_CALLBACK(button_press_event_cb), nullptr);
 | |
|   g_signal_connect(mContainer, "button-release-event",
 | |
|                    G_CALLBACK(button_release_event_cb), nullptr);
 | |
|   g_signal_connect(mContainer, "scroll-event", G_CALLBACK(scroll_event_cb),
 | |
|                    nullptr);
 | |
|   if (gtk_check_version(3, 18, 0) == nullptr) {
 | |
|     g_signal_connect(mContainer, "event", G_CALLBACK(generic_event_cb),
 | |
|                      nullptr);
 | |
|   }
 | |
|   g_signal_connect(mContainer, "touch-event", G_CALLBACK(touch_event_cb),
 | |
|                    nullptr);
 | |
| 
 | |
|   LOG("  nsWindow type %d %s\n", int(mWindowType),
 | |
|       mIsPIPWindow ? "PIP window" : "");
 | |
|   LOG("  mShell %p (window %p) mContainer %p mGdkWindow %p XID 0x%lx\n", mShell,
 | |
|       GetToplevelGdkWindow(), mContainer, mGdkWindow, GetX11Window());
 | |
| 
 | |
|   // Set default application name when it's empty.
 | |
|   if (mGtkWindowAppName.IsEmpty()) {
 | |
|     mGtkWindowAppName = gAppData->name;
 | |
|   }
 | |
| 
 | |
|   mCreated = true;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsWindow::RefreshWindowClass(void) {
 | |
|   GdkWindow* gdkWindow = GetToplevelGdkWindow();
 | |
|   if (!gdkWindow) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!mGtkWindowRoleName.IsEmpty()) {
 | |
|     gdk_window_set_role(gdkWindow, mGtkWindowRoleName.get());
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
|   if (GdkIsX11Display()) {
 | |
|     XClassHint* class_hint = XAllocClassHint();
 | |
|     if (!class_hint) return;
 | |
| 
 | |
|     const char* res_name =
 | |
|         !mGtkWindowAppName.IsEmpty() ? mGtkWindowAppName.get() : gAppData->name;
 | |
| 
 | |
|     const char* res_class = !mGtkWindowAppClass.IsEmpty()
 | |
|                                 ? mGtkWindowAppClass.get()
 | |
|                                 : gdk_get_program_class();
 | |
| 
 | |
|     if (!res_name || !res_class) {
 | |
|       XFree(class_hint);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     class_hint->res_name = const_cast<char*>(res_name);
 | |
|     class_hint->res_class = const_cast<char*>(res_class);
 | |
| 
 | |
|     // Can't use gtk_window_set_wmclass() for this; it prints
 | |
|     // a warning & refuses to make the change.
 | |
|     GdkDisplay* display = gdk_display_get_default();
 | |
|     XSetClassHint(GDK_DISPLAY_XDISPLAY(display),
 | |
|                   gdk_x11_window_get_xid(gdkWindow), class_hint);
 | |
|     XFree(class_hint);
 | |
|   }
 | |
| #endif /* MOZ_X11 */
 | |
| 
 | |
| #ifdef MOZ_WAYLAND
 | |
|   static auto sGdkWaylandWindowSetApplicationId =
 | |
|       (void (*)(GdkWindow*, const char*))dlsym(
 | |
|           RTLD_DEFAULT, "gdk_wayland_window_set_application_id");
 | |
| 
 | |
|   if (GdkIsWaylandDisplay() && sGdkWaylandWindowSetApplicationId &&
 | |
|       !mGtkWindowAppClass.IsEmpty()) {
 | |
|     sGdkWaylandWindowSetApplicationId(gdkWindow, mGtkWindowAppClass.get());
 | |
|   }
 | |
| #endif /* MOZ_WAYLAND */
 | |
| }
 | |
| 
 | |
| void nsWindow::SetWindowClass(const nsAString& xulWinType,
 | |
|                               const nsAString& xulWinClass,
 | |
|                               const nsAString& xulWinName) {
 | |
|   if (!mShell) return;
 | |
| 
 | |
|   // If window type attribute is set, parse it into name and role
 | |
|   if (!xulWinType.IsEmpty()) {
 | |
|     char* res_name = ToNewCString(xulWinType, mozilla::fallible);
 | |
|     const char* role = nullptr;
 | |
| 
 | |
|     if (res_name) {
 | |
|       // Parse res_name into a name and role. Characters other than
 | |
|       // [A-Za-z0-9_-] are converted to '_'. Anything after the first
 | |
|       // colon is assigned to role; if there's no colon, assign the
 | |
|       // whole thing to both role and res_name.
 | |
|       for (char* c = res_name; *c; c++) {
 | |
|         if (':' == *c) {
 | |
|           *c = 0;
 | |
|           role = c + 1;
 | |
|         } else if (!isascii(*c) ||
 | |
|                    (!isalnum(*c) && ('_' != *c) && ('-' != *c))) {
 | |
|           *c = '_';
 | |
|         }
 | |
|       }
 | |
|       res_name[0] = (char)toupper(res_name[0]);
 | |
|       if (!role) role = res_name;
 | |
| 
 | |
|       mGtkWindowAppName = res_name;
 | |
|       mGtkWindowRoleName = role;
 | |
|       free(res_name);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If window class attribute is set, store it as app class
 | |
|   // If this attribute is not set, reset app class to default
 | |
|   if (!xulWinClass.IsEmpty()) {
 | |
|     CopyUTF16toUTF8(xulWinClass, mGtkWindowAppClass);
 | |
|   } else {
 | |
|     mGtkWindowAppClass = nullptr;
 | |
|   }
 | |
| 
 | |
|   // If window class attribute is set, store it as app name
 | |
|   // If both name and type are not set, reset app name to default
 | |
|   if (!xulWinName.IsEmpty()) {
 | |
|     CopyUTF16toUTF8(xulWinName, mGtkWindowAppName);
 | |
|   } else if (xulWinType.IsEmpty()) {
 | |
|     mGtkWindowAppClass = nullptr;
 | |
|   }
 | |
| 
 | |
|   RefreshWindowClass();
 | |
| }
 | |
| 
 | |
| nsAutoCString nsWindow::GetDebugTag() const {
 | |
|   nsAutoCString tag;
 | |
|   tag.AppendPrintf("[%p]", this);
 | |
|   return tag;
 | |
| }
 | |
| 
 | |
| void nsWindow::NativeMoveResize(bool aMoved, bool aResized) {
 | |
|   GdkPoint topLeft = [&] {
 | |
|     auto target = mBounds.TopLeft();
 | |
|     // gtk_window_move will undo the csd offset, but nothing else, so only add
 | |
|     // the client offset if drawing to the csd titlebar.
 | |
|     if (DrawsToCSDTitlebar()) {
 | |
|       target += mClientOffset;
 | |
|     }
 | |
|     return DevicePixelsToGdkPointRoundDown(target);
 | |
|   }();
 | |
|   GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mLastSizeRequest);
 | |
| 
 | |
|   LOG("nsWindow::NativeMoveResize move %d resize %d to %d,%d -> %d x %d\n",
 | |
|       aMoved, aResized, topLeft.x, topLeft.y, size.width, size.height);
 | |
| 
 | |
|   if (aResized && !AreBoundsSane()) {
 | |
|     LOG("  bounds are insane, hidding the window");
 | |
|     // We have been resized but to incorrect size.
 | |
|     // If someone has set this so that the needs show flag is false
 | |
|     // and it needs to be hidden, update the flag and hide the
 | |
|     // window.  This flag will be cleared the next time someone
 | |
|     // hides the window or shows it.  It also prevents us from
 | |
|     // calling NativeShow(false) excessively on the window which
 | |
|     // causes unneeded X traffic.
 | |
|     if (!mNeedsShow && mIsShown) {
 | |
|       mNeedsShow = true;
 | |
|       NativeShow(false);
 | |
|     }
 | |
|     if (aMoved) {
 | |
|       LOG("  moving to %d x %d", topLeft.x, topLeft.y);
 | |
|       gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y);
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Set position to hidden window on X11 may fail, so save the position
 | |
|   // and move it when it's shown.
 | |
|   if (aMoved && GdkIsX11Display() && IsPopup() &&
 | |
|       !gtk_widget_get_visible(GTK_WIDGET(mShell))) {
 | |
|     LOG("  store position of hidden popup window");
 | |
|     mHiddenPopupPositioned = true;
 | |
|     mPopupPosition = {topLeft.x, topLeft.y};
 | |
|   }
 | |
| 
 | |
|   if (IsWaylandPopup()) {
 | |
|     NativeMoveResizeWaylandPopup(aMoved, aResized);
 | |
|   } else {
 | |
|     // x and y give the position of the window manager frame top-left.
 | |
|     if (aMoved) {
 | |
|       gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y);
 | |
|     }
 | |
|     if (aResized) {
 | |
|       gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
 | |
|       if (mIsDragPopup) {
 | |
|         // DND window is placed inside container so we need to make hard size
 | |
|         // request to ensure parent container is resized too.
 | |
|         gtk_widget_set_size_request(GTK_WIDGET(mShell), size.width,
 | |
|                                     size.height);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aResized) {
 | |
|     // Recompute the input region, in case the window grew or shrunk.
 | |
|     SetInputRegion(mInputRegion);
 | |
|   }
 | |
| 
 | |
|   // Does it need to be shown because bounds were previously insane?
 | |
|   if (mNeedsShow && mIsShown && aResized) {
 | |
|     NativeShow(true);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // We pause compositor to avoid rendering of obsoleted remote content which
 | |
| // produces flickering.
 | |
| // Re-enable compositor again when remote content is updated or
 | |
| // timeout happens.
 | |
| 
 | |
| // Define maximal compositor pause when it's paused to avoid flickering,
 | |
| // in milliseconds.
 | |
| #define COMPOSITOR_PAUSE_TIMEOUT (1000)
 | |
| 
 | |
| void nsWindow::PauseCompositorFlickering() {
 | |
|   bool pauseCompositor = IsTopLevelWindowType() &&
 | |
|                          mCompositorState == COMPOSITOR_ENABLED &&
 | |
|                          mCompositorWidgetDelegate && !mIsDestroyed;
 | |
|   if (!pauseCompositor) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LOG("nsWindow::PauseCompositorFlickering()");
 | |
| 
 | |
|   MozClearHandleID(mCompositorPauseTimeoutID, g_source_remove);
 | |
| 
 | |
|   CompositorBridgeChild* remoteRenderer = GetRemoteRenderer();
 | |
|   if (remoteRenderer) {
 | |
|     remoteRenderer->SendPause();
 | |
|     mCompositorState = COMPOSITOR_PAUSED_FLICKERING;
 | |
|     mCompositorPauseTimeoutID = (int)g_timeout_add(
 | |
|         COMPOSITOR_PAUSE_TIMEOUT,
 | |
|         [](void* data) -> gint {
 | |
|           nsWindow* window = static_cast<nsWindow*>(data);
 | |
|           if (!window->IsDestroyed()) {
 | |
|             window->ResumeCompositorFlickering();
 | |
|           }
 | |
|           return true;
 | |
|         },
 | |
|         this);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool nsWindow::IsWaitingForCompositorResume() {
 | |
|   return mCompositorState == COMPOSITOR_PAUSED_FLICKERING;
 | |
| }
 | |
| 
 | |
| void nsWindow::ResumeCompositorFlickering() {
 | |
|   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   LOG("nsWindow::ResumeCompositorFlickering()\n");
 | |
| 
 | |
|   if (mIsDestroyed || !IsWaitingForCompositorResume()) {
 | |
|     LOG("  early quit\n");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MozClearHandleID(mCompositorPauseTimeoutID, g_source_remove);
 | |
| 
 | |
|   // mCompositorWidgetDelegate can be deleted during timeout.
 | |
|   // In such case just flip compositor back to enabled and let
 | |
|   // SetCompositorWidgetDelegate() or Map event resume it.
 | |
|   if (!mCompositorWidgetDelegate) {
 | |
|     mCompositorState = COMPOSITOR_ENABLED;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   ResumeCompositorImpl();
 | |
| }
 | |
| 
 | |
| void nsWindow::ResumeCompositorFromCompositorThread() {
 | |
|   nsCOMPtr<nsIRunnable> event =
 | |
|       NewRunnableMethod("nsWindow::ResumeCompositorFlickering", this,
 | |
|                         &nsWindow::ResumeCompositorFlickering);
 | |
|   NS_DispatchToMainThread(event.forget());
 | |
| }
 | |
| 
 | |
| void nsWindow::ResumeCompositorImpl() {
 | |
|   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   LOG("nsWindow::ResumeCompositorImpl()\n");
 | |
| 
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mCompositorWidgetDelegate);
 | |
|   mCompositorWidgetDelegate->EnableRendering(GetX11Window(), GetShapedState());
 | |
| 
 | |
|   // As WaylandStartVsync needs mCompositorWidgetDelegate this is the right
 | |
|   // time to start it.
 | |
|   WaylandStartVsync();
 | |
| 
 | |
|   CompositorBridgeChild* remoteRenderer = GetRemoteRenderer();
 | |
|   MOZ_RELEASE_ASSERT(remoteRenderer);
 | |
|   remoteRenderer->SendResume();
 | |
|   remoteRenderer->SendForcePresent(wr::RenderReasons::WIDGET);
 | |
|   mCompositorState = COMPOSITOR_ENABLED;
 | |
| }
 | |
| 
 | |
| void nsWindow::WaylandStartVsync() {
 | |
| #ifdef MOZ_WAYLAND
 | |
|   if (!mWaylandVsyncSource) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LOG_VSYNC("nsWindow::WaylandStartVsync");
 | |
| 
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mCompositorWidgetDelegate);
 | |
|   if (mCompositorWidgetDelegate->AsGtkCompositorWidget() &&
 | |
|       mCompositorWidgetDelegate->AsGtkCompositorWidget()
 | |
|           ->GetNativeLayerRoot()) {
 | |
|     LOG_VSYNC("  use source NativeLayerRootWayland");
 | |
|     mWaylandVsyncSource->MaybeUpdateSource(
 | |
|         mCompositorWidgetDelegate->AsGtkCompositorWidget()
 | |
|             ->GetNativeLayerRoot()
 | |
|             ->AsNativeLayerRootWayland());
 | |
|   } else {
 | |
|     LOG_VSYNC("  use source mContainer");
 | |
|     mWaylandVsyncSource->MaybeUpdateSource(mContainer);
 | |
|   }
 | |
| 
 | |
|   mWaylandVsyncSource->EnableMonitor();
 | |
| #endif
 | |
| }
 | |
| 
 | |
| void nsWindow::WaylandStopVsync() {
 | |
| #ifdef MOZ_WAYLAND
 | |
|   if (!mWaylandVsyncSource) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LOG_VSYNC("nsWindow::WaylandStopVsync");
 | |
| 
 | |
|   // The widget is going to be hidden, so clear the surface of our
 | |
|   // vsync source.
 | |
|   mWaylandVsyncSource->DisableMonitor();
 | |
|   mWaylandVsyncSource->MaybeUpdateSource(nullptr);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| void nsWindow::NativeShow(bool aAction) {
 | |
|   if (aAction) {
 | |
|     // unset our flag now that our window has been shown
 | |
|     mNeedsShow = true;
 | |
|     auto removeShow = MakeScopeExit([&] { mNeedsShow = false; });
 | |
| 
 | |
|     LOG("nsWindow::NativeShow show\n");
 | |
| 
 | |
|     if (IsWaylandPopup()) {
 | |
|       mPopupClosed = false;
 | |
|       if (WaylandPopupConfigure()) {
 | |
|         AddWindowToPopupHierarchy();
 | |
|         UpdateWaylandPopupHierarchy();
 | |
|         if (mPopupClosed) {
 | |
|           return;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     // Set up usertime/startupID metadata for the created window.
 | |
|     // On X11 we use gtk_window_set_startup_id() so we need to call it
 | |
|     // before show.
 | |
|     if (GdkIsX11Display()) {
 | |
|       SetUserTimeAndStartupTokenForActivatedWindow();
 | |
|     }
 | |
|     if (GdkIsWaylandDisplay()) {
 | |
|       if (IsWaylandPopup()) {
 | |
|         ShowWaylandPopupWindow();
 | |
|       } else {
 | |
|         ShowWaylandToplevelWindow();
 | |
|       }
 | |
|     } else {
 | |
|       LOG("  calling gtk_widget_show(mShell)\n");
 | |
|       gtk_widget_show(mShell);
 | |
|     }
 | |
|     if (GdkIsWaylandDisplay()) {
 | |
|       SetUserTimeAndStartupTokenForActivatedWindow();
 | |
| #ifdef MOZ_WAYLAND
 | |
|       auto token = std::move(mWindowActivationTokenFromEnv);
 | |
|       if (!token.IsEmpty()) {
 | |
|         FocusWaylandWindow(token.get());
 | |
|       }
 | |
| #endif
 | |
|     }
 | |
|     if (mHiddenPopupPositioned && IsPopup()) {
 | |
|       LOG("  re-position hidden popup window");
 | |
|       gtk_window_move(GTK_WINDOW(mShell), mPopupPosition.x, mPopupPosition.y);
 | |
|       mHiddenPopupPositioned = false;
 | |
|     }
 | |
|   } else {
 | |
|     LOG("nsWindow::NativeShow hide\n");
 | |
|     if (GdkIsWaylandDisplay()) {
 | |
|       if (IsWaylandPopup()) {
 | |
|         // We can't close tracked popups directly as they may have visible
 | |
|         // child popups. Just mark is as closed and let
 | |
|         // UpdateWaylandPopupHierarchy() do the job.
 | |
|         if (IsInPopupHierarchy()) {
 | |
|           WaylandPopupMarkAsClosed();
 | |
|           UpdateWaylandPopupHierarchy();
 | |
|         } else {
 | |
|           // Close untracked popups directly.
 | |
|           HideWaylandPopupWindow(/* aTemporaryHide */ false,
 | |
|                                  /* aRemoveFromPopupList */ true);
 | |
|         }
 | |
|       } else {
 | |
|         HideWaylandToplevelWindow();
 | |
|       }
 | |
|     } else {
 | |
|       // Workaround window freezes on GTK versions before 3.21.2 by
 | |
|       // ensuring that configure events get dispatched to windows before
 | |
|       // they are unmapped. See bug 1225044.
 | |
|       if (gtk_check_version(3, 21, 2) != nullptr && mPendingConfigures > 0) {
 | |
|         GtkAllocation allocation;
 | |
|         gtk_widget_get_allocation(GTK_WIDGET(mShell), &allocation);
 | |
| 
 | |
|         GdkEventConfigure event;
 | |
|         PodZero(&event);
 | |
|         event.type = GDK_CONFIGURE;
 | |
|         event.window = mGdkWindow;
 | |
|         event.send_event = TRUE;
 | |
|         event.x = allocation.x;
 | |
|         event.y = allocation.y;
 | |
|         event.width = allocation.width;
 | |
|         event.height = allocation.height;
 | |
| 
 | |
|         auto* shellClass = GTK_WIDGET_GET_CLASS(mShell);
 | |
|         for (unsigned int i = 0; i < mPendingConfigures; i++) {
 | |
|           Unused << shellClass->configure_event(mShell, &event);
 | |
|         }
 | |
|         mPendingConfigures = 0;
 | |
|       }
 | |
|       gtk_widget_hide(mShell);
 | |
| 
 | |
|       ClearTransparencyBitmap();  // Release some resources
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::SetHasMappedToplevel(bool aState) {
 | |
|   LOG("nsWindow::SetHasMappedToplevel(%d)", aState);
 | |
|   if (aState == mHasMappedToplevel) {
 | |
|     return;
 | |
|   }
 | |
|   // Even when aState == mHasMappedToplevel (as when this method is called
 | |
|   // from Show()), child windows need to have their state checked, so don't
 | |
|   // return early.
 | |
|   mHasMappedToplevel = aState;
 | |
|   if (aState && mNeedsToRetryCapturingMouse) {
 | |
|     CaptureRollupEvents(true);
 | |
|     MOZ_ASSERT(!mNeedsToRetryCapturingMouse);
 | |
|   }
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntSize nsWindow::GetSafeWindowSize(LayoutDeviceIntSize aSize) {
 | |
|   // The X protocol uses CARD32 for window sizes, but the server (1.11.3)
 | |
|   // reads it as CARD16.  Sizes of pixmaps, used for drawing, are (unsigned)
 | |
|   // CARD16 in the protocol, but the server's ProcCreatePixmap returns
 | |
|   // BadAlloc if dimensions cannot be represented by signed shorts.
 | |
|   // Because we are creating Cairo surfaces to represent window buffers,
 | |
|   // we also must ensure that the window can fit in a Cairo surface.
 | |
|   LayoutDeviceIntSize result = aSize;
 | |
|   int32_t maxSize = 32767;
 | |
|   if (mWindowRenderer && mWindowRenderer->AsKnowsCompositor()) {
 | |
|     maxSize = std::min(
 | |
|         maxSize, mWindowRenderer->AsKnowsCompositor()->GetMaxTextureSize());
 | |
|   }
 | |
|   if (result.width > maxSize) {
 | |
|     result.width = maxSize;
 | |
|   }
 | |
|   if (result.height > maxSize) {
 | |
|     result.height = maxSize;
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| void nsWindow::SetTransparencyMode(TransparencyMode aMode) {
 | |
|   bool isTransparent = aMode == TransparencyMode::Transparent;
 | |
| 
 | |
|   if (mIsTransparent == isTransparent) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mWindowType != WindowType::Popup) {
 | |
|     // https://bugzilla.mozilla.org/show_bug.cgi?id=1344839 reported
 | |
|     // problems cleaning the layer manager for toplevel windows.
 | |
|     // Ignore the request so as to workaround that.
 | |
|     // mIsTransparent is set in Create() if transparency may be required.
 | |
|     if (isTransparent) {
 | |
|       NS_WARNING("Transparent mode not supported on non-popup windows.");
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!isTransparent) {
 | |
|     ClearTransparencyBitmap();
 | |
|   }  // else the new default alpha values are "all 1", so we don't
 | |
|   // need to change anything yet
 | |
| 
 | |
|   mIsTransparent = isTransparent;
 | |
| 
 | |
|   if (!mHasAlphaVisual) {
 | |
|     // The choice of layer manager depends on
 | |
|     // GtkCompositorWidgetInitData::Shaped(), which will need to change, so
 | |
|     // clean out the old layer manager.
 | |
|     DestroyLayerManager();
 | |
|   }
 | |
| }
 | |
| 
 | |
| TransparencyMode nsWindow::GetTransparencyMode() {
 | |
|   return mIsTransparent ? TransparencyMode::Transparent
 | |
|                         : TransparencyMode::Opaque;
 | |
| }
 | |
| 
 | |
| gint nsWindow::GetInputRegionMarginInGdkCoords() {
 | |
|   return DevicePixelsToGdkCoordRoundDown(mInputRegion.mMargin);
 | |
| }
 | |
| 
 | |
| void nsWindow::SetInputRegion(const InputRegion& aInputRegion) {
 | |
|   mInputRegion = aInputRegion;
 | |
| 
 | |
|   GdkWindow* window = GetToplevelGdkWindow();
 | |
|   if (!window) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LOG("nsWindow::SetInputRegion(%d, %d)", aInputRegion.mFullyTransparent,
 | |
|       int(aInputRegion.mMargin));
 | |
| 
 | |
|   cairo_rectangle_int_t rect = {0, 0, 0, 0};
 | |
|   cairo_region_t* region = nullptr;
 | |
|   auto releaseRegion = MakeScopeExit([&] {
 | |
|     if (region) {
 | |
|       cairo_region_destroy(region);
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   if (aInputRegion.mFullyTransparent) {
 | |
|     region = cairo_region_create_rectangle(&rect);
 | |
|   } else if (aInputRegion.mMargin != 0) {
 | |
|     LayoutDeviceIntRect inputRegion(LayoutDeviceIntPoint(), mLastSizeRequest);
 | |
|     inputRegion.Deflate(aInputRegion.mMargin);
 | |
|     GdkRectangle gdkRect = DevicePixelsToGdkRectRoundOut(inputRegion);
 | |
|     rect = {gdkRect.x, gdkRect.y, gdkRect.width, gdkRect.height};
 | |
|     region = cairo_region_create_rectangle(&rect);
 | |
|   }
 | |
| 
 | |
|   gdk_window_input_shape_combine_region(window, region, 0, 0);
 | |
| 
 | |
|   // On Wayland gdk_window_input_shape_combine_region() call is cached and
 | |
|   // applied to underlying wl_surface when GdkWindow is repainted.
 | |
|   // Force repaint of GdkWindow to apply the change immediately.
 | |
|   if (GdkIsWaylandDisplay()) {
 | |
|     gdk_window_invalidate_rect(window, nullptr, false);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // For setting the draggable titlebar region from CSS
 | |
| // with -moz-window-dragging: drag.
 | |
| void nsWindow::UpdateWindowDraggingRegion(
 | |
|     const LayoutDeviceIntRegion& aRegion) {
 | |
|   if (mDraggableRegion != aRegion) {
 | |
|     mDraggableRegion = aRegion;
 | |
|   }
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntCoord nsWindow::GetTitlebarRadius() {
 | |
|   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 | |
|   int32_t cssCoord = LookAndFeel::GetInt(LookAndFeel::IntID::TitlebarRadius);
 | |
|   return GdkCoordToDevicePixels(cssCoord);
 | |
| }
 | |
| 
 | |
| // See subtract_corners_from_region() at gtk/gtkwindow.c
 | |
| // We need to subtract corners from toplevel window opaque region
 | |
| // to draw transparent corners of default Gtk titlebar.
 | |
| // Both implementations (cairo_region_t and wl_region) needs to be synced.
 | |
| static void SubtractTitlebarCorners(cairo_region_t* aRegion, int aX, int aY,
 | |
|                                     int aWindowWidth, int aWindowHeight,
 | |
|                                     int aTitlebarRadius) {
 | |
|   if (!aTitlebarRadius) {
 | |
|     return;
 | |
|   }
 | |
|   cairo_rectangle_int_t rect = {aX, aY, aTitlebarRadius, aTitlebarRadius};
 | |
|   cairo_region_subtract_rectangle(aRegion, &rect);
 | |
|   rect = {
 | |
|       aX + aWindowWidth - aTitlebarRadius,
 | |
|       aY,
 | |
|       aTitlebarRadius,
 | |
|       aTitlebarRadius,
 | |
|   };
 | |
|   cairo_region_subtract_rectangle(aRegion, &rect);
 | |
|   rect = {
 | |
|       aX,
 | |
|       aY + aWindowHeight - aTitlebarRadius,
 | |
|       aTitlebarRadius,
 | |
|       aTitlebarRadius,
 | |
|   };
 | |
|   cairo_region_subtract_rectangle(aRegion, &rect);
 | |
|   rect = {
 | |
|       aX + aWindowWidth - aTitlebarRadius,
 | |
|       aY + aWindowHeight - aTitlebarRadius,
 | |
|       aTitlebarRadius,
 | |
|       aTitlebarRadius,
 | |
|   };
 | |
|   cairo_region_subtract_rectangle(aRegion, &rect);
 | |
| }
 | |
| 
 | |
| void nsWindow::UpdateTopLevelOpaqueRegion() {
 | |
|   if (!mCompositedScreen) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   GdkWindow* window = GetToplevelGdkWindow();
 | |
|   if (!window) {
 | |
|     return;
 | |
|   }
 | |
|   MOZ_ASSERT(gdk_window_get_window_type(window) == GDK_WINDOW_TOPLEVEL);
 | |
| 
 | |
|   int x = 0;
 | |
|   int y = 0;
 | |
| 
 | |
|   gdk_window_get_position(mGdkWindow, &x, &y);
 | |
| 
 | |
|   int width = DevicePixelsToGdkCoordRoundDown(mBounds.width);
 | |
|   int height = DevicePixelsToGdkCoordRoundDown(mBounds.height);
 | |
| 
 | |
|   cairo_region_t* region = cairo_region_create();
 | |
|   cairo_rectangle_int_t rect = {x, y, width, height};
 | |
|   cairo_region_union_rectangle(region, &rect);
 | |
| 
 | |
|   // TODO: We actually could get a proper opaque region from layout, see
 | |
|   // nsIWidget::UpdateOpaqueRegion. This could simplify titlebar drawing.
 | |
|   int radius = DoDrawTilebarCorners() ? int(GetTitlebarRadius()) : 0;
 | |
|   SubtractTitlebarCorners(region, x, y, width, height, radius);
 | |
| 
 | |
|   gdk_window_set_opaque_region(window, region);
 | |
| 
 | |
|   cairo_region_destroy(region);
 | |
| 
 | |
| #ifdef MOZ_WAYLAND
 | |
|   if (GdkIsWaylandDisplay()) {
 | |
|     moz_container_wayland_update_opaque_region(mContainer, radius);
 | |
|   }
 | |
| #endif
 | |
| }
 | |
| 
 | |
| bool nsWindow::IsChromeWindowTitlebar() {
 | |
|   return mDrawInTitlebar && !mIsPIPWindow &&
 | |
|          mWindowType == WindowType::TopLevel;
 | |
| }
 | |
| 
 | |
| bool nsWindow::DoDrawTilebarCorners() {
 | |
|   return IsChromeWindowTitlebar() && mSizeMode == nsSizeMode_Normal &&
 | |
|          !mIsTiled;
 | |
| }
 | |
| 
 | |
| void nsWindow::ResizeTransparencyBitmap() {
 | |
|   if (!mTransparencyBitmap) return;
 | |
| 
 | |
|   if (mBounds.width == mTransparencyBitmapWidth &&
 | |
|       mBounds.height == mTransparencyBitmapHeight) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   int32_t newRowBytes = GetBitmapStride(mBounds.width);
 | |
|   int32_t newSize = newRowBytes * mBounds.height;
 | |
|   auto* newBits = new gchar[newSize];
 | |
|   // fill new mask with "transparent", first
 | |
|   memset(newBits, 0, newSize);
 | |
| 
 | |
|   // Now copy the intersection of the old and new areas into the new mask
 | |
|   int32_t copyWidth = std::min(mBounds.width, mTransparencyBitmapWidth);
 | |
|   int32_t copyHeight = std::min(mBounds.height, mTransparencyBitmapHeight);
 | |
|   int32_t oldRowBytes = GetBitmapStride(mTransparencyBitmapWidth);
 | |
|   int32_t copyBytes = GetBitmapStride(copyWidth);
 | |
| 
 | |
|   int32_t i;
 | |
|   gchar* fromPtr = mTransparencyBitmap;
 | |
|   gchar* toPtr = newBits;
 | |
|   for (i = 0; i < copyHeight; i++) {
 | |
|     memcpy(toPtr, fromPtr, copyBytes);
 | |
|     fromPtr += oldRowBytes;
 | |
|     toPtr += newRowBytes;
 | |
|   }
 | |
| 
 | |
|   delete[] mTransparencyBitmap;
 | |
|   mTransparencyBitmap = newBits;
 | |
|   mTransparencyBitmapWidth = mBounds.width;
 | |
|   mTransparencyBitmapHeight = mBounds.height;
 | |
| }
 | |
| 
 | |
| static bool ChangedMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
 | |
|                             int32_t aMaskHeight, const nsIntRect& aRect,
 | |
|                             uint8_t* aAlphas, int32_t aStride) {
 | |
|   int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
 | |
|   int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
 | |
|   for (y = aRect.y; y < yMax; y++) {
 | |
|     gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
 | |
|     uint8_t* alphas = aAlphas;
 | |
|     for (x = aRect.x; x < xMax; x++) {
 | |
|       bool newBit = *alphas > 0x7f;
 | |
|       alphas++;
 | |
| 
 | |
|       gchar maskByte = maskBytes[x >> 3];
 | |
|       bool maskBit = (maskByte & (1 << (x & 7))) != 0;
 | |
| 
 | |
|       if (maskBit != newBit) {
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
|     aAlphas += aStride;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| static void UpdateMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
 | |
|                            int32_t aMaskHeight, const nsIntRect& aRect,
 | |
|                            uint8_t* aAlphas, int32_t aStride) {
 | |
|   int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
 | |
|   int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
 | |
|   for (y = aRect.y; y < yMax; y++) {
 | |
|     gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
 | |
|     uint8_t* alphas = aAlphas;
 | |
|     for (x = aRect.x; x < xMax; x++) {
 | |
|       bool newBit = *alphas > 0x7f;
 | |
|       alphas++;
 | |
| 
 | |
|       gchar mask = 1 << (x & 7);
 | |
|       gchar maskByte = maskBytes[x >> 3];
 | |
|       // Note: '-newBit' turns 0 into 00...00 and 1 into 11...11
 | |
|       maskBytes[x >> 3] = (maskByte & ~mask) | (-newBit & mask);
 | |
|     }
 | |
|     aAlphas += aStride;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::ApplyTransparencyBitmap() {
 | |
| #ifdef MOZ_X11
 | |
|   // We use X11 calls where possible, because GDK handles expose events
 | |
|   // for shaped windows in a way that's incompatible with us (Bug 635903).
 | |
|   // It doesn't occur when the shapes are set through X.
 | |
|   Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
 | |
|   Window xDrawable = GDK_WINDOW_XID(mGdkWindow);
 | |
|   Pixmap maskPixmap = XCreateBitmapFromData(
 | |
|       xDisplay, xDrawable, mTransparencyBitmap, mTransparencyBitmapWidth,
 | |
|       mTransparencyBitmapHeight);
 | |
|   XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
 | |
|                     ShapeSet);
 | |
|   XFreePixmap(xDisplay, maskPixmap);
 | |
| #endif  // MOZ_X11
 | |
| }
 | |
| 
 | |
| void nsWindow::ClearTransparencyBitmap() {
 | |
|   if (!mTransparencyBitmap) return;
 | |
| 
 | |
|   delete[] mTransparencyBitmap;
 | |
|   mTransparencyBitmap = nullptr;
 | |
|   mTransparencyBitmapWidth = 0;
 | |
|   mTransparencyBitmapHeight = 0;
 | |
| 
 | |
|   if (!mShell) return;
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
|   if (MOZ_UNLIKELY(!mGdkWindow)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
 | |
|   Window xWindow = gdk_x11_window_get_xid(mGdkWindow);
 | |
| 
 | |
|   XShapeCombineMask(xDisplay, xWindow, ShapeBounding, 0, 0, X11None, ShapeSet);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| nsresult nsWindow::UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
 | |
|                                                         uint8_t* aAlphas,
 | |
|                                                         int32_t aStride) {
 | |
|   NS_ASSERTION(mIsTransparent, "Window is not transparent");
 | |
|   NS_ASSERTION(!mTransparencyBitmapForTitlebar,
 | |
|                "Transparency bitmap is already used for titlebar rendering");
 | |
| 
 | |
|   if (mTransparencyBitmap == nullptr) {
 | |
|     int32_t size = GetBitmapStride(mBounds.width) * mBounds.height;
 | |
|     mTransparencyBitmap = new gchar[size];
 | |
|     memset(mTransparencyBitmap, 255, size);
 | |
|     mTransparencyBitmapWidth = mBounds.width;
 | |
|     mTransparencyBitmapHeight = mBounds.height;
 | |
|   } else {
 | |
|     ResizeTransparencyBitmap();
 | |
|   }
 | |
| 
 | |
|   nsIntRect rect;
 | |
|   rect.IntersectRect(aRect, nsIntRect(0, 0, mBounds.width, mBounds.height));
 | |
| 
 | |
|   if (!ChangedMaskBits(mTransparencyBitmap, mBounds.width, mBounds.height, rect,
 | |
|                        aAlphas, aStride)) {
 | |
|     // skip the expensive stuff if the mask bits haven't changed; hopefully
 | |
|     // this is the common case
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   UpdateMaskBits(mTransparencyBitmap, mBounds.width, mBounds.height, rect,
 | |
|                  aAlphas, aStride);
 | |
| 
 | |
|   if (!mNeedsShow) {
 | |
|     ApplyTransparencyBitmap();
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| #define TITLEBAR_HEIGHT 10
 | |
| 
 | |
| LayoutDeviceIntRect nsWindow::GetTitlebarRect() {
 | |
|   // See NS_NATIVE_EGL_WINDOW why we can't block here.
 | |
|   auto ret = LayoutDeviceIntRect();
 | |
| 
 | |
|   if (mDestroyMutex.TryLock()) {
 | |
|     if (mGdkWindow && mDrawInTitlebar) {
 | |
|       int height = 0;
 | |
|       if (DoDrawTilebarCorners()) {
 | |
|         height = GdkCeiledScaleFactor() * TITLEBAR_HEIGHT;
 | |
|       }
 | |
|       ret = LayoutDeviceIntRect(0, 0, mBounds.width, height);
 | |
|     }
 | |
|     mDestroyMutex.Unlock();
 | |
|   }
 | |
| 
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| void nsWindow::UpdateTitlebarTransparencyBitmap() {
 | |
|   NS_ASSERTION(mTransparencyBitmapForTitlebar,
 | |
|                "Transparency bitmap is already used to draw window shape");
 | |
| 
 | |
|   if (!mGdkWindow || !mDrawInTitlebar ||
 | |
|       (mBounds.width == mTransparencyBitmapWidth &&
 | |
|        mBounds.height == mTransparencyBitmapHeight)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   bool maskCreate =
 | |
|       !mTransparencyBitmap || mBounds.width > mTransparencyBitmapWidth;
 | |
| 
 | |
|   bool maskUpdate =
 | |
|       !mTransparencyBitmap || mBounds.width != mTransparencyBitmapWidth;
 | |
| 
 | |
|   LayoutDeviceIntCoord radius = GetTitlebarRadius();
 | |
|   if (maskCreate) {
 | |
|     delete[] mTransparencyBitmap;
 | |
|     int32_t size = GetBitmapStride(mBounds.width) * radius;
 | |
|     mTransparencyBitmap = new gchar[size];
 | |
|     mTransparencyBitmapWidth = mBounds.width;
 | |
|   } else {
 | |
|     mTransparencyBitmapWidth = mBounds.width;
 | |
|   }
 | |
|   mTransparencyBitmapHeight = mBounds.height;
 | |
| 
 | |
|   if (maskUpdate) {
 | |
|     cairo_surface_t* surface = cairo_image_surface_create(
 | |
|         CAIRO_FORMAT_A8, mTransparencyBitmapWidth, radius);
 | |
|     if (!surface) return;
 | |
| 
 | |
|     cairo_t* cr = cairo_create(surface);
 | |
| 
 | |
|     GtkWidgetState state;
 | |
|     memset((void*)&state, 0, sizeof(state));
 | |
|     GdkRectangle rect = {0, 0, mTransparencyBitmapWidth, radius};
 | |
| 
 | |
|     moz_gtk_widget_paint(MOZ_GTK_HEADER_BAR, cr, &rect, &state, 0,
 | |
|                          GTK_TEXT_DIR_NONE);
 | |
| 
 | |
|     cairo_destroy(cr);
 | |
|     cairo_surface_mark_dirty(surface);
 | |
|     cairo_surface_flush(surface);
 | |
| 
 | |
|     UpdateMaskBits(mTransparencyBitmap, mTransparencyBitmapWidth, radius,
 | |
|                    nsIntRect(0, 0, mTransparencyBitmapWidth, radius),
 | |
|                    cairo_image_surface_get_data(surface),
 | |
|                    cairo_format_stride_for_width(CAIRO_FORMAT_A8,
 | |
|                                                  mTransparencyBitmapWidth));
 | |
| 
 | |
|     cairo_surface_destroy(surface);
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
|   if (!mNeedsShow) {
 | |
|     Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
 | |
|     Window xDrawable = GDK_WINDOW_XID(mGdkWindow);
 | |
| 
 | |
|     Pixmap maskPixmap =
 | |
|         XCreateBitmapFromData(xDisplay, xDrawable, mTransparencyBitmap,
 | |
|                               mTransparencyBitmapWidth, radius);
 | |
| 
 | |
|     XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
 | |
|                       ShapeSet);
 | |
| 
 | |
|     if (mTransparencyBitmapHeight > radius) {
 | |
|       XRectangle rect = {0, 0, (unsigned short)mTransparencyBitmapWidth,
 | |
|                          (unsigned short)(mTransparencyBitmapHeight - radius)};
 | |
|       XShapeCombineRectangles(xDisplay, xDrawable, ShapeBounding, 0, radius,
 | |
|                               &rect, 1, ShapeUnion, 0);
 | |
|     }
 | |
| 
 | |
|     XFreePixmap(xDisplay, maskPixmap);
 | |
|   }
 | |
| #endif
 | |
| }
 | |
| 
 | |
| GtkWidget* nsWindow::GetToplevelWidget() const { return mShell; }
 | |
| 
 | |
| GdkWindow* nsWindow::GetToplevelGdkWindow() const {
 | |
|   return gtk_widget_get_window(mShell);
 | |
| }
 | |
| 
 | |
| nsWindow* nsWindow::GetContainerWindow() const {
 | |
|   GtkWidget* owningWidget = GTK_WIDGET(mContainer);
 | |
|   if (!owningWidget) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsWindow* window = get_window_for_gtk_widget(owningWidget);
 | |
|   NS_ASSERTION(window, "No nsWindow for container widget");
 | |
|   return window;
 | |
| }
 | |
| 
 | |
| void nsWindow::SetUrgencyHint(GtkWidget* top_window, bool state) {
 | |
|   LOG("  nsWindow::SetUrgencyHint widget %p\n", top_window);
 | |
| 
 | |
|   if (!top_window) return;
 | |
| 
 | |
|   // TODO: Use xdg-activation on Wayland?
 | |
|   gdk_window_set_urgency_hint(gtk_widget_get_window(top_window), state);
 | |
| }
 | |
| 
 | |
| void nsWindow::SetDefaultIcon(void) { SetIcon(u"default"_ns); }
 | |
| 
 | |
| gint nsWindow::ConvertBorderStyles(BorderStyle aStyle) {
 | |
|   gint w = 0;
 | |
| 
 | |
|   if (aStyle == BorderStyle::Default) return -1;
 | |
| 
 | |
|   // note that we don't handle BorderStyle::Close yet
 | |
|   if (aStyle & BorderStyle::All) w |= GDK_DECOR_ALL;
 | |
|   if (aStyle & BorderStyle::Border) w |= GDK_DECOR_BORDER;
 | |
|   if (aStyle & BorderStyle::ResizeH) w |= GDK_DECOR_RESIZEH;
 | |
|   if (aStyle & BorderStyle::Title) w |= GDK_DECOR_TITLE;
 | |
|   if (aStyle & BorderStyle::Menu) w |= GDK_DECOR_MENU;
 | |
|   if (aStyle & BorderStyle::Minimize) w |= GDK_DECOR_MINIMIZE;
 | |
|   if (aStyle & BorderStyle::Maximize) w |= GDK_DECOR_MAXIMIZE;
 | |
| 
 | |
|   return w;
 | |
| }
 | |
| 
 | |
| class FullscreenTransitionWindow final : public nsISupports {
 | |
|  public:
 | |
|   NS_DECL_ISUPPORTS
 | |
| 
 | |
|   explicit FullscreenTransitionWindow(GtkWidget* aWidget);
 | |
| 
 | |
|   GtkWidget* mWindow;
 | |
| 
 | |
|  private:
 | |
|   ~FullscreenTransitionWindow();
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS0(FullscreenTransitionWindow)
 | |
| 
 | |
| FullscreenTransitionWindow::FullscreenTransitionWindow(GtkWidget* aWidget) {
 | |
|   mWindow = gtk_window_new(GTK_WINDOW_POPUP);
 | |
|   GtkWindow* gtkWin = GTK_WINDOW(mWindow);
 | |
| 
 | |
|   gtk_window_set_type_hint(gtkWin, GDK_WINDOW_TYPE_HINT_SPLASHSCREEN);
 | |
|   GtkWindowSetTransientFor(gtkWin, GTK_WINDOW(aWidget));
 | |
|   gtk_window_set_decorated(gtkWin, false);
 | |
| 
 | |
|   GdkWindow* gdkWin = gtk_widget_get_window(aWidget);
 | |
|   GdkScreen* screen = gtk_widget_get_screen(aWidget);
 | |
|   gint monitorNum = gdk_screen_get_monitor_at_window(screen, gdkWin);
 | |
|   GdkRectangle monitorRect;
 | |
|   gdk_screen_get_monitor_geometry(screen, monitorNum, &monitorRect);
 | |
|   gtk_window_set_screen(gtkWin, screen);
 | |
|   gtk_window_move(gtkWin, monitorRect.x, monitorRect.y);
 | |
|   MOZ_ASSERT(monitorRect.width > 0 && monitorRect.height > 0,
 | |
|              "Can't resize window smaller than 1x1.");
 | |
|   gtk_window_resize(gtkWin, monitorRect.width, monitorRect.height);
 | |
| 
 | |
|   GdkRGBA bgColor;
 | |
|   bgColor.red = bgColor.green = bgColor.blue = 0.0;
 | |
|   bgColor.alpha = 1.0;
 | |
|   gtk_widget_override_background_color(mWindow, GTK_STATE_FLAG_NORMAL,
 | |
|                                        &bgColor);
 | |
| 
 | |
|   gtk_widget_set_opacity(mWindow, 0.0);
 | |
|   gtk_widget_show(mWindow);
 | |
| }
 | |
| 
 | |
| FullscreenTransitionWindow::~FullscreenTransitionWindow() {
 | |
|   gtk_widget_destroy(mWindow);
 | |
| }
 | |
| 
 | |
| class FullscreenTransitionData {
 | |
|  public:
 | |
|   FullscreenTransitionData(nsIWidget::FullscreenTransitionStage aStage,
 | |
|                            uint16_t aDuration, nsIRunnable* aCallback,
 | |
|                            FullscreenTransitionWindow* aWindow)
 | |
|       : mStage(aStage),
 | |
|         mStartTime(TimeStamp::Now()),
 | |
|         mDuration(TimeDuration::FromMilliseconds(aDuration)),
 | |
|         mCallback(aCallback),
 | |
|         mWindow(aWindow) {}
 | |
| 
 | |
|   static const guint sInterval = 1000 / 30;  // 30fps
 | |
|   static gboolean TimeoutCallback(gpointer aData);
 | |
| 
 | |
|  private:
 | |
|   nsIWidget::FullscreenTransitionStage mStage;
 | |
|   TimeStamp mStartTime;
 | |
|   TimeDuration mDuration;
 | |
|   nsCOMPtr<nsIRunnable> mCallback;
 | |
|   RefPtr<FullscreenTransitionWindow> mWindow;
 | |
| };
 | |
| 
 | |
| /* static */
 | |
| gboolean FullscreenTransitionData::TimeoutCallback(gpointer aData) {
 | |
|   bool finishing = false;
 | |
|   auto* data = static_cast<FullscreenTransitionData*>(aData);
 | |
|   gdouble opacity = (TimeStamp::Now() - data->mStartTime) / data->mDuration;
 | |
|   if (opacity >= 1.0) {
 | |
|     opacity = 1.0;
 | |
|     finishing = true;
 | |
|   }
 | |
|   if (data->mStage == nsIWidget::eAfterFullscreenToggle) {
 | |
|     opacity = 1.0 - opacity;
 | |
|   }
 | |
|   gtk_widget_set_opacity(data->mWindow->mWindow, opacity);
 | |
| 
 | |
|   if (!finishing) {
 | |
|     return TRUE;
 | |
|   }
 | |
|   NS_DispatchToMainThread(data->mCallback.forget());
 | |
|   delete data;
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| /* virtual */
 | |
| bool nsWindow::PrepareForFullscreenTransition(nsISupports** aData) {
 | |
|   if (!mCompositedScreen) {
 | |
|     return false;
 | |
|   }
 | |
|   *aData = do_AddRef(new FullscreenTransitionWindow(mShell)).take();
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /* virtual */
 | |
| void nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
 | |
|                                            uint16_t aDuration,
 | |
|                                            nsISupports* aData,
 | |
|                                            nsIRunnable* aCallback) {
 | |
|   auto* data = static_cast<FullscreenTransitionWindow*>(aData);
 | |
|   // This will be released at the end of the last timeout callback for it.
 | |
|   auto* transitionData =
 | |
|       new FullscreenTransitionData(aStage, aDuration, aCallback, data);
 | |
|   g_timeout_add_full(G_PRIORITY_HIGH, FullscreenTransitionData::sInterval,
 | |
|                      FullscreenTransitionData::TimeoutCallback, transitionData,
 | |
|                      nullptr);
 | |
| }
 | |
| 
 | |
| already_AddRefed<widget::Screen> nsWindow::GetWidgetScreen() {
 | |
|   // Wayland can read screen directly
 | |
|   if (GdkIsWaylandDisplay()) {
 | |
|     if (RefPtr<Screen> screen = ScreenHelperGTK::GetScreenForWindow(this)) {
 | |
|       return screen.forget();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // GetScreenBounds() is slow for the GTK port so we override and use
 | |
|   // mBounds directly.
 | |
|   ScreenManager& screenManager = ScreenManager::GetSingleton();
 | |
|   LayoutDeviceIntRect bounds = mBounds;
 | |
|   DesktopIntRect deskBounds = RoundedToInt(bounds / GetDesktopToDeviceScale());
 | |
|   return screenManager.ScreenForRect(deskBounds);
 | |
| }
 | |
| 
 | |
| RefPtr<VsyncDispatcher> nsWindow::GetVsyncDispatcher() {
 | |
| #ifdef MOZ_WAYLAND
 | |
|   if (mWaylandVsyncDispatcher) {
 | |
|     return mWaylandVsyncDispatcher;
 | |
|   }
 | |
| #endif
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| bool nsWindow::SynchronouslyRepaintOnResize() {
 | |
|   if (GdkIsWaylandDisplay()) {
 | |
|     // See Bug 1734368
 | |
|     // Don't request synchronous repaint on HW accelerated backend - mesa can be
 | |
|     // deadlocked when it's missing back buffer and main event loop is blocked.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // default is synced repaint.
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void nsWindow::KioskLockOnMonitor() {
 | |
|   // Available as of GTK 3.18+
 | |
|   static auto sGdkWindowFullscreenOnMonitor =
 | |
|       (void (*)(GdkWindow* window, gint monitor))dlsym(
 | |
|           RTLD_DEFAULT, "gdk_window_fullscreen_on_monitor");
 | |
| 
 | |
|   if (!sGdkWindowFullscreenOnMonitor) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   int monitor = mKioskMonitor.value();
 | |
|   if (monitor < 0 || monitor >= ScreenHelperGTK::GetMonitorCount()) {
 | |
|     LOG("nsWindow::KioskLockOnMonitor() wrong monitor number! (%d)\n", monitor);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LOG("nsWindow::KioskLockOnMonitor() locked on %d\n", monitor);
 | |
|   sGdkWindowFullscreenOnMonitor(GetToplevelGdkWindow(), monitor);
 | |
| }
 | |
| 
 | |
| static bool IsFullscreenSupported(GtkWidget* aShell) {
 | |
| #ifdef MOZ_X11
 | |
|   GdkScreen* screen = gtk_widget_get_screen(aShell);
 | |
|   GdkAtom atom = gdk_atom_intern("_NET_WM_STATE_FULLSCREEN", FALSE);
 | |
|   return gdk_x11_screen_supports_net_wm_hint(screen, atom);
 | |
| #else
 | |
|   return true;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| nsresult nsWindow::MakeFullScreen(bool aFullScreen) {
 | |
|   LOG("nsWindow::MakeFullScreen aFullScreen %d\n", aFullScreen);
 | |
| 
 | |
|   if (GdkIsX11Display() && !IsFullscreenSupported(mShell)) {
 | |
|     return NS_ERROR_NOT_AVAILABLE;
 | |
|   }
 | |
| 
 | |
|   if (aFullScreen) {
 | |
|     if (mSizeMode != nsSizeMode_Fullscreen &&
 | |
|         mSizeMode != nsSizeMode_Minimized) {
 | |
|       mLastSizeModeBeforeFullscreen = mSizeMode;
 | |
|     }
 | |
|     if (mIsPIPWindow) {
 | |
|       gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_NORMAL);
 | |
|       if (gUseAspectRatio) {
 | |
|         mAspectRatioSaved = mAspectRatio;
 | |
|         mAspectRatio = 0.0f;
 | |
|         ApplySizeConstraints();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (mKioskMonitor.isSome()) {
 | |
|       KioskLockOnMonitor();
 | |
|     } else {
 | |
|       gtk_window_fullscreen(GTK_WINDOW(mShell));
 | |
|     }
 | |
|   } else {
 | |
|     // Kiosk mode always use fullscreen mode.
 | |
|     if (gKioskMode) {
 | |
|       return NS_ERROR_NOT_AVAILABLE;
 | |
|     }
 | |
| 
 | |
|     gtk_window_unfullscreen(GTK_WINDOW(mShell));
 | |
| 
 | |
|     if (mIsPIPWindow) {
 | |
|       gtk_window_set_type_hint(GTK_WINDOW(mShell),
 | |
|                                GDK_WINDOW_TYPE_HINT_UTILITY);
 | |
|       if (gUseAspectRatio) {
 | |
|         mAspectRatio = mAspectRatioSaved;
 | |
|         // ApplySizeConstraints();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(mLastSizeModeBeforeFullscreen != nsSizeMode_Fullscreen);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsWindow::SetWindowDecoration(BorderStyle aStyle) {
 | |
|   LOG("nsWindow::SetWindowDecoration() Border style %x\n", int(aStyle));
 | |
| 
 | |
|   // We can't use mGdkWindow directly here as it can be
 | |
|   // derived from mContainer which is not a top-level GdkWindow.
 | |
|   GdkWindow* window = GetToplevelGdkWindow();
 | |
| 
 | |
|   // Sawfish, metacity, and presumably other window managers get
 | |
|   // confused if we change the window decorations while the window
 | |
|   // is visible.
 | |
|   bool wasVisible = false;
 | |
|   if (gdk_window_is_visible(window)) {
 | |
|     gdk_window_hide(window);
 | |
|     wasVisible = true;
 | |
|   }
 | |
| 
 | |
|   gint wmd = ConvertBorderStyles(aStyle);
 | |
|   if (wmd != -1) gdk_window_set_decorations(window, (GdkWMDecoration)wmd);
 | |
| 
 | |
|   if (wasVisible) gdk_window_show(window);
 | |
| 
 | |
|     // For some window managers, adding or removing window decorations
 | |
|     // requires unmapping and remapping our toplevel window.  Go ahead
 | |
|     // and flush the queue here so that we don't end up with a BadWindow
 | |
|     // error later when this happens (when the persistence timer fires
 | |
|     // and GetWindowPos is called)
 | |
| #ifdef MOZ_X11
 | |
|   if (GdkIsX11Display()) {
 | |
|     XSync(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), X11False);
 | |
|   } else
 | |
| #endif /* MOZ_X11 */
 | |
|   {
 | |
|     gdk_flush();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::HideWindowChrome(bool aShouldHide) {
 | |
|   SetWindowDecoration(aShouldHide ? BorderStyle::None : mBorderStyle);
 | |
| }
 | |
| 
 | |
| bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
 | |
|                               bool aAlwaysRollup) {
 | |
|   LOG("nsWindow::CheckForRollup() aAlwaysRollup %d", aAlwaysRollup);
 | |
|   nsIRollupListener* rollupListener = GetActiveRollupListener();
 | |
|   nsCOMPtr<nsIWidget> rollupWidget;
 | |
|   if (rollupListener) {
 | |
|     rollupWidget = rollupListener->GetRollupWidget();
 | |
|   }
 | |
|   if (!rollupWidget) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   auto* currentPopup =
 | |
|       (GdkWindow*)rollupWidget->GetNativeData(NS_NATIVE_WINDOW);
 | |
|   if (!aAlwaysRollup && is_mouse_in_window(currentPopup, aMouseX, aMouseY)) {
 | |
|     return false;
 | |
|   }
 | |
|   bool retVal = false;
 | |
|   if (aIsWheel) {
 | |
|     retVal = rollupListener->ShouldConsumeOnMouseWheelEvent();
 | |
|     if (!rollupListener->ShouldRollupOnMouseWheelEvent()) {
 | |
|       return retVal;
 | |
|     }
 | |
|   }
 | |
|   LayoutDeviceIntPoint point;
 | |
|   nsIRollupListener::RollupOptions options{0,
 | |
|                                            nsIRollupListener::FlushViews::Yes};
 | |
|   // if we're dealing with menus, we probably have submenus and
 | |
|   // we don't want to rollup if the click is in a parent menu of
 | |
|   // the current submenu
 | |
|   if (!aAlwaysRollup) {
 | |
|     AutoTArray<nsIWidget*, 5> widgetChain;
 | |
|     uint32_t sameTypeCount =
 | |
|         rollupListener->GetSubmenuWidgetChain(&widgetChain);
 | |
|     for (unsigned long i = 0; i < widgetChain.Length(); ++i) {
 | |
|       nsIWidget* widget = widgetChain[i];
 | |
|       auto* currWindow = (GdkWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
 | |
|       if (is_mouse_in_window(currWindow, aMouseX, aMouseY)) {
 | |
|         // Don't roll up if the mouse event occurred within a menu of the same
 | |
|         // type.
 | |
|         // If the mouse event occurred in a menu higher than that, roll up, but
 | |
|         // pass the number of popups to Rollup so that only those of the same
 | |
|         // type close up.
 | |
|         if (i < sameTypeCount) {
 | |
|           return retVal;
 | |
|         }
 | |
|         options.mCount = sameTypeCount;
 | |
|         break;
 | |
|       }
 | |
|     }  // foreach parent menu widget
 | |
|     if (!aIsWheel) {
 | |
|       point = GdkEventCoordsToDevicePixels(aMouseX, aMouseY);
 | |
|       options.mPoint = &point;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (mSizeMode == nsSizeMode_Minimized) {
 | |
|     // When we try to rollup in a minimized window, transitionend events for
 | |
|     // panels might not fire and thus we might not hide the popup after all,
 | |
|     // see bug 1810797.
 | |
|     options.mAllowAnimations = nsIRollupListener::AllowAnimations::No;
 | |
|   }
 | |
| 
 | |
|   if (rollupListener->Rollup(options)) {
 | |
|     retVal = true;
 | |
|   }
 | |
|   return retVal;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| bool nsWindow::DragInProgress() {
 | |
|   nsCOMPtr<nsIDragService> dragService =
 | |
|       do_GetService("@mozilla.org/widget/dragservice;1");
 | |
|   if (!dragService) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIDragSession> currentDragSession;
 | |
|   dragService->GetCurrentSession(getter_AddRefs(currentDragSession));
 | |
|   return !!currentDragSession;
 | |
| }
 | |
| 
 | |
| // This is an ugly workaround for
 | |
| // https://bugzilla.mozilla.org/show_bug.cgi?id=1622107
 | |
| // We try to detect when Wayland compositor / gtk fails to deliver
 | |
| // info about finished D&D operations and cancel it on our own.
 | |
| MOZ_CAN_RUN_SCRIPT static void WaylandDragWorkaround(GdkEventButton* aEvent) {
 | |
|   static int buttonPressCountWithDrag = 0;
 | |
| 
 | |
|   // We track only left button state as Firefox performs D&D on left
 | |
|   // button only.
 | |
|   if (aEvent->button != 1 || aEvent->type != GDK_BUTTON_PRESS) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIDragService> dragService =
 | |
|       do_GetService("@mozilla.org/widget/dragservice;1");
 | |
|   if (!dragService) {
 | |
|     return;
 | |
|   }
 | |
|   nsCOMPtr<nsIDragSession> currentDragSession;
 | |
|   dragService->GetCurrentSession(getter_AddRefs(currentDragSession));
 | |
| 
 | |
|   if (!currentDragSession) {
 | |
|     buttonPressCountWithDrag = 0;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   buttonPressCountWithDrag++;
 | |
|   if (buttonPressCountWithDrag > 1) {
 | |
|     NS_WARNING(
 | |
|         "Quit unfinished Wayland Drag and Drop operation. Buggy Wayland "
 | |
|         "compositor?");
 | |
|     buttonPressCountWithDrag = 0;
 | |
|     dragService->EndDragSession(false, 0);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static nsWindow* get_window_for_gtk_widget(GtkWidget* widget) {
 | |
|   gpointer user_data = g_object_get_data(G_OBJECT(widget), "nsWindow");
 | |
|   return static_cast<nsWindow*>(user_data);
 | |
| }
 | |
| 
 | |
| static nsWindow* get_window_for_gdk_window(GdkWindow* window) {
 | |
|   gpointer user_data = g_object_get_data(G_OBJECT(window), "nsWindow");
 | |
|   return static_cast<nsWindow*>(user_data);
 | |
| }
 | |
| 
 | |
| static bool is_mouse_in_window(GdkWindow* aWindow, gdouble aMouseX,
 | |
|                                gdouble aMouseY) {
 | |
|   GdkWindow* window = aWindow;
 | |
|   if (!window) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   gint x = 0;
 | |
|   gint y = 0;
 | |
| 
 | |
|   {
 | |
|     gint offsetX = 0;
 | |
|     gint offsetY = 0;
 | |
| 
 | |
|     while (window) {
 | |
|       gint tmpX = 0;
 | |
|       gint tmpY = 0;
 | |
| 
 | |
|       gdk_window_get_position(window, &tmpX, &tmpY);
 | |
|       GtkWidget* widget = get_gtk_widget_for_gdk_window(window);
 | |
| 
 | |
|       // if this is a window, compute x and y given its origin and our
 | |
|       // offset
 | |
|       if (GTK_IS_WINDOW(widget)) {
 | |
|         x = tmpX + offsetX;
 | |
|         y = tmpY + offsetY;
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       offsetX += tmpX;
 | |
|       offsetY += tmpY;
 | |
|       window = gdk_window_get_parent(window);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   gint margin = 0;
 | |
|   if (nsWindow* w = get_window_for_gdk_window(aWindow)) {
 | |
|     margin = w->GetInputRegionMarginInGdkCoords();
 | |
|   }
 | |
| 
 | |
|   x += margin;
 | |
|   y += margin;
 | |
| 
 | |
|   gint w = gdk_window_get_width(aWindow) - margin;
 | |
|   gint h = gdk_window_get_height(aWindow) - margin;
 | |
| 
 | |
|   return aMouseX > x && aMouseX < x + w && aMouseY > y && aMouseY < y + h;
 | |
| }
 | |
| 
 | |
| static GtkWidget* get_gtk_widget_for_gdk_window(GdkWindow* window) {
 | |
|   gpointer user_data = nullptr;
 | |
|   gdk_window_get_user_data(window, &user_data);
 | |
| 
 | |
|   return GTK_WIDGET(user_data);
 | |
| }
 | |
| 
 | |
| static GdkCursor* get_gtk_cursor(nsCursor aCursor) {
 | |
|   GdkCursor* gdkcursor = nullptr;
 | |
|   uint8_t newType = 0xff;
 | |
| 
 | |
|   if ((gdkcursor = gCursorCache[aCursor])) {
 | |
|     return gdkcursor;
 | |
|   }
 | |
| 
 | |
|   GdkDisplay* defaultDisplay = gdk_display_get_default();
 | |
| 
 | |
|   switch (aCursor) {
 | |
|     case eCursor_standard:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "default");
 | |
|       break;
 | |
|     case eCursor_wait:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "wait");
 | |
|       break;
 | |
|     case eCursor_select:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "text");
 | |
|       break;
 | |
|     case eCursor_hyperlink:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "pointer");
 | |
|       break;
 | |
|     case eCursor_n_resize:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "n-resize");
 | |
|       break;
 | |
|     case eCursor_s_resize:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "s-resize");
 | |
|       break;
 | |
|     case eCursor_w_resize:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "w-resize");
 | |
|       break;
 | |
|     case eCursor_e_resize:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "e-resize");
 | |
|       break;
 | |
|     case eCursor_nw_resize:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "nw-resize");
 | |
|       break;
 | |
|     case eCursor_se_resize:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "se-resize");
 | |
|       break;
 | |
|     case eCursor_ne_resize:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "ne-resize");
 | |
|       break;
 | |
|     case eCursor_sw_resize:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "sw-resize");
 | |
|       break;
 | |
|     case eCursor_crosshair:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "crosshair");
 | |
|       break;
 | |
|     case eCursor_move:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "move");
 | |
|       break;
 | |
|     case eCursor_help:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "help");
 | |
|       break;
 | |
|     case eCursor_copy:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "copy");
 | |
|       if (!gdkcursor) newType = MOZ_CURSOR_COPY;
 | |
|       break;
 | |
|     case eCursor_alias:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "alias");
 | |
|       if (!gdkcursor) newType = MOZ_CURSOR_ALIAS;
 | |
|       break;
 | |
|     case eCursor_context_menu:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "context-menu");
 | |
|       if (!gdkcursor) newType = MOZ_CURSOR_CONTEXT_MENU;
 | |
|       break;
 | |
|     case eCursor_cell:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "cell");
 | |
|       break;
 | |
|     case eCursor_grab:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "grab");
 | |
|       if (!gdkcursor) newType = MOZ_CURSOR_HAND_GRAB;
 | |
|       break;
 | |
|     case eCursor_grabbing:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "grabbing");
 | |
|       if (!gdkcursor) newType = MOZ_CURSOR_HAND_GRABBING;
 | |
|       break;
 | |
|     case eCursor_spinning:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "progress");
 | |
|       if (!gdkcursor) newType = MOZ_CURSOR_SPINNING;
 | |
|       break;
 | |
|     case eCursor_zoom_in:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "zoom-in");
 | |
|       if (!gdkcursor) newType = MOZ_CURSOR_ZOOM_IN;
 | |
|       break;
 | |
|     case eCursor_zoom_out:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "zoom-out");
 | |
|       if (!gdkcursor) newType = MOZ_CURSOR_ZOOM_OUT;
 | |
|       break;
 | |
|     case eCursor_not_allowed:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "not-allowed");
 | |
|       if (!gdkcursor) newType = MOZ_CURSOR_NOT_ALLOWED;
 | |
|       break;
 | |
|     case eCursor_no_drop:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "no-drop");
 | |
|       if (!gdkcursor) {  // this nonstandard sequence makes it work on KDE and
 | |
|                          // GNOME
 | |
|         gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "forbidden");
 | |
|       }
 | |
|       if (!gdkcursor) {
 | |
|         gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "circle");
 | |
|       }
 | |
|       if (!gdkcursor) newType = MOZ_CURSOR_NOT_ALLOWED;
 | |
|       break;
 | |
|     case eCursor_vertical_text:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "vertical-text");
 | |
|       if (!gdkcursor) {
 | |
|         newType = MOZ_CURSOR_VERTICAL_TEXT;
 | |
|       }
 | |
|       break;
 | |
|     case eCursor_all_scroll:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "move");
 | |
|       break;
 | |
|     case eCursor_nesw_resize:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "nesw-resize");
 | |
|       if (!gdkcursor) newType = MOZ_CURSOR_NESW_RESIZE;
 | |
|       break;
 | |
|     case eCursor_nwse_resize:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "nwse-resize");
 | |
|       if (!gdkcursor) newType = MOZ_CURSOR_NWSE_RESIZE;
 | |
|       break;
 | |
|     case eCursor_ns_resize:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "ns-resize");
 | |
|       break;
 | |
|     case eCursor_ew_resize:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "ew-resize");
 | |
|       break;
 | |
|     case eCursor_row_resize:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "row-resize");
 | |
|       break;
 | |
|     case eCursor_col_resize:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "col-resize");
 | |
|       break;
 | |
|     case eCursor_none:
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "none");
 | |
|       if (!gdkcursor) newType = MOZ_CURSOR_NONE;
 | |
|       break;
 | |
|     default:
 | |
|       NS_ASSERTION(aCursor, "Invalid cursor type");
 | |
|       gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "default");
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   // If by now we don't have a xcursor, this means we have to make a custom
 | |
|   // one. First, we try creating a named cursor based on the hash of our
 | |
|   // custom bitmap, as libXcursor has some magic to convert bitmapped cursors
 | |
|   // to themed cursors
 | |
|   if (newType != 0xFF && GtkCursors[newType].hash) {
 | |
|     gdkcursor =
 | |
|         gdk_cursor_new_from_name(defaultDisplay, GtkCursors[newType].hash);
 | |
|   }
 | |
| 
 | |
|   // If we still don't have a xcursor, we now really create a bitmap cursor
 | |
|   if (newType != 0xff && !gdkcursor) {
 | |
|     GdkPixbuf* cursor_pixbuf =
 | |
|         gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32);
 | |
|     if (!cursor_pixbuf) return nullptr;
 | |
| 
 | |
|     guchar* data = gdk_pixbuf_get_pixels(cursor_pixbuf);
 | |
| 
 | |
|     // Read data from GtkCursors and compose RGBA surface from 1bit bitmap and
 | |
|     // mask GtkCursors bits and mask are 32x32 monochrome bitmaps (1 bit for
 | |
|     // each pixel) so it's 128 byte array (4 bytes for are one bitmap row and
 | |
|     // there are 32 rows here).
 | |
|     const unsigned char* bits = GtkCursors[newType].bits;
 | |
|     const unsigned char* mask_bits = GtkCursors[newType].mask_bits;
 | |
| 
 | |
|     for (int i = 0; i < 128; i++) {
 | |
|       char bit = (char)*bits++;
 | |
|       char mask = (char)*mask_bits++;
 | |
|       for (int j = 0; j < 8; j++) {
 | |
|         unsigned char pix = ~(((bit >> j) & 0x01) * 0xff);
 | |
|         *data++ = pix;
 | |
|         *data++ = pix;
 | |
|         *data++ = pix;
 | |
|         *data++ = (((mask >> j) & 0x01) * 0xff);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     gdkcursor = gdk_cursor_new_from_pixbuf(
 | |
|         gdk_display_get_default(), cursor_pixbuf, GtkCursors[newType].hot_x,
 | |
|         GtkCursors[newType].hot_y);
 | |
| 
 | |
|     g_object_unref(cursor_pixbuf);
 | |
|   }
 | |
| 
 | |
|   gCursorCache[aCursor] = gdkcursor;
 | |
| 
 | |
|   return gdkcursor;
 | |
| }
 | |
| 
 | |
| // gtk callbacks
 | |
| 
 | |
| void draw_window_of_widget(GtkWidget* widget, GdkWindow* aWindow, cairo_t* cr) {
 | |
|   if (gtk_cairo_should_draw_window(cr, aWindow)) {
 | |
|     RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
 | |
|     if (!window) {
 | |
|       NS_WARNING("Cannot get nsWindow from GtkWidget");
 | |
|     } else {
 | |
|       cairo_save(cr);
 | |
|       gtk_cairo_transform_to_window(cr, widget, aWindow);
 | |
|       // TODO - window->OnExposeEvent() can destroy this or other windows,
 | |
|       // do we need to handle it somehow?
 | |
|       window->OnExposeEvent(cr);
 | |
|       cairo_restore(cr);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| gboolean expose_event_cb(GtkWidget* widget, cairo_t* cr) {
 | |
|   draw_window_of_widget(widget, gtk_widget_get_window(widget), cr);
 | |
| 
 | |
|   // A strong reference is already held during "draw" signal emission,
 | |
|   // but GTK+ 3.4 wants the object to live a little longer than that
 | |
|   // (bug 1225970).
 | |
|   g_object_ref(widget);
 | |
|   g_idle_add(
 | |
|       [](gpointer data) -> gboolean {
 | |
|         g_object_unref(data);
 | |
|         return G_SOURCE_REMOVE;
 | |
|       },
 | |
|       widget);
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static gboolean configure_event_cb(GtkWidget* widget,
 | |
|                                    GdkEventConfigure* event) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
 | |
|   if (!window) {
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   return window->OnConfigureEvent(widget, event);
 | |
| }
 | |
| 
 | |
| // Some Gtk widget code may call gtk_widget_unrealize() which destroys
 | |
| // mGdkWindow. We need to listen on this signal and re-create
 | |
| // mGdkWindow when we're already mapped.
 | |
| static void widget_map_cb(GtkWidget* widget) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
 | |
|   if (!window) {
 | |
|     return;
 | |
|   }
 | |
|   window->OnMap();
 | |
| }
 | |
| 
 | |
| static void widget_unmap_cb(GtkWidget* widget) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
 | |
|   if (!window) {
 | |
|     return;
 | |
|   }
 | |
|   window->OnUnmap();
 | |
| }
 | |
| 
 | |
| static void widget_unrealize_cb(GtkWidget* widget) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
 | |
|   if (!window) {
 | |
|     return;
 | |
|   }
 | |
|   window->OnUnrealize();
 | |
| }
 | |
| 
 | |
| static void size_allocate_cb(GtkWidget* widget, GtkAllocation* allocation) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
 | |
|   if (!window) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   window->OnSizeAllocate(allocation);
 | |
| }
 | |
| 
 | |
| static void toplevel_window_size_allocate_cb(GtkWidget* widget,
 | |
|                                              GtkAllocation* allocation) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
 | |
|   if (!window) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   window->UpdateTopLevelOpaqueRegion();
 | |
| }
 | |
| 
 | |
| static gboolean delete_event_cb(GtkWidget* widget, GdkEventAny* event) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
 | |
|   if (!window) {
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   window->OnDeleteEvent();
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean enter_notify_event_cb(GtkWidget* widget,
 | |
|                                       GdkEventCrossing* event) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
 | |
|   if (!window) {
 | |
|     return TRUE;
 | |
|   }
 | |
| 
 | |
|   // We have stored leave notify - check if it's the correct one and
 | |
|   // fire it before enter notify in such case.
 | |
|   if (sStoredLeaveNotifyEvent) {
 | |
|     auto clearNofityEvent =
 | |
|         MakeScopeExit([&] { sStoredLeaveNotifyEvent = nullptr; });
 | |
|     if (event->x_root == sStoredLeaveNotifyEvent->x_root &&
 | |
|         event->y_root == sStoredLeaveNotifyEvent->y_root &&
 | |
|         window->ApplyEnterLeaveMutterWorkaround()) {
 | |
|       // Enter/Leave notify events has the same coordinates
 | |
|       // and uses know buggy window config.
 | |
|       // Consider it as a bogus one.
 | |
|       return TRUE;
 | |
|     }
 | |
|     RefPtr<nsWindow> leftWindow =
 | |
|         get_window_for_gdk_window(sStoredLeaveNotifyEvent->window);
 | |
|     if (leftWindow) {
 | |
|       leftWindow->OnLeaveNotifyEvent(sStoredLeaveNotifyEvent.get());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   window->OnEnterNotifyEvent(event);
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean leave_notify_event_cb(GtkWidget* widget,
 | |
|                                       GdkEventCrossing* event) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
 | |
|   if (!window) {
 | |
|     return TRUE;
 | |
|   }
 | |
| 
 | |
|   if (window->ApplyEnterLeaveMutterWorkaround()) {
 | |
|     // The leave event is potentially wrong, don't fire it now but store
 | |
|     // it for further check at enter_notify_event_cb().
 | |
|     sStoredLeaveNotifyEvent.reset(reinterpret_cast<GdkEventCrossing*>(
 | |
|         gdk_event_copy(reinterpret_cast<GdkEvent*>(event))));
 | |
|   } else {
 | |
|     sStoredLeaveNotifyEvent = nullptr;
 | |
|     window->OnLeaveNotifyEvent(event);
 | |
|   }
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static nsWindow* GetFirstNSWindowForGDKWindow(GdkWindow* aGdkWindow) {
 | |
|   nsWindow* window;
 | |
|   while (!(window = get_window_for_gdk_window(aGdkWindow))) {
 | |
|     // The event has bubbled to the moz_container widget as passed into each
 | |
|     // caller's *widget parameter, but its corresponding nsWindow is an ancestor
 | |
|     // of the window that we need.  Instead, look at event->window and find the
 | |
|     // first ancestor nsWindow of it because event->window may be in a plugin.
 | |
|     aGdkWindow = gdk_window_get_parent(aGdkWindow);
 | |
|     if (!aGdkWindow) {
 | |
|       window = nullptr;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   return window;
 | |
| }
 | |
| 
 | |
| static gboolean motion_notify_event_cb(GtkWidget* widget,
 | |
|                                        GdkEventMotion* event) {
 | |
|   UpdateLastInputEventTime(event);
 | |
| 
 | |
|   RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
 | |
|   if (!window) return FALSE;
 | |
| 
 | |
|   window->OnMotionNotifyEvent(event);
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean button_press_event_cb(GtkWidget* widget,
 | |
|                                       GdkEventButton* event) {
 | |
|   UpdateLastInputEventTime(event);
 | |
| 
 | |
|   RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
 | |
|   if (!window) return FALSE;
 | |
| 
 | |
|   window->OnButtonPressEvent(event);
 | |
| 
 | |
|   if (GdkIsWaylandDisplay()) {
 | |
|     WaylandDragWorkaround(event);
 | |
|   }
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean button_release_event_cb(GtkWidget* widget,
 | |
|                                         GdkEventButton* event) {
 | |
|   UpdateLastInputEventTime(event);
 | |
| 
 | |
|   RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
 | |
|   if (!window) return FALSE;
 | |
| 
 | |
|   window->OnButtonReleaseEvent(event);
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean focus_in_event_cb(GtkWidget* widget, GdkEventFocus* event) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
 | |
|   if (!window) return FALSE;
 | |
| 
 | |
|   window->OnContainerFocusInEvent(event);
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static gboolean focus_out_event_cb(GtkWidget* widget, GdkEventFocus* event) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
 | |
|   if (!window) return FALSE;
 | |
| 
 | |
|   window->OnContainerFocusOutEvent(event);
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
| // For long-lived popup windows that don't really take focus themselves but
 | |
| // may have elements that accept keyboard input when the parent window is
 | |
| // active, focus is handled specially.  These windows include noautohide
 | |
| // panels.  (This special handling is not necessary for temporary popups where
 | |
| // the keyboard is grabbed.)
 | |
| //
 | |
| // Mousing over or clicking on these windows should not cause them to steal
 | |
| // focus from their parent windows, so, the input field of WM_HINTS is set to
 | |
| // False to request that the window manager not set the input focus to this
 | |
| // window.  http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7
 | |
| //
 | |
| // However, these windows can still receive WM_TAKE_FOCUS messages from the
 | |
| // window manager, so they can still detect when the user has indicated that
 | |
| // they wish to direct keyboard input at these windows.  When the window
 | |
| // manager offers focus to these windows (after a mouse over or click, for
 | |
| // example), a request to make the parent window active is issued.  When the
 | |
| // parent window becomes active, keyboard events will be received.
 | |
| 
 | |
| static GdkFilterReturn popup_take_focus_filter(GdkXEvent* gdk_xevent,
 | |
|                                                GdkEvent* event, gpointer data) {
 | |
|   auto* xevent = static_cast<XEvent*>(gdk_xevent);
 | |
|   if (xevent->type != ClientMessage) return GDK_FILTER_CONTINUE;
 | |
| 
 | |
|   XClientMessageEvent& xclient = xevent->xclient;
 | |
|   if (xclient.message_type != gdk_x11_get_xatom_by_name("WM_PROTOCOLS")) {
 | |
|     return GDK_FILTER_CONTINUE;
 | |
|   }
 | |
| 
 | |
|   Atom atom = xclient.data.l[0];
 | |
|   if (atom != gdk_x11_get_xatom_by_name("WM_TAKE_FOCUS")) {
 | |
|     return GDK_FILTER_CONTINUE;
 | |
|   }
 | |
| 
 | |
|   guint32 timestamp = xclient.data.l[1];
 | |
| 
 | |
|   GtkWidget* widget = get_gtk_widget_for_gdk_window(event->any.window);
 | |
|   if (!widget) return GDK_FILTER_CONTINUE;
 | |
| 
 | |
|   GtkWindow* parent = gtk_window_get_transient_for(GTK_WINDOW(widget));
 | |
|   if (!parent) return GDK_FILTER_CONTINUE;
 | |
| 
 | |
|   if (gtk_window_is_active(parent)) {
 | |
|     return GDK_FILTER_REMOVE;  // leave input focus on the parent
 | |
|   }
 | |
| 
 | |
|   GdkWindow* parent_window = gtk_widget_get_window(GTK_WIDGET(parent));
 | |
|   if (!parent_window) return GDK_FILTER_CONTINUE;
 | |
| 
 | |
|   // In case the parent has not been deiconified.
 | |
|   gdk_window_show_unraised(parent_window);
 | |
| 
 | |
|   // Request focus on the parent window.
 | |
|   // Use gdk_window_focus rather than gtk_window_present to avoid
 | |
|   // raising the parent window.
 | |
|   gdk_window_focus(parent_window, timestamp);
 | |
|   return GDK_FILTER_REMOVE;
 | |
| }
 | |
| #endif /* MOZ_X11 */
 | |
| 
 | |
| static gboolean key_press_event_cb(GtkWidget* widget, GdkEventKey* event) {
 | |
|   LOGW("key_press_event_cb\n");
 | |
| 
 | |
|   UpdateLastInputEventTime(event);
 | |
| 
 | |
|   // find the window with focus and dispatch this event to that widget
 | |
|   nsWindow* window = get_window_for_gtk_widget(widget);
 | |
|   if (!window) return FALSE;
 | |
| 
 | |
|   RefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
|   // Keyboard repeat can cause key press events to queue up when there are
 | |
|   // slow event handlers (bug 301029).  Throttle these events by removing
 | |
|   // consecutive pending duplicate KeyPress events to the same window.
 | |
|   // We use the event time of the last one.
 | |
|   // Note: GDK calls XkbSetDetectableAutorepeat so that KeyRelease events
 | |
|   // are generated only when the key is physically released.
 | |
| #  define NS_GDKEVENT_MATCH_MASK 0x1FFF  // GDK_SHIFT_MASK .. GDK_BUTTON5_MASK
 | |
|   // Our headers undefine X11 KeyPress - let's redefine it here.
 | |
| #  ifndef KeyPress
 | |
| #    define KeyPress 2
 | |
| #  endif
 | |
|   GdkDisplay* gdkDisplay = gtk_widget_get_display(widget);
 | |
|   if (GdkIsX11Display(gdkDisplay)) {
 | |
|     Display* dpy = GDK_DISPLAY_XDISPLAY(gdkDisplay);
 | |
|     while (XPending(dpy)) {
 | |
|       XEvent next_event;
 | |
|       XPeekEvent(dpy, &next_event);
 | |
|       GdkWindow* nextGdkWindow =
 | |
|           gdk_x11_window_lookup_for_display(gdkDisplay, next_event.xany.window);
 | |
|       if (nextGdkWindow != event->window || next_event.type != KeyPress ||
 | |
|           next_event.xkey.keycode != event->hardware_keycode ||
 | |
|           next_event.xkey.state != (event->state & NS_GDKEVENT_MATCH_MASK)) {
 | |
|         break;
 | |
|       }
 | |
|       XNextEvent(dpy, &next_event);
 | |
|       event->time = next_event.xkey.time;
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   return focusWindow->OnKeyPressEvent(event);
 | |
| }
 | |
| 
 | |
| static gboolean key_release_event_cb(GtkWidget* widget, GdkEventKey* event) {
 | |
|   LOGW("key_release_event_cb\n");
 | |
| 
 | |
|   UpdateLastInputEventTime(event);
 | |
| 
 | |
|   // find the window with focus and dispatch this event to that widget
 | |
|   nsWindow* window = get_window_for_gtk_widget(widget);
 | |
|   if (!window) return FALSE;
 | |
| 
 | |
|   RefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
 | |
|   return focusWindow->OnKeyReleaseEvent(event);
 | |
| }
 | |
| 
 | |
| static gboolean property_notify_event_cb(GtkWidget* aWidget,
 | |
|                                          GdkEventProperty* aEvent) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gdk_window(aEvent->window);
 | |
|   if (!window) return FALSE;
 | |
| 
 | |
|   return window->OnPropertyNotifyEvent(aWidget, aEvent);
 | |
| }
 | |
| 
 | |
| static gboolean scroll_event_cb(GtkWidget* widget, GdkEventScroll* event) {
 | |
|   RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
 | |
|   if (!window) return FALSE;
 | |
| 
 | |
|   window->OnScrollEvent(event);
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean visibility_notify_event_cb(GtkWidget* widget,
 | |
|                                            GdkEventVisibility* event) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
 | |
|   if (!window) {
 | |
|     return FALSE;
 | |
|   }
 | |
|   window->OnVisibilityNotifyEvent(event->state);
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static void hierarchy_changed_cb(GtkWidget* widget,
 | |
|                                  GtkWidget* previous_toplevel) {
 | |
|   GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
 | |
|   GdkWindowState old_window_state = GDK_WINDOW_STATE_WITHDRAWN;
 | |
|   GdkEventWindowState event;
 | |
| 
 | |
|   event.new_window_state = GDK_WINDOW_STATE_WITHDRAWN;
 | |
| 
 | |
|   if (GTK_IS_WINDOW(previous_toplevel)) {
 | |
|     g_signal_handlers_disconnect_by_func(
 | |
|         previous_toplevel, FuncToGpointer(window_state_event_cb), widget);
 | |
|     GdkWindow* win = gtk_widget_get_window(previous_toplevel);
 | |
|     if (win) {
 | |
|       old_window_state = gdk_window_get_state(win);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (GTK_IS_WINDOW(toplevel)) {
 | |
|     g_signal_connect_swapped(toplevel, "window-state-event",
 | |
|                              G_CALLBACK(window_state_event_cb), widget);
 | |
|     GdkWindow* win = gtk_widget_get_window(toplevel);
 | |
|     if (win) {
 | |
|       event.new_window_state = gdk_window_get_state(win);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   event.changed_mask =
 | |
|       static_cast<GdkWindowState>(old_window_state ^ event.new_window_state);
 | |
| 
 | |
|   if (event.changed_mask) {
 | |
|     event.type = GDK_WINDOW_STATE;
 | |
|     event.window = nullptr;
 | |
|     event.send_event = TRUE;
 | |
|     window_state_event_cb(widget, &event);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static gboolean window_state_event_cb(GtkWidget* widget,
 | |
|                                       GdkEventWindowState* event) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
 | |
|   if (!window) return FALSE;
 | |
| 
 | |
|   window->OnWindowStateEvent(widget, event);
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static void settings_xft_dpi_changed_cb(GtkSettings* gtk_settings,
 | |
|                                         GParamSpec* pspec, nsWindow* data) {
 | |
|   RefPtr<nsWindow> window = data;
 | |
|   window->OnDPIChanged();
 | |
|   // Even though the window size in screen pixels has not changed,
 | |
|   // nsViewManager stores the dimensions in app units.
 | |
|   // DispatchResized() updates those.
 | |
|   window->DispatchResized();
 | |
| }
 | |
| 
 | |
| static void check_resize_cb(GtkContainer* container, gpointer user_data) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(GTK_WIDGET(container));
 | |
|   if (!window) {
 | |
|     return;
 | |
|   }
 | |
|   window->OnCheckResize();
 | |
| }
 | |
| 
 | |
| static void screen_composited_changed_cb(GdkScreen* screen,
 | |
|                                          gpointer user_data) {
 | |
|   // This callback can run before gfxPlatform::Init() in rare
 | |
|   // cases involving the profile manager. When this happens,
 | |
|   // we have no reason to reset any compositors as graphics
 | |
|   // hasn't been initialized yet.
 | |
|   if (GPUProcessManager::Get()) {
 | |
|     GPUProcessManager::Get()->ResetCompositors();
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void widget_composited_changed_cb(GtkWidget* widget,
 | |
|                                          gpointer user_data) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
 | |
|   if (!window) {
 | |
|     return;
 | |
|   }
 | |
|   window->OnCompositedChanged();
 | |
| }
 | |
| 
 | |
| static void scale_changed_cb(GtkWidget* widget, GParamSpec* aPSpec,
 | |
|                              gpointer aPointer) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
 | |
|   if (!window) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   window->OnScaleChanged(/* aNotify = */ true);
 | |
| }
 | |
| 
 | |
| static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent) {
 | |
|   UpdateLastInputEventTime(aEvent);
 | |
| 
 | |
|   RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(aEvent->window);
 | |
|   if (!window) {
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   return window->OnTouchEvent(aEvent);
 | |
| }
 | |
| 
 | |
| // This function called generic because there is no signal specific to touchpad
 | |
| // pinch events.
 | |
| static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent) {
 | |
|   if (aEvent->type != GDK_TOUCHPAD_PINCH) {
 | |
|     return FALSE;
 | |
|   }
 | |
|   // Using reinterpret_cast because the touchpad_pinch field of GdkEvent is not
 | |
|   // available in GTK+ versions lower than v3.18
 | |
|   GdkEventTouchpadPinch* event =
 | |
|       reinterpret_cast<GdkEventTouchpadPinch*>(aEvent);
 | |
| 
 | |
|   RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
 | |
| 
 | |
|   if (!window) {
 | |
|     return FALSE;
 | |
|   }
 | |
|   return window->OnTouchpadPinchEvent(event);
 | |
| }
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////
 | |
| // These are all of our drag and drop operations
 | |
| 
 | |
| void nsWindow::InitDragEvent(WidgetDragEvent& aEvent) {
 | |
|   // set the keyboard modifiers
 | |
|   guint modifierState = KeymapWrapper::GetCurrentModifierState();
 | |
|   KeymapWrapper::InitInputEvent(aEvent, modifierState);
 | |
| }
 | |
| 
 | |
| static LayoutDeviceIntPoint GetWindowDropPosition(nsWindow* aWindow, int aX,
 | |
|                                                   int aY) {
 | |
|   // Workaround for Bug 1710344
 | |
|   // Caused by Gtk issue https://gitlab.gnome.org/GNOME/gtk/-/issues/4437
 | |
|   if (aWindow->IsWaylandPopup()) {
 | |
|     int tx = 0, ty = 0;
 | |
|     gdk_window_get_position(aWindow->GetToplevelGdkWindow(), &tx, &ty);
 | |
|     aX += tx;
 | |
|     aY += ty;
 | |
|   }
 | |
|   LOGDRAG("WindowDropPosition [%d, %d]", aX, aY);
 | |
|   return aWindow->GdkPointToDevicePixels({aX, aY});
 | |
| }
 | |
| 
 | |
| gboolean WindowDragMotionHandler(GtkWidget* aWidget,
 | |
|                                  GdkDragContext* aDragContext, gint aX, gint aY,
 | |
|                                  guint aTime) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
 | |
|   if (!window) {
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   // figure out which internal widget this drag motion actually happened on
 | |
|   nscoord retx = 0;
 | |
|   nscoord rety = 0;
 | |
| 
 | |
|   GdkWindow* innerWindow = get_inner_gdk_window(gtk_widget_get_window(aWidget),
 | |
|                                                 aX, aY, &retx, &rety);
 | |
|   RefPtr<nsWindow> innerMostWindow = get_window_for_gdk_window(innerWindow);
 | |
|   if (!innerMostWindow) {
 | |
|     innerMostWindow = window;
 | |
|   }
 | |
|   LOGDRAG("WindowDragMotionHandler target nsWindow [%p]",
 | |
|           innerMostWindow.get());
 | |
| 
 | |
|   RefPtr<nsDragService> dragService = nsDragService::GetInstance();
 | |
|   nsDragService::AutoEventLoop loop(dragService);
 | |
|   if (!dragService->ScheduleMotionEvent(
 | |
|           innerMostWindow, aDragContext,
 | |
|           GetWindowDropPosition(innerMostWindow, retx, rety), aTime)) {
 | |
|     return FALSE;
 | |
|   }
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean drag_motion_event_cb(GtkWidget* aWidget,
 | |
|                                      GdkDragContext* aDragContext, gint aX,
 | |
|                                      gint aY, guint aTime, gpointer aData) {
 | |
|   return WindowDragMotionHandler(aWidget, aDragContext, aX, aY, aTime);
 | |
| }
 | |
| 
 | |
| void WindowDragLeaveHandler(GtkWidget* aWidget) {
 | |
|   LOGDRAG("WindowDragLeaveHandler()\n");
 | |
| 
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
 | |
|   if (!window) {
 | |
|     LOGDRAG("    Failed - can't find nsWindow!\n");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   RefPtr<nsDragService> dragService = nsDragService::GetInstance();
 | |
|   nsDragService::AutoEventLoop loop(dragService);
 | |
| 
 | |
|   nsWindow* mostRecentDragWindow = dragService->GetMostRecentDestWindow();
 | |
|   if (!mostRecentDragWindow) {
 | |
|     // This can happen when the target will not accept a drop.  A GTK drag
 | |
|     // source sends the leave message to the destination before the
 | |
|     // drag-failed signal on the source widget, but the leave message goes
 | |
|     // via the X server, and so doesn't get processed at least until the
 | |
|     // event loop runs again.
 | |
|     LOGDRAG("    Failed - GetMostRecentDestWindow()!\n");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aWidget != window->GetGtkWidget()) {
 | |
|     // When the drag moves between widgets, GTK can send leave signal for
 | |
|     // the old widget after the motion or drop signal for the new widget.
 | |
|     // We'll send the leave event when the motion or drop event is run.
 | |
|     LOGDRAG("    Failed - GtkWidget mismatch!\n");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LOGDRAG("WindowDragLeaveHandler nsWindow %p\n", (void*)mostRecentDragWindow);
 | |
|   dragService->ScheduleLeaveEvent();
 | |
| }
 | |
| 
 | |
| static void drag_leave_event_cb(GtkWidget* aWidget,
 | |
|                                 GdkDragContext* aDragContext, guint aTime,
 | |
|                                 gpointer aData) {
 | |
|   WindowDragLeaveHandler(aWidget);
 | |
| }
 | |
| 
 | |
| gboolean WindowDragDropHandler(GtkWidget* aWidget, GdkDragContext* aDragContext,
 | |
|                                gint aX, gint aY, guint aTime) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
 | |
|   if (!window) return FALSE;
 | |
| 
 | |
|   // figure out which internal widget this drag motion actually happened on
 | |
|   nscoord retx = 0;
 | |
|   nscoord rety = 0;
 | |
| 
 | |
|   GdkWindow* innerWindow = get_inner_gdk_window(gtk_widget_get_window(aWidget),
 | |
|                                                 aX, aY, &retx, &rety);
 | |
|   RefPtr<nsWindow> innerMostWindow = get_window_for_gdk_window(innerWindow);
 | |
| 
 | |
|   if (!innerMostWindow) {
 | |
|     innerMostWindow = window;
 | |
|   }
 | |
| 
 | |
|   LOGDRAG("WindowDragDropHandler nsWindow [%p]", innerMostWindow.get());
 | |
|   RefPtr<nsDragService> dragService = nsDragService::GetInstance();
 | |
|   nsDragService::AutoEventLoop loop(dragService);
 | |
|   return dragService->ScheduleDropEvent(
 | |
|       innerMostWindow, aDragContext,
 | |
|       GetWindowDropPosition(innerMostWindow, retx, rety), aTime);
 | |
| }
 | |
| 
 | |
| static gboolean drag_drop_event_cb(GtkWidget* aWidget,
 | |
|                                    GdkDragContext* aDragContext, gint aX,
 | |
|                                    gint aY, guint aTime, gpointer aData) {
 | |
|   return WindowDragDropHandler(aWidget, aDragContext, aX, aY, aTime);
 | |
| }
 | |
| 
 | |
| static void drag_data_received_event_cb(GtkWidget* aWidget,
 | |
|                                         GdkDragContext* aDragContext, gint aX,
 | |
|                                         gint aY,
 | |
|                                         GtkSelectionData* aSelectionData,
 | |
|                                         guint aInfo, guint aTime,
 | |
|                                         gpointer aData) {
 | |
|   RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
 | |
|   if (!window) return;
 | |
| 
 | |
|   window->OnDragDataReceivedEvent(aWidget, aDragContext, aX, aY, aSelectionData,
 | |
|                                   aInfo, aTime, aData);
 | |
| }
 | |
| 
 | |
| static nsresult initialize_prefs(void) {
 | |
|   if (Preferences::HasUserValue("widget.use-aspect-ratio")) {
 | |
|     gUseAspectRatio = Preferences::GetBool("widget.use-aspect-ratio", true);
 | |
|   } else {
 | |
|     gUseAspectRatio = IsGnomeDesktopEnvironment() || IsKdeDesktopEnvironment();
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // TODO: Can we simplify it for mShell/mContainer only scenario?
 | |
| static GdkWindow* get_inner_gdk_window(GdkWindow* aWindow, gint x, gint y,
 | |
|                                        gint* retx, gint* rety) {
 | |
|   gint cx, cy, cw, ch;
 | |
|   GList* children = gdk_window_peek_children(aWindow);
 | |
|   for (GList* child = g_list_last(children); child;
 | |
|        child = g_list_previous(child)) {
 | |
|     auto* childWindow = (GdkWindow*)child->data;
 | |
|     if (get_window_for_gdk_window(childWindow)) {
 | |
|       gdk_window_get_geometry(childWindow, &cx, &cy, &cw, &ch);
 | |
|       if ((cx < x) && (x < (cx + cw)) && (cy < y) && (y < (cy + ch)) &&
 | |
|           gdk_window_is_visible(childWindow)) {
 | |
|         return get_inner_gdk_window(childWindow, x - cx, y - cy, retx, rety);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   *retx = x;
 | |
|   *rety = y;
 | |
|   return aWindow;
 | |
| }
 | |
| 
 | |
| #ifdef ACCESSIBILITY
 | |
| void nsWindow::CreateRootAccessible() {
 | |
|   if (!mRootAccessible) {
 | |
|     LOG("nsWindow:: Create Toplevel Accessibility\n");
 | |
|     mRootAccessible = GetRootAccessible();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::DispatchEventToRootAccessible(uint32_t aEventType) {
 | |
|   if (!a11y::ShouldA11yBeEnabled()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsAccessibilityService* accService = GetOrCreateAccService();
 | |
|   if (!accService) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Get the root document accessible and fire event to it.
 | |
|   a11y::LocalAccessible* acc = GetRootAccessible();
 | |
|   if (acc) {
 | |
|     accService->FireAccessibleEvent(aEventType, acc);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::DispatchActivateEventAccessible(void) {
 | |
|   DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_ACTIVATE);
 | |
| }
 | |
| 
 | |
| void nsWindow::DispatchDeactivateEventAccessible(void) {
 | |
|   DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_DEACTIVATE);
 | |
| }
 | |
| 
 | |
| void nsWindow::DispatchMaximizeEventAccessible(void) {
 | |
|   DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_MAXIMIZE);
 | |
| }
 | |
| 
 | |
| void nsWindow::DispatchMinimizeEventAccessible(void) {
 | |
|   DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_MINIMIZE);
 | |
| }
 | |
| 
 | |
| void nsWindow::DispatchRestoreEventAccessible(void) {
 | |
|   DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_RESTORE);
 | |
| }
 | |
| 
 | |
| #endif /* #ifdef ACCESSIBILITY */
 | |
| 
 | |
| void nsWindow::SetInputContext(const InputContext& aContext,
 | |
|                                const InputContextAction& aAction) {
 | |
|   if (!mIMContext) {
 | |
|     return;
 | |
|   }
 | |
|   mIMContext->SetInputContext(this, &aContext, &aAction);
 | |
| }
 | |
| 
 | |
| InputContext nsWindow::GetInputContext() {
 | |
|   InputContext context;
 | |
|   if (!mIMContext) {
 | |
|     context.mIMEState.mEnabled = IMEEnabled::Disabled;
 | |
|     context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
 | |
|   } else {
 | |
|     context = mIMContext->GetInputContext();
 | |
|   }
 | |
|   return context;
 | |
| }
 | |
| 
 | |
| TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() {
 | |
|   if (NS_WARN_IF(!mIMContext)) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   return mIMContext;
 | |
| }
 | |
| 
 | |
| bool nsWindow::GetEditCommands(NativeKeyBindingsType aType,
 | |
|                                const WidgetKeyboardEvent& aEvent,
 | |
|                                nsTArray<CommandInt>& aCommands) {
 | |
|   // Validate the arguments.
 | |
|   if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   Maybe<WritingMode> writingMode;
 | |
|   if (aEvent.NeedsToRemapNavigationKey()) {
 | |
|     if (RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher()) {
 | |
|       writingMode = dispatcher->MaybeQueryWritingModeAtSelection();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
 | |
|   keyBindings->GetEditCommands(aEvent, writingMode, aCommands);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| already_AddRefed<DrawTarget> nsWindow::StartRemoteDrawingInRegion(
 | |
|     const LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode) {
 | |
|   return mSurfaceProvider.StartRemoteDrawingInRegion(aInvalidRegion,
 | |
|                                                      aBufferMode);
 | |
| }
 | |
| 
 | |
| void nsWindow::EndRemoteDrawingInRegion(
 | |
|     DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
 | |
|   mSurfaceProvider.EndRemoteDrawingInRegion(aDrawTarget, aInvalidRegion);
 | |
| }
 | |
| 
 | |
| bool nsWindow::GetDragInfo(WidgetMouseEvent* aMouseEvent, GdkWindow** aWindow,
 | |
|                            gint* aButton, gint* aRootX, gint* aRootY) {
 | |
|   if (aMouseEvent->mButton != MouseButton::ePrimary) {
 | |
|     // we can only begin a move drag with the left mouse button
 | |
|     return false;
 | |
|   }
 | |
|   *aButton = 1;
 | |
| 
 | |
|   // get the gdk window for this widget
 | |
|   GdkWindow* gdk_window = mGdkWindow;
 | |
|   if (!gdk_window) {
 | |
|     return false;
 | |
|   }
 | |
| #ifdef DEBUG
 | |
|   // GDK_IS_WINDOW(...) expands to a statement-expression, and
 | |
|   // statement-expressions are not allowed in template-argument lists. So we
 | |
|   // have to make the MOZ_ASSERT condition indirect.
 | |
|   if (!GDK_IS_WINDOW(gdk_window)) {
 | |
|     MOZ_ASSERT(false, "must really be window");
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   // find the top-level window
 | |
|   gdk_window = gdk_window_get_toplevel(gdk_window);
 | |
|   MOZ_ASSERT(gdk_window, "gdk_window_get_toplevel should not return null");
 | |
|   *aWindow = gdk_window;
 | |
| 
 | |
|   if (!aMouseEvent->mWidget) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
|   if (GdkIsX11Display()) {
 | |
|     // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=789054
 | |
|     // To avoid crashes disable double-click on WM without _NET_WM_MOVERESIZE.
 | |
|     // See _should_perform_ewmh_drag() at gdkwindow-x11.c
 | |
|     // XXXsmaug remove this old hack. gtk should be fixed now.
 | |
|     GdkScreen* screen = gdk_window_get_screen(gdk_window);
 | |
|     GdkAtom atom = gdk_atom_intern("_NET_WM_MOVERESIZE", FALSE);
 | |
|     if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
 | |
|       static TimeStamp lastTimeStamp;
 | |
|       if (lastTimeStamp != aMouseEvent->mTimeStamp) {
 | |
|         lastTimeStamp = aMouseEvent->mTimeStamp;
 | |
|       } else {
 | |
|         return false;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   // FIXME: It would be nice to have the widget position at the time
 | |
|   // of the event, but it's relatively unlikely that the widget has
 | |
|   // moved since the mousedown.  (On the other hand, it's quite likely
 | |
|   // that the mouse has moved, which is why we use the mouse position
 | |
|   // from the event.)
 | |
|   LayoutDeviceIntPoint offset = aMouseEvent->mWidget->WidgetToScreenOffset();
 | |
|   *aRootX = aMouseEvent->mRefPoint.x + offset.x;
 | |
|   *aRootY = aMouseEvent->mRefPoint.y + offset.y;
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| nsIWidget::WindowRenderer* nsWindow::GetWindowRenderer() {
 | |
|   if (mIsDestroyed) {
 | |
|     // Prevent external code from triggering the re-creation of the
 | |
|     // LayerManager/Compositor during shutdown. Just return what we currently
 | |
|     // have, which is most likely null.
 | |
|     return mWindowRenderer;
 | |
|   }
 | |
| 
 | |
|   return nsBaseWidget::GetWindowRenderer();
 | |
| }
 | |
| 
 | |
| void nsWindow::DidGetNonBlankPaint() {
 | |
|   if (mGotNonBlankPaint) {
 | |
|     return;
 | |
|   }
 | |
|   mGotNonBlankPaint = true;
 | |
|   if (!mConfiguredClearColor) {
 | |
|     // Nothing to do, we hadn't overridden the clear color.
 | |
|     mConfiguredClearColor = true;
 | |
|     return;
 | |
|   }
 | |
|   // Reset the clear color set in the expose event to transparent.
 | |
|   GetWindowRenderer()->AsWebRender()->WrBridge()->SendSetDefaultClearColor(
 | |
|       NS_TRANSPARENT);
 | |
| }
 | |
| 
 | |
| /* nsWindow::SetCompositorWidgetDelegate() sets remote GtkCompositorWidget
 | |
|  * to render into with compositor.
 | |
|  *
 | |
|  * SetCompositorWidgetDelegate(delegate) is called from
 | |
|  * nsBaseWidget::CreateCompositor(), i.e. nsWindow::GetWindowRenderer().
 | |
|  *
 | |
|  * SetCompositorWidgetDelegate(null) is called from
 | |
|  * nsBaseWidget::DestroyCompositor().
 | |
|  */
 | |
| void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
 | |
|   LOG("nsWindow::SetCompositorWidgetDelegate %p mIsMapped %d "
 | |
|       "mCompositorWidgetDelegate %p\n",
 | |
|       delegate, mIsMapped, mCompositorWidgetDelegate);
 | |
| 
 | |
|   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 | |
|   if (delegate) {
 | |
|     mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate();
 | |
|     MOZ_ASSERT(mCompositorWidgetDelegate,
 | |
|                "nsWindow::SetCompositorWidgetDelegate called with a "
 | |
|                "non-PlatformCompositorWidgetDelegate");
 | |
|     if (mIsMapped) {
 | |
|       ConfigureCompositor();
 | |
|     }
 | |
|   } else {
 | |
|     mCompositorWidgetDelegate = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult nsWindow::SetNonClientMargins(const LayoutDeviceIntMargin& aMargins) {
 | |
|   SetDrawsInTitlebar(aMargins.top == 0);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| bool nsWindow::IsAlwaysUndecoratedWindow() const {
 | |
|   if (mIsPIPWindow || gKioskMode) {
 | |
|     return true;
 | |
|   }
 | |
|   if (mWindowType == WindowType::Dialog &&
 | |
|       mBorderStyle != BorderStyle::Default &&
 | |
|       mBorderStyle != BorderStyle::All &&
 | |
|       !(mBorderStyle & BorderStyle::Title) &&
 | |
|       !(mBorderStyle & BorderStyle::ResizeH)) {
 | |
|     return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void nsWindow::SetDrawsInTitlebar(bool aState) {
 | |
|   LOG("nsWindow::SetDrawsInTitlebar() State %d mGtkWindowDecoration %d\n",
 | |
|       aState, (int)mGtkWindowDecoration);
 | |
| 
 | |
|   if (mGtkWindowDecoration == GTK_DECORATION_NONE ||
 | |
|       aState == mDrawInTitlebar) {
 | |
|     LOG("  already set, quit");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mUndecorated) {
 | |
|     MOZ_ASSERT(aState, "Unexpected decoration request");
 | |
|     MOZ_ASSERT(!gtk_window_get_decorated(GTK_WINDOW(mShell)));
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mDrawInTitlebar = aState;
 | |
| 
 | |
|   if (mGtkWindowDecoration == GTK_DECORATION_SYSTEM) {
 | |
|     SetWindowDecoration(aState ? BorderStyle::Border : mBorderStyle);
 | |
|   } else if (mGtkWindowDecoration == GTK_DECORATION_CLIENT) {
 | |
|     LOG("    Using CSD mode\n");
 | |
| 
 | |
|     if (!gtk_widget_get_realized(GTK_WIDGET(mShell))) {
 | |
|       LOG("    Using CSD mode fast path\n");
 | |
|       gtk_window_set_titlebar(GTK_WINDOW(mShell),
 | |
|                               aState ? gtk_fixed_new() : nullptr);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     /* Window manager does not support GDK_DECOR_BORDER,
 | |
|      * emulate it by CSD.
 | |
|      *
 | |
|      * gtk_window_set_titlebar() works on unrealized widgets only,
 | |
|      * we need to handle mShell carefully here.
 | |
|      * When CSD is enabled mGdkWindow is owned by mContainer which is good
 | |
|      * as we can't delete our mGdkWindow. To make mShell unrealized while
 | |
|      * mContainer is preserved we temporary reparent mContainer to an
 | |
|      * invisible GtkWindow.
 | |
|      */
 | |
|     bool visible = !mNeedsShow && mIsShown;
 | |
|     if (visible) {
 | |
|       NativeShow(false);
 | |
|     }
 | |
| 
 | |
|     // Using GTK_WINDOW_POPUP rather than
 | |
|     // GTK_WINDOW_TOPLEVEL in the hope that POPUP results in less
 | |
|     // initialization and window manager interaction.
 | |
|     GtkWidget* tmpWindow = gtk_window_new(GTK_WINDOW_POPUP);
 | |
|     gtk_widget_realize(tmpWindow);
 | |
| 
 | |
|     gtk_widget_reparent(GTK_WIDGET(mContainer), tmpWindow);
 | |
|     gtk_widget_unrealize(GTK_WIDGET(mShell));
 | |
| 
 | |
|     // Add a hidden titlebar widget to trigger CSD, but disable the default
 | |
|     // titlebar.  GtkFixed is a somewhat random choice for a simple unused
 | |
|     // widget. gtk_window_set_titlebar() takes ownership of the titlebar
 | |
|     // widget.
 | |
|     gtk_window_set_titlebar(GTK_WINDOW(mShell),
 | |
|                             aState ? gtk_fixed_new() : nullptr);
 | |
| 
 | |
|     /* A workaround for https://bugzilla.gnome.org/show_bug.cgi?id=791081
 | |
|      * gtk_widget_realize() throws:
 | |
|      * "In pixman_region32_init_rect: Invalid rectangle passed"
 | |
|      * when mShell has default 1x1 size.
 | |
|      */
 | |
|     GtkAllocation allocation = {0, 0, 0, 0};
 | |
|     gtk_widget_get_preferred_width(GTK_WIDGET(mShell), nullptr,
 | |
|                                    &allocation.width);
 | |
|     gtk_widget_get_preferred_height(GTK_WIDGET(mShell), nullptr,
 | |
|                                     &allocation.height);
 | |
|     gtk_widget_size_allocate(GTK_WIDGET(mShell), &allocation);
 | |
| 
 | |
|     gtk_widget_realize(GTK_WIDGET(mShell));
 | |
|     gtk_widget_reparent(GTK_WIDGET(mContainer), GTK_WIDGET(mShell));
 | |
| 
 | |
|     // Label mShell toplevel window so property_notify_event_cb callback
 | |
|     // can find its way home.
 | |
|     g_object_set_data(G_OBJECT(GetToplevelGdkWindow()), "nsWindow", this);
 | |
| 
 | |
|     if (AreBoundsSane()) {
 | |
|       GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
 | |
|       LOG("    resize to %d x %d\n", size.width, size.height);
 | |
|       gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
 | |
|     }
 | |
| 
 | |
|     if (visible) {
 | |
|       mNeedsShow = true;
 | |
|       NativeShow(true);
 | |
|     }
 | |
| 
 | |
|     gtk_widget_destroy(tmpWindow);
 | |
|   }
 | |
| 
 | |
|   if (mTransparencyBitmapForTitlebar) {
 | |
|     if (mDrawInTitlebar && mSizeMode == nsSizeMode_Normal && !mIsTiled) {
 | |
|       UpdateTitlebarTransparencyBitmap();
 | |
|     } else {
 | |
|       ClearTransparencyBitmap();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| GtkWindow* nsWindow::GetCurrentTopmostWindow() const {
 | |
|   GtkWindow* parentWindow = GTK_WINDOW(GetGtkWidget());
 | |
|   GtkWindow* topmostParentWindow = nullptr;
 | |
|   while (parentWindow) {
 | |
|     topmostParentWindow = parentWindow;
 | |
|     parentWindow = gtk_window_get_transient_for(parentWindow);
 | |
|   }
 | |
|   return topmostParentWindow;
 | |
| }
 | |
| 
 | |
| gint nsWindow::GdkCeiledScaleFactor() {
 | |
|   if (IsTopLevelWindowType()) {
 | |
|     return mCeiledScaleFactor;
 | |
|   }
 | |
|   if (nsWindow* topmost = GetTopmostWindow()) {
 | |
|     return topmost->mCeiledScaleFactor;
 | |
|   }
 | |
|   return ScreenHelperGTK::GetGTKMonitorScaleFactor();
 | |
| }
 | |
| 
 | |
| double nsWindow::FractionalScaleFactor() {
 | |
| #ifdef MOZ_WAYLAND
 | |
|   double fractional_scale = [&] {
 | |
|     if (IsTopLevelWindowType()) {
 | |
|       return mFractionalScaleFactor;
 | |
|     }
 | |
|     if (nsWindow* topmost = GetTopmostWindow()) {
 | |
|       return topmost->mFractionalScaleFactor;
 | |
|     }
 | |
|     return 0.0;
 | |
|   }();
 | |
|   if (fractional_scale != 0.0) {
 | |
|     return fractional_scale;
 | |
|   }
 | |
| #endif
 | |
|   return GdkCeiledScaleFactor();
 | |
| }
 | |
| 
 | |
| gint nsWindow::DevicePixelsToGdkCoordRoundUp(int aPixels) {
 | |
|   double scale = FractionalScaleFactor();
 | |
|   return ceil(aPixels / scale);
 | |
| }
 | |
| 
 | |
| gint nsWindow::DevicePixelsToGdkCoordRoundDown(int aPixels) {
 | |
|   double scale = FractionalScaleFactor();
 | |
|   return floor(aPixels / scale);
 | |
| }
 | |
| 
 | |
| GdkPoint nsWindow::DevicePixelsToGdkPointRoundDown(
 | |
|     const LayoutDeviceIntPoint& aPoint) {
 | |
|   double scale = FractionalScaleFactor();
 | |
|   return {int(aPoint.x / scale), int(aPoint.y / scale)};
 | |
| }
 | |
| 
 | |
| GdkRectangle nsWindow::DevicePixelsToGdkRectRoundOut(
 | |
|     const LayoutDeviceIntRect& aRect) {
 | |
|   double scale = FractionalScaleFactor();
 | |
|   int x = floor(aRect.x / scale);
 | |
|   int y = floor(aRect.y / scale);
 | |
|   int right = ceil((aRect.x + aRect.width) / scale);
 | |
|   int bottom = ceil((aRect.y + aRect.height) / scale);
 | |
|   return {x, y, right - x, bottom - y};
 | |
| }
 | |
| 
 | |
| GdkRectangle nsWindow::DevicePixelsToGdkSizeRoundUp(
 | |
|     const LayoutDeviceIntSize& aSize) {
 | |
|   double scale = FractionalScaleFactor();
 | |
|   gint width = ceil(aSize.width / scale);
 | |
|   gint height = ceil(aSize.height / scale);
 | |
|   return {0, 0, width, height};
 | |
| }
 | |
| 
 | |
| int nsWindow::GdkCoordToDevicePixels(gint aCoord) {
 | |
|   return (int)(aCoord * FractionalScaleFactor());
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntPoint nsWindow::GdkEventCoordsToDevicePixels(gdouble aX,
 | |
|                                                             gdouble aY) {
 | |
|   double scale = FractionalScaleFactor();
 | |
|   return LayoutDeviceIntPoint::Floor((float)(aX * scale), (float)(aY * scale));
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntPoint nsWindow::GdkPointToDevicePixels(const GdkPoint& aPoint) {
 | |
|   double scale = FractionalScaleFactor();
 | |
|   return LayoutDeviceIntPoint::Floor((float)(aPoint.x * scale),
 | |
|                                      (float)(aPoint.y * scale));
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntRect nsWindow::GdkRectToDevicePixels(const GdkRectangle& aRect) {
 | |
|   double scale = FractionalScaleFactor();
 | |
|   return LayoutDeviceIntRect::RoundIn(
 | |
|       (float)(aRect.x * scale), (float)(aRect.y * scale),
 | |
|       (float)(aRect.width * scale), (float)(aRect.height * scale));
 | |
| }
 | |
| 
 | |
| nsresult nsWindow::SynthesizeNativeMouseEvent(
 | |
|     LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
 | |
|     MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
 | |
|     nsIObserver* aObserver) {
 | |
|   LOG("SynthesizeNativeMouseEvent(%d, %d, %d, %d, %d)", aPoint.x.value,
 | |
|       aPoint.y.value, int(aNativeMessage), int(aButton), int(aModifierFlags));
 | |
| 
 | |
|   AutoObserverNotifier notifier(aObserver, "mouseevent");
 | |
| 
 | |
|   if (!mGdkWindow) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // When a button-press/release event is requested, create it here and put it
 | |
|   // in the event queue. This will not emit a motion event - this needs to be
 | |
|   // done explicitly *before* requesting a button-press/release. You will also
 | |
|   // need to wait for the motion event to be dispatched before requesting a
 | |
|   // button-press/release event to maintain the desired event order.
 | |
|   switch (aNativeMessage) {
 | |
|     case NativeMouseMessage::ButtonDown:
 | |
|     case NativeMouseMessage::ButtonUp: {
 | |
|       GdkEvent event;
 | |
|       memset(&event, 0, sizeof(GdkEvent));
 | |
|       event.type = aNativeMessage == NativeMouseMessage::ButtonDown
 | |
|                        ? GDK_BUTTON_PRESS
 | |
|                        : GDK_BUTTON_RELEASE;
 | |
|       switch (aButton) {
 | |
|         case MouseButton::ePrimary:
 | |
|         case MouseButton::eMiddle:
 | |
|         case MouseButton::eSecondary:
 | |
|         case MouseButton::eX1:
 | |
|         case MouseButton::eX2:
 | |
|           event.button.button = aButton + 1;
 | |
|           break;
 | |
|         default:
 | |
|           return NS_ERROR_INVALID_ARG;
 | |
|       }
 | |
|       event.button.state =
 | |
|           KeymapWrapper::ConvertWidgetModifierToGdkState(aModifierFlags);
 | |
|       event.button.window = mGdkWindow;
 | |
|       event.button.time = GDK_CURRENT_TIME;
 | |
| 
 | |
|       // Get device for event source
 | |
|       event.button.device = GdkGetPointer();
 | |
| 
 | |
|       event.button.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
 | |
|       event.button.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
 | |
| 
 | |
|       LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
 | |
|       event.button.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
 | |
|       event.button.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
 | |
| 
 | |
|       gdk_event_put(&event);
 | |
|       return NS_OK;
 | |
|     }
 | |
|     case NativeMouseMessage::Move: {
 | |
|       // We don't support specific events other than button-press/release. In
 | |
|       // all other cases we'll synthesize a motion event that will be emitted by
 | |
|       // gdk_display_warp_pointer().
 | |
|       // XXX How to activate native modifier for the other events?
 | |
| #ifdef MOZ_WAYLAND
 | |
|       // Impossible to warp the pointer on Wayland.
 | |
|       // For pointer lock, pointer-constraints and relative-pointer are used.
 | |
|       if (GdkIsWaylandDisplay()) {
 | |
|         return NS_OK;
 | |
|       }
 | |
| #endif
 | |
|       GdkScreen* screen = gdk_window_get_screen(mGdkWindow);
 | |
|       GdkPoint point = DevicePixelsToGdkPointRoundDown(aPoint);
 | |
|       gdk_device_warp(GdkGetPointer(), screen, point.x, point.y);
 | |
|       return NS_OK;
 | |
|     }
 | |
|     case NativeMouseMessage::EnterWindow:
 | |
|     case NativeMouseMessage::LeaveWindow:
 | |
|       MOZ_ASSERT_UNREACHABLE("Non supported mouse event on Linux");
 | |
|       return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
|   return NS_ERROR_UNEXPECTED;
 | |
| }
 | |
| 
 | |
| void nsWindow::CreateAndPutGdkScrollEvent(mozilla::LayoutDeviceIntPoint aPoint,
 | |
|                                           double aDeltaX, double aDeltaY) {
 | |
|   GdkEvent event;
 | |
|   memset(&event, 0, sizeof(GdkEvent));
 | |
|   event.type = GDK_SCROLL;
 | |
|   event.scroll.window = mGdkWindow;
 | |
|   event.scroll.time = GDK_CURRENT_TIME;
 | |
|   // Get device for event source
 | |
|   GdkDisplay* display = gdk_window_get_display(mGdkWindow);
 | |
|   GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
 | |
|   // See note in nsWindow::SynthesizeNativeTouchpadPan about the device we use
 | |
|   // here.
 | |
|   event.scroll.device = gdk_device_manager_get_client_pointer(device_manager);
 | |
|   event.scroll.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
 | |
|   event.scroll.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
 | |
| 
 | |
|   LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
 | |
|   event.scroll.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
 | |
|   event.scroll.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
 | |
| 
 | |
|   // The delta values are backwards on Linux compared to Windows and Cocoa,
 | |
|   // hence the negation.
 | |
|   event.scroll.direction = GDK_SCROLL_SMOOTH;
 | |
|   event.scroll.delta_x = -aDeltaX;
 | |
|   event.scroll.delta_y = -aDeltaY;
 | |
| 
 | |
|   gdk_event_put(&event);
 | |
| }
 | |
| 
 | |
| nsresult nsWindow::SynthesizeNativeMouseScrollEvent(
 | |
|     mozilla::LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage,
 | |
|     double aDeltaX, double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
 | |
|     uint32_t aAdditionalFlags, nsIObserver* aObserver) {
 | |
|   AutoObserverNotifier notifier(aObserver, "mousescrollevent");
 | |
| 
 | |
|   if (!mGdkWindow) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   CreateAndPutGdkScrollEvent(aPoint, aDeltaX, aDeltaY);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId,
 | |
|                                               TouchPointerState aPointerState,
 | |
|                                               LayoutDeviceIntPoint aPoint,
 | |
|                                               double aPointerPressure,
 | |
|                                               uint32_t aPointerOrientation,
 | |
|                                               nsIObserver* aObserver) {
 | |
|   AutoObserverNotifier notifier(aObserver, "touchpoint");
 | |
| 
 | |
|   if (!mGdkWindow) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   GdkEvent event;
 | |
|   memset(&event, 0, sizeof(GdkEvent));
 | |
| 
 | |
|   static std::map<uint32_t, GdkEventSequence*> sKnownPointers;
 | |
| 
 | |
|   auto result = sKnownPointers.find(aPointerId);
 | |
|   switch (aPointerState) {
 | |
|     case TOUCH_CONTACT:
 | |
|       if (result == sKnownPointers.end()) {
 | |
|         // GdkEventSequence isn't a thing we can instantiate, and never gets
 | |
|         // dereferenced in the gtk code. It's an opaque pointer, the only
 | |
|         // requirement is that it be distinct from other instances of
 | |
|         // GdkEventSequence*.
 | |
|         event.touch.sequence = (GdkEventSequence*)((uintptr_t)aPointerId);
 | |
|         sKnownPointers[aPointerId] = event.touch.sequence;
 | |
|         event.type = GDK_TOUCH_BEGIN;
 | |
|       } else {
 | |
|         event.touch.sequence = result->second;
 | |
|         event.type = GDK_TOUCH_UPDATE;
 | |
|       }
 | |
|       break;
 | |
|     case TOUCH_REMOVE:
 | |
|       event.type = GDK_TOUCH_END;
 | |
|       if (result == sKnownPointers.end()) {
 | |
|         NS_WARNING("Tried to synthesize touch-end for unknown pointer!");
 | |
|         return NS_ERROR_UNEXPECTED;
 | |
|       }
 | |
|       event.touch.sequence = result->second;
 | |
|       sKnownPointers.erase(result);
 | |
|       break;
 | |
|     case TOUCH_CANCEL:
 | |
|       event.type = GDK_TOUCH_CANCEL;
 | |
|       if (result == sKnownPointers.end()) {
 | |
|         NS_WARNING("Tried to synthesize touch-cancel for unknown pointer!");
 | |
|         return NS_ERROR_UNEXPECTED;
 | |
|       }
 | |
|       event.touch.sequence = result->second;
 | |
|       sKnownPointers.erase(result);
 | |
|       break;
 | |
|     case TOUCH_HOVER:
 | |
|     default:
 | |
|       return NS_ERROR_NOT_IMPLEMENTED;
 | |
|   }
 | |
| 
 | |
|   event.touch.window = mGdkWindow;
 | |
|   event.touch.time = GDK_CURRENT_TIME;
 | |
| 
 | |
|   GdkDisplay* display = gdk_window_get_display(mGdkWindow);
 | |
|   GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
 | |
|   event.touch.device = gdk_device_manager_get_client_pointer(device_manager);
 | |
| 
 | |
|   event.touch.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
 | |
|   event.touch.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
 | |
| 
 | |
|   LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
 | |
|   event.touch.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
 | |
|   event.touch.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
 | |
| 
 | |
|   gdk_event_put(&event);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsWindow::SynthesizeNativeTouchPadPinch(
 | |
|     TouchpadGesturePhase aEventPhase, float aScale, LayoutDeviceIntPoint aPoint,
 | |
|     int32_t aModifierFlags) {
 | |
|   if (!mGdkWindow) {
 | |
|     return NS_OK;
 | |
|   }
 | |
|   GdkEvent event;
 | |
|   memset(&event, 0, sizeof(GdkEvent));
 | |
| 
 | |
|   GdkEventTouchpadPinch* touchpad_event =
 | |
|       reinterpret_cast<GdkEventTouchpadPinch*>(&event);
 | |
|   touchpad_event->type = GDK_TOUCHPAD_PINCH;
 | |
| 
 | |
|   const ScreenIntPoint widgetToScreenOffset = ViewAs<ScreenPixel>(
 | |
|       WidgetToScreenOffset(),
 | |
|       PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
 | |
| 
 | |
|   ScreenPoint pointInWindow =
 | |
|       ViewAs<ScreenPixel>(
 | |
|           aPoint,
 | |
|           PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent) -
 | |
|       widgetToScreenOffset;
 | |
| 
 | |
|   gdouble dx = 0, dy = 0;
 | |
| 
 | |
|   switch (aEventPhase) {
 | |
|     case PHASE_BEGIN:
 | |
|       touchpad_event->phase = GDK_TOUCHPAD_GESTURE_PHASE_BEGIN;
 | |
|       mCurrentSynthesizedTouchpadPinch = {pointInWindow, pointInWindow};
 | |
|       break;
 | |
|     case PHASE_UPDATE:
 | |
|       dx = pointInWindow.x - mCurrentSynthesizedTouchpadPinch.mCurrentFocus.x;
 | |
|       dy = pointInWindow.y - mCurrentSynthesizedTouchpadPinch.mCurrentFocus.y;
 | |
|       mCurrentSynthesizedTouchpadPinch.mCurrentFocus = pointInWindow;
 | |
|       touchpad_event->phase = GDK_TOUCHPAD_GESTURE_PHASE_UPDATE;
 | |
|       break;
 | |
|     case PHASE_END:
 | |
|       touchpad_event->phase = GDK_TOUCHPAD_GESTURE_PHASE_END;
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       return NS_ERROR_NOT_IMPLEMENTED;
 | |
|   }
 | |
| 
 | |
|   touchpad_event->window = mGdkWindow;
 | |
|   // We only set the fields of GdkEventTouchpadPinch which are
 | |
|   // actually used in OnTouchpadPinchEvent().
 | |
|   // GdkEventTouchpadPinch has additional fields.
 | |
|   // If OnTouchpadPinchEvent() is changed to use other fields, this function
 | |
|   // will need to change to set them as well.
 | |
|   touchpad_event->time = GDK_CURRENT_TIME;
 | |
|   touchpad_event->scale = aScale;
 | |
|   touchpad_event->x_root = DevicePixelsToGdkCoordRoundDown(
 | |
|       mCurrentSynthesizedTouchpadPinch.mBeginFocus.x +
 | |
|       ScreenCoord(widgetToScreenOffset.x));
 | |
|   touchpad_event->y_root = DevicePixelsToGdkCoordRoundDown(
 | |
|       mCurrentSynthesizedTouchpadPinch.mBeginFocus.y +
 | |
|       ScreenCoord(widgetToScreenOffset.y));
 | |
| 
 | |
|   touchpad_event->x = DevicePixelsToGdkCoordRoundDown(
 | |
|       mCurrentSynthesizedTouchpadPinch.mBeginFocus.x);
 | |
|   touchpad_event->y = DevicePixelsToGdkCoordRoundDown(
 | |
|       mCurrentSynthesizedTouchpadPinch.mBeginFocus.y);
 | |
| 
 | |
|   touchpad_event->dx = dx;
 | |
|   touchpad_event->dy = dy;
 | |
| 
 | |
|   touchpad_event->state = aModifierFlags;
 | |
| 
 | |
|   gdk_event_put(&event);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsWindow::SynthesizeNativeTouchpadPan(TouchpadGesturePhase aEventPhase,
 | |
|                                                LayoutDeviceIntPoint aPoint,
 | |
|                                                double aDeltaX, double aDeltaY,
 | |
|                                                int32_t aModifierFlags,
 | |
|                                                nsIObserver* aObserver) {
 | |
|   AutoObserverNotifier notifier(aObserver, "touchpadpanevent");
 | |
| 
 | |
|   if (!mGdkWindow) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // This should/could maybe send GdkEventTouchpadSwipe events, however we don't
 | |
|   // currently consume those (either real user input or testing events). So we
 | |
|   // send gdk scroll events to be more like what we do for real user input. If
 | |
|   // we start consuming GdkEventTouchpadSwipe and get those hooked up to swipe
 | |
|   // to nav, then maybe we should test those too.
 | |
| 
 | |
|   mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase = Some(aEventPhase);
 | |
|   MOZ_ASSERT(mCurrentSynthesizedTouchpadPan.mSavedObserver == 0);
 | |
|   mCurrentSynthesizedTouchpadPan.mSavedObserver = notifier.SaveObserver();
 | |
| 
 | |
|   // Note that CreateAndPutGdkScrollEvent sets the device source for the created
 | |
|   // event as the "client pointer" (a kind of default device) which will
 | |
|   // probably be of type mouse. We would ideally want to set the device of the
 | |
|   // created event to be a touchpad, but the system might not have a touchpad.
 | |
|   // To get around this we use
 | |
|   // mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase being something to
 | |
|   // indicate that we should treat the source of the event as touchpad in
 | |
|   // OnScrollEvent.
 | |
|   CreateAndPutGdkScrollEvent(aPoint, aDeltaX, aDeltaY);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsWindow::GtkWindowDecoration nsWindow::GetSystemGtkWindowDecoration() {
 | |
|   static GtkWindowDecoration sGtkWindowDecoration = [] {
 | |
|     // Allow MOZ_GTK_TITLEBAR_DECORATION to override our heuristics
 | |
|     if (const char* decorationOverride =
 | |
|             getenv("MOZ_GTK_TITLEBAR_DECORATION")) {
 | |
|       if (strcmp(decorationOverride, "none") == 0) {
 | |
|         return GTK_DECORATION_NONE;
 | |
|       }
 | |
|       if (strcmp(decorationOverride, "client") == 0) {
 | |
|         return GTK_DECORATION_CLIENT;
 | |
|       }
 | |
|       if (strcmp(decorationOverride, "system") == 0) {
 | |
|         return GTK_DECORATION_SYSTEM;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // nsWindow::GetSystemGtkWindowDecoration can be called from various
 | |
|     // threads so we can't use gfxPlatformGtk here.
 | |
|     if (GdkIsWaylandDisplay()) {
 | |
|       return GTK_DECORATION_CLIENT;
 | |
|     }
 | |
| 
 | |
|     // GTK_CSD forces CSD mode - use also CSD because window manager
 | |
|     // decorations does not work with CSD.
 | |
|     // We check GTK_CSD as well as gtk_window_should_use_csd() does.
 | |
|     if (const char* csdOverride = getenv("GTK_CSD")) {
 | |
|       return *csdOverride == '0' ? GTK_DECORATION_NONE : GTK_DECORATION_CLIENT;
 | |
|     }
 | |
| 
 | |
|     // TODO: Consider switching this to GetDesktopEnvironmentIdentifier().
 | |
|     const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
 | |
|     if (!currentDesktop) {
 | |
|       return GTK_DECORATION_NONE;
 | |
|     }
 | |
|     if (strstr(currentDesktop, "i3")) {
 | |
|       return GTK_DECORATION_NONE;
 | |
|     }
 | |
| 
 | |
|     // Tested desktops: pop:GNOME, KDE, Enlightenment, LXDE, openbox, MATE,
 | |
|     // X-Cinnamon, Pantheon, Deepin, GNOME, LXQt, Unity.
 | |
|     return GTK_DECORATION_CLIENT;
 | |
|   }();
 | |
|   return sGtkWindowDecoration;
 | |
| }
 | |
| 
 | |
| bool nsWindow::TitlebarUseShapeMask() {
 | |
|   static int useShapeMask = []() {
 | |
|     // Don't use titlebar shape mask on Wayland
 | |
|     if (!GdkIsX11Display()) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     // We can't use shape masks on Mutter/X.org as we can't resize Firefox
 | |
|     // window there (Bug 1530252).
 | |
|     if (IsGnomeDesktopEnvironment()) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     return Preferences::GetBool("widget.titlebar-x11-use-shape-mask", false);
 | |
|   }();
 | |
|   return useShapeMask;
 | |
| }
 | |
| 
 | |
| int32_t nsWindow::RoundsWidgetCoordinatesTo() { return GdkCeiledScaleFactor(); }
 | |
| 
 | |
| void nsWindow::GetCompositorWidgetInitData(
 | |
|     mozilla::widget::CompositorWidgetInitData* aInitData) {
 | |
|   nsCString displayName;
 | |
| 
 | |
|   LOG("nsWindow::GetCompositorWidgetInitData");
 | |
|   EnsureGdkWindow();
 | |
| 
 | |
|   *aInitData = mozilla::widget::GtkCompositorWidgetInitData(
 | |
|       GetX11Window(), displayName, GetShapedState(), GdkIsX11Display(),
 | |
|       GetClientSize());
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
|   if (GdkIsX11Display()) {
 | |
|     // Make sure the window XID is propagated to X server, we can fail otherwise
 | |
|     // in GPU process (Bug 1401634).
 | |
|     Display* display = DefaultXDisplay();
 | |
|     XFlush(display);
 | |
|     displayName = nsCString(XDisplayString(display));
 | |
|   }
 | |
| #endif
 | |
| }
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
| /* XApp progress support currently works by setting a property
 | |
|  * on a window with this Atom name.  A supporting window manager
 | |
|  * will notice this and pass it along to whatever handling has
 | |
|  * been implemented on that end (e.g. passing it on to a taskbar
 | |
|  * widget.)  There is no issue if WM support is lacking, this is
 | |
|  * simply ignored in that case.
 | |
|  *
 | |
|  * See https://github.com/linuxmint/xapps/blob/master/libxapp/xapp-gtk-window.c
 | |
|  * for further details.
 | |
|  */
 | |
| 
 | |
| #  define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS"
 | |
| 
 | |
| static void set_window_hint_cardinal(Window xid, const gchar* atom_name,
 | |
|                                      gulong cardinal) {
 | |
|   GdkDisplay* display;
 | |
| 
 | |
|   display = gdk_display_get_default();
 | |
| 
 | |
|   if (cardinal > 0) {
 | |
|     XChangeProperty(GDK_DISPLAY_XDISPLAY(display), xid,
 | |
|                     gdk_x11_get_xatom_by_name_for_display(display, atom_name),
 | |
|                     XA_CARDINAL, 32, PropModeReplace, (guchar*)&cardinal, 1);
 | |
|   } else {
 | |
|     XDeleteProperty(GDK_DISPLAY_XDISPLAY(display), xid,
 | |
|                     gdk_x11_get_xatom_by_name_for_display(display, atom_name));
 | |
|   }
 | |
| }
 | |
| #endif  // MOZ_X11
 | |
| 
 | |
| void nsWindow::SetProgress(unsigned long progressPercent) {
 | |
| #ifdef MOZ_X11
 | |
| 
 | |
|   if (!GdkIsX11Display()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!mShell) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   progressPercent = MIN(progressPercent, 100);
 | |
| 
 | |
|   set_window_hint_cardinal(GDK_WINDOW_XID(GetToplevelGdkWindow()),
 | |
|                            PROGRESS_HINT, progressPercent);
 | |
| #endif  // MOZ_X11
 | |
| }
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
| void nsWindow::SetCompositorHint(WindowComposeRequest aState) {
 | |
|   if (!GdkIsX11Display()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   gulong value = aState;
 | |
|   GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
 | |
|   gdk_property_change(GetToplevelGdkWindow(),
 | |
|                       gdk_atom_intern("_NET_WM_BYPASS_COMPOSITOR", FALSE),
 | |
|                       cardinal_atom,
 | |
|                       32,  // format
 | |
|                       GDK_PROP_MODE_REPLACE, (guchar*)&value, 1);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| nsresult nsWindow::SetSystemFont(const nsCString& aFontName) {
 | |
|   GtkSettings* settings = gtk_settings_get_default();
 | |
|   g_object_set(settings, "gtk-font-name", aFontName.get(), nullptr);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsWindow::GetSystemFont(nsCString& aFontName) {
 | |
|   GtkSettings* settings = gtk_settings_get_default();
 | |
|   gchar* fontName = nullptr;
 | |
|   g_object_get(settings, "gtk-font-name", &fontName, nullptr);
 | |
|   if (fontName) {
 | |
|     aFontName.Assign(fontName);
 | |
|     g_free(fontName);
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
 | |
|   nsCOMPtr<nsIWidget> window = new nsWindow();
 | |
|   return window.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
 | |
|   nsCOMPtr<nsIWidget> window = new nsWindow();
 | |
|   return window.forget();
 | |
| }
 | |
| 
 | |
| #ifdef MOZ_WAYLAND
 | |
| static void 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) {
 | |
|   RefPtr<nsWindow> window(reinterpret_cast<nsWindow*>(data));
 | |
| 
 | |
|   WidgetMouseEvent event(true, eMouseMove, window, WidgetMouseEvent::eReal);
 | |
| 
 | |
|   double scale = window->FractionalScaleFactor();
 | |
|   event.mRefPoint = window->GetNativePointerLockCenter();
 | |
|   event.mRefPoint.x += int(wl_fixed_to_double(dx_w) * scale);
 | |
|   event.mRefPoint.y += int(wl_fixed_to_double(dy_w) * scale);
 | |
| 
 | |
|   event.AssignEventTime(window->GetWidgetEventTime(time_lo));
 | |
|   window->DispatchInputEvent(&event);
 | |
| }
 | |
| 
 | |
| static const struct zwp_relative_pointer_v1_listener relative_pointer_listener =
 | |
|     {
 | |
|         relative_pointer_handle_relative_motion,
 | |
| };
 | |
| 
 | |
| void nsWindow::SetNativePointerLockCenter(
 | |
|     const LayoutDeviceIntPoint& aLockCenter) {
 | |
|   mNativePointerLockCenter = aLockCenter;
 | |
| }
 | |
| 
 | |
| void nsWindow::LockNativePointer() {
 | |
|   if (!GdkIsWaylandDisplay()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   auto* waylandDisplay = WaylandDisplayGet();
 | |
| 
 | |
|   auto* pointerConstraints = waylandDisplay->GetPointerConstraints();
 | |
|   if (!pointerConstraints) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   auto* relativePointerMgr = waylandDisplay->GetRelativePointerManager();
 | |
|   if (!relativePointerMgr) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   GdkDisplay* display = gdk_display_get_default();
 | |
| 
 | |
|   GdkDeviceManager* manager = gdk_display_get_device_manager(display);
 | |
|   MOZ_ASSERT(manager);
 | |
| 
 | |
|   GdkDevice* device = gdk_device_manager_get_client_pointer(manager);
 | |
|   if (!device) {
 | |
|     NS_WARNING("Could not find Wayland pointer to lock");
 | |
|     return;
 | |
|   }
 | |
|   wl_pointer* pointer = gdk_wayland_device_get_wl_pointer(device);
 | |
|   MOZ_ASSERT(pointer);
 | |
| 
 | |
|   wl_surface* surface =
 | |
|       gdk_wayland_window_get_wl_surface(gtk_widget_get_window(GetGtkWidget()));
 | |
|   if (!surface) {
 | |
|     /* Can be null when the window is hidden.
 | |
|      * Though it's unlikely that a lock request comes in that case, be
 | |
|      * defensive. */
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mLockedPointer || mRelativePointer) {
 | |
|     UnlockNativePointer();
 | |
|   }
 | |
| 
 | |
|   mLockedPointer = zwp_pointer_constraints_v1_lock_pointer(
 | |
|       pointerConstraints, surface, pointer, nullptr,
 | |
|       ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
 | |
|   if (!mLockedPointer) {
 | |
|     NS_WARNING("Could not lock Wayland pointer");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mRelativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer(
 | |
|       relativePointerMgr, pointer);
 | |
|   if (!mRelativePointer) {
 | |
|     NS_WARNING("Could not create relative Wayland pointer");
 | |
|     zwp_locked_pointer_v1_destroy(mLockedPointer);
 | |
|     mLockedPointer = nullptr;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   zwp_relative_pointer_v1_add_listener(mRelativePointer,
 | |
|                                        &relative_pointer_listener, this);
 | |
| }
 | |
| 
 | |
| void nsWindow::UnlockNativePointer() {
 | |
|   if (!GdkIsWaylandDisplay()) {
 | |
|     return;
 | |
|   }
 | |
|   if (mRelativePointer) {
 | |
|     zwp_relative_pointer_v1_destroy(mRelativePointer);
 | |
|     mRelativePointer = nullptr;
 | |
|   }
 | |
|   if (mLockedPointer) {
 | |
|     zwp_locked_pointer_v1_destroy(mLockedPointer);
 | |
|     mLockedPointer = nullptr;
 | |
|   }
 | |
| }
 | |
| #endif
 | |
| 
 | |
| bool nsWindow::GetTopLevelWindowActiveState(nsIFrame* aFrame) {
 | |
|   // Used by window frame and button box rendering. We can end up in here in
 | |
|   // the content process when rendering one of these moz styles freely in a
 | |
|   // page. Fail in this case, there is no applicable window focus state.
 | |
|   if (!XRE_IsParentProcess()) {
 | |
|     return false;
 | |
|   }
 | |
|   // All headless windows are considered active so they are painted.
 | |
|   if (gfxPlatform::IsHeadless()) {
 | |
|     return true;
 | |
|   }
 | |
|   // Get the widget. nsIFrame's GetNearestWidget walks up the view chain
 | |
|   // until it finds a real window.
 | |
|   nsWindow* window = static_cast<nsWindow*>(aFrame->GetNearestWidget());
 | |
|   if (!window) {
 | |
|     return false;
 | |
|   }
 | |
|   return !window->mTitlebarBackdropState;
 | |
| }
 | |
| 
 | |
| static nsIFrame* FindTitlebarFrame(nsIFrame* aFrame) {
 | |
|   for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
 | |
|     StyleAppearance appearance =
 | |
|         childFrame->StyleDisplay()->EffectiveAppearance();
 | |
|     if (appearance == StyleAppearance::MozWindowTitlebar ||
 | |
|         appearance == StyleAppearance::MozWindowTitlebarMaximized) {
 | |
|       return childFrame;
 | |
|     }
 | |
| 
 | |
|     if (nsIFrame* foundFrame = FindTitlebarFrame(childFrame)) {
 | |
|       return foundFrame;
 | |
|     }
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| nsIFrame* nsWindow::GetFrame() const {
 | |
|   nsView* view = nsView::GetViewFor(this);
 | |
|   if (!view) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   return view->GetFrame();
 | |
| }
 | |
| 
 | |
| void nsWindow::UpdateMozWindowActive() {
 | |
|   // Update activation state for the :-moz-window-inactive pseudoclass.
 | |
|   // Normally, this follows focus; we override it here to follow
 | |
|   // GDK_WINDOW_STATE_FOCUSED.
 | |
|   if (mozilla::dom::Document* document = GetDocument()) {
 | |
|     if (nsPIDOMWindowOuter* window = document->GetWindow()) {
 | |
|       if (RefPtr<mozilla::dom::BrowsingContext> bc =
 | |
|               window->GetBrowsingContext()) {
 | |
|         bc->SetIsActiveBrowserWindow(!mTitlebarBackdropState);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::ForceTitlebarRedraw() {
 | |
|   MOZ_ASSERT(mDrawInTitlebar, "We should not redraw invisible titlebar.");
 | |
| 
 | |
|   if (!mWidgetListener || !mWidgetListener->GetPresShell()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsIFrame* frame = GetFrame();
 | |
|   if (!frame) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   frame = FindTitlebarFrame(frame);
 | |
|   if (frame) {
 | |
|     nsIContent* content = frame->GetContent();
 | |
|     if (content) {
 | |
|       nsLayoutUtils::PostRestyleEvent(content->AsElement(), RestyleHint{0},
 | |
|                                       nsChangeHint_RepaintFrame);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::LockAspectRatio(bool aShouldLock) {
 | |
|   if (!gUseAspectRatio) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aShouldLock) {
 | |
|     int decWidth = 0, decHeight = 0;
 | |
|     AddCSDDecorationSize(&decWidth, &decHeight);
 | |
| 
 | |
|     float width =
 | |
|         DevicePixelsToGdkCoordRoundDown(mLastSizeRequest.width) + decWidth;
 | |
|     float height =
 | |
|         DevicePixelsToGdkCoordRoundDown(mLastSizeRequest.height) + decHeight;
 | |
| 
 | |
|     mAspectRatio = width / height;
 | |
|     LOG("nsWindow::LockAspectRatio() width %f height %f aspect %f", width,
 | |
|         height, mAspectRatio);
 | |
|   } else {
 | |
|     mAspectRatio = 0.0;
 | |
|     LOG("nsWindow::LockAspectRatio() removed aspect ratio");
 | |
|   }
 | |
| 
 | |
|   ApplySizeConstraints();
 | |
| }
 | |
| 
 | |
| nsWindow* nsWindow::GetFocusedWindow() { return gFocusWindow; }
 | |
| 
 | |
| #ifdef MOZ_WAYLAND
 | |
| bool nsWindow::SetEGLNativeWindowSize(
 | |
|     const LayoutDeviceIntSize& aEGLWindowSize) {
 | |
|   if (!GdkIsWaylandDisplay()) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // SetEGLNativeWindowSize() is called from renderer/compositor thread,
 | |
|   // make sure nsWindow is not destroyed.
 | |
|   bool paint = false;
 | |
| 
 | |
|   // See NS_NATIVE_EGL_WINDOW why we can't block here.
 | |
|   if (mDestroyMutex.TryLock()) {
 | |
|     if (!mIsDestroyed) {
 | |
|       gint scale = GdkCeiledScaleFactor();
 | |
|       LOG("nsWindow::SetEGLNativeWindowSize() %d x %d scale %d (unscaled %d x "
 | |
|           "%d)",
 | |
|           aEGLWindowSize.width, aEGLWindowSize.height, scale,
 | |
|           aEGLWindowSize.width / scale, aEGLWindowSize.height / scale);
 | |
|       paint = moz_container_wayland_egl_window_set_size(
 | |
|           mContainer, aEGLWindowSize.ToUnknownSize(), scale);
 | |
|     }
 | |
|     mDestroyMutex.Unlock();
 | |
|   }
 | |
|   return paint;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| nsWindow* nsWindow::GetWindow(GdkWindow* window) {
 | |
|   return get_window_for_gdk_window(window);
 | |
| }
 | |
| 
 | |
| void nsWindow::ClearRenderingQueue() {
 | |
|   LOG("nsWindow::ClearRenderingQueue()");
 | |
| 
 | |
|   if (mWidgetListener) {
 | |
|     mWidgetListener->RequestWindowClose(this);
 | |
|   }
 | |
|   DestroyLayerManager();
 | |
| }
 | |
| 
 | |
| // Apply workaround for Mutter compositor bug (mzbz#1777269).
 | |
| //
 | |
| // When we open a popup window (tooltip for instance) attached to
 | |
| // GDK_WINDOW_TYPE_HINT_UTILITY parent popup, Mutter compositor sends bogus
 | |
| // leave/enter events to the GDK_WINDOW_TYPE_HINT_UTILITY popup.
 | |
| // That leads to immediate tooltip close. As a workaround ignore these
 | |
| // bogus events.
 | |
| //
 | |
| // We need to check two affected window types:
 | |
| //
 | |
| // - toplevel window with at least two child popups where the first one is
 | |
| //   GDK_WINDOW_TYPE_HINT_UTILITY.
 | |
| // - GDK_WINDOW_TYPE_HINT_UTILITY popup with a child popup
 | |
| //
 | |
| // We need to mask two bogus leave/enter sequences:
 | |
| //  1) Leave (popup) -> Enter (toplevel)
 | |
| //  2) Leave (toplevel) -> Enter (popup)
 | |
| //
 | |
| // TODO: persistent (non-tracked) popups with tooltip/child popups?
 | |
| //
 | |
| bool nsWindow::ApplyEnterLeaveMutterWorkaround() {
 | |
|   // Leave (toplevel) case
 | |
|   if (mWindowType == WindowType::TopLevel && mWaylandPopupNext &&
 | |
|       mWaylandPopupNext->mWaylandPopupNext &&
 | |
|       gtk_window_get_type_hint(GTK_WINDOW(mWaylandPopupNext->GetGtkWidget())) ==
 | |
|           GDK_WINDOW_TYPE_HINT_UTILITY) {
 | |
|     LOG("nsWindow::ApplyEnterLeaveMutterWorkaround(): leave toplevel");
 | |
|     return true;
 | |
|   }
 | |
|   // Leave (popup) case
 | |
|   if (IsWaylandPopup() && mWaylandPopupNext &&
 | |
|       gtk_window_get_type_hint(GTK_WINDOW(mShell)) ==
 | |
|           GDK_WINDOW_TYPE_HINT_UTILITY) {
 | |
|     LOG("nsWindow::ApplyEnterLeaveMutterWorkaround(): leave popup");
 | |
|     return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void nsWindow::NotifyOcclusionState(OcclusionState aState) {
 | |
|   if (!IsTopLevelWindowType()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   bool isFullyOccluded = aState == OcclusionState::OCCLUDED;
 | |
|   if (mIsFullyOccluded == isFullyOccluded) {
 | |
|     return;
 | |
|   }
 | |
|   mIsFullyOccluded = isFullyOccluded;
 | |
| 
 | |
|   LOG("nsWindow::NotifyOcclusionState() mIsFullyOccluded %d", mIsFullyOccluded);
 | |
|   if (mWidgetListener) {
 | |
|     mWidgetListener->OcclusionStateChanged(mIsFullyOccluded);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsWindow::SetDragSource(GdkDragContext* aSourceDragContext) {
 | |
|   mSourceDragContext = aSourceDragContext;
 | |
|   if (IsPopup() &&
 | |
|       (widget::GdkIsWaylandDisplay() || widget::IsXWaylandProtocol())) {
 | |
|     if (auto* menuPopupFrame = GetMenuPopupFrame(GetFrame())) {
 | |
|       menuPopupFrame->SetIsDragSource(!!aSourceDragContext);
 | |
|     }
 | |
|   }
 | |
| }
 |