Compute minimum repaint and show them in overlay

For repaint tests, compute minimum repaint by comparing pixel dump
before and after repaintTest() and output repaint in layout test result.

In overlay, visualize the minimum repaint in expected results.

In this way, when making repaint change and checking actual results,
we can easily discover under-repaint bugs if we see any solid black
area not covered by repaint rects.

This CL depends on chromium-side CL https://codereview.chromium.org/301243022.

Review URL: https://codereview.chromium.org/315443002

git-svn-id: svn://svn.chromium.org/blink/trunk@176257 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 8f7dfe07
......@@ -2,6 +2,7 @@
// time.
window.testIsAsync = false;
window.outputRepaintRects = true;
window.generateMinimumRepaint = false; // See comments about 'Minimum repaint' below.
function runRepaintTest()
{
......@@ -24,12 +25,24 @@ function runRepaintTest()
// All repaint tests are asynchronous.
testRunner.waitUntilDone();
testRunner.displayAsyncThen(function() {
function continueRepaintTest()
{
window.internals.startTrackingRepaints(document);
repaintTest();
if (!window.testIsAsync)
finishRepaintTest();
});
}
if (window.generateMinimumRepaint) {
testRunner.capturePixelsAsyncThen(function(width, height, snapshot) {
window.widthBeforeRepaint = width;
window.heightBeforeRepaint = height;
window.snapshotBeforeRepaint = snapshot;
continueRepaintTest();
});
} else {
testRunner.displayAsyncThen(continueRepaintTest);
};
}
function runRepaintAndPixelTest()
......@@ -58,17 +71,120 @@ function finishRepaintTest()
internals.stopTrackingRepaints(document);
// Play nice with JS tests which may want to print out assert results.
if (window.isJsTest)
window.outputRepaintRects = false;
function repaintTestDone()
{
// Play nice with JS tests which may want to print out assert results.
if (window.isJsTest)
window.outputRepaintRects = false;
if (window.outputRepaintRects)
testRunner.setCustomTextOutput(repaintRects);
if (window.afterTest)
window.afterTest();
// Play nice with async JS tests which want to notifyDone themselves.
if (!window.jsTestIsAsync)
testRunner.notifyDone();
}
if (window.generateMinimumRepaint) {
internals.forceFullRepaint(document);
testRunner.capturePixelsAsyncThen(function(width, height, snapshot) {
if (window.outputRepaintRects) {
var minimumRepaint = computeMinimumRepaint(width, height, snapshot);
if (minimumRepaint.length)
repaintRects += '\nMinimum repaint:\n' + JSON.stringify(minimumRepaint, null, 2);
}
repaintTestDone();
});
} else {
repaintTestDone();
}
}
// Minimum repaint
//
// Definitions
// - minimum-repaint = region(diff(snapshot-before-repaintTest,
// snapshot-after-repaintTest-and-full-repaint))
// It includes all pixels that should be changed after repaintTest.
// - actual-repaint = repaint rects recorded during repaintTest() and
// forceStyleRecalc().
// - potential-under-repaint = subtract(minimum-repaint, actual-repaint)
// Potential-under-repaint will be shown in layout test overlay in black if
// any minimum-repaint region is not covered by actual-repaint.
//
// Potential-under-repaints don't always mean bug:
// - Some know visualization issues (crbug.com/381221) may cause false
// under-repaint;
// - Screen updates caused by composited layer re-compositing may not need
// repaint.
//
// How to use
// 1. Set window.generateMinimumRepaint to true in some repaint test or change
// this script to force window.generateMinimumRepaint to true.
// 2. Run layout tests. Repaint tests will result text diffs, which is because
// of the minimum repaint output in the actual results and doesn't mean the
// tests failed.
// 3. In layout test result page, click 'overlay' link or expand the test to
// see the 'overlay' pane.
// 4. Click 'Highlight under-repaint' button. You'll see black region if there
// is any under-repaint.
//
// Known issues
// crbug.com/381221
function computeMinimumRepaint(width, height, snapshot)
{
var result = [];
if (width > widthBeforeRepaint) {
result.push([widthBeforeRepaint, 0, width - widthBeforeRepaint, Math.max(height, heightBeforeRepaint)]);
width = widthBeforeRepaint;
}
if (height > heightBeforeRepaint) {
result.push([0, heightBeforeRepaint, width, height - heightBeforeRepaint]);
height = heightBeforeRepaint;
}
var dataBefore = new Uint32Array(snapshotBeforeRepaint);
var dataAfter = new Uint32Array(snapshot);
var rectsMayContinue = [];
var index = 0;
for (var y = 0; y < height; ++y) {
var x = 0;
var rectsMayContinueIndex = 0;
var nextRectsMayContinue = [];
while (true) {
while (x < width && dataBefore[index] == dataAfter[index]) {
++x;
++index;
}
xBegin = x;
while (x < width && dataBefore[index] != dataAfter[index]) {
++x;
++index;
}
xEnd = x;
if (window.outputRepaintRects)
testRunner.setCustomTextOutput(repaintRects);
var xWidth = xEnd - xBegin;
if (!xWidth)
break;
if (window.afterTest)
window.afterTest();
var rectMayContinue = rectsMayContinue[rectsMayContinueIndex];
while (rectMayContinue && rectMayContinue[0] < xBegin)
rectMayContinue = rectsMayContinue[++rectsMayContinueIndex];
// Play nice with async JS tests which want to notifyDone themselves.
if (!window.jsTestIsAsync)
testRunner.notifyDone();
if (rectMayContinue && rectMayContinue[0] == xBegin && rectMayContinue[2] == xWidth) {
++rectMayContinue[3];
nextRectsMayContinue.push(rectMayContinue);
} else {
var newRect = [xBegin, y, xWidth, 1];
nextRectsMayContinue.push(newRect);
result.push(newRect);
}
}
rectsMayContinue = nextRectsMayContinue;
}
return result;
}
......@@ -2029,6 +2029,17 @@ void Internals::updateLayoutIgnorePendingStylesheetsAndRunPostLayoutTasks(Node*
document->updateLayoutIgnorePendingStylesheets(Document::RunPostLayoutTasksSynchronously);
}
void Internals::forceFullRepaint(Document* document, ExceptionState& exceptionState)
{
if (!document || !document->view()) {
exceptionState.throwDOMException(InvalidAccessError, document ? "The document's view cannot be retrieved." : "The document provided is invalid.");
return;
}
if (RenderView *renderView = document->renderView())
renderView->repaintViewAndCompositedLayers();
}
PassRefPtrWillBeRawPtr<ClientRectList> Internals::draggableRegions(Document* document, ExceptionState& exceptionState)
{
return annotatedRegions(document, true, exceptionState);
......
......@@ -277,6 +277,7 @@ public:
void stopTrackingRepaints(Document*, ExceptionState&);
void updateLayoutIgnorePendingStylesheetsAndRunPostLayoutTasks(ExceptionState&);
void updateLayoutIgnorePendingStylesheetsAndRunPostLayoutTasks(Node*, ExceptionState&);
void forceFullRepaint(Document*, ExceptionState&);
PassRefPtrWillBeRawPtr<ClientRectList> draggableRegions(Document*, ExceptionState&);
PassRefPtrWillBeRawPtr<ClientRectList> nonDraggableRegions(Document*, ExceptionState&);
......
......@@ -234,6 +234,8 @@
// specified without security checks. Unspecified means this document.
[RaisesException] void updateLayoutIgnorePendingStylesheetsAndRunPostLayoutTasks(optional Node node);
[RaisesException] void forceFullRepaint(Document document);
// Returns a list of draggable/non-draggable regions in the document.
[RaisesException] ClientRectList draggableRegions(Document document);
[RaisesException] ClientRectList nonDraggableRegions(Document document);
......
......@@ -24,6 +24,11 @@ def generate_repaint_overlay_html(test_name, actual_text, expected_text):
expected_rects = make_js_rect(expected_text)
actual_rects = make_js_rect(actual_text)
minimum_repaint = '[]'
minimum_repaint_match = re.search('Minimum repaint:\n(\[.*\n\])', actual_text, re.DOTALL)
if minimum_repaint_match:
minimum_repaint = minimum_repaint_match.group(1)
return """<!DOCTYPE HTML>
<html>
<head>
......@@ -35,21 +40,23 @@ def generate_repaint_overlay_html(test_name, actual_text, expected_text):
}
iframe {
position: absolute;
top: 60px;
top: 80px;
left: 0;
border: 0;
z-index: -1;
}
canvas {
position: absolute;
top: 60px;
top: 80px;
left: 0;
z-index: 1;
}
#actual { display: none; }
#actual, #minimum-repaint {
display: none;
}
#dump {
position: absolute;
top: 60px;
top: 80px;
left: 0;
z-index: 2;
display: none;
......@@ -57,14 +64,23 @@ def generate_repaint_overlay_html(test_name, actual_text, expected_text):
</style>
</head>
<body>
Note: This version doesn't support transformation and layer offset yet.<br>
<label><input type="checkbox" checked onchange="toggle_test(this.checked)">Show test</label>
<label><input type="checkbox" checked onchange="toggle_diff_only(this.checked)">Show differences only</label>
<a href="http://crbug.com/381221">Known issues (layer transformations, layer offsets, etc.)</a><br>
<label><input id="show-test" type="checkbox" checked onchange="toggle_test(this.checked)">Show test</label>
<label><input id="show-diff-only" type="checkbox" checked onchange="toggle_diff_only(this.checked)">Diffs only</label>
<label><input type="checkbox" checked onchange="toggle_hide_duplicate_rects(this.checked)">Hide duplicate rects</label>
<label><input type="checkbox" onchange="toggle_print_rects(this.checked)">Dump rects</label>
<label><input type="checkbox" onchange="toggle_dump_rects(this.checked)">Dump rects</label>
<label title="See fast/repaint/resources/text-based-repaint.js for how this works">
<input id="show-minimum-repaint" type="checkbox" onchange="toggle_minimum_repaint(this.checked)">Minimum repaint
</label>
<label><input id="use-solid-colors" type="checkbox" onchange="toggle_solid_color(this.checked)">Use solid colors</label>
<br>
<button title="See fast/repaint/resources/text-based-repaint.js for how this works" onclick="highlight_under_repaint()">
Highlight under-repaint
</button>
<br>
<span id='type'>Expected Invalidations</span>
<div id=overlay>
<canvas id='minimum-repaint' width='2000' height='2000'></canvas>
<canvas id='expected' width='2000' height='2000'></canvas>
<canvas id='actual' width='2000' height='2000'></canvas>
<pre id='dump'></pre>
......@@ -72,6 +88,7 @@ Note: This version doesn't support transformation and layer offset yet.<br>
<script>
var show_diff_only = true;
var hide_duplicate_rects = true;
var overlay_opacity = 0.25;
function toggle_test(show_test) {
iframe.style.display = show_test ? 'block' : 'none';
......@@ -87,12 +104,34 @@ function toggle_hide_duplicate_rects(new_hide_duplicate_rects) {
draw_repaint_rects();
}
function toggle_print_rects(dump_rects) {
function toggle_dump_rects(dump_rects) {
document.getElementById('dump').style.display = dump_rects ? 'block' : 'none';
}
function toggle_minimum_repaint(show_minimum_repaint) {
document.getElementById('minimum-repaint').style.display = show_minimum_repaint ? 'block' : 'none';
}
function toggle_solid_color(use_solid_color) {
overlay_opacity = use_solid_color ? 1 : 0.25;
draw_repaint_rects();
draw_minimum_repaint();
}
function highlight_under_repaint() {
document.getElementById('show-test').checked = false;
toggle_test(false);
document.getElementById('show-diff-only').checked = false;
show_diff_only = false;
document.getElementById('show-minimum-repaint').checked = true;
toggle_minimum_repaint(true);
document.getElementById('use-solid-colors').checked = true;
toggle_solid_color(true);
}
var original_expected_rects = %(expected_rects)s;
var original_actual_rects = %(actual_rects)s;
var minimum_repaint = %(minimum_repaint)s;
function rectsEqual(rect1, rect2) {
return rect1[0] == rect2[0] && rect1[1] == rect2[1] && rect1[2] == rect2[2] && rect1[3] == rect2[3];
......@@ -126,12 +165,12 @@ function draw_rects(context, rects) {
for (var i = 0; i < rects.length; i++) {
var rect = rects[i];
context.fillRect(rect[0], rect[1], rect[2], rect[3]);
context.strokeRect(rect[0], rect[1], rect[2], rect[3]);
}
}
var expected_canvas = document.getElementById('expected');
var actual_canvas = document.getElementById('actual');
var minimum_repaint_canvas = document.getElementById('minimum-repaint');
function dump_rects(rects) {
var result = '';
......@@ -143,6 +182,7 @@ function dump_rects(rects) {
function draw_repaint_rects() {
var expected_rects = original_expected_rects.slice(0);
var actual_rects = original_actual_rects.slice(0);
if (hide_duplicate_rects) {
removeDuplicateRects(expected_rects);
removeDuplicateRects(actual_rects);
......@@ -153,21 +193,26 @@ function draw_repaint_rects() {
document.getElementById('dump').textContent =
'Expected:\\n' + dump_rects(expected_rects)
+ '\\nActual:\\n' + dump_rects(actual_rects);
+ '\\nActual:\\n' + dump_rects(actual_rects)
+ '\\nMinimal:\\n' + dump_rects(minimum_repaint);
var expected_ctx = expected_canvas.getContext("2d");
expected_ctx.lineWidth = 0.5;
expected_ctx.strokeStyle = 'rgba(255, 0, 0, 1)';
expected_ctx.fillStyle = 'rgba(255, 0, 0, 0.25)';
expected_ctx.fillStyle = 'rgba(255, 0, 0, ' + overlay_opacity + ')';
draw_rects(expected_ctx, expected_rects);
var actual_ctx = actual_canvas.getContext("2d");
actual_ctx.lineWidth = 1;
actual_ctx.strokeStyle = 'rgba(0, 255, 0, 1)';
actual_ctx.fillStyle = 'rgba(0, 255, 0, 0.25)';
actual_ctx.fillStyle = 'rgba(0, 255, 0, ' + overlay_opacity + ')';
draw_rects(actual_ctx, actual_rects);
}
function draw_minimum_repaint() {
var context = minimum_repaint_canvas.getContext("2d");
context.fillStyle = 'rgba(0, 0, 0, 1)';
draw_rects(context, minimum_repaint);
}
draw_repaint_rects();
draw_minimum_repaint();
var path = decodeURIComponent(location.search).substr(1);
var iframe = document.createElement('iframe');
......@@ -201,4 +246,5 @@ setInterval(flip, 3000);
'title': test_name,
'expected_rects': expected_rects,
'actual_rects': actual_rects,
'minimum_repaint': minimum_repaint,
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment