tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 361c34aa2d3abd03c71a1275ba84c354658f2f11
parent a9a9189629bb925700b6612d32a51e1c4e01f9a4
Author: stransky <stransky@redhat.com>
Date:   Fri, 24 Oct 2025 06:53:54 +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:
Mlayout/base/Units.h | 13+++++++++++++
Mwidget/gtk/ScreenHelperGTK.cpp | 28+++++++++++++++-------------
Mwidget/gtk/nsWindow.cpp | 764+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mwidget/gtk/nsWindow.h | 87++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Dwidget/nsBaseWidget.h | 0
Mwidget/nsIWidget.h | 4+++-
6 files changed, 565 insertions(+), 331 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 @@ -78,21 +78,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. @@ -110,10 +101,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,33 +918,43 @@ 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) { // 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(); + 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(); } } @@ -998,19 +1001,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; } @@ -1018,11 +1023,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) { @@ -1070,33 +1074,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) { @@ -1116,23 +1145,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); } @@ -1141,27 +1194,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) { @@ -1566,10 +1622,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()) { @@ -1659,7 +1713,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; @@ -2013,11 +2068,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. @@ -2060,7 +2122,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; @@ -2072,17 +2135,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; @@ -2091,24 +2154,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); @@ -2149,14 +2215,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); @@ -2203,8 +2270,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); } } @@ -2226,8 +2293,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 && @@ -2238,10 +2305,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); @@ -2253,10 +2320,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); @@ -2302,7 +2383,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(); } @@ -2616,15 +2698,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); @@ -3181,16 +3266,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) { @@ -3203,12 +3304,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 @@ -3229,85 +3331,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; } @@ -3321,45 +3447,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()); @@ -3374,10 +3570,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()) { @@ -3385,7 +3581,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(); @@ -3683,7 +3880,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) { @@ -4120,20 +4321,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 @@ -4146,21 +4347,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); } } @@ -4179,7 +4378,7 @@ void nsWindow::SchedulePendingBounds(MayChangeCsdMargin aMayChangeCsdMargin) { void nsWindow::MaybeRecomputeBounds() { LOG("MaybeRecomputeBounds %d", mPendingBoundsChange); if (mPendingBoundsChange) { - RecomputeBounds(MayChangeCsdMargin(mPendingBoundsChangeMayChangeCsdMargin)); + RecomputeBounds(mPendingBoundsChangeMayChangeCsdMargin); } } @@ -4318,10 +4517,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. @@ -4388,7 +4588,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; @@ -5482,7 +5682,7 @@ void nsWindow::OnWindowStateEvent(GtkWidget* aWidget, }(); if (mSizeMode != oldSizeMode || mIsTiled != oldIsTiled) { - RecomputeBounds(MayChangeCsdMargin::No); + RecomputeBounds(/* MayChangeCsdMargin */ false); } if (mSizeMode != oldSizeMode) { if (mWidgetListener) { @@ -5578,7 +5778,7 @@ void nsWindow::RefreshScale(bool aRefreshScreen, bool aForceRefresh) { return; } - RecomputeBounds(MayChangeCsdMargin::Yes); + RecomputeBounds(/* MayChangeCsdMargin */ true, /* ScaleChanged*/ true); if (mWidgetListener) { if (PresShell* presShell = mWidgetListener->GetPresShell()) { @@ -6064,7 +6264,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(); @@ -6087,13 +6286,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 && @@ -6104,7 +6304,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"); @@ -6184,10 +6383,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"); @@ -6256,8 +6455,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"; @@ -6282,7 +6480,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 @@ -6617,21 +6815,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"); @@ -6657,10 +6862,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()) { @@ -6705,16 +6911,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(); @@ -6806,7 +7013,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; } @@ -6872,26 +7080,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) { @@ -6958,10 +7178,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); } @@ -7250,11 +7470,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); } @@ -8961,10 +9179,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) { @@ -9027,9 +9243,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) { @@ -9063,18 +9279,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(); @@ -9087,26 +9291,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, @@ -9744,8 +9928,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(); }