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 "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/Components.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/CompositorBridgeChild.h"
#include "mozilla/layers/CompositorBridgeParent.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/layers/KnowsCompositor.h"
#include "mozilla/layers/WebRenderBridgeChild.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/layers/APZInputBridge.h"
#include "mozilla/layers/IAPZCTreeManager.h"
#include "mozilla/Likely.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/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"
#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 widget_map_cb(GtkWidget* widget);
static void widget_unrealize_cb(GtkWidget* widget);
static void size_allocate_cb(GtkWidget* widget, GtkAllocation* allocation);
static void toplevel_window_size_allocate_cb(GtkWidget* widget,
GtkAllocation* allocation);
static gboolean delete_event_cb(GtkWidget* widget, GdkEventAny* event);
static gboolean enter_notify_event_cb(GtkWidget* widget,
GdkEventCrossing* event);
static gboolean leave_notify_event_cb(GtkWidget* widget,
GdkEventCrossing* event);
static gboolean motion_notify_event_cb(GtkWidget* widget,
GdkEventMotion* event);
MOZ_CAN_RUN_SCRIPT static gboolean button_press_event_cb(GtkWidget* widget,
GdkEventButton* event);
static gboolean button_release_event_cb(GtkWidget* widget,
GdkEventButton* event);
static gboolean focus_in_event_cb(GtkWidget* widget, GdkEventFocus* event);
static gboolean focus_out_event_cb(GtkWidget* widget, GdkEventFocus* event);
static gboolean key_press_event_cb(GtkWidget* widget, GdkEventKey* event);
static gboolean key_release_event_cb(GtkWidget* widget, GdkEventKey* event);
static gboolean property_notify_event_cb(GtkWidget* widget,
GdkEventProperty* event);
static gboolean scroll_event_cb(GtkWidget* widget, GdkEventScroll* event);
static void hierarchy_changed_cb(GtkWidget* widget,
GtkWidget* previous_toplevel);
static gboolean window_state_event_cb(GtkWidget* widget,
GdkEventWindowState* event);
static void settings_xft_dpi_changed_cb(GtkSettings* settings,
GParamSpec* pspec, nsWindow* data);
static void check_resize_cb(GtkContainer* container, gpointer user_data);
static void screen_composited_changed_cb(GdkScreen* screen, gpointer user_data);
static void widget_composited_changed_cb(GtkWidget* widget, gpointer user_data);
static void scale_changed_cb(GtkWidget* widget, GParamSpec* aPSpec,
gpointer aPointer);
static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent);
static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent);
static nsWindow* GetFirstNSWindowForGDKWindow(GdkWindow* aGdkWindow);
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#ifdef MOZ_X11
static GdkFilterReturn popup_take_focus_filter(GdkXEvent* gdk_xevent,
GdkEvent* event, gpointer data);
#endif /* MOZ_X11 */
#ifdef __cplusplus
}
#endif /* __cplusplus */
static gboolean drag_motion_event_cb(GtkWidget* aWidget,
GdkDragContext* aDragContext, gint aX,
gint aY, guint aTime, gpointer aData);
static void drag_leave_event_cb(GtkWidget* aWidget,
GdkDragContext* aDragContext, guint aTime,
gpointer aData);
static gboolean drag_drop_event_cb(GtkWidget* aWidget,
GdkDragContext* aDragContext, gint aX,
gint aY, guint aTime, gpointer aData);
static void drag_data_received_event_cb(GtkWidget* aWidget,
GdkDragContext* aDragContext, gint aX,
gint aY,
GtkSelectionData* aSelectionData,
guint aInfo, guint32 aTime,
gpointer aData);
/* initialization static functions */
static nsresult initialize_prefs(void);
static guint32 sLastUserInputTime = GDK_CURRENT_TIME;
static guint32 sRetryGrabTime;
static SystemTimeConverter<guint32>& TimeConverter() {
static SystemTimeConverter<guint32> sTimeConverterSingleton;
return sTimeConverterSingleton;
}
bool nsWindow::sTransparentMainWindow = false;
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 gTransparentWindows = true;
static bool gUseMoveToRect = true;
static bool gUseAspectRatio = true;
static uint32_t gLastTouchID = 0;
#define NS_WINDOW_TITLE_MAX_LENGTH 4095
#define kWindowPositionSlop 20
// cursor cache
static GdkCursor* gCursorCache[eCursorCount];
// Sometimes this actually also includes the state of the modifier keys, but
// only the button state bits are used.
static guint gButtonState;
static inline int32_t GetBitmapStride(int32_t width) {
#if defined(MOZ_X11)
return (width + 7) / 8;
#else
return cairo_format_stride_for_width(CAIRO_FORMAT_A1, width);
#endif
}
static inline bool TimestampIsNewerThan(guint32 a, guint32 b) {
// Timestamps are just the least significant bits of a monotonically
// increasing function, and so the use of unsigned overflow arithmetic.
return a - b <= G_MAXUINT32 / 2;
}
static void UpdateLastInputEventTime(void* aGdkEvent) {
nsCOMPtr<nsIUserIdleServiceInternal> idleService =
do_GetService("@mozilla.org/widget/useridleservice;1");
if (idleService) {
idleService->ResetIdleTimeOut(0);
}
guint timestamp = gdk_event_get_time(static_cast<GdkEvent*>(aGdkEvent));
if (timestamp == GDK_CURRENT_TIME) return;
sLastUserInputTime = timestamp;
}
void GetWindowOrigin(GdkWindow* aWindow, int* aX, int* aY) {
*aX = 0;
*aY = 0;
if (aWindow) {
gdk_window_get_origin(aWindow, aX, aY);
}
// GetWindowOrigin / gdk_window_get_origin is very fast on Wayland as the
// window position is cached by Gtk.
// 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()
: mIsDestroyed(false),
mNeedsDispatchResized(false),
mIsShown(false),
mNeedsShow(false),
mIsMapped(false),
mEnabled(true),
mCreated(false),
mHandleTouchEvent(false),
mIsDragPopup(false),
mPopupHint(),
mWindowScaleFactorChanged(true),
mWindowScaleFactor(1),
mCompositedScreen(gdk_screen_is_composited(gdk_screen_get_default())),
mIsAccelerated(false),
mShell(nullptr),
mContainer(nullptr),
mGdkWindow(nullptr),
mWindowShouldStartDragging(false),
mCompositorWidgetDelegate(nullptr),
mCompositorState(COMPOSITOR_ENABLED),
mCompositorPauseTimeoutID(0),
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),
mIsChildWindow(false),
mAlwaysOnTop(false),
mNoAutoHide(false),
mMouseTransparent(false),
mIsTransparent(false),
mTransparencyBitmap(nullptr),
mTransparencyBitmapWidth(0),
mTransparencyBitmapHeight(0),
mTransparencyBitmapForTitlebar(false),
mHasAlphaVisual(false),
mLastMotionPressure(0),
mLastSizeMode(nsSizeMode_Normal),
mBoundsAreValid(true),
mPopupTrackInHierarchy(false),
mPopupTrackInHierarchyConfigured(false),
mHiddenPopupPositioned(false),
mPopupPosition(),
mPopupAnchored(false),
mPopupContextMenu(false),
mRelativePopupPosition(),
mRelativePopupOffset(),
mPopupMatchesLayout(false),
mPopupChanged(false),
mPopupTemporaryHidden(false),
mPopupClosed(false),
mPopupUseMoveToRect(false),
mPopupLastAnchor(),
mPreferredPopupRect(),
mPreferredPopupRectFlushed(false),
mWaitingForMoveToRectCallback(false),
mNewBoundsAfterMoveToRect(LayoutDeviceIntRect(0, 0, 0, 0))
#ifdef ACCESSIBILITY
,
mRootAccessible(nullptr)
#endif
#ifdef MOZ_X11
,
mXWindow(X11None),
mXVisual(nullptr),
mXDepth(0),
mIsShaped(false)
#endif
#ifdef MOZ_WAYLAND
,
mNativePointerLockCenter(LayoutDeviceIntPoint()),
mLockedPointer(nullptr),
mRelativePointer(nullptr),
mXdgToken(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()");
delete[] mTransparencyBitmap;
mTransparencyBitmap = nullptr;
Destroy();
}
/* static */
void nsWindow::ReleaseGlobals() {
for (auto& cursor : gCursorCache) {
if (cursor) {
g_object_unref(cursor);
cursor = nullptr;
}
}
}
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() {
LOG("nsWindow::DispatchResized() size [%d, %d]", (int)(mBounds.width),
(int)(mBounds.height));
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;
}
// Walk the list of child windows and call destroy on them.
void nsWindow::DestroyChildWindows() {
LOG("nsWindow::DestroyChildWindows()");
if (!mGdkWindow) {
return;
}
while (GList* children = gdk_window_peek_children(mGdkWindow)) {
GdkWindow* child = GDK_WINDOW(children->data);
nsWindow* kid = get_window_for_gdk_window(child);
if (kid) {
kid->Destroy();
}
}
}
void nsWindow::Destroy() {
if (mIsDestroyed || !mCreated) return;
LOG("nsWindow::Destroy\n");
// Clear up WebRender queue
RevokeTransactionIdAllocator();
DisableRenderingToWindow();
mIsDestroyed = true;
mCreated = false;
/** Need to clean our LayerManager up while still alive */
if (mWindowRenderer) {
mWindowRenderer->Destroy();
}
mWindowRenderer = nullptr;
#ifdef MOZ_WAYLAND
// Shut down our local vsync source
if (mWaylandVsyncSource) {
mWaylandVsyncSource->Shutdown();
mWaylandVsyncSource = nullptr;
}
g_clear_pointer(&mXdgToken, xdg_activation_token_v1_destroy);
#endif
if (mCompositorPauseTimeoutID) {
g_source_remove(mCompositorPauseTimeoutID);
mCompositorPauseTimeoutID = 0;
}
// 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();
// Ensure any resources assigned to the window get cleaned up first
// to avoid double-freeing.
mSurfaceProvider.CleanupResources();
g_signal_handlers_disconnect_by_data(gtk_settings_get_default(), this);
nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
if (rollupListener) {
nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
if (static_cast<nsIWidget*>(this) == rollupWidget) {
rollupListener->Rollup(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;
}
gtk_widget_destroy(mShell);
mShell = nullptr;
mContainer = nullptr;
MOZ_ASSERT(!mGdkWindow,
"mGdkWindow should be NULL when mContainer is destroyed");
#ifdef ACCESSIBILITY
if (mRootAccessible) {
mRootAccessible = nullptr;
}
#endif
// Save until last because OnDestroy() may cause us to be deleted.
OnDestroy();
}
nsIWidget* nsWindow::GetParent(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();
}
// Reparent a child window to a new parent.
void nsWindow::SetParent(nsIWidget* aNewParent) {
LOG("nsWindow::SetParent() new parent %p", aNewParent);
if (!mIsChildWindow) {
NS_WARNING("Used by child widgets only");
return;
}
nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
if (mParent) {
mParent->RemoveChild(this);
}
mParent = aNewParent;
// We're already deleted, quit.
if (!mGdkWindow || mIsDestroyed || !aNewParent) {
return;
}
aNewParent->AddChild(this);
auto* newParent = static_cast<nsWindow*>(aNewParent);
// New parent is deleted, quit.
if (newParent->mIsDestroyed) {
Destroy();
return;
}
GdkWindow* window = GetToplevelGdkWindow();
GdkWindow* parentWindow = newParent->GetToplevelGdkWindow();
LOG(" child GdkWindow %p set parent GdkWindow %p", window, parentWindow);
gdk_window_reparent(window, parentWindow, 0, 0);
bool parentHasMappedToplevel = newParent && newParent->mHasMappedToplevel;
if (mHasMappedToplevel != parentHasMappedToplevel) {
SetHasMappedToplevel(parentHasMappedToplevel);
}
}
bool nsWindow::WidgetTypeSupportsAcceleration() {
if (mWindowType == eWindowType_invisible) {
return false;
}
if (IsSmallPopup()) {
return false;
}
// Workaround for Bug 1479135
// We draw transparent popups on non-compositing screens by SW as we don't
// implement X shape masks in WebRender.
if (mWindowType == eWindowType_popup) {
return HasRemoteContent() && mCompositedScreen;
}
return true;
}
void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) {
MOZ_ASSERT(aNewParent, "null widget");
MOZ_ASSERT(!mIsDestroyed, "");
MOZ_ASSERT(!static_cast<nsWindow*>(aNewParent)->mIsDestroyed, "");
MOZ_ASSERT(
!mParent,
"nsWindow::ReparentNativeWidget() works on toplevel windows only.");
auto* newParent = static_cast<nsWindow*>(aNewParent);
GtkWindow* newParentWidget = GTK_WINDOW(newParent->GetGtkWidget());
LOG("nsWindow::ReparentNativeWidget new parent %p\n", newParent);
gtk_window_set_transient_for(GTK_WINDOW(mShell), newParentWidget);
}
void nsWindow::SetModal(bool aModal) {
LOG("nsWindow::SetModal %d\n", aModal);
if (mIsDestroyed) return;
gtk_window_set_modal(GTK_WINDOW(mShell), aModal ? TRUE : FALSE);
}
// nsIWidget method, which means IsShown.
bool nsWindow::IsVisible() const { return mIsShown; }
void nsWindow::RegisterTouchWindow() {
mHandleTouchEvent = true;
mTouches.Clear();
}
void nsWindow::ConstrainPosition(bool aAllowSlop, int32_t* aX, int32_t* aY) {
if (!mShell || GdkIsWaylandDisplay()) {
return;
}
double dpiScale = GetDefaultScale().scale;
// we need to use the window size in logical screen pixels
int32_t logWidth = std::max(NSToIntRound(mBounds.width / dpiScale), 1);
int32_t logHeight = std::max(NSToIntRound(mBounds.height / dpiScale), 1);
/* get our playing field. use the current screen, or failing that
for any reason, use device caps for the default screen. */
nsCOMPtr<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(IsPopup());
*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(IsPopup());
*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;
}
if (mSizeConstraints.mMaxSize !=
LayoutDeviceIntSize(NS_MAXSIZE, NS_MAXSIZE)) {
AddCSDDecorationSize(&geometry.max_width, &geometry.max_height);
hints |= GDK_HINT_MAX_SIZE;
}
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;
mIsShown = aState;
LOG("nsWindow::Show state %d frame %s\n", aState, GetFrameTag().get());
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 x:%d y:%d -> w:%d h:%d repaint %d aMove %d\n", aX,
aY, aWidth, aHeight, aRepaint, aMove);
ConstrainSize(&aWidth, &aHeight);
LOG(" ConstrainSize: w:%d h;%d\n", aWidth, aHeight);
if (aMove) {
mBounds.x = aX;
mBounds.y = aY;
}
// 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.
if (!aMove && !AreBoundsSane() && IsWaylandPopup()) {
aMove = true;
}
// We have updated position from layout, move.
if (mPreferredPopupRectFlushed) {
aMove = true;
}
// 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;
}
NativeMoveResize(aMove, true);
NotifyRollupGeometryChange();
DispatchResized();
}
void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
LOG("nsWindow::Resize %f %f\n", aWidth, aHeight);
double scale =
BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
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 [%f,%f] -> [%f x %f] repaint %d\n", aX, aY, 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) {
double scale =
BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
int32_t x = NSToIntRound(aX * scale);
int32_t y = NSToIntRound(aY * scale);
LOG("nsWindow::Move to %d %d\n", x, y);
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.
LOG(" bounds %d %d\n", mBounds.y, mBounds.y);
if (x == mBounds.x && y == mBounds.y && mWindowType != eWindowType_popup) {
LOG(" position is the same, return\n");
return;
}
// XXX Should we do some AreBoundsSane check here?
mBounds.x = x;
mBounds.y = y;
if (!mCreated) {
LOG(" is not created, return.\n");
return;
}
if (IsWaylandPopup()) {
int32_t p2a = AppUnitsPerCSSPixel() / gfxPlatformGtk::GetFontScaleFactor();
if (mPreferredPopupRect.x != mBounds.x * p2a &&
mPreferredPopupRect.y != mBounds.y * p2a) {
NativeMoveResize(/* move */ true, /* resize */ false);
NotifyRollupGeometryChange();
} else {
LOG(" mBounds same as mPreferredPopupRect, no need to move");
}
} else {
NativeMoveResize(/* move */ true, /* resize */ false);
NotifyRollupGeometryChange();
}
}
bool nsWindow::IsPopup() const { return mWindowType == eWindowType_popup; }
bool nsWindow::IsWaylandPopup() const {
return GdkIsWaylandDisplay() && IsPopup();
}
static nsMenuPopupFrame* GetMenuPopupFrame(nsIFrame* aFrame) {
return do_QueryFrame(aFrame);
}
void nsWindow::AppendPopupToHierarchyList(nsWindow* aToplevelWindow) {
mWaylandToplevel = aToplevelWindow;
nsWindow* popup = aToplevelWindow;
while (popup && popup->mWaylandPopupNext) {
popup = popup->mWaylandPopupNext;
}
popup->mWaylandPopupNext = this;
mWaylandPopupPrev = popup;
mWaylandPopupNext = nullptr;
mPopupChanged = true;
mPopupClosed = false;
}
void nsWindow::RemovePopupFromHierarchyList() {
// We're already removed from the popup hierarchy
if (!IsInPopupHierarchy()) {
return;
}
mWaylandPopupPrev->mWaylandPopupNext = mWaylandPopupNext;
if (mWaylandPopupNext) {
mWaylandPopupNext->mWaylandPopupPrev = mWaylandPopupPrev;
mWaylandPopupNext->mPopupChanged = true;
}
mWaylandPopupNext = mWaylandPopupPrev = nullptr;
}
void nsWindow::HideWaylandWindow() {
LOG("nsWindow::HideWaylandWindow: [%p]\n", this);
PauseCompositorHiddenWindow();
gtk_widget_hide(mShell);
}
// Gtk refuses to map popup window with x < 0 && y < 0 relative coordinates
// as a workaround just fool around and place the popup temporary to 0,0.
bool nsWindow::WaylandPopupRemoveNegativePosition(int* aX, int* aY) {
LOG("nsWindow::WaylandPopupRemoveNegativePosition() [%p]\n", this);
int x, y;
GdkWindow* window = gtk_widget_get_window(mShell);
gdk_window_get_origin(window, &x, &y);
if (x >= 0 || y >= 0) {
LOG(" coordinates are correct");
return false;
}
LOG(" wrong coord (%d, %d) move to 0,0", x, y);
gdk_window_move(window, 0, 0);
if (aX) {
*aX = x;
}
if (aY) {
*aY = y;
}
return true;
}
void nsWindow::ShowWaylandWindow() {
LOG("nsWindow::ShowWaylandWindow: [%p]\n", this);
if (!IsWaylandPopup()) {
LOG(" toplevel, show it now");
gtk_widget_show(mShell);
return;
}
if (!mPopupTrackInHierarchy) {
LOG(" popup is not tracked in popup hierarchy, show it now");
gtk_widget_show(mShell);
return;
}
// Popup position was checked before gdk_window_move_to_rect() callback
// so just show it.
if (mPopupUseMoveToRect && mWaitingForMoveToRectCallback) {
LOG(" active move-to-rect callback, show it as is");
gtk_widget_show(mShell);
return;
}
if (gtk_widget_is_visible(mShell)) {
LOG(" is already visible, quit");
return;
}
int x, y;
bool moved = WaylandPopupRemoveNegativePosition(&x, &y);
gtk_widget_show(mShell);
if (moved) {
LOG(" move back to (%d, %d) and show", x, y);
gdk_window_move(gtk_widget_get_window(mShell), x, y);
}
}
void nsWindow::WaylandPopupMarkAsClosed() {
LOG("nsWindow::WaylandPopupMarkAsClosed: [%p]\n", this);
mPopupClosed = true;
// If we have any child popup window notify it about
// parent switch.
if (mWaylandPopupNext) {
mWaylandPopupNext->mPopupChanged = true;
}
}
nsWindow* nsWindow::WaylandPopupFindLast(nsWindow* aPopup) {
while (aPopup && aPopup->mWaylandPopupNext) {
aPopup = aPopup->mWaylandPopupNext;
}
return aPopup;
}
// Hide and potentially removes popup from popup hierarchy.
void nsWindow::HideWaylandPopupWindow(bool aTemporaryHide,
bool aRemoveFromPopupList) {
LOG("nsWindow::HideWaylandPopupWindow: remove from list %d\n",
aRemoveFromPopupList);
if (aRemoveFromPopupList) {
RemovePopupFromHierarchyList();
}
if (!mPopupClosed) {
mPopupClosed = !aTemporaryHide;
}
bool visible = gtk_widget_is_visible(mShell);
LOG(" gtk_widget_is_visible() = %d\n", visible);
// Restore only popups which are really visible
mPopupTemporaryHidden = aTemporaryHide && visible;
// Hide only visible popups or popups closed pernamently.
if (visible) {
HideWaylandWindow();
// If there's pending Move-To-Rect callback and we hide the popup
// the callback won't be called any more.
mWaitingForMoveToRectCallback = false;
}
// Clear rendering transactions of closed window and disable rendering to it
// for details).
if (mPopupClosed) {
RevokeTransactionIdAllocator();
}
}
void nsWindow::HideWaylandToplevelWindow() {
LOG("nsWindow::HideWaylandToplevelWindow: [%p]\n", this);
if (mWaylandPopupNext) {
nsWindow* popup = WaylandPopupFindLast(mWaylandPopupNext);
while (popup->mWaylandToplevel != nullptr) {
nsWindow* prev = popup->mWaylandPopupPrev;
popup->HideWaylandPopupWindow(/* aTemporaryHide */ false,
/* aRemoveFromPopupList */ true);
popup = prev;
}
}
HideWaylandWindow();
}
void nsWindow::WaylandPopupRemoveClosedPopups() {
LOG("nsWindow::WaylandPopupRemoveClosedPopups: [%p]\n", this);
nsWindow* popup = this;
while (popup) {
nsWindow* next = popup->mWaylandPopupNext;
if (popup->mPopupClosed) {
popup->HideWaylandPopupWindow(/* aTemporaryHide */ false,
/* aRemoveFromPopupList */ true);
}
popup = next;
}
}
// Hide all tooltips except the latest one.
void nsWindow::WaylandPopupHideTooltips() {
LOG("nsWindow::WaylandPopupHideTooltips");
MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
nsWindow* popup = mWaylandPopupNext;
while (popup && popup->mWaylandPopupNext) {
if (popup->mPopupType == ePopupTypeTooltip) {
LOG(" hidding tooltip [%p]", popup);
popup->WaylandPopupMarkAsClosed();
}
popup = popup->mWaylandPopupNext;
}
}
// We can't show popups with remote content or overflow popups
// on top of regular ones.
// If there's any remote popup opened, close all parent popups of it.
void nsWindow::CloseAllPopupsBeforeRemotePopup() {
LOG("nsWindow::CloseAllPopupsBeforeRemotePopup");
MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
// Don't waste time when there's only one popup opened.
if (!mWaylandPopupNext || mWaylandPopupNext->mWaylandPopupNext == nullptr) {
return;
}
// Find the first opened remote content popup
nsWindow* remotePopup = mWaylandPopupNext;
while (remotePopup) {
if (remotePopup->HasRemoteContent() ||
remotePopup->IsWidgetOverflowWindow()) {
LOG(" remote popup [%p]", remotePopup);
break;
}
remotePopup = remotePopup->mWaylandPopupNext;
}
if (!remotePopup) {
return;
}
// ...hide opened popups before the remote one.
nsWindow* popup = mWaylandPopupNext;
while (popup && popup != remotePopup) {
LOG(" hidding popup [%p]", popup);
popup->WaylandPopupMarkAsClosed();
popup = popup->mWaylandPopupNext;
}
}
static void GetLayoutPopupWidgetChain(
nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
pm->GetSubmenuWidgetChain(aLayoutWidgetHierarchy);
aLayoutWidgetHierarchy->Reverse();
}
// Compare 'this' popup position in Wayland widget hierarchy
// (mWaylandPopupPrev/mWaylandPopupNext) with
// 'this' popup position in layout hierarchy.
//
// When aMustMatchParent is true we also request
// 'this' parents match, i.e. 'this' has the same parent in
// both layout and widget hierarchy.
bool nsWindow::IsPopupInLayoutPopupChain(
nsTArray<nsIWidget*>* aLayoutWidgetHierarchy, bool aMustMatchParent) {
int len = (int)aLayoutWidgetHierarchy->Length();
for (int i = 0; i < len; i++) {
if (this == (*aLayoutWidgetHierarchy)[i]) {
if (!aMustMatchParent) {
return true;
}
// Find correct parent popup for 'this' according to widget
// hierarchy. That means we need to skip closed popups.
nsWindow* parentPopup = nullptr;
if (mWaylandPopupPrev != mWaylandToplevel) {
parentPopup = mWaylandPopupPrev;
while (parentPopup != mWaylandToplevel && parentPopup->mPopupClosed) {
parentPopup = parentPopup->mWaylandPopupPrev;
}
}
if (i == 0) {
// We found 'this' popups as a first popup in layout hierarchy.
// It matches layout hierarchy if it's first widget also in
// wayland widget hierarchy (i.e. parent is null).
return parentPopup == nullptr;
}
return parentPopup == (*aLayoutWidgetHierarchy)[i - 1];
}
}
return false;
}
// Hide popups which are not in popup chain.
void nsWindow::WaylandPopupHierarchyHideByLayout(
nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
LOG("nsWindow::WaylandPopupHierarchyHideByLayout");
MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
// Hide all popups which are not in layout popup chain
nsWindow* popup = mWaylandPopupNext;
while (popup) {
// Tooltips are not tracked in layout chain
if (!popup->mPopupClosed && popup->mPopupType != ePopupTypeTooltip) {
if (!popup->IsPopupInLayoutPopupChain(aLayoutWidgetHierarchy,
/* aMustMatchParent */ false)) {
LOG(" hidding popup [%p]", popup);
popup->WaylandPopupMarkAsClosed();
}
}
popup = popup->mWaylandPopupNext;
}
}
// Mark popups outside of layout hierarchy
void nsWindow::WaylandPopupHierarchyValidateByLayout(
nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
LOG("nsWindow::WaylandPopupHierarchyValidateByLayout");
nsWindow* popup = mWaylandPopupNext;
while (popup) {
if (popup->mPopupType == ePopupTypeTooltip) {
popup->mPopupMatchesLayout = true;
} else if (!popup->mPopupClosed) {
popup->mPopupMatchesLayout = popup->IsPopupInLayoutPopupChain(
aLayoutWidgetHierarchy, /* aMustMatchParent */ true);
LOG(" popup [%p] parent window [%p] matches layout %d\n", (void*)popup,
(void*)popup->mWaylandPopupPrev, popup->mPopupMatchesLayout);
}
popup = popup->mWaylandPopupNext;
}
}
void nsWindow::WaylandPopupHierarchyHideTemporary() {
LOG("nsWindow::WaylandPopupHierarchyHideTemporary() [%p]", this);
nsWindow* popup = WaylandPopupFindLast(this);
while (popup) {
LOG(" temporary hidding popup [%p]", popup);
nsWindow* prev = popup->mWaylandPopupPrev;
popup->HideWaylandPopupWindow(/* aTemporaryHide */ true,
/* aRemoveFromPopupList */ false);
if (popup == this) {
break;
}
popup = prev;
}
}
void nsWindow::WaylandPopupHierarchyShowTemporaryHidden() {
LOG("nsWindow::WaylandPopupHierarchyShowTemporaryHidden()");
nsWindow* popup = this;
while (popup) {
if (popup->mPopupTemporaryHidden) {
popup->mPopupTemporaryHidden = false;
LOG(" showing temporary hidden popup [%p]", popup);
popup->ShowWaylandWindow();
}
popup = popup->mWaylandPopupNext;
}
}
void nsWindow::WaylandPopupHierarchyCalculatePositions() {
LOG("nsWindow::WaylandPopupHierarchyCalculatePositions()");
// Set widget hierarchy in Gtk
nsWindow* popup = mWaylandToplevel->mWaylandPopupNext;
while (popup) {
LOG(" popup [%p] set parent window [%p]", (void*)popup,
(void*)popup->mWaylandPopupPrev);
gtk_window_set_transient_for(GTK_WINDOW(popup->mShell),
GTK_WINDOW(popup->mWaylandPopupPrev->mShell));
popup = popup->mWaylandPopupNext;
}
popup = this;
while (popup) {
// Anchored window has mPopupPosition already calculated against
// its parent, no need to recalculate.
LOG(" popup [%p] bounds [%d, %d] -> [%d x %d]", popup,
(int)(popup->mBounds.x / FractionalScaleFactor()),
(int)(popup->mBounds.y / FractionalScaleFactor()),
(int)(popup->mBounds.width / FractionalScaleFactor()),
(int)(popup->mBounds.height / FractionalScaleFactor()));
#ifdef MOZ_LOGGING
if (LOG_ENABLED()) {
nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
if (popupFrame) {
auto pos = popupFrame->GetPosition();
auto size = popupFrame->GetSize();
int32_t p2a =
AppUnitsPerCSSPixel() / gfxPlatformGtk::GetFontScaleFactor();
LOG(" popup [%p] layout [%d, %d] -> [%d x %d]", popup, pos.x / p2a,
pos.y / p2a, size.width / p2a, size.height / p2a);
}
}
#endif
if (popup->mPopupContextMenu && !popup->mPopupAnchored) {
LOG(" popup [%p] is first context menu", popup);
static int menuOffsetX =
LookAndFeel::GetInt(LookAndFeel::IntID::ContextMenuOffsetHorizontal);
static int menuOffsetY =
LookAndFeel::GetInt(LookAndFeel::IntID::ContextMenuOffsetVertical);
popup->