commit 5d107da570b0f5fbc62a8f200a6853d665d6f80e
parent 67d77bed33441aa1ce5205eb187fd38e3be16b9e
Author: stransky <stransky@redhat.com>
Date: Fri, 31 Oct 2025 18:22:37 +0000
Bug 1992198 [Linux] Use logical (widget) coordinates to calculate widget size/positions/boundaries r=emilio,layout-reviewers
Store and use unscaled (widget / gdk) coordinates to position/size widgets
and scale them on demand.
This approach avoids rounding errors on fractional scaled displays as we only scale up the coordinates.
Differential Revision: https://phabricator.services.mozilla.com/D268009
Diffstat:
6 files changed, 573 insertions(+), 333 deletions(-)
diff --git a/layout/base/Units.h b/layout/base/Units.h
@@ -178,6 +178,7 @@ typedef gfx::SizeTyped<DesktopPixel> DesktopSize;
typedef gfx::IntSizeTyped<DesktopPixel> DesktopIntSize;
typedef gfx::RectTyped<DesktopPixel> DesktopRect;
typedef gfx::IntRectTyped<DesktopPixel> DesktopIntRect;
+typedef gfx::IntMarginTyped<DesktopPixel> DesktopIntMargin;
typedef gfx::CoordTyped<ExternalPixel> ExternalCoord;
typedef gfx::IntCoordTyped<ExternalPixel> ExternalIntCoord;
@@ -706,6 +707,9 @@ struct ParentLayerPixel {};
* - on Windows *with* per-monitor DPI support, they are physical device pixels
* on each screen; note that this means the scaling between CSS pixels and
* desktop pixels may vary across multiple displays.
+ * - on Linux, they're "Gdk points", which correspond to widget coordinates
+ * used by Gdk and X11 or Wayland compositor and are mapped to actual
+ * screen pixels by per-monitor screen scale.
*/
struct DesktopPixel {};
@@ -950,6 +954,15 @@ gfx::MarginTyped<Dst, F> operator/(
aMargin.bottom.value / aScale.yScale, aMargin.left.value / aScale.xScale);
}
+template <class Src, class Dst>
+gfx::MarginTyped<Dst> operator*(const gfx::IntMarginTyped<Src>& aMargin,
+ const gfx::ScaleFactor<Src, Dst>& aScale) {
+ return gfx::MarginTyped<Dst>(float(aMargin.top.value) * aScale.scale,
+ float(aMargin.right.value) * aScale.scale,
+ float(aMargin.bottom.value) * aScale.scale,
+ float(aMargin.left.value) * aScale.scale);
+}
+
// Calculate the max or min or the ratios of the widths and heights of two
// sizes, returning a scale factor in the correct units.
diff --git a/widget/gtk/ScreenHelperGTK.cpp b/widget/gtk/ScreenHelperGTK.cpp
@@ -79,21 +79,12 @@ static already_AddRefed<Screen> MakeScreenGtk(unsigned int aMonitor,
workarea.width * geometryScaleFactor,
workarea.height * geometryScaleFactor);
- LayoutDeviceIntRect rect;
DesktopToLayoutDeviceScale contentsScale(1.0);
CSSToLayoutDeviceScale defaultCssScale(geometryScaleFactor);
- if (GdkIsX11Display()) {
- GdkRectangle monitor;
- gdk_screen_get_monitor_geometry(defaultScreen, aMonitor, &monitor);
- rect = LayoutDeviceIntRect(monitor.x * geometryScaleFactor,
- monitor.y * geometryScaleFactor,
- monitor.width * geometryScaleFactor,
- monitor.height * geometryScaleFactor);
- } else {
- // Use per-monitor scaling factor in Wayland.
- contentsScale.scale = geometryScaleFactor;
+ contentsScale.scale = geometryScaleFactor;
#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay()) {
if (StaticPrefs::widget_wayland_fractional_scale_enabled()) {
// Check if we're using fractional scale (see Bug 1985720).
// In such case use workarea is already scaled by fractional scale factor.
@@ -111,10 +102,21 @@ static already_AddRefed<Screen> MakeScreenGtk(unsigned int aMonitor,
contentsScale.scale = fractionalScale;
}
}
-#endif
// Don't report screen shift in Wayland, see bug 1795066.
availRect.MoveTo(0, 0);
- // We use Gtk workarea on Wayland as it matches our needs (Bug 1732682).
+ }
+#endif
+
+ // Use workarea as screen rect on Wayland (Bug 1732682).
+ LayoutDeviceIntRect rect;
+ if (GdkIsX11Display()) {
+ GdkRectangle monitor;
+ gdk_screen_get_monitor_geometry(defaultScreen, aMonitor, &monitor);
+ rect = LayoutDeviceIntRect(monitor.x * geometryScaleFactor,
+ monitor.y * geometryScaleFactor,
+ monitor.width * geometryScaleFactor,
+ monitor.height * geometryScaleFactor);
+ } else {
rect = availRect;
}
diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp
@@ -453,7 +453,7 @@ nsWindow::nsWindow()
mConfiguredClearColor(false),
mGotNonBlankPaint(false),
mNeedsToRetryCapturingMouse(false) {
- mSizeConstraints.mMaxSize = GetSafeWindowSize(mSizeConstraints.mMaxSize);
+ SetSafeWindowSize(mSizeConstraints.mMaxSize);
if (!gGlobalsInitialized) {
gGlobalsInitialized = true;
@@ -590,15 +590,14 @@ void nsWindow::DispatchResized() {
auto clientSize = GetClientSize();
- LOG("nsWindow::DispatchResized() size [%d, %d] client size [%d, %d]",
- (int)(mBounds.width), (int)(mBounds.height), (int)clientSize.width,
+ LOG("nsWindow::DispatchResized() client size [%d, %d]", (int)clientSize.width,
(int)clientSize.height);
- // Check mBounds size
+ // Check out painting texture size
if (mCompositorSession &&
- !wr::WindowSizeSanityCheck(mBounds.width, mBounds.height)) {
- gfxCriticalNoteOnce << "Invalid mBounds in MaybeDispatchResized " << mBounds
- << " size state " << mSizeMode;
+ !wr::WindowSizeSanityCheck(clientSize.width, clientSize.height)) {
+ gfxCriticalNoteOnce << "Invalid mClientArea in MaybeDispatchResized "
+ << clientSize << " size state " << mSizeMode;
}
// Notify the GtkCompositorWidget of a ClientSizeChange
@@ -653,7 +652,7 @@ void nsWindow::OnDestroy(void) {
}
bool nsWindow::AreBoundsSane() {
- // Check requested size, as mBounds might not have been updated.
+ // Check requested size, as mClientArea might not have been updated.
return !mLastSizeRequest.IsEmpty();
}
@@ -769,14 +768,7 @@ float nsWindow::GetDPI() {
double nsWindow::GetDefaultScaleInternal() { return FractionalScaleFactor(); }
DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScale() {
-#ifdef MOZ_WAYLAND
- if (GdkIsWaylandDisplay()) {
- return DesktopToLayoutDeviceScale(FractionalScaleFactor());
- }
-#endif
-
- // In Gtk/X11, we manage windows using device pixels.
- return DesktopToLayoutDeviceScale(1.0);
+ return DesktopToLayoutDeviceScale(FractionalScaleFactor());
}
bool nsWindow::WidgetTypeSupportsAcceleration() {
@@ -893,8 +885,9 @@ void nsWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
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);
+ auto bounds = GetScreenBounds();
+ int32_t logWidth = std::max(NSToIntRound(bounds.width / dpiScale), 1);
+ int32_t logHeight = std::max(NSToIntRound(bounds.height / dpiScale), 1);
/* get our playing field. use the current screen, or failing that
for any reason, use device caps for the default screen. */
@@ -925,35 +918,51 @@ void nsWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
aPoint = ConstrainPositionToBounds(aPoint, {logWidth, logHeight}, screenRect);
}
-void nsWindow::ConstrainSize(int* aWidth, int* aHeight) {
+bool nsWindow::ConstrainSizeWithScale(int* aWidth, int* aHeight,
+ double aScale) {
+ // Ensure minimal 1x1 window size
+ if (*aWidth <= mClientMargin.LeftRight()) {
+ *aWidth = mClientMargin.LeftRight()+1;
+ }
+ if (*aHeight <= mClientMargin.TopBottom()) {
+ *aHeight <= mClientMargin.TopBottom()+1;
+ }
+
// We store our constraints in inner sizes for convenience, but that means we
// need to also constrain inner sizes as inner, rather than outer.
- *aWidth -= mClientMargin.LeftRight();
- *aHeight -= mClientMargin.TopBottom();
- nsIWidget::ConstrainSize(aWidth, aHeight);
- *aWidth += mClientMargin.LeftRight();
- *aHeight += mClientMargin.TopBottom();
- *aWidth = std::max(*aWidth, 1);
- *aHeight = std::max(*aHeight, 1);
+ int scaledWidth = (*aWidth - mClientMargin.LeftRight()) * aScale;
+ int scaledHeight = (*aHeight - mClientMargin.TopBottom()) * aScale;
+ int tmpWidth = scaledWidth, tmpHeight = scaledHeight;
+ nsIWidget::ConstrainSize(&tmpWidth, &tmpHeight);
+ if (tmpWidth != scaledWidth || tmpHeight != scaledHeight) {
+ *aWidth = int(round(tmpWidth / aScale)) + mClientMargin.LeftRight();
+ *aHeight = int(round(tmpHeight / aScale)) + mClientMargin.TopBottom();
+ return true;
+ }
+ return false;
}
+// aConstrains are set is in device pixel sizes as it describes
+// max texture / window size in pixels.
void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
- mSizeConstraints.mMinSize = GetSafeWindowSize(aConstraints.mMinSize);
- mSizeConstraints.mMaxSize = GetSafeWindowSize(aConstraints.mMaxSize);
+ mSizeConstraints = aConstraints;
+ SetSafeWindowSize(mSizeConstraints.mMinSize);
+ SetSafeWindowSize(mSizeConstraints.mMaxSize);
// Store constraints as inner sizes rather than outer sizes.
if (SizeMode() == nsSizeMode_Normal) {
+ auto margin = ToLayoutDevicePixels(mClientMargin);
if (mSizeConstraints.mMinSize.height) {
- mSizeConstraints.mMinSize.height -= mClientMargin.TopBottom();
+ mSizeConstraints.mMinSize.height -= margin.TopBottom();
}
if (mSizeConstraints.mMinSize.width) {
- mSizeConstraints.mMinSize.width -= mClientMargin.LeftRight();
+ mSizeConstraints.mMinSize.width -= margin.LeftRight();
}
if (mSizeConstraints.mMaxSize.height != NS_MAXSIZE) {
- mSizeConstraints.mMaxSize.height -= mClientMargin.TopBottom();
+ mSizeConstraints.mMaxSize.height -= margin.TopBottom();
}
if (mSizeConstraints.mMaxSize.width != NS_MAXSIZE) {
- mSizeConstraints.mMaxSize.width -= mClientMargin.LeftRight();
+ mSizeConstraints.mMaxSize.width -= margin.LeftRight();
}
}
@@ -1000,19 +1009,21 @@ void nsWindow::ApplySizeConstraints() {
if (constraints.mMinSize != LayoutDeviceIntSize()) {
gtk_widget_set_size_request(
GTK_WIDGET(mContainer),
- DevicePixelsToGdkCoordRoundUp(constraints.mMinSize.width),
- DevicePixelsToGdkCoordRoundUp(constraints.mMinSize.height));
+ DevicePixelsToGdkCoordRound(constraints.mMinSize.width),
+ DevicePixelsToGdkCoordRound(constraints.mMinSize.height));
if (ToplevelUsesCSD()) {
- constraints.mMinSize.height += mClientMargin.TopBottom();
- constraints.mMinSize.width += mClientMargin.LeftRight();
+ auto margin = ToLayoutDevicePixels(mClientMargin);
+ constraints.mMinSize.height += margin.TopBottom();
+ constraints.mMinSize.width += margin.LeftRight();
}
hints |= GDK_HINT_MIN_SIZE;
}
if (mSizeConstraints.mMaxSize !=
LayoutDeviceIntSize(NS_MAXSIZE, NS_MAXSIZE)) {
if (ToplevelUsesCSD()) {
- constraints.mMaxSize.height += mClientMargin.TopBottom();
- constraints.mMaxSize.width += mClientMargin.LeftRight();
+ auto margin = ToLayoutDevicePixels(mClientMargin);
+ constraints.mMaxSize.height += margin.TopBottom();
+ constraints.mMaxSize.width += margin.LeftRight();
}
hints |= GDK_HINT_MAX_SIZE;
}
@@ -1020,11 +1031,10 @@ void nsWindow::ApplySizeConstraints() {
// Constraints for the shell are outer sizes, but with SSD we need to use
// inner sizes.
GdkGeometry geometry{
- .min_width = DevicePixelsToGdkCoordRoundUp(constraints.mMinSize.width),
- .min_height = DevicePixelsToGdkCoordRoundUp(constraints.mMinSize.height),
- .max_width = DevicePixelsToGdkCoordRoundDown(constraints.mMaxSize.width),
- .max_height =
- DevicePixelsToGdkCoordRoundDown(constraints.mMaxSize.height),
+ .min_width = DevicePixelsToGdkCoordRound(constraints.mMinSize.width),
+ .min_height = DevicePixelsToGdkCoordRound(constraints.mMinSize.height),
+ .max_width = DevicePixelsToGdkCoordRound(constraints.mMaxSize.width),
+ .max_height = DevicePixelsToGdkCoordRound(constraints.mMaxSize.height),
};
if (mAspectRatio != 0.0f && !mAspectResizer) {
@@ -1072,33 +1082,58 @@ void nsWindow::Show(bool aState) {
RefreshWindowClass();
}
-void nsWindow::ResizeInt(const Maybe<LayoutDeviceIntPoint>& aMove,
- LayoutDeviceIntSize aSize) {
+LayoutDeviceIntPoint nsWindow::ToLayoutDevicePixels(
+ const DesktopIntPoint& aPoint) {
+ return LayoutDeviceIntPoint::Round(aPoint * GetDesktopToDeviceScale());
+}
+
+LayoutDeviceIntSize nsWindow::ToLayoutDevicePixels(
+ const DesktopIntSize& aSize) {
+ return LayoutDeviceIntSize::Round(aSize * GetDesktopToDeviceScale());
+}
+
+LayoutDeviceIntRect nsWindow::ToLayoutDevicePixels(
+ const DesktopIntRect& aRect) {
+ return LayoutDeviceIntRect::Round(aRect * GetDesktopToDeviceScale());
+}
+
+LayoutDeviceIntMargin nsWindow::ToLayoutDevicePixels(
+ const DesktopIntMargin& aMargin) {
+ return (aMargin * GetDesktopToDeviceScale()).Rounded();
+}
+
+DesktopIntPoint nsWindow::ToDesktopPixels(const LayoutDeviceIntPoint& aPoint) {
+ return DesktopIntPoint::Round(aPoint / GetDesktopToDeviceScale());
+}
+
+DesktopIntSize nsWindow::ToDesktopPixels(const LayoutDeviceIntSize& aSize) {
+ return DesktopIntSize::Round(aSize / GetDesktopToDeviceScale());
+}
+
+DesktopIntRect nsWindow::ToDesktopPixels(const LayoutDeviceIntRect& aRect) {
+ return DesktopIntRect::Round(aRect / GetDesktopToDeviceScale());
+}
+
+void nsWindow::ResizeInt(const Maybe<DesktopIntPoint>& aMove,
+ DesktopIntSize aSize) {
LOG("nsWindow::ResizeInt w:%d h:%d\n", aSize.width, aSize.height);
+
const bool moved =
- aMove && (*aMove != mLastMoveRequest || mBounds.TopLeft() != *aMove);
+ aMove && (*aMove != mLastMoveRequest || mClientArea.TopLeft() != *aMove);
if (moved) {
LOG(" with move to left:%d top:%d", aMove->x.value, aMove->y.value);
mLastMoveRequest = *aMove;
}
- ConstrainSize(&aSize.width, &aSize.height);
- LOG(" ConstrainSize: w:%d h;%d\n", aSize.width, aSize.height);
-
- const bool resized = aSize != mLastSizeRequest || mBounds.Size() != aSize;
+ const bool resized = aSize != mLastSizeRequest || mClientArea.Size() != aSize;
#if MOZ_LOGGING
- LOG(" resized %d aSize [%d, %d] mLastSizeRequest [%d, %d] mBounds [%d, %d]",
+ LOG(" resized %d aSize [%d, %d] mLastSizeRequest [%d, %d] "
+ "mClientArea [%d, %d]",
resized, aSize.width, aSize.height, mLastSizeRequest.width,
- mLastSizeRequest.height, mBounds.width, mBounds.height);
+ mLastSizeRequest.height, mClientArea.width, mClientArea.height);
#endif
mLastSizeRequest = aSize;
- // Check size
- if (mCompositorSession &&
- !wr::WindowSizeSanityCheck(aSize.width, aSize.height)) {
- gfxCriticalNoteOnce << "Invalid aSize in ResizeInt " << aSize
- << " size state " << mSizeMode;
- }
// Recalculate aspect ratio when resized from DOM
if (mAspectRatio != 0.0) {
@@ -1118,23 +1153,47 @@ void nsWindow::ResizeInt(const Maybe<LayoutDeviceIntPoint>& aMove,
}
void nsWindow::Resize(const DesktopSize& aSize, bool aRepaint) {
- auto size = LayoutDeviceIntSize::Round(aSize * GetDesktopToDeviceScale());
LOG("nsWindow::Resize %s (scaled %s)", ToString(aSize).c_str(),
- ToString(size).c_str());
+ ToString(aSize).c_str());
+
+ double scale = GetDesktopToDeviceScale().scale;
+ auto size = DesktopIntSize::Round(aSize);
+ auto scaledSize = ToLayoutDevicePixels(size);
+
+ if (ConstrainSizeWithScale(&size.width, &size.height, scale)) {
+ LOG(" ConstrainSizeWithScale: w:%d h:%d coord scale %f", size.width,
+ size.height, scale);
+ }
+ if (mCompositorSession &&
+ !wr::WindowSizeSanityCheck(scaledSize.width, scaledSize.height)) {
+ gfxCriticalNoteOnce << "Invalid aSize in ResizeInt " << scaledSize
+ << " size state " << mSizeMode;
+ }
+
ResizeInt(Nothing(), size);
}
void nsWindow::Resize(const DesktopRect& aRect, bool aRepaint) {
- auto size =
- LayoutDeviceIntSize::Round(aRect.Size() * GetDesktopToDeviceScale());
- auto topLeft =
- LayoutDeviceIntPoint::Round(aRect.TopLeft() * GetDesktopToDeviceScale());
+ double scale = GetDesktopToDeviceScale().scale;
+ auto size = DesktopIntSize::Round(aRect.Size());
+ auto topLeft = DesktopIntPoint::Round(aRect.TopLeft());
+ auto scaledSize = ToLayoutDevicePixels(size);
LOG("nsWindow::Resize [%.2f,%.2f] -> [%.2f x %.2f] scaled [%d,%d] -> "
- "[%d x %d] repaint %d\n",
+ "[%d x %d] repaint %d",
aRect.x, aRect.y, aRect.width, aRect.height, topLeft.x.value,
topLeft.y.value, size.width, size.height, aRepaint);
+ if (ConstrainSizeWithScale(&size.width, &size.height, scale)) {
+ LOG(" ConstrainSizeWithScale: w:%d h:%d coord scale %f", size.width,
+ size.height, scale);
+ }
+ if (mCompositorSession &&
+ !wr::WindowSizeSanityCheck(scaledSize.width, scaledSize.height)) {
+ gfxCriticalNoteOnce << "Invalid aSize in ResizeInt " << scaledSize
+ << " size state " << mSizeMode;
+ }
+
ResizeInt(Some(topLeft), size);
}
@@ -1143,27 +1202,30 @@ void nsWindow::Enable(bool aState) { mEnabled = aState; }
bool nsWindow::IsEnabled() const { return mEnabled; }
void nsWindow::Move(const DesktopPoint& aTopLeft) {
- auto request =
- LayoutDeviceIntPoint::Round(aTopLeft * GetDesktopToDeviceScale());
- LOG("nsWindow::Move to %d x %d\n", request.x.value, request.y.value);
+ double scale = GetDesktopToDeviceScale().scale;
+ auto request = DesktopIntPoint::Round(aTopLeft);
+
+ LOG("nsWindow::Move to [%d x %d] scale %f scaled [%.2f x %.2f]",
+ request.x.value, request.y.value, scale, request.x.value * scale,
+ request.y.value * scale);
if (mSizeMode != nsSizeMode_Normal && IsTopLevelWidget()) {
- LOG(" size state is not normal, bailing");
+ LOG(" size state is not normal, can't move, bailing");
return;
}
// 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 x %d\n", mBounds.x, mBounds.y);
- if (mBounds.TopLeft() == request && mLastMoveRequest == request &&
+ auto pos = GetScreenBoundsUnscaled().TopLeft();
+ LOG(" bounds %d x %d\n", int(pos.x), int(pos.y));
+ if (pos == request && mLastMoveRequest == request &&
mWindowType != WindowType::Popup) {
LOG(" position is the same, return\n");
return;
}
// XXX Should we do some AreBoundsSane check here?
-
mLastMoveRequest = request;
if (!mCreated) {
@@ -1568,10 +1630,8 @@ void nsWindow::WaylandPopupHierarchyCalculatePositions() {
// 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()));
+ (int)(popup->mClientArea.x), (int)(popup->mClientArea.y),
+ (int)(popup->mClientArea.width), (int)(popup->mClientArea.height));
#ifdef MOZ_LOGGING
if (LOG_ENABLED()) {
if (nsMenuPopupFrame* popupFrame = GetPopupFrame()) {
@@ -1661,7 +1721,8 @@ GdkPoint nsWindow::WaylandGetParentPosition() {
GdkPoint topLeft = {0, 0};
nsWindow* window = GetEffectiveParent();
if (window->IsPopup()) {
- topLeft = DevicePixelsToGdkPointRoundDown(window->WidgetToScreenOffset());
+ auto offset = window->WidgetToScreenOffsetUnscaled();
+ topLeft = GdkPoint{offset.x, offset.y};
}
LOG("nsWindow::WaylandGetParentPosition() [%d, %d]\n", topLeft.x, topLeft.y);
return topLeft;
@@ -2015,11 +2076,18 @@ void nsWindow::WaylandPopupPropagateChangesToLayout(bool aMove, bool aResize) {
}
}
if (aMove) {
- LOG(" needPositionUpdate, bounds [%d, %d]", mBounds.x, mBounds.y);
- NotifyWindowMoved(mBounds.x, mBounds.y, ByMoveToRect::Yes);
+ auto pos = ToLayoutDevicePixels(mClientArea);
+ LOG(" needPositionUpdate, bounds [%d, %d]", pos.x, pos.y);
+ NotifyWindowMoved(pos.x, pos.y, ByMoveToRect::Yes);
}
}
+#ifdef MOZ_WAYLAND
+LayoutDeviceIntSize nsWindow::GetMoveToRectPopupSize() {
+ return ToLayoutDevicePixels(mMoveToRectPopupSize);
+}
+#endif
+
void nsWindow::NativeMoveResizeWaylandPopupCallback(
const GdkRectangle* aFinalSize, bool aFlippedX, bool aFlippedY) {
// We're getting move-to-rect callback without move-to-rect call.
@@ -2062,7 +2130,8 @@ void nsWindow::NativeMoveResizeWaylandPopupCallback(
// perfectly round-trip. Compare gdk rects to check whether size or positions
// have changed from what we'd request otherwise, in order to avoid
// flickering.
- const GdkRectangle currentGdkRect = DevicePixelsToGdkRectRoundOut(mBounds);
+ const GdkRectangle currentGdkRect{mClientArea.x, mClientArea.y,
+ mClientArea.width, mClientArea.height};
auto scale = GdkCeiledScaleFactor();
auto IsSubstantiallyDifferent = [=](gint a, gint b) {
return std::abs(a - b) > scale;
@@ -2074,17 +2143,17 @@ void nsWindow::NativeMoveResizeWaylandPopupCallback(
const bool needsSizeUpdate =
IsSubstantiallyDifferent(finalGdkRect.width, currentGdkRect.width) ||
IsSubstantiallyDifferent(finalGdkRect.height, currentGdkRect.height);
+ const DesktopIntRect newClientArea = DesktopIntRect(
+ finalGdkRect.x, finalGdkRect.y, finalGdkRect.width, finalGdkRect.height);
- const LayoutDeviceIntRect newBounds =
- MaybeRoundToDisplayPixels(GdkRectToDevicePixels(finalGdkRect));
- LOG(" orig gdk [%d, %d] -> [%d x %d]\n", currentGdkRect.x, currentGdkRect.y,
+ LOG(" orig gdk [%d, %d] -> [%d x %d]", currentGdkRect.x, currentGdkRect.y,
currentGdkRect.width, currentGdkRect.height);
LOG(" new gdk [%d, %d] -> [%d x %d]\n", finalGdkRect.x, finalGdkRect.y,
finalGdkRect.width, finalGdkRect.height);
- LOG(" orig mBounds [%d, %d] -> [%d x %d]\n", mBounds.x, mBounds.y,
- mBounds.width, mBounds.height);
- LOG(" new mBounds [%d, %d] -> [%d x %d]", newBounds.x, newBounds.y,
- newBounds.width, newBounds.height);
+ LOG(" orig mClientArea [%d, %d] -> [%d x %d]", mClientArea.x, mClientArea.y,
+ mClientArea.width, mClientArea.height);
+ LOG(" new mClientArea [%d, %d] -> [%d x %d]", newClientArea.x,
+ newClientArea.y, newClientArea.width, newClientArea.height);
if (!needsSizeUpdate && !needsPositionUpdate) {
return;
@@ -2093,24 +2162,27 @@ void nsWindow::NativeMoveResizeWaylandPopupCallback(
// Wayland compositor changed popup size request from layout.
// Set the constraints to use them in nsMenuPopupFrame::SetPopupPosition().
// Beware that gtk_window_resize() requests sizes asynchronously and so
- // newBounds might not have the size from the most recent
+ // newClientArea might not have the size from the most recent
// gtk_window_resize().
- if (newBounds.width < mLastSizeRequest.width) {
- mMoveToRectPopupSize.width = newBounds.width;
+ if (mClientArea.width < mLastSizeRequest.width) {
+ mMoveToRectPopupSize.width = newClientArea.width;
}
- if (newBounds.height < mLastSizeRequest.height) {
- mMoveToRectPopupSize.height = newBounds.height;
+ if (mClientArea.height < mLastSizeRequest.height) {
+ mMoveToRectPopupSize.height = newClientArea.height;
}
LOG(" mMoveToRectPopupSize set to [%d, %d]", mMoveToRectPopupSize.width,
mMoveToRectPopupSize.height);
}
- mBounds = newBounds;
- mLastSizeRequest = newBounds.Size();
- mLastMoveRequest = newBounds.TopLeft();
- // Check mBounds size
+
+ mClientArea = newClientArea;
+ mLastSizeRequest = newClientArea.Size();
+ mLastMoveRequest = newClientArea.TopLeft();
+
+ // Check mClientArea size
+ auto scaledSize = ToLayoutDevicePixels(mClientArea);
if (mCompositorSession &&
- !wr::WindowSizeSanityCheck(mBounds.width, mBounds.height)) {
- gfxCriticalNoteOnce << "Invalid mBounds in PopupCallback " << mBounds
+ !wr::WindowSizeSanityCheck(scaledSize.width, scaledSize.height)) {
+ gfxCriticalNoteOnce << "Invalid mClientArea in PopupCallback " << scaledSize
<< " size state " << mSizeMode;
}
WaylandPopupPropagateChangesToLayout(needsPositionUpdate, needsSizeUpdate);
@@ -2151,14 +2223,15 @@ bool nsWindow::IsPopupDirectionRTL() {
// It's used when we position noautihode popup and we don't use xdg_positioner.
// See Bug 1718867
void nsWindow::WaylandPopupSetDirectPosition() {
- const LayoutDeviceIntRect frameRect(mLastMoveRequest, mLastSizeRequest);
- GdkRectangle rect = DevicePixelsToGdkRectRoundOut(frameRect);
+ GdkRectangle rect{mLastMoveRequest.x, mLastMoveRequest.y,
+ mLastSizeRequest.width, mLastSizeRequest.height};
LOG("nsWindow::WaylandPopupSetDirectPosition %d,%d -> %d x %d\n", rect.x,
rect.y, rect.width, rect.height);
mPopupPosition = {rect.x, rect.y};
- mBounds = frameRect;
+ mClientArea.MoveTo(mLastMoveRequest);
+ mClientArea.SizeTo(mLastSizeRequest);
if (mIsDragPopup) {
gtk_window_move(GTK_WINDOW(mShell), rect.x, rect.y);
@@ -2205,8 +2278,8 @@ void nsWindow::WaylandPopupSetDirectPosition() {
gtk_window_resize(GTK_WINDOW(mShell), rect.width, rect.height);
if (mPopupPosition.x != rect.x) {
- mBounds.MoveTo(GdkPointToDevicePixels(mPopupPosition));
- LOG(" setting new bounds [%d, %d]\n", mBounds.x, mBounds.y);
+ mClientArea.MoveTo(mPopupPosition.x, mPopupPosition.y);
+ LOG(" setting new client area [%d, %d]\n", mClientArea.x, mClientArea.y);
WaylandPopupPropagateChangesToLayout(/* move */ true, /* resize */ false);
}
}
@@ -2228,8 +2301,8 @@ bool nsWindow::WaylandPopupFitsToplevelWindow() {
int parentHeight = gdk_window_get_height(toplevelGdkWindow);
LOG(" parent size %d x %d", parentWidth, parentHeight);
- GdkRectangle requestedRect = DevicePixelsToGdkRectRoundOut(
- LayoutDeviceIntRect(mLastMoveRequest, mLastSizeRequest));
+ GdkRectangle requestedRect{mLastMoveRequest.x, mLastMoveRequest.y,
+ mLastSizeRequest.width, mLastSizeRequest.height};
LOG(" popup topleft %d, %d size %d x %d", requestedRect.x, requestedRect.y,
requestedRect.width, requestedRect.height);
bool fits = requestedRect.x >= 0 && requestedRect.y >= 0 &&
@@ -2240,10 +2313,10 @@ bool nsWindow::WaylandPopupFitsToplevelWindow() {
}
void nsWindow::NativeMoveResizeWaylandPopup(bool aMove, bool aResize) {
- const LayoutDeviceIntRect frameRect(mLastMoveRequest, mLastSizeRequest);
- GdkRectangle rect = DevicePixelsToGdkRectRoundOut(frameRect);
+ GdkRectangle rect{mLastMoveRequest.x, mLastMoveRequest.y,
+ mLastSizeRequest.width, mLastSizeRequest.height};
- LOG("nsWindow::NativeMoveResizeWaylandPopup Bounds %d,%d -> %d x %d move %d "
+ LOG("nsWindow::NativeMoveResizeWaylandPopup [%d,%d] -> [%d x %d] move %d "
"resize %d\n",
rect.x, rect.y, rect.width, rect.height, aMove, aResize);
@@ -2255,10 +2328,24 @@ void nsWindow::NativeMoveResizeWaylandPopup(bool aMove, bool aResize) {
return;
}
+ // We expect all Wayland popus have zero margin. If not, just position
+ // it as is and throw an error message.
+ if (!mClientMargin.IsAllZero()) {
+ gfxCriticalNoteOnce << "Invalid non-zero margin for WaylandPopup!";
+ if (aMove) {
+ gtk_window_move(GTK_WINDOW(mShell), mLastMoveRequest.x,
+ mLastMoveRequest.y);
+ }
+ if (aResize) {
+ gtk_window_resize(GTK_WINDOW(mShell), mLastSizeRequest.width,
+ mLastSizeRequest.height);
+ }
+ }
+
if (mWaitingForMoveToRectCallback) {
LOG(" waiting for move to rect, scheduling");
- // mBounds position must not be overwritten before it is applied.
- // OnShellConfigureEvent() will not set mBounds to an old position for
+ // mClientArea position must not be overwritten before it is applied.
+ // OnShellConfigureEvent() will not set mClientArea to an old position for
// GTK_WINDOW_POPUP.
MOZ_ASSERT(gtk_window_get_window_type(GTK_WINDOW(mShell)) ==
GTK_WINDOW_POPUP);
@@ -2304,7 +2391,8 @@ void nsWindow::NativeMoveResizeWaylandPopup(bool aMove, bool aResize) {
LOG(" popup position changed from [%d, %d] to [%d, %d]\n", mPopupPosition.x,
mPopupPosition.y, rect.x, rect.y);
mPopupPosition = {rect.x, rect.y};
- mBounds = frameRect;
+ mClientArea.MoveTo(mLastMoveRequest);
+ mClientArea.SizeTo(mLastSizeRequest);
UpdateWaylandPopupHierarchy();
}
@@ -2618,15 +2706,18 @@ bool nsWindow::WaylandPopupCheckAndGetAnchor(GdkRectangle* aPopupAnchor,
// Update popup layout coordinates from layout by recent popup hierarchy
// (calculate correct position according to parent window)
// and convert to Gtk coordinates.
- LayoutDeviceIntRect anchorRect = mPopupMoveToRectParams.mAnchorRect;
+ DesktopIntRect anchorRect =
+ ToDesktopPixels(mPopupMoveToRectParams.mAnchorRect);
if (!WaylandPopupIsFirst()) {
GdkPoint parent = WaylandGetParentPosition();
LOG(" subtract parent position from anchor [%d, %d]\n", parent.x,
parent.y);
- anchorRect.MoveBy(-GdkPointToDevicePixels(parent));
+ anchorRect.x -= parent.x;
+ anchorRect.y -= parent.y;
}
- *aPopupAnchor = DevicePixelsToGdkRectRoundOut(anchorRect);
+ *aPopupAnchor = GdkRectangle{anchorRect.x, anchorRect.y, anchorRect.width,
+ anchorRect.height};
LOG(" anchored to rectangle [%d, %d] -> [%d x %d]", aPopupAnchor->x,
aPopupAnchor->y, aPopupAnchor->width, aPopupAnchor->height);
@@ -3183,16 +3274,32 @@ void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
LOG(" widget now has focus in SetFocus()");
}
-LayoutDeviceIntRect nsWindow::GetScreenBounds() { return mBounds; }
+DesktopIntRect nsWindow::GetScreenBoundsUnscaled() {
+ DesktopIntRect bounds = mClientArea;
+ bounds.Inflate(mClientMargin);
+ return bounds;
+}
+
+LayoutDeviceIntRect nsWindow::GetScreenBounds() {
+ return ToLayoutDevicePixels(GetScreenBoundsUnscaled());
+}
+
+LayoutDeviceIntRect nsWindow::GetBounds() {
+ return ToLayoutDevicePixels(GetScreenBoundsUnscaled());
+}
LayoutDeviceIntSize nsWindow::GetClientSize() {
- return GetClientBounds().Size();
+ return ToLayoutDevicePixels(mClientArea).Size();
}
LayoutDeviceIntRect nsWindow::GetClientBounds() {
- LayoutDeviceIntRect rect = GetBounds();
- rect.Deflate(mClientMargin);
- return rect;
+ return ToLayoutDevicePixels(mClientArea);
+}
+
+LayoutDeviceIntPoint nsWindow::GetClientOffset() {
+ auto scale = FractionalScaleFactor();
+ return LayoutDeviceIntPoint(int(round(mClientMargin.left * scale)),
+ int(round(mClientMargin.top * scale)));
}
nsresult nsWindow::GetRestoredBounds(LayoutDeviceIntRect& aRect) {
@@ -3205,12 +3312,13 @@ nsresult nsWindow::GetRestoredBounds(LayoutDeviceIntRect& aRect) {
// though it's a bit unfortunate.
aRect = GetScreenBounds();
aRect.SizeTo(GetClientSize());
+ LOG("nsWindow::GetRestoredBounds() %s", ToString(aRect).c_str());
return NS_OK;
}
LayoutDeviceIntMargin nsWindow::NormalSizeModeClientToWindowMargin() {
if (SizeMode() == nsSizeMode_Normal) {
- return mClientMargin;
+ return ToLayoutDevicePixels(mClientMargin);
}
// TODO(emilio): When not using CSD decorations or not in the normal
// sizemode, we can't know the size of the titlebar and window borders before
@@ -3231,85 +3339,109 @@ LayoutDeviceIntCoord GetXWindowBorder(GdkWindow* aWin) {
}
#endif
-void nsWindow::RecomputeBounds(MayChangeCsdMargin aMayChangeCsdMargin) {
- const bool mayChangeCsdMargin =
- aMayChangeCsdMargin == MayChangeCsdMargin::Yes;
- LOG("RecomputeBounds(%d)", mayChangeCsdMargin);
- mPendingBoundsChange = false;
- mPendingBoundsChangeMayChangeCsdMargin = false;
+/*
+ Window bounds are computed differently on X11, X11 CSD and Wayland CSD.
+ Let's say a non-maximized toplevel window is visible at 100,100:
+
+ X11 (titlebar height is 40, decoration size 0):
+ Frame bounds: contains window position which includes window
+ decoration and system titlebar (100, 60)
+ Toplevel bounds: contains window position without tilebar (100,100)
+ mGdkWindow position: 0,0 (located at toplevel origin)
+
+ X11 CSD (without system titlebar, CSD size is 40,40):
+ Frame bounds: contains window position which includes CSD
+ decorations (60,60).
+ Toplevel bounds: same as frame bounds.
+ mGdkWindow position: Relative position to toplevel decoration left
+ top corner, so matches CSD size - (40,40).
+
+ Wayland CSD (with system titlebar, CSD size is 40,40, titlebar height 50):
+ Frame bounds: zero as Wayland doesn't report window position (0,0)
+ Toplevel bounds: same as frame bounds.
+ mGdkWindow position: Relative position to toplevel decoration left
+ top corner, so matches CSD + titlebar size - (40, 90).
+
+ Wayland CSD (without system titlebar, CSD size is 40,40):
+ Frame bounds: zero as Wayland doesn't report window position (0,0)
+ Toplevel bounds: same as frame bounds.
+ mGdkWindow position: Relative position to toplevel decoration left
+ top corner, so matches CSD size - (40,40).
+*/
+#ifdef MOZ_X11
+void nsWindow::RecomputeBoundsX11(bool aMayChangeCsdMargin) {
+ LOG("RecomputeBoundsX11(%d)", aMayChangeCsdMargin);
auto* toplevel = GetToplevelGdkWindow();
- if (!toplevel || mIsDestroyed) {
- return;
- }
- auto GetFrameBounds = [&](GdkWindow* aWin) {
+ // Window position and size with window decoration AND system titlebar.
+ auto GetFrameTitlebarBounds = [&](GdkWindow* aWin) {
GdkRectangle b{0};
gdk_window_get_frame_extents(aWin, &b);
-#ifdef MOZ_X11
- const bool isX11 = GdkIsX11Display();
- if (isX11 && gtk_check_version(3, 24, 35) &&
+ if (gtk_check_version(3, 24, 35) &&
gdk_window_get_window_type(aWin) == GDK_WINDOW_TEMP) {
// Workaround for https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4820
// Bug 1775017 Gtk < 3.24.35 returns scaled values for
// override redirected window on X11.
- return LayoutDeviceRect(b.x, b.y, b.width, b.height);
+ double scale = FractionalScaleFactor();
+ return DesktopIntRect(int(round(b.x / scale)), int(round(b.y / scale)),
+ int(round(b.width / scale)),
+ int(round(b.height / scale)));
}
-#endif
- auto result = GdkRectToFloatDevicePixels(b);
-#ifdef MOZ_X11
- if (isX11 && gtk_check_version(3, 24, 50)) {
+ auto result = DesktopIntRect(b.x, b.y, b.width, b.height);
+ if (gtk_check_version(3, 24, 50)) {
if (auto border = GetXWindowBorder(aWin)) {
// Workaround for
// https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/8423
// Bug 1958174 Gtk doesn't account for window border sizes on X11.
- result.width += 2 * border;
- result.height += 2 * border;
+ double scale = FractionalScaleFactor();
+ result.width += 2 * border / scale;
+ result.height += 2 * border / scale;
}
}
-#endif
return result;
};
+ // Window position and size with decoration byt WITHOUT system titlebar.
auto GetBounds = [&](GdkWindow* aWin) {
GdkRectangle b{0};
- if (IsTopLevelWidget() && GdkIsX11Display() && aWin == toplevel) {
+ if (IsTopLevelWidget() && aWin == toplevel) {
// We want the up-to-date size from the X server, not the last configure
// event size, to avoid spurious resizes on e.g. sizemode changes.
gdk_window_get_geometry(aWin, nullptr, nullptr, &b.width, &b.height);
gdk_window_get_origin(aWin, &b.x, &b.y);
- return GdkRectToFloatDevicePixels(b);
+ return DesktopIntRect(b.x, b.y, b.width, b.height);
}
gdk_window_get_position(aWin, &b.x, &b.y);
b.width = gdk_window_get_width(aWin);
b.height = gdk_window_get_height(aWin);
- return GdkRectToFloatDevicePixels(b);
+ return DesktopIntRect(b.x, b.y, b.width, b.height);
};
- const auto oldBounds = mBounds;
- const auto oldMargin = mClientMargin;
- const auto frameBounds = GetFrameBounds(toplevel);
- const bool decorated =
- IsTopLevelWidget() && mSizeMode != nsSizeMode_Fullscreen && !mUndecorated;
+ const auto toplevelBoundsWithTitlebar = GetFrameTitlebarBounds(toplevel);
const auto toplevelBounds = GetBounds(toplevel);
- mBounds = [&] {
- auto bounds = frameBounds;
- // The frameBounds should always really be as wide as the toplevel bounds,
- // but when opening multiple windows in quick succession on X11 they might
- // not (see bug 1988787). This prevents having a really small window in such
- // case.
+ // Unscaled bounds include decorations and titlebar (X11)
+ DesktopIntRect finalBounds = [&] {
+ auto bounds = toplevelBoundsWithTitlebar;
+ // The toplevelBoundsWithTitlebar should always really be as wide as the
+ // toplevel bounds, but when opening multiple windows in quick succession on
+ // X11 they might not (see bug 1988787). This prevents having a really small
+ // window in such case.
bounds.width = std::max(bounds.width, toplevelBounds.width);
bounds.height = std::max(bounds.height, toplevelBounds.height);
- return LayoutDeviceIntRect::Round(bounds);
+ return bounds;
}();
+ const bool decorated =
+ IsTopLevelWidget() && mSizeMode != nsSizeMode_Fullscreen && !mUndecorated;
if (!decorated) {
mClientMargin = {};
- } else if (mayChangeCsdMargin) {
- const auto clientRectRelativeToFrame = [&] {
+ } else if (aMayChangeCsdMargin) {
+ // TODO: Do we really need so complex margin calculation on X11?
+ const DesktopIntRect clientRectRelativeToFrame = [&] {
auto topLevelBoundsRelativeToFrame = toplevelBounds;
- topLevelBoundsRelativeToFrame -= frameBounds.TopLeft();
+ topLevelBoundsRelativeToFrame -= toplevelBoundsWithTitlebar.TopLeft();
if (!mGdkWindow) {
return topLevelBoundsRelativeToFrame;
}
@@ -3323,45 +3455,115 @@ void nsWindow::RecomputeBounds(MayChangeCsdMargin aMayChangeCsdMargin) {
return gdkWindowBounds + topLevelBoundsRelativeToFrame.TopLeft();
}();
- // This is a bit tricky. For fractional scaling, we want the size and
- // position to be rounded independently, to match the compositor scale,
- // which is not the behavior of LayoutDeviceIntRect::Round (which rounds the
- // edges instead). See bug 1849092.
- const LayoutDeviceIntRect roundedClientRect(
- LayoutDeviceIntPoint::Round(clientRectRelativeToFrame.TopLeft()),
- LayoutDeviceIntSize::Round(clientRectRelativeToFrame.Size()));
+ mClientMargin = DesktopIntRect(DesktopIntPoint(), finalBounds.Size()) -
+ clientRectRelativeToFrame;
+ mClientMargin.EnsureAtLeast(DesktopIntMargin());
+ } else {
+ // Assume the client margin remains the same.
+ }
+
+ mClientArea = finalBounds;
+ mClientArea.Deflate(mClientMargin);
+}
+#endif
+#ifdef MOZ_WAYLAND
+void nsWindow::RecomputeBoundsWayland(bool aMayChangeCsdMargin) {
+ LOG("RecomputeBoundsWayland(%d)", aMayChangeCsdMargin);
+
+ auto GetBounds = [&](GdkWindow* aWin) {
+ GdkRectangle b{0};
+ gdk_window_get_position(aWin, &b.x, &b.y);
+ b.width = gdk_window_get_width(aWin);
+ b.height = gdk_window_get_height(aWin);
+ return DesktopIntRect(b.x, b.y, b.width, b.height);
+ };
+
+ const auto toplevelBounds = GetBounds(GetToplevelGdkWindow());
+
+ mClientArea = GetBounds(mGdkWindow);
+ if (mClientArea.X() < 0 || mClientArea.Y() < 0 || mClientArea.Width() <= 1 ||
+ mClientArea.Height() <= 1) {
+ // If we don't have gdkwindow bounds, assume we take the whole toplevel.
+ mClientArea = toplevelBounds;
+ }
+
+ const bool decorated =
+ IsTopLevelWidget() && mSizeMode != nsSizeMode_Fullscreen && !mUndecorated;
+ if (!decorated) {
+ mClientMargin = {};
+ } else if (aMayChangeCsdMargin) {
mClientMargin =
- LayoutDeviceIntRect(LayoutDeviceIntPoint(), mBounds.Size()) -
- roundedClientRect;
- mClientMargin.EnsureAtLeast(LayoutDeviceIntMargin());
+ DesktopIntRect(DesktopIntPoint(), toplevelBounds.Size()) - mClientArea;
+ mClientMargin.EnsureAtLeast(DesktopIntMargin());
} else {
// Assume the client margin remains the same.
}
+}
+#endif
+
+void nsWindow::RecomputeBounds(bool aMayChangeCsdMargin, bool aScaleChange) {
+ LOG("RecomputeBounds() margin %d scale change %d", aMayChangeCsdMargin,
+ aScaleChange);
+
+ mPendingBoundsChange = false;
+ mPendingBoundsChangeMayChangeCsdMargin = false;
+
+ auto* toplevel = GetToplevelGdkWindow();
+ if (!toplevel || mIsDestroyed) {
+ return;
+ }
+
+ const auto oldClientArea = mClientArea;
+ const auto oldMargin = mClientMargin;
+
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ RecomputeBoundsX11(aMayChangeCsdMargin);
+ }
+#endif
+#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay()) {
+ RecomputeBoundsWayland(aMayChangeCsdMargin);
+ }
+#endif
+
+#ifdef MOZ_LOGGING
+ if (LOG_ENABLED()) {
+ if (mHasReceivedSizeAllocate) {
+ if (!(mClientArea == mReceivedClientArea)) {
+ LOG("Received and calculated client areas are different! received %s "
+ "calculated %s",
+ ToString(mReceivedClientArea).c_str(),
+ ToString(mClientArea).c_str());
+ }
+ }
+ }
+#endif
if (IsPopup()) {
- // Popup windows not be moved by the window manager, and so any change in
+ // Popup windows is not moved by the window manager, and so any change in
// position is a result of our direction.
//
- // mBounds has already been set in Move() or Resize(), and that is more
+ // mClientArea has already been set in Move() or Resize(), and that is more
// up-to-date than the position in the ConfigureNotify event if the event
// is from an earlier window move.
//
- // NOTE(emilio): If we remove the early mBounds change in Move() /
+ // NOTE(emilio): If we remove the early mClientArea change in Move() /
// Resize(), we should be able to remove this special case (but some tests
// would need to be adjusted to deal with the async popup moves).
- MOZ_ASSERT(mLastMoveRequest == oldBounds.TopLeft());
- mBounds.MoveTo(oldBounds.TopLeft());
+ MOZ_ASSERT(mLastMoveRequest == oldClientArea.TopLeft());
+ mClientArea.MoveTo(oldClientArea.TopLeft());
}
// Sometimes the window manager gives us garbage sizes (way past the maximum
// texture size) causing crashes if we don't enforce sane sizes here.
- auto unconstrainedBounds = mBounds;
- mBounds.SizeTo(GetSafeWindowSize(mBounds.Size()));
+ auto size = mClientArea.Size();
+ if (SetSafeWindowSize(size)) {
+ mClientArea.SizeTo(size);
+ }
- LOG("bounds old: %s new -> %s (%s unconstrained, frame = %s, toplevel = %s)",
- ToString(oldBounds).c_str(), ToString(mBounds).c_str(),
- ToString(unconstrainedBounds).c_str(), ToString(frameBounds).c_str(),
- ToString(toplevelBounds).c_str());
+ LOG("client area old: %s new -> %s", ToString(oldClientArea).c_str(),
+ ToString(mClientArea).c_str());
LOG("margin old: %s new -> %s", ToString(oldMargin).c_str(),
ToString(mClientMargin).c_str());
@@ -3376,10 +3578,10 @@ void nsWindow::RecomputeBounds(MayChangeCsdMargin aMayChangeCsdMargin) {
// We need to send WindowMoved even if only the client margins changed
// so that BrowserParent picks up the new offsets.
- const bool moved =
- clientMarginsChanged || oldBounds.TopLeft() != mBounds.TopLeft();
- const bool resized =
- clientMarginsChanged || oldBounds.Size() != mBounds.Size();
+ const bool moved = aScaleChange || aMayChangeCsdMargin ||
+ oldClientArea.TopLeft() != mClientArea.TopLeft();
+ const bool resized = aScaleChange || aMayChangeCsdMargin ||
+ oldClientArea.Size() != mClientArea.Size();
if (moved) {
if (IsTopLevelWidget()) {
@@ -3387,7 +3589,8 @@ void nsWindow::RecomputeBounds(MayChangeCsdMargin aMayChangeCsdMargin) {
// from Cygwin/X (bug 672103).
RollupAllMenus();
}
- NotifyWindowMoved(mBounds.x, mBounds.y);
+ auto pos = ToLayoutDevicePixels(mClientArea.TopLeft());
+ NotifyWindowMoved(pos.x, pos.y);
}
if (resized) {
DispatchResized();
@@ -3685,7 +3888,11 @@ void nsWindow::SetIcon(const nsAString& aIconSpec) {
}
LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
- return mBounds.TopLeft() + GetClientOffset();
+ return ToLayoutDevicePixels(mClientArea.TopLeft());
+}
+
+DesktopIntPoint nsWindow::WidgetToScreenOffsetUnscaled() {
+ return DesktopIntPoint(mClientArea.x, mClientArea.y);
}
void nsWindow::CaptureRollupEvents(bool aDoCapture) {
@@ -4122,20 +4329,20 @@ gboolean nsWindow::OnShellConfigureEvent(GdkEventConfigure* aEvent) {
}
void nsWindow::OnContainerSizeAllocate(GtkAllocation* aAllocation) {
- LOG("nsWindow::OnContainerSizeAllocate [%d,%d] -> [%d x %d] scaled[%.2f] "
- "[%.2f x "
- "%.2f]",
+ LOG("nsWindow::OnContainerSizeAllocate [%d,%d] -> [%d x %d] scaled [%.2f] "
+ "[%.2f x %.2f]",
aAllocation->x, aAllocation->y, aAllocation->width, aAllocation->height,
FractionalScaleFactor(), aAllocation->width * FractionalScaleFactor(),
aAllocation->height * FractionalScaleFactor());
mHasReceivedSizeAllocate = true;
+ mReceivedClientArea = DesktopIntRect(aAllocation->x, aAllocation->y,
+ aAllocation->width, aAllocation->height);
+
if (!mGdkWindow) {
return;
}
- auto oldClientBounds = GetClientBounds();
-
// Bounds will get updated on the main configure.
// Gecko permits running nested event loops during processing of events,
// GtkWindow callers of gtk_widget_size_allocate expect the signal handlers
@@ -4148,21 +4355,19 @@ void nsWindow::OnContainerSizeAllocate(GtkAllocation* aAllocation) {
// Invalidate the new part of the window now for the pending paint to
// minimize background flashes (GDK does not do this for external
// renewClientSizes of toplevels.)
- LayoutDeviceIntRect newClientBounds = GdkRectToDevicePixels(*aAllocation);
- if (oldClientBounds.Size() == newClientBounds.Size()) {
+ if (mClientArea.Size() == mReceivedClientArea.Size()) {
return;
}
- if (oldClientBounds.width < newClientBounds.width) {
- GdkRectangle rect = DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect(
- oldClientBounds.width, 0, newClientBounds.width - oldClientBounds.width,
- newClientBounds.height));
+ if (mClientArea.width < mReceivedClientArea.width) {
+ GdkRectangle rect{mClientArea.width, 0,
+ mReceivedClientArea.width - mClientArea.width,
+ mReceivedClientArea.height};
gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
}
- if (oldClientBounds.height < newClientBounds.height) {
- GdkRectangle rect = DevicePixelsToGdkRectRoundOut(
- LayoutDeviceIntRect(0, oldClientBounds.height, newClientBounds.width,
- newClientBounds.height - oldClientBounds.height));
+ if (mClientArea.height < mReceivedClientArea.height) {
+ GdkRectangle rect{0, mClientArea.height, mReceivedClientArea.width,
+ mReceivedClientArea.height - mClientArea.height};
gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
}
}
@@ -4181,7 +4386,7 @@ void nsWindow::SchedulePendingBounds(MayChangeCsdMargin aMayChangeCsdMargin) {
void nsWindow::MaybeRecomputeBounds() {
LOG("MaybeRecomputeBounds %d", mPendingBoundsChange);
if (mPendingBoundsChange) {
- RecomputeBounds(MayChangeCsdMargin(mPendingBoundsChangeMayChangeCsdMargin));
+ RecomputeBounds(mPendingBoundsChangeMayChangeCsdMargin);
}
}
@@ -4320,10 +4525,11 @@ Maybe<GdkWindowEdge> nsWindow::CheckResizerEdge(
const int resizerHeight = 15 * GdkCeiledScaleFactor();
const int resizerWidth = resizerHeight * 4;
+ auto bounds = GetScreenBounds();
const int topDist = aPoint.y;
const int leftDist = aPoint.x;
- const int rightDist = mBounds.width - aPoint.x;
- const int bottomDist = mBounds.height - aPoint.y;
+ const int rightDist = bounds.width - aPoint.x;
+ const int bottomDist = bounds.height - aPoint.y;
// We can't emulate resize of North/West edges on Wayland as we can't shift
// toplevel window.
@@ -4390,7 +4596,7 @@ void nsWindow::EmulateResizeDrag(GdkEventMotion* aEvent) {
auto oldPoint = mLastResizePoint;
mLastResizePoint = newPoint;
- GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
+ auto size = GetScreenBoundsUnscaled().Size();
size.width += newPoint.x - oldPoint.x;
size.height += newPoint.y - oldPoint.y;
@@ -5484,7 +5690,7 @@ void nsWindow::OnWindowStateEvent(GtkWidget* aWidget,
}();
if (mSizeMode != oldSizeMode || mIsTiled != oldIsTiled) {
- RecomputeBounds(MayChangeCsdMargin::No);
+ RecomputeBounds(/* MayChangeCsdMargin */ false);
}
if (mSizeMode != oldSizeMode) {
if (mWidgetListener) {
@@ -5580,7 +5786,7 @@ void nsWindow::RefreshScale(bool aRefreshScreen, bool aForceRefresh) {
return;
}
- RecomputeBounds(MayChangeCsdMargin::Yes);
+ RecomputeBounds(/* MayChangeCsdMargin */ true, /* ScaleChanged*/ true);
if (mWidgetListener) {
if (PresShell* presShell = mWidgetListener->GetPresShell()) {
@@ -6066,7 +6272,6 @@ void nsWindow::ConfigureCompositor() {
nsresult nsWindow::Create(nsIWidget* aParent, const LayoutDeviceIntRect& aRect,
const widget::InitData& aInitData) {
MOZ_DIAGNOSTIC_ASSERT(aInitData.mWindowType != WindowType::Invisible);
-
#ifdef ACCESSIBILITY
// Send a DBus message to check whether a11y is enabled
a11y::PreInit();
@@ -6089,13 +6294,14 @@ nsresult nsWindow::Create(nsIWidget* aParent, const LayoutDeviceIntRect& aRect,
LOG("nsWindow::Create()");
// save our bounds
- mBounds = aRect;
- LOG(" mBounds: x:%d y:%d w:%d h:%d\n", mBounds.x, mBounds.y, mBounds.width,
- mBounds.height);
+ LOG(" mBounds: x:%d y:%d w:%d h:%d\n", aRect.x, aRect.y, aRect.width,
+ aRect.height);
- ConstrainSize(&mBounds.width, &mBounds.height);
- mLastSizeRequest = mBounds.Size();
- mLastMoveRequest = mBounds.TopLeft();
+ mClientArea = ToDesktopPixels(aRect);
+ ConstrainSizeWithScale(&mClientArea.width, &mClientArea.height,
+ GetDesktopToDeviceScale().scale);
+ mLastSizeRequest = mClientArea.Size();
+ mLastMoveRequest = mClientArea.TopLeft();
const bool popupNeedsAlphaVisual =
mWindowType == WindowType::Popup &&
@@ -6106,7 +6312,6 @@ nsresult nsWindow::Create(nsIWidget* aParent, const LayoutDeviceIntRect& aRect,
LOG(" parent window [%p]", parentnsWindow);
MOZ_ASSERT_IF(mWindowType == WindowType::Popup, parentnsWindow);
-
if (mWindowType != WindowType::Dialog && mWindowType != WindowType::Popup &&
mWindowType != WindowType::TopLevel) {
MOZ_ASSERT_UNREACHABLE("Unexpected eWindowType");
@@ -6186,10 +6391,10 @@ nsresult nsWindow::Create(nsIWidget* aParent, const LayoutDeviceIntRect& aRect,
// actually placed the window somewhere. If no placement has taken
// place, we just let the window manager Do The Right Thing.
if (AreBoundsSane()) {
- GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
- LOG(" nsWindow::Create() Initial resize to %d x %d\n", size.width,
- size.height);
- gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ LOG(" nsWindow::Create() Initial resize to %d x %d\n", mClientArea.width,
+ mClientArea.height);
+ gtk_window_resize(GTK_WINDOW(mShell), mClientArea.width,
+ mClientArea.height);
}
if (mIsPIPWindow) {
LOG(" Is PIP window\n");
@@ -6258,8 +6463,7 @@ nsresult nsWindow::Create(nsIWidget* aParent, const LayoutDeviceIntRect& aRect,
if (GdkIsX11Display()) {
NativeMoveResize(/* move */ true, /* resize */ false);
} else if (AreBoundsSane()) {
- GdkRectangle rect = DevicePixelsToGdkRectRoundOut(mBounds);
- mPopupPosition = {rect.x, rect.y};
+ mPopupPosition = {mClientArea.x, mClientArea.y};
}
} else { // must be WindowType::TopLevel
mGtkWindowRoleName = "Toplevel";
@@ -6284,7 +6488,7 @@ nsresult nsWindow::Create(nsIWidget* aParent, const LayoutDeviceIntRect& aRect,
mSurface = new WaylandSurface(
parentnsWindow ? MOZ_WL_SURFACE(parentnsWindow->GetMozContainer())
: nullptr,
- gfx::IntSize(mLastSizeRequest.width, mLastSizeRequest.height));
+ gfx::IntSize(ToLayoutDevicePixels(mLastSizeRequest).ToUnknownSize()));
}
container = moz_container_new(this, mSurface);
#else
@@ -6619,21 +6823,28 @@ nsAutoCString nsWindow::GetDebugTag() const {
}
void nsWindow::NativeMoveResize(bool aMoved, bool aResized) {
- const LayoutDeviceIntRect frameRect(mLastMoveRequest, mLastSizeRequest);
+ const DesktopIntRect frameRect(mLastMoveRequest, mLastSizeRequest);
+
+ // Window size is calculated without decorations, Gtk adds decorations
+ // size to our request at gtk_window_resize().
GdkRectangle moveResizeRect = [&] {
auto cr = frameRect;
+ // In CSD mode gtk_window_move() / gtk_window_resize() expects coordinates
+ // without CSD frame so remove it.
cr.Deflate(mClientMargin);
- // gtk_window_move will undo the csd offset properly on its own, but
- // nothing else, so do it manually if not drawing to the CSD titlebar.
+ // SSD mode expects coordinates with decorations (outer frame)
+ // so put the margin offset back.
if (!ToplevelUsesCSD()) {
- cr -= GetClientOffset();
+ cr -= DesktopIntPoint(mClientMargin.left, mClientMargin.top);
}
- return DevicePixelsToGdkRectRoundOut(cr);
+ return GdkRectangle{cr.x, cr.y, cr.width, cr.height};
}();
- LOG("nsWindow::NativeMoveResize move %d resize %d to %d,%d -> %d x %d\n",
- aMoved, aResized, moveResizeRect.x, moveResizeRect.y,
- moveResizeRect.width, moveResizeRect.height);
+ LOG("nsWindow::NativeMoveResize mLastMoveRequest [%d,%d] mClientMargin "
+ "[%d,%d] move %d resize %d to [%d,%d] -> [%d x %d]\n",
+ int(mLastMoveRequest.x), int(mLastMoveRequest.y), int(mClientMargin.left),
+ int(mClientMargin.top), aMoved, aResized, moveResizeRect.x,
+ moveResizeRect.y, moveResizeRect.width, moveResizeRect.height);
if (aResized && !AreBoundsSane()) {
LOG(" bounds are insane, hidding the window");
@@ -6659,10 +6870,11 @@ void nsWindow::NativeMoveResize(bool aMoved, bool aResized) {
// and move it when it's shown.
if (aMoved && GdkIsX11Display() && IsPopup() &&
!gtk_widget_get_visible(GTK_WIDGET(mShell))) {
- LOG(" store position of hidden popup window");
mHiddenPopupPositioned = true;
mPopupPosition = {moveResizeRect.x, moveResizeRect.y};
- mBounds.MoveTo(mLastMoveRequest);
+ mClientArea.MoveTo(mLastMoveRequest);
+ LOG(" store position of hidden popup window [%d, %d]", mPopupPosition.x,
+ mPopupPosition.y);
}
if (IsWaylandPopup()) {
@@ -6707,16 +6919,17 @@ void nsWindow::NativeMoveResize(bool aMoved, bool aResized) {
// honored: "If the configure request has not changed, we don't ever resend
// it, because it could mean fighting the user or window manager."
// https://gitlab.gnome.org/GNOME/gtk/-/blob/3.24.31/gtk/gtkwindow.c#L9782
- // So we don't update mBounds until OnContainerSizeAllocate() when we know
+ // So we don't update mClientArea until OnContainerSizeAllocate() when we know
// the request is granted.
bool isOrWillBeVisible = mHasReceivedSizeAllocate || mNeedsShow || mIsShown;
if (!isOrWillBeVisible || IsPopup()) {
if (aResized) {
- mBounds.SizeTo(mLastSizeRequest);
+ mClientArea.SizeTo(mLastSizeRequest);
}
if (aMoved) {
- mBounds.MoveTo(mLastMoveRequest);
- NotifyWindowMoved(mBounds.x, mBounds.y);
+ mClientArea.MoveTo(mLastMoveRequest);
+ auto pos = ToLayoutDevicePixels(mLastMoveRequest);
+ NotifyWindowMoved(pos.x, pos.y);
}
if (aResized) {
DispatchResized();
@@ -6808,7 +7021,8 @@ void nsWindow::NativeShow(bool aAction) {
#endif
}
if (mHiddenPopupPositioned && IsPopup()) {
- LOG(" re-position hidden popup window");
+ LOG(" re-position hidden popup window [%d, %d]", mPopupPosition.x,
+ mPopupPosition.y);
gtk_window_move(GTK_WINDOW(mShell), mPopupPosition.x, mPopupPosition.y);
mHiddenPopupPositioned = false;
}
@@ -6874,26 +7088,38 @@ void nsWindow::SetHasMappedToplevel(bool aState) {
}
}
-LayoutDeviceIntSize nsWindow::GetSafeWindowSize(LayoutDeviceIntSize aSize) {
+bool nsWindow::SetSafeWindowSize(LayoutDeviceIntSize& aSize) {
// The X protocol uses CARD32 for window sizes, but the server (1.11.3)
// reads it as CARD16. Sizes of pixmaps, used for drawing, are (unsigned)
// CARD16 in the protocol, but the server's ProcCreatePixmap returns
// BadAlloc if dimensions cannot be represented by signed shorts.
// Because we are creating Cairo surfaces to represent window buffers,
// we also must ensure that the window can fit in a Cairo surface.
- LayoutDeviceIntSize result = aSize;
- int32_t maxSize = 32767;
+ // Keep some reserve for window margin/decorations.
+ bool changed = false;
+ int32_t maxSize = 32000;
if (mWindowRenderer && mWindowRenderer->AsKnowsCompositor()) {
maxSize = std::min(
maxSize, mWindowRenderer->AsKnowsCompositor()->GetMaxTextureSize());
}
- if (result.width > maxSize) {
- result.width = maxSize;
+ if (aSize.width > maxSize) {
+ aSize.width = maxSize;
+ changed = true;
}
- if (result.height > maxSize) {
- result.height = maxSize;
+ if (aSize.height > maxSize) {
+ aSize.height = maxSize;
+ changed = true;
}
- return result;
+ return changed;
+}
+
+bool nsWindow::SetSafeWindowSize(DesktopIntSize& aSize) {
+ auto layoutDeviceSize = ToLayoutDevicePixels(aSize);
+ auto ret = SetSafeWindowSize(layoutDeviceSize);
+ if (ret) {
+ aSize = ToDesktopPixels(layoutDeviceSize);
+ }
+ return ret;
}
void nsWindow::SetTransparencyMode(TransparencyMode aMode) {
@@ -6960,10 +7186,10 @@ void nsWindow::SetInputRegion(const InputRegion& aInputRegion) {
if (aInputRegion.mFullyTransparent) {
region = cairo_region_create_rectangle(&rect);
} else if (aInputRegion.mMargin != 0) {
- LayoutDeviceIntRect inputRegion(LayoutDeviceIntPoint(), mLastSizeRequest);
+ DesktopIntRect inputRegion(DesktopIntPoint(), mLastSizeRequest);
inputRegion.Deflate(aInputRegion.mMargin);
- GdkRectangle gdkRect = DevicePixelsToGdkRectRoundOut(inputRegion);
- rect = {gdkRect.x, gdkRect.y, gdkRect.width, gdkRect.height};
+ rect = {inputRegion.x, inputRegion.y, inputRegion.width,
+ inputRegion.height};
region = cairo_region_create_rectangle(&rect);
}
@@ -7252,11 +7478,9 @@ already_AddRefed<widget::Screen> nsWindow::GetWidgetScreen() {
}
}
- // GetScreenBounds() is slow for the GTK port so we override and use
- // mBounds directly.
ScreenManager& screenManager = ScreenManager::GetSingleton();
- LayoutDeviceIntRect bounds = mBounds;
- DesktopIntRect deskBounds = RoundedToInt(bounds / GetDesktopToDeviceScale());
+ DesktopIntRect deskBounds =
+ RoundedToInt(GetScreenBounds() / GetDesktopToDeviceScale());
return screenManager.ScreenForRect(deskBounds);
}
@@ -8963,10 +9187,8 @@ void nsWindow::SetCustomTitlebar(bool aState) {
g_object_set_data(G_OBJECT(GetToplevelGdkWindow()), "nsWindow", this);
if (AreBoundsSane()) {
- GdkRectangle size =
- DevicePixelsToGdkSizeRoundUp(GetClientBounds().Size());
- LOG(" resize to %d x %d\n", size.width, size.height);
- gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ gtk_window_resize(GTK_WINDOW(mShell), mClientArea.width,
+ mClientArea.height);
}
if (visible) {
@@ -9029,9 +9251,9 @@ double nsWindow::FractionalScaleFactor() {
return ScreenHelperGTK::GetGTKMonitorFractionalScaleFactor();
}
-gint nsWindow::DevicePixelsToGdkCoordRoundUp(int aPixels) {
+gint nsWindow::DevicePixelsToGdkCoordRound(int aPixels) {
double scale = FractionalScaleFactor();
- return ceil(aPixels / scale);
+ return int(round(aPixels / scale));
}
gint nsWindow::DevicePixelsToGdkCoordRoundDown(int aPixels) {
@@ -9065,18 +9287,6 @@ GdkRectangle nsWindow::DevicePixelsToGdkRectRoundIn(
return {x, y, std::max(right - x, 0), std::max(bottom - y, 0)};
}
-GdkRectangle nsWindow::DevicePixelsToGdkSizeRoundUp(
- const LayoutDeviceIntSize& aSize) {
- double scale = FractionalScaleFactor();
- gint width = ceil(aSize.width / scale);
- gint height = ceil(aSize.height / scale);
- return {0, 0, width, height};
-}
-
-int nsWindow::GdkCoordToDevicePixels(gint aCoord) {
- return (int)(aCoord * FractionalScaleFactor());
-}
-
LayoutDeviceIntPoint nsWindow::GdkEventCoordsToDevicePixels(gdouble aX,
gdouble aY) {
double scale = FractionalScaleFactor();
@@ -9089,26 +9299,6 @@ LayoutDeviceIntPoint nsWindow::GdkPointToDevicePixels(const GdkPoint& aPoint) {
(float)(aPoint.y * scale));
}
-LayoutDeviceIntMargin nsWindow::GtkBorderToDevicePixels(
- const GtkBorder& aBorder) {
- double scale = FractionalScaleFactor();
- return LayoutDeviceIntMargin{
- (int)(aBorder.top * scale), (int)(aBorder.right * scale),
- (int)(aBorder.bottom * scale), (int)(aBorder.left * scale)};
-}
-
-LayoutDeviceIntRect nsWindow::GdkRectToDevicePixels(const GdkRectangle& aRect) {
- return LayoutDeviceIntRect::Round(GdkRectToFloatDevicePixels(aRect));
-}
-
-LayoutDeviceRect nsWindow::GdkRectToFloatDevicePixels(
- const GdkRectangle& aRect) {
- double scale = FractionalScaleFactor();
- return LayoutDeviceRect((float)(aRect.x * scale), (float)(aRect.y * scale),
- (float)(aRect.width * scale),
- (float)(aRect.height * scale));
-}
-
nsresult nsWindow::SynthesizeNativeMouseEvent(
LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
@@ -9746,8 +9936,8 @@ void nsWindow::LockAspectRatio(bool aShouldLock) {
}
if (aShouldLock) {
- float width = DevicePixelsToGdkCoordRoundDown(mLastSizeRequest.width);
- float height = DevicePixelsToGdkCoordRoundDown(mLastSizeRequest.height);
+ float width = mLastSizeRequest.width;
+ float height = mLastSizeRequest.height;
mAspectRatio = width / height;
LOG("nsWindow::LockAspectRatio() width %.2f height %.2f aspect %.2f", width,
diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h
@@ -203,25 +203,30 @@ class nsWindow final : public nsIWidget {
void MoveToWorkspace(const nsAString& workspaceID) override;
void Enable(bool aState) override;
void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override;
+ LayoutDeviceIntRect GetBounds() override;
LayoutDeviceIntRect GetScreenBounds() override;
+ DesktopIntRect GetScreenBoundsUnscaled();
LayoutDeviceIntRect GetClientBounds() override;
- LayoutDeviceIntRect GetBounds() override { return mBounds; }
LayoutDeviceIntSize GetClientSize() override;
- LayoutDeviceIntPoint GetClientOffset() override {
- return LayoutDeviceIntPoint(mClientMargin.left, mClientMargin.top);
- }
+ LayoutDeviceIntPoint GetClientOffset() override;
GdkPoint GetCsdOffsetInGdkCoords();
LayoutDeviceIntPoint GetScreenEdgeSlop() override;
nsresult GetRestoredBounds(LayoutDeviceIntRect&) override;
bool PersistClientBounds() const override { return true; }
LayoutDeviceIntMargin NormalSizeModeClientToWindowMargin() override;
- void ConstrainSize(int* aWidth, int* aHeight) override;
+ bool ConstrainSizeWithScale(int* aWidth, int* aHeight, double aScale);
// Recomputes the bounds according to our current window position. Dispatches
// move / resizes as needed.
+ void RecomputeBounds(bool aMayChangeCsdMargin, bool aScaleChange = false);
+#ifdef MOZ_X11
+ void RecomputeBoundsX11(bool aMayChangeCsdMargin);
+#endif
+#ifdef MOZ_WAYLAND
+ void RecomputeBoundsWayland(bool aMayChangeCsdMargin);
+#endif
enum class MayChangeCsdMargin : bool { No = false, Yes };
- void RecomputeBounds(MayChangeCsdMargin);
void SchedulePendingBounds(MayChangeCsdMargin);
void MaybeRecomputeBounds();
@@ -233,6 +238,7 @@ class nsWindow final : public nsIWidget {
void SetWindowClass(const nsAString& xulWinType, const nsAString& xulWinClass,
const nsAString& xulWinName) override;
LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ DesktopIntPoint WidgetToScreenOffsetUnscaled();
void CaptureRollupEvents(bool aDoCapture) override;
[[nodiscard]] nsresult GetAttention(int32_t aCycleCount) override;
bool HasPendingInputEvent() override;
@@ -423,21 +429,25 @@ class nsWindow final : public nsIWidget {
gint GdkCeiledScaleFactor();
double FractionalScaleFactor();
+ LayoutDeviceIntPoint ToLayoutDevicePixels(const DesktopIntPoint&);
+ LayoutDeviceIntSize ToLayoutDevicePixels(const DesktopIntSize&);
+ LayoutDeviceIntRect ToLayoutDevicePixels(const DesktopIntRect&);
+ LayoutDeviceIntMargin ToLayoutDevicePixels(const DesktopIntMargin&);
+ DesktopIntSize ToDesktopPixels(const LayoutDeviceIntSize&);
+ DesktopIntRect ToDesktopPixels(const LayoutDeviceIntRect&);
+ DesktopIntPoint ToDesktopPixels(const LayoutDeviceIntPoint&);
+
// To GDK
- gint DevicePixelsToGdkCoordRoundUp(int);
+ gint DevicePixelsToGdkCoordRound(int);
+
gint DevicePixelsToGdkCoordRoundDown(int);
GdkPoint DevicePixelsToGdkPointRoundDown(const LayoutDeviceIntPoint&);
- GdkRectangle DevicePixelsToGdkSizeRoundUp(const LayoutDeviceIntSize&);
GdkRectangle DevicePixelsToGdkRectRoundOut(const LayoutDeviceIntRect&);
GdkRectangle DevicePixelsToGdkRectRoundIn(const LayoutDeviceIntRect&);
// From GDK
- int GdkCoordToDevicePixels(gint);
LayoutDeviceIntPoint GdkPointToDevicePixels(const GdkPoint&);
LayoutDeviceIntPoint GdkEventCoordsToDevicePixels(gdouble aX, gdouble aY);
- LayoutDeviceIntRect GdkRectToDevicePixels(const GdkRectangle&);
- LayoutDeviceIntMargin GtkBorderToDevicePixels(const GtkBorder&);
- LayoutDeviceRect GdkRectToFloatDevicePixels(const GdkRectangle&);
bool WidgetTypeSupportsAcceleration() override;
bool WidgetTypeSupportsNativeCompositing() override;
@@ -486,9 +496,7 @@ class nsWindow final : public nsIWidget {
const LayoutDeviceIntPoint& aLockCenter) override;
void LockNativePointer() override;
void UnlockNativePointer() override;
- LayoutDeviceIntSize GetMoveToRectPopupSize() const override {
- return mMoveToRectPopupSize;
- };
+ LayoutDeviceIntSize GetMoveToRectPopupSize() override;
#endif
void ResumeCompositorImpl();
@@ -528,7 +536,9 @@ class nsWindow final : public nsIWidget {
void NativeShow(bool aAction);
void SetHasMappedToplevel(bool aState);
- LayoutDeviceIntSize GetSafeWindowSize(LayoutDeviceIntSize aSize);
+
+ bool SetSafeWindowSize(LayoutDeviceIntSize& aSize);
+ bool SetSafeWindowSize(DesktopIntSize& aSize);
void DispatchContextMenuEventFromMouseEvent(
uint16_t domButton, GdkEventButton* aEvent,
@@ -565,8 +575,8 @@ class nsWindow final : public nsIWidget {
nsWindow* GetTransientForWindowIfPopup();
bool IsHandlingTouchSequence(GdkEventSequence* aSequence);
- void ResizeInt(const mozilla::Maybe<LayoutDeviceIntPoint>& aMove,
- LayoutDeviceIntSize aSize);
+ void ResizeInt(const mozilla::Maybe<DesktopIntPoint>& aMove,
+ DesktopIntSize aSize);
void NativeMoveResizeWaylandPopup(bool aMove, bool aResize);
// Returns a window edge if the given point (in device pixels) is within a
@@ -614,18 +624,41 @@ class nsWindow final : public nsIWidget {
constexpr static const int sNoScale = -1;
int mCeiledScaleFactor = sNoScale;
- // The size requested, which might not be reflected in mBounds. Used in
+ // Client area received by OnContainerSizeAllocate().
+ // We don't use it directly but as a reference.
+ DesktopIntRect mReceivedClientArea{};
+
+ // The size requested, which might not be reflected in mClientArea. Used in
// WaylandPopupSetDirectPosition() to remember intended size for popup
// positioning, in LockAspect() to remember the intended aspect ratio, and
// to remember a size requested while waiting for moved-to-rect when
- // OnSizeAllocate() might change mBounds.Size().
- LayoutDeviceIntSize mLastSizeRequest;
+ // OnSizeAllocate() might change mClientArea.Size().
+ // All these values are in unscaled (Gdk) coordinates.
+ DesktopIntSize mLastSizeRequest;
// Same but for positioning. Used to track move requests.
- LayoutDeviceIntPoint mLastMoveRequest;
- // Margin from mBounds to the client rect (including CSD decorations).
- LayoutDeviceIntMargin mClientMargin;
- // Bounds of the window, as in GetBounds().
- LayoutDeviceIntRect mBounds;
+ DesktopIntPoint mLastMoveRequest;
+
+ // Window bounds (mBounds) are composed as
+ // mClientArea.Inflate(mClientMargin)*scale, i.e.:
+ //
+ // mBounds.x = (mClientArea.x - mClientMargin.left) * scale;
+ // mBounds.y = (mClientArea.y - mClientMargin.top) * scale;
+ // mBounds.width = (mClientArea.width +
+ // (mClientMargin.right + mClientMargin.left)) * scale;
+ // mBounds.height = (mClientArea.height +
+ // (mClientMargin.top + mClientMargin.bottom)) * scale;
+ //
+ // We use mClientMargin and mClientArea in Gdk (logical, widget) coordinates
+ // instead of mBounds i device pixel coordinates to avoid
+ // rounding errors.
+
+ // mClientMargin contains CSD decorations size on Wayland and
+ // CSD decorations and system titlebar size on X11.
+ DesktopIntMargin mClientMargin{};
+
+ // mClientArea is window rendering area. mClientArea.x, mClientArea.y are
+ // equal to mClientMargin.left, mClientMargin.top.
+ DesktopIntRect mClientArea{};
// This field omits duplicate scroll events caused by GNOME bug 726878.
guint32 mLastScrollEventTime = GDK_CURRENT_TIME;
@@ -935,7 +968,7 @@ class nsWindow final : public nsIWidget {
// When popup is resized by Gtk by move-to-rect callback,
// we store final popup size here. Then we use mMoveToRectPopupSize size
// in following popup operations unless mLayoutPopupSizeCleared is set.
- LayoutDeviceIntSize mMoveToRectPopupSize;
+ DesktopIntSize mMoveToRectPopupSize;
#ifdef MOZ_ENABLE_DBUS
RefPtr<mozilla::widget::DBusMenuBar> mDBusMenuBar;
diff --git a/widget/nsBaseWidget.h b/widget/nsBaseWidget.h
diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h
@@ -446,6 +446,8 @@ class nsIWidget : public nsSupportsWeakReference {
typedef mozilla::DesktopIntRect DesktopIntRect;
typedef mozilla::DesktopPoint DesktopPoint;
typedef mozilla::DesktopIntPoint DesktopIntPoint;
+ typedef mozilla::DesktopIntSize DesktopIntSize;
+ typedef mozilla::DesktopIntMargin DesktopIntMargin;
typedef mozilla::DesktopRect DesktopRect;
typedef mozilla::DesktopSize DesktopSize;
typedef mozilla::CSSPoint CSSPoint;
@@ -1921,7 +1923,7 @@ class nsIWidget : public nsSupportsWeakReference {
/**
* Wayland specific routines.
*/
- virtual LayoutDeviceIntSize GetMoveToRectPopupSize() const {
+ virtual LayoutDeviceIntSize GetMoveToRectPopupSize() {
NS_WARNING("GetLayoutPopupRect implemented only for wayland");
return LayoutDeviceIntSize();
}