Bug 735857 - Treat background-attachment:fixed as background-attachment:scroll if it's on a non-root element affected by a transform. r=mstange
MozReview-Commit-ID: 1lnQoD98xv3
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -3044,17 +3044,17 @@ nsCSSRendering::PaintBackgroundWithSC(ns
// When we're drawing a single layer, use the specified composition op,
// otherwise get the compositon op from the image layer.
CompositionOp co = (aLayer >= 0) ? aCompositonOp :
(paintMask ? GetGFXCompositeMode(layer.mComposite) :
GetGFXBlendMode(layer.mBlendMode));
nsBackgroundLayerState state =
PrepareImageLayer(aPresContext, aForFrame,
aFlags, paintBorderArea, clipState.mBGClipArea,
- layer, co);
+ layer, nullptr, co);
result &= state.mImageRenderer.PrepareResult();
if (!state.mFillArea.IsEmpty()) {
// Always using OP_OVER mode while drawing the bottom mask layer.
bool isBottomMaskLayer = paintMask ?
(i == (layers.mImageCount - 1)) : false;
if (co != CompositionOp::OP_OVER && !isBottomMaskLayer) {
NS_ASSERTION(ctx->CurrentOp() == CompositionOp::OP_OVER,
"It is assumed the initial op is OP_OVER, when it is restored later");
@@ -3078,17 +3078,18 @@ nsCSSRendering::PaintBackgroundWithSC(ns
return result;
}
nsRect
nsCSSRendering::ComputeImageLayerPositioningArea(nsPresContext* aPresContext,
nsIFrame* aForFrame,
const nsRect& aBorderArea,
const nsStyleImageLayers::Layer& aLayer,
- nsIFrame** aAttachedToFrame)
+ nsIFrame** aAttachedToFrame,
+ bool* aOutIsTransformedFixed)
{
// Compute background origin area relative to aBorderArea now as we may need
// it to compute the effective image size for a CSS gradient.
nsRect bgPositioningArea;
nsIAtom* frameType = aForFrame->GetType();
nsIFrame* geometryFrame = aForFrame;
if (MOZ_UNLIKELY(frameType == nsGkAtoms::scrollFrame &&
@@ -3155,28 +3156,35 @@ nsCSSRendering::ComputeImageLayerPositio
pageContentFrame =
nsLayoutUtils::GetClosestFrameOfType(aForFrame, nsGkAtoms::pageContentFrame);
if (pageContentFrame) {
attachedToFrame = pageContentFrame;
}
// else this is an embedded shell and its root frame is what we want
}
- // Set the background positioning area to the viewport's area
- // (relative to aForFrame)
- bgPositioningArea =
- nsRect(-aForFrame->GetOffsetTo(attachedToFrame), attachedToFrame->GetSize());
-
- if (!pageContentFrame) {
- // Subtract the size of scrollbars.
- nsIScrollableFrame* scrollableFrame =
- aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
- if (scrollableFrame) {
- nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes();
- bgPositioningArea.Deflate(scrollbars);
+ // If the background is affected by a transform, treat is as if it
+ // wasn't fixed.
+ if (nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame)) {
+ attachedToFrame = aForFrame;
+ *aOutIsTransformedFixed = true;
+ } else {
+ // Set the background positioning area to the viewport's area
+ // (relative to aForFrame)
+ bgPositioningArea =
+ nsRect(-aForFrame->GetOffsetTo(attachedToFrame), attachedToFrame->GetSize());
+
+ if (!pageContentFrame) {
+ // Subtract the size of scrollbars.
+ nsIScrollableFrame* scrollableFrame =
+ aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
+ if (scrollableFrame) {
+ nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes();
+ bgPositioningArea.Deflate(scrollbars);
+ }
}
}
}
*aAttachedToFrame = attachedToFrame;
return bgPositioningArea;
}
@@ -3219,16 +3227,17 @@ ComputeDrawnSizeForBackground(const CSSS
nsBackgroundLayerState
nsCSSRendering::PrepareImageLayer(nsPresContext* aPresContext,
nsIFrame* aForFrame,
uint32_t aFlags,
const nsRect& aBorderArea,
const nsRect& aBGClipRect,
const nsStyleImageLayers::Layer& aLayer,
+ bool* aOutIsTransformedFixed,
CompositionOp aCompositonOp)
{
/*
* The properties we need to keep in mind when drawing style image
* layers are:
*
* background-image/ mask-image
* background-repeat/ mask-repeat
@@ -3300,34 +3309,38 @@ nsCSSRendering::PrepareImageLayer(nsPres
nsBackgroundLayerState state(aForFrame, &aLayer.mImage, irFlags);
if (!state.mImageRenderer.PrepareImage()) {
// There's no image or it's not ready to be painted.
return state;
}
// The frame to which the background is attached
nsIFrame* attachedToFrame = aForFrame;
+ // Is the background marked 'fixed', but affected by a transform?
+ bool transformedFixed = false;
// Compute background origin area relative to aBorderArea now as we may need
// it to compute the effective image size for a CSS gradient.
nsRect bgPositioningArea =
ComputeImageLayerPositioningArea(aPresContext, aForFrame, aBorderArea,
- aLayer, &attachedToFrame);
+ aLayer, &attachedToFrame, &transformedFixed);
+ if (aOutIsTransformedFixed) {
+ *aOutIsTransformedFixed = transformedFixed;
+ }
// For background-attachment:fixed backgrounds, we'll limit the area
// where the background can be drawn to the viewport.
nsRect bgClipRect = aBGClipRect;
// Compute the anchor point.
//
// relative to aBorderArea.TopLeft() (which is where the top-left
// of aForFrame's border-box will be rendered)
nsPoint imageTopLeft;
- if (NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED == aLayer.mAttachment) {
- if ((aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) &&
- !IsTransformed(aForFrame, attachedToFrame)) {
+ if (NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED == aLayer.mAttachment && !transformedFixed) {
+ if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) {
// Clip background-attachment:fixed backgrounds to the viewport, if we're
// painting to the screen and not transformed. This avoids triggering
// tiling in common cases, without affecting output since drawing is
// always clipped to the viewport when we draw to the screen. (But it's
// not a pure optimization since it can affect the values of pixels at the
// edge of the viewport --- whether they're sampled from a putative "next
// tile" or not.)
bgClipRect.IntersectRect(bgClipRect, bgPositioningArea + aBorderArea.TopLeft());
--- a/layout/base/nsCSSRendering.h
+++ b/layout/base/nsCSSRendering.h
@@ -523,25 +523,27 @@ struct nsCSSRendering {
bool& aDrawBackgroundImage,
bool& aDrawBackgroundColor);
static nsRect
ComputeImageLayerPositioningArea(nsPresContext* aPresContext,
nsIFrame* aForFrame,
const nsRect& aBorderArea,
const nsStyleImageLayers::Layer& aLayer,
- nsIFrame** aAttachedToFrame);
+ nsIFrame** aAttachedToFrame,
+ bool* aOutTransformedFixed);
static nsBackgroundLayerState
PrepareImageLayer(nsPresContext* aPresContext,
nsIFrame* aForFrame,
uint32_t aFlags,
const nsRect& aBorderArea,
const nsRect& aBGClipRect,
const nsStyleImageLayers::Layer& aLayer,
+ bool* aOutIsTransformedFixed = nullptr,
CompositionOp aCompositionOp = CompositionOp::OP_OVER);
struct ImageLayerClipState {
nsRect mBGClipArea; // Affected by mClippedRadii
nsRect mAdditionalBGClipArea; // Not affected by mClippedRadii
nsRect mDirtyRect;
gfxRect mDirtyRectGfx;
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -2348,29 +2348,32 @@ nsDisplayBackgroundImage::nsDisplayBackg
const nsStyleBackground* aBackgroundStyle)
: nsDisplayImageContainer(aBuilder, aFrame)
, mBackgroundStyle(aBackgroundStyle)
, mLayer(aLayer)
, mIsRasterImage(false)
{
MOZ_COUNT_CTOR(nsDisplayBackgroundImage);
- mBounds = GetBoundsInternal(aBuilder);
- if (ShouldFixToViewport(aBuilder)) {
- mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(this);
- }
-
nsPresContext* presContext = mFrame->PresContext();
uint32_t flags = aBuilder->GetBackgroundPaintFlags();
nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
const nsStyleImageLayers::Layer &layer = mBackgroundStyle->mImage.mLayers[mLayer];
+ bool isTransformedFixed;
nsBackgroundLayerState state =
nsCSSRendering::PrepareImageLayer(presContext, mFrame, flags,
- borderArea, borderArea, layer);
+ borderArea, borderArea, layer,
+ &isTransformedFixed);
+ mShouldTreatAsFixed = ComputeShouldTreatAsFixed(isTransformedFixed);
+
+ mBounds = GetBoundsInternal(aBuilder);
+ if (ShouldFixToViewport(aBuilder)) {
+ mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(this);
+ }
mFillRect = state.mFillArea;
mDestRect = state.mDestArea;
nsImageRenderer* imageRenderer = &state.mImageRenderer;
// We only care about images here, not gradients.
if (imageRenderer->IsRasterImage()) {
mIsRasterImage = true;
@@ -2593,44 +2596,65 @@ RoundedBorderIntersectsRect(nsIFrame* aF
static bool RoundedRectContainsRect(const nsRect& aRoundedRect,
const nscoord aRadii[8],
const nsRect& aContainedRect) {
nsRegion rgn = nsLayoutUtils::RoundedRectIntersectRect(aRoundedRect, aRadii, aContainedRect);
return rgn.Contains(aContainedRect);
}
bool
+nsDisplayBackgroundImage::ShouldTreatAsFixed() const
+{
+ return mShouldTreatAsFixed;
+}
+
+bool
+nsDisplayBackgroundImage::ComputeShouldTreatAsFixed(bool isTransformedFixed) const
+{
+ if (!mBackgroundStyle)
+ return false;
+
+ const nsStyleImageLayers::Layer &layer = mBackgroundStyle->mImage.mLayers[mLayer];
+ if (layer.mAttachment != NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED)
+ return false;
+
+ // background-attachment:fixed is treated as background-attachment:scroll
+ // if it's affected by a transform.
+ // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=17521.
+ return !isTransformedFixed;
+}
+
+bool
nsDisplayBackgroundImage::IsSingleFixedPositionImage(nsDisplayListBuilder* aBuilder,
const nsRect& aClipRect,
gfxRect* aDestRect)
{
if (!mBackgroundStyle)
return false;
if (mBackgroundStyle->mImage.mLayers.Length() != 1)
return false;
- const nsStyleImageLayers::Layer &layer = mBackgroundStyle->mImage.mLayers[mLayer];
- if (layer.mAttachment != NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED)
+ if (!ShouldTreatAsFixed())
return false;
// We only care about images here, not gradients.
if (!mIsRasterImage)
return false;
int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
*aDestRect = nsLayoutUtils::RectToGfxRect(mFillRect, appUnitsPerDevPixel);
return true;
}
bool
nsDisplayBackgroundImage::IsNonEmptyFixedImage() const
{
- return mBackgroundStyle->mImage.mLayers[mLayer].mAttachment == NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED &&
+ return ShouldTreatAsFixed() &&
!mBackgroundStyle->mImage.mLayers[mLayer].mImage.IsEmpty();
}
bool
nsDisplayBackgroundImage::ShouldFixToViewport(nsDisplayListBuilder* aBuilder)
{
// APZ needs background-attachment:fixed images layerized for correctness.
RefPtr<LayerManager> layerManager = aBuilder->GetWidgetLayerManager();
@@ -2882,21 +2906,23 @@ nsDisplayBackgroundImage::IsUniform(nsDi
nsRect
nsDisplayBackgroundImage::GetPositioningArea()
{
if (!mBackgroundStyle) {
return nsRect();
}
nsIFrame* attachedToFrame;
+ bool transformedFixed;
return nsCSSRendering::ComputeImageLayerPositioningArea(
mFrame->PresContext(), mFrame,
nsRect(ToReferenceFrame(), mFrame->GetSize()),
mBackgroundStyle->mImage.mLayers[mLayer],
- &attachedToFrame) + ToReferenceFrame();
+ &attachedToFrame,
+ &transformedFixed) + ToReferenceFrame();
}
bool
nsDisplayBackgroundImage::RenderingMightDependOnPositioningAreaSizeChange()
{
if (!mBackgroundStyle)
return false;
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -2742,16 +2742,18 @@ protected:
typedef class mozilla::layers::ImageLayer ImageLayer;
bool TryOptimizeToImageLayer(LayerManager* aManager, nsDisplayListBuilder* aBuilder);
bool IsSingleFixedPositionImage(nsDisplayListBuilder* aBuilder,
const nsRect& aClipRect,
gfxRect* aDestRect);
bool IsNonEmptyFixedImage() const;
nsRect GetBoundsInternal(nsDisplayListBuilder* aBuilder);
+ bool ShouldTreatAsFixed() const;
+ bool ComputeShouldTreatAsFixed(bool isTransformedFixed) const;
void PaintInternal(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx,
const nsRect& aBounds, nsRect* aClipRect);
// Determine whether we want to be separated into our own layer, independent
// of whether this item can actually be layerized.
enum ImageLayerization {
WHENEVER_POSSIBLE,
@@ -2766,16 +2768,18 @@ protected:
const nsStyleBackground* mBackgroundStyle;
nsCOMPtr<imgIContainer> mImage;
nsRect mFillRect;
nsRect mDestRect;
/* Bounds of this display item */
nsRect mBounds;
uint32_t mLayer;
bool mIsRasterImage;
+ /* Whether the image should be treated as fixed to the viewport. */
+ bool mShouldTreatAsFixed;
};
/**
* A display item to paint the native theme background for a frame.
*/
class nsDisplayThemedBackground : public nsDisplayItem {
public:
--- a/layout/generic/nsCanvasFrame.h
+++ b/layout/generic/nsCanvasFrame.h
@@ -215,17 +215,17 @@ public:
}
virtual bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) override
{
// Put background-attachment:fixed canvas background images in their own
// compositing layer. Since we know their background painting area can't
// change (unless the viewport size itself changes), async scrolling
// will work well.
- return mBackgroundStyle->mImage.mLayers[mLayer].mAttachment == NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED &&
+ return ShouldTreatAsFixed() &&
!mBackgroundStyle->mImage.mLayers[mLayer].mImage.IsEmpty();
}
// We still need to paint a background color as well as an image for this item,
// so we can't support this yet.
virtual bool SupportsOptimizingToImage() override { return false; }
new file mode 100644
--- /dev/null
+++ b/layout/reftests/backgrounds/fixed-bg-inside-transform-ref.html
@@ -0,0 +1,35 @@
+<html>
+<head>
+<style>
+ body {
+ height: 4000px;
+ margin: 0;
+ }
+
+ #outer {
+ margin: 200px;
+ height: 700px;
+ width: 300px;
+ transform: rotate(45deg);
+ -webkit-transform: rotate(45deg);
+ overflow: hidden;
+ }
+
+ #inner {
+ height: 700px;
+ background-image: radial-gradient(farthest-corner at center, blue, black);
+ background-size: 300px 300px;
+ background-position: 200px 200px;
+ background-color: lime;
+ background-repeat: no-repeat;
+ background-attachment: scroll;
+ }
+</style>
+</head>
+<body>
+<div id="outer">
+ <div id="inner">
+ </div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/backgrounds/fixed-bg-inside-transform.html
@@ -0,0 +1,35 @@
+<html>
+<head>
+<style>
+ body {
+ height: 4000px;
+ margin: 0;
+ }
+
+ #outer {
+ margin: 200px;
+ height: 700px;
+ width: 300px;
+ transform: rotate(45deg);
+ -webkit-transform: rotate(45deg);
+ overflow: hidden;
+ }
+
+ #inner {
+ height: 700px;
+ background-image: radial-gradient(farthest-corner at center, blue, black);
+ background-size: 300px 300px;
+ background-position: 200px 200px;
+ background-color: lime;
+ background-repeat: no-repeat;
+ background-attachment: fixed;
+ }
+</style>
+</head>
+<body>
+<div id="outer">
+ <div id="inner">
+ </div>
+</div>
+</body>
+</html>
--- a/layout/reftests/backgrounds/reftest.list
+++ b/layout/reftests/backgrounds/reftest.list
@@ -118,16 +118,17 @@ random-if(OSX==1010) == background-size-
# doesn't sample too far astray from the boundaries).
fails == background-size-zoom-repeat.html background-size-zoom-repeat-ref.html
# -moz-default-background-color and -moz-default-color (bug 591341)
== background-moz-default-background-color.html background-moz-default-background-color-ref.html
random-if(B2G||Mulet) == fixed-bg-with-transform-outside-viewport-1.html fixed-bg-with-transform-outside-viewport-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
fuzzy(2,83) == fixed-bg-border-radius.html fixed-bg-border-radius-ref.html
+== fixed-bg-inside-transform.html fixed-bg-inside-transform-ref.html
HTTP == root-background-1.html root-background-ref.html
HTTP != root-background-1.html about:blank
random-if(B2G||Mulet) == really-big-background.html really-big-background-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
== body-background.html body-background-ref.html
== table-background.html table-background-ref.html
== table-background-print.html table-background-print-ref.html
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -2687,22 +2687,23 @@ nsChangeHint nsStyleBackground::CalcDiff
hint |= nsChangeHint_RepaintFrame;
}
hint |= mImage.CalcDifference(aOther.mImage);
return hint;
}
-bool nsStyleBackground::HasFixedBackground() const
+bool nsStyleBackground::HasFixedBackground(nsIFrame* aFrame) const
{
NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, mImage) {
const nsStyleImageLayers::Layer &layer = mImage.mLayers[i];
if (layer.mAttachment == NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED &&
- !layer.mImage.IsEmpty()) {
+ !layer.mImage.IsEmpty() &&
+ !nsLayoutUtils::IsTransformed(aFrame)) {
return true;
}
}
return false;
}
bool nsStyleBackground::IsTransparent() const
{
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -749,17 +749,17 @@ struct nsStyleBackground {
// True if this background is completely transparent.
bool IsTransparent() const;
// We have to take slower codepaths for fixed background attachment,
// but we don't want to do that when there's no image.
// Not inline because it uses an nsCOMPtr<imgIRequest>
// FIXME: Should be in nsStyleStructInlines.h.
- bool HasFixedBackground() const;
+ bool HasFixedBackground(nsIFrame* aFrame) const;
const nsStyleImageLayers::Layer& BottomLayer() const { return mImage.BottomLayer(); }
nsStyleImageLayers mImage;
nscolor mBackgroundColor; // [reset]
};
// See https://bugzilla.mozilla.org/show_bug.cgi?id=271586#c43 for why
--- a/layout/tables/nsTableFrame.cpp
+++ b/layout/tables/nsTableFrame.cpp
@@ -1090,17 +1090,17 @@ nsDisplayTableItem::GetBounds(nsDisplayL
}
void
nsDisplayTableItem::UpdateForFrameBackground(nsIFrame* aFrame)
{
nsStyleContext *bgSC;
if (!nsCSSRendering::FindBackground(aFrame, &bgSC))
return;
- if (!bgSC->StyleBackground()->HasFixedBackground())
+ if (!bgSC->StyleBackground()->HasFixedBackground(aFrame))
return;
mPartHasFixedBackground = true;
}
nsDisplayItemGeometry*
nsDisplayTableItem::AllocateGeometry(nsDisplayListBuilder* aBuilder)
{