Bug 702739. When a element has an active transform and the element's content (before being transformed) is no larger than the window, prerender its entire contents into layers when any of it is visible, so we don't have to rerender it and/or resize its layers as it moves into or out of view. r=mats

This commit is contained in:
Robert O'Callahan 2011-12-18 21:46:44 +13:00
parent 092577b79e
commit 7a9dc0adaf
7 changed files with 138 additions and 62 deletions

View File

@ -2567,6 +2567,15 @@ nsDisplayTransform::GetResultingTransformMatrix(const nsIFrame* aFrame,
(newOrigin + toMozOrigin, result);
}
bool
nsDisplayTransform::ShouldPrerenderTransformedContent(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame)
{
return aFrame->AreLayersMarkedActive(nsChangeHint_UpdateTransformLayer) &&
aFrame->GetVisualOverflowRectRelativeToSelf().Size() <=
aBuilder->ReferenceFrame()->GetSize();
}
/* If the matrix is singular, or a hidden backface is shown, the frame won't be visible or hit. */
static bool IsFrameVisible(nsIFrame* aFrame, const gfx3DMatrix& aMatrix)
{
@ -2644,7 +2653,8 @@ bool nsDisplayTransform::ComputeVisibility(nsDisplayListBuilder *aBuilder,
* think that it's painting in its original rectangular coordinate space.
* If we can't untransform, take the entire overflow rect */
nsRect untransformedVisibleRect;
if (!UntransformRect(mVisibleRect,
if (ShouldPrerenderTransformedContent(aBuilder, mFrame) ||
!UntransformRect(mVisibleRect,
mFrame,
aBuilder->ToReferenceFrame(mFrame),
&untransformedVisibleRect))

View File

@ -2210,6 +2210,12 @@ public:
float aFactor,
const nsRect* aBoundsOverride = nsnull,
nsIFrame** aOutAncestor = nsnull);
/**
* Return true when we should try to prerender the entire contents of the
* transformed frame even when it's not completely visible (yet).
*/
static bool ShouldPrerenderTransformedContent(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame);
private:
nsDisplayWrapList mStoredList;

View File

@ -44,6 +44,7 @@ include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk
_CHROME_FILES = \
paint_listener.js \
test_bug370436.html \
test_bug396367-1.html \
test_bug396367-2.html \
@ -62,10 +63,11 @@ _CHROME_FILES = \
chrome_over_plugin_window.xul \
test_default_background.xul \
default_background_window.xul \
test_leaf_layers_partition_browser_window.xul \
test_no_clip_iframe.xul \
no_clip_iframe_window.xul \
no_clip_iframe_subdoc.html \
test_leaf_layers_partition_browser_window.xul \
test_no_clip_iframe.xul \
no_clip_iframe_window.xul \
no_clip_iframe_subdoc.html \
test_prerendered_transforms.html \
test_printpreview.xul \
printpreview_helper.xul \
test_printpreview_bug396024.xul \

View File

@ -8,6 +8,7 @@
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript"
src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
<script type="text/javascript" src="paint_listener.js"></script>
<div id="container" xmlns="http://www.w3.org/1999/xhtml" style="height:400px; overflow:auto; background:gray">
<div style="height:0">
@ -33,50 +34,6 @@
SimpleTest.waitForExplicitFinish();
var accumulatedRect = null;
var onpaint = function() {};
function paintListener(event) {
if (event.target != window)
return;
dump("got MozAfterPaint: " + event.boundingClientRect.left + "," + event.boundingClientRect.top + "," +
event.boundingClientRect.right + "," + event.boundingClientRect.bottom + "\n");
if (accumulatedRect) {
accumulatedRect[0] = Math.min(accumulatedRect[0], event.boundingClientRect.left);
accumulatedRect[1] = Math.min(accumulatedRect[1], event.boundingClientRect.top);
accumulatedRect[2] = Math.max(accumulatedRect[2], event.boundingClientRect.right);
accumulatedRect[3] = Math.max(accumulatedRect[3], event.boundingClientRect.bottom);
} else {
accumulatedRect = [event.boundingClientRect.left, event.boundingClientRect.top,
event.boundingClientRect.right, event.boundingClientRect.bottom];
}
onpaint();
}
window.addEventListener("MozAfterPaint", paintListener, false);
function waitForAllPaintsFlushed(callback, subdoc) {
document.documentElement.getBoundingClientRect();
if (subdoc) {
subdoc.documentElement.getBoundingClientRect();
}
var CI = Components.interfaces;
var utils = window.QueryInterface(CI.nsIInterfaceRequestor)
.getInterface(CI.nsIDOMWindowUtils);
if (!utils.isMozAfterPaintPending) {
dump("done...\n");
var result = accumulatedRect;
accumulatedRect = null;
onpaint = function() {};
if (!result) {
result = [0,0,0,0];
}
callback(result[0], result[1], result[2], result[3]);
return;
}
dump("waiting for paint...\n");
onpaint = function() { waitForAllPaintsFlushed(callback); };
}
var Ci = Components.interfaces;
var frame = document.getElementById("f");
var fl = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
@ -118,7 +75,6 @@
ref.style.visibility = "hidden";
document.getElementById("container").style.height = "400px";
waitForAllPaintsFlushed(function() {
dump("Scrolling\n");
frame.contentWindow.scrollTo(0,80);
waitForAllPaintsFlushed(function(x1, y1, x2, y2) {
ok(x1 <= 1 && x2 >= 151 && y1 <= 0 && y2 >= 400,

View File

@ -0,0 +1,50 @@
var accumulatedRect = null;
var onpaint = function() {};
var debug = false;
function paintListener(event) {
if (event.target != window)
return;
if (debug) {
dump("got MozAfterPaint: " + event.boundingClientRect.left + "," + event.boundingClientRect.top + "," +
event.boundingClientRect.right + "," + event.boundingClientRect.bottom + "\n");
}
if (accumulatedRect) {
accumulatedRect[0] = Math.min(accumulatedRect[0], event.boundingClientRect.left);
accumulatedRect[1] = Math.min(accumulatedRect[1], event.boundingClientRect.top);
accumulatedRect[2] = Math.max(accumulatedRect[2], event.boundingClientRect.right);
accumulatedRect[3] = Math.max(accumulatedRect[3], event.boundingClientRect.bottom);
} else {
accumulatedRect = [event.boundingClientRect.left, event.boundingClientRect.top,
event.boundingClientRect.right, event.boundingClientRect.bottom];
}
onpaint();
}
window.addEventListener("MozAfterPaint", paintListener, false);
function waitForAllPaintsFlushed(callback, subdoc) {
document.documentElement.getBoundingClientRect();
if (subdoc) {
subdoc.documentElement.getBoundingClientRect();
}
var CI = Components.interfaces;
var utils = window.QueryInterface(CI.nsIInterfaceRequestor)
.getInterface(CI.nsIDOMWindowUtils);
if (!utils.isMozAfterPaintPending) {
if (debug) {
dump("done...\n");
}
var result = accumulatedRect;
accumulatedRect = null;
onpaint = function() {};
if (!result) {
result = [0,0,0,0];
}
callback(result[0], result[1], result[2], result[3]);
return;
}
if (debug) {
dump("waiting for paint...\n");
}
onpaint = function() { waitForAllPaintsFlushed(callback, subdoc); };
}

View File

@ -0,0 +1,47 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test that active transformed elements coming into view are prerendered so we don't have to redraw constantly</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="paint_listener.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body onload="startTest()">
<div>
<div id="t" style="position:absolute; left:0; top:500px; -moz-transform: translatex(-100px); width:200px; height:100px; background:yellow;">
<div style="text-align:right">Hello</div>
<div style="text-align:left">Kitty</div>
</div>
</div>
<pre id="test">
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
var t = document.getElementById("t");
var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
function startTest() {
// Do a couple of transform changes to ensure we've triggered activity heuristics
waitForAllPaintsFlushed(function () {
t.style.MozTransform = "translatex(-75px)";
waitForAllPaintsFlushed(function () {
t.style.MozTransform = "translatex(-50px)";
waitForAllPaintsFlushed(function () {
// Clear paint state now and move again.
utils.checkAndClearPaintedState(t);
// Don't move to 0 since that might trigger some special case that turns off transforms.
t.style.MozTransform = "translatex(-1px)";
waitForAllPaintsFlushed(function () {
var painted = utils.checkAndClearPaintedState(t);
is(painted, false, "Transformed element should not have been painted");
SimpleTest.finish();
});
});
});
});
}
</script>
</pre>
</body>
</html>

View File

@ -1680,19 +1680,24 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder,
bool inTransform = aBuilder->IsInTransform();
if ((mState & NS_FRAME_MAY_BE_TRANSFORMED) &&
disp->HasTransform()) {
// Transform dirtyRect into our frame's local coordinate space. Note that
// the new value is the bounds of the old value's transformed vertices, so
// the area covered by dirtyRect may increase here.
//
// Although we don't bother to check for and maintain the 1x1 size of the
// magic rect indicating a hit test point, in reality this is extremely
// unlikely to matter. The rect starts off with dimensions of 1x1 *app*
// units, and it would require a very large number of elements with
// transforms along a parent chain to noticably expand this by an entire
// device pixel.
if (Preserves3DChildren() || !nsDisplayTransform::UntransformRect(dirtyRect, this, nsPoint(0, 0), &dirtyRect)) {
// we have a singular transform - just grab the entire overflow rect
if (nsDisplayTransform::ShouldPrerenderTransformedContent(aBuilder, this) ||
Preserves3DChildren()) {
dirtyRect = GetVisualOverflowRectRelativeToSelf();
} else {
// Transform dirtyRect into our frame's local coordinate space. Note that
// the new value is the bounds of the old value's transformed vertices, so
// the area covered by dirtyRect may increase here.
//
// Although we don't bother to check for and maintain the 1x1 size of the
// magic rect indicating a hit test point, in reality this is extremely
// unlikely to matter. The rect starts off with dimensions of 1x1 *app*
// units, and it would require a very large number of elements with
// transforms along a parent chain to noticably expand this by an entire
// device pixel.
if (!nsDisplayTransform::UntransformRect(dirtyRect, this, nsPoint(0, 0), &dirtyRect)) {
// we have a singular transform - just grab the entire overflow rect
dirtyRect = GetVisualOverflowRectRelativeToSelf();
}
}
inTransform = true;
}