Bug 786502 - Separate background layers into separate items. r=roc

Separate out background layers into separate display-list items, so that
backgrounds that are a mix of fixed and non-fixed layers will be treated
individually.
This commit is contained in:
Chris Lord 2012-09-13 11:34:23 +01:00
parent 959da9d526
commit bb16d413e7
10 changed files with 154 additions and 54 deletions

View File

@ -1482,7 +1482,8 @@ nsCSSRendering::PaintBackground(nsPresContext* aPresContext,
const nsRect& aDirtyRect,
const nsRect& aBorderArea,
uint32_t aFlags,
nsRect* aBGClipRect)
nsRect* aBGClipRect,
int32_t aLayer)
{
SAMPLE_LABEL("nsCSSRendering", "PaintBackground");
NS_PRECONDITION(aForFrame,
@ -1510,7 +1511,7 @@ nsCSSRendering::PaintBackground(nsPresContext* aPresContext,
PaintBackgroundWithSC(aPresContext, aRenderingContext, aForFrame,
aDirtyRect, aBorderArea, sc,
*aForFrame->GetStyleBorder(), aFlags,
aBGClipRect);
aBGClipRect, aLayer);
}
static bool
@ -2328,7 +2329,8 @@ nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext,
nsStyleContext* aBackgroundSC,
const nsStyleBorder& aBorder,
uint32_t aFlags,
nsRect* aBGClipRect)
nsRect* aBGClipRect,
int32_t aLayer)
{
NS_PRECONDITION(aForFrame,
"Frame is expected to be provided to PaintBackground");
@ -2372,6 +2374,13 @@ nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext,
drawBackgroundImage,
drawBackgroundColor);
// If we're not drawing the back-most layer, we don't want to draw the
// background color.
const nsStyleBackground *bg = aBackgroundSC->GetStyleBackground();
if (drawBackgroundColor && aLayer >= 0 && aLayer != bg->mImageCount - 1) {
drawBackgroundColor = false;
}
// At this point, drawBackgroundImage and drawBackgroundColor are
// true if and only if we are actually supposed to paint an image or
// color into aDirtyRect, respectively.
@ -2406,7 +2415,6 @@ nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext,
// SetupCurrentBackgroundClip. (Arguably it should be the
// intersection, but that breaks the table painter -- in particular,
// taking the intersection breaks reftests/bugs/403249-1[ab].)
const nsStyleBackground *bg = aBackgroundSC->GetStyleBackground();
BackgroundClipState clipState;
uint8_t currentBackgroundClip;
bool isSolidBorder;
@ -2456,13 +2464,27 @@ nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext,
return;
}
if (bg->mImageCount < 1) {
// Return if there are no background layers, all work from this point
// onwards happens iteratively on these.
return;
}
// Validate the layer range before we start iterating.
int32_t startLayer = aLayer;
int32_t nLayers = 1;
if (startLayer < 0) {
startLayer = (int32_t)bg->mImageCount - 1;
nLayers = bg->mImageCount;
}
// Ensure we get invalidated for loads of the image. We need to do
// this here because this might be the only code that knows about the
// association of the style data with the frame.
if (aBackgroundSC != aForFrame->GetStyleContext()) {
ImageLoader* loader = aPresContext->Document()->StyleImageLoader();
NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(i, bg) {
NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT_WITH_RANGE(i, bg, startLayer, nLayers) {
if (bg->mLayers[i].mImage.GetType() == eStyleImageType_Image) {
imgIRequest *image = bg->mLayers[i].mImage.GetImageData();
@ -2479,7 +2501,7 @@ nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext,
if (drawBackgroundImage) {
bool clipSet = false;
NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(i, bg) {
NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT_WITH_RANGE(i, bg, startLayer, nLayers) {
const nsStyleBackground::Layer &layer = bg->mLayers[i];
if (!aBGClipRect) {
uint8_t newBackgroundClip = layer.mClip;

View File

@ -335,12 +335,17 @@ struct nsCSSRendering {
const nsRect& aDirtyRect,
const nsRect& aBorderArea,
uint32_t aFlags,
nsRect* aBGClipRect = nullptr);
nsRect* aBGClipRect = nullptr,
int32_t aLayer = -1);
/**
* Same as |PaintBackground|, except using the provided style structs.
* This short-circuits the code that ensures that the root element's
* background is drawn on the canvas.
* The aLayer parameter allows you to paint a single layer of the background.
* The default value for aLayer, -1, means that all layers will be painted.
* The background color will only be painted if the back-most layer is also
* being painted.
*/
static void PaintBackgroundWithSC(nsPresContext* aPresContext,
nsRenderingContext& aRenderingContext,
@ -350,7 +355,8 @@ struct nsCSSRendering {
nsStyleContext *aStyleContext,
const nsStyleBorder& aBorder,
uint32_t aFlags,
nsRect* aBGClipRect = nullptr);
nsRect* aBGClipRect = nullptr,
int32_t aLayer = -1);
/**
* Returns the rectangle covered by the given background layer image, taking

View File

@ -1362,8 +1362,11 @@ RegisterThemeGeometry(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
}
nsDisplayBackground::nsDisplayBackground(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame)
nsIFrame* aFrame,
uint32_t aLayer)
: nsDisplayItem(aBuilder, aFrame)
, mIsBottommostLayer(true)
, mLayer(aLayer)
{
MOZ_COUNT_CTOR(nsDisplayBackground);
const nsStyleDisplay* disp = mFrame->GetStyleDisplay();
@ -1383,8 +1386,17 @@ nsDisplayBackground::nsDisplayBackground(nsDisplayListBuilder* aBuilder,
nsPresContext* presContext = mFrame->PresContext();
nsStyleContext* bgSC;
bool hasBG = nsCSSRendering::FindBackground(presContext, mFrame, &bgSC);
if (hasBG && bgSC->GetStyleBackground()->HasFixedBackground()) {
aBuilder->SetHasFixedItems();
if (hasBG) {
const nsStyleBackground* bg = bgSC->GetStyleBackground();
if (mLayer != bg->mImageCount - 1) {
mIsBottommostLayer = false;
}
// Check if this background layer is attachment-fixed
if (!bg->mLayers[mLayer].mImage.IsEmpty() &&
bg->mLayers[mLayer].mAttachment == NS_STYLE_BG_ATTACHMENT_FIXED) {
aBuilder->SetHasFixedItems();
}
}
}
}
@ -1396,6 +1408,39 @@ nsDisplayBackground::~nsDisplayBackground()
#endif
}
/*static*/ nsresult
nsDisplayBackground::AppendBackgroundItemsToTop(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame,
nsDisplayList* aList,
nsDisplayBackground** aBackground)
{
nsStyleContext* bgSC;
const nsStyleBackground* bg = nullptr;
nsPresContext* presContext = aFrame->PresContext();
if (!aFrame->IsThemed() &&
nsCSSRendering::FindBackground(presContext, aFrame, &bgSC)) {
bg = bgSC->GetStyleBackground();
}
// Passing bg == nullptr in this macro will result in one iteration with
// i = 0.
bool backgroundSet = !aBackground;
NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(i, bg) {
nsDisplayBackground* bgItem =
new (aBuilder) nsDisplayBackground(aBuilder, aFrame, i);
nsresult rv = aList->AppendNewToTop(bgItem);
if (rv != NS_OK) {
return rv;
}
if (!backgroundSet) {
*aBackground = bgItem;
backgroundSet = true;
}
}
return NS_OK;
}
// Helper for RoundedRectIntersectsRect.
static bool
CheckCorner(nscoord aXOffset, nscoord aYOffset,
@ -1574,16 +1619,11 @@ nsDisplayBackground::TryOptimizeToImageLayer(nsDisplayListBuilder* aBuilder)
const nsStyleBackground *bg = bgSC->GetStyleBackground();
// We could pretty easily support multiple image layers, but for now we
// just punt here.
if (bg->mLayers.Length() != 1)
return false;
uint32_t flags = aBuilder->GetBackgroundPaintFlags();
nsPoint offset = ToReferenceFrame();
nsRect borderArea = nsRect(offset, mFrame->GetSize());
const nsStyleBackground::Layer &layer = bg->mLayers[0];
const nsStyleBackground::Layer &layer = bg->mLayers[mLayer];
nsBackgroundLayerState state =
nsCSSRendering::PrepareBackgroundLayer(presContext,
@ -1784,7 +1824,7 @@ nsDisplayBackground::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
*aSnap = true;
nsRect borderBox = nsRect(ToReferenceFrame(), mFrame->GetSize());
if (NS_GET_A(bg->mBackgroundColor) == 255 &&
if (mIsBottommostLayer && NS_GET_A(bg->mBackgroundColor) == 255 &&
!nsCSSRendering::IsCanvasFrame(mFrame)) {
result = GetInsideClipRegion(presContext, bottomLayer.mClip, borderBox, aSnap);
}
@ -1796,13 +1836,11 @@ nsDisplayBackground::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
// Of course, if there's only one frame in the flow, it doesn't matter.
if (bg->mBackgroundInlinePolicy == NS_STYLE_BG_INLINE_POLICY_EACH_BOX ||
(!mFrame->GetPrevContinuation() && !mFrame->GetNextContinuation())) {
NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(i, bg) {
const nsStyleBackground::Layer& layer = bg->mLayers[i];
if (layer.mImage.IsOpaque()) {
nsRect r = nsCSSRendering::GetBackgroundLayerRect(presContext, mFrame,
borderBox, *bg, layer);
result.Or(result, GetInsideClipRegion(presContext, layer.mClip, r, aSnap));
}
const nsStyleBackground::Layer& layer = bg->mLayers[mLayer];
if (layer.mImage.IsOpaque()) {
nsRect r = nsCSSRendering::GetBackgroundLayerRect(presContext, mFrame,
borderBox, *bg, layer);
result.Or(result, GetInsideClipRegion(presContext, layer.mClip, r, aSnap));
}
}
@ -1887,15 +1925,13 @@ nsDisplayBackground::ShouldFixToViewport(nsDisplayListBuilder* aBuilder)
if (!bg->HasFixedBackground())
return false;
NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(i, bg) {
const nsStyleBackground::Layer& layer = bg->mLayers[i];
if (layer.mAttachment != NS_STYLE_BG_ATTACHMENT_FIXED &&
!layer.mImage.IsEmpty()) {
return false;
}
if (layer.mClip != NS_STYLE_BG_CLIP_BORDER)
return false;
const nsStyleBackground::Layer& layer = bg->mLayers[mLayer];
if (layer.mAttachment != NS_STYLE_BG_ATTACHMENT_FIXED &&
!layer.mImage.IsEmpty()) {
return false;
}
if (layer.mClip != NS_STYLE_BG_CLIP_BORDER)
return false;
if (nsLayoutUtils::HasNonZeroCorner(mFrame->GetStyleBorder()->mBorderRadius))
return false;
@ -1925,7 +1961,7 @@ nsDisplayBackground::Paint(nsDisplayListBuilder* aBuilder,
nsCSSRendering::PaintBackground(mFrame->PresContext(), *aCtx, mFrame,
mVisibleRect,
nsRect(offset, mFrame->GetSize()),
flags);
flags, nullptr, mLayer);
}
nsRect
@ -1943,6 +1979,13 @@ nsDisplayBackground::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
return r + ToReferenceFrame();
}
uint32_t
nsDisplayBackground::GetPerFrameKey()
{
return (mLayer << nsDisplayItem::TYPE_BITS) |
nsDisplayItem::GetPerFrameKey();
}
nsRect
nsDisplayOutline::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
*aSnap = false;

View File

@ -1604,9 +1604,19 @@ private:
*/
class nsDisplayBackground : public nsDisplayItem {
public:
nsDisplayBackground(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
// aLayer signifies which background layer this item represents
nsDisplayBackground(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
uint32_t aLayer);
virtual ~nsDisplayBackground();
// This will create and append new items for all the layers of the
// background. If given, aBackground will be set with the address of the
// bottom-most background item.
static nsresult AppendBackgroundItemsToTop(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame,
nsDisplayList* aList,
nsDisplayBackground** aBackground = nullptr);
virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
LayerManager* aManager,
const ContainerParameters& aParameters);
@ -1628,6 +1638,7 @@ public:
virtual bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder);
virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap);
virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx);
virtual uint32_t GetPerFrameKey();
NS_DISPLAY_DECL_NAME("Background", TYPE_BACKGROUND)
// Returns the value of GetUnderlyingFrame()->IsThemed(), but cached
bool IsThemed() { return mIsThemed; }
@ -1645,11 +1656,14 @@ protected:
/* Used to cache mFrame->IsThemed() since it isn't a cheap call */
bool mIsThemed;
/* true if this item represents the bottom-most background layer */
bool mIsBottommostLayer;
nsITheme::Transparency mThemeTransparency;
/* If this background can be a simple image layer, we store the format here. */
nsRefPtr<ImageContainer> mImageContainer;
gfxRect mDestRect;
uint32_t mLayer;
};
/**

View File

@ -202,7 +202,7 @@ nsDisplayCanvasBackground::Paint(nsDisplayListBuilder* aBuilder,
nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
nsPoint offset = ToReferenceFrame();
nsRect bgClipRect = frame->CanvasArea() + offset;
if (NS_GET_A(mExtraBackgroundColor) > 0) {
if (mIsBottommostLayer && NS_GET_A(mExtraBackgroundColor) > 0) {
aCtx->SetColor(mExtraBackgroundColor);
aCtx->FillRect(bgClipRect);
}
@ -234,7 +234,7 @@ nsDisplayCanvasBackground::Paint(nsDisplayListBuilder* aBuilder,
surf ? bounds : mVisibleRect,
nsRect(offset, mFrame->GetSize()),
aBuilder->GetBackgroundPaintFlags(),
&bgClipRect);
&bgClipRect, mLayer);
if (surf) {
BlitSurface(dest, mDestRect, surf);
@ -295,10 +295,19 @@ nsCanvasFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
// We don't have any border or outline, and our background draws over
// the overflow area, so just add nsDisplayCanvasBackground instead of
// calling DisplayBorderBackgroundOutline.
if (IsVisibleForPainting(aBuilder)) {
rv = aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
nsDisplayCanvasBackground(aBuilder, this));
NS_ENSURE_SUCCESS(rv, rv);
if (IsVisibleForPainting(aBuilder)) {
nsStyleContext* bgSC;
const nsStyleBackground* bg = nullptr;
if (!IsThemed() &&
nsCSSRendering::FindBackground(PresContext(), this, &bgSC)) {
bg = bgSC->GetStyleBackground();
}
// Create separate items for each background layer.
NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(i, bg) {
rv = aLists.BorderBackground()->AppendNewToTop(
new (aBuilder) nsDisplayCanvasBackground(aBuilder, this, i));
NS_ENSURE_SUCCESS(rv, rv);
}
}
nsIFrame* kid;

View File

@ -123,8 +123,8 @@ protected:
*/
class nsDisplayCanvasBackground : public nsDisplayBackground {
public:
nsDisplayCanvasBackground(nsDisplayListBuilder* aBuilder, nsIFrame *aFrame)
: nsDisplayBackground(aBuilder, aFrame)
nsDisplayCanvasBackground(nsDisplayListBuilder* aBuilder, nsIFrame *aFrame, uint32_t aLayer)
: nsDisplayBackground(aBuilder, aFrame, aLayer)
{
mExtraBackgroundColor = NS_RGBA(0,0,0,0);
}

View File

@ -1457,16 +1457,18 @@ nsFrame::DisplayBackgroundUnconditional(nsDisplayListBuilder* aBuilder,
bool aForceBackground,
nsDisplayBackground** aBackground)
{
*aBackground = nullptr;
// Here we don't try to detect background propagation. Frames that might
// receive a propagated background should just set aForceBackground to
// true.
if (aBuilder->IsForEventDelivery() || aForceBackground ||
!GetStyleBackground()->IsTransparent() || GetStyleDisplay()->mAppearance) {
nsDisplayBackground* bg = new (aBuilder) nsDisplayBackground(aBuilder, this);
*aBackground = bg;
return aLists.BorderBackground()->AppendNewToTop(bg);
return nsDisplayBackground::AppendBackgroundItemsToTop(aBuilder, this,
aLists.BorderBackground(),
aBackground);
}
*aBackground = nullptr;
return NS_OK;
}

View File

@ -498,14 +498,14 @@ public:
static void ShutdownLayerActivityTimer();
/**
* Adds display item for standard CSS background if necessary.
* Adds display items for standard CSS background if necessary.
* Does not check IsVisibleForPainting.
* @param aForceBackground draw the background even if the frame
* background style appears to have no background --- this is useful
* for frames that might receive a propagated background via
* nsCSSRendering::FindBackground
* @param aBackground *aBackground is set to the new nsDisplayBackground item,
* if one is created, otherwise null.
* @param aBackground *aBackground is set to the bottom-most
* nsDisplayBackground item, if any are created, otherwise null.
*/
nsresult DisplayBackgroundUnconditional(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists,

View File

@ -487,7 +487,11 @@ struct nsStyleBackground {
const Layer& BottomLayer() const { return mLayers[mImageCount - 1]; }
#define NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(var_, stylebg_) \
for (uint32_t var_ = (stylebg_)->mImageCount; var_-- != 0; )
for (uint32_t var_ = (stylebg_) ? (stylebg_)->mImageCount : 1; var_-- != 0; )
#define NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT_WITH_RANGE(var_, stylebg_, start_, count_) \
NS_ASSERTION((start_) >= 0 && (uint32_t)(start_) < ((stylebg_) ? (stylebg_)->mImageCount : 1), "Invalid layer start!"); \
NS_ASSERTION((count_) > 0 && (count_) <= (start_) + 1, "Invalid layer range!"); \
for (uint32_t var_ = (start_) + 1; var_-- != (uint32_t)((start_) + 1 - (count_)); )
nscolor mBackgroundColor; // [reset]

View File

@ -1164,8 +1164,8 @@ nsTableFrame::DisplayGenericTablePart(nsDisplayListBuilder* aBuilder,
// handling events.
// XXX how to handle collapsed borders?
if (aBuilder->IsForEventDelivery()) {
nsresult rv = lists->BorderBackground()->AppendNewToTop(
new (aBuilder) nsDisplayBackground(aBuilder, aFrame));
nsresult rv = nsDisplayBackground::AppendBackgroundItemsToTop(aBuilder, aFrame,
lists->BorderBackground());
NS_ENSURE_SUCCESS(rv, rv);
}