From 992cbd5b9377448d1f63182b2a320bd6ee0c68ca Mon Sep 17 00:00:00 2001 From: Botond Ballo Date: Mon, 25 Apr 2016 17:20:13 -0400 Subject: [PATCH] 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 --HG-- extra : rebase_source : 0cfd2c2a7926210775b6a1beff69ba32f2a10d6c --- layout/base/nsCSSRendering.cpp | 47 ++++++++++++------- layout/base/nsCSSRendering.h | 4 +- layout/base/nsDisplayList.cpp | 46 ++++++++++++++---- layout/base/nsDisplayList.h | 4 ++ layout/generic/nsCanvasFrame.h | 2 +- .../fixed-bg-inside-transform-ref.html | 35 ++++++++++++++ .../fixed-bg-inside-transform.html | 35 ++++++++++++++ layout/reftests/backgrounds/reftest.list | 1 + layout/style/nsStyleStruct.cpp | 5 +- layout/style/nsStyleStruct.h | 2 +- layout/tables/nsTableFrame.cpp | 2 +- 11 files changed, 150 insertions(+), 33 deletions(-) create mode 100644 layout/reftests/backgrounds/fixed-bg-inside-transform-ref.html create mode 100644 layout/reftests/backgrounds/fixed-bg-inside-transform.html diff --git a/layout/base/nsCSSRendering.cpp b/layout/base/nsCSSRendering.cpp index 835afea2072c..1ee3978715ab 100644 --- a/layout/base/nsCSSRendering.cpp +++ b/layout/base/nsCSSRendering.cpp @@ -3052,7 +3052,7 @@ nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext, 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. @@ -3086,7 +3086,8 @@ 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. @@ -3163,18 +3164,25 @@ nsCSSRendering::ComputeImageLayerPositioningArea(nsPresContext* aPresContext, // 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 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); + if (!pageContentFrame) { + // Subtract the size of scrollbars. + nsIScrollableFrame* scrollableFrame = + aPresContext->PresShell()->GetRootScrollFrameAsScrollable(); + if (scrollableFrame) { + nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes(); + bgPositioningArea.Deflate(scrollbars); + } } } } @@ -3227,6 +3235,7 @@ nsCSSRendering::PrepareImageLayer(nsPresContext* aPresContext, const nsRect& aBorderArea, const nsRect& aBGClipRect, const nsStyleImageLayers::Layer& aLayer, + bool* aOutIsTransformedFixed, CompositionOp aCompositonOp) { /* @@ -3308,11 +3317,16 @@ nsCSSRendering::PrepareImageLayer(nsPresContext* aPresContext, // 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. @@ -3323,9 +3337,8 @@ nsCSSRendering::PrepareImageLayer(nsPresContext* aPresContext, // 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 diff --git a/layout/base/nsCSSRendering.h b/layout/base/nsCSSRendering.h index c80ab9324ee5..6d4144490c0f 100644 --- a/layout/base/nsCSSRendering.h +++ b/layout/base/nsCSSRendering.h @@ -528,7 +528,8 @@ struct nsCSSRendering { nsIFrame* aForFrame, const nsRect& aBorderArea, const nsStyleImageLayers::Layer& aLayer, - nsIFrame** aAttachedToFrame); + nsIFrame** aAttachedToFrame, + bool* aOutTransformedFixed); static nsBackgroundLayerState PrepareImageLayer(nsPresContext* aPresContext, @@ -537,6 +538,7 @@ struct nsCSSRendering { const nsRect& aBorderArea, const nsRect& aBGClipRect, const nsStyleImageLayers::Layer& aLayer, + bool* aOutIsTransformedFixed = nullptr, CompositionOp aCompositionOp = CompositionOp::OP_OVER); struct ImageLayerClipState { diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index f1a3c716764f..1c846e9b3ca2 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -2337,19 +2337,22 @@ nsDisplayBackgroundImage::nsDisplayBackgroundImage(nsDisplayListBuilder* aBuilde { 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; @@ -2581,6 +2584,28 @@ static bool RoundedRectContainsRect(const nsRect& aRoundedRect, 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, @@ -2592,8 +2617,7 @@ nsDisplayBackgroundImage::IsSingleFixedPositionImage(nsDisplayListBuilder* aBuil 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. @@ -2609,7 +2633,7 @@ nsDisplayBackgroundImage::IsSingleFixedPositionImage(nsDisplayListBuilder* aBuil bool nsDisplayBackgroundImage::IsNonEmptyFixedImage() const { - return mBackgroundStyle->mImage.mLayers[mLayer].mAttachment == NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED && + return ShouldTreatAsFixed() && !mBackgroundStyle->mImage.mLayers[mLayer].mImage.IsEmpty(); } @@ -2871,11 +2895,13 @@ nsDisplayBackgroundImage::GetPositioningArea() 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 diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index a0ae52e115c1..5e12dba86f32 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -2721,6 +2721,8 @@ protected: 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); @@ -2745,6 +2747,8 @@ protected: nsRect mBounds; uint32_t mLayer; bool mIsRasterImage; + /* Whether the image should be treated as fixed to the viewport. */ + bool mShouldTreatAsFixed; }; diff --git a/layout/generic/nsCanvasFrame.h b/layout/generic/nsCanvasFrame.h index bbf688be9c9d..5b628be78f37 100644 --- a/layout/generic/nsCanvasFrame.h +++ b/layout/generic/nsCanvasFrame.h @@ -220,7 +220,7 @@ public: // 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(); } diff --git a/layout/reftests/backgrounds/fixed-bg-inside-transform-ref.html b/layout/reftests/backgrounds/fixed-bg-inside-transform-ref.html new file mode 100644 index 000000000000..c763fa5b940e --- /dev/null +++ b/layout/reftests/backgrounds/fixed-bg-inside-transform-ref.html @@ -0,0 +1,35 @@ + + + + + +
+
+
+
+ + diff --git a/layout/reftests/backgrounds/fixed-bg-inside-transform.html b/layout/reftests/backgrounds/fixed-bg-inside-transform.html new file mode 100644 index 000000000000..d2995162749a --- /dev/null +++ b/layout/reftests/backgrounds/fixed-bg-inside-transform.html @@ -0,0 +1,35 @@ + + + + + +
+
+
+
+ + diff --git a/layout/reftests/backgrounds/reftest.list b/layout/reftests/backgrounds/reftest.list index 34235a01ea4d..cbc7400ce7fd 100644 --- a/layout/reftests/backgrounds/reftest.list +++ b/layout/reftests/backgrounds/reftest.list @@ -123,6 +123,7 @@ fails == background-size-zoom-repeat.html background-size-zoom-repeat-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 diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index 021f4c69b027..0bca24ec373e 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -2684,12 +2684,13 @@ nsChangeHint nsStyleBackground::CalcDifference(const nsStyleBackground& aOther) 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; } } diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index 9ae1ac11b8f9..e1b25d44f665 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -815,7 +815,7 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleBackground { // but we don't want to do that when there's no image. // Not inline because it uses an nsCOMPtr // FIXME: Should be in nsStyleStructInlines.h. - bool HasFixedBackground() const; + bool HasFixedBackground(nsIFrame* aFrame) const; const nsStyleImageLayers::Layer& BottomLayer() const { return mImage.BottomLayer(); } diff --git a/layout/tables/nsTableFrame.cpp b/layout/tables/nsTableFrame.cpp index 59ffe00d5d21..57d3ff0cd4be 100644 --- a/layout/tables/nsTableFrame.cpp +++ b/layout/tables/nsTableFrame.cpp @@ -1095,7 +1095,7 @@ nsDisplayTableItem::UpdateForFrameBackground(nsIFrame* aFrame) nsStyleContext *bgSC; if (!nsCSSRendering::FindBackground(aFrame, &bgSC)) return; - if (!bgSC->StyleBackground()->HasFixedBackground()) + if (!bgSC->StyleBackground()->HasFixedBackground(aFrame)) return; mPartHasFixedBackground = true;