commit 44d73a751d067da5cd44f13ac404d38f2580da1f
parent 74a5eb2166f033f2f4bd95a7799b0b0eee89ab42
Author: Emilio Cobos Álvarez <emilio@crisal.io>
Date: Mon, 15 Dec 2025 14:45:13 +0000
Bug 2005169 - Fix reftest-snapshot and overflow interaction of overflowing replaced elements. r=layout-reviewers,dholbert
The patch in the regressing bug should have updated the overflow areas
of the replaced elements when overflowing.
Factor the dest rect computation for canvas and video as well so that
it's easier to manage.
Note that for object-fit computation purposes we need to ignore size
containment so that also required a minor refactor.
Differential Revision: https://phabricator.services.mozilla.com/D275762
Diffstat:
7 files changed, 102 insertions(+), 95 deletions(-)
diff --git a/layout/generic/ReflowInput.h b/layout/generic/ReflowInput.h
@@ -368,6 +368,12 @@ struct ReflowInput : public SizeComputationInput {
// unconstrained dimensions replaced by zero.
nsSize ComputedSizeAsContainerIfConstrained() const;
+ // Return the physical content box relative to the frame itself.
+ nsRect ComputedPhysicalContentBoxRelativeToSelf() const {
+ auto bp = ComputedPhysicalBorderPadding();
+ return nsRect(nsPoint(bp.left, bp.top), ComputedPhysicalSize());
+ }
+
// Get the writing mode of the containing block, to resolve float/clear
// logical sides appropriately.
WritingMode GetCBWritingMode() const;
diff --git a/layout/generic/nsHTMLCanvasFrame.cpp b/layout/generic/nsHTMLCanvasFrame.cpp
@@ -67,30 +67,23 @@ class nsDisplayCanvas final : public nsPaintedDisplayItem {
*aSnap = false;
auto* f = static_cast<nsHTMLCanvasFrame*>(Frame());
auto* canvas = HTMLCanvasElement::FromNode(f->GetContent());
- nsRegion result;
- if (canvas->GetIsOpaque()) {
- // OK, the entire region painted by the canvas is opaque. But what is
- // that region? It's the canvas's "dest rect" (controlled by the
- // object-fit/object-position CSS properties), clipped to the container's
- // content box (which is what GetBounds() returns). So, we grab those
- // rects and intersect them.
- nsRect constraintRect = GetBounds(aBuilder, aSnap);
-
- // Need intrinsic size & ratio, for ComputeObjectDestRect:
- CSSIntSize canvasSize = f->GetCanvasSize();
- IntrinsicSize intrinsicSize = IntrinsicSizeFromCanvasSize(canvasSize);
- AspectRatio intrinsicRatio = IntrinsicRatioFromCanvasSize(canvasSize);
-
- const nsRect destRect = nsLayoutUtils::ComputeObjectDestRect(
- constraintRect, intrinsicSize, intrinsicRatio, f->StylePosition());
- return nsRegion(destRect.Intersect(constraintRect));
+ if (!canvas->GetIsOpaque()) {
+ return {};
}
- return result;
+ // OK, the entire region painted by the canvas is opaque. But what is
+ // that region? It's the canvas's "dest rect" (controlled by the
+ // object-fit/object-position CSS properties), clipped to the container's
+ // ink overflow box (which is what GetBounds() returns). So, we grab those
+ // rects and intersect them.
+ const nsRect constraintRect = GetBounds(aBuilder, aSnap);
+ const nsRect destRect =
+ f->GetDestRect(f->GetContentRectRelativeToSelf() + ToReferenceFrame());
+ return nsRegion(destRect.Intersect(constraintRect));
}
nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override {
*aSnap = true;
- return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
+ return Frame()->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
}
bool CreateWebRenderCommands(
@@ -111,12 +104,8 @@ class nsDisplayCanvas final : public nsPaintedDisplayItem {
element->FlushOffscreenCanvas();
auto* canvasFrame = static_cast<nsHTMLCanvasFrame*>(mFrame);
- CSSIntSize canvasSizeInPx = canvasFrame->GetCanvasSize();
- IntrinsicSize intrinsicSize = IntrinsicSizeFromCanvasSize(canvasSizeInPx);
- AspectRatio intrinsicRatio = IntrinsicRatioFromCanvasSize(canvasSizeInPx);
- nsRect area = mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame();
- nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
- area, intrinsicSize, intrinsicRatio, mFrame->StylePosition());
+ nsRect dest = canvasFrame->GetDestRect(
+ mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame());
LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
dest, mFrame->PresContext()->AppUnitsPerDevPixel());
@@ -141,8 +130,7 @@ class nsDisplayCanvas final : public nsPaintedDisplayItem {
aManager->CommandBuilder()
.CreateOrRecycleWebRenderUserData<WebRenderCanvasData>(
this, &isRecycled);
- nsHTMLCanvasFrame* canvasFrame =
- static_cast<nsHTMLCanvasFrame*>(mFrame);
+ auto* canvasFrame = static_cast<nsHTMLCanvasFrame*>(mFrame);
if (!canvasFrame->UpdateWebRenderCanvasData(aDisplayListBuilder,
canvasData)) {
return true;
@@ -153,19 +141,8 @@ class nsDisplayCanvas final : public nsPaintedDisplayItem {
// Push IFrame for async image pipeline.
// XXX Remove this once partial display list update is supported.
-
- CSSIntSize canvasSizeInPx =
- CSSIntSize::FromUnknownSize(data->GetSize());
- IntrinsicSize intrinsicSize =
- IntrinsicSizeFromCanvasSize(canvasSizeInPx);
- AspectRatio intrinsicRatio =
- IntrinsicRatioFromCanvasSize(canvasSizeInPx);
-
- nsRect area =
- mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame();
- nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
- area, intrinsicSize, intrinsicRatio, mFrame->StylePosition());
-
+ nsRect dest = canvasFrame->GetDestRect(
+ mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame());
LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
dest, mFrame->PresContext()->AppUnitsPerDevPixel());
@@ -206,15 +183,8 @@ class nsDisplayCanvas final : public nsPaintedDisplayItem {
return true;
}
- IntrinsicSize intrinsicSize =
- IntrinsicSizeFromCanvasSize(canvasSizeInPx);
- AspectRatio intrinsicRatio =
- IntrinsicRatioFromCanvasSize(canvasSizeInPx);
-
- nsRect area =
- mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame();
- nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
- area, intrinsicSize, intrinsicRatio, mFrame->StylePosition());
+ nsRect dest = canvasFrame->GetDestRect(
+ mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame());
LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
dest, mFrame->PresContext()->AppUnitsPerDevPixel());
@@ -242,25 +212,22 @@ class nsDisplayCanvas final : public nsPaintedDisplayItem {
}
void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override {
- nsHTMLCanvasFrame* f = static_cast<nsHTMLCanvasFrame*>(Frame());
- HTMLCanvasElement* canvas = HTMLCanvasElement::FromNode(f->GetContent());
+ auto* f = static_cast<nsHTMLCanvasFrame*>(Frame());
+ auto* canvas = HTMLCanvasElement::FromNode(f->GetContent());
- nsRect area = f->GetContentRectRelativeToSelf() + ToReferenceFrame();
CSSIntSize canvasSizeInPx = f->GetCanvasSize();
-
nsPresContext* presContext = f->PresContext();
canvas->HandlePrintCallback(presContext);
- if (canvasSizeInPx.width <= 0 || canvasSizeInPx.height <= 0 ||
- area.IsEmpty()) {
+ if (canvasSizeInPx.width <= 0 || canvasSizeInPx.height <= 0) {
return;
}
- IntrinsicSize intrinsicSize = IntrinsicSizeFromCanvasSize(canvasSizeInPx);
- AspectRatio intrinsicRatio = IntrinsicRatioFromCanvasSize(canvasSizeInPx);
-
- nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
- area, intrinsicSize, intrinsicRatio, f->StylePosition());
+ nsRect dest = f->GetDestRect(mFrame->GetContentRectRelativeToSelf() +
+ ToReferenceFrame());
+ if (dest.IsEmpty()) {
+ return;
+ }
gfxContextMatrixAutoSaveRestore saveMatrix(aCtx);
@@ -405,6 +372,14 @@ nsIFrame::SizeComputationResult nsHTMLCanvasFrame::ComputeSize(
AspectRatioUsage::None};
}
+nsRect nsHTMLCanvasFrame::GetDestRect(const nsRect& aFrameContentBox) const {
+ const CSSIntSize canvasSizeInPx = GetCanvasSize();
+ IntrinsicSize intrinsicSize = IntrinsicSizeFromCanvasSize(canvasSizeInPx);
+ AspectRatio intrinsicRatio = IntrinsicRatioFromCanvasSize(canvasSizeInPx);
+ return nsLayoutUtils::ComputeObjectDestRect(aFrameContentBox, intrinsicSize,
+ intrinsicRatio, StylePosition());
+}
+
void nsHTMLCanvasFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aMetrics,
const ReflowInput& aReflowInput,
@@ -424,6 +399,8 @@ void nsHTMLCanvasFrame::Reflow(nsPresContext* aPresContext,
aMetrics.SetSize(wm, finalSize);
aMetrics.SetOverflowAreasToDesiredBounds();
+ aMetrics.mOverflowAreas.UnionAllWith(
+ GetDestRect(aReflowInput.ComputedPhysicalContentBoxRelativeToSelf()));
FinishAndStoreOverflow(&aMetrics);
// Reflow the single anon block child.
diff --git a/layout/generic/nsHTMLCanvasFrame.h b/layout/generic/nsHTMLCanvasFrame.h
@@ -65,6 +65,8 @@ class nsHTMLCanvasFrame final : public nsContainerFrame {
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) override;
+ nsRect GetDestRect(const nsRect& aFrameContentBox) const;
+
#ifdef ACCESSIBILITY
mozilla::a11y::AccType AccessibleType() override;
#endif
diff --git a/layout/generic/nsImageFrame.cpp b/layout/generic/nsImageFrame.cpp
@@ -1635,15 +1635,11 @@ void nsImageFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
}
aMetrics.SetOverflowAreasToDesiredBounds();
- bool imageOK = mKind != Kind::ImageLoadingContent ||
- ImageOk(mContent->AsElement()->State());
+ const bool imageOK = mKind != Kind::ImageLoadingContent ||
+ ImageOk(mContent->AsElement()->State());
// Determine if the size is available
- bool haveSize = false;
- if (loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) {
- haveSize = true;
- }
-
+ const bool haveSize = loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE;
if (!imageOK || !haveSize) {
nsRect altFeedbackSize(
0, 0,
@@ -1656,10 +1652,19 @@ void nsImageFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
// outside the image.
nsRect& inkOverflow = aMetrics.InkOverflow();
inkOverflow.UnionRect(inkOverflow, altFeedbackSize);
- } else if (PresShell()->IsActive()) {
- // We've just reflowed and we should have an accurate size, so we're ready
- // to request a decode.
- MaybeDecodeForPredictedSize();
+ } else {
+ // Union with our dest rect (note that it will most likely get clipped in
+ // FinishAndStoreOverflow). Only do this if we're not fragmented, since in
+ // that case overflow goes into our continuation.
+ if (aStatus.IsComplete()) {
+ aMetrics.mOverflowAreas.UnionAllWith(
+ GetDestRect(aReflowInput.ComputedPhysicalContentBoxRelativeToSelf()));
+ }
+ if (PresShell()->IsActive()) {
+ // We've just reflowed and we should have an accurate size, so we're ready
+ // to request a decode.
+ MaybeDecodeForPredictedSize();
+ }
}
FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
@@ -2351,10 +2356,8 @@ void nsDisplayImage::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
}
nsRect nsDisplayImage::GetDestRect() const {
- bool snap = true;
- const nsRect frameContentBox = GetBounds(&snap);
- nsImageFrame* imageFrame = static_cast<nsImageFrame*>(mFrame);
- return imageFrame->GetDestRect(frameContentBox);
+ auto* f = static_cast<nsImageFrame*>(mFrame);
+ return f->GetDestRect(f->GetContentRectRelativeToSelf() + ToReferenceFrame());
}
nsRect nsDisplayImage::GetDestRectViewTransition() const {
@@ -2581,7 +2584,8 @@ ImgDrawResult nsImageFrame::PaintImage(gfxContext& aRenderingContext,
"bad width");
nsPoint anchorPoint;
- nsRect dest = GetDestRect(GetContentRectRelativeToSelf() + aPt, &anchorPoint);
+ const nsRect dest =
+ GetDestRect(GetContentRectRelativeToSelf() + aPt, &anchorPoint);
SVGImageContext svgContext;
SVGImageContext::MaybeStoreContextPaint(svgContext, this, aImage);
diff --git a/layout/generic/nsImageFrame.h b/layout/generic/nsImageFrame.h
@@ -458,7 +458,7 @@ class nsDisplayImage final : public nsPaintedDisplayItem {
nsRect GetBounds(bool* aSnap) const {
*aSnap = true;
- return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
+ return Frame()->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
}
nsRect GetBounds(nsDisplayListBuilder*, bool* aSnap) const final {
diff --git a/layout/generic/nsVideoFrame.cpp b/layout/generic/nsVideoFrame.cpp
@@ -219,6 +219,12 @@ bool nsVideoFrame::ReflowFinished() {
return false;
}
+nsRect nsVideoFrame::GetDestRect(const nsRect& aContentBox) const {
+ return nsLayoutUtils::ComputeObjectDestRect(
+ aContentBox, GetIntrinsicSize(/* aIgnoreContainment = */ true),
+ GetIntrinsicRatio(/* aIgnoreContainment = */ true), StylePosition());
+}
+
void nsVideoFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) {
@@ -335,6 +341,10 @@ void nsVideoFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
aMetrics.SetSize(myWM, logicalDesiredSize);
aMetrics.SetOverflowAreasToDesiredBounds();
+ if (HasVideoElement()) {
+ aMetrics.mOverflowAreas.UnionAllWith(
+ GetDestRect(aReflowInput.ComputedPhysicalContentBoxRelativeToSelf()));
+ }
FinishAndStoreOverflow(&aMetrics);
@@ -385,13 +395,17 @@ Maybe<nsSize> nsVideoFrame::PosterImageSize() const {
}
AspectRatio nsVideoFrame::GetIntrinsicRatio() const {
+ return GetIntrinsicRatio(/* aIgnoreContainment = */ false);
+}
+
+AspectRatio nsVideoFrame::GetIntrinsicRatio(bool aIgnoreContainment) const {
if (!HasVideoElement()) {
// Audio elements have no intrinsic ratio.
return AspectRatio();
}
// 'contain:[inline-]size' replaced elements have no intrinsic ratio.
- if (GetContainSizeAxes().IsAny()) {
+ if (!aIgnoreContainment && GetContainSizeAxes().IsAny()) {
return AspectRatio();
}
@@ -442,8 +456,9 @@ bool nsVideoFrame::ShouldDisplayPoster() const {
return true;
}
-IntrinsicSize nsVideoFrame::GetIntrinsicSize() {
- const auto containAxes = GetContainSizeAxes();
+IntrinsicSize nsVideoFrame::GetIntrinsicSize(bool aIgnoreContainment) const {
+ const auto containAxes =
+ aIgnoreContainment ? ContainSizeAxes(false, false) : GetContainSizeAxes();
const auto isVideo = HasVideoElement();
// Intrinsic size will be given by contain-intrinsic-size if the element is
// size-contained. If both axes have containment, FinishIntrinsicSize() will
@@ -484,6 +499,10 @@ IntrinsicSize nsVideoFrame::GetIntrinsicSize() {
IntrinsicSize(kFallbackIntrinsicSize));
}
+IntrinsicSize nsVideoFrame::GetIntrinsicSize() {
+ return GetIntrinsicSize(/* aIgnoreContainment = */ false);
+}
+
void nsVideoFrame::UpdatePosterSource(bool aNotify) {
NS_ASSERTION(HasVideoElement(), "Only call this on <video> elements.");
HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());
@@ -551,9 +570,9 @@ class nsDisplayVideo final : public nsPaintedDisplayItem {
NS_DISPLAY_DECL_NAME("Video", TYPE_VIDEO)
already_AddRefed<ImageContainer> GetImageContainer(gfxRect& aDestGFXRect) {
- nsRect area = Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
- HTMLVideoElement* element =
- static_cast<HTMLVideoElement*>(Frame()->GetContent());
+ auto* f = static_cast<nsVideoFrame*>(Frame());
+ nsRect area = f->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
+ auto* element = static_cast<HTMLVideoElement*>(f->GetContent());
Maybe<CSSIntSize> videoSizeInPx = element->GetVideoSize();
if (videoSizeInPx.isNothing() || area.IsEmpty()) {
@@ -573,12 +592,9 @@ class nsDisplayVideo final : public nsPaintedDisplayItem {
return nullptr;
}
- const auto aspectRatio = AspectRatio::FromSize(*videoSizeInPx);
- const IntrinsicSize intrinsicSize(CSSPixel::ToAppUnits(*videoSizeInPx));
- nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
- area, intrinsicSize, aspectRatio, Frame()->StylePosition());
-
- aDestGFXRect = Frame()->PresContext()->AppUnitsToGfxUnits(dest);
+ nsRect dest =
+ f->GetDestRect(f->GetContentRectRelativeToSelf() + ToReferenceFrame());
+ aDestGFXRect = f->PresContext()->AppUnitsToGfxUnits(dest);
aDestGFXRect.Round();
if (aDestGFXRect.IsEmpty()) {
return nullptr;
@@ -587,7 +603,7 @@ class nsDisplayVideo final : public nsPaintedDisplayItem {
return container.forget();
}
- virtual bool CreateWebRenderCommands(
+ bool CreateWebRenderCommands(
mozilla::wr::DisplayListBuilder& aBuilder,
mozilla::wr::IpcResourceUpdateQueue& aResources,
const mozilla::layers::StackingContextHelper& aSc,
@@ -616,11 +632,10 @@ class nsDisplayVideo final : public nsPaintedDisplayItem {
// For opaque videos, we will want to override GetOpaqueRegion here.
// This is tracked by bug 1545498.
- virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
- bool* aSnap) const override {
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override {
*aSnap = true;
nsIFrame* f = Frame();
- return f->GetContentRectRelativeToSelf() + ToReferenceFrame();
+ return f->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
}
// Only report FirstContentfulPaint when the video is set
@@ -630,8 +645,7 @@ class nsDisplayVideo final : public nsPaintedDisplayItem {
return video->VideoWidth() > 0;
}
- virtual void Paint(nsDisplayListBuilder* aBuilder,
- gfxContext* aCtx) override {
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override {
HTMLVideoElement* element =
static_cast<HTMLVideoElement*>(Frame()->GetContent());
gfxRect destGFXRect;
diff --git a/layout/generic/nsVideoFrame.h b/layout/generic/nsVideoFrame.h
@@ -49,7 +49,9 @@ class nsVideoFrame : public nsContainerFrame,
/* get the size of the video's display */
mozilla::IntrinsicSize GetIntrinsicSize() final;
+ mozilla::IntrinsicSize GetIntrinsicSize(bool aIgnoreContainment) const;
mozilla::AspectRatio GetIntrinsicRatio() const final;
+ mozilla::AspectRatio GetIntrinsicRatio(bool aIgnoreContainment) const;
SizeComputationResult ComputeSize(
const SizeComputationInput& aSizingInput, mozilla::WritingMode aWM,
const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
@@ -61,6 +63,8 @@ class nsVideoFrame : public nsContainerFrame,
nscoord IntrinsicISize(const mozilla::IntrinsicSizeInput& aInput,
mozilla::IntrinsicISizeType aType) final;
+ nsRect GetDestRect(const nsRect& aContentBox) const;
+
void Destroy(DestroyContext&) final;
void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,