Bug 640048 Fix edge case for z-ordering for async scrollable elements r=roc

This commit is contained in:
Benjamin Stover 2011-05-01 18:53:01 -07:00
parent 16c85e6947
commit 1b6808fa69
4 changed files with 278 additions and 75 deletions

View File

@ -189,7 +189,7 @@ static void UnmarkFrameForDisplay(nsIFrame* aFrame) {
}
static void RecordFrameMetrics(nsIFrame* aForFrame,
nsIFrame* aViewportFrame,
nsIFrame* aScrollFrame,
ContainerLayer* aRoot,
nsRect aVisibleRect,
nsRect aViewport,
@ -209,8 +209,8 @@ static void RecordFrameMetrics(nsIFrame* aForFrame,
}
nsIScrollableFrame* scrollableFrame = nsnull;
if (aViewportFrame)
scrollableFrame = aViewportFrame->GetScrollTargetFrame();
if (aScrollFrame)
scrollableFrame = aScrollFrame->GetScrollTargetFrame();
if (scrollableFrame) {
nsSize contentSize =
@ -464,6 +464,16 @@ nsDisplayList::ComputeVisibilityForSublist(nsDisplayListBuilder* aBuilder,
continue;
}
nsDisplayList* list = item->GetList();
if (list && item->ShouldFlattenAway(aBuilder)) {
// The elements on the list >= i no longer serve any use.
elements.SetLength(i);
list->FlattenTo(&elements);
i = elements.Length();
item->~nsDisplayItem();
continue;
}
nsRect bounds = item->GetBounds(aBuilder);
nsRegion itemVisible;
@ -1496,6 +1506,11 @@ nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
mList.AppendToTop(aItem);
}
nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame)
: nsDisplayItem(aBuilder, aFrame) {
}
nsDisplayWrapList::~nsDisplayWrapList() {
mList.DeleteAll();
}
@ -1761,18 +1776,60 @@ nsDisplayOwnLayer::BuildLayer(nsDisplayListBuilder* aBuilder,
nsDisplayScrollLayer::nsDisplayScrollLayer(nsDisplayListBuilder* aBuilder,
nsDisplayList* aList,
nsIFrame* aForFrame,
nsIFrame* aViewportFrame)
: nsDisplayOwnLayer(aBuilder, aForFrame, aList)
, mViewportFrame(aViewportFrame)
nsIFrame* aScrolledFrame,
nsIFrame* aScrollFrame)
: nsDisplayWrapList(aBuilder, aForFrame, aList)
, mScrollFrame(aScrollFrame)
, mScrolledFrame(aScrolledFrame)
{
#ifdef NS_BUILD_REFCNT_LOGGING
MOZ_COUNT_CTOR(nsDisplayScrollLayer);
#endif
NS_ASSERTION(mFrame && mFrame->GetContent(),
NS_ASSERTION(mScrolledFrame && mScrolledFrame->GetContent(),
"Need a child frame with content");
}
nsDisplayScrollLayer::nsDisplayScrollLayer(nsDisplayListBuilder* aBuilder,
nsDisplayItem* aItem,
nsIFrame* aForFrame,
nsIFrame* aScrolledFrame,
nsIFrame* aScrollFrame)
: nsDisplayWrapList(aBuilder, aForFrame, aItem)
, mScrollFrame(aScrollFrame)
, mScrolledFrame(aScrolledFrame)
{
#ifdef NS_BUILD_REFCNT_LOGGING
MOZ_COUNT_CTOR(nsDisplayScrollLayer);
#endif
NS_ASSERTION(mScrolledFrame && mScrolledFrame->GetContent(),
"Need a child frame with content");
}
nsDisplayScrollLayer::nsDisplayScrollLayer(nsDisplayListBuilder* aBuilder,
nsIFrame* aForFrame,
nsIFrame* aScrolledFrame,
nsIFrame* aScrollFrame)
: nsDisplayWrapList(aBuilder, aForFrame)
, mScrollFrame(aScrollFrame)
, mScrolledFrame(aScrolledFrame)
{
#ifdef NS_BUILD_REFCNT_LOGGING
MOZ_COUNT_CTOR(nsDisplayScrollLayer);
#endif
NS_ASSERTION(mScrolledFrame && mScrolledFrame->GetContent(),
"Need a child frame with content");
}
#ifdef NS_BUILD_REFCNT_LOGGING
nsDisplayScrollLayer::~nsDisplayScrollLayer()
{
MOZ_COUNT_DTOR(nsDisplayScrollLayer);
}
#endif
already_AddRefed<Layer>
nsDisplayScrollLayer::BuildLayer(nsDisplayListBuilder* aBuilder,
LayerManager* aManager) {
@ -1781,19 +1838,19 @@ nsDisplayScrollLayer::BuildLayer(nsDisplayListBuilder* aBuilder,
// Get the already set unique ID for scrolling this content remotely.
// Or, if not set, generate a new ID.
nsIContent* content = mFrame->GetContent();
nsIContent* content = mScrolledFrame->GetContent();
ViewID scrollId = nsLayoutUtils::FindIDFor(content);
nsRect viewport = mViewportFrame->GetRect() -
mViewportFrame->GetPosition() +
aBuilder->ToReferenceFrame(mViewportFrame);
nsRect viewport = mScrollFrame->GetRect() -
mScrollFrame->GetPosition() +
aBuilder->ToReferenceFrame(mScrollFrame);
bool usingDisplayport = false;
nsRect displayport;
if (content) {
usingDisplayport = nsLayoutUtils::GetDisplayPort(content, &displayport);
}
RecordFrameMetrics(mFrame, mViewportFrame, layer, mVisibleRect, viewport,
RecordFrameMetrics(mScrolledFrame, mScrollFrame, layer, mVisibleRect, viewport,
(usingDisplayport ? &displayport : nsnull), scrollId);
return layer.forget();
@ -1806,12 +1863,12 @@ nsDisplayScrollLayer::ComputeVisibility(nsDisplayListBuilder* aBuilder,
PRBool& aContainsRootContentDocBG)
{
nsRect displayport;
if (nsLayoutUtils::GetDisplayPort(mFrame->GetContent(), &displayport)) {
if (nsLayoutUtils::GetDisplayPort(mScrolledFrame->GetContent(), &displayport)) {
// The visible region for the children may be much bigger than the hole we
// are viewing the children from, so that the compositor process has enough
// content to asynchronously pan while content is being refreshed.
nsRegion childVisibleRegion = displayport + aBuilder->ToReferenceFrame(mViewportFrame);
nsRegion childVisibleRegion = displayport + aBuilder->ToReferenceFrame(mScrollFrame);
nsRect boundedRect;
boundedRect.IntersectRect(childVisibleRegion.GetBounds(), mList.GetBounds(aBuilder));
@ -1824,25 +1881,82 @@ nsDisplayScrollLayer::ComputeVisibility(nsDisplayListBuilder* aBuilder,
return visible;
} else {
return nsDisplayOwnLayer::ComputeVisibility(aBuilder, aVisibleRegion,
return nsDisplayWrapList::ComputeVisibility(aBuilder, aVisibleRegion,
aAllowVisibleRegionExpansion,
aContainsRootContentDocBG);
}
}
#ifdef NS_BUILD_REFCNT_LOGGING
nsDisplayScrollLayer::~nsDisplayScrollLayer()
LayerState
nsDisplayScrollLayer::GetLayerState(nsDisplayListBuilder* aBuilder,
LayerManager* aManager)
{
MOZ_COUNT_DTOR(nsDisplayScrollLayer);
// Force this as a layer so we can scroll asynchronously.
// This causes incorrect rendering for rounded clips!
return LAYER_ACTIVE_FORCE;
}
PRBool
nsDisplayScrollLayer::TryMerge(nsDisplayListBuilder* aBuilder,
nsDisplayItem* aItem)
{
if (aItem->GetType() != TYPE_SCROLL_LAYER) {
return PR_FALSE;
}
nsDisplayScrollLayer* other = static_cast<nsDisplayScrollLayer*>(aItem);
if (other->mScrolledFrame != this->mScrolledFrame) {
return PR_FALSE;
}
FrameProperties props = mScrolledFrame->Properties();
props.Set(nsIFrame::ScrollLayerCount(),
reinterpret_cast<void*>(GetScrollLayerCount() - 1));
mList.AppendToBottom(&other->mList);
return PR_TRUE;
}
PRBool
nsDisplayScrollLayer::ShouldFlattenAway(nsDisplayListBuilder* aBuilder)
{
return GetScrollLayerCount() > 1;
}
PRWord
nsDisplayScrollLayer::GetScrollLayerCount()
{
FrameProperties props = mScrolledFrame->Properties();
#ifdef DEBUG
PRBool hasCount = PR_FALSE;
PRWord result = reinterpret_cast<PRWord>(
props.Get(nsIFrame::ScrollLayerCount(), &hasCount));
// If this aborts, then the property was either not added before scroll
// layers were created or the property was deleted to early. If the latter,
// make sure that nsDisplayScrollInfoLayer is on the bottom of the list so
// that it is processed last.
NS_ABORT_IF_FALSE(hasCount, "nsDisplayScrollLayer should always be defined");
return result;
#else
return reinterpret_cast<PRWord>(props.Get(nsIFrame::ScrollLayerCount()));
#endif
}
PRWord
nsDisplayScrollLayer::RemoveScrollLayerCount()
{
PRWord result = GetScrollLayerCount();
FrameProperties props = mScrolledFrame->Properties();
props.Remove(nsIFrame::ScrollLayerCount());
return result;
}
nsDisplayScrollInfoLayer::nsDisplayScrollInfoLayer(
nsDisplayListBuilder* aBuilder,
nsDisplayList* aList,
nsIFrame* aForFrame,
nsIFrame* aViewportFrame)
: nsDisplayScrollLayer(aBuilder, aList, aForFrame, aViewportFrame)
nsIFrame* aScrolledFrame,
nsIFrame* aScrollFrame)
: nsDisplayScrollLayer(aBuilder, aScrolledFrame, aScrolledFrame, aScrollFrame)
{
#ifdef NS_BUILD_REFCNT_LOGGING
MOZ_COUNT_CTOR(nsDisplayScrollInfoLayer);
@ -1856,6 +1970,30 @@ nsDisplayScrollInfoLayer::~nsDisplayScrollInfoLayer()
}
#endif
LayerState
nsDisplayScrollInfoLayer::GetLayerState(nsDisplayListBuilder* aBuilder,
LayerManager* aManager)
{
return LAYER_ACTIVE_EMPTY;
}
PRBool
nsDisplayScrollInfoLayer::TryMerge(nsDisplayListBuilder* aBuilder,
nsDisplayItem* aItem)
{
return PR_FALSE;
}
PRBool
nsDisplayScrollInfoLayer::ShouldFlattenAway(nsDisplayListBuilder* aBuilder)
{
// Layer metadata for a particular scroll frame needs to be unique. Only
// one nsDisplayScrollLayer (with rendered content) or one
// nsDisplayScrollInfoLayer (with only the metadata) should survive the
// visibility computation.
return RemoveScrollLayerCount() == 1;
}
nsDisplayClip::nsDisplayClip(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame, nsDisplayItem* aItem,
const nsRect& aRect)

View File

@ -750,7 +750,17 @@ public:
virtual PRBool TryMerge(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) {
return PR_FALSE;
}
/**
* During the visibility computation and after TryMerge, display lists may
* return PR_TRUE here to flatten themselves away, removing them. This
* flattening is distinctly different from FlattenTo, which occurs before
* items are merged together.
*/
virtual PRBool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) {
return PR_FALSE;
}
/**
* If this is a leaf item we return null, otherwise we return the wrapped
* list.
@ -1656,6 +1666,7 @@ public:
nsDisplayList* aList);
nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
nsDisplayItem* aItem);
nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
virtual ~nsDisplayWrapList();
virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames);
@ -1788,9 +1799,9 @@ public:
};
/**
* This creates a layer for the given list of items, whose visibility is
* determined by the displayport for the given frame instead of what is
* passed in to ComputeVisibility.
* This potentially creates a layer for the given list of items, whose
* visibility is determined by the displayport for the given frame instead of
* what is passed in to ComputeVisibility.
*
* Here in content, we can use this to render more content than is actually
* visible. Then, the compositing process can manipulate the generated layer
@ -1799,17 +1810,34 @@ public:
* Note that setting the displayport will not change any hit testing! The
* content process will know nothing about what the user is actually seeing,
* so it can only do hit testing for what is supposed to be the visible region.
*
* It is possible for scroll boxes to have content that can be both above and
* below content outside of the scroll box. We cannot create layers for these
* cases. This is accomplished by wrapping display items with
* nsDisplayScrollLayers. nsDisplayScrollLayers with the same scroll frame will
* be merged together. If more than one nsDisplayScrollLayer exists after
* merging, all nsDisplayScrollLayers will be flattened out so that no new
* layer is created at all.
*/
class nsDisplayScrollLayer : public nsDisplayOwnLayer
class nsDisplayScrollLayer : public nsDisplayWrapList
{
public:
/**
* @param aForFrame This will determine what the displayport is. It should be
* the root content frame of the scrolled area.
* @param aViewportFrame The viewport frame you see this content through.
* @param aScrolledFrame This will determine what the displayport is. It should be
* the root content frame of the scrolled area. Note
* that nsDisplayScrollLayer will expect for
* ScrollLayerCount to be defined on aScrolledFrame.
* @param aScrollFrame The viewport frame you see this content through.
*/
nsDisplayScrollLayer(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
nsIFrame* aForFrame, nsIFrame* aViewportFrame);
nsIFrame* aForFrame, nsIFrame* aScrolledFrame,
nsIFrame* aScrollFrame);
nsDisplayScrollLayer(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem,
nsIFrame* aForFrame, nsIFrame* aScrolledFrame,
nsIFrame* aScrollFrame);
nsDisplayScrollLayer(nsDisplayListBuilder* aBuilder,
nsIFrame* aForFrame, nsIFrame* aScrolledFrame,
nsIFrame* aScrollFrame);
NS_DISPLAY_DECL_NAME("ScrollLayer", TYPE_SCROLL_LAYER)
#ifdef NS_BUILD_REFCNT_LOGGING
@ -1825,26 +1853,40 @@ public:
PRBool& aContainsRootContentDocBG);
virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
LayerManager* aManager)
{
// Force this as a layer so we can scroll asynchronously.
// This causes incorrect rendering for rounded clips!
return mozilla::LAYER_ACTIVE_FORCE;
}
LayerManager* aManager);
virtual PRBool TryMerge(nsDisplayListBuilder* aBuilder,
nsDisplayItem* aItem);
virtual PRBool ShouldFlattenAway(nsDisplayListBuilder* aBuilder);
// Get the number of nsDisplayScrollLayers for a scroll frame. Note that this
// number does not include nsDisplayScrollInfoLayers. If this number is not 1
// after merging, all the nsDisplayScrollLayers should flatten away.
PRWord GetScrollLayerCount();
PRWord RemoveScrollLayerCount();
private:
nsIFrame* mViewportFrame;
nsIFrame* mScrollFrame;
nsIFrame* mScrolledFrame;
};
/**
* Like nsDisplayScrollLayer, but only has metadata on the scroll frame. This
* creates a layer that has no Thebes child layer, but still allows the
* compositor process to know of the scroll frame's existence.
*
* After visibility computation, nsDisplayScrollInfoLayers should only exist if
* nsDisplayScrollLayers were all flattened away.
*
* Important!! Add info layers to the bottom of the list so they are only
* considered after the others have flattened out!
*/
class nsDisplayScrollInfoLayer : public nsDisplayScrollLayer
{
public:
nsDisplayScrollInfoLayer(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
nsIFrame* aForFrame, nsIFrame* aViewportFrame);
nsDisplayScrollInfoLayer(nsDisplayListBuilder* aBuilder,
nsIFrame* aScrolledFrame, nsIFrame* aScrollFrame);
NS_DISPLAY_DECL_NAME("ScrollInfoLayer", TYPE_SCROLL_INFO_LAYER)
#ifdef NS_BUILD_REFCNT_LOGGING
@ -1852,11 +1894,12 @@ public:
#endif
virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
LayerManager* aManager)
{
return mozilla::LAYER_ACTIVE_EMPTY;
}
LayerManager* aManager);
virtual PRBool TryMerge(nsDisplayListBuilder* aBuilder,
nsDisplayItem* aItem);
virtual PRBool ShouldFlattenAway(nsDisplayListBuilder* aBuilder);
};
/**

View File

@ -1905,6 +1905,43 @@ nsGfxScrollFrameInner::ShouldBuildLayer() const
return mShouldBuildLayer;
}
class ScrollLayerWrapper : public nsDisplayWrapper
{
public:
ScrollLayerWrapper(nsIFrame* aScrollFrame, nsIFrame* aScrolledFrame)
: mCount(0)
, mProps(aScrolledFrame->Properties())
, mScrollFrame(aScrollFrame)
, mScrolledFrame(aScrolledFrame)
{
SetCount(0);
}
virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame,
nsDisplayList* aList) {
SetCount(++mCount);
return new (aBuilder) nsDisplayScrollLayer(aBuilder, aList, nsnull, mScrolledFrame, mScrollFrame);
}
virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
nsDisplayItem* aItem) {
SetCount(++mCount);
return new (aBuilder) nsDisplayScrollLayer(aBuilder, aItem, aItem->GetUnderlyingFrame(), mScrolledFrame, mScrollFrame);
}
protected:
void SetCount(PRWord aCount) {
mProps.Set(nsIFrame::ScrollLayerCount(), reinterpret_cast<void*>(aCount));
}
PRWord mCount;
FrameProperties mProps;
nsIFrame* mScrollFrame;
nsIFrame* mScrolledFrame;
};
nsresult
nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsRect& aDirtyRect,
@ -1965,16 +2002,11 @@ nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &dirtyRect);
nsDisplayListCollection set;
rv = mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, set);
NS_ENSURE_SUCCESS(rv, rv);
// Since making new layers is expensive, only use nsDisplayScrollLayer
// if the area is scrollable.
//
// Scroll frames can be generated with a scroll range that is 0, 0.
// Furthermore, it is not worth the memory tradeoff to allow asynchronous
// scrolling of small scroll frames. We use an arbitrary minimum scroll
// range of 20 pixels to eliminate many gfx scroll frames from becoming a
// layer.
//
nsRect scrollRange = GetScrollRange();
ScrollbarStyles styles = GetScrollbarStylesFromFrame();
mShouldBuildLayer =
@ -1986,37 +2018,25 @@ nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
(!mIsRoot || !mOuter->PresContext()->IsRootContentDocument()));
if (ShouldBuildLayer()) {
nsDisplayList list;
// ScrollLayerWrapper must always be created because it initializes the
// scroll layer count. The display lists depend on this.
ScrollLayerWrapper wrapper(mOuter, mScrolledFrame);
if (usingDisplayport) {
// Once a displayport is set, assume that scrolling needs to be fast
// so create a layer with all the content inside. The compositor
// process will be able to scroll the content asynchronously.
//
// Note that using StackingContext breaks z order, so the resulting
// rendering can be incorrect for weird edge cases!
rv = mScrolledFrame->BuildDisplayListForStackingContext(
aBuilder, dirtyRect + mOuter->GetOffsetTo(mScrolledFrame), &list);
nsDisplayScrollLayer* layerItem = new (aBuilder) nsDisplayScrollLayer(
aBuilder, &list, mScrolledFrame, mOuter);
set.Content()->AppendNewToTop(layerItem);
} else {
// If there is no displayport set, there is no reason here to force a
// layer that needs a memory-expensive allocation, but the compositor
// process would still like to know that it exists.
nsDisplayScrollLayer* layerItem = new (aBuilder) nsDisplayScrollInfoLayer(
aBuilder, &list, mScrolledFrame, mOuter);
set.Content()->AppendNewToTop(layerItem);
rv = mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, set);
wrapper.WrapListsInPlace(aBuilder, mOuter, set);
}
} else {
rv = mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, set);
// In case we are not using displayport or the nsDisplayScrollLayers are
// flattened during visibility computation, we still need to export the
// metadata about this scroll box to the compositor process.
nsDisplayScrollInfoLayer* layerItem = new (aBuilder) nsDisplayScrollInfoLayer(
aBuilder, mScrolledFrame, mOuter);
set.Content()->AppendNewToBottom(layerItem);
}
NS_ENSURE_SUCCESS(rv, rv);
nsRect clip;
clip = mScrollPort + aBuilder->ToReferenceFrame(mOuter);

View File

@ -894,6 +894,8 @@ public:
NS_DECLARE_FRAME_PROPERTY(UsedPaddingProperty, DestroyMargin)
NS_DECLARE_FRAME_PROPERTY(UsedBorderProperty, DestroyMargin)
NS_DECLARE_FRAME_PROPERTY(ScrollLayerCount, nsnull)
/**
* Return the distance between the border edge of the frame and the
* margin edge of the frame. Like GetRect(), returns the dimensions