Bug 1437036 - Stop the reftest harness from waiting for MozAfterPaint during infinite/superlong animations. r?hiro
These tests rely on an optimization within Gecko where it stops firing
MozAfterPaint events if there was no visible change to the generated
layers. This allows the reftest harness to exit the
waiting-for-MozAfterPaint loop and proceed with the test. However, with
webrender, this optimization does not exist and so the loop never exits.
In order to solve this problem, this patch adds an explicit mechanism to
exit the loop by means of a class attribute on the root element of the
test page.
MozReview-Commit-ID: 17ta5kLPDr9
--- a/layout/reftests/transform-3d/animate-backface-hidden.html
+++ b/layout/reftests/transform-3d/animate-backface-hidden.html
@@ -31,16 +31,19 @@ document.getElementById("test").addEvent
function StartListener(event) {
var test = document.getElementById("test");
test.style.animationPlayState = 'running';
test.addEventListener("animationiteration", IterationListener);
}
function IterationListener(event) {
- setTimeout(RemoveReftestWait, 0);
+ window.addEventListener("MozAfterPaint", () => {
+ requestAnimationFrame(RemoveReftestWait);
+ }, { once: true });
}
function RemoveReftestWait() {
document.documentElement.classList.remove("reftest-wait");
+ document.documentElement.classList.add("reftest-ignore-pending-paints");
}
</script>
--- a/layout/reftests/transform-3d/animate-preserve3d-child.html
+++ b/layout/reftests/transform-3d/animate-preserve3d-child.html
@@ -1,10 +1,10 @@
<!DOCTYPE HTML>
-<html class="reftest-wait">
+<html class="reftest-wait reftest-no-flush">
<title>Testcase, bug 1176969</title>
<style>
body { padding: 50px }
#grandparent { perspective: 400px }
@keyframes spin {
@@ -41,16 +41,19 @@ body { padding: 50px }
</div>
</div>
<script>
document.getElementById("parent").addEventListener("animationiteration", IterationListener);
function IterationListener(event) {
- setTimeout(RemoveReftestWait, 0);
+ window.addEventListener("MozAfterPaint", () => {
+ requestAnimationFrame(RemoveReftestWait);
+ }, { once: true });
}
function RemoveReftestWait() {
document.documentElement.classList.remove("reftest-wait");
+ document.documentElement.classList.add("reftest-ignore-pending-paints");
}
</script>
--- a/layout/reftests/transform-3d/animate-preserve3d-parent.html
+++ b/layout/reftests/transform-3d/animate-preserve3d-parent.html
@@ -1,10 +1,10 @@
<!DOCTYPE HTML>
-<html class="reftest-wait">
+<html class="reftest-wait reftest-no-flush">
<title>Testcase, bug 1176969</title>
<style>
body { padding: 50px }
#grandparent { perspective: 400px }
@keyframes spin {
@@ -43,16 +43,19 @@ document.getElementById("parent").addEve
function StartListener(event) {
var test = document.getElementById("parent");
test.style.animationPlayState = 'running';
test.addEventListener("animationiteration", IterationListener);
}
function IterationListener(event) {
- setTimeout(RemoveReftestWait, 0);
+ window.addEventListener("MozAfterPaint", () => {
+ requestAnimationFrame(RemoveReftestWait);
+ }, { once: true });
}
function RemoveReftestWait() {
document.documentElement.classList.remove("reftest-wait");
+ document.documentElement.classList.add("reftest-ignore-pending-paints");
}
</script>
--- a/layout/reftests/transform-3d/reftest.list
+++ b/layout/reftests/transform-3d/reftest.list
@@ -66,17 +66,17 @@ fails-if(webrender) != 1157984-1.html ab
fuzzy(3,99) == animate-cube-radians.html animate-cube-radians-ref.html # subpixel AA
fuzzy(3,99) fuzzy-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)&&!layersGPUAccelerated,16,6) == animate-cube-radians-zoom.html animate-cube-radians-zoom-ref.html
!= animate-cube-radians-ref.html animate-cube-radians-zoom-ref.html
fuzzy(3,99) == animate-cube-degrees.html animate-cube-degrees-ref.html # subpixel AA
== animate-cube-degrees-zoom.html animate-cube-degrees-zoom-ref.html
!= animate-cube-degrees-ref.html animate-cube-degrees-zoom-ref.html
fuzzy-if(gtkWidget,128,100) fuzzy-if(Android||OSX==1010||(gtkWidget&&layersGPUAccelerated),143,100) fuzzy-if(winWidget||OSX<1010,141,100) == preserves3d-nested.html preserves3d-nested-ref.html
fuzzy-if(cocoaWidget,128,9) == animate-preserve3d-parent.html animate-preserve3d-ref.html # intermittently fuzzy on Mac
-fuzzy-if(cocoaWidget,128,9) == animate-preserve3d-child.html animate-preserve3d-ref.html # intermittently fuzzy on Mac
+fuzzy-if(cocoaWidget,128,9) skip-if(Android) == animate-preserve3d-child.html animate-preserve3d-ref.html # intermittently fuzzy on Mac, bug 1461311 for Android
== animate-backface-hidden.html about:blank
== 1245450-1.html green-rect.html
fuzzy(1,2000) == opacity-preserve3d-1.html opacity-preserve3d-1-ref.html
fuzzy(1,15000) == opacity-preserve3d-2.html opacity-preserve3d-2-ref.html
fuzzy(1,10000) == opacity-preserve3d-3.html opacity-preserve3d-3-ref.html
fuzzy(1,10000) == opacity-preserve3d-4.html opacity-preserve3d-4-ref.html
== opacity-preserve3d-5.html opacity-preserve3d-5-ref.html
== snap-perspective-1.html snap-perspective-1-ref.html
--- a/layout/tools/reftest/README.txt
+++ b/layout/tools/reftest/README.txt
@@ -660,8 +660,25 @@ and they won't cause any error messages
Skip Forcing A Content Process Layer-Tree Update: reftest-no-sync-layers attribute
==================================================================================
Normally when an multi-process reftest test ends, we force the content process
to push a layer-tree update to the compositor before taking the snapshot.
Setting the "reftest-no-sync-layers" attribute on the root element skips this
step, enabling testing that layer-tree updates are being correctly generated.
However the test must manually wait for a MozAfterPaint event before ending.
+
+Avoid hanging on long/infinite animation tests: reftest-ignore-pending-paints
+=============================================================================
+
+If a test contains a long animation, and the desired behaviour is to take a
+snapshot partway through the animation, the usual procedure is to have a part
+of the animation that is visually unchanging, and when the test page reaches that
+part, it removes the reftest-wait to allow the harness to finish. However, this
+relies on an optimization inside Gecko that stops repaints if it detects that
+nothing will visually change (by detecting an empty invalidation area, for
+example). In some cases, this optimization may not trigger (e.g. with WebRender
+enabled). For such cases, the reftest-wait class attribute can be replaced by
+reftest-ignore-pending-paints on the root html element, and this will make the
+harness ignore any pending repaints (i.e. stop listening for MozAfterPaint) and
+just go ahead and finish the test.
+Note that any reftest that attempts to use this feature without animations will
+fail with an error.
--- a/layout/tools/reftest/reftest-content.js
+++ b/layout/tools/reftest/reftest-content.js
@@ -417,20 +417,36 @@ function resetDisplayportAndViewport() {
// XXX currently the displayport configuration lives on the
// presshell and so is "reset" on nav when we get a new presshell.
}
function shouldWaitForExplicitPaintWaiters() {
return gExplicitPendingPaintCount > 0;
}
-function shouldWaitForPendingPaints() {
+function shouldWaitForPendingPaints(contentRootElement) {
// if gHaveCanvasSnapshot is false, we're not taking snapshots so
// there is no need to wait for pending paints to be flushed.
- return gHaveCanvasSnapshot && windowUtils().isMozAfterPaintPending;
+ return gHaveCanvasSnapshot &&
+ !shouldIgnorePendingMozAfterPaints(contentRootElement) &&
+ windowUtils().isMozAfterPaintPending;
+}
+
+function shouldIgnorePendingMozAfterPaints(contentRootElement) {
+ // use getAttribute because className works differently in HTML and SVG
+ var ignore = contentRootElement &&
+ contentRootElement.hasAttribute('class') &&
+ contentRootElement.getAttribute('class').split(/\s+/)
+ .includes("reftest-ignore-pending-paints");
+ // getAnimations is nightly-only, so check it exists before calling it
+ if (ignore && contentRootElement.ownerDocument.getAnimations
+ && contentRootElement.ownerDocument.getAnimations().length == 0) {
+ LogError("reftest-ignore-pending-paints should only be used on documents with animations!");
+ }
+ return ignore;
}
function shouldWaitForReftestWaitRemoval(contentRootElement) {
// use getAttribute because className works differently in HTML and SVG
return contentRootElement &&
contentRootElement.hasAttribute('class') &&
contentRootElement.getAttribute('class').split(/\s+/)
.includes("reftest-wait");
@@ -606,23 +622,23 @@ function WaitForTestEnd(contentRootEleme
? FlushMode.IGNORE_THROTTLED_ANIMATIONS
: FlushMode.ALL;
FlushRendering(flushMode);
}
switch (state) {
case STATE_WAITING_TO_FIRE_INVALIDATE_EVENT: {
LogInfo("MakeProgress: STATE_WAITING_TO_FIRE_INVALIDATE_EVENT");
- if (shouldWaitForExplicitPaintWaiters() || shouldWaitForPendingPaints()) {
+ if (shouldWaitForExplicitPaintWaiters() || shouldWaitForPendingPaints(contentRootElement)) {
gFailureReason = "timed out waiting for pending paint count to reach zero";
if (shouldWaitForExplicitPaintWaiters()) {
gFailureReason += " (waiting for MozPaintWaitFinished)";
LogInfo("MakeProgress: waiting for MozPaintWaitFinished");
}
- if (shouldWaitForPendingPaints()) {
+ if (shouldWaitForPendingPaints(contentRootElement)) {
gFailureReason += " (waiting for MozAfterPaint)";
LogInfo("MakeProgress: waiting for MozAfterPaint");
}
return;
}
state = STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL;
var hasReftestWait = shouldWaitForReftestWaitRemoval(contentRootElement);
@@ -650,17 +666,17 @@ function WaitForTestEnd(contentRootEleme
LogInfo("MakeProgress: setting up print mode");
setupPrintMode();
}
if (hasReftestWait && !shouldWaitForReftestWaitRemoval(contentRootElement)) {
// MozReftestInvalidate handler removed reftest-wait.
// We expect something to have been invalidated...
FlushRendering(FlushMode.ALL);
- if (!shouldWaitForPendingPaints() && !shouldWaitForExplicitPaintWaiters()) {
+ if (!shouldWaitForPendingPaints(contentRootElement) && !shouldWaitForExplicitPaintWaiters()) {
LogWarning("MozInvalidateEvent didn't invalidate");
}
}
// Try next state
MakeProgress();
return;
}
@@ -715,24 +731,24 @@ function WaitForTestEnd(contentRootEleme
case STATE_WAITING_FOR_APZ_FLUSH:
LogInfo("MakeProgress: STATE_WAITING_FOR_APZ_FLUSH");
// Nothing to do here; once we get the apz-repaints-flushed event
// we will go to STATE_WAITING_TO_FINISH
return;
case STATE_WAITING_TO_FINISH:
LogInfo("MakeProgress: STATE_WAITING_TO_FINISH");
- if (shouldWaitForExplicitPaintWaiters() || shouldWaitForPendingPaints()) {
+ if (shouldWaitForExplicitPaintWaiters() || shouldWaitForPendingPaints(contentRootElement)) {
gFailureReason = "timed out waiting for pending paint count to " +
"reach zero (after reftest-wait removed and switch to print mode)";
if (shouldWaitForExplicitPaintWaiters()) {
gFailureReason += " (waiting for MozPaintWaitFinished)";
LogInfo("MakeProgress: waiting for MozPaintWaitFinished");
}
- if (shouldWaitForPendingPaints()) {
+ if (shouldWaitForPendingPaints(contentRootElement)) {
gFailureReason += " (waiting for MozAfterPaint)";
LogInfo("MakeProgress: waiting for MozAfterPaint");
}
return;
}
if (contentRootElement) {
var elements = getNoPaintElements(contentRootElement);
for (var i = 0; i < elements.length; ++i) {
@@ -1075,16 +1091,25 @@ function DoAssertionCheck()
}
function LoadURI(uri)
{
var flags = webNavigation().LOAD_FLAGS_NONE;
webNavigation().loadURI(uri, flags, null, null, null);
}
+function LogError(str)
+{
+ if (gVerbose) {
+ sendSyncMessage("reftest:Log", { type: "error", msg: str });
+ } else {
+ sendAsyncMessage("reftest:Log", { type: "error", msg: str });
+ }
+}
+
function LogWarning(str)
{
if (gVerbose) {
sendSyncMessage("reftest:Log", { type: "warning", msg: str });
} else {
sendAsyncMessage("reftest:Log", { type: "warning", msg: str });
}
}
--- a/layout/tools/reftest/reftest.jsm
+++ b/layout/tools/reftest/reftest.jsm
@@ -1507,16 +1507,19 @@ function RecvInitCanvasWithSnapshot()
function RecvLog(type, msg)
{
msg = "[CONTENT] " + msg;
if (type == "info") {
TestBuffer(msg);
} else if (type == "warning") {
logger.warning(msg);
+ } else if (type == "error") {
+ logger.error("REFTEST TEST-UNEXPECTED-FAIL | " + g.currentURL + " | " + msg + "\n");
+ ++g.testResults.Exception;
} else {
logger.error("REFTEST TEST-UNEXPECTED-FAIL | " + g.currentURL + " | unknown log type " + type + "\n");
++g.testResults.Exception;
}
}
function RecvScriptResults(runtimeMs, error, results)
{