зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1164227 - Don't allow invalid region simplification to invalidate unchanged scrolled contents. r=roc
--HG-- extra : amend_source : 54ccbcac64986195c060e3fddd835dd5c01b66eb
This commit is contained in:
Родитель
7524d2bb65
Коммит
bd011ed848
|
@ -1295,6 +1295,16 @@ public:
|
|||
nsPoint mLastAnimatedGeometryRootOrigin;
|
||||
nsPoint mAnimatedGeometryRootOrigin;
|
||||
|
||||
// If mIgnoreInvalidationsOutsideRect is set, this contains the bounds of the
|
||||
// layer's old visible region, in layer pixels.
|
||||
nsIntRect mOldVisibleBounds;
|
||||
|
||||
// If set, invalidations that fall outside of this rect should not result in
|
||||
// calls to layer->InvalidateRegion during DLBI. Instead, the parts outside
|
||||
// this rectangle will be invalidated in InvalidateVisibleBoundsChangesForScrolledLayer.
|
||||
// See the comment in ComputeAndSetIgnoreInvalidationRect for more information.
|
||||
Maybe<nsIntRect> mIgnoreInvalidationsOutsideRect;
|
||||
|
||||
nsRefPtr<ColorLayer> mColorLayer;
|
||||
nsRefPtr<ImageLayer> mImageLayer;
|
||||
};
|
||||
|
@ -1471,23 +1481,29 @@ AppendToString(nsACString& s, const nsIntRegion& r,
|
|||
* *after* aTranslation has been applied, so we need to
|
||||
* apply the inverse of that transform before calling InvalidateRegion.
|
||||
*/
|
||||
static void
|
||||
InvalidatePostTransformRegion(PaintedLayer* aLayer, const nsIntRegion& aRegion,
|
||||
const nsIntPoint& aTranslation)
|
||||
template<typename RegionOrRect> void
|
||||
InvalidatePostTransformRegion(PaintedLayer* aLayer, const RegionOrRect& aRegion,
|
||||
const nsIntPoint& aTranslation,
|
||||
PaintedDisplayItemLayerUserData* aData)
|
||||
{
|
||||
// Convert the region from the coordinates of the container layer
|
||||
// (relative to the snapped top-left of the display list reference frame)
|
||||
// to the PaintedLayer's own coordinates
|
||||
nsIntRegion rgn = aRegion;
|
||||
RegionOrRect rgn = aRegion;
|
||||
rgn.MoveBy(-aTranslation);
|
||||
aLayer->InvalidateRegion(rgn);
|
||||
#ifdef MOZ_DUMP_PAINTING
|
||||
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
|
||||
nsAutoCString str;
|
||||
AppendToString(str, rgn);
|
||||
printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get());
|
||||
if (aData->mIgnoreInvalidationsOutsideRect) {
|
||||
rgn = rgn.Intersect(*aData->mIgnoreInvalidationsOutsideRect);
|
||||
}
|
||||
if (!rgn.IsEmpty()) {
|
||||
aLayer->InvalidateRegion(rgn);
|
||||
#ifdef MOZ_DUMP_PAINTING
|
||||
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
|
||||
nsAutoCString str;
|
||||
AppendToString(str, rgn);
|
||||
printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -1501,7 +1517,7 @@ InvalidatePostTransformRegion(PaintedLayer* aLayer, const nsRect& aRect,
|
|||
nsRect rect = aClip.ApplyNonRoundedIntersection(aRect);
|
||||
|
||||
nsIntRect pixelRect = rect.ScaleToOutsidePixels(data->mXScale, data->mYScale, data->mAppUnitsPerDevPixel);
|
||||
InvalidatePostTransformRegion(aLayer, pixelRect, aTranslation);
|
||||
InvalidatePostTransformRegion(aLayer, pixelRect, aTranslation, data);
|
||||
}
|
||||
|
||||
|
||||
|
@ -2106,6 +2122,89 @@ ContainerState::RecyclePaintedLayer(PaintedLayer* aLayer,
|
|||
return data;
|
||||
}
|
||||
|
||||
static void
|
||||
ComputeAndSetIgnoreInvalidationRect(PaintedLayer* aLayer,
|
||||
PaintedDisplayItemLayerUserData* aData,
|
||||
const nsIFrame* aAnimatedGeometryRoot,
|
||||
nsDisplayListBuilder* aBuilder,
|
||||
const nsIntPoint& aLayerTranslation)
|
||||
{
|
||||
if (!aLayer->Manager()->IsWidgetLayerManager()) {
|
||||
// This optimization is only useful for layers with retained content.
|
||||
return;
|
||||
}
|
||||
|
||||
const nsIFrame* parentFrame = aAnimatedGeometryRoot->GetParent();
|
||||
|
||||
// GetDirtyRectForScrolledContents will return an empty rect if parentFrame
|
||||
// is not a scrollable frame.
|
||||
nsRect dirtyRect = aBuilder->GetDirtyRectForScrolledContents(parentFrame);
|
||||
|
||||
if (dirtyRect.IsEmpty()) {
|
||||
// parentFrame is not a scrollable frame, or we didn't encounter it during
|
||||
// display list building (though this shouldn't happen), or it's empty.
|
||||
// In all those cases this optimization is not needed.
|
||||
return;
|
||||
}
|
||||
|
||||
// parentFrame is a scrollable frame, and aLayer contains the scrolled
|
||||
// contents of that frame.
|
||||
|
||||
// maxNewVisibleBounds is a conservative approximation of the new visible
|
||||
// region of aLayer.
|
||||
nsIntRect maxNewVisibleBounds =
|
||||
dirtyRect.ScaleToOutsidePixels(aData->mXScale, aData->mYScale,
|
||||
aData->mAppUnitsPerDevPixel) - aLayerTranslation;
|
||||
aData->mOldVisibleBounds = aLayer->GetVisibleRegion().GetBounds();
|
||||
|
||||
// When the visible region of aLayer changes (e.g. due to scrolling),
|
||||
// three distinct types of invalidations need to be triggered:
|
||||
// (1) Items (or parts of items) that have left the visible region need
|
||||
// to be invalidated so that the pixels they painted are no longer
|
||||
// part of the layer's valid region.
|
||||
// (2) Items (or parts of items) that weren't in the old visible region
|
||||
// but are in the new visible region need to be invalidated. This
|
||||
// invalidation isn't required for painting the right layer
|
||||
// contents, because these items weren't part of the layer's valid
|
||||
// region, so they'd be painted anyway. It is, however, necessary in
|
||||
// order to get an accurate invalid region for the layer tree that
|
||||
// aLayer is in, for example for partial compositing.
|
||||
// (3) Any changes that happened in the intersection of the old and the
|
||||
// new visible region need to be invalidated. There shouldn't be any
|
||||
// of these when scrolling static content.
|
||||
//
|
||||
// We'd like to guarantee that we won't invalidate anything in the
|
||||
// intersection area of the old and the new visible region if all
|
||||
// invalidation are of type (1) and (2). However, if we just call
|
||||
// aLayer->InvalidateRegion for the invalidations of type (1) and (2),
|
||||
// at some point we'll hit the complexity limit of the layer's invalid
|
||||
// region. And the resulting region simplification can cause the region
|
||||
// to intersect with the intersection of the old and the new visible
|
||||
// region.
|
||||
// In order to get around this problem, we're using the following approach:
|
||||
// - aData->mIgnoreInvalidationsOutsideRect is set to a conservative
|
||||
// approximation of the intersection of the old and the new visible
|
||||
// region. At this point we don't know the layer's new visible region.
|
||||
// - As long as we don't know the layer's new visible region, we ignore all
|
||||
// invalidations outside that rectangle, so roughly some of the
|
||||
// invalidations of type (1) and (2).
|
||||
// - Once we know the layer's new visible region, which happens at some
|
||||
// point during PostprocessRetainedLayers, we invalidate a conservative
|
||||
// approximation of (1) and (2). Specifically, we invalidate the region
|
||||
// union of the old visible bounds and the new visible bounds, minus
|
||||
// aData->mIgnoreInvalidationsOutsideRect. That region is simple enough
|
||||
// that it will never be simplified on its own.
|
||||
// We unset mIgnoreInvalidationsOutsideRect at this point.
|
||||
// - Any other invalidations that happen on the layer after this point, e.g.
|
||||
// during WillEndTransaction, will just happen regularly. If they are of
|
||||
// type (1) or (2), they won't change the layer's invalid region because
|
||||
// they fall inside the region we invalidated in the previous step.
|
||||
// Consequently, aData->mIgnoreInvalidationsOutsideRect is safe from
|
||||
// invalidations as long as there are no invalidations of type (3).
|
||||
aData->mIgnoreInvalidationsOutsideRect =
|
||||
Some(maxNewVisibleBounds.Intersect(aData->mOldVisibleBounds));
|
||||
}
|
||||
|
||||
void
|
||||
ContainerState::PreparePaintedLayerForUse(PaintedLayer* aLayer,
|
||||
PaintedDisplayItemLayerUserData* aData,
|
||||
|
@ -2139,6 +2238,8 @@ ContainerState::PreparePaintedLayerForUse(PaintedLayer* aLayer,
|
|||
Matrix matrix = Matrix::Translation(pixOffset.x, pixOffset.y);
|
||||
aLayer->SetBaseTransform(Matrix4x4::From2D(matrix));
|
||||
|
||||
ComputeAndSetIgnoreInvalidationRect(aLayer, aData, aAnimatedGeometryRoot, mBuilder, pixOffset);
|
||||
|
||||
// FIXME: Temporary workaround for bug 681192 and bug 724786.
|
||||
#ifndef MOZ_WIDGET_ANDROID
|
||||
// Calculate exact position of the top-left of the active scrolled root.
|
||||
|
@ -3948,7 +4049,8 @@ FrameLayerBuilder::ComputeGeometryChangeForItem(DisplayItemData* aData)
|
|||
}
|
||||
InvalidatePostTransformRegion(paintedLayer,
|
||||
combined.ScaleToOutsidePixels(layerData->mXScale, layerData->mYScale, layerData->mAppUnitsPerDevPixel),
|
||||
layerData->mTranslation);
|
||||
layerData->mTranslation,
|
||||
layerData);
|
||||
}
|
||||
|
||||
aData->EndUpdate(geometry);
|
||||
|
@ -4066,7 +4168,8 @@ FrameLayerBuilder::AddPaintedDisplayItem(PaintedLayerData* aLayerData,
|
|||
}
|
||||
|
||||
InvalidatePostTransformRegion(layer, invalid,
|
||||
GetTranslationForPaintedLayer(layer));
|
||||
GetTranslationForPaintedLayer(layer),
|
||||
paintedData);
|
||||
}
|
||||
}
|
||||
ClippedDisplayItem* cdi =
|
||||
|
@ -4272,6 +4375,39 @@ ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry)
|
|||
aEntry->mLayer->SetFrameMetrics(metricsArray);
|
||||
}
|
||||
|
||||
static void
|
||||
InvalidateVisibleBoundsChangesForScrolledLayer(PaintedLayer* aLayer)
|
||||
{
|
||||
PaintedDisplayItemLayerUserData* data =
|
||||
static_cast<PaintedDisplayItemLayerUserData*>(aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
|
||||
|
||||
if (data->mIgnoreInvalidationsOutsideRect) {
|
||||
// We haven't invalidated anything outside *data->mIgnoreInvalidationsOutsideRect
|
||||
// during DLBI. Now is the right time to do that, because at this point aLayer
|
||||
// knows its new visible region.
|
||||
// We use the visible regions' bounds here (as opposed to the true region)
|
||||
// in order to limit rgn's complexity. The only possible disadvantage of
|
||||
// this is that it might cause us to unnecessarily recomposite parts of the
|
||||
// window that are in the visible region's bounds but not in the visible
|
||||
// region itself, but that is acceptable for scrolled layers.
|
||||
nsIntRegion rgn;
|
||||
rgn.Or(data->mOldVisibleBounds, aLayer->GetVisibleRegion().GetBounds());
|
||||
rgn.Sub(rgn, *data->mIgnoreInvalidationsOutsideRect);
|
||||
if (!rgn.IsEmpty()) {
|
||||
aLayer->InvalidateRegion(rgn);
|
||||
#ifdef MOZ_DUMP_PAINTING
|
||||
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
|
||||
printf_stderr("Invalidating changes of the visible region bounds of the scrolled contents\n");
|
||||
nsAutoCString str;
|
||||
AppendToString(str, rgn);
|
||||
printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
data->mIgnoreInvalidationsOutsideRect = Nothing();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ContainerState::PostprocessRetainedLayers(nsIntRegion* aOpaqueRegionForContainer)
|
||||
{
|
||||
|
@ -4310,6 +4446,11 @@ ContainerState::PostprocessRetainedLayers(nsIntRegion* aOpaqueRegionForContainer
|
|||
SetOuterVisibleRegionForLayer(e->mLayer, e->mVisibleRegion,
|
||||
e->mLayerContentsVisibleRect.width >= 0 ? &e->mLayerContentsVisibleRect : nullptr);
|
||||
|
||||
PaintedLayer* p = e->mLayer->AsPaintedLayer();
|
||||
if (p) {
|
||||
InvalidateVisibleBoundsChangesForScrolledLayer(p);
|
||||
}
|
||||
|
||||
if (!e->mOpaqueRegion.IsEmpty()) {
|
||||
const nsIFrame* animatedGeometryRootToCover = animatedGeometryRootForOpaqueness;
|
||||
if (e->mOpaqueForAnimatedGeometryRootParent &&
|
||||
|
|
|
@ -1269,6 +1269,24 @@ nsDisplayListBuilder::AppendNewScrollInfoItemForHoisting(nsDisplayScrollInfoLaye
|
|||
mScrollInfoItemsForHoisting->AppendNewToTop(aScrollInfoItem);
|
||||
}
|
||||
|
||||
void
|
||||
nsDisplayListBuilder::StoreDirtyRectForScrolledContents(const nsIFrame* aScrollableFrame,
|
||||
const nsRect& aDirty)
|
||||
{
|
||||
mDirtyRectForScrolledContents.Put(const_cast<nsIFrame*>(aScrollableFrame),
|
||||
aDirty + ToReferenceFrame(aScrollableFrame));
|
||||
}
|
||||
|
||||
nsRect
|
||||
nsDisplayListBuilder::GetDirtyRectForScrolledContents(const nsIFrame* aScrollableFrame) const
|
||||
{
|
||||
nsRect result;
|
||||
if (!mDirtyRectForScrolledContents.Get(const_cast<nsIFrame*>(aScrollableFrame), &result)) {
|
||||
return nsRect();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void nsDisplayListSet::MoveTo(const nsDisplayListSet& aDestination) const
|
||||
{
|
||||
aDestination.BorderBackground()->AppendToTop(BorderBackground());
|
||||
|
|
|
@ -854,6 +854,22 @@ public:
|
|||
|
||||
void AppendNewScrollInfoItemForHoisting(nsDisplayScrollInfoLayer* aScrollInfoItem);
|
||||
|
||||
/**
|
||||
* Store the dirty rect of the scrolled contents of aScrollableFrame. This
|
||||
* is a bound for the extents of the new visible region of the scrolled
|
||||
* layer.
|
||||
* @param aScrollableFrame the scrollable frame
|
||||
* @param aDirty the dirty rect, relative to aScrollableFrame
|
||||
*/
|
||||
void StoreDirtyRectForScrolledContents(const nsIFrame* aScrollableFrame, const nsRect& aDirty);
|
||||
|
||||
/**
|
||||
* Retrieve the stored dirty rect for the scrolled contents of aScrollableFrame.
|
||||
* @param aScrollableFrame the scroll frame
|
||||
* @return the dirty rect, relative to aScrollableFrame's *reference frame*
|
||||
*/
|
||||
nsRect GetDirtyRectForScrolledContents(const nsIFrame* aScrollableFrame) const;
|
||||
|
||||
private:
|
||||
void MarkOutOfFlowFrameForDisplay(nsIFrame* aDirtyFrame, nsIFrame* aFrame,
|
||||
const nsRect& aDirtyRect);
|
||||
|
@ -930,6 +946,10 @@ private:
|
|||
mWillChangeBudget;
|
||||
// Assert that we never check the budget before its fully calculated.
|
||||
mutable mozilla::DebugOnly<bool> mWillChangeBudgetCalculated;
|
||||
|
||||
// rects are relative to the frame's reference frame
|
||||
nsDataHashtable<nsPtrHashKey<nsIFrame>, nsRect> mDirtyRectForScrolledContents;
|
||||
|
||||
// Relative to mCurrentFrame.
|
||||
nsRect mDirtyRect;
|
||||
nsRegion mWindowExcludeGlassRegion;
|
||||
|
|
|
@ -2972,6 +2972,7 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
|||
}
|
||||
}
|
||||
|
||||
aBuilder->StoreDirtyRectForScrolledContents(mOuter, dirtyRect);
|
||||
mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, scrolledContent);
|
||||
|
||||
if (idSetter.ShouldForceLayerForScrollParent() &&
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" class="reftest-wait">
|
||||
<meta charset="utf-8">
|
||||
<title>Bug 1164227 - Testcase for the invalid region simplification bug</title>
|
||||
|
||||
<style>
|
||||
|
||||
#scrollbox {
|
||||
width: 400px;
|
||||
height: 500px;
|
||||
overflow: auto;
|
||||
margin: 80px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.contents {
|
||||
height: 600px;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.boxes > div {
|
||||
box-sizing: border-box;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border: 1px solid black;
|
||||
float: left;
|
||||
margin-left: -2px;
|
||||
}
|
||||
|
||||
.boxes > div:nth-child(odd) {
|
||||
transform: translateY(500px);
|
||||
}
|
||||
|
||||
.reftest-no-paint {
|
||||
position: absolute;
|
||||
top: 250px;
|
||||
left: 30px;
|
||||
width: 200px;
|
||||
height: 50px;
|
||||
border: 1px solid red;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="scrollbox">
|
||||
|
||||
<div class="contents">
|
||||
|
||||
<div class="boxes">
|
||||
<div style="margin-top: 0px"></div>
|
||||
<div style="margin-top: 1px"></div>
|
||||
<div style="margin-top: 2px"></div>
|
||||
<div style="margin-top: 3px"></div>
|
||||
<div style="margin-top: 4px"></div>
|
||||
<div style="margin-top: 5px"></div>
|
||||
<div style="margin-top: 6px"></div>
|
||||
<div style="margin-top: 7px"></div>
|
||||
<div style="margin-top: 8px"></div>
|
||||
<div style="margin-top: 9px"></div>
|
||||
<div style="margin-top: 10px"></div>
|
||||
<div style="margin-top: 11px"></div>
|
||||
<div style="margin-top: 12px"></div>
|
||||
<div style="margin-top: 13px"></div>
|
||||
<div style="margin-top: 14px"></div>
|
||||
<div style="margin-top: 15px"></div>
|
||||
<div style="margin-top: 16px"></div>
|
||||
<div style="margin-top: 17px"></div>
|
||||
<div style="margin-top: 18px"></div>
|
||||
<div style="margin-top: 19px"></div>
|
||||
<div style="margin-top: 20px"></div>
|
||||
<div style="margin-top: 21px"></div>
|
||||
<div style="margin-top: 22px"></div>
|
||||
<div style="margin-top: 23px"></div>
|
||||
<div style="margin-top: 24px"></div>
|
||||
<div style="margin-top: 25px"></div>
|
||||
<div style="margin-top: 26px"></div>
|
||||
<div style="margin-top: 27px"></div>
|
||||
<div style="margin-top: 28px"></div>
|
||||
<div style="margin-top: 29px"></div>
|
||||
<div style="margin-top: 30px"></div>
|
||||
<div style="margin-top: 31px"></div>
|
||||
<div style="margin-top: 32px"></div>
|
||||
<div style="margin-top: 33px"></div>
|
||||
<div style="margin-top: 34px"></div>
|
||||
<div style="margin-top: 35px"></div>
|
||||
<div style="margin-top: 36px"></div>
|
||||
<div style="margin-top: 37px"></div>
|
||||
<div style="margin-top: 38px"></div>
|
||||
<div style="margin-top: 39px"></div>
|
||||
</div>
|
||||
|
||||
<div class="reftest-no-paint"></div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
var scrollbox = document.querySelector("#scrollbox");
|
||||
scrollbox.scrollTop = 100;
|
||||
|
||||
window.addEventListener("MozReftestInvalidate", function (e) {
|
||||
scrollbox.scrollTop = 0;
|
||||
document.documentElement.removeAttribute("class");
|
||||
});
|
||||
|
||||
</script>
|
|
@ -66,3 +66,4 @@ pref(layout.animated-image-layers.enabled,true) skip-if(Android||gtk2Widget) ==
|
|||
!= layer-splitting-7.html about:blank
|
||||
fuzzy-if(gtk2Widget,2,4) == image-scrolling-zoom-1.html image-scrolling-zoom-1-ref.html
|
||||
!= image-scrolling-zoom-1-ref.html image-scrolling-zoom-1-notref.html
|
||||
!= fast-scrolling.html about:blank
|
||||
|
|
Загрузка…
Ссылка в новой задаче