Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=2:tabstop=2:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsWindow.h"
#include <algorithm>
#include <dlfcn.h>
#include <gdk/gdkkeysyms.h>
#include <wchar.h>
#include "ClientLayerManager.h"
#include "gfx2DGlue.h"
#include "gfxContext.h"
#include "gfxImageSurface.h"
#include "gfxPlatformGtk.h"
#include "gfxUtils.h"
#include "GLContextProvider.h"
#include "GLContext.h"
#include "GtkCompositorWidget.h"
#include "gtkdrawing.h"
#include "imgIContainer.h"
#include "InputData.h"
#include "Layers.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/WheelEventBinding.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/gfx/HelpersCairo.h"
#include "mozilla/layers/LayersTypes.h"
#include "mozilla/layers/CompositorBridgeParent.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/layers/KnowsCompositor.h"
#include "mozilla/layers/WebRenderBridgeChild.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/layers/APZInputBridge.h"
#include "mozilla/layers/IAPZCTreeManager.h"
#include "mozilla/Likely.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/StaticPrefs_ui.h"
#include "mozilla/StaticPrefs_widget.h"
#include "mozilla/TextEventDispatcher.h"
#include "mozilla/TextEvents.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/WidgetUtils.h"
#include "mozilla/WritingModes.h"
#include "mozilla/X11Util.h"
#include "mozilla/XREAppData.h"
#include "NativeKeyBindings.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsAppRunner.h"
#include "nsDragService.h"
#include "nsGTKToolkit.h"
#include "nsGtkKeyUtils.h"
#include "nsGtkCursors.h"
#include "nsGfxCIID.h"
#include "nsGtkUtils.h"
#include "nsIFile.h"
#include "nsIGSettingsService.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsImageToPixbuf.h"
#include "nsINode.h"
#include "nsIRollupListener.h"
#include "nsIScreenManager.h"
#include "nsIUserIdleServiceInternal.h"
#include "nsIWidgetListener.h"
#include "nsLayoutUtils.h"
#include "nsMenuPopupFrame.h"
#include "nsPresContext.h"
#include "nsShmImage.h"
#include "nsString.h"
#include "nsWidgetsCID.h"
#include "nsViewManager.h"
#include "nsXPLookAndFeel.h"
#include "prlink.h"
#include "ScreenHelperGTK.h"
#include "SystemTimeConverter.h"
#include "WidgetUtilsGtk.h"
#include "mozilla/X11Util.h"
#ifdef ACCESSIBILITY
# include "mozilla/a11y/LocalAccessible.h"
# include "mozilla/a11y/Platform.h"
# include "nsAccessibilityService.h"
#endif
#ifdef MOZ_X11
# include <gdk/gdkkeysyms-compat.h>
# include <X11/Xatom.h>
# include <X11/extensions/XShm.h>
# include <X11/extensions/shape.h>
# include "gfxXlibSurface.h"
# include "GLContextGLX.h" // for GLContextGLX::FindVisual()
# include "GLContextEGL.h" // for GLContextEGL::FindVisual()
# include "WindowSurfaceX11Image.h"
# include "WindowSurfaceX11SHM.h"
# include "WindowSurfaceXRender.h"
#endif
#ifdef MOZ_WAYLAND
# include "nsIClipboard.h"
# include "nsView.h"
#endif
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::layers;
using namespace mozilla::widget;
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* cr);
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);
MOZ_CAN_RUN_SCRIPT static gboolean button_press_event_cb(GtkWidget* widget,
GdkEventButton* event);
static gboolean button_release_event_cb(GtkWidget* widget,
GdkEventButton* event);
static gboolean focus_in_event_cb(GtkWidget* widget, GdkEventFocus* event);
static gboolean focus_out_event_cb(GtkWidget* widget, GdkEventFocus* event);
static gboolean key_press_event_cb(GtkWidget* widget, GdkEventKey* event);
static gboolean key_release_event_cb(GtkWidget* widget, GdkEventKey* event);
static gboolean property_notify_event_cb(GtkWidget* widget,
GdkEventProperty* event);
static gboolean scroll_event_cb(GtkWidget* widget, GdkEventScroll* event);
static 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::GtkWindowDecoration nsWindow::sGtkWindowDecoration =
GTK_DECORATION_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 gUseAspectRatio = true;
static GList* gVisibleWaylandPopupWindows = nullptr;
static uint32_t gLastTouchID = 0;
#define NS_WINDOW_TITLE_MAX_LENGTH 4095
#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) {
*aX = 0;
*aY = 0;
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),
mWindowScaleFactorChanged(true),
mWindowScaleFactor(1),
mCompositedScreen(gdk_screen_is_composited(gdk_screen_get_default())),
#ifdef MOZ_WAYLAND
mNeedsCompositorResume(false),
mCompositorInitiallyPaused(false),
mNativePointerLockCenter(LayoutDeviceIntPoint()),
#endif
mShell(nullptr),
mContainer(nullptr),
mGdkWindow(nullptr),
mWindowShouldStartDragging(false),
mCompositorWidgetDelegate(nullptr),
mHasMappedToplevel(false),
mRetryPointerGrab(false),
mSizeState(nsSizeMode_Normal),
mAspectRatio(0.0f),
mAspectRatioSaved(0.0f),
mLastScrollEventTime(GDK_CURRENT_TIME),
mPendingConfigures(0),
mGtkWindowDecoration(GTK_DECORATION_NONE),
mDrawToContainer(false),
mDrawInTitlebar(false),
mTitlebarBackdropState(false),
mIsPIPWindow(false),
mIsWaylandPanelWindow(false),
mAlwaysOnTop(false),
mIsTransparent(false),
mTransparencyBitmap(nullptr),
mTransparencyBitmapWidth(0),
mTransparencyBitmapHeight(0),
mTransparencyBitmapForTitlebar(false),
mHasAlphaVisual(false),
mLastMotionPressure(0),
mLastSizeMode(nsSizeMode_Normal),
mBoundsAreValid(true),
mPreferredPopupRectFlushed(false),
mWaitingForMoveToRectCB(false),
mPendingSizeRect(LayoutDeviceIntRect(0, 0, 0, 0))
#ifdef ACCESSIBILITY
,
mRootAccessible(nullptr)
#endif
#ifdef MOZ_X11
,
mXWindow(X11None),
mXVisual(nullptr),
mXDepth(0)
#endif
#ifdef MOZ_WAYLAND
,
mLockedPointer(nullptr),
mRelativePointer(nullptr)
#endif
{
mWindowType = eWindowType_child;
mSizeConstraints.mMaxSize = GetSafeWindowSize(mSizeConstraints.mMaxSize);
if (!gGlobalsInitialized) {
gGlobalsInitialized = true;
// It's OK if either of these fail, but it may not be one day.
initialize_prefs();
#ifdef MOZ_WAYLAND
// Wayland provides clipboard data to application on focus-in event
// so we need to init our clipboard hooks before we create window
// and get focus.
if (GdkIsWaylandDisplay()) {
nsCOMPtr<nsIClipboard> clipboard =
do_GetService("@mozilla.org/widget/clipboard;1");
NS_ASSERTION(clipboard, "Failed to init clipboard!");
}
#endif
}
}
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) {
LOG(("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 FractionalScaleFactor() * gfxPlatformGtk::GetFontScaleFactor();
}
DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScale() {
#ifdef MOZ_WAYLAND
if (GdkIsWaylandDisplay()) {
return DesktopToLayoutDeviceScale(GdkCeiledScaleFactor());
}
#endif
// In Gtk/X11, we manage windows using device pixels.
return DesktopToLayoutDeviceScale(1.0);
}
DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScaleByScreen() {
#ifdef MOZ_WAYLAND
// In Wayland there's no way to get absolute position of the window and use it
// to determine the screen factor of the monitor on which the window is
// placed. The window is notified of the current scale factor but not at this
// point, so the GdkScaleFactor can return wrong value which can lead to wrong
// popup placement. We need to use parent's window scale factor for the new
// one.
if (GdkIsWaylandDisplay()) {
nsView* view = nsView::GetViewFor(this);
if (view) {
nsView* parentView = view->GetParent();
if (parentView) {
nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr);
if (parentWidget) {
return DesktopToLayoutDeviceScale(
parentWidget->RoundsWidgetCoordinatesTo());
}
NS_WARNING("Widget has no parent");
}
} else {
NS_WARNING("Cannot find widget view");
}
}
#endif
return nsBaseWidget::GetDesktopToDeviceScale();
}
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 (mSizeState == nsSizeMode_Normal &&
mGtkWindowDecoration == GTK_DECORATION_CLIENT && mDrawInTitlebar) {
GtkBorder decorationSize = GetCSDDecorationSize(!mIsTopLevel);
*aWidth += decorationSize.left + decorationSize.right;
*aHeight += decorationSize.top + decorationSize.bottom;
}
}
#ifdef MOZ_WAYLAND
bool nsWindow::GetCSDDecorationOffset(int* aDx, int* aDy) {
if (mSizeState == nsSizeMode_Normal &&
mGtkWindowDecoration == GTK_DECORATION_CLIENT && mDrawInTitlebar) {
GtkBorder decorationSize = GetCSDDecorationSize(!mIsTopLevel);
*aDx = decorationSize.left;
*aDy = decorationSize.top;
return true;
}
return false;
}
#endif
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)) {
if (GdkIsWaylandDisplay()) {
gtk_widget_set_size_request(GTK_WIDGET(mContainer), geometry.min_width,
geometry.min_height);
}
AddCSDDecorationSize(&geometry.min_width, &geometry.min_height);
hints |= GDK_HINT_MIN_SIZE;
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 GdkIsWaylandDisplay() && 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);
}
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));
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);
// Convert newBounds to "absolute" coordinates (relative to toplevel)
newBounds.x += x_parent * GdkCeiledScaleFactor();
newBounds.y += y_parent * GdkCeiledScaleFactor();