Bug 424915. Improve display list analysis so that sites with content covered by the scrolling element, such as GMail, can use bitblit scrolling. r+sr=dbaron,a=beltzner

This commit is contained in:
roc+@cs.cmu.edu 2008-04-10 20:46:37 -07:00
parent ef3215ce8e
commit 1dead6507c
5 changed files with 97 additions and 63 deletions

View File

@ -52,9 +52,9 @@
#include "gfxContext.h"
nsDisplayListBuilder::nsDisplayListBuilder(nsIFrame* aReferenceFrame,
PRBool aIsForEvents, PRBool aBuildCaret, nsIFrame* aMovingFrame)
PRBool aIsForEvents, PRBool aBuildCaret)
: mReferenceFrame(aReferenceFrame),
mMovingFrame(aMovingFrame),
mMovingFrame(nsnull),
mIgnoreScrollFrame(nsnull),
mCurrentTableItem(nsnull),
mBuildCaret(aBuildCaret),
@ -233,23 +233,23 @@ nsDisplayItem::OptimizeVisibility(nsDisplayListBuilder* aBuilder,
nsRect bounds = GetBounds(aBuilder);
if (!aVisibleRegion->Intersects(bounds))
return PR_FALSE;
nsIFrame* f = GetUnderlyingFrame();
NS_ASSERTION(f, "GetUnderlyingFrame() must return non-null for leaf items");
if (aBuilder->HasMovingFrames() && aBuilder->IsMovingFrame(f)) {
// If this frame is in the moving subtree, and it doesn't
// require repainting just because it's moved, then just remove it now
// because it's not relevant.
if (!IsVaryingRelativeToFrame(aBuilder, aBuilder->GetRootMovingFrame()))
return PR_FALSE;
// keep it, but don't let it cover other display items (see nsLayoutUtils::
// ComputeRepaintRegionForCopy)
return PR_TRUE;
}
PRBool isMoving = aBuilder->IsMovingFrame(f);
if (IsOpaque(aBuilder)) {
aVisibleRegion->SimpleSubtract(bounds);
nsRect opaqueArea = bounds;
if (isMoving) {
// The display list should include items for both the before and after
// states (see nsLayoutUtils::ComputeRepaintRegionForCopy. So the
// only area we want to cover is the the area that was opaque in the
// before state and in the after state.
opaqueArea.IntersectRect(bounds - aBuilder->GetMoveDelta(), bounds);
}
aVisibleRegion->SimpleSubtract(opaqueArea);
}
return PR_TRUE;
}
@ -509,26 +509,29 @@ nsDisplayBackground::IsUniform(nsDisplayListBuilder* aBuilder) {
}
PRBool
nsDisplayBackground::IsVaryingRelativeToFrame(nsDisplayListBuilder* aBuilder,
nsIFrame* aAncestorFrame)
nsDisplayBackground::IsVaryingRelativeToMovingFrame(nsDisplayListBuilder* aBuilder)
{
NS_ASSERTION(aBuilder->IsMovingFrame(mFrame),
"IsVaryingRelativeToMovingFrame called on non-moving frame!");
nsPresContext* presContext = mFrame->PresContext();
PRBool isCanvas;
const nsStyleBackground* bg;
PRBool hasBG =
nsCSSRendering::FindBackground(mFrame->PresContext(), mFrame, &bg, &isCanvas);
nsCSSRendering::FindBackground(presContext, mFrame, &bg, &isCanvas);
if (!hasBG)
return PR_FALSE;
if (!bg->HasFixedBackground())
return PR_FALSE;
// aAncestorFrame is the frame that is going to be moved.
// Check if mFrame is equal to aAncestorFrame or aAncestorFrame is an
// ancestor of mFrame in the same document. If this is true, mFrame
nsIFrame* movingFrame = aBuilder->GetRootMovingFrame();
// movingFrame is the frame that is going to be moved. It must be equal
// to mFrame or some ancestor of mFrame, see assertion above.
// If mFrame is in the same document as movingFrame, then mFrame
// will move relative to its viewport, which means this display item will
// change when it is moved. If they are in different documents, we do not
// change when it is moved. If they are in different documents, we do not
// want to return true because mFrame won't move relative to its viewport.
return mFrame == aAncestorFrame ||
nsLayoutUtils::IsProperAncestorFrame(aAncestorFrame, mFrame);
return movingFrame->PresContext() == presContext;
}
void
@ -676,13 +679,13 @@ PRBool nsDisplayWrapList::IsUniform(nsDisplayListBuilder* aBuilder) {
return PR_FALSE;
}
PRBool nsDisplayWrapList::IsVaryingRelativeToFrame(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame) {
for (nsDisplayItem* i = mList.GetBottom(); i != nsnull; i = i->GetAbove()) {
if (i->IsVaryingRelativeToFrame(aBuilder, aFrame))
return PR_TRUE;
}
return PR_FALSE;
PRBool nsDisplayWrapList::IsVaryingRelativeToMovingFrame(nsDisplayListBuilder* aBuilder) {
// The only existing consumer of IsVaryingRelativeToMovingFrame is
// nsLayoutUtils::ComputeRepaintRegionForCopy, which refrains from calling
// this on wrapped lists.
NS_WARNING("nsDisplayWrapList::IsVaryingRelativeToMovingFrame called unexpectedly");
// We could try to do something but let's conservatively just return PR_TRUE.
return PR_TRUE;
}
void nsDisplayWrapList::Paint(nsDisplayListBuilder* aBuilder,

View File

@ -133,7 +133,7 @@ public:
* operation, we specify the scrolled frame as the moving frame.
*/
nsDisplayListBuilder(nsIFrame* aReferenceFrame, PRBool aIsForEvents,
PRBool aBuildCaret, nsIFrame* aMovingFrame = nsnull);
PRBool aBuildCaret);
~nsDisplayListBuilder();
/**
@ -154,14 +154,30 @@ public:
* a new stacking context
*/
PRBool IsAtRootOfPseudoStackingContext() { return mIsAtRootOfPseudoStackingContext; }
/**
* Indicate that we'll use this display list to analyze the effects
* of aMovingFrame moving by aMoveDelta. The move has already been
* applied to the frame tree.
*/
void SetMovingFrame(nsIFrame* aMovingFrame, const nsPoint& aMoveDelta) {
mMovingFrame = aMovingFrame;
mMoveDelta = aMoveDelta;
}
/**
* @return PR_TRUE if we are doing analysis of moving frames
*/
PRBool HasMovingFrames() { return mMovingFrame != nsnull; }
/**
* @return the frame that is being hypothetically moved
* @return the frame that was moved
*/
nsIFrame* GetRootMovingFrame() { return mMovingFrame; }
/**
* @return the amount by which mMovingFrame was moved.
* Only valid when GetRootMovingFrame() returns non-null.
*/
const nsPoint& GetMoveDelta() { return mMoveDelta; }
/**
* @return PR_TRUE if aFrame is, or is a descendant of, the hypothetical
* moving frame
@ -305,6 +321,7 @@ private:
nsIFrame* mReferenceFrame;
nsIFrame* mMovingFrame;
nsIFrame* mIgnoreScrollFrame;
nsPoint mMoveDelta; // only valid when mMovingFrame is non-null
PLArenaPool mPool;
nsCOMPtr<nsISelection> mBoundingSelection;
nsAutoTArray<PresShellState,8> mPresShellStates;
@ -426,11 +443,12 @@ public:
virtual PRBool IsUniform(nsDisplayListBuilder* aBuilder) { return PR_FALSE; }
/**
* @return PR_FALSE if the painting performed by the item is invariant
* when frame aFrame is moved relative to its parent (so it would be safe
* to update the display by just copying pixels from their old to new location)
* when frame aFrame is moved relative to aBuilder->GetRootMovingFrame().
* This can only be called when aBuilder->IsMovingFrame(mFrame) is true.
* It return PR_TRUE for all wrapped lists.
*/
virtual PRBool IsVaryingRelativeToFrame(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame) { return PR_FALSE; }
virtual PRBool IsVaryingRelativeToMovingFrame(nsDisplayListBuilder* aBuilder)
{ return PR_FALSE; }
/**
* Actually paint this item to some rendering context.
* @param aDirtyRect relative to aBuilder->ReferenceFrame()
@ -996,8 +1014,7 @@ public:
virtual nsIFrame* HitTest(nsDisplayListBuilder* aBuilder, nsPoint aPt,
HitTestState* aState) { return mFrame; }
virtual PRBool IsOpaque(nsDisplayListBuilder* aBuilder);
virtual PRBool IsVaryingRelativeToFrame(nsDisplayListBuilder* aBuilder,
nsIFrame* aAncestorFrame);
virtual PRBool IsVaryingRelativeToMovingFrame(nsDisplayListBuilder* aBuilder);
virtual PRBool IsUniform(nsDisplayListBuilder* aBuilder);
virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder);
virtual void Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx,
@ -1080,8 +1097,7 @@ public:
virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder);
virtual PRBool IsOpaque(nsDisplayListBuilder* aBuilder);
virtual PRBool IsUniform(nsDisplayListBuilder* aBuilder);
virtual PRBool IsVaryingRelativeToFrame(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame);
virtual PRBool IsVaryingRelativeToMovingFrame(nsDisplayListBuilder* aBuilder);
virtual void Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx,
const nsRect& aDirtyRect);
virtual PRBool OptimizeVisibility(nsDisplayListBuilder* aBuilder,

View File

@ -177,11 +177,14 @@ PrintDisplayListTo(nsDisplayListBuilder* aBuilder, const nsDisplayList& aList,
default:
break;
}
fprintf(aOutput, "%s %p(%s) (%d,%d,%d,%d)%s%s\n", i->Name(),
fprintf(aOutput, "%s %p(%s) (%d,%d,%d,%d)%s%s%s%s\n", i->Name(),
(void*)f, NS_ConvertUTF16toUTF8(fName).get(),
rect.x, rect.y, rect.width, rect.height,
i->IsOpaque(aBuilder) ? " opaque" : "",
i->IsUniform(aBuilder) ? " uniform" : "");
i->IsUniform(aBuilder) ? " uniform" : "",
f && aBuilder->IsMovingFrame(f) ? " moving" : "",
f && aBuilder->IsMovingFrame(f) && !i->GetList() &&
i->IsVaryingRelativeToMovingFrame(aBuilder) ? " varying" : "");
nsDisplayList* list = i->GetList();
if (list) {
PrintDisplayListTo(aBuilder, *list, aIndent + 4, aOutput);

View File

@ -1025,21 +1025,19 @@ AddItemsToRegion(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
AddItemsToRegion(aBuilder, sublist, aRect, aClipRect, aDelta, aRegion);
}
} else {
// Items left in the list are either IsVaryingRelativeToFrame
// or !IsMovingFrame (i.e., not in the moving subtree)
nsRect r;
if (r.IntersectRect(aClipRect, item->GetBounds(aBuilder))) {
PRBool inMovingSubtree = PR_FALSE;
if (item->IsVaryingRelativeToFrame(aBuilder, aBuilder->GetRootMovingFrame())) {
nsIFrame* f = item->GetUnderlyingFrame();
NS_ASSERTION(f, "Must have an underlying frame for leaf item");
inMovingSubtree = aBuilder->IsMovingFrame(f);
AccumulateItemInRegion(aRegion, aRect + aDelta, r, item);
}
if (!inMovingSubtree) {
// if it's uniform and it includes both the old and new areas, then
// we don't need to paint it
nsIFrame* f = item->GetUnderlyingFrame();
NS_ASSERTION(f, "Must have an underlying frame for leaf item");
if (aBuilder->IsMovingFrame(f)) {
if (item->IsVaryingRelativeToMovingFrame(aBuilder)) {
// something like background-attachment:fixed that varies
// its drawing when it moves
AccumulateItemInRegion(aRegion, aRect + aDelta, r, item);
}
} else {
// not moving. If it's uniform and it includes both the old and
// new areas, then we don't need to paint it
PRBool skip = r.Contains(aRect) && r.Contains(aRect + aDelta) &&
item->IsUniform(aBuilder);
if (!skip) {
@ -1067,15 +1065,21 @@ nsLayoutUtils::ComputeRepaintRegionForCopy(nsIFrame* aRootFrame,
"The root frame shouldn't be the one that's moving, that makes no sense");
// Build the 'after' display list over the whole area of interest.
// Frames under aMovingFrame will not be allowed to affect (clip or cover)
// non-moving frame display items ... then we can be sure the non-moving
// frame display items we get are the same ones we would have gotten if
// we had constructed the 'before' display list.
// (We have to build the 'after' display list because the frame/view
// hierarchy has already been updated for the move.)
// hierarchy has already been updated for the move.
// We need to ensure that the non-moving frame display items we get
// are the same ones we would have gotten if we had constructed the
// 'before' display list. So opaque moving items are only considered to
// cover the intersection of their old and new bounds (see
// nsDisplayItem::OptimizeVisibility). A moving clip item is not allowed
// to clip non-moving items --- this is enforced by the code that sets
// up nsDisplayClip items, in particular see ApplyAbsPosClipping.
// XXX but currently a non-moving clip item can incorrectly clip
// moving moving items! See bug 428156.
nsRect rect;
rect.UnionRect(aCopyRect, aCopyRect + aDelta);
nsDisplayListBuilder builder(aRootFrame, PR_FALSE, PR_TRUE, aMovingFrame);
nsDisplayListBuilder builder(aRootFrame, PR_FALSE, PR_TRUE);
builder.SetMovingFrame(aMovingFrame, aDelta);
nsDisplayList list;
builder.EnterPresShell(aRootFrame, rect);

View File

@ -419,9 +419,17 @@ public:
*
* This function assumes that the caller will do a bitblt copy of aCopyRect
* to aCopyRect+aPt. It computes a region that must be repainted in order
* for the resulting rendering to be correct.
* for the resulting rendering to be correct. Frame geometry must have
* already been adjusted for the scroll/copy operation.
*
* The region consists of:
* Conceptually it works by computing a display list in the before-state
* and a display list in the after-state and analyzing them to find the
* differences. In practice it is only feasible to build a display list
* in the after-state (plus building two display lists would be less
* efficient), so we use some unfortunately tricky techniques to get by
* with just the after-list.
*
* The output region consists of:
* a) any visible background-attachment:fixed areas in the after-move display
* list
* b) any visible areas of the before-move display list corresponding to