diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /widget/gtk/nsWindow.cpp | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/gtk/nsWindow.cpp')
-rw-r--r-- | widget/gtk/nsWindow.cpp | 8420 |
1 files changed, 8420 insertions, 0 deletions
diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp new file mode 100644 index 0000000000..f435464f7a --- /dev/null +++ b/widget/gtk/nsWindow.cpp @@ -0,0 +1,8420 @@ +/* -*- 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 "mozilla/ArrayUtils.h" +#include "mozilla/EventForwards.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/PresShell.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_ui.h" +#include "mozilla/TextEvents.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/WidgetUtils.h" +#include "mozilla/X11Util.h" +#include "mozilla/XREAppData.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/WheelEventBinding.h" +#include "InputData.h" +#include "nsAppRunner.h" +#include <algorithm> + +#include "GeckoProfiler.h" + +#include "prlink.h" +#include "nsGTKToolkit.h" +#include "nsIRollupListener.h" +#include "nsINode.h" + +#include "nsWidgetsCID.h" +#include "nsDragService.h" +#include "nsIWidgetListener.h" +#include "nsIScreenManager.h" +#include "SystemTimeConverter.h" +#include "nsViewManager.h" +#include "nsMenuPopupFrame.h" +#include "nsXPLookAndFeel.h" + +#include "nsGtkKeyUtils.h" +#include "nsGtkCursors.h" +#include "ScreenHelperGTK.h" + +#include <gtk/gtk.h> +#include <gtk/gtkx.h> + +#ifdef MOZ_WAYLAND +# include <gdk/gdkwayland.h> +#endif /* MOZ_WAYLAND */ + +#ifdef MOZ_X11 +# include <gdk/gdkx.h> +# include <X11/Xatom.h> +# include <X11/extensions/XShm.h> +# include <X11/extensions/shape.h> +# include <gdk/gdkkeysyms-compat.h> +#endif /* MOZ_X11 */ + +#include <gdk/gdkkeysyms.h> + +#if defined(MOZ_WAYLAND) +# include <gdk/gdkwayland.h> +# include "nsView.h" +#endif + +#include "nsGkAtoms.h" + +#include "mozilla/Assertions.h" +#include "mozilla/Likely.h" +#include "mozilla/Preferences.h" +#include "nsGfxCIID.h" +#include "nsGtkUtils.h" +#include "nsLayoutUtils.h" +#include "mozilla/layers/LayersTypes.h" +#include "nsIUserIdleServiceInternal.h" +#include "GLContext.h" +#include "gfx2DGlue.h" + +#ifdef ACCESSIBILITY +# include "mozilla/a11y/Accessible.h" +# include "mozilla/a11y/Platform.h" +# include "nsAccessibilityService.h" + +using namespace mozilla; +using namespace mozilla::widget; +#endif + +/* For SetIcon */ +#include "nsAppDirectoryServiceDefs.h" +#include "nsString.h" +#include "nsIFile.h" + +/* SetCursor(imgIContainer*) */ +#include <gdk/gdk.h> +#include <wchar.h> +#include "imgIContainer.h" +#include "nsGfxCIID.h" +#include "nsImageToPixbuf.h" +#include "nsIInterfaceRequestorUtils.h" +#include "ClientLayerManager.h" +#include "nsIGSettingsService.h" + +#include "gfxPlatformGtk.h" +#include "gfxContext.h" +#include "gfxImageSurface.h" +#include "gfxUtils.h" +#include "Layers.h" +#include "GLContextProvider.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/HelpersCairo.h" +#include "mozilla/gfx/GPUProcessManager.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" + +#ifdef MOZ_X11 +# include "mozilla/gfx/gfxVars.h" +# include "GLContextGLX.h" // for GLContextGLX::FindVisual() +# include "GLContextEGL.h" // for GLContextEGL::FindVisual() +# include "GtkCompositorWidget.h" +# include "gfxXlibSurface.h" +# include "WindowSurfaceX11Image.h" +# include "WindowSurfaceX11SHM.h" +# include "WindowSurfaceXRender.h" +#endif // MOZ_X11 +#ifdef MOZ_WAYLAND +# include "nsIClipboard.h" +#endif + +#include "nsShmImage.h" +#include "gtkdrawing.h" + +#include "NativeKeyBindings.h" + +#include <dlfcn.h> +#include "nsPresContext.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::widget; +using namespace mozilla::layers; +using mozilla::gl::GLContextEGL; +using mozilla::gl::GLContextGLX; + +// 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; + +GdkEventMask GDK_TOUCHPAD_GESTURE_MASK = static_cast<GdkEventMask>(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; + +#if !GTK_CHECK_VERSION(3, 22, 0) +typedef enum { + GDK_ANCHOR_FLIP_X = 1 << 0, + GDK_ANCHOR_FLIP_Y = 1 << 1, + GDK_ANCHOR_SLIDE_X = 1 << 2, + GDK_ANCHOR_SLIDE_Y = 1 << 3, + GDK_ANCHOR_RESIZE_X = 1 << 4, + GDK_ANCHOR_RESIZE_Y = 1 << 5, + GDK_ANCHOR_FLIP = GDK_ANCHOR_FLIP_X | GDK_ANCHOR_FLIP_Y, + GDK_ANCHOR_SLIDE = GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_SLIDE_Y, + GDK_ANCHOR_RESIZE = GDK_ANCHOR_RESIZE_X | GDK_ANCHOR_RESIZE_Y +} GdkAnchorHints; +#endif + +/* 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); + +static int is_parent_ungrab_enter(GdkEventCrossing* aEvent); +static int is_parent_grab_leave(GdkEventCrossing* aEvent); + +/* callbacks from widgets */ +static gboolean expose_event_cb(GtkWidget* widget, cairo_t* rect); +static gboolean configure_event_cb(GtkWidget* widget, GdkEventConfigure* event); +static void container_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); +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 void hierarchy_changed_cb(GtkWidget* widget, + GtkWidget* previous_toplevel); +static gboolean window_state_event_cb(GtkWidget* widget, + GdkEventWindowState* event); +static void settings_changed_cb(GtkSettings* settings, GParamSpec* pspec, + nsWindow* data); +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 guint32 sRetryGrabTime; + +static SystemTimeConverter<guint32>& TimeConverter() { + static SystemTimeConverter<guint32> sTimeConverterSingleton; + return sTimeConverterSingleton; +} + +nsWindow::CSDSupportLevel nsWindow::sCSDSupportLevel = CSD_SUPPORT_UNKNOWN; +bool nsWindow::sTransparentMainWindow = false; +static bool sIgnoreChangedSettings = false; + +void nsWindow::WithSettingsChangesIgnored(const std::function<void()>& aFn) { + AutoRestore ar(sIgnoreChangedSettings); + sIgnoreChangedSettings = true; + aFn(); +} + +namespace mozilla { + +class CurrentX11TimeGetter { + public: + explicit CurrentX11TimeGetter(GdkWindow* aWindow) + : mWindow(aWindow), mAsyncUpdateStart() {} + + 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; +}; + +} // namespace mozilla + +static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID); + +// 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 gRaiseWindows = true; +static bool gUseWaylandVsync = true; +static bool gUseWaylandUseOpaqueRegion = true; +static bool gUseAspectRatio = true; +static GList* gVisibleWaylandPopupWindows = nullptr; +static uint32_t gLastTouchID = 0; + +#define NS_WINDOW_TITLE_MAX_LENGTH 4095 + +// If after selecting profile window, the startup fail, please refer to +// http://bugzilla.gnome.org/show_bug.cgi?id=88940 + +// needed for imgIContainer cursors +// GdkDisplay* was added in 2.2 +typedef struct _GdkDisplay GdkDisplay; + +#define kWindowPositionSlop 20 + +// cursor cache +static GdkCursor* gCursorCache[eCursorCount]; + +static GtkWidget* gInvisibleContainer = nullptr; + +// 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; +} + +void GetWindowOrigin(GdkWindow* aWindow, int* aX, int* aY) { + if (aWindow) { + gdk_window_get_origin(aWindow, aX, aY); + } + + // 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. +#if 0 + *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; + } +#endif +} + +nsWindow::nsWindow() { + mIsTopLevel = false; + mIsDestroyed = false; + mListenForResizes = false; + mNeedsDispatchResized = false; + mIsShown = false; + mNeedsShow = false; + mEnabled = true; + mCreated = false; + mHandleTouchEvent = false; + mIsDragPopup = false; + mIsX11Display = gfxPlatformGtk::GetPlatform()->IsX11Display(); + + mContainer = nullptr; + mGdkWindow = nullptr; + mShell = nullptr; + mCompositorWidgetDelegate = nullptr; + mHasMappedToplevel = false; + mRetryPointerGrab = false; + mWindowType = eWindowType_child; + mSizeState = nsSizeMode_Normal; + mBoundsAreValid = true; + mAspectRatio = 0.0f; + mAspectRatioSaved = 0.0f; + mLastSizeMode = nsSizeMode_Normal; + mSizeConstraints.mMaxSize = GetSafeWindowSize(mSizeConstraints.mMaxSize); + +#ifdef MOZ_X11 + mOldFocusWindow = 0; + + mXDisplay = nullptr; + mXWindow = X11None; + mXVisual = nullptr; + mXDepth = 0; +#endif /* MOZ_X11 */ + +#ifdef MOZ_WAYLAND + mNeedsCompositorResume = false; + mCompositorInitiallyPaused = false; +#endif + mWaitingForMoveToRectCB = false; + mPendingSizeRect = LayoutDeviceIntRect(0, 0, 0, 0); + + 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 (!mIsX11Display) { + nsCOMPtr<nsIClipboard> clipboard = + do_GetService("@mozilla.org/widget/clipboard;1"); + NS_ASSERTION(clipboard, "Failed to init clipboard!"); + } +#endif + } + + mLastMotionPressure = 0; + +#ifdef ACCESSIBILITY + mRootAccessible = nullptr; +#endif + + mIsTransparent = false; + mTransparencyBitmap = nullptr; + mTransparencyBitmapForTitlebar = false; + + mTransparencyBitmapWidth = 0; + mTransparencyBitmapHeight = 0; + + mLastScrollEventTime = GDK_CURRENT_TIME; + + mPendingConfigures = 0; + mCSDSupportLevel = CSD_SUPPORT_NONE; + mDrawToContainer = false; + mDrawInTitlebar = false; + mTitlebarBackdropState = false; + + mHasAlphaVisual = false; + mIsPIPWindow = false; + mAlwaysOnTop = false; + + mWindowScaleFactorChanged = true; + mWindowScaleFactor = 1; + + mCompositedScreen = gdk_screen_is_composited(gdk_screen_get_default()); +} + +nsWindow::~nsWindow() { + LOG(("nsWindow::~nsWindow() [%p]\n", (void*)this)); + + delete[] mTransparencyBitmap; + mTransparencyBitmap = nullptr; + + Destroy(); +} + +/* static */ +void nsWindow::ReleaseGlobals() { + for (auto& cursor : gCursorCache) { + if (cursor) { + g_object_unref(cursor); + cursor = nullptr; + } + } +} + +void nsWindow::CommonCreate(nsIWidget* aParent, bool aListenForResizes) { + mParent = aParent; + mListenForResizes = aListenForResizes; + mCreated = true; +} + +void nsWindow::DispatchActivateEvent(void) { + NS_ASSERTION(mContainer || mIsDestroyed, + "DispatchActivateEvent only intended for container windows"); + +#ifdef ACCESSIBILITY + DispatchActivateEventAccessible(); +#endif // ACCESSIBILITY + + if (mWidgetListener) mWidgetListener->WindowActivated(); +} + +void nsWindow::DispatchDeactivateEvent(void) { + if (mWidgetListener) mWidgetListener->WindowDeactivated(); + +#ifdef ACCESSIBILITY + DispatchDeactivateEventAccessible(); +#endif // ACCESSIBILITY +} + +void nsWindow::DispatchResized() { + mNeedsDispatchResized = false; + if (mWidgetListener) { + mWidgetListener->WindowResized(this, mBounds.width, mBounds.height); + } + if (mAttachedWidgetListener) { + mAttachedWidgetListener->WindowResized(this, mBounds.width, mBounds.height); + } +} + +void nsWindow::MaybeDispatchResized() { + if (mNeedsDispatchResized && !mIsDestroyed) { + 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() { + return mBounds.width > 0 && mBounds.height > 0; +} + +static GtkWidget* EnsureInvisibleContainer() { + if (!gInvisibleContainer) { + // GtkWidgets need to be anchored to a GtkWindow to be realized (to + // have a window). Using GTK_WINDOW_POPUP rather than + // GTK_WINDOW_TOPLEVEL in the hope that POPUP results in less + // initialization and window manager interaction. + GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP); + gInvisibleContainer = moz_container_new(); + gtk_container_add(GTK_CONTAINER(window), gInvisibleContainer); + gtk_widget_realize(gInvisibleContainer); + } + return gInvisibleContainer; +} + +static void CheckDestroyInvisibleContainer() { + MOZ_ASSERT(gInvisibleContainer, "oh, no"); + + if (!gdk_window_peek_children(gtk_widget_get_window(gInvisibleContainer))) { + // No children, so not in use. + // Make sure to destroy the GtkWindow also. + gtk_widget_destroy(gtk_widget_get_parent(gInvisibleContainer)); + gInvisibleContainer = nullptr; + } +} + +// Change the containing GtkWidget on a sub-hierarchy of GdkWindows belonging +// to aOldWidget and rooted at aWindow, and reparent any child GtkWidgets of +// the GdkWindow hierarchy to aNewWidget. +static void SetWidgetForHierarchy(GdkWindow* aWindow, GtkWidget* aOldWidget, + GtkWidget* aNewWidget) { + gpointer data; + gdk_window_get_user_data(aWindow, &data); + + if (data != aOldWidget) { + if (!GTK_IS_WIDGET(data)) return; + + auto* widget = static_cast<GtkWidget*>(data); + if (gtk_widget_get_parent(widget) != aOldWidget) return; + + // This window belongs to a child widget, which will no longer be a + // child of aOldWidget. + gtk_widget_reparent(widget, aNewWidget); + + return; + } + + GList* children = gdk_window_get_children(aWindow); + for (GList* list = children; list; list = list->next) { + SetWidgetForHierarchy(GDK_WINDOW(list->data), aOldWidget, aNewWidget); + } + g_list_free(children); + + gdk_window_set_user_data(aWindow, aNewWidget); +} + +// Walk the list of child windows and call destroy on them. +void 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(); + } else { + // This child is not an nsWindow. + // Destroy the child GtkWidget. + gpointer data; + gdk_window_get_user_data(child, &data); + if (GTK_IS_WIDGET(data)) { + gtk_widget_destroy(static_cast<GtkWidget*>(data)); + } + } + } +} + +void nsWindow::Destroy() { + if (mIsDestroyed || !mCreated) return; + + LOG(("nsWindow::Destroy [%p]\n", (void*)this)); + mIsDestroyed = true; + mCreated = false; + + /** Need to clean our LayerManager up while still alive */ + if (mLayerManager) { + mLayerManager->Destroy(); + } + mLayerManager = nullptr; + +#ifdef MOZ_WAYLAND + // Shut down our local vsync source + if (mWaylandVsyncSource) { + mWaylandVsyncSource->Shutdown(); + mWaylandVsyncSource = nullptr; + } +#endif + + // It is safe to call DestroyeCompositor several times (here and + // in the parent class) since it will take effect only once. + // The reason we call it here is because on gtk platforms we need + // to destroy the compositor before we destroy the gdk window (which + // destroys the the gl context attached to it). + DestroyCompositor(); + +#ifdef MOZ_X11 + // Ensure any resources assigned to the window get cleaned up first + // to avoid double-freeing. + mSurfaceProvider.CleanupResources(); +#endif + + ClearCachedResources(); + + 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(0, false, nullptr, nullptr); + } + } + + // 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) { + LOGFOCUS(("automatically losing focus...\n")); + gFocusWindow = nullptr; + } + + GtkWidget* owningWidget = GetMozContainerWidget(); + if (mShell) { + gtk_widget_destroy(mShell); + mShell = nullptr; + mContainer = nullptr; + MOZ_ASSERT(!mGdkWindow, + "mGdkWindow should be NULL when mContainer is destroyed"); + } else if (mContainer) { + gtk_widget_destroy(GTK_WIDGET(mContainer)); + mContainer = nullptr; + MOZ_ASSERT(!mGdkWindow, + "mGdkWindow should be NULL when mContainer is destroyed"); + } else if (mGdkWindow) { + // Destroy child windows to ensure that their mThebesSurfaces are + // released and to remove references from GdkWindows back to their + // container widget. (OnContainerUnrealize() does this when the + // MozContainer widget is destroyed.) + DestroyChildWindows(); + + gdk_window_set_user_data(mGdkWindow, nullptr); + g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", nullptr); + gdk_window_destroy(mGdkWindow); + mGdkWindow = nullptr; + } + + if (gInvisibleContainer && owningWidget == gInvisibleContainer) { + CheckDestroyInvisibleContainer(); + } + +#ifdef ACCESSIBILITY + if (mRootAccessible) { + mRootAccessible = nullptr; + } +#endif + + // Save until last because OnDestroy() may cause us to be deleted. + OnDestroy(); +} + +nsIWidget* nsWindow::GetParent(void) { return mParent; } + +float nsWindow::GetDPI() { + float dpi = 96.0f; + nsCOMPtr<nsIScreen> screen = GetWidgetScreen(); + if (screen) { + screen->GetDpi(&dpi); + } + return dpi; +} + +double nsWindow::GetDefaultScaleInternal() { + return GdkScaleFactor() * gfxPlatformGtk::GetFontScaleFactor(); +} + +DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScale() { +#ifdef MOZ_WAYLAND + if (!mIsX11Display) { + return DesktopToLayoutDeviceScale(GdkScaleFactor()); + } +#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 (!mIsX11Display) { + 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(); +} + +void nsWindow::SetParent(nsIWidget* aNewParent) { + if (!mGdkWindow) { + MOZ_ASSERT_UNREACHABLE("The native window has already been destroyed"); + return; + } + + if (mContainer) { + // FIXME bug 1469183 + NS_ERROR("nsWindow should not have a container here"); + return; + } + + nsCOMPtr<nsIWidget> kungFuDeathGrip = this; + if (mParent) { + mParent->RemoveChild(this); + } + mParent = aNewParent; + + GtkWidget* oldContainer = GetMozContainerWidget(); + if (!oldContainer) { + // The GdkWindows have been destroyed so there is nothing else to + // reparent. + MOZ_ASSERT(gdk_window_is_destroyed(mGdkWindow), + "live GdkWindow with no widget"); + return; + } + + nsWindow* newParent = static_cast<nsWindow*>(aNewParent); + GdkWindow* newParentWindow = nullptr; + GtkWidget* newContainer = nullptr; + if (aNewParent) { + aNewParent->AddChild(this); + newParentWindow = newParent->mGdkWindow; + newContainer = newParent->GetMozContainerWidget(); + } else { + // aNewParent is nullptr, but reparent to a hidden window to avoid + // destroying the GdkWindow and its descendants. + // An invisible container widget is needed to hold descendant + // GtkWidgets. + newContainer = EnsureInvisibleContainer(); + newParentWindow = gtk_widget_get_window(newContainer); + } + + if (!newContainer) { + // The new parent GdkWindow has been destroyed. + MOZ_ASSERT(!newParentWindow || gdk_window_is_destroyed(newParentWindow), + "live GdkWindow with no widget"); + Destroy(); + } else { + if (newContainer != oldContainer) { + MOZ_ASSERT(!gdk_window_is_destroyed(newParentWindow), + "destroyed GdkWindow with widget"); + SetWidgetForHierarchy(mGdkWindow, oldContainer, newContainer); + + if (oldContainer == gInvisibleContainer) { + CheckDestroyInvisibleContainer(); + } + } + + gdk_window_reparent(mGdkWindow, newParentWindow, + DevicePixelsToGdkCoordRoundDown(mBounds.x), + DevicePixelsToGdkCoordRoundDown(mBounds.y)); + } + + bool parentHasMappedToplevel = newParent && newParent->mHasMappedToplevel; + if (mHasMappedToplevel != parentHasMappedToplevel) { + SetHasMappedToplevel(parentHasMappedToplevel); + } +} + +bool nsWindow::WidgetTypeSupportsAcceleration() { + 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 == eWindowType_popup) { + return 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(!gdk_window_is_destroyed(mGdkWindow), + "destroyed GdkWindow with widget"); + + MOZ_ASSERT( + !mParent, + "nsWindow::ReparentNativeWidget() works on toplevel windows only."); + + auto* newParent = static_cast<nsWindow*>(aNewParent); + GtkWindow* newParentWidget = GTK_WINDOW(newParent->GetGtkWidget()); + GtkWindow* shell = GTK_WINDOW(mShell); + + if (shell && gtk_window_get_transient_for(shell)) { + gtk_window_set_transient_for(shell, newParentWidget); + } +} + +void nsWindow::SetModal(bool aModal) { + LOG(("nsWindow::SetModal [%p] %d\n", (void*)this, aModal)); + if (mIsDestroyed) return; + if (!mIsTopLevel || !mShell) return; + gtk_window_set_modal(GTK_WINDOW(mShell), aModal ? TRUE : FALSE); +} + +// nsIWidget method, which means IsShown. +bool nsWindow::IsVisible() const { return mIsShown; } + +void nsWindow::RegisterTouchWindow() { + mHandleTouchEvent = true; + mTouches.Clear(); +} + +void nsWindow::ConstrainPosition(bool aAllowSlop, int32_t* aX, int32_t* aY) { + if (!mIsTopLevel || !mShell) 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<nsIScreen> screen; + nsCOMPtr<nsIScreenManager> screenmgr = + do_GetService("@mozilla.org/gfx/screenmanager;1"); + if (screenmgr) { + screenmgr->ScreenForRect(*aX, *aY, logWidth, logHeight, + getter_AddRefs(screen)); + } + + // We don't have any screen so leave the coordinates as is + if (!screen) return; + + nsIntRect screenRect; + if (mSizeMode != nsSizeMode_Fullscreen) { + // For normalized windows, use the desktop work area. + screen->GetAvailRectDisplayPix(&screenRect.x, &screenRect.y, + &screenRect.width, &screenRect.height); + } else { + // For full screen windows, use the desktop. + screen->GetRectDisplayPix(&screenRect.x, &screenRect.y, &screenRect.width, + &screenRect.height); + } + + if (aAllowSlop) { + if (*aX < screenRect.x - logWidth + kWindowPositionSlop) + *aX = screenRect.x - logWidth + kWindowPositionSlop; + else if (*aX >= screenRect.XMost() - kWindowPositionSlop) + *aX = screenRect.XMost() - kWindowPositionSlop; + + if (*aY < screenRect.y - logHeight + kWindowPositionSlop) + *aY = screenRect.y - logHeight + kWindowPositionSlop; + else if (*aY >= screenRect.YMost() - kWindowPositionSlop) + *aY = screenRect.YMost() - kWindowPositionSlop; + } else { + if (*aX < screenRect.x) + *aX = screenRect.x; + else if (*aX >= screenRect.XMost() - logWidth) + *aX = screenRect.XMost() - logWidth; + + if (*aY < screenRect.y) + *aY = screenRect.y; + else if (*aY >= screenRect.YMost() - logHeight) + *aY = screenRect.YMost() - logHeight; + } +} + +void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) { + mSizeConstraints.mMinSize = GetSafeWindowSize(aConstraints.mMinSize); + mSizeConstraints.mMaxSize = GetSafeWindowSize(aConstraints.mMaxSize); + + ApplySizeConstraints(); +} + +void nsWindow::AddCSDDecorationSize(int* aWidth, int* aHeight) { + if (mCSDSupportLevel == CSD_SUPPORT_CLIENT && mDrawInTitlebar) { + GtkBorder decorationSize = GetCSDDecorationSize(!mIsTopLevel); + *aWidth += decorationSize.left + decorationSize.right; + *aHeight += decorationSize.top + decorationSize.bottom; + } +} + +void nsWindow::ApplySizeConstraints(void) { + 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(0, 0)) { + AddCSDDecorationSize(&geometry.min_width, &geometry.min_height); + hints |= GDK_HINT_MIN_SIZE; + LOG(("nsWindow::ApplySizeConstraints [%p] min size %d %d\n", (void*)this, + geometry.min_width, geometry.min_height)); + } + if (mSizeConstraints.mMaxSize != + LayoutDeviceIntSize(NS_MAXSIZE, NS_MAXSIZE)) { + AddCSDDecorationSize(&geometry.max_width, &geometry.max_height); + hints |= GDK_HINT_MAX_SIZE; + LOG(("nsWindow::ApplySizeConstraints [%p] max size %d %d\n", (void*)this, + geometry.max_width, geometry.max_height)); + } + + if (mAspectRatio != 0.0f) { + 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; + + // Clear our cached resources when the window is hidden. + if (mIsShown && !aState) { + ClearCachedResources(); + } + + mIsShown = aState; + + LOG(("nsWindow::Show [%p] state %d\n", (void*)this, aState)); + + if (aState) { + // Now that this window is shown, mHasMappedToplevel needs to be + // tracked on viewable descendants. + SetHasMappedToplevel(mHasMappedToplevel); + } + + // 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); +} + +void nsWindow::ResizeInt(int aX, int aY, int aWidth, int aHeight, bool aMove, + bool aRepaint) { + LOG(("nsWindow::ResizeInt [%p] x:%d y:%d -> w:%d h:%d repaint %d aMove %d\n", + (void*)this, aX, aY, aWidth, aHeight, aRepaint, aMove)); + + ConstrainSize(&aWidth, &aHeight); + + LOG((" ConstrainSize: w:%d h;%d\n", aWidth, aHeight)); + + // If we used to have insane bounds, we may have skipped actually positioning + // the widget in NativeMoveResizeWaylandPopup, in which case we need to + // actually position it now as well. + const bool hadInsaneWaylandPopupDimensions = + !AreBoundsSane() && IsWaylandPopup(); + + if (aMove) { + mBounds.x = aX; + mBounds.y = aY; + } + + // For top-level windows, aWidth and aHeight should possibly be + // interpreted as frame bounds, but NativeResize treats these as window + // bounds (Bug 581866). + mBounds.SizeTo(aWidth, aHeight); + + // We set correct mBounds in advance here. This can be invalided by state + // event. + mBoundsAreValid = true; + + // Recalculate aspect ratio when resized from DOM + if (mAspectRatio != 0.0) { + LockAspectRatio(true); + } + + if (!mCreated) return; + + if (aMove || mPreferredPopupRectFlushed || hadInsaneWaylandPopupDimensions) { + LOG((" Need also to move, flushed? %d, bounds were insane: %d\n", + mPreferredPopupRectFlushed, hadInsaneWaylandPopupDimensions)); + NativeMoveResize(); + } else { + NativeResize(); + } + + NotifyRollupGeometryChange(); + + // send a resize notification if this is a toplevel + if (mIsTopLevel || mListenForResizes) { + DispatchResized(); + } +} + +void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) { + LOG(("nsWindow::Resize [%p] %f %f\n", (void*)this, aWidth, aHeight)); + + double scale = + BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0; + int32_t width = NSToIntRound(scale * aWidth); + int32_t height = NSToIntRound(scale * aHeight); + + ResizeInt(0, 0, width, height, /* aMove */ false, aRepaint); +} + +void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight, + bool aRepaint) { + LOG(("nsWindow::Resize [%p] %f %f repaint %d\n", (void*)this, aWidth, aHeight, + aRepaint)); + + double scale = + BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0; + int32_t width = NSToIntRound(scale * aWidth); + int32_t height = NSToIntRound(scale * aHeight); + + int32_t x = NSToIntRound(scale * aX); + int32_t y = NSToIntRound(scale * aY); + + ResizeInt(x, y, width, height, /* aMove */ true, aRepaint); +} + +void nsWindow::Enable(bool aState) { mEnabled = aState; } + +bool nsWindow::IsEnabled() const { return mEnabled; } + +void nsWindow::Move(double aX, double aY) { + LOG(("nsWindow::Move [%p] %f %f\n", (void*)this, aX, aY)); + + double scale = + BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0; + int32_t x = NSToIntRound(aX * scale); + int32_t y = NSToIntRound(aY * scale); + + if (mWindowType == eWindowType_toplevel || + mWindowType == eWindowType_dialog) { + SetSizeMode(nsSizeMode_Normal); + } + + // 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. + if (x == mBounds.x && y == mBounds.y && mWindowType != eWindowType_popup) + return; + + // XXX Should we do some AreBoundsSane check here? + + mBounds.x = x; + mBounds.y = y; + + if (!mCreated) return; + + if (IsWaylandPopup()) { + int32_t p2a = AppUnitsPerCSSPixel() / gfxPlatformGtk::GetFontScaleFactor(); + if (mPreferredPopupRect.x != mBounds.x * p2a && + mPreferredPopupRect.y != mBounds.y * p2a) { + NativeMove(); + NotifyRollupGeometryChange(); + } else { + LOG((" mBounds same as mPreferredPopupRect, no need to move")); + } + } else { + NativeMove(); + NotifyRollupGeometryChange(); + } +} + +bool nsWindow::IsPopup() { + return mIsTopLevel && mWindowType == eWindowType_popup; +} + +bool nsWindow::IsWaylandPopup() { return !mIsX11Display && IsPopup(); } + +void nsWindow::HideWaylandTooltips() { + while (gVisibleWaylandPopupWindows) { + nsWindow* window = + static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data); + if (window->mPopupType != ePopupTypeTooltip) break; + LOG(("nsWindow::HideWaylandTooltips [%p] hidding tooltip [%p].\n", + (void*)this, window)); + window->HideWaylandWindow(); + } +} + +void nsWindow::HideWaylandOpenedPopups() { + while (gVisibleWaylandPopupWindows) { + nsWindow* window = + static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data); + window->HideWaylandWindow(); + } +} + +// Hide popup nsWindows which are no longer in the nsXULPopupManager widget +// chain list. +void nsWindow::CleanupWaylandPopups() { + LOG(("nsWindow::CleanupWaylandPopups...\n")); + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + AutoTArray<nsIWidget*, 5> widgetChain; + pm->GetSubmenuWidgetChain(&widgetChain); + GList* popupList = gVisibleWaylandPopupWindows; + while (popupList) { + LOG((" Looking for %p [nsWindow]\n", popupList->data)); + nsWindow* waylandWnd = static_cast<nsWindow*>(popupList->data); + // Remove only menu popups or empty frames - they are most likely + // already rolledup popups + if (waylandWnd->IsMainMenuWindow() || !waylandWnd->GetFrame()) { + bool popupFound = false; + for (unsigned long i = 0; i < widgetChain.Length(); i++) { + if (waylandWnd == widgetChain[i]) { + popupFound = true; + break; + } + } + if (!popupFound) { + LOG((" nsWindow [%p] not found in PopupManager, hiding it.\n", + waylandWnd)); + waylandWnd->HideWaylandWindow(); + popupList = gVisibleWaylandPopupWindows; + } else { + LOG((" nsWindow [%p] is still open.\n", waylandWnd)); + popupList = popupList->next; + } + } else { + popupList = popupList->next; + } + } +} + +static nsMenuPopupFrame* GetMenuPopupFrame(nsIFrame* aFrame) { + if (aFrame) { + nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(aFrame); + return menuPopupFrame; + } + return nullptr; +} + +// The MenuList popups are used as dropdown menus for example in WebRTC +// microphone/camera chooser or autocomplete widgets. +bool nsWindow::IsMainMenuWindow() { + nsMenuPopupFrame* menuPopupFrame = GetMenuPopupFrame(GetFrame()); + if (menuPopupFrame) { + LOG((" nsMenuPopupFrame [%p] type: %d IsMenu: %d, IsMenuList: %d\n", + menuPopupFrame, menuPopupFrame->PopupType(), menuPopupFrame->IsMenu(), + menuPopupFrame->IsMenuList())); + return mPopupType == ePopupTypeMenu && !menuPopupFrame->IsMenuList(); + } + return false; +} + +GtkWindow* nsWindow::GetTopmostWindow() { + nsView* view = nsView::GetViewFor(this); + if (view) { + nsView* parentView = view->GetParent(); + if (parentView) { + nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr); + if (parentWidget) { + nsWindow* parentnsWindow = static_cast<nsWindow*>(parentWidget); + LOG((" Topmost window: %p [nsWindow]\n", parentnsWindow)); + return GTK_WINDOW(parentnsWindow->mShell); + } + } + } + return nullptr; +} + +GtkWindow* nsWindow::GetCurrentWindow() { + GtkWindow* parentGtkWindow = nullptr; + // get the last opened window from gVisibleWaylandPopupWindows + if (gVisibleWaylandPopupWindows) { + nsWindow* parentnsWindow = + static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data); + if (parentnsWindow) { + LOG((" Setting parent to last opened window: %p [nsWindow]\n", + parentnsWindow)); + parentGtkWindow = GTK_WINDOW(parentnsWindow->GetGtkWidget()); + } + } + // get the topmost window if the last opened windows are empty + if (!parentGtkWindow) { + parentGtkWindow = GetTopmostWindow(); + } + if (parentGtkWindow && GTK_IS_WINDOW(parentGtkWindow)) { + return GTK_WINDOW(parentGtkWindow); + } else { + LOG((" Failed to get current window for %p: %p\n", this, parentGtkWindow)); + } + return nullptr; +} + +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; +} + +// 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. +GtkWidget* nsWindow::ConfigureWaylandPopupWindows() { + MOZ_ASSERT(this->mWindowType == eWindowType_popup); + LOG( + ("nsWindow::ConfigureWaylandPopupWindows [%p], frame %p hasRemoteContent " + "%d\n", + (void*)this, this->GetFrame(), this->HasRemoteContent())); +#if DEBUG + if (this->GetFrame() && this->GetFrame()->GetContent()->GetID()) { + nsCString nodeId; + this->GetFrame()->GetContent()->GetID()->ToUTF8String(nodeId); + LOG((" [%p] popup node id=%s\n", this, nodeId.get())); + } +#endif + + if (!GetFrame()) { + LOG((" Window without frame cannot be configured.\n")); + return nullptr; + } + + // Check if we're already configured. + if (gVisibleWaylandPopupWindows && + g_list_find(gVisibleWaylandPopupWindows, this)) { + LOG((" [%p] is already configured.\n", (void*)this)); + return GTK_WIDGET(gtk_window_get_transient_for(GTK_WINDOW(mShell))); + } + + // If we're opening a new window we don't want to attach it to a tooltip + // as it's short lived temporary window. + HideWaylandTooltips(); + + // Cleanup already closed menus + CleanupWaylandPopups(); + + if (gVisibleWaylandPopupWindows && + (HasRemoteContent() || IsWidgetOverflowWindow())) { + nsWindow* openedWindow = + static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data); + LOG((" this [%p], lastOpenedWindow [%p]", this, openedWindow)); + if (openedWindow != this) { + LOG( + (" Hiding all opened popups because the window is remote content or " + "overflow-widget")); + HideWaylandOpenedPopups(); + } + } + + GtkWindow* parentGtkWindow = GetCurrentWindow(); + if (parentGtkWindow) { + MOZ_ASSERT(parentGtkWindow != GTK_WINDOW(this->GetGtkWidget()), + "Cannot set self as parent"); + gtk_window_set_transient_for(GTK_WINDOW(mShell), + GTK_WINDOW(parentGtkWindow)); + // Add current window to the visible popup list + gVisibleWaylandPopupWindows = + g_list_prepend(gVisibleWaylandPopupWindows, this); + LOG((" Parent window for %p: %p [GtkWindow]", this, parentGtkWindow)); + } + + MOZ_ASSERT(parentGtkWindow, "NO parent window for %p: expect popup glitches"); + return GTK_WIDGET(parentGtkWindow); +} + +static void NativeMoveResizeWaylandPopupCallback( + GdkWindow* window, const GdkRectangle* flipped_rect, + const GdkRectangle* final_rect, gboolean flipped_x, gboolean flipped_y, + void* aWindow) { + LOG(("NativeMoveResizeWaylandPopupCallback [%p] flipped_x %d flipped_y %d\n", + aWindow, flipped_x, flipped_y)); + + LOG((" flipped_rect x=%d y=%d width=%d height=%d\n", flipped_rect->x, + flipped_rect->y, flipped_rect->width, flipped_rect->height)); + LOG((" final_rect x=%d y=%d width=%d height=%d\n", final_rect->x, + final_rect->y, final_rect->width, final_rect->height)); + nsWindow* wnd = get_window_for_gdk_window(window); + + wnd->NativeMoveResizeWaylandPopupCB(final_rect, flipped_x, flipped_y); +} + +void nsWindow::NativeMoveResizeWaylandPopupCB(const GdkRectangle* aFinalSize, + bool aFlippedX, bool aFlippedY) { + LOG((" orig mBounds x=%d y=%d width=%d height=%d\n", mBounds.x, mBounds.y, + mBounds.width, mBounds.height)); + + // Remove signal handler because it can also be called from + // xdg_popup_configure + GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(mShell)); + if (g_signal_handler_find( + gdkWindow, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr, + FuncToGpointer(NativeMoveResizeWaylandPopupCallback), this)) { + LOG((" Disconnecting NativeMoveResizeWaylandPopupCallback")); + g_signal_handlers_disconnect_by_func( + gdkWindow, FuncToGpointer(NativeMoveResizeWaylandPopupCallback), this); + } + mWaitingForMoveToRectCB = false; + + // We ignore the callback position data because the another resize has been + // called before the callback have been triggered. + if (mPendingSizeRect.height > 0 || mPendingSizeRect.width > 0) { + LOG( + (" Another resize called during waiting for callback, calling " + "Resize(%d, %d)\n", + mPendingSizeRect.width, mPendingSizeRect.height)); + // Set the preferred size to zero to avoid wrong size of popup because the + // mPreferredPopupRect is used in nsMenuPopupFrame to set dimensions + mPreferredPopupRect = nsRect(0, 0, 0, 0); + + // We need to schedule another resize because the window has been resized + // again before callback was called. + Resize(mPendingSizeRect.width, mPendingSizeRect.height, true); + DispatchResized(); + mPendingSizeRect.width = mPendingSizeRect.height = 0; + return; + } + + GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell)); + if (!parentGtkWindow || !GTK_IS_WIDGET(parentGtkWindow)) { + NS_WARNING("Popup has no parent!"); + return; + } + + // The position of the menu in GTK is relative to it's parent window while + // in mBounds we have position relative to toplevel window. We need to check + // and update mBounds in the toplevel coordinates. + int x_parent, y_parent; + GetWindowOrigin(gtk_widget_get_window(GTK_WIDGET(parentGtkWindow)), &x_parent, + &y_parent); + + LayoutDeviceIntRect newBounds(aFinalSize->x, aFinalSize->y, aFinalSize->width, + aFinalSize->height); + + newBounds.x = GdkCoordToDevicePixels(newBounds.x); + newBounds.y = GdkCoordToDevicePixels(newBounds.y); + + double scale = + BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0; + int32_t newWidth = NSToIntRound(scale * newBounds.width); + int32_t newHeight = NSToIntRound(scale * newBounds.height); + + LOG((" new mBounds x=%d y=%d width=%d height=%d\n", newBounds.x, + newBounds.y, newWidth, newHeight)); + + bool needsPositionUpdate = + (newBounds.x != mBounds.x || newBounds.y != mBounds.y); + bool needsSizeUpdate = + (newWidth != mBounds.width || newHeight != mBounds.height); + // Update view + + if (needsSizeUpdate) { + LOG((" needSizeUpdate\n")); + int32_t p2a = AppUnitsPerCSSPixel() / gfxPlatformGtk::GetFontScaleFactor(); + mPreferredPopupRect = nsRect(NSIntPixelsToAppUnits(newBounds.x, p2a), + NSIntPixelsToAppUnits(newBounds.y, p2a), + NSIntPixelsToAppUnits(newBounds.width, p2a), + NSIntPixelsToAppUnits(newBounds.height, p2a)); + mPreferredPopupRectFlushed = false; + Resize(newBounds.width, newBounds.height, true); + DispatchResized(); + + nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame()); + if (popupFrame) { + RefPtr<PresShell> presShell = popupFrame->PresShell(); + presShell->FrameNeedsReflow(popupFrame, IntrinsicDirty::Resize, + NS_FRAME_IS_DIRTY); + } + } + + if (needsPositionUpdate) { + LOG((" needPositionUpdate\n")); + // The newBounds are in coordinates relative to the parent window/popup. + // The NotifyWindowMoved requires the coordinates relative to the toplevel. + // We use the gdk_window_get_origin to get correct coordinates. + gint x = 0, y = 0; + GetWindowOrigin(gtk_widget_get_window(GTK_WIDGET(mShell)), &x, &y); + NotifyWindowMoved(GdkCoordToDevicePixels(x), GdkCoordToDevicePixels(y)); + } +} + +#ifdef MOZ_WAYLAND +static GdkGravity PopupAlignmentToGdkGravity(int8_t aAlignment) { + switch (aAlignment) { + case POPUPALIGNMENT_NONE: + return GDK_GRAVITY_NORTH_WEST; + break; + case POPUPALIGNMENT_TOPLEFT: + return GDK_GRAVITY_NORTH_WEST; + break; + case POPUPALIGNMENT_TOPRIGHT: + return GDK_GRAVITY_NORTH_EAST; + break; + case POPUPALIGNMENT_BOTTOMLEFT: + return GDK_GRAVITY_SOUTH_WEST; + break; + case POPUPALIGNMENT_BOTTOMRIGHT: + return GDK_GRAVITY_SOUTH_EAST; + break; + case POPUPALIGNMENT_LEFTCENTER: + return GDK_GRAVITY_WEST; + break; + case POPUPALIGNMENT_RIGHTCENTER: + return GDK_GRAVITY_EAST; + break; + case POPUPALIGNMENT_TOPCENTER: + return GDK_GRAVITY_NORTH; + break; + case POPUPALIGNMENT_BOTTOMCENTER: + return GDK_GRAVITY_SOUTH; + break; + } + return GDK_GRAVITY_STATIC; +} +#endif + +void nsWindow::NativeMoveResizeWaylandPopup(GdkPoint* aPosition, + GdkRectangle* aSize) { + // 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"); + LOG(("nsWindow::NativeMoveResizeWaylandPopup [%p]\n", (void*)this)); + + // 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", mBounds.width, + mBounds.height)); + return; + } + + if (aSize) { + gtk_window_resize(GTK_WINDOW(mShell), aSize->width, aSize->height); + } + + GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(mShell)); + + // Use standard gtk_window_move() instead of gdk_window_move_to_rect() when: + // - gdk_window_move_to_rect() is not available + // - the widget doesn't have a valid GdkWindow + if (!sGdkWindowMoveToRect || !gdkWindow) { + LOG((" use gtk_window_move(%d, %d)\n", aPosition->x, aPosition->y)); + gtk_window_move(GTK_WINDOW(mShell), aPosition->x, aPosition->y); + return; + } + + GtkWidget* parentWindow = ConfigureWaylandPopupWindows(); + LOG(("nsWindow::NativeMoveResizeWaylandPopup: Set popup parent %p\n", + parentWindow)); + + // Get anchor rectangle + LayoutDeviceIntRect anchorRect(0, 0, 0, 0); + nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame()); + + int32_t p2a; + double devPixelsPerCSSPixel = StaticPrefs::layout_css_devPixelsPerPx(); + if (devPixelsPerCSSPixel > 0.0) { + p2a = AppUnitsPerCSSPixel() / devPixelsPerCSSPixel * GdkScaleFactor(); + } else { + p2a = AppUnitsPerCSSPixel() / gfxPlatformGtk::GetFontScaleFactor(); + } + if (popupFrame) { +#ifdef MOZ_WAYLAND + anchorRect = LayoutDeviceIntRect::FromAppUnitsToOutside( + popupFrame->GetAnchorRect(), p2a); +#endif + } + +#ifdef MOZ_WAYLAND + bool hasAnchorRect = true; +#endif + if (anchorRect.width == 0) { + LOG((" No anchor rect given, use aPosition for anchor")); + anchorRect.SetRect(aPosition->x, aPosition->y, 1, 1); +#ifdef MOZ_WAYLAND + hasAnchorRect = false; +#endif + } + LOG((" anchor x %d y %d width %d height %d (absolute coords)\n", + anchorRect.x, anchorRect.y, anchorRect.width, anchorRect.height)); + + // Anchor rect is in the toplevel coordinates but we need to transfer it to + // the coordinates relative to the popup parent for the + // gdk_window_move_to_rect + int x_parent = 0, y_parent = 0; + GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell)); + if (parentGtkWindow) { + GetWindowOrigin(gtk_widget_get_window(GTK_WIDGET(parentGtkWindow)), + &x_parent, &y_parent); + } + LOG((" x_parent %d y_parent %d\n", x_parent, y_parent)); + anchorRect.MoveBy(-x_parent, -y_parent); + GdkRectangle rect = {anchorRect.x, anchorRect.y, anchorRect.width, + anchorRect.height}; + + // Get gravity and flip type + GdkGravity rectAnchor = GDK_GRAVITY_NORTH_WEST; + GdkGravity menuAnchor = GDK_GRAVITY_NORTH_WEST; + FlipType flipType = FlipType_Default; + int8_t position = -1; + if (popupFrame) { +#ifdef MOZ_WAYLAND + rectAnchor = PopupAlignmentToGdkGravity(popupFrame->GetPopupAnchor()); + menuAnchor = PopupAlignmentToGdkGravity(popupFrame->GetPopupAlignment()); + flipType = popupFrame->GetFlipType(); + position = popupFrame->GetAlignmentPosition(); +#endif + } else { + LOG((" NO ANCHOR INFO")); + if (GetTextDirection() == GTK_TEXT_DIR_RTL) { + rectAnchor = GDK_GRAVITY_NORTH_EAST; + menuAnchor = GDK_GRAVITY_NORTH_EAST; + } + } + LOG((" parentRect gravity: %d anchor gravity: %d\n", rectAnchor, menuAnchor)); + + GdkAnchorHints hints = GdkAnchorHints(GDK_ANCHOR_RESIZE); + + // slideHorizontal from nsMenuPopupFrame::SetPopupPosition + 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); + } + + if (popupFrame && 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 (!IsMainMenuWindow()) { + // we don't want to slide menus to fit the screen rather resize them + hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE); + } + + // A workaround for https://gitlab.gnome.org/GNOME/gtk/issues/1986 + // gdk_window_move_to_rect() does not reposition visible windows. + static auto sGtkWidgetIsVisible = + (gboolean(*)(GtkWidget*))dlsym(RTLD_DEFAULT, "gtk_widget_is_visible"); + + bool isWidgetVisible = + (sGtkWidgetIsVisible != nullptr) && sGtkWidgetIsVisible(mShell); + if (isWidgetVisible) { + PauseRemoteRenderer(); + gtk_widget_hide(mShell); + } + + LOG((" requested rect: x: %d y: %d width: %d height: %d\n", rect.x, rect.y, + rect.width, rect.height)); + if (aSize) { + LOG((" aSize: x%d y%d w%d h%d\n", aSize->x, aSize->y, aSize->width, + aSize->height)); + } else { + LOG((" No aSize given")); + } + + // Inspired by nsMenuPopupFrame::AdjustPositionForAnchorAlign + nsPoint cursorOffset(0, 0); +#ifdef MOZ_WAYLAND + // Offset is already computed to the tooltips + if (hasAnchorRect && popupFrame && mPopupType != ePopupTypeTooltip) { + nsMargin margin(0, 0, 0, 0); + popupFrame->StyleMargin()->GetMargin(margin); + switch (popupFrame->GetPopupAlignment()) { + case POPUPALIGNMENT_TOPRIGHT: + cursorOffset.MoveBy(-margin.right, margin.top); + break; + case POPUPALIGNMENT_BOTTOMLEFT: + cursorOffset.MoveBy(margin.left, -margin.bottom); + break; + case POPUPALIGNMENT_BOTTOMRIGHT: + cursorOffset.MoveBy(-margin.right, -margin.bottom); + break; + case POPUPALIGNMENT_TOPLEFT: + default: + cursorOffset.MoveBy(margin.left, margin.top); + break; + } + } +#endif + + if (!g_signal_handler_find( + gdkWindow, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr, + FuncToGpointer(NativeMoveResizeWaylandPopupCallback), this)) { + g_signal_connect(gdkWindow, "moved-to-rect", + G_CALLBACK(NativeMoveResizeWaylandPopupCallback), this); + } + + LOG((" popup window cursor offset x: %d y: %d\n", cursorOffset.x / p2a, + cursorOffset.y / p2a)); + mWaitingForMoveToRectCB = true; + sGdkWindowMoveToRect(gdkWindow, &rect, rectAnchor, menuAnchor, hints, + cursorOffset.x / p2a, cursorOffset.y / p2a); + + if (isWidgetVisible) { + // We show the popup with the same configuration so no need to call + // ConfigureWaylandPopupWindows() before gtk_widget_show(). + gtk_widget_show(mShell); + } +} + +void nsWindow::NativeMove() { + GdkPoint point = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft()); + + LOG(("nsWindow::NativeMove [%p] %d %d\n", (void*)this, point.x, point.y)); + + if (IsWaylandPopup()) { + GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size()); + NativeMoveResizeWaylandPopup(&point, &size); + } else if (mIsTopLevel) { + gtk_window_move(GTK_WINDOW(mShell), point.x, point.y); + } else if (mGdkWindow) { + gdk_window_move(mGdkWindow, point.x, point.y); + } +} + +void nsWindow::SetZIndex(int32_t aZIndex) { + nsIWidget* oldPrev = GetPrevSibling(); + + nsBaseWidget::SetZIndex(aZIndex); + + if (GetPrevSibling() == oldPrev) { + return; + } + + NS_ASSERTION(!mContainer, "Expected Mozilla child widget"); + + // We skip the nsWindows that don't have mGdkWindows. + // These are probably in the process of being destroyed. + + 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 [%p] %d\n", (void*)this, aMode)); + + // Save the requested state. + nsBaseWidget::SetSizeMode(aMode); + + // return if there's no shell or our current state is the same as + // the mode we were just set to. + if (!mShell || mSizeState == mSizeMode) { + LOG((" already set")); + return; + } + + 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: + LOG((" set normal")); + // nsSizeMode_Normal, really. + if (mSizeState == nsSizeMode_Minimized) + gtk_window_deiconify(GTK_WINDOW(mShell)); + else if (mSizeState == nsSizeMode_Maximized) + gtk_window_unmaximize(GTK_WINDOW(mShell)); + break; + } + + // Request mBounds update from configure event as we may not get + // OnSizeAllocate for size state changes (Bug 1489463). + mBoundsAreValid = false; + + mSizeState = mSizeMode; +} + +static bool GetWindowManagerName(GdkWindow* gdk_window, nsACString& wmName) { + if (!gfxPlatformGtk::GetPlatform()->IsX11Display()) { + return false; + } + + 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)); + + int actual_format_return; + Atom actual_type_return; + unsigned long nitems_return; + unsigned long bytes_after_return; + unsigned char* prop_return = nullptr; + auto releaseXProperty = MakeScopeExit([&] { + if (prop_return) { + XFree(prop_return); + } + }); + + Atom property = XInternAtom(xdisplay, "_NET_SUPPORTING_WM_CHECK", true); + Atom req_type = XInternAtom(xdisplay, "WINDOW", true); + if (!property || !req_type) { + return false; + } + int result = + XGetWindowProperty(xdisplay, root_win, property, + 0L, // offset + sizeof(Window) / 4, // length + false, // delete + req_type, &actual_type_return, &actual_format_return, + &nitems_return, &bytes_after_return, &prop_return); + + if (result != Success || bytes_after_return != 0 || nitems_return != 1) { + return false; + } + + Window wmWindow = reinterpret_cast<Window*>(prop_return)[0]; + if (!wmWindow) { + return false; + } + + XFree(prop_return); + prop_return = nullptr; + + property = XInternAtom(xdisplay, "_NET_WM_NAME", true); + req_type = XInternAtom(xdisplay, "UTF8_STRING", true); + if (!property || !req_type) { + return false; + } + { + // Suppress fatal errors for a missing window. + ScopedXErrorHandler handler; + result = + XGetWindowProperty(xdisplay, wmWindow, property, + 0L, // offset + INT32_MAX, // length + false, // delete + req_type, &actual_type_return, &actual_format_return, + &nitems_return, &bytes_after_return, &prop_return); + } + + if (result != Success || bytes_after_return != 0) { + return false; + } + + wmName = reinterpret_cast<const char*>(prop_return); + return true; +} + +#define kDesktopMutterSchema "org.gnome.mutter" +#define kDesktopDynamicWorkspacesKey "dynamic-workspaces" + +static bool WorkspaceManagementDisabled(GdkWindow* gdk_window) { + if (Preferences::GetBool("widget.disable-workspace-management", false)) { + return true; + } + if (Preferences::HasUserValue("widget.workspace-management")) { + return Preferences::GetBool("widget.workspace-management"); + } + + static const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP"); + if (currentDesktop && strstr(currentDesktop, "GNOME")) { + // 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(nsLiteralCString(kDesktopMutterSchema), + getter_AddRefs(mutterSettings)); + if (mutterSettings) { + if (NS_SUCCEEDED(mutterSettings->GetBoolean( + nsLiteralCString(kDesktopDynamicWorkspacesKey), + &usesDynamicWorkspaces))) { + } + } + } + return usesDynamicWorkspaces; + } + + // When XDG_CURRENT_DESKTOP is missing, try to get window manager name. + if (!currentDesktop) { + nsAutoCString wmName; + if (GetWindowManagerName(gdk_window, wmName)) { + if (wmName.EqualsLiteral("bspwm")) { + return true; + } + if (wmName.EqualsLiteral("i3")) { + return true; + } + } + } + + return false; +} + +void nsWindow::GetWorkspaceID(nsAString& workspaceID) { + workspaceID.Truncate(); + + if (!mIsX11Display || !mShell) { + return; + } + // Get the gdk window for this widget. + GdkWindow* gdk_window = gtk_widget_get_window(mShell); + if (!gdk_window) { + return; + } + + if (WorkspaceManagementDisabled(gdk_window)) { + 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)) { + return; + } + + workspaceID.AppendInt((int32_t)wm_desktop[0]); + g_free(wm_desktop); +} + +void nsWindow::MoveToWorkspace(const nsAString& workspaceIDStr) { + nsresult rv = NS_OK; + int32_t workspaceID = workspaceIDStr.ToInteger(&rv); + if (NS_FAILED(rv) || !workspaceID || !mIsX11Display || !mShell) { + return; + } + + // Get the gdk window for this widget. + GdkWindow* gdk_window = gtk_widget_get_window(mShell); + if (!gdk_window) { + 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); +} + +typedef void (*SetUserTimeFunc)(GdkWindow* aWindow, guint32 aTimestamp); + +static void SetUserTimeAndStartupIDForActivatedWindow(GtkWidget* aWindow) { + nsGTKToolkit* GTKToolkit = nsGTKToolkit::GetToolkit(); + if (!GTKToolkit) return; + + nsAutoCString desktopStartupID; + GTKToolkit->GetDesktopStartupID(&desktopStartupID); + if (desktopStartupID.IsEmpty()) { + // 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. + uint32_t timestamp = GTKToolkit->GetFocusTimestamp(); + if (timestamp) { + gdk_window_focus(gtk_widget_get_window(aWindow), timestamp); + GTKToolkit->SetFocusTimestamp(0); + } + return; + } + + gtk_window_set_startup_id(GTK_WINDOW(aWindow), desktopStartupID.get()); + + // 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 + GTKToolkit->SetFocusTimestamp(0); + GTKToolkit->SetDesktopStartupID(""_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. + GdkDisplay* gdkDisplay = gdk_display_get_default(); + guint32 timestamp = GDK_IS_X11_DISPLAY(gdkDisplay) + ? gdk_x11_display_get_user_time(gdkDisplay) + : gtk_get_current_event_time(); + + if (sLastUserInputTime != GDK_CURRENT_TIME && + TimestampIsNewerThan(sLastUserInputTime, timestamp)) { + return sLastUserInputTime; + } + + return timestamp; +} + +void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) { + // 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. + + LOGFOCUS((" SetFocus %d [%p]\n", aRaise == Raise::Yes, (void*)this)); + + GtkWidget* owningWidget = GetMozContainerWidget(); + if (!owningWidget) return; + + // Raise the window if someone passed in true and the prefs are + // set properly. + GtkWidget* toplevelWidget = gtk_widget_get_toplevel(owningWidget); + + if (gRaiseWindows && aRaise == Raise::Yes && toplevelWidget && + !gtk_widget_has_focus(owningWidget) && + !gtk_widget_has_focus(toplevelWidget)) { + GtkWidget* top_window = GetToplevelWidget(); + if (top_window && (gtk_widget_get_visible(top_window))) { + gdk_window_show_unraised(gtk_widget_get_window(top_window)); + // Unset the urgency hint if possible. + SetUrgencyHint(top_window, false); + } + } + + RefPtr<nsWindow> owningWindow = get_window_for_gtk_widget(owningWidget); + if (!owningWindow) 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 (gRaiseWindows && owningWindow->mIsShown && owningWindow->mShell && + !gtk_window_is_active(GTK_WINDOW(owningWindow->mShell))) { + if (!mIsX11Display && + Preferences::GetBool("testing.browserTestHarness.running", false)) { + // Wayland does not support focus changes so we need to workaround it + // by window hide/show sequence but only when it's running in testsuite. + owningWindow->NativeShow(false); + owningWindow->NativeShow(true); + return; + } + + uint32_t timestamp = GDK_CURRENT_TIME; + + nsGTKToolkit* GTKToolkit = nsGTKToolkit::GetToolkit(); + if (GTKToolkit) timestamp = GTKToolkit->GetFocusTimestamp(); + + LOGFOCUS((" requesting toplevel activation [%p]\n", (void*)this)); + NS_ASSERTION(owningWindow->mWindowType != eWindowType_popup || mParent, + "Presenting an override-redirect window"); + gtk_window_present_with_time(GTK_WINDOW(owningWindow->mShell), timestamp); + + if (GTKToolkit) GTKToolkit->SetFocusTimestamp(0); + } + return; + } + + // aRaise == No means that keyboard events should be dispatched from this + // widget. + + // Ensure owningWidget is the focused GtkWidget within its toplevel window. + // + // For eWindowType_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(owningWidget)) { + // 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(owningWidget); + gBlockActivateEvent = false; + } + + // If this is the widget that already has focus, return. + if (gFocusWindow == this) { + LOGFOCUS((" already have focus [%p]\n", (void*)this)); + return; + } + + // Set this window to be the focused child window + gFocusWindow = this; + + if (mIMContext) { + mIMContext->OnFocusWindow(this); + } + + LOGFOCUS((" widget now has focus in SetFocus() [%p]\n", (void*)this)); +} + +LayoutDeviceIntRect nsWindow::GetScreenBounds() { + LayoutDeviceIntRect rect; + if (mIsTopLevel && mContainer) { + // use the point including window decorations + gint x, y; + gdk_window_get_root_origin(gtk_widget_get_window(GTK_WIDGET(mContainer)), + &x, &y); + rect.MoveTo(GdkPointToDevicePixels({x, y})); + } else { + rect.MoveTo(WidgetToScreenOffset()); + } + // 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. + rect.SizeTo(mBounds.Size()); +#if MOZ_LOGGING + gint scale = GdkScaleFactor(); + LOG(("GetScreenBounds [%p] %d,%d -> %d x %d, unscaled %d,%d -> %d x %d\n", + this, 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::UpdateClientOffsetFromFrameExtents() { + AUTO_PROFILER_LABEL("nsWindow::UpdateClientOffsetFromFrameExtents", OTHER); + + if (mCSDSupportLevel == CSD_SUPPORT_CLIENT && mDrawInTitlebar) { + return; + } + + if (!mIsTopLevel || !mShell || + gtk_window_get_window_type(GTK_WINDOW(mShell)) == GTK_WINDOW_POPUP) { + mClientOffset = nsIntPoint(0, 0); + return; + } + + GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL); + + GdkAtom type_returned; + int format_returned; + int length_returned; + long* frame_extents; + + if (!gdk_property_get(gtk_widget_get_window(mShell), + gdk_atom_intern("_NET_FRAME_EXTENTS", FALSE), + cardinal_atom, + 0, // offset + 4 * 4, // length + FALSE, // delete + &type_returned, &format_returned, &length_returned, + (guchar**)&frame_extents) || + length_returned / sizeof(glong) != 4) { + mClientOffset = nsIntPoint(0, 0); + } else { + // data returned is in the order left, right, top, bottom + auto left = int32_t(frame_extents[0]); + auto top = int32_t(frame_extents[2]); + g_free(frame_extents); + + mClientOffset = nsIntPoint(left, top); + } + + // 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); + + LOG(("nsWindow::UpdateClientOffsetFromFrameExtents [%p] %d,%d\n", (void*)this, + mClientOffset.x, mClientOffset.y)); +} + +LayoutDeviceIntPoint nsWindow::GetClientOffset() { + return mIsX11Display ? LayoutDeviceIntPoint::FromUnknownPoint(mClientOffset) + : LayoutDeviceIntPoint(0, 0); +} + +gboolean nsWindow::OnPropertyNotifyEvent(GtkWidget* aWidget, + GdkEventProperty* aEvent) { + if (aEvent->atom == gdk_atom_intern("_NET_FRAME_EXTENTS", FALSE)) { + UpdateClientOffsetFromFrameExtents(); + return FALSE; + } + + if (GetCurrentTimeGetter()->PropertyNotifyHandler(aWidget, aEvent)) { + return TRUE; + } + + return FALSE; +} + +static GdkCursor* GetCursorForImage(imgIContainer* aCursorImage, + uint32_t aHotspotX, uint32_t aHotspotY) { + if (!aCursorImage) { + return nullptr; + } + GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(aCursorImage); + if (!pixbuf) { + return nullptr; + } + + int width = gdk_pixbuf_get_width(pixbuf); + int height = gdk_pixbuf_get_height(pixbuf); + + auto CleanupPixBuf = + mozilla::MakeScopeExit([&]() { g_object_unref(pixbuf); }); + + // 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 (width > 128 || height > 128) { + 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)) { + GdkPixbuf* alphaBuf = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0); + g_object_unref(pixbuf); + pixbuf = alphaBuf; + if (!alphaBuf) { + return nullptr; + } + } + + return gdk_cursor_new_from_pixbuf(gdk_display_get_default(), pixbuf, + aHotspotX, aHotspotY); +} + +void nsWindow::SetCursor(nsCursor aDefaultCursor, imgIContainer* aCursorImage, + uint32_t aHotspotX, uint32_t aHotspotY) { + // if we're not the toplevel window pass up the cursor request to + // the toplevel window to handle it. + if (!mContainer && mGdkWindow) { + nsWindow* window = GetContainerWindow(); + if (!window) return; + + window->SetCursor(aDefaultCursor, aCursorImage, aHotspotX, aHotspotY); + return; + } + + // Only change cursor if it's actually been changed + if (!aCursorImage && aDefaultCursor == mCursor && !mUpdateCursor) { + return; + } + + mUpdateCursor = false; + mCursor = eCursorInvalid; + + // Try to set the cursor image first, and fall back to the numeric cursor. + GdkCursor* newCursor = GetCursorForImage(aCursorImage, aHotspotX, aHotspotY); + if (!newCursor) { + newCursor = get_gtk_cursor(aDefaultCursor); + if (newCursor) { + mCursor = aDefaultCursor; + } + } + + auto CleanupCursor = mozilla::MakeScopeExit([&]() { + // get_gtk_cursor returns a weak reference, which we shouldn't unref. + if (newCursor && mCursor == eCursorInvalid) { + g_object_unref(newCursor); + } + }); + + if (!newCursor || !mContainer) { + return; + } + + gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)), + newCursor); +} + +void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) { + if (!mGdkWindow) return; + + GdkRectangle rect = DevicePixelsToGdkRectRoundOut(aRect); + gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE); + + LOGDRAW(("Invalidate (rect) [%p]: %d %d %d %d\n", (void*)this, rect.x, rect.y, + rect.width, rect.height)); +} + +void* nsWindow::GetNativeData(uint32_t aDataType) { + switch (aDataType) { + case NS_NATIVE_WINDOW: + case NS_NATIVE_WIDGET: { + if (!mGdkWindow) return nullptr; + + return mGdkWindow; + } + + case NS_NATIVE_DISPLAY: { +#ifdef MOZ_X11 + GdkDisplay* gdkDisplay = gdk_display_get_default(); + if (gdkDisplay && GDK_IS_X11_DISPLAY(gdkDisplay)) { + return GDK_DISPLAY_XDISPLAY(gdkDisplay); + } +#endif /* MOZ_X11 */ + // Don't bother to return native display on Wayland as it's for + // X11 only NPAPI plugins. + return nullptr; + } + case NS_NATIVE_SHELLWIDGET: + return GetToplevelWidget(); + + case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID: + case NS_NATIVE_SHAREABLE_WINDOW: + if (mIsX11Display) { + return (void*)GDK_WINDOW_XID(gdk_window_get_toplevel(mGdkWindow)); + } + NS_WARNING( + "nsWindow::GetNativeData(): " + "NS_NATIVE_SHAREABLE_WINDOW / 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: { + if (mIsX11Display) { + return mGdkWindow ? (void*)GDK_WINDOW_XID(mGdkWindow) : nullptr; + } +#ifdef MOZ_WAYLAND + if (mContainer) { + return moz_container_wayland_get_egl_window(mContainer, + GdkScaleFactor()); + } +#endif + return nullptr; + } + 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()); + } +} + +LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() { + nsIntPoint origin(0, 0); + GetWindowOrigin(mGdkWindow, &origin.x, &origin.y); + + return GdkPointToDevicePixels({origin.x, origin.y}); +} + +void nsWindow::CaptureMouse(bool aCapture) { + LOG(("CaptureMouse %p\n", (void*)this)); + + if (!mGdkWindow) return; + + if (!mContainer) return; + + if (aCapture) { + gtk_grab_add(GTK_WIDGET(mContainer)); + GrabPointer(GetLastUserInputTime()); + } else { + ReleaseGrabs(); + gtk_grab_remove(GTK_WIDGET(mContainer)); + } +} + +void nsWindow::CaptureRollupEvents(nsIRollupListener* aListener, + bool aDoCapture) { + if (!mGdkWindow) return; + + if (!mContainer) return; + + LOG(("CaptureRollupEvents %p %i\n", this, int(aDoCapture))); + + if (aDoCapture) { + gRollupListener = aListener; + // Don't add a grab if a drag is in progress, or if the widget is a drag + // feedback popup. (panels with type="drag"). + if (!mIsDragPopup && !nsWindow::DragInProgress()) { + gtk_grab_add(GTK_WIDGET(mContainer)); + GrabPointer(GetLastUserInputTime()); + } + } else { + if (!nsWindow::DragInProgress()) { + ReleaseGrabs(); + } + // 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)); + gRollupListener = nullptr; + } +} + +nsresult nsWindow::GetAttention(int32_t aCycleCount) { + LOG(("nsWindow::GetAttention [%p]\n", (void*)this)); + + 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 (mIsX11Display) { + 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; +} + +#if 0 +# ifdef DEBUG +// Paint flashing code (disabled for cairo - see below) + +# define CAPS_LOCK_IS_ON \ + (KeymapWrapper::AreModifiersCurrentlyActive(KeymapWrapper::CAPS_LOCK)) + +# define WANT_PAINT_FLASHING (debug_WantPaintFlashing() && CAPS_LOCK_IS_ON) + +# ifdef MOZ_X11 +static void +gdk_window_flash(GdkWindow * aGdkWindow, + unsigned int aTimes, + unsigned int aInterval, // Milliseconds + GdkRegion * aRegion) +{ + gint x; + gint y; + gint width; + gint height; + guint i; + GdkGC * gc = 0; + GdkColor white; + + gdk_window_get_geometry(aGdkWindow,nullptr,nullptr,&width,&height); + + gdk_window_get_origin (aGdkWindow, + &x, + &y); + + gc = gdk_gc_new(gdk_get_default_root_window()); + + white.pixel = WhitePixel(gdk_display,DefaultScreen(gdk_display)); + + gdk_gc_set_foreground(gc,&white); + gdk_gc_set_function(gc,GDK_XOR); + gdk_gc_set_subwindow(gc,GDK_INCLUDE_INFERIORS); + + gdk_region_offset(aRegion, x, y); + gdk_gc_set_clip_region(gc, aRegion); + + /* + * Need to do this twice so that the XOR effect can replace + * the original window contents. + */ + for (i = 0; i < aTimes * 2; i++) + { + gdk_draw_rectangle(gdk_get_default_root_window(), + gc, + TRUE, + x, + y, + width, + height); + + gdk_flush(); + + PR_Sleep(PR_MillisecondsToInterval(aInterval)); + } + + gdk_gc_destroy(gc); + + gdk_region_offset(aRegion, -x, -y); +} +# endif /* MOZ_X11 */ +# endif // DEBUG +#endif + +#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(r.x, r.y, r.width, r.height)); + LOGDRAW(("\t%f %f %f %f\n", r.x, r.y, r.width, r.height)); + } + + cairo_rectangle_list_destroy(rects); + return true; +} + +#ifdef MOZ_WAYLAND +void nsWindow::MaybeResumeCompositor() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + if (mIsDestroyed || !mNeedsCompositorResume) { + return; + } + + if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) { + MOZ_ASSERT(mCompositorWidgetDelegate); + if (mCompositorWidgetDelegate) { + mCompositorInitiallyPaused = false; + mNeedsCompositorResume = false; + remoteRenderer->SendResumeAsync(); + } + remoteRenderer->SendForcePresent(); + } +} + +void nsWindow::CreateCompositorVsyncDispatcher() { + if (!mWaylandVsyncSource) { + nsBaseWidget::CreateCompositorVsyncDispatcher(); + return; + } + + if (XRE_IsParentProcess()) { + if (!mCompositorVsyncDispatcherLock) { + mCompositorVsyncDispatcherLock = + MakeUnique<Mutex>("mCompositorVsyncDispatcherLock"); + } + MutexAutoLock lock(*mCompositorVsyncDispatcherLock); + if (!mCompositorVsyncDispatcher) { + mCompositorVsyncDispatcher = + new CompositorVsyncDispatcher(mWaylandVsyncSource); + } + } +} +#endif + +gboolean nsWindow::OnExposeEvent(cairo_t* cr) { + // Send any pending resize events so that layout can update. + // May run event loop. + 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 (!mIsX11Display && !moz_container_wayland_can_draw(mContainer)) { + return FALSE; + } +#endif + + nsIWidgetListener* listener = GetListener(); + if (!listener) return FALSE; + + LOGDRAW(("received expose event [%p] %p 0x%lx (rects follow):\n", this, + mGdkWindow, mIsX11Display ? gdk_x11_window_get_xid(mGdkWindow) : 0)); + LayoutDeviceIntRegion exposeRegion; + if (!ExtractExposeRegion(exposeRegion, cr)) { + return FALSE; + } + + gint scale = GdkScaleFactor(); + LayoutDeviceIntRegion region = exposeRegion; + region.ScaleRoundOut(scale, scale); + + if (GetLayerManager()->AsKnowsCompositor() && mCompositorSession) { + // 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. + GetLayerManager()->SetNeedsComposite(true); + GetLayerManager()->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 (GetLayerManager()->AsKnowsCompositor() && + GetLayerManager()->NeedsComposite()) { + GetLayerManager()->ScheduleComposite(); + GetLayerManager()->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 (eTransparencyTransparent == GetTransparencyMode()) { + auto window = static_cast<nsWindow*>(GetTopLevelWidget()); + if (mTransparencyBitmapForTitlebar) { + if (mSizeState == 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 (!shaped) { + GList* children = gdk_window_peek_children(mGdkWindow); + while (children) { + GdkWindow* gdkWin = GDK_WINDOW(children->data); + nsWindow* kid = get_window_for_gdk_window(gdkWin); + if (kid && gdk_window_is_visible(gdkWin)) { + AutoTArray<LayoutDeviceIntRect, 1> clipRects; + kid->GetWindowClipRegion(&clipRects); + LayoutDeviceIntRect bounds = kid->GetBounds(); + for (uint32_t i = 0; i < clipRects.Length(); ++i) { + LayoutDeviceIntRect r = clipRects[i] + bounds.TopLeft(); + region.Sub(region, r); + } + } + children = children->next; + } + } + + if (region.IsEmpty()) { + return TRUE; + } + + // If this widget uses OMTC... + if (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT || + GetLayerManager()->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; + } + RefPtr<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 = gfxContext::CreatePreservingTransformOrNull(destDT); + } else { + gfxUtils::ClipToRegion(dt, region.ToUnknownRegion()); + ctx = gfxContext::CreatePreservingTransformOrNull(dt); + } + MOZ_ASSERT(ctx); // checked both dt and destDT valid draw target above + +# 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 (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC) { + if (GetTransparencyMode() == eTransparencyTransparent && + 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, 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 = nullptr; + 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. + + LOG(("configure event [%p] %d %d %d %d\n", (void*)this, aEvent->x, aEvent->y, + aEvent->width, aEvent->height)); + + if (mPendingConfigures > 0) { + mPendingConfigures--; + } + + LayoutDeviceIntRect screenBounds = GetScreenBounds(); + + if (mWindowType == eWindowType_toplevel || + mWindowType == eWindowType_dialog) { + // This check avoids unwanted rollup on spurious configure events from + // Cygwin/X (bug 672103). + if (mBounds.x != screenBounds.x || mBounds.y != screenBounds.y) { + CheckForRollup(0, 0, false, true); + } + } + + NS_ASSERTION(GTK_IS_WINDOW(aWidget), + "Configure event on widget that is not a GtkWindow"); + if (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. + GetLayerManager()->FlushRendering(); + return FALSE; + } + + mBounds.MoveTo(screenBounds.TopLeft()); + + // XXX mozilla will invalidate the entire window after this move + // complete. wtf? + NotifyWindowMoved(mBounds.x, mBounds.y); + + // A GTK app would usually update its client area size in response to + // a "size-allocate" signal. + // However, we need to set mBounds in advance at Resize() + // as JS code expects immediate window size change. + // If Gecko requests a resize from GTK, but subsequently, + // before a corresponding "size-allocate" signal is emitted, the window is + // resized to its former size via other means, such as maximizing, + // then there is no "size-allocate" signal from which to update + // the value of mBounds. Similarly, if Gecko's resize request is refused + // by the window manager, then there will be no "size-allocate" signal. + // In the refused request case, the window manager is required to dispatch + // a ConfigureNotify event. mBounds can then be updated here. + // This seems to also be sufficient to update mBounds when Gecko resizes + // the window from maximized size and then immediately maximizes again. + if (!mBoundsAreValid) { + GtkAllocation allocation = {-1, -1, 0, 0}; + gtk_window_get_size(GTK_WINDOW(mShell), &allocation.width, + &allocation.height); + OnSizeAllocate(&allocation); + } + + return FALSE; +} + +void nsWindow::OnContainerUnrealize() { + // 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. + + if (mGdkWindow) { + DestroyChildWindows(); + + g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", nullptr); + mGdkWindow = nullptr; + } +} + +void nsWindow::OnSizeAllocate(GtkAllocation* aAllocation) { + LOG(("nsWindow::OnSizeAllocate [%p] %d,%d -> %d x %d\n", (void*)this, + 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. + if (mCSDSupportLevel == CSD_SUPPORT_CLIENT) { + if (!mIsX11Display || (mIsX11Display && mDrawInTitlebar)) { + UpdateClientOffsetFromCSDWindow(); + } + } + + mBoundsAreValid = true; + + LayoutDeviceIntSize size = GdkRectToDevicePixels(*aAllocation).Size(); + if (mBounds.Size() == size) { + LOG((" Already the same size")); + // We were already resized at nsWindow::OnConfigureEvent() so skip it. + 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 (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); + } + + mBounds.SizeTo(size); + +#ifdef MOZ_X11 + // Notify the GtkCompositorWidget of a ClientSizeChange + if (mCompositorWidgetDelegate) { + mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize()); + } +#endif + + // 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. + mNeedsDispatchResized = true; + NS_DispatchToCurrentThread(NewRunnableMethod( + "nsWindow::MaybeDispatchResized", this, &nsWindow::MaybeDispatchResized)); +} + +void nsWindow::OnDeleteEvent() { + if (mWidgetListener) mWidgetListener->RequestWindowClose(this); +} + +void nsWindow::OnEnterNotifyEvent(GdkEventCrossing* aEvent) { + // 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 != nullptr) return; + + // Check before is_parent_ungrab_enter() as the button state may have + // changed while a non-Gecko ancestor window had a pointer grab. + DispatchMissedButtonReleases(aEvent); + + if (is_parent_ungrab_enter(aEvent)) return; + + WidgetMouseEvent event(true, eMouseEnterIntoWidget, this, + WidgetMouseEvent::eReal); + + event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y); + event.AssignEventTime(GetWidgetEventTime(aEvent->time)); + + LOG(("OnEnterNotify: %p\n", (void*)this)); + + DispatchInputEvent(&event); +} + +// XXX Is this the right test for embedding cases? +static bool is_top_level_mouse_exit(GdkWindow* aWindow, + GdkEventCrossing* aEvent) { + auto x = gint(aEvent->x_root); + auto y = gint(aEvent->y_root); + GdkDisplay* display = gdk_window_get_display(aWindow); + GdkWindow* winAtPt = gdk_display_get_window_at_pointer(display, &x, &y); + if (!winAtPt) return true; + GdkWindow* topLevelAtPt = gdk_window_get_toplevel(winAtPt); + GdkWindow* topLevelWidget = gdk_window_get_toplevel(aWindow); + return topLevelAtPt != topLevelWidget; +} + +void nsWindow::OnLeaveNotifyEvent(GdkEventCrossing* aEvent) { + // 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 != nullptr) return; + + WidgetMouseEvent event(true, eMouseExitFromWidget, this, + WidgetMouseEvent::eReal); + + event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y); + event.AssignEventTime(GetWidgetEventTime(aEvent->time)); + + event.mExitFrom = Some(is_top_level_mouse_exit(mGdkWindow, aEvent) + ? WidgetMouseEvent::ePlatformTopLevel + : WidgetMouseEvent::ePlatformChild); + + LOG(("OnLeaveNotify: %p\n", (void*)this)); + + DispatchInputEvent(&event); +} + +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::OnMotionNotifyEvent(GdkEventMotion* aEvent) { + 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; + if (mIsX11Display) { + // 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; + } + } + + if (canDrag) { + gdk_window_begin_move_drag(gdk_window, 1, aEvent->x_root, aEvent->y_root, + aEvent->time); + return; + } + } + + // see if we can compress this event + // XXXldb Why skip every other motion event when we have multiple, + // but not more than that? + bool synthEvent = false; +#ifdef MOZ_X11 + XEvent xevent; + + if (mIsX11Display) { + while (XPending(GDK_WINDOW_XDISPLAY(aEvent->window))) { + XEvent peeked; + XPeekEvent(GDK_WINDOW_XDISPLAY(aEvent->window), &peeked); + if (peeked.xany.window != gdk_x11_window_get_xid(aEvent->window) || + peeked.type != MotionNotify) + break; + + synthEvent = true; + XNextEvent(GDK_WINDOW_XDISPLAY(aEvent->window), &xevent); + } + } +#endif /* MOZ_X11 */ + + 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; + + guint modifierState; + if (synthEvent) { +#ifdef MOZ_X11 + event.mRefPoint.x = nscoord(xevent.xmotion.x); + event.mRefPoint.y = nscoord(xevent.xmotion.y); + + modifierState = xevent.xmotion.state; + + event.AssignEventTime(GetWidgetEventTime(xevent.xmotion.time)); +#else + event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y); + + modifierState = aEvent->state; + + event.AssignEventTime(GetWidgetEventTime(aEvent->time)); +#endif /* MOZ_X11 */ + } else { + event.mRefPoint = GetRefPoint(this, aEvent); + + modifierState = aEvent->state; + + event.AssignEventTime(GetWidgetEventTime(aEvent->time)); + } + + KeymapWrapper::InitInputEvent(event, modifierState); + + 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 on %p\n", guint(buttonType + 1), + (void*)this)); + + // 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) { + aEvent.mRefPoint = GetRefPoint(this, aGdkEvent); + + 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) { + if (domButton == MouseButton::eSecondary && MOZ_LIKELY(!mIsDestroyed)) { + WidgetMouseEvent contextMenuEvent(true, eContextMenu, this, + WidgetMouseEvent::eReal); + InitButtonEvent(contextMenuEvent, aEvent); + contextMenuEvent.mPressure = mLastMotionPressure; + DispatchInputEvent(&contextMenuEvent); + } +} + +void nsWindow::OnButtonPressEvent(GdkEventButton* aEvent) { + LOG(("Button %u press on %p\n", aEvent->button, (void*)this)); + + // 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. + GdkEvent* peekedEvent = gdk_event_peek(); + if (peekedEvent) { + GdkEventType type = peekedEvent->any.type; + gdk_event_free(peekedEvent); + if (type == GDK_2BUTTON_PRESS || type == GDK_3BUTTON_PRESS) return; + } + + nsWindow* containerWindow = GetContainerWindow(); + if (!gFocusWindow && containerWindow) { + containerWindow->DispatchActivateEvent(); + } + + // check to see if we should rollup + if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false)) 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 to back/forward + case 8: + if (!Preferences::GetBool("mousebutton.4th.enabled", true)) { + return; + } + DispatchCommandEvent(nsGkAtoms::Back); + return; + case 9: + 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); + event.mPressure = mLastMotionPressure; + + nsEventStatus eventStatus = DispatchInputEvent(&event); + + LayoutDeviceIntPoint refPoint = + GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y); + if (mDraggableRegion.Contains(refPoint.x, refPoint.y) && + domButton == MouseButton::ePrimary && + eventStatus != nsEventStatus_eConsumeNoDefault) { + mWindowShouldStartDragging = true; + } + + // right menu click on linux should also pop up a context menu + if (!StaticPrefs::ui_context_menus_after_mouseup()) { + DispatchContextMenuEventFromMouseEvent(domButton, aEvent); + } +} + +void nsWindow::OnButtonReleaseEvent(GdkEventButton* aEvent) { + LOG(("Button %u release on %p\n", aEvent->button, (void*)this)); + + 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); + + WidgetMouseEvent event(true, eMouseUp, this, WidgetMouseEvent::eReal); + event.mButton = domButton; + InitButtonEvent(event, aEvent); + gdouble pressure = 0; + gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure); + event.mPressure = pressure ? pressure : mLastMotionPressure; + + // The mRefPoint is manipulated in DispatchInputEvent, we're saving it + // to use it for the doubleclick position check. + LayoutDeviceIntPoint pos = event.mRefPoint; + + nsEventStatus eventStatus = DispatchInputEvent(&event); + + bool defaultPrevented = (eventStatus == 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.x, pos.y)) { + if (mSizeState == nsSizeMode_Maximized) { + SetSizeMode(nsSizeMode_Normal); + } else { + SetSizeMode(nsSizeMode_Maximized); + } + } + mLastMotionPressure = pressure; + + // right menu click on linux should also pop up a context menu + if (StaticPrefs::ui_context_menus_after_mouseup()) { + DispatchContextMenuEventFromMouseEvent(domButton, aEvent); + } + + // Open window manager menu on PIP window to allow user + // to place it on top / all workspaces. + if (mIsPIPWindow && aEvent->button == 3) { + static auto sGdkWindowShowWindowMenu = + (gboolean(*)(GdkWindow * window, GdkEvent*)) + dlsym(RTLD_DEFAULT, "gdk_window_show_window_menu"); + if (sGdkWindowShowWindowMenu) { + sGdkWindowShowWindowMenu(mGdkWindow, (GdkEvent*)aEvent); + } + } +} + +void nsWindow::OnContainerFocusInEvent(GdkEventFocus* aEvent) { + LOGFOCUS(("OnContainerFocusInEvent [%p]\n", (void*)this)); + + // 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) { + LOGFOCUS(("activated notification is blocked [%p]\n", (void*)this)); + 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; + } + + LOGFOCUS(("Events sent from focus in event [%p]\n", (void*)this)); +} + +void nsWindow::OnContainerFocusOutEvent(GdkEventFocus* aEvent) { + LOGFOCUS(("OnContainerFocusOutEvent [%p]\n", (void*)this)); + + if (mWindowType == eWindowType_toplevel || + mWindowType == eWindowType_dialog) { + nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID); + nsCOMPtr<nsIDragSession> dragSession; + dragService->GetCurrentSession(getter_AddRefs(dragSession)); + + // Rollup popups 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. + bool shouldRollup = !dragSession; + if (!shouldRollup) { + // we also roll up when a drag is from a different application + nsCOMPtr<nsINode> sourceNode; + dragSession->GetSourceNode(getter_AddRefs(sourceNode)); + shouldRollup = (sourceNode == nullptr); + } + + if (shouldRollup) { + CheckForRollup(0, 0, false, true); + } + } + + 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(); + } + + LOGFOCUS(("Done with container focus out [%p]\n", (void*)this)); +} + +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(aEventTime, 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 (!mIsX11Display) { + // 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 { + CurrentX11TimeGetter* getCurrentTime = GetCurrentTimeGetter(); + MOZ_ASSERT(getCurrentTime, + "Null current time getter despite having a window"); + eventTimeStamp = + TimeConverter().GetTimeStampFromSystemTime(aEventTime, *getCurrentTime); + } + return eventTimeStamp; +} + +mozilla::CurrentX11TimeGetter* nsWindow::GetCurrentTimeGetter() { + MOZ_ASSERT(mGdkWindow, "Expected mGdkWindow to be set"); + if (MOZ_UNLIKELY(!mCurrentTimeGetter)) { + mCurrentTimeGetter = MakeUnique<CurrentX11TimeGetter>(mGdkWindow); + } + return mCurrentTimeGetter.get(); +} + +gboolean nsWindow::OnKeyPressEvent(GdkEventKey* aEvent) { + LOGFOCUS(("OnKeyPressEvent [%p]\n", (void*)this)); + + RefPtr<nsWindow> self(this); + KeymapWrapper::HandleKeyPressEvent(self, aEvent); + return TRUE; +} + +gboolean nsWindow::OnKeyReleaseEvent(GdkEventKey* aEvent) { + LOGFOCUS(("OnKeyReleaseEvent [%p]\n", (void*)this)); + + RefPtr<nsWindow> self(this); + if (NS_WARN_IF(!KeymapWrapper::HandleKeyReleaseEvent(self, 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) { + if (StaticPrefs::apz_gtk_kinetic_scroll_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)); + PanGestureInput::PanGestureType eventType = + PanGestureInput::PANGESTURE_PAN; + if (sGdkEventIsScrollStopEvent((GdkEvent*)aEvent)) { + eventType = PanGestureInput::PANGESTURE_END; + mPanInProgress = false; + } else if (!mPanInProgress) { + eventType = PanGestureInput::PANGESTURE_START; + mPanInProgress = true; + } + + LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent); + PanGestureInput panEvent( + eventType, aEvent->time, GetEventTimeStamp(aEvent->time), + ScreenPoint(touchPoint.x, touchPoint.y), + ScreenPoint(aEvent->delta_x, aEvent->delta_y), + KeymapWrapper::ComputeKeyModifiers(aEvent->state)); + panEvent.mDeltaType = PanGestureInput::PANDELTA_PAGE; + panEvent.mSimulateMomentum = true; + + DispatchPanGestureInput(panEvent); + + return; + } + + // Older GTK doesn't support stop events, so we can't support fling + wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY; + } + + // 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.mIsNoLineOrPageDelta = true; + + break; + } + case GDK_SCROLL_UP: + wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = -3; + break; + case GDK_SCROLL_DOWN: + wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = 3; + break; + case GDK_SCROLL_LEFT: + wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = -1; + break; + case GDK_SCROLL_RIGHT: + wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = 1; + break; + } + + wheelEvent.mRefPoint = GetRefPoint(this, aEvent); + + KeymapWrapper::InitInputEvent(wheelEvent, aEvent->state); + + wheelEvent.AssignEventTime(GetWidgetEventTime(aEvent->time)); + + DispatchInputEvent(&wheelEvent); +} + +void nsWindow::OnWindowStateEvent(GtkWidget* aWidget, + GdkEventWindowState* aEvent) { + LOG( + ("nsWindow::OnWindowStateEvent [%p] for %p changed 0x%x new_window_state " + "0x%x\n", + (void*)this, 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)); + if (mHasMappedToplevel != mapped) { + 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 some DE still have this issue + // (Bug 1624199) so let's remove it for Wayland only. + if (mIsX11Display) { + 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 + // mSizeState and obtaining a focus. + bool waylandWasIconified = + (!mIsX11Display && aEvent->changed_mask & GDK_WINDOW_STATE_FOCUSED && + aEvent->new_window_state & GDK_WINDOW_STATE_FOCUSED && + mSizeState == 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; + } + + if (aEvent->new_window_state & GDK_WINDOW_STATE_ICONIFIED) { + LOG(("\tIconified\n")); + mSizeState = nsSizeMode_Minimized; +#ifdef ACCESSIBILITY + DispatchMinimizeEventAccessible(); +#endif // ACCESSIBILITY + } else if (aEvent->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) { + LOG(("\tFullscreen\n")); + mSizeState = nsSizeMode_Fullscreen; + } else if (aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) { + LOG(("\tMaximized\n")); + mSizeState = nsSizeMode_Maximized; +#ifdef ACCESSIBILITY + DispatchMaximizeEventAccessible(); +#endif // ACCESSIBILITY + } else { + LOG(("\tNormal\n")); + mSizeState = nsSizeMode_Normal; +#ifdef ACCESSIBILITY + DispatchRestoreEventAccessible(); +#endif // ACCESSIBILITY + } + + if (aEvent->new_window_state & GDK_WINDOW_STATE_TILED) { + LOG(("\tTiled\n")); + mIsTiled = true; + } else { + LOG(("\tNot tiled\n")); + mIsTiled = false; + } + + if (mWidgetListener) { + mWidgetListener->SizeModeChanged(mSizeState); + if (aEvent->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) { + mWidgetListener->FullscreenChanged(aEvent->new_window_state & + GDK_WINDOW_STATE_FULLSCREEN); + } + } + + if (mDrawInTitlebar && mTransparencyBitmapForTitlebar) { + if (mSizeState == nsSizeMode_Normal && !mIsTiled) { + UpdateTitlebarTransparencyBitmap(); + } else { + ClearTransparencyBitmap(); + } + } +} + +void nsWindow::ThemeChanged() { + // Everything could've changed. + NotifyThemeChanged(ThemeChangeKind::StyleAndLayout); + + if (!mGdkWindow || MOZ_UNLIKELY(mIsDestroyed)) return; + + // Dispatch theme change notification to all child windows + GList* children = gdk_window_peek_children(mGdkWindow); + while (children) { + GdkWindow* gdkWin = GDK_WINDOW(children->data); + + auto* win = (nsWindow*)g_object_get_data(G_OBJECT(gdkWin), "nsWindow"); + + if (win && win != this) { // guard against infinite recursion + RefPtr<nsWindow> kungFuDeathGrip = win; + win->ThemeChanged(); + } + + children = children->next; + } + + IMContextWrapper::OnThemeChanged(); +} + +void nsWindow::OnDPIChanged() { + if (mWidgetListener) { + if (PresShell* presShell = mWidgetListener->GetPresShell()) { + presShell->BackingScaleFactorChanged(); + // Update menu's font size etc. + // This affects style / layout because it affects system font sizes. + presShell->ThemeChanged(ThemeChangeKind::StyleAndLayout); + } + 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(GtkAllocation* aAllocation) { + LOG(("nsWindow::OnScaleChanged [%p] %d,%d -> %d x %d\n", (void*)this, + aAllocation->x, aAllocation->y, aAllocation->width, + aAllocation->height)); + + // Force scale factor recalculation + mWindowScaleFactorChanged = true; + + // This eventually propagate new scale to the PuppetWidgets + OnDPIChanged(); + + // configure_event is already fired before scale-factor signal, + // but size-allocate isn't fired by changing scale + OnSizeAllocate(aAllocation); + + // Client offset are updated by _NET_FRAME_EXTENTS on X11 when system titlebar + // is enabled. In ither 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. + if (mCSDSupportLevel == CSD_SUPPORT_CLIENT) { + if (!mIsX11Display || (mIsX11Display && mDrawInTitlebar)) { + UpdateClientOffsetFromCSDWindow(); + } + } + +#ifdef MOZ_WAYLAND + // We need to update scale when scale of egl window is changed. + if (mContainer && moz_container_wayland_has_egl_window(mContainer)) { + moz_container_wayland_set_scale_factor(mContainer); + } +#endif +} + +void nsWindow::DispatchDragEvent(EventMessage aMsg, + const LayoutDeviceIntPoint& aRefPoint, + guint aTime) { + 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(%p)\n", (void*)this)); + + RefPtr<nsDragService> dragService = nsDragService::GetInstance(); + dragService->TargetDataReceived(aWidget, aDragContext, aX, aY, aSelectionData, + aInfo, aTime); +} + +nsWindow* nsWindow::GetTransientForWindowIfPopup() { + if (mWindowType != eWindowType_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()) { + PinchGestureInput::PinchGestureType pinchGestureType = + PinchGestureInput::PINCHGESTURE_SCALE; + ScreenCoord CurrentSpan; + ScreenCoord PreviousSpan; + + switch (aEvent->phase) { + case GDK_TOUCHPAD_GESTURE_PHASE_BEGIN: + pinchGestureType = PinchGestureInput::PINCHGESTURE_START; + CurrentSpan = aEvent->scale; + + // 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; + mLastPinchEventSpan = aEvent->scale; + break; + + case GDK_TOUCHPAD_GESTURE_PHASE_UPDATE: + pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE; + if (aEvent->scale == mLastPinchEventSpan) { + return FALSE; + } + CurrentSpan = aEvent->scale; + PreviousSpan = mLastPinchEventSpan; + mLastPinchEventSpan = aEvent->scale; + break; + + case GDK_TOUCHPAD_GESTURE_PHASE_END: + pinchGestureType = PinchGestureInput::PINCHGESTURE_END; + CurrentSpan = aEvent->scale; + PreviousSpan = mLastPinchEventSpan; + break; + + default: + return FALSE; + } + + LayoutDeviceIntPoint touchpadPoint = GetRefPoint(this, aEvent); + PinchGestureInput event( + pinchGestureType, PinchGestureInput::TRACKPAD, aEvent->time, + GetEventTimeStamp(aEvent->time), ExternalPoint(0, 0), + ScreenPoint(touchpadPoint.x, touchpadPoint.y), CurrentSpan, + PreviousSpan, KeymapWrapper::ComputeKeyModifiers(aEvent->state)); + + DispatchPinchGestureInput(event); + } + return TRUE; +} + +gboolean nsWindow::OnTouchEvent(GdkEventTouch* aEvent) { + 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: + msg = eTouchStart; + break; + case GDK_TOUCH_UPDATE: + msg = eTouchMove; + break; + case GDK_TOUCH_END: + msg = eTouchEnd; + break; + case GDK_TOUCH_CANCEL: + msg = eTouchCancel; + break; + default: + return FALSE; + } + + 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); + event.mTime = aEvent->time; + + if (aEvent->type == GDK_TOUCH_BEGIN || aEvent->type == GDK_TOUCH_UPDATE) { + mTouches.Put(aEvent->sequence, std::move(touch)); + // add all touch points to event object + for (auto iter = mTouches.Iter(); !iter.Done(); iter.Next()) { + event.mTouches.AppendElement(new dom::Touch(*iter.UserData())); + } + } else if (aEvent->type == GDK_TOUCH_END || + aEvent->type == GDK_TOUCH_CANCEL) { + *event.mTouches.AppendElement() = std::move(touch); + } + + DispatchInputEvent(&event); + 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 = GetSystemCSDSupportLevel() != CSD_SUPPORT_NONE; + } + } + transparencyConfigured = true; + } + + return sTransparentMainWindow; +} + +static GdkWindow* CreateGdkWindow(GdkWindow* parent, GtkWidget* widget) { + GdkWindowAttr attributes; + gint attributes_mask = GDK_WA_VISUAL; + + attributes.event_mask = kEvents; + + attributes.width = 1; + attributes.height = 1; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual(widget); + attributes.window_type = GDK_WINDOW_CHILD; + + GdkWindow* window = gdk_window_new(parent, &attributes, attributes_mask); + gdk_window_set_user_data(window, widget); + + return window; +} + +// Configure GL visual on X11. We add alpha silently +// if we use WebRender to workaround NVIDIA specific Bug 1663273. +bool nsWindow::ConfigureX11GLVisual(bool aUseAlpha) { + if (!mIsX11Display) { + return false; + } + + // If using WebRender on X11, we need to select a visual with a depth + // buffer, as well as an alpha channel if transparency is requested. This + // must be done before the widget is realized. + bool useWebRender = gfx::gfxVars::UseWebRender(); + auto* screen = gtk_widget_get_screen(mShell); + int visualId = 0; + bool haveVisual; + + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1663003 + // We need to use GLX to get visual even on EGL until + // EGL can provide compositable visual: + // https://gitlab.freedesktop.org/mesa/mesa/-/issues/149 + if ((true /* !gfx::gfxVars::UseEGL() */)) { + auto* display = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(mShell)); + int screenNumber = GDK_SCREEN_XNUMBER(screen); + haveVisual = GLContextGLX::FindVisual(display, screenNumber, useWebRender, + aUseAlpha || useWebRender, &visualId); + } else { + haveVisual = GLContextEGL::FindVisual(useWebRender, + aUseAlpha || useWebRender, &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!"); + if (aUseAlpha || useWebRender) { + // We try to use a fallback alpha visual + GdkScreen* screen = gtk_widget_get_screen(mShell); + gdkVisual = gdk_screen_get_rgba_visual(screen); + } + } + if (gdkVisual) { + // TODO: We use alpha visual even on non-compositing screens (Bug 1479135). + gtk_widget_set_visual(mShell, gdkVisual); + mHasAlphaVisual = aUseAlpha; + } + + return true; +} + +nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent, + const LayoutDeviceIntRect& aRect, + nsWidgetInitData* aInitData) { +#ifdef MOZ_LOGGING + if (this->GetFrame() && this->GetFrame()->GetContent()) { + nsCString nodeName = + NS_ConvertUTF16toUTF8(this->GetFrame()->GetContent()->NodeName()); + LOG(("nsWindow::Create: creating [%p]: nodename %s\n", this, + nodeName.get())); + } +#endif + // only set the base parent if we're going to be a dialog or a + // toplevel + nsIWidget* baseParent = + aInitData && (aInitData->mWindowType == eWindowType_dialog || + aInitData->mWindowType == eWindowType_toplevel || + aInitData->mWindowType == eWindowType_invisible) + ? nullptr + : aParent; + +#ifdef ACCESSIBILITY + // Send a DBus message to check whether a11y is enabled + a11y::PreInit(); +#endif + + // Ensure that the toolkit is created. + nsGTKToolkit::GetToolkit(); + + // initialize all the common bits of this class + BaseCreate(baseParent, aInitData); + + // Do we need to listen for resizes? + bool listenForResizes = false; + ; + if (aNativeParent || (aInitData && aInitData->mListenForResizes)) + listenForResizes = true; + + // and do our common creation + CommonCreate(aParent, listenForResizes); + + // save our bounds + mBounds = aRect; + LOG((" mBounds: x:%d y:%d w:%d h:%d\n", mBounds.x, mBounds.y, mBounds.width, + mBounds.height)); + + mPreferredPopupRectFlushed = false; + + ConstrainSize(&mBounds.width, &mBounds.height); + + GtkWidget* eventWidget = nullptr; + bool popupNeedsAlphaVisual = (mWindowType == eWindowType_popup && + (aInitData && aInitData->mSupportTranslucency)); + + // Figure out our parent window - only used for eWindowType_child + GtkWidget* parentMozContainer = nullptr; + GtkContainer* parentGtkContainer = nullptr; + GdkWindow* parentGdkWindow = nullptr; + nsWindow* parentnsWindow = nullptr; + + if (aParent) { + parentnsWindow = static_cast<nsWindow*>(aParent); + parentGdkWindow = parentnsWindow->mGdkWindow; + } else if (aNativeParent && GDK_IS_WINDOW(aNativeParent)) { + parentGdkWindow = GDK_WINDOW(aNativeParent); + parentnsWindow = get_window_for_gdk_window(parentGdkWindow); + if (!parentnsWindow) return NS_ERROR_FAILURE; + + } else if (aNativeParent && GTK_IS_CONTAINER(aNativeParent)) { + parentGtkContainer = GTK_CONTAINER(aNativeParent); + } + + if (parentGdkWindow) { + // get the widget for the window - it should be a moz container + parentMozContainer = parentnsWindow->GetMozContainerWidget(); + if (!parentMozContainer) return NS_ERROR_FAILURE; + } + // ^^ only used for eWindowType_child + + if (!mIsX11Display) { + if (mWindowType == eWindowType_child) { + // eWindowType_child is not supported on Wayland. Just switch to toplevel + // as a workaround. + mWindowType = eWindowType_toplevel; + } else if (mWindowType == eWindowType_popup && !aNativeParent && !aParent) { + // Workaround for Wayland where the popup windows always need to have + // parent window. For example webrtc ui is a popup window without parent. + mWindowType = eWindowType_toplevel; + } + } + + mAlwaysOnTop = aInitData && aInitData->mAlwaysOnTop; + mIsPIPWindow = aInitData && aInitData->mPIPWindow; + + // ok, create our windows + switch (mWindowType) { + case eWindowType_dialog: + case eWindowType_popup: + case eWindowType_toplevel: + case eWindowType_invisible: { + mIsTopLevel = true; + + // 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 == eWindowType_popup) { + type = (mIsX11Display && aInitData->mNoAutoHide) ? GTK_WINDOW_TOPLEVEL + : GTK_WINDOW_POPUP; + } + mShell = gtk_window_new(type); + + // Ensure gfxPlatform is initialized, since that is what initializes + // gfxVars, used below. + Unused << gfxPlatform::GetPlatform(); + + if (mWindowType == eWindowType_toplevel || + mWindowType == eWindowType_dialog) { + bool isPopup = mIsPIPWindow || mWindowType == eWindowType_dialog; + mCSDSupportLevel = GetSystemCSDSupportLevel(isPopup); + } + + // Don't use transparency for PictureInPicture windows. + bool toplevelNeedsAlphaVisual = false; + if (mWindowType == eWindowType_toplevel && !mIsPIPWindow) { + toplevelNeedsAlphaVisual = IsToplevelWindowTransparent(); + } + + bool isGLVisualSet = false; + bool isAccelerated = ComputeShouldAccelerate(); +#ifdef MOZ_X11 + if (isAccelerated) { + isGLVisualSet = ConfigureX11GLVisual(popupNeedsAlphaVisual || + toplevelNeedsAlphaVisual); + } +#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 eTransparencyTransparent + // from layout, so set mIsTransparent here. + if (mWindowType == eWindowType_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. + NativeResize(); + + if (mWindowType == eWindowType_dialog) { + mGtkWindowRoleName = "Dialog"; + + SetDefaultIcon(); + gtk_window_set_type_hint(GTK_WINDOW(mShell), + GDK_WINDOW_TYPE_HINT_DIALOG); + + if (parentnsWindow) { + gtk_window_set_transient_for( + GTK_WINDOW(mShell), GTK_WINDOW(parentnsWindow->GetGtkWidget())); + LOG(( + "nsWindow::Create(): dialog [%p], parent window %p [GdkWindow]\n", + this, aNativeParent)); + } + + } else if (mWindowType == eWindowType_popup) { + mGtkWindowRoleName = "Popup"; + + if (aInitData->mNoAutoHide) { + // ... but the window manager does not decorate this window, + // nor provide a separate taskbar icon. + if (mBorderStyle == eBorderStyle_default) { + gtk_window_set_decorated(GTK_WINDOW(mShell), FALSE); + } else { + bool decorate = mBorderStyle & eBorderStyle_title; + gtk_window_set_decorated(GTK_WINDOW(mShell), decorate); + if (decorate) { + gtk_window_set_deletable(GTK_WINDOW(mShell), + mBorderStyle & eBorderStyle_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 (mIsX11Display) { + gtk_widget_realize(mShell); + gdk_window_add_filter(gtk_widget_get_window(mShell), + popup_take_focus_filter, nullptr); + } +#endif + } + + GdkWindowTypeHint gtkTypeHint; + if (aInitData->mIsDragPopup) { + gtkTypeHint = GDK_WINDOW_TYPE_HINT_DND; + mIsDragPopup = true; + } else { + switch (aInitData->mPopupHint) { + case ePopupTypeMenu: + gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU; + break; + case ePopupTypeTooltip: + gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP; + break; + default: + gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY; + break; + } + } + gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint); + if (parentnsWindow) { + LOG(("nsWindow::Create() [%p]: parent window for popup: %p\n", this, + parentnsWindow)); + gtk_window_set_transient_for( + GTK_WINDOW(mShell), GTK_WINDOW(parentnsWindow->GetGtkWidget())); + } + + // We need realized mShell at NativeMove(). + gtk_widget_realize(mShell); + + // With popup windows, we want to control their position, so don't + // wait for the window manager to place them (which wouldn't + // happen with override-redirect windows anyway). + NativeMove(); + } else { // must be eWindowType_toplevel + mGtkWindowRoleName = "Toplevel"; + SetDefaultIcon(); + + if (mIsPIPWindow) { + gtk_window_set_type_hint(GTK_WINDOW(mShell), + GDK_WINDOW_TYPE_HINT_UTILITY); + } + + // 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); + } + + // Create a container to hold child windows and child GtkWidgets. + GtkWidget* container = moz_container_new(); + mContainer = MOZ_CONTAINER(container); +#ifdef MOZ_WAYLAND + if (!mIsX11Display && isAccelerated) { + mCompositorInitiallyPaused = true; + RefPtr<nsWindow> self(this); + moz_container_wayland_add_initial_draw_callback( + mContainer, [self]() -> void { + self->mNeedsCompositorResume = true; + self->MaybeResumeCompositor(); + }); + } +#endif + + // "csd" style is set when widget is realized so we need to call + // it explicitly now. + gtk_widget_realize(mShell); + + /* There are several cases here: + * + * 1) We're running on Gtk+ without client side decorations. + * Content is rendered to mShell window and we listen + * to the Gtk+ events on mShell + * 2) We're running on Gtk+ and client side decorations + * are drawn by Gtk+ to mShell. Content is rendered to mContainer + * and we listen to the Gtk+ events on mContainer. + * 3) We're running on Wayland. All gecko content is rendered + * to mContainer and we listen to the Gtk+ events on mContainer. + */ + GtkStyleContext* style = gtk_widget_get_style_context(mShell); + mDrawToContainer = !mIsX11Display || + (mCSDSupportLevel == CSD_SUPPORT_CLIENT) || + gtk_style_context_has_class(style, "csd"); + eventWidget = (mDrawToContainer) ? container : mShell; + + // Prevent GtkWindow from painting a background to avoid flickering. + gtk_widget_set_app_paintable(eventWidget, TRUE); + + gtk_widget_add_events(eventWidget, kEvents); + if (mDrawToContainer) { + gtk_widget_add_events(mShell, GDK_PROPERTY_CHANGE_MASK); + gtk_widget_set_app_paintable(mShell, TRUE); + } + 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, mDrawToContainer); + + 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); + } + + // the drawing window + mGdkWindow = gtk_widget_get_window(eventWidget); + + if (mWindowType == eWindowType_popup) { + // gdk does not automatically set the cursor for "temporary" + // windows, which are what gtk uses for popups. + + mCursor = eCursor_wait; // force SetCursor to actually set the + // cursor, even though our internal state + // indicates that we already have the + // standard cursor. + SetCursor(eCursor_standard, nullptr, 0, 0); + + if (aInitData->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. + SetWindowMouseTransparent(aInitData->mMouseTransparent); + } + } break; + + case eWindowType_plugin: + case eWindowType_plugin_ipc_chrome: + case eWindowType_plugin_ipc_content: + MOZ_ASSERT_UNREACHABLE("Unexpected eWindowType_plugin*"); + return NS_ERROR_FAILURE; + + case eWindowType_child: { + if (parentMozContainer) { + mGdkWindow = CreateGdkWindow(parentGdkWindow, parentMozContainer); + mHasMappedToplevel = parentnsWindow->mHasMappedToplevel; + } else if (parentGtkContainer) { + // This MozContainer has its own window for drawing and receives + // events because there is no mShell widget (corresponding to this + // nsWindow). + GtkWidget* container = moz_container_new(); + mContainer = MOZ_CONTAINER(container); + eventWidget = container; + gtk_widget_add_events(eventWidget, kEvents); + gtk_container_add(parentGtkContainer, container); + gtk_widget_realize(container); + + mGdkWindow = gtk_widget_get_window(container); + } else { + NS_WARNING( + "Warning: tried to create a new child widget with no parent!"); + return NS_ERROR_FAILURE; + } + } break; + default: + break; + } + + // label the drawing window with this object so we can find our way home + g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", this); + if (mDrawToContainer) { + // Also label mShell toplevel window, + // property_notify_event_cb callback also needs to find its way home + g_object_set_data(G_OBJECT(gtk_widget_get_window(mShell)), "nsWindow", + this); + } + + if (mContainer) g_object_set_data(G_OBJECT(mContainer), "nsWindow", this); + + if (mShell) g_object_set_data(G_OBJECT(mShell), "nsWindow", this); + + // attach listeners for events + if (mShell) { + 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, "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 == eWindowType_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), + 0)) { + g_signal_connect(screen, "composited-changed", + G_CALLBACK(screen_composited_changed_cb), nullptr); + } + + GtkSettings* default_settings = gtk_settings_get_default(); + g_signal_connect_after(default_settings, "notify::gtk-theme-name", + G_CALLBACK(settings_changed_cb), this); + g_signal_connect_after(default_settings, "notify::gtk-font-name", + G_CALLBACK(settings_changed_cb), this); + g_signal_connect_after(default_settings, "notify::gtk-enable-animations", + G_CALLBACK(settings_changed_cb), this); + g_signal_connect_after(default_settings, "notify::gtk-decoration-layout", + G_CALLBACK(settings_changed_cb), this); + g_signal_connect_after(default_settings, "notify::gtk-xft-dpi", + G_CALLBACK(settings_xft_dpi_changed_cb), this); + // For remote LookAndFeel, to refresh the content processes' copies: + g_signal_connect_after(default_settings, "notify::gtk-cursor-blink-time", + G_CALLBACK(settings_changed_cb), this); + g_signal_connect_after(default_settings, "notify::gtk-cursor-blink", + G_CALLBACK(settings_changed_cb), this); + g_signal_connect_after(default_settings, + "notify::gtk-entry-select-on-focus", + G_CALLBACK(settings_changed_cb), this); + g_signal_connect_after(default_settings, + "notify::gtk-primary-button-warps-slider", + G_CALLBACK(settings_changed_cb), this); + g_signal_connect_after(default_settings, "notify::gtk-menu-popup-delay", + G_CALLBACK(settings_changed_cb), this); + g_signal_connect_after(default_settings, "notify::gtk-dnd-drag-threshold", + G_CALLBACK(settings_changed_cb), this); + } + + if (mContainer) { + // Widget signals + g_signal_connect(mContainer, "unrealize", + G_CALLBACK(container_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); + + gtk_drag_dest_set((GtkWidget*)mContainer, (GtkDestDefaults)0, nullptr, 0, + (GdkDragAction)0); + + g_signal_connect(mContainer, "drag_motion", + G_CALLBACK(drag_motion_event_cb), nullptr); + g_signal_connect(mContainer, "drag_leave", G_CALLBACK(drag_leave_event_cb), + nullptr); + g_signal_connect(mContainer, "drag_drop", G_CALLBACK(drag_drop_event_cb), + nullptr); + g_signal_connect(mContainer, "drag_data_received", + G_CALLBACK(drag_data_received_event_cb), nullptr); + +#ifdef MOZ_X11 + if (mIsX11Display) { + GtkWidget* widgets[] = {GTK_WIDGET(mContainer), + !mDrawToContainer ? mShell : nullptr}; + for (size_t i = 0; i < ArrayLength(widgets) && widgets[i]; ++i) { + // Double buffering is controlled by the window's owning + // widget. Disable double buffering for painting directly to the + // X Window. + gtk_widget_set_double_buffered(widgets[i], FALSE); + } + } +#endif + + // We create input contexts for all containers, except for + // toplevel popup windows + if (mWindowType != eWindowType_popup) { + mIMContext = new IMContextWrapper(this); + } + } else if (!mIMContext) { + nsWindow* container = GetContainerWindow(); + if (container) { + mIMContext = container->mIMContext; + } + } + + if (eventWidget) { + // 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(eventWidget, "enter-notify-event", + G_CALLBACK(enter_notify_event_cb), nullptr); + g_signal_connect(eventWidget, "leave-notify-event", + G_CALLBACK(leave_notify_event_cb), nullptr); + g_signal_connect(eventWidget, "motion-notify-event", + G_CALLBACK(motion_notify_event_cb), nullptr); + g_signal_connect(eventWidget, "button-press-event", + G_CALLBACK(button_press_event_cb), nullptr); + g_signal_connect(eventWidget, "button-release-event", + G_CALLBACK(button_release_event_cb), nullptr); + g_signal_connect(eventWidget, "scroll-event", G_CALLBACK(scroll_event_cb), + nullptr); + if (gtk_check_version(3, 18, 0) == nullptr) { + g_signal_connect(eventWidget, "event", G_CALLBACK(generic_event_cb), + nullptr); + } + g_signal_connect(eventWidget, "touch-event", G_CALLBACK(touch_event_cb), + nullptr); + } + + LOG(("nsWindow [%p] %s %s\n", (void*)this, + mWindowType == eWindowType_toplevel ? "Toplevel" : "Popup", + mIsPIPWindow ? "PIP window" : "")); + if (mShell) { + LOG(("\tmShell %p mContainer %p mGdkWindow %p 0x%lx\n", mShell, mContainer, + mGdkWindow, mIsX11Display ? gdk_x11_window_get_xid(mGdkWindow) : 0)); + } else if (mContainer) { + LOG(("\tmContainer %p mGdkWindow %p\n", mContainer, mGdkWindow)); + } else if (mGdkWindow) { + LOG(("\tmGdkWindow %p parent %p\n", mGdkWindow, + gdk_window_get_parent(mGdkWindow))); + } + + // resize so that everything is set to the right dimensions + if (!mIsTopLevel) + Resize(mBounds.x, mBounds.y, mBounds.width, mBounds.height, false); + +#ifdef MOZ_X11 + if (mIsX11Display && mGdkWindow) { + mXDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow); + mXWindow = gdk_x11_window_get_xid(mGdkWindow); + + GdkVisual* gdkVisual = gdk_window_get_visual(mGdkWindow); + mXVisual = gdk_x11_visual_get_xvisual(gdkVisual); + mXDepth = gdk_visual_get_depth(gdkVisual); + bool shaped = popupNeedsAlphaVisual && !mHasAlphaVisual; + + mSurfaceProvider.Initialize(mXDisplay, mXWindow, mXVisual, mXDepth, shaped); + + if (mIsTopLevel) { + // 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); + } + } +# ifdef MOZ_WAYLAND + else if (!mIsX11Display) { + mSurfaceProvider.Initialize(this); + WaylandStartVsync(); + } +# endif +#endif + + // Set default application name when it's empty. + if (mGtkWindowAppName.IsEmpty()) { + mGtkWindowAppName = gAppData->name; + } + RefreshWindowClass(); + + return NS_OK; +} + +void nsWindow::RefreshWindowClass(void) { + GdkWindow* gdkWindow = gtk_widget_get_window(mShell); + if (!gdkWindow) { + return; + } + + if (!mGtkWindowRoleName.IsEmpty()) { + gdk_window_set_role(gdkWindow, mGtkWindowRoleName.get()); + } + +#ifdef MOZ_X11 + if (!mGtkWindowAppName.IsEmpty() && mIsX11Display) { + XClassHint* class_hint = XAllocClassHint(); + if (!class_hint) { + return; + } + const char* res_class = gdk_get_program_class(); + if (!res_class) return; + + class_hint->res_name = const_cast<char*>(mGtkWindowAppName.get()); + 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 */ +} + +void nsWindow::SetWindowClass(const nsAString& xulWinType) { + if (!mShell) return; + + char* res_name = ToNewCString(xulWinType, mozilla::fallible); + if (!res_name) return; + + const char* role = nullptr; + + // 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] = toupper(res_name[0]); + if (!role) role = res_name; + + mGtkWindowAppName = res_name; + mGtkWindowRoleName = role; + free(res_name); + + RefreshWindowClass(); +} + +void nsWindow::NativeResize() { + if (!AreBoundsSane()) { + // 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); + } + return; + } + + GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size()); + + LOG(("nsWindow::NativeResize [%p] %d %d\n", (void*)this, size.width, + size.height)); + + if (mIsTopLevel) { + MOZ_ASSERT(size.width > 0 && size.height > 0, + "Can't resize window smaller than 1x1."); + gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height); + if (mWaitingForMoveToRectCB) { + LOG(("Waiting for move to rect, schedulling ")); + mPendingSizeRect = mBounds; + } + } else if (mContainer) { + GtkWidget* widget = GTK_WIDGET(mContainer); + GtkAllocation allocation, prev_allocation; + gtk_widget_get_allocation(widget, &prev_allocation); + allocation.x = prev_allocation.x; + allocation.y = prev_allocation.y; + allocation.width = size.width; + allocation.height = size.height; + gtk_widget_size_allocate(widget, &allocation); + } else if (mGdkWindow) { + gdk_window_resize(mGdkWindow, size.width, size.height); + } + +#ifdef MOZ_X11 + // Notify the GtkCompositorWidget of a ClientSizeChange + // This is different than OnSizeAllocate to catch initial sizing + if (mCompositorWidgetDelegate) { + mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize()); + } +#endif + + // Does it need to be shown because bounds were previously insane? + if (mNeedsShow && mIsShown) { + NativeShow(true); + } +} + +void nsWindow::NativeMoveResize() { + if (!AreBoundsSane()) { + // 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); + } + NativeMove(); + + return; + } + + GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size()); + GdkPoint topLeft = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft()); + + LOG(("nsWindow::NativeMoveResize [%p] %d %d %d %d\n", (void*)this, topLeft.x, + topLeft.y, size.width, size.height)); + + if (IsWaylandPopup()) { + NativeMoveResizeWaylandPopup(&topLeft, &size); + } else { + if (mIsTopLevel) { + // x and y give the position of the window manager frame top-left. + gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y); + // This sets the client window size. + MOZ_ASSERT(size.width > 0 && size.height > 0, + "Can't resize window smaller than 1x1."); + gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height); + } else if (mContainer) { + GtkAllocation allocation; + allocation.x = topLeft.x; + allocation.y = topLeft.y; + allocation.width = size.width; + allocation.height = size.height; + gtk_widget_size_allocate(GTK_WIDGET(mContainer), &allocation); + } else if (mGdkWindow) { + gdk_window_move_resize(mGdkWindow, topLeft.x, topLeft.y, size.width, + size.height); + } + } + +#ifdef MOZ_X11 + // Notify the GtkCompositorWidget of a ClientSizeChange + // This is different than OnSizeAllocate to catch initial sizing + if (mCompositorWidgetDelegate) { + mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize()); + } +#endif + + // Does it need to be shown because bounds were previously insane? + if (mNeedsShow && mIsShown) { + NativeShow(true); + } +} + +void nsWindow::PauseRemoteRenderer() { +#ifdef MOZ_WAYLAND + if (!mIsDestroyed) { + if (mContainer) { + // Because wl_egl_window is destroyed on moz_container_unmap(), + // the current compositor cannot use it anymore. To avoid crash, + // pause the compositor and destroy EGLSurface & resume the compositor + // and re-create EGLSurface on next expose event. + + // moz_container_wayland_has_egl_window() could not be used here, since + // there is a case that resume compositor is not completed yet. + + CompositorBridgeChild* remoteRenderer = GetRemoteRenderer(); + bool needsCompositorPause = !mNeedsCompositorResume && !!remoteRenderer && + mCompositorWidgetDelegate; + if (needsCompositorPause) { + // XXX slow sync IPC + remoteRenderer->SendPause(); + // Re-request initial draw callback + RefPtr<nsWindow> self(this); + moz_container_wayland_add_initial_draw_callback( + mContainer, [self]() -> void { + self->mNeedsCompositorResume = true; + self->MaybeResumeCompositor(); + }); + } else { + DestroyLayerManager(); + } + } + } +#endif +} + +void nsWindow::HideWaylandWindow() { + if (mWindowType == eWindowType_popup) { + LOG(("nsWindow::HideWaylandWindow: popup [%p]\n", this)); + GList* foundWindow = g_list_find(gVisibleWaylandPopupWindows, this); + if (foundWindow) { + gVisibleWaylandPopupWindows = + g_list_delete_link(gVisibleWaylandPopupWindows, foundWindow); + } + } + PauseRemoteRenderer(); + gtk_widget_hide(mShell); +} + +void nsWindow::WaylandStartVsync() { +#ifdef MOZ_WAYLAND + // only use for toplevel windows for now - see bug 1619246 + if (!gUseWaylandVsync || mWindowType != eWindowType_toplevel) { + return; + } + + if (!mWaylandVsyncSource) { + mWaylandVsyncSource = new mozilla::WaylandVsyncSource(mContainer); + } + WaylandVsyncSource::WaylandDisplay& display = + static_cast<WaylandVsyncSource::WaylandDisplay&>( + mWaylandVsyncSource->GetGlobalDisplay()); + display.EnableMonitor(); +#endif +} + +void nsWindow::WaylandStopVsync() { +#ifdef MOZ_WAYLAND + if (mWaylandVsyncSource) { + // The widget is going to be hidden, so clear the surface of our + // vsync source. + WaylandVsyncSource::WaylandDisplay& display = + static_cast<WaylandVsyncSource::WaylandDisplay&>( + mWaylandVsyncSource->GetGlobalDisplay()); + display.DisableMonitor(); + } +#endif +} + +void nsWindow::NativeShow(bool aAction) { + if (aAction) { + // unset our flag now that our window has been shown + mNeedsShow = false; + + if (mIsTopLevel) { + // Set up usertime/startupID metadata for the created window. + if (mWindowType != eWindowType_invisible) { + SetUserTimeAndStartupIDForActivatedWindow(mShell); + } + // Update popup window hierarchy run-time on Wayland. + if (IsWaylandPopup()) { + if (!ConfigureWaylandPopupWindows()) { + mNeedsShow = true; + return; + } + } + + LOG((" calling gtk_widget_show(mShell)\n")); + gtk_widget_show(mShell); + if (!mIsX11Display) { + WaylandStartVsync(); + } + } else if (mContainer) { + LOG((" calling gtk_widget_show(mContainer)\n")); + gtk_widget_show(GTK_WIDGET(mContainer)); + } else if (mGdkWindow) { + LOG((" calling gdk_window_show_unraised\n")); + gdk_window_show_unraised(mGdkWindow); + } + } else { + // There's a chance that when the popup will be shown again it might be + // resized because parent could be moved meanwhile. + mPreferredPopupRect = nsRect(0, 0, 0, 0); + mPreferredPopupRectFlushed = false; + if (!mIsX11Display) { + WaylandStopVsync(); + if (IsWaylandPopup() && IsMainMenuWindow()) { + CleanupWaylandPopups(); + } + HideWaylandWindow(); + } else if (mIsTopLevel) { + // 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 + } else if (mContainer) { + gtk_widget_hide(GTK_WIDGET(mContainer)); + } else if (mGdkWindow) { + gdk_window_hide(mGdkWindow); + } + } +} + +void nsWindow::SetHasMappedToplevel(bool aState) { + // 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. + bool oldState = mHasMappedToplevel; + mHasMappedToplevel = aState; + + // mHasMappedToplevel is not updated for children of windows that are + // hidden; GDK knows not to send expose events for these windows. The + // state is recorded on the hidden window itself, but, for child trees of + // hidden windows, their state essentially becomes disconnected from their + // hidden parent. When the hidden parent gets shown, the child trees are + // reconnected, and the state of the window being shown can be easily + // propagated. + if (!mIsShown || !mGdkWindow) return; + + if (aState && !oldState) { + // Check that a grab didn't fail due to the window not being + // viewable. + EnsureGrabs(); + } + + for (GList* children = gdk_window_peek_children(mGdkWindow); children; + children = children->next) { + GdkWindow* gdkWin = GDK_WINDOW(children->data); + nsWindow* child = get_window_for_gdk_window(gdkWin); + + if (child && child->mHasMappedToplevel != aState) { + child->SetHasMappedToplevel(aState); + } + } +} + +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 (mLayerManager && mLayerManager->AsKnowsCompositor()) { + maxSize = std::min(maxSize, + mLayerManager->AsKnowsCompositor()->GetMaxTextureSize()); + } + if (result.width > maxSize) { + result.width = maxSize; + } + if (result.height > maxSize) { + result.height = maxSize; + } + return result; +} + +void nsWindow::EnsureGrabs(void) { + if (mRetryPointerGrab) GrabPointer(sRetryGrabTime); +} + +void nsWindow::CleanLayerManagerRecursive(void) { + if (mLayerManager) { + mLayerManager->Destroy(); + mLayerManager = nullptr; + } + + DestroyCompositor(); + + GList* children = gdk_window_peek_children(mGdkWindow); + for (GList* list = children; list; list = list->next) { + nsWindow* window = get_window_for_gdk_window(GDK_WINDOW(list->data)); + if (window) { + window->CleanLayerManagerRecursive(); + } + } +} + +void nsWindow::SetTransparencyMode(nsTransparencyMode aMode) { + if (!mShell) { + // Pass the request to the toplevel window + GtkWidget* topWidget = GetToplevelWidget(); + if (!topWidget) return; + + nsWindow* topWindow = get_window_for_gtk_widget(topWidget); + if (!topWindow) return; + + topWindow->SetTransparencyMode(aMode); + return; + } + + bool isTransparent = aMode == eTransparencyTransparent; + + if (mIsTransparent == isTransparent) { + return; + } + + if (mWindowType != eWindowType_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. + CleanLayerManagerRecursive(); + } +} + +nsTransparencyMode nsWindow::GetTransparencyMode() { + if (!mShell) { + // Pass the request to the toplevel window + GtkWidget* topWidget = GetToplevelWidget(); + if (!topWidget) { + return eTransparencyOpaque; + } + + nsWindow* topWindow = get_window_for_gtk_widget(topWidget); + if (!topWindow) { + return eTransparencyOpaque; + } + + return topWindow->GetTransparencyMode(); + } + + return mIsTransparent ? eTransparencyTransparent : eTransparencyOpaque; +} + +void nsWindow::SetWindowMouseTransparent(bool aIsTransparent) { + if (!mGdkWindow) { + return; + } + + cairo_rectangle_int_t emptyRect = {0, 0, 0, 0}; + cairo_region_t* region = + aIsTransparent ? cairo_region_create_rectangle(&emptyRect) : nullptr; + + gdk_window_input_shape_combine_region(mGdkWindow, region, 0, 0); + if (region) { + cairo_region_destroy(region); + } +} + +// For setting the draggable titlebar region from CSS +// with -moz-window-dragging: drag. +void nsWindow::UpdateWindowDraggingRegion( + const LayoutDeviceIntRegion& aRegion) { + if (mDraggableRegion != aRegion) { + mDraggableRegion = aRegion; + } +} + +// 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) { + cairo_rectangle_int_t rect = {aX, aY, TITLEBAR_SHAPE_MASK_HEIGHT, + TITLEBAR_SHAPE_MASK_HEIGHT}; + cairo_region_subtract_rectangle(aRegion, &rect); + rect = { + aX + aWindowWidth - TITLEBAR_SHAPE_MASK_HEIGHT, + aY, + TITLEBAR_SHAPE_MASK_HEIGHT, + TITLEBAR_SHAPE_MASK_HEIGHT, + }; + cairo_region_subtract_rectangle(aRegion, &rect); +} + +void nsWindow::UpdateTopLevelOpaqueRegion(void) { + if (!mCompositedScreen) { + return; + } + + GdkWindow* window = + (mDrawToContainer) ? gtk_widget_get_window(mShell) : mGdkWindow; + if (!window) { + return; + } + MOZ_ASSERT(gdk_window_get_window_type(window) == GDK_WINDOW_TOPLEVEL); + + int x = 0; + int y = 0; + + if (mDrawToContainer) { + 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); + + bool subtractCorners = DoDrawTilebarCorners(); + if (subtractCorners) { + SubtractTitlebarCorners(region, x, y, width); + } + + gdk_window_set_opaque_region(window, region); + + cairo_region_destroy(region); + +#ifdef MOZ_WAYLAND + // We don't set opaque region to mozContainer by default due to Bug 1615098. + if (!mIsX11Display && gUseWaylandUseOpaqueRegion) { + moz_container_wayland_update_opaque_region(mContainer, subtractCorners); + } +#endif +} + +bool nsWindow::IsChromeWindowTitlebar() { + return mDrawInTitlebar && !mIsPIPWindow && + mWindowType == eWindowType_toplevel; +} + +bool nsWindow::DoDrawTilebarCorners() { + return IsChromeWindowTitlebar() && mSizeState == nsSizeMode_Normal && + !mIsTiled; +} + +nsresult nsWindow::ConfigureChildren( + const nsTArray<Configuration>& aConfigurations) { + // If this is a remotely updated widget we receive clipping, position, and + // size information from a source other than our owner. Don't let our parent + // update this information. + if (mWindowType == eWindowType_plugin_ipc_chrome) { + return NS_OK; + } + + for (uint32_t i = 0; i < aConfigurations.Length(); ++i) { + const Configuration& configuration = aConfigurations[i]; + auto* w = static_cast<nsWindow*>(configuration.mChild.get()); + NS_ASSERTION(w->GetParent() == this, "Configured widget is not a child"); + w->SetWindowClipRegion(configuration.mClipRegion, true); + if (w->mBounds.Size() != configuration.mBounds.Size()) { + w->Resize(configuration.mBounds.x, configuration.mBounds.y, + configuration.mBounds.width, configuration.mBounds.height, + true); + } else if (w->mBounds.TopLeft() != configuration.mBounds.TopLeft()) { + w->Move(configuration.mBounds.x, configuration.mBounds.y); + } + w->SetWindowClipRegion(configuration.mClipRegion, false); + } + return NS_OK; +} + +nsresult nsWindow::SetWindowClipRegion( + const nsTArray<LayoutDeviceIntRect>& aRects, bool aIntersectWithExisting) { + const nsTArray<LayoutDeviceIntRect>* newRects = &aRects; + + AutoTArray<LayoutDeviceIntRect, 1> intersectRects; + if (aIntersectWithExisting) { + AutoTArray<LayoutDeviceIntRect, 1> existingRects; + GetWindowClipRegion(&existingRects); + + LayoutDeviceIntRegion existingRegion = RegionFromArray(existingRects); + LayoutDeviceIntRegion newRegion = RegionFromArray(aRects); + LayoutDeviceIntRegion intersectRegion; + intersectRegion.And(newRegion, existingRegion); + + // If mClipRects is null we haven't set a clip rect yet, so we + // need to set the clip even if it is equal. + if (mClipRects && intersectRegion.IsEqual(existingRegion)) { + return NS_OK; + } + + if (!intersectRegion.IsEqual(newRegion)) { + ArrayFromRegion(intersectRegion, intersectRects); + newRects = &intersectRects; + } + } + + if (IsWindowClipRegionEqual(*newRects)) return NS_OK; + + StoreWindowClipRegion(*newRects); + + if (!mGdkWindow) return NS_OK; + + cairo_region_t* region = cairo_region_create(); + for (uint32_t i = 0; i < newRects->Length(); ++i) { + const LayoutDeviceIntRect& r = newRects->ElementAt(i); + cairo_rectangle_int_t rect = {r.x, r.y, r.width, r.height}; + cairo_region_union_rectangle(region, &rect); + } + + gdk_window_shape_combine_region(mGdkWindow, region, 0, 0); + cairo_region_destroy(region); + + return NS_OK; +} + +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); +#else + cairo_surface_t* maskBitmap; + maskBitmap = cairo_image_surface_create_for_data( + (unsigned char*)mTransparencyBitmap, CAIRO_FORMAT_A1, + mTransparencyBitmapWidth, mTransparencyBitmapHeight, + GetBitmapStride(mTransparencyBitmapWidth)); + if (!maskBitmap) return; + + cairo_region_t* maskRegion = gdk_cairo_region_create_from_surface(maskBitmap); + gtk_widget_shape_combine_region(mShell, maskRegion); + cairo_region_destroy(maskRegion); + cairo_surface_destroy(maskBitmap); +#endif // MOZ_X11 +} + +void nsWindow::ClearTransparencyBitmap() { + if (!mTransparencyBitmap) return; + + delete[] mTransparencyBitmap; + mTransparencyBitmap = nullptr; + mTransparencyBitmapWidth = 0; + mTransparencyBitmapHeight = 0; + + if (!mShell) return; + +#ifdef MOZ_X11 + if (!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) { + if (!mShell) { + // Pass the request to the toplevel window + GtkWidget* topWidget = GetToplevelWidget(); + if (!topWidget) return NS_ERROR_FAILURE; + + nsWindow* topWindow = get_window_for_gtk_widget(topWidget); + if (!topWindow) return NS_ERROR_FAILURE; + + return topWindow->UpdateTranslucentWindowAlphaInternal(aRect, aAlphas, + 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; +} + +bool nsWindow::GetTitlebarRect(mozilla::gfx::Rect& aRect) { + if (!mGdkWindow || !mDrawInTitlebar) { + return false; + } + + aRect = gfx::Rect(0, 0, mBounds.width, TITLEBAR_SHAPE_MASK_HEIGHT); + return true; +} + +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; + + if (maskCreate) { + if (mTransparencyBitmap) { + delete[] mTransparencyBitmap; + } + int32_t size = GetBitmapStride(mBounds.width) * TITLEBAR_SHAPE_MASK_HEIGHT; + 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, TITLEBAR_SHAPE_MASK_HEIGHT); + if (!surface) return; + + cairo_t* cr = cairo_create(surface); + + GtkWidgetState state; + memset((void*)&state, 0, sizeof(state)); + GdkRectangle rect = {0, 0, mTransparencyBitmapWidth, + TITLEBAR_SHAPE_MASK_HEIGHT}; + + 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, + TITLEBAR_SHAPE_MASK_HEIGHT, + nsIntRect(0, 0, mTransparencyBitmapWidth, TITLEBAR_SHAPE_MASK_HEIGHT), + cairo_image_surface_get_data(surface), + cairo_format_stride_for_width(CAIRO_FORMAT_A8, + mTransparencyBitmapWidth)); + + cairo_surface_destroy(surface); + } + + if (!mNeedsShow) { + Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow); + Window xDrawable = GDK_WINDOW_XID(mGdkWindow); + + Pixmap maskPixmap = XCreateBitmapFromData( + xDisplay, xDrawable, mTransparencyBitmap, mTransparencyBitmapWidth, + TITLEBAR_SHAPE_MASK_HEIGHT); + + XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap, + ShapeSet); + + if (mTransparencyBitmapHeight > TITLEBAR_SHAPE_MASK_HEIGHT) { + XRectangle rect = {0, 0, (unsigned short)mTransparencyBitmapWidth, + (unsigned short)(mTransparencyBitmapHeight - + TITLEBAR_SHAPE_MASK_HEIGHT)}; + XShapeCombineRectangles(xDisplay, xDrawable, ShapeBounding, 0, + TITLEBAR_SHAPE_MASK_HEIGHT, &rect, 1, ShapeUnion, + 0); + } + + XFreePixmap(xDisplay, maskPixmap); + } +} + +void nsWindow::GrabPointer(guint32 aTime) { + LOG(("GrabPointer time=0x%08x retry=%d\n", (unsigned int)aTime, + mRetryPointerGrab)); + + mRetryPointerGrab = false; + sRetryGrabTime = aTime; + + // If the window isn't visible, just set the flag to retry the + // grab. When this window becomes visible, the grab will be + // retried. + if (!mHasMappedToplevel) { + LOG(("GrabPointer: window not visible\n")); + mRetryPointerGrab = true; + return; + } + + if (!mGdkWindow) return; + + if (!mIsX11Display) { + // Don't to the grab on Wayland as it causes a regression + // from Bug 1377084. + return; + } + + gint retval; + // Note that we need GDK_TOUCH_MASK below to work around a GDK/X11 bug that + // causes touch events that would normally be received by this client on + // other windows to be discarded during the grab. + retval = gdk_pointer_grab( + mGdkWindow, TRUE, + (GdkEventMask)(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | + GDK_POINTER_MOTION_MASK | GDK_TOUCH_MASK), + (GdkWindow*)nullptr, nullptr, aTime); + + if (retval == GDK_GRAB_NOT_VIEWABLE) { + LOG(("GrabPointer: window not viewable; will retry\n")); + mRetryPointerGrab = true; + } else if (retval != GDK_GRAB_SUCCESS) { + LOG(("GrabPointer: pointer grab failed: %i\n", retval)); + // A failed grab indicates that another app has grabbed the pointer. + // Check for rollup now, because, without the grab, we likely won't + // get subsequent button press events. Do this with an event so that + // popups don't rollup while potentially adjusting the grab for + // this popup. + nsCOMPtr<nsIRunnable> event = + NewRunnableMethod("nsWindow::CheckForRollupDuringGrab", this, + &nsWindow::CheckForRollupDuringGrab); + NS_DispatchToCurrentThread(event.forget()); + } +} + +void nsWindow::ReleaseGrabs(void) { + LOG(("ReleaseGrabs\n")); + + mRetryPointerGrab = false; + + if (!mIsX11Display) { + // Don't to the ungrab on Wayland as it causes a regression + // from Bug 1377084. + return; + } + + gdk_pointer_ungrab(GDK_CURRENT_TIME); +} + +GtkWidget* nsWindow::GetToplevelWidget() { + if (mShell) { + return mShell; + } + + GtkWidget* widget = GetMozContainerWidget(); + if (!widget) return nullptr; + + return gtk_widget_get_toplevel(widget); +} + +GtkWidget* nsWindow::GetMozContainerWidget() { + if (!mGdkWindow) return nullptr; + + if (mContainer) return GTK_WIDGET(mContainer); + + GtkWidget* owningWidget = get_gtk_widget_for_gdk_window(mGdkWindow); + return owningWidget; +} + +nsWindow* nsWindow::GetContainerWindow() { + GtkWidget* owningWidget = GetMozContainerWidget(); + 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) { + if (!top_window) return; + + gdk_window_set_urgency_hint(gtk_widget_get_window(top_window), state); +} + +void nsWindow::SetDefaultIcon(void) { SetIcon(u"default"_ns); } + +gint nsWindow::ConvertBorderStyles(nsBorderStyle aStyle) { + gint w = 0; + + if (aStyle == eBorderStyle_default) return -1; + + // note that we don't handle eBorderStyle_close yet + if (aStyle & eBorderStyle_all) w |= GDK_DECOR_ALL; + if (aStyle & eBorderStyle_border) w |= GDK_DECOR_BORDER; + if (aStyle & eBorderStyle_resizeh) w |= GDK_DECOR_RESIZEH; + if (aStyle & eBorderStyle_title) w |= GDK_DECOR_TITLE; + if (aStyle & eBorderStyle_menu) w |= GDK_DECOR_MENU; + if (aStyle & eBorderStyle_minimize) w |= GDK_DECOR_MINIMIZE; + if (aStyle & eBorderStyle_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); + gtk_window_set_transient_for(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); + + GdkColor bgColor; + bgColor.red = bgColor.green = bgColor.blue = 0; + gtk_widget_modify_bg(mWindow, GTK_STATE_NORMAL, &bgColor); + + gtk_window_set_opacity(gtkWin, 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_window_set_opacity(GTK_WINDOW(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<nsIScreen> nsWindow::GetWidgetScreen() { + nsCOMPtr<nsIScreenManager> screenManager; + screenManager = do_GetService("@mozilla.org/gfx/screenmanager;1"); + if (!screenManager) { + return nullptr; + } + + // GetScreenBounds() is slow for the GTK port so we override and use + // mBounds directly. + LayoutDeviceIntRect bounds = mBounds; + if (!mIsTopLevel) { + bounds.MoveTo(WidgetToScreenOffset()); + } + + DesktopIntRect deskBounds = RoundedToInt(bounds / GetDesktopToDeviceScale()); + nsCOMPtr<nsIScreen> screen; + screenManager->ScreenForRect(deskBounds.x, deskBounds.y, deskBounds.width, + deskBounds.height, getter_AddRefs(screen)); + return screen.forget(); +} + +RefPtr<VsyncSource> nsWindow::GetVsyncSource() { +#ifdef MOZ_WAYLAND + if (mWaylandVsyncSource) { + return mWaylandVsyncSource; + } +#endif + + return nullptr; +} + +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); + if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) { + return false; + } +#endif + return true; +} + +nsresult nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen) { + LOG(("nsWindow::MakeFullScreen [%p] aFullScreen %d\n", (void*)this, + aFullScreen)); + + if (mIsX11Display && !IsFullscreenSupported(mShell)) { + return NS_ERROR_NOT_AVAILABLE; + } + + bool wasFullscreen = mSizeState == nsSizeMode_Fullscreen; + if (aFullScreen != wasFullscreen && mWidgetListener) { + mWidgetListener->FullscreenWillChange(aFullScreen); + } + + if (aFullScreen) { + if (mSizeMode != nsSizeMode_Fullscreen) mLastSizeMode = mSizeMode; + + mSizeMode = nsSizeMode_Fullscreen; + + if (mIsPIPWindow) { + gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_NORMAL); + if (gUseAspectRatio) { + mAspectRatioSaved = mAspectRatio; + mAspectRatio = 0.0f; + ApplySizeConstraints(); + } + } + + gtk_window_fullscreen(GTK_WINDOW(mShell)); + } else { + mSizeMode = mLastSizeMode; + 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(); + } + } + } + + NS_ASSERTION(mLastSizeMode != nsSizeMode_Fullscreen, + "mLastSizeMode should never be fullscreen"); + return NS_OK; +} + +void nsWindow::SetWindowDecoration(nsBorderStyle aStyle) { + LOG(("nsWindow::SetWindowDecoration() [%p] Border style %x\n", (void*)this, + aStyle)); + + if (!mShell) { + // Pass the request to the toplevel window + GtkWidget* topWidget = GetToplevelWidget(); + if (!topWidget) return; + + nsWindow* topWindow = get_window_for_gtk_widget(topWidget); + if (!topWindow) return; + + topWindow->SetWindowDecoration(aStyle); + return; + } + + // We can't use mGdkWindow directly here as it can be + // derived from mContainer which is not a top-level GdkWindow. + GdkWindow* window = gtk_widget_get_window(mShell); + + // 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 (mIsX11Display) { + XSync(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), X11False); + } else +#endif /* MOZ_X11 */ + { + gdk_flush(); + } +} + +void nsWindow::HideWindowChrome(bool aShouldHide) { + SetWindowDecoration(aShouldHide ? eBorderStyle_none : mBorderStyle); +} + +bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel, + bool aAlwaysRollup) { + nsIRollupListener* rollupListener = GetActiveRollupListener(); + nsCOMPtr<nsIWidget> rollupWidget; + if (rollupListener) { + rollupWidget = rollupListener->GetRollupWidget(); + } + if (!rollupWidget) { + nsBaseWidget::gRollupListener = nullptr; + return false; + } + + bool retVal = false; + auto* currentPopup = + (GdkWindow*)rollupWidget->GetNativeData(NS_NATIVE_WINDOW); + if (aAlwaysRollup || !is_mouse_in_window(currentPopup, aMouseX, aMouseY)) { + bool rollup = true; + if (aIsWheel) { + rollup = rollupListener->ShouldRollupOnMouseWheelEvent(); + retVal = rollupListener->ShouldConsumeOnMouseWheelEvent(); + } + // 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 + uint32_t popupsToRollup = UINT32_MAX; + 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) { + rollup = false; + } else { + popupsToRollup = sameTypeCount; + } + break; + } + } // foreach parent menu widget + } // if rollup listener knows about menus + + // if we've determined that we should still rollup, do it. + bool usePoint = !aIsWheel && !aAlwaysRollup; + IntPoint point; + if (usePoint) { + LayoutDeviceIntPoint p = GdkEventCoordsToDevicePixels(aMouseX, aMouseY); + point = p.ToUnknownPoint(); + } + if (rollup && + rollupListener->Rollup(popupsToRollup, true, + usePoint ? &point : nullptr, nullptr)) { + retVal = true; + } + } + return retVal; +} + +/* static */ +bool nsWindow::DragInProgress(void) { + nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID); + + if (!dragService) return false; + + nsCOMPtr<nsIDragSession> currentDragSession; + dragService->GetCurrentSession(getter_AddRefs(currentDragSession)); + + return currentDragSession != nullptr; +} + +static bool is_mouse_in_window(GdkWindow* aWindow, gdouble aMouseX, + gdouble aMouseY) { + gint x = 0; + gint y = 0; + gint w, h; + + gint offsetX = 0; + gint offsetY = 0; + + GdkWindow* window = aWindow; + + 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); + } + + w = gdk_window_get_width(aWindow); + h = gdk_window_get_height(aWindow); + + if (aMouseX > x && aMouseX < x + w && aMouseY > y && aMouseY < y + h) + return true; + + return false; +} + +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 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(); + + // The strategy here is to use standard GDK cursors, and, if not available, + // load by standard name with gdk_cursor_new_from_name. + // Spec is here: http://www.freedesktop.org/wiki/Specifications/cursor-spec/ + switch (aCursor) { + case eCursor_standard: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_PTR); + break; + case eCursor_wait: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_WATCH); + break; + case eCursor_select: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_XTERM); + break; + case eCursor_hyperlink: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_HAND2); + break; + case eCursor_n_resize: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_SIDE); + break; + case eCursor_s_resize: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_SIDE); + break; + case eCursor_w_resize: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_SIDE); + break; + case eCursor_e_resize: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_RIGHT_SIDE); + break; + case eCursor_nw_resize: + gdkcursor = + gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_LEFT_CORNER); + break; + case eCursor_se_resize: + gdkcursor = + gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_RIGHT_CORNER); + break; + case eCursor_ne_resize: + gdkcursor = + gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_RIGHT_CORNER); + break; + case eCursor_sw_resize: + gdkcursor = + gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_LEFT_CORNER); + break; + case eCursor_crosshair: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_CROSSHAIR); + break; + case eCursor_move: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_FLEUR); + break; + case eCursor_help: + gdkcursor = + gdk_cursor_new_for_display(defaultDisplay, GDK_QUESTION_ARROW); + break; + case eCursor_copy: // CSS3 + 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_for_display(defaultDisplay, GDK_PLUS); + break; + // Those two aren’t standardized. Trying both KDE’s and GNOME’s names + case eCursor_grab: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "openhand"); + if (!gdkcursor) newType = MOZ_CURSOR_HAND_GRAB; + break; + case eCursor_grabbing: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "closedhand"); + if (!gdkcursor) + 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) // nonstandard, yet common + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "crossed_circle"); + 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: + newType = MOZ_CURSOR_VERTICAL_TEXT; + break; + case eCursor_all_scroll: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_FLEUR); + break; + case eCursor_nesw_resize: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "size_bdiag"); + if (!gdkcursor) newType = MOZ_CURSOR_NESW_RESIZE; + break; + case eCursor_nwse_resize: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "size_fdiag"); + if (!gdkcursor) newType = MOZ_CURSOR_NWSE_RESIZE; + break; + case eCursor_ns_resize: + gdkcursor = + gdk_cursor_new_for_display(defaultDisplay, GDK_SB_V_DOUBLE_ARROW); + break; + case eCursor_ew_resize: + gdkcursor = + gdk_cursor_new_for_display(defaultDisplay, GDK_SB_H_DOUBLE_ARROW); + break; + // Here, two better fitting cursors exist in some cursor themes. Try those + // first + case eCursor_row_resize: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "split_v"); + if (!gdkcursor) + gdkcursor = + gdk_cursor_new_for_display(defaultDisplay, GDK_SB_V_DOUBLE_ARROW); + break; + case eCursor_col_resize: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "split_h"); + if (!gdkcursor) + gdkcursor = + gdk_cursor_new_for_display(defaultDisplay, GDK_SB_H_DOUBLE_ARROW); + break; + case eCursor_none: + newType = MOZ_CURSOR_NONE; + break; + default: + NS_ASSERTION(aCursor, "Invalid cursor type"); + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_PTR); + 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 = *bits++; + char mask = *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_gdk_window(aWindow); + 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); + } + } + + GList* children = gdk_window_get_children(aWindow); + GList* child = children; + while (child) { + GdkWindow* window = GDK_WINDOW(child->data); + gpointer windowWidget; + gdk_window_get_user_data(window, &windowWidget); + if (windowWidget == widget) { + draw_window_of_widget(widget, window, cr); + } + child = g_list_next(child); + } + g_list_free(children); +} + +/* 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); +} + +static void container_unrealize_cb(GtkWidget* widget) { + RefPtr<nsWindow> window = get_window_for_gtk_widget(widget); + if (!window) { + return; + } + + window->OnContainerUnrealize(); +} + +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; + } + + window->OnEnterNotifyEvent(event); + + return TRUE; +} + +static gboolean leave_notify_event_cb(GtkWidget* widget, + GdkEventCrossing* event) { + if (is_parent_grab_leave(event)) { + return TRUE; + } + + // bug 369599: Suppress LeaveNotify events caused by pointer grabs to + // avoid generating spurious mouse exit events. + auto x = gint(event->x_root); + auto y = gint(event->y_root); + GdkDisplay* display = gtk_widget_get_display(widget); + GdkWindow* winAtPt = gdk_display_get_window_at_pointer(display, &x, &y); + if (winAtPt == event->window) { + return TRUE; + } + + RefPtr<nsWindow> window = get_window_for_gdk_window(event->window); + if (!window) return TRUE; + + 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); + + 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); + + nsWindow* window = GetFirstNSWindowForGDKWindow(event->window); + if (!window) return FALSE; + + window->OnButtonPressEvent(event); + + return TRUE; +} + +static gboolean button_release_event_cb(GtkWidget* widget, + GdkEventButton* event) { + UpdateLastInputEventTime(event); + + 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 deconified. + 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) { + LOG(("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 (GDK_IS_X11_DISPLAY(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) { + LOG(("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) { + nsWindow* window = GetFirstNSWindowForGDKWindow(event->window); + if (!window) return FALSE; + + window->OnScrollEvent(event); + + 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_changed_cb(GtkSettings* settings, GParamSpec* pspec, + nsWindow* data) { + if (sIgnoreChangedSettings) { + return; + } + RefPtr<nsWindow> window = data; + window->ThemeChanged(); +} + +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; + } + + GtkAllocation allocation; + gtk_widget_get_allocation(widget, &allocation); + window->OnScaleChanged(&allocation); +} + +static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent) { + UpdateLastInputEventTime(aEvent); + + 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); + + 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); +} + +gboolean WindowDragMotionHandler(GtkWidget* aWidget, + GdkDragContext* aDragContext, + nsWaylandDragContext* aWaylandDragContext, + 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(("nsWindow drag-motion signal for %p\n", (void*)innerMostWindow)); + + LayoutDeviceIntPoint point = window->GdkPointToDevicePixels({retx, rety}); + + RefPtr<nsDragService> dragService = nsDragService::GetInstance(); + return dragService->ScheduleMotionEvent(innerMostWindow, aDragContext, + aWaylandDragContext, point, aTime); +} + +static gboolean drag_motion_event_cb(GtkWidget* aWidget, + GdkDragContext* aDragContext, gint aX, + gint aY, guint aTime, gpointer aData) { + return WindowDragMotionHandler(aWidget, aDragContext, nullptr, aX, aY, aTime); +} + +void WindowDragLeaveHandler(GtkWidget* aWidget) { + RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget); + if (!window) return; + + RefPtr<nsDragService> dragService = nsDragService::GetInstance(); + + 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. + return; + } + + GtkWidget* mozContainer = mostRecentDragWindow->GetMozContainerWidget(); + if (aWidget != mozContainer) { + // 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. + return; + } + + LOGDRAG(("nsWindow drag-leave signal for %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, + nsWaylandDragContext* aWaylandDragContext, + 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(("nsWindow drag-drop signal for %p\n", (void*)innerMostWindow)); + + LayoutDeviceIntPoint point = window->GdkPointToDevicePixels({retx, rety}); + + RefPtr<nsDragService> dragService = nsDragService::GetInstance(); + return dragService->ScheduleDropEvent(innerMostWindow, aDragContext, + aWaylandDragContext, point, aTime); +} + +static gboolean drag_drop_event_cb(GtkWidget* aWidget, + GdkDragContext* aDragContext, gint aX, + gint aY, guint aTime, gpointer aData) { + return WindowDragDropHandler(aWidget, aDragContext, nullptr, 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) { + gRaiseWindows = + Preferences::GetBool("mozilla.widget.raise-on-setfocus", true); + gUseWaylandVsync = + Preferences::GetBool("widget.wayland_vsync.enabled", false); + gUseWaylandUseOpaqueRegion = + Preferences::GetBool("widget.wayland.use-opaque-region", false); + + if (Preferences::HasUserValue("widget.use-aspect-ratio")) { + gUseAspectRatio = Preferences::GetBool("widget.use-aspect-ratio", true); + } else { + static const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP"); + gUseAspectRatio = + currentDesktop ? (strstr(currentDesktop, "GNOME") != nullptr) : false; + } + + return NS_OK; +} + +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; +} + +static int is_parent_ungrab_enter(GdkEventCrossing* aEvent) { + return (GDK_CROSSING_UNGRAB == aEvent->mode) && + ((GDK_NOTIFY_ANCESTOR == aEvent->detail) || + (GDK_NOTIFY_VIRTUAL == aEvent->detail)); +} + +static int is_parent_grab_leave(GdkEventCrossing* aEvent) { + return (GDK_CROSSING_GRAB == aEvent->mode) && + ((GDK_NOTIFY_ANCESTOR == aEvent->detail) || + (GDK_NOTIFY_VIRTUAL == aEvent->detail)); +} + +#ifdef ACCESSIBILITY +void nsWindow::CreateRootAccessible() { + if (mIsTopLevel && !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::Accessible* 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; +} + +void nsWindow::GetEditCommandsRemapped(NativeKeyBindingsType aType, + const WidgetKeyboardEvent& aEvent, + nsTArray<CommandInt>& aCommands, + uint32_t aGeckoKeyCode, + uint32_t aNativeKeyCode) { + // If aEvent.mNativeKeyEvent is nullptr, the event was created by chrome + // script. In such case, we shouldn't expose the OS settings to it. + // So, just ignore such events here. + if (!aEvent.mNativeKeyEvent) { + return; + } + WidgetKeyboardEvent modifiedEvent(aEvent); + modifiedEvent.mKeyCode = aGeckoKeyCode; + static_cast<GdkEventKey*>(modifiedEvent.mNativeKeyEvent)->keyval = + aNativeKeyCode; + + NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType); + keyBindings->GetEditCommands(modifiedEvent, aCommands); +} + +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; + } + + if (aEvent.mKeyCode >= NS_VK_LEFT && aEvent.mKeyCode <= NS_VK_DOWN) { + // Check if we're targeting content with vertical writing mode, + // and if so remap the arrow keys. + // XXX This may be expensive. + WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText, + this); + nsEventStatus status; + DispatchEvent(&querySelectedTextEvent, status); + + if (querySelectedTextEvent.FoundSelection() && + querySelectedTextEvent.mReply->mWritingMode.IsVertical()) { + uint32_t geckoCode = 0; + uint32_t gdkCode = 0; + switch (aEvent.mKeyCode) { + case NS_VK_LEFT: + if (querySelectedTextEvent.mReply->mWritingMode.IsVerticalLR()) { + geckoCode = NS_VK_UP; + gdkCode = GDK_Up; + } else { + geckoCode = NS_VK_DOWN; + gdkCode = GDK_Down; + } + break; + + case NS_VK_RIGHT: + if (querySelectedTextEvent.mReply->mWritingMode.IsVerticalLR()) { + geckoCode = NS_VK_DOWN; + gdkCode = GDK_Down; + } else { + geckoCode = NS_VK_UP; + gdkCode = GDK_Up; + } + break; + + case NS_VK_UP: + geckoCode = NS_VK_LEFT; + gdkCode = GDK_Left; + break; + + case NS_VK_DOWN: + geckoCode = NS_VK_RIGHT; + gdkCode = GDK_Right; + break; + } + + GetEditCommandsRemapped(aType, aEvent, aCommands, geckoCode, gdkCode); + return true; + } + } + + NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType); + keyBindings->GetEditCommands(aEvent, aCommands); + return true; +} + +already_AddRefed<DrawTarget> nsWindow::StartRemoteDrawingInRegion( + LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode) { + return mSurfaceProvider.StartRemoteDrawingInRegion(aInvalidRegion, + aBufferMode); +} + +void nsWindow::EndRemoteDrawingInRegion( + DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) { + mSurfaceProvider.EndRemoteDrawingInRegion(aDrawTarget, aInvalidRegion); +} + +// Code shared begin BeginMoveDrag and BeginResizeDrag +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; + } + + if (mIsX11Display) { + // 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)) { + static unsigned int lastTimeStamp = 0; + if (lastTimeStamp != aMouseEvent->mTime) { + lastTimeStamp = aMouseEvent->mTime; + } else { + return false; + } + } + } + + // 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; +} + +nsresult nsWindow::BeginResizeDrag(WidgetGUIEvent* aEvent, int32_t aHorizontal, + int32_t aVertical) { + NS_ENSURE_ARG_POINTER(aEvent); + + if (aEvent->mClass != eMouseEventClass) { + // you can only begin a resize drag with a mouse event + return NS_ERROR_INVALID_ARG; + } + + GdkWindow* gdk_window; + gint button, screenX, screenY; + if (!GetDragInfo(aEvent->AsMouseEvent(), &gdk_window, &button, &screenX, + &screenY)) { + return NS_ERROR_FAILURE; + } + + // work out what GdkWindowEdge we're talking about + GdkWindowEdge window_edge; + if (aVertical < 0) { + if (aHorizontal < 0) { + window_edge = GDK_WINDOW_EDGE_NORTH_WEST; + } else if (aHorizontal == 0) { + window_edge = GDK_WINDOW_EDGE_NORTH; + } else { + window_edge = GDK_WINDOW_EDGE_NORTH_EAST; + } + } else if (aVertical == 0) { + if (aHorizontal < 0) { + window_edge = GDK_WINDOW_EDGE_WEST; + } else if (aHorizontal == 0) { + return NS_ERROR_INVALID_ARG; + } else { + window_edge = GDK_WINDOW_EDGE_EAST; + } + } else { + if (aHorizontal < 0) { + window_edge = GDK_WINDOW_EDGE_SOUTH_WEST; + } else if (aHorizontal == 0) { + window_edge = GDK_WINDOW_EDGE_SOUTH; + } else { + window_edge = GDK_WINDOW_EDGE_SOUTH_EAST; + } + } + + // tell the window manager to start the resize + gdk_window_begin_resize_drag(gdk_window, window_edge, button, screenX, + screenY, aEvent->mTime); + + return NS_OK; +} + +nsIWidget::LayerManager* nsWindow::GetLayerManager( + PLayerTransactionChild* aShadowManager, LayersBackend aBackendHint, + LayerManagerPersistence aPersistence) { + 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 mLayerManager; + } + + return nsBaseWidget::GetLayerManager(aShadowManager, aBackendHint, + aPersistence); +} + +void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) { + if (delegate) { + mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate(); + MOZ_ASSERT(mCompositorWidgetDelegate, + "nsWindow::SetCompositorWidgetDelegate called with a " + "non-PlatformCompositorWidgetDelegate"); +#ifdef MOZ_WAYLAND + MaybeResumeCompositor(); +#endif + } else { + mCompositorWidgetDelegate = nullptr; + } +} + +void nsWindow::ClearCachedResources() { + if (mLayerManager && mLayerManager->GetBackendType() == + mozilla::layers::LayersBackend::LAYERS_BASIC) { + mLayerManager->ClearCachedResources(); + } + + GList* children = gdk_window_peek_children(mGdkWindow); + for (GList* list = children; list; list = list->next) { + nsWindow* window = get_window_for_gdk_window(GDK_WINDOW(list->data)); + if (window) { + window->ClearCachedResources(); + } + } +} + +/* nsWindow::UpdateClientOffsetFromCSDWindow() is designed to be called from + * nsWindow::OnConfigureEvent() when mContainer window is already positioned. + * + * It works only for CSD decorated GtkWindow. + */ +void nsWindow::UpdateClientOffsetFromCSDWindow() { + int x, y; + gdk_window_get_position(mGdkWindow, &x, &y); + + x = GdkCoordToDevicePixels(x); + y = GdkCoordToDevicePixels(y); + + if (mClientOffset.x != x || mClientOffset.y != y) { + mClientOffset = nsIntPoint(x, y); + + LOG(("nsWindow::UpdateClientOffsetFromCSDWindow [%p] %d, %d\n", (void*)this, + mClientOffset.x, mClientOffset.y)); + + // 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); + } +} + +nsresult nsWindow::SetNonClientMargins(LayoutDeviceIntMargin& aMargins) { + SetDrawsInTitlebar(aMargins.top == 0); + return NS_OK; +} + +void nsWindow::SetDrawsInTitlebar(bool aState) { + LOG(("nsWindow::SetDrawsInTitlebar() [%p] State %d mCSDSupportLevel %d\n", + (void*)this, aState, (int)mCSDSupportLevel)); + + if (!mShell || mCSDSupportLevel == CSD_SUPPORT_NONE || + aState == mDrawInTitlebar) { + return; + } + + if (mCSDSupportLevel == CSD_SUPPORT_SYSTEM) { + SetWindowDecoration(aState ? eBorderStyle_border : mBorderStyle); + } else if (mCSDSupportLevel == CSD_SUPPORT_CLIENT) { + LOG((" Using CSD mode\n")); + + /* 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. + */ + 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)); + + if (aState) { + // 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), gtk_fixed_new()); + } else { + gtk_window_set_titlebar(GTK_WINDOW(mShell), 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)); + mNeedsShow = true; + NativeResize(); + + // Label mShell toplevel window so property_notify_event_cb callback + // can find its way home. + g_object_set_data(G_OBJECT(gtk_widget_get_window(mShell)), "nsWindow", + this); +#ifdef MOZ_X11 + SetCompositorHint(GTK_WIDGET_COMPOSIDED_ENABLED); +#endif + RefreshWindowClass(); + + gtk_widget_destroy(tmpWindow); + } + + mDrawInTitlebar = aState; + + if (mTransparencyBitmapForTitlebar) { + if (mDrawInTitlebar && mSizeState == nsSizeMode_Normal && !mIsTiled) { + UpdateTitlebarTransparencyBitmap(); + } else { + ClearTransparencyBitmap(); + } + } +} + +GtkWindow* nsWindow::GetCurrentTopmostWindow() { + GtkWindow* parentWindow = GTK_WINDOW(GetGtkWidget()); + GtkWindow* topmostParentWindow = nullptr; + while (parentWindow) { + topmostParentWindow = parentWindow; + parentWindow = gtk_window_get_transient_for(parentWindow); + } + return topmostParentWindow; +} + +gint nsWindow::GdkScaleFactor() { + // We depend on notify::scale-factor callback which is reliable for toplevel + // windows only, so don't use scale cache for popup windows. + if (mWindowType == eWindowType_toplevel && !mWindowScaleFactorChanged) { + return mWindowScaleFactor; + } + + GdkWindow* scaledGdkWindow = mGdkWindow; + if (!mIsX11Display) { + // For popup windows/dialogs with parent window we need to get scale factor + // of the topmost window. Otherwise the scale factor of the popup is + // not updated during it's hidden. + if (mWindowType == eWindowType_popup || mWindowType == eWindowType_dialog) { + // Get toplevel window for scale factor: + GtkWindow* topmostParentWindow = GetCurrentTopmostWindow(); + if (topmostParentWindow) { + scaledGdkWindow = + gtk_widget_get_window(GTK_WIDGET(topmostParentWindow)); + } else { + NS_WARNING("Popup/Dialog has no parent."); + } + // Fallback for windows which parent has been unrealized. + if (!scaledGdkWindow) { + scaledGdkWindow = mGdkWindow; + } + } + } + + // Available as of GTK 3.10+ + static auto sGdkWindowGetScaleFactorPtr = + (gint(*)(GdkWindow*))dlsym(RTLD_DEFAULT, "gdk_window_get_scale_factor"); + if (sGdkWindowGetScaleFactorPtr && scaledGdkWindow) { + mWindowScaleFactor = (*sGdkWindowGetScaleFactorPtr)(scaledGdkWindow); + mWindowScaleFactorChanged = false; + } else { + mWindowScaleFactor = ScreenHelperGTK::GetGTKMonitorScaleFactor(); + } + + return mWindowScaleFactor; +} + +gint nsWindow::DevicePixelsToGdkCoordRoundUp(int pixels) { + gint scale = GdkScaleFactor(); + return (pixels + scale - 1) / scale; +} + +gint nsWindow::DevicePixelsToGdkCoordRoundDown(int pixels) { + gint scale = GdkScaleFactor(); + return pixels / scale; +} + +GdkPoint nsWindow::DevicePixelsToGdkPointRoundDown(LayoutDeviceIntPoint point) { + gint scale = GdkScaleFactor(); + return {point.x / scale, point.y / scale}; +} + +GdkRectangle nsWindow::DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect rect) { + gint scale = GdkScaleFactor(); + int x = rect.x / scale; + int y = rect.y / scale; + int right = (rect.x + rect.width + scale - 1) / scale; + int bottom = (rect.y + rect.height + scale - 1) / scale; + return {x, y, right - x, bottom - y}; +} + +GdkRectangle nsWindow::DevicePixelsToGdkSizeRoundUp( + LayoutDeviceIntSize pixelSize) { + gint scale = GdkScaleFactor(); + gint width = (pixelSize.width + scale - 1) / scale; + gint height = (pixelSize.height + scale - 1) / scale; + return {0, 0, width, height}; +} + +int nsWindow::GdkCoordToDevicePixels(gint coord) { + return coord * GdkScaleFactor(); +} + +LayoutDeviceIntPoint nsWindow::GdkEventCoordsToDevicePixels(gdouble x, + gdouble y) { + gint scale = GdkScaleFactor(); + return LayoutDeviceIntPoint::Round(x * scale, y * scale); +} + +LayoutDeviceIntPoint nsWindow::GdkPointToDevicePixels(GdkPoint point) { + gint scale = GdkScaleFactor(); + return LayoutDeviceIntPoint(point.x * scale, point.y * scale); +} + +LayoutDeviceIntRect nsWindow::GdkRectToDevicePixels(GdkRectangle rect) { + gint scale = GdkScaleFactor(); + return LayoutDeviceIntRect(rect.x * scale, rect.y * scale, rect.width * scale, + rect.height * scale); +} + +nsresult nsWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint, + uint32_t aNativeMessage, + uint32_t aModifierFlags, + nsIObserver* aObserver) { + AutoObserverNotifier notifier(aObserver, "mouseevent"); + + if (!mGdkWindow) { + return NS_OK; + } + + GdkDisplay* display = gdk_window_get_display(mGdkWindow); + + // 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. + if (aNativeMessage == GDK_BUTTON_PRESS || + aNativeMessage == GDK_BUTTON_RELEASE) { + GdkEvent event; + memset(&event, 0, sizeof(GdkEvent)); + event.type = (GdkEventType)aNativeMessage; + event.button.button = 1; + event.button.window = mGdkWindow; + event.button.time = GDK_CURRENT_TIME; + + // Get device for event source + GdkDeviceManager* device_manager = gdk_display_get_device_manager(display); + event.button.device = gdk_device_manager_get_client_pointer(device_manager); + + 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); + } else { + // 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(). + GdkScreen* screen = gdk_window_get_screen(mGdkWindow); + GdkPoint point = DevicePixelsToGdkPointRoundDown(aPoint); + gdk_display_warp_pointer(display, screen, point.x, point.y); + } + + return NS_OK; +} + +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; + } + + 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); + 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); + + 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; +} + +nsWindow::CSDSupportLevel nsWindow::GetSystemCSDSupportLevel(bool aIsPopup) { + if (sCSDSupportLevel != CSD_SUPPORT_UNKNOWN) { + return sCSDSupportLevel; + } + + // Allow MOZ_GTK_TITLEBAR_DECORATION to override our heuristics + const char* decorationOverride = getenv("MOZ_GTK_TITLEBAR_DECORATION"); + if (decorationOverride) { + if (strcmp(decorationOverride, "none") == 0) { + sCSDSupportLevel = CSD_SUPPORT_NONE; + } else if (strcmp(decorationOverride, "client") == 0) { + sCSDSupportLevel = CSD_SUPPORT_CLIENT; + } else if (strcmp(decorationOverride, "system") == 0) { + sCSDSupportLevel = CSD_SUPPORT_SYSTEM; + } + return sCSDSupportLevel; + } + + // nsWindow::GetSystemCSDSupportLevel can be called from various threads + // so we can't use gfxPlatformGtk here. + if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) { + sCSDSupportLevel = CSD_SUPPORT_CLIENT; + return sCSDSupportLevel; + } + + const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP"); + if (currentDesktop) { + // GNOME Flashback (fallback) + if (strstr(currentDesktop, "GNOME-Flashback:GNOME") != nullptr) { + sCSDSupportLevel = aIsPopup ? CSD_SUPPORT_CLIENT : CSD_SUPPORT_SYSTEM; + // Pop Linux Bug 1629198 + } else if (strstr(currentDesktop, "pop:GNOME") != nullptr) { + sCSDSupportLevel = CSD_SUPPORT_CLIENT; + // gnome-shell + } else if (strstr(currentDesktop, "GNOME") != nullptr) { + sCSDSupportLevel = aIsPopup ? CSD_SUPPORT_CLIENT : CSD_SUPPORT_SYSTEM; + } else if (strstr(currentDesktop, "XFCE") != nullptr) { + sCSDSupportLevel = CSD_SUPPORT_CLIENT; + } else if (strstr(currentDesktop, "X-Cinnamon") != nullptr) { + sCSDSupportLevel = CSD_SUPPORT_SYSTEM; + // KDE Plasma + } else if (strstr(currentDesktop, "KDE") != nullptr) { + sCSDSupportLevel = CSD_SUPPORT_CLIENT; + } else if (strstr(currentDesktop, "Enlightenment") != nullptr) { + sCSDSupportLevel = CSD_SUPPORT_CLIENT; + } else if (strstr(currentDesktop, "LXDE") != nullptr) { + sCSDSupportLevel = CSD_SUPPORT_CLIENT; + } else if (strstr(currentDesktop, "openbox") != nullptr) { + sCSDSupportLevel = CSD_SUPPORT_CLIENT; + } else if (strstr(currentDesktop, "i3") != nullptr) { + sCSDSupportLevel = CSD_SUPPORT_NONE; + } else if (strstr(currentDesktop, "MATE") != nullptr) { + sCSDSupportLevel = CSD_SUPPORT_CLIENT; + // Ubuntu Unity + } else if (strstr(currentDesktop, "Unity") != nullptr) { + sCSDSupportLevel = CSD_SUPPORT_SYSTEM; + // Elementary OS + } else if (strstr(currentDesktop, "Pantheon") != nullptr) { + sCSDSupportLevel = CSD_SUPPORT_SYSTEM; + } else if (strstr(currentDesktop, "LXQt") != nullptr) { + sCSDSupportLevel = CSD_SUPPORT_SYSTEM; + } else if (strstr(currentDesktop, "Deepin") != nullptr) { + sCSDSupportLevel = CSD_SUPPORT_CLIENT; + } else { +// Release or beta builds are not supposed to be broken +// so disable titlebar rendering on untested/unknown systems. +#if defined(RELEASE_OR_BETA) + sCSDSupportLevel = CSD_SUPPORT_NONE; +#else + sCSDSupportLevel = CSD_SUPPORT_CLIENT; +#endif + } + } else { + sCSDSupportLevel = CSD_SUPPORT_NONE; + } + + // 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 (sCSDSupportLevel == CSD_SUPPORT_SYSTEM) { + const char* csdOverride = getenv("GTK_CSD"); + if (csdOverride && g_strcmp0(csdOverride, "1") == 0) { + sCSDSupportLevel = CSD_SUPPORT_CLIENT; + } + } + + return sCSDSupportLevel; +} + +bool nsWindow::TitlebarUseShapeMask() { + static int useShapeMask = []() { + // Don't use titlebar shape mask on Wayland + if (!gfxPlatformGtk::GetPlatform()->IsX11Display()) { + return false; + } + + // We can'y use shape masks on Mutter/X.org as we can't resize Firefox + // window there (Bug 1530252). + const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP"); + if (currentDesktop) { + if (strstr(currentDesktop, "GNOME") != nullptr) { + const char* sessionType = getenv("XDG_SESSION_TYPE"); + if (sessionType && strstr(sessionType, "x11") != nullptr) { + return false; + } + } + } + + return Preferences::GetBool("widget.titlebar-x11-use-shape-mask", false); + }(); + return useShapeMask; +} + +bool nsWindow::HideTitlebarByDefault() { + static int hideTitlebar = []() { + // When user defined widget.default-hidden-titlebar don't do any + // heuristics and just follow it. + if (Preferences::HasUserValue("widget.default-hidden-titlebar")) { + return Preferences::GetBool("widget.default-hidden-titlebar", false); + } + + // Don't hide titlebar when it's disabled on current desktop. + const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP"); + if (!currentDesktop || GetSystemCSDSupportLevel() == CSD_SUPPORT_NONE) { + return false; + } + + // We hide system titlebar on Gnome/ElementaryOS without any restriction. + return ((strstr(currentDesktop, "GNOME-Flashback:GNOME") != nullptr || + strstr(currentDesktop, "GNOME") != nullptr || + strstr(currentDesktop, "Pantheon") != nullptr)); + }(); + return hideTitlebar; +} + +int32_t nsWindow::RoundsWidgetCoordinatesTo() { return GdkScaleFactor(); } + +void nsWindow::GetCompositorWidgetInitData( + mozilla::widget::CompositorWidgetInitData* aInitData) { + // Make sure the window XID is propagated to X server, we can fail otherwise + // in GPU process (Bug 1401634). + if (mXDisplay && mXWindow != X11None) { + XFlush(mXDisplay); + } + + bool isShaped = + mIsTransparent && !mHasAlphaVisual && !mTransparencyBitmapForTitlebar; + *aInitData = mozilla::widget::GtkCompositorWidgetInitData( + (mXWindow != X11None) ? mXWindow : (uintptr_t) nullptr, + mXDisplay ? nsCString(XDisplayString(mXDisplay)) : nsCString(), isShaped, + mIsX11Display, GetClientSize()); +} + +#ifdef MOZ_WAYLAND +bool nsWindow::WaylandSurfaceNeedsClear() { + if (mContainer) { + return moz_container_wayland_surface_needs_clear(MOZ_CONTAINER(mContainer)); + } + return false; +} +#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 (!mIsX11Display) { + return; + } + + if (!mShell) { + return; + } + + progressPercent = MIN(progressPercent, 100); + + set_window_hint_cardinal(GDK_WINDOW_XID(gtk_widget_get_window(mShell)), + PROGRESS_HINT, progressPercent); +#endif // MOZ_X11 +} + +#ifdef MOZ_X11 +void nsWindow::SetCompositorHint(WindowComposeRequest aState) { + if (!mIsX11Display) { + return; + } + + gulong value = aState; + GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL); + gdk_property_change(gtk_widget_get_window(mShell), + 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 +nsresult nsWindow::GetScreenRect(LayoutDeviceIntRect* aRect) { + typedef struct _GdkMonitor GdkMonitor; + static auto s_gdk_display_get_monitor_at_window = + (GdkMonitor * (*)(GdkDisplay*, GdkWindow*)) + dlsym(RTLD_DEFAULT, "gdk_display_get_monitor_at_window"); + + static auto s_gdk_monitor_get_workarea = + (void (*)(GdkMonitor*, GdkRectangle*))dlsym(RTLD_DEFAULT, + "gdk_monitor_get_workarea"); + + if (!s_gdk_display_get_monitor_at_window || !s_gdk_monitor_get_workarea) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + GtkWindow* topmostParentWindow = GetCurrentTopmostWindow(); + GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(topmostParentWindow)); + + GdkMonitor* monitor = + s_gdk_display_get_monitor_at_window(gdk_display_get_default(), gdkWindow); + if (monitor) { + GdkRectangle workArea; + s_gdk_monitor_get_workarea(monitor, &workArea); + // The monitor offset won't help us in Wayland, because we can't get the + // absolute position of our window. + aRect->x = aRect->y = 0; + aRect->width = workArea.width; + aRect->height = workArea.height; + LOG((" workarea for [%p], monitor %p: x%d y%d w%d h%d\n", this, monitor, + workArea.x, workArea.y, workArea.width, workArea.height)); + return NS_OK; + } + return NS_ERROR_NOT_IMPLEMENTED; +} +#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; + } + + // Get our toplevel nsWindow. + if (!window->mIsTopLevel) { + GtkWidget* widget = window->GetMozContainerWidget(); + if (!widget) { + return false; + } + + GtkWidget* toplevelWidget = gtk_widget_get_toplevel(widget); + window = get_window_for_gtk_widget(toplevelWidget); + 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(void) { + 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(void) { + 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); + } + } +} + +GtkTextDirection nsWindow::GetTextDirection() { + nsIFrame* frame = GetFrame(); + if (!frame) { + return GTK_TEXT_DIR_LTR; + } + + WritingMode wm = frame->GetWritingMode(); + return wm.IsPhysicalLTR() ? GTK_TEXT_DIR_LTR : GTK_TEXT_DIR_RTL; +} + +void nsWindow::LockAspectRatio(bool aShouldLock) { + if (!gUseAspectRatio) { + return; + } + + if (aShouldLock) { + int decWidth = 0, decHeight = 0; + AddCSDDecorationSize(&decWidth, &decHeight); + + float width = + (float)DevicePixelsToGdkCoordRoundDown(mBounds.width) + decWidth; + float height = + (float)DevicePixelsToGdkCoordRoundDown(mBounds.height) + decHeight; + + mAspectRatio = width / height; + LOG(("nsWindow::LockAspectRatio() [%p] width %f height %f aspect %f\n", + (void*)this, width, height, mAspectRatio)); + } else { + mAspectRatio = 0.0; + LOG(("nsWindow::LockAspectRatio() [%p] removed aspect ratio\n", + (void*)this)); + } + + ApplySizeConstraints(); +} + +#ifdef MOZ_WAYLAND +void nsWindow::SetEGLNativeWindowSize( + const LayoutDeviceIntSize& aEGLWindowSize) { + if (mContainer && !mIsX11Display) { + moz_container_wayland_egl_window_set_size(mContainer, aEGLWindowSize.width, + aEGLWindowSize.height); + } +} + +nsWindow* nsWindow::GetFocusedWindow() { return gFocusWindow; } +#endif + +LayoutDeviceIntRect nsWindow::GetMozContainerSize() { + LayoutDeviceIntRect size(0, 0, 0, 0); + if (mContainer) { + GtkAllocation allocation; + gtk_widget_get_allocation(GTK_WIDGET(mContainer), &allocation); + int scale = GdkScaleFactor(); + size.width = allocation.width * scale; + size.height = allocation.height * scale; + } + return size; +} |