diff --git a/layout/xul/nsMenuPopupFrame.cpp b/layout/xul/nsMenuPopupFrame.cpp --- a/layout/xul/nsMenuPopupFrame.cpp +++ b/layout/xul/nsMenuPopupFrame.cpp @@ -1502,7 +1502,7 @@ nscoord oldAlignmentOffset = mAlignmentOffset; - bool inWayland = false; + static bool inWayland = false; #ifdef MOZ_WAYLAND inWayland = !GDK_IS_X11_DISPLAY(gdk_display_get_default()); #endif @@ -1512,9 +1512,9 @@ // However, if a panel is already constrained or flipped (mIsOffset), then we // want to continue to calculate this. Also, always do this for content // shells, so that the popup doesn't extend outside the containing frame. - if (!inWayland && (mInContentShell || (mFlip != FlipType_None && - (!aIsMove || mIsOffset || - mPopupType != ePopupTypePanel)))) { + if (mInContentShell || + (mFlip != FlipType_None && + (!aIsMove || mIsOffset || mPopupType != ePopupTypePanel))) { int32_t appPerDev = presContext->AppUnitsPerDevPixel(); LayoutDeviceIntRect anchorRectDevPix = LayoutDeviceIntRect::FromAppUnitsToNearest(anchorRect, appPerDev); @@ -1532,60 +1532,66 @@ if (mRect.width > screenRect.width) mRect.width = screenRect.width; if (mRect.height > screenRect.height) mRect.height = screenRect.height; - // at this point the anchor (anchorRect) is within the available screen - // area (screenRect) and the popup is known to be no larger than the screen. + // We can't get the subsequent change of the popup position under + // waylande where gdk_window_move_to_rect is used to place them + // because we don't know the absolute position of the window on the screen. + if (!inWayland) { + // at this point the anchor (anchorRect) is within the available screen + // area (screenRect) and the popup is known to be no larger than the + // screen. - // We might want to "slide" an arrow if the panel is of the correct type - - // but we can only slide on one axis - the other axis must be "flipped or - // resized" as normal. - bool slideHorizontal = false, slideVertical = false; - if (mFlip == FlipType_Slide) { - int8_t position = GetAlignmentPosition(); - slideHorizontal = position >= POPUPPOSITION_BEFORESTART && - position <= POPUPPOSITION_AFTEREND; - slideVertical = position >= POPUPPOSITION_STARTBEFORE && - position <= POPUPPOSITION_ENDAFTER; - } + // We might want to "slide" an arrow if the panel is of the correct type - + // but we can only slide on one axis - the other axis must be "flipped or + // resized" as normal. + bool slideHorizontal = false, slideVertical = false; + if (mFlip == FlipType_Slide) { + int8_t position = GetAlignmentPosition(); + slideHorizontal = position >= POPUPPOSITION_BEFORESTART && + position <= POPUPPOSITION_AFTEREND; + slideVertical = position >= POPUPPOSITION_STARTBEFORE && + position <= POPUPPOSITION_ENDAFTER; + } - // Next, check if there is enough space to show the popup at full size when - // positioned at screenPoint. If not, flip the popups to the opposite side - // of their anchor point, or resize them as necessary. - bool endAligned = IsDirectionRTL() - ? mPopupAlignment == POPUPALIGNMENT_TOPLEFT || - mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT - : mPopupAlignment == POPUPALIGNMENT_TOPRIGHT || - mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT; - nscoord preOffsetScreenPoint = screenPoint.x; - if (slideHorizontal) { - mRect.width = SlideOrResize(screenPoint.x, mRect.width, screenRect.x, - screenRect.XMost(), &mAlignmentOffset); - } else { - mRect.width = FlipOrResize( - screenPoint.x, mRect.width, screenRect.x, screenRect.XMost(), - anchorRect.x, anchorRect.XMost(), margin.left, margin.right, - offsetForContextMenu.x, hFlip, endAligned, &mHFlip); - } - mIsOffset = preOffsetScreenPoint != screenPoint.x; + // Next, check if there is enough space to show the popup at full size + // when positioned at screenPoint. If not, flip the popups to the opposite + // side of their anchor point, or resize them as necessary. + bool endAligned = IsDirectionRTL() + ? mPopupAlignment == POPUPALIGNMENT_TOPLEFT || + mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT + : mPopupAlignment == POPUPALIGNMENT_TOPRIGHT || + mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT; + nscoord preOffsetScreenPoint = screenPoint.x; + if (slideHorizontal) { + mRect.width = SlideOrResize(screenPoint.x, mRect.width, screenRect.x, + screenRect.XMost(), &mAlignmentOffset); + } else { + mRect.width = FlipOrResize( + screenPoint.x, mRect.width, screenRect.x, screenRect.XMost(), + anchorRect.x, anchorRect.XMost(), margin.left, margin.right, + offsetForContextMenu.x, hFlip, endAligned, &mHFlip); + } + mIsOffset = preOffsetScreenPoint != screenPoint.x; - endAligned = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT || - mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT; - preOffsetScreenPoint = screenPoint.y; - if (slideVertical) { - mRect.height = SlideOrResize(screenPoint.y, mRect.height, screenRect.y, - screenRect.YMost(), &mAlignmentOffset); - } else { - mRect.height = FlipOrResize( - screenPoint.y, mRect.height, screenRect.y, screenRect.YMost(), - anchorRect.y, anchorRect.YMost(), margin.top, margin.bottom, - offsetForContextMenu.y, vFlip, endAligned, &mVFlip); + endAligned = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT || + mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT; + preOffsetScreenPoint = screenPoint.y; + if (slideVertical) { + mRect.height = SlideOrResize(screenPoint.y, mRect.height, screenRect.y, + screenRect.YMost(), &mAlignmentOffset); + } else { + mRect.height = FlipOrResize( + screenPoint.y, mRect.height, screenRect.y, screenRect.YMost(), + anchorRect.y, anchorRect.YMost(), margin.top, margin.bottom, + offsetForContextMenu.y, vFlip, endAligned, &mVFlip); + } + mIsOffset = mIsOffset || (preOffsetScreenPoint != screenPoint.y); + + NS_ASSERTION(screenPoint.x >= screenRect.x && + screenPoint.y >= screenRect.y && + screenPoint.x + mRect.width <= screenRect.XMost() && + screenPoint.y + mRect.height <= screenRect.YMost(), + "Popup is offscreen"); } - mIsOffset = mIsOffset || (preOffsetScreenPoint != screenPoint.y); - - NS_ASSERTION(screenPoint.x >= screenRect.x && - screenPoint.y >= screenRect.y && - screenPoint.x + mRect.width <= screenRect.XMost() && - screenPoint.y + mRect.height <= screenRect.YMost(), - "Popup is offscreen"); } // snap the popup's position in screen coordinates to device pixels, @@ -1687,6 +1693,14 @@ screen->GetAvailRect(&screenRectPixels.x, &screenRectPixels.y, &screenRectPixels.width, &screenRectPixels.height); } +#ifdef MOZ_WAYLAND + else { + if (GetWidget() && + GetWidget()->GetScreenRect(&screenRectPixels) != NS_OK) { + NS_WARNING("Cannot get screen rect from widget!"); + } + } +#endif } if (mInContentShell) { diff --git a/widget/ScreenManager.cpp b/widget/ScreenManager.cpp --- a/widget/ScreenManager.cpp +++ b/widget/ScreenManager.cpp @@ -11,6 +11,11 @@ #include "mozilla/dom/DOMTypes.h" #include "mozilla/Logging.h" #include "mozilla/StaticPtr.h" +#ifdef MOZ_WAYLAND +# include +# include +# include +#endif /* MOZ_WAYLAND */ static mozilla::LazyLogModule sScreenLog("WidgetScreen"); @@ -104,6 +109,15 @@ NS_IMETHODIMP ScreenManager::ScreenForRect(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight, nsIScreen** aOutScreen) { +#ifdef MOZ_WAYLAND + static bool inWayland = !GDK_IS_X11_DISPLAY(gdk_display_get_default()); + + if (inWayland) { + *aOutScreen = nullptr; + return NS_OK; + } +#endif + if (mScreenList.IsEmpty()) { MOZ_LOG(sScreenLog, LogLevel::Warning, ("No screen available. This can happen in xpcshell.")); diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h --- a/widget/gtk/nsWindow.h +++ b/widget/gtk/nsWindow.h @@ -398,6 +398,9 @@ static bool HideTitlebarByDefault(); static bool GetTopLevelWindowActiveState(nsIFrame* aFrame); static bool TitlebarCanUseShapeMask(); +#ifdef MOZ_WAYLAND + virtual nsresult GetScreenRect(LayoutDeviceIntRect* aRect) override; +#endif protected: virtual ~nsWindow(); @@ -630,6 +633,7 @@ void HideWaylandTooltips(); void HideWaylandPopupAndAllChildren(); void CleanupWaylandPopups(); + GtkWindow* GetCurrentTopmostWindow(); /** * |mIMContext| takes all IME related stuff. diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp --- a/widget/gtk/nsWindow.cpp +++ b/widget/gtk/nsWindow.cpp @@ -1294,10 +1294,14 @@ GdkWindow* window, const GdkRectangle* flipped_rect, const GdkRectangle* final_rect, gboolean flipped_x, gboolean flipped_y, void* aWindow) { - LOG(("%s [%p] flipped %d %d\n", __FUNCTION__, aWindow, flipped_rect->x, - flipped_rect->y)); - LOG(("%s [%p] final %d %d\n", __FUNCTION__, aWindow, final_rect->x, - final_rect->y)); + LOG(("%s [%p] flipped_x %d flipped_y %d\n", __FUNCTION__, aWindow, flipped_x, + flipped_y)); + + LOG(("%s [%p] flipped %d %d w:%d h:%d\n", __FUNCTION__, aWindow, + flipped_rect->x, flipped_rect->y, flipped_rect->width, + flipped_rect->height)); + LOG(("%s [%p] final %d %d w:%d h:%d\n", __FUNCTION__, aWindow, final_rect->x, + final_rect->y, final_rect->width, final_rect->height)); } #endif @@ -1384,6 +1388,16 @@ HideWaylandWindow(); } + LOG( + ("nsWindow::NativeMoveResizeWaylandPopup [%p]: requested rect: x%d y%d " + "w%d h%d\n", + this, 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")); + } sGdkWindowMoveToRect(gdkWindow, &rect, rectAnchor, menuAnchor, hints, 0, 0); if (isWidgetVisible) { @@ -1399,7 +1413,8 @@ LOG(("nsWindow::NativeMove [%p] %d %d\n", (void*)this, point.x, point.y)); if (IsWaylandPopup()) { - NativeMoveResizeWaylandPopup(&point, nullptr); + GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size()); + NativeMoveResizeWaylandPopup(&point, &size); } else if (mIsTopLevel) { gtk_window_move(GTK_WINDOW(mShell), point.x, point.y); } else if (mGdkWindow) { @@ -6724,6 +6739,16 @@ } } +GtkWindow* nsWindow::GetCurrentTopmostWindow() { + GtkWindow* parentWindow = GTK_WINDOW(GetGtkWidget()); + GtkWindow* topmostParentWindow; + while (parentWindow) { + topmostParentWindow = parentWindow; + parentWindow = gtk_window_get_transient_for(parentWindow); + } + return topmostParentWindow; +} + gint nsWindow::GdkScaleFactor() { GdkWindow* scaledGdkWindow = mGdkWindow; if (!mIsX11Display) { @@ -6732,12 +6757,7 @@ // not updated during it's hidden. if (mWindowType == eWindowType_popup || mWindowType == eWindowType_dialog) { // Get toplevel window for scale factor: - GtkWindow* parentWindow = GTK_WINDOW(GetGtkWidget()); - GtkWindow* topmostParentWindow; - while (parentWindow) { - topmostParentWindow = parentWindow; - parentWindow = gtk_window_get_transient_for(parentWindow); - } + GtkWindow* topmostParentWindow = GetCurrentTopmostWindow(); if (topmostParentWindow) { scaledGdkWindow = gtk_widget_get_window(GTK_WIDGET(topmostParentWindow)); @@ -7268,6 +7288,41 @@ 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); + aRect->x = workArea.x; + aRect->y = workArea.y; + 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 diff --git a/widget/moz.build b/widget/moz.build --- a/widget/moz.build +++ b/widget/moz.build @@ -210,7 +210,6 @@ 'PuppetBidiKeyboard.cpp', 'PuppetWidget.cpp', 'Screen.cpp', - 'ScreenManager.cpp', 'SharedWidgetUtils.cpp', 'TextEventDispatcher.cpp', 'VsyncDispatcher.cpp', @@ -242,6 +241,7 @@ SOURCES += [ 'nsBaseDragService.cpp', 'nsBaseWidget.cpp', + 'ScreenManager.cpp', ] if CONFIG['MOZ_INSTRUMENT_EVENT_LOOP']: diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h --- a/widget/nsIWidget.h +++ b/widget/nsIWidget.h @@ -1713,6 +1713,15 @@ return NS_ERROR_NOT_IMPLEMENTED; } + // Get rectangle of the screen where the window is placed. + // It's used to detect popup overflow under Wayland because + // Screenmanager does not work under it. +#ifdef MOZ_WAYLAND + virtual nsresult GetScreenRect(LayoutDeviceIntRect* aRect) { + return NS_ERROR_NOT_IMPLEMENTED; + } +#endif + private: class LongTapInfo { public: