Bug 1565401 - return valid screen size on multimonitor setup under Wayland; r=stransky,NeilDeakin

By getting the right screen size we can shrink the popup menus which overflows
the screen size under Wayland. The ScreenManager does not help us, because we
can't get absolute window position, but we can use gdk_display_get_monitor_at_window
and gdk_monitor_get_workarea to get the correct screen rectangle.

Differential Revision: https://phabricator.services.mozilla.com/D49289

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jan Horak 2019-10-17 11:27:08 +00:00
parent 9d78fc40f2
commit 4d0cec02a5
6 changed files with 164 additions and 68 deletions

View File

@ -1502,7 +1502,7 @@ nsresult nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame,
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 @@ nsresult nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame,
// 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 @@ nsresult nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame,
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;
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");
}
// 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);
}
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 @@ LayoutDeviceIntRect nsMenuPopupFrame::GetConstraintRect(
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) {

View File

@ -11,6 +11,11 @@
#include "mozilla/dom/DOMTypes.h"
#include "mozilla/Logging.h"
#include "mozilla/StaticPtr.h"
#ifdef MOZ_WAYLAND
# include <gdk/gdk.h>
# include <gdk/gdkx.h>
# include <gdk/gdkwayland.h>
#endif /* MOZ_WAYLAND */
static mozilla::LazyLogModule sScreenLog("WidgetScreen");
@ -104,6 +109,15 @@ void ScreenManager::CopyScreensToAllRemotesIfIsParent() {
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."));

View File

@ -1294,10 +1294,14 @@ static void NativeMoveResizeWaylandPopupCallback(
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 @@ void nsWindow::NativeMoveResizeWaylandPopup(GdkPoint* aPosition,
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 @@ void nsWindow::NativeMove() {
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 @@ void nsWindow::SetDrawsInTitlebar(bool aState) {
}
}
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 @@ gint nsWindow::GdkScaleFactor() {
// 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 @@ already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
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

View File

@ -398,6 +398,9 @@ class nsWindow final : public nsBaseWidget {
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 @@ class nsWindow final : public nsBaseWidget {
void HideWaylandTooltips();
void HideWaylandPopupAndAllChildren();
void CleanupWaylandPopups();
GtkWindow* GetCurrentTopmostWindow();
/**
* |mIMContext| takes all IME related stuff.

View File

@ -210,7 +210,6 @@ UNIFIED_SOURCES += [
'PuppetBidiKeyboard.cpp',
'PuppetWidget.cpp',
'Screen.cpp',
'ScreenManager.cpp',
'SharedWidgetUtils.cpp',
'TextEventDispatcher.cpp',
'VsyncDispatcher.cpp',
@ -242,6 +241,7 @@ if CONFIG['MOZ_XUL'] and CONFIG['NS_PRINTING']:
SOURCES += [
'nsBaseDragService.cpp',
'nsBaseWidget.cpp',
'ScreenManager.cpp',
]
if CONFIG['MOZ_INSTRUMENT_EVENT_LOOP']:

View File

@ -1713,6 +1713,15 @@ class nsIWidget : public nsISupports {
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: