Move parseFontFaceDescriptor to CSSPropertyParser.cpp
[chromium-blink-merge.git] / third_party / WebKit / LayoutTests / fast / harness / results.html
blob779da5c0ce6d6c6ad6d30ddba1ad542cbea828b8
1 <!DOCTYPE html>
2 <style>
3 html {
4 height: 100%;
6 body {
7 margin: 0;
8 font-family: Helvetica, sans-serif;
9 font-size: 11pt;
10 display: -webkit-flex;
11 -webkit-flex-direction: column;
12 height: 100%;
15 body > * {
16 margin-left: 4px;
17 margin-top: 4px;
20 h1 {
21 font-size: 14pt;
22 margin-top: 1.5em;
25 p {
26 margin-bottom: 0.3em;
29 tr:not(.results-row) td {
30 white-space: nowrap;
33 tr:not(.results-row) td:first-of-type {
34 white-space: normal;
37 td:not(:first-of-type) {
38 text-transform: lowercase;
41 td {
42 padding: 1px 4px;
45 th:empty, td:empty {
46 padding: 0;
49 th {
50 -webkit-user-select: none;
51 -moz-user-select: none;
54 .content-container {
55 -webkit-flex: 1;
56 min-height: -webkit-min-content;
57 overflow: auto;
60 .note {
61 color: gray;
62 font-size: smaller;
65 .results-row {
66 background-color: white;
69 .results-row iframe, .results-row img {
70 width: 800px;
71 height: 600px;
74 .results-row[data-expanded="false"] {
75 display: none;
78 #toolbar {
79 position: fixed;
80 padding: 4px;
81 top: 2px;
82 right: 2px;
83 text-align: right;
84 background-color: rgba(255, 255, 255, 0.85);
85 border: 1px solid silver;
86 border-radius: 4px;
89 .expand-button {
90 background-color: white;
91 border: 1px solid gray;
92 cursor: default;
93 display: inline-block;
94 line-height: 1em;
95 margin: 0 3px 0 0;
96 position: relative;
97 text-align: center;
98 -webkit-user-select: none;
99 width: 1em;
102 .current {
103 color: red;
106 .current .expand-button {
107 border-color: red;
110 tbody .flag {
111 display: none;
114 tbody.flagged .flag {
115 display: inline;
118 .stopped-running-early-message {
119 border: 3px solid #d00;
120 font-weight: bold;
121 display: inline-block;
122 padding: 3px;
125 .result-container {
126 display: inline-block;
127 border: 1px solid gray;
128 margin: 4px;
131 .result-container iframe, .result-container img {
132 border: 0;
133 vertical-align: top;
136 .label {
137 padding-left: 3px;
138 font-weight: bold;
139 font-size: small;
140 background-color: silver;
143 .pixel-zoom-container {
144 position: fixed;
145 top: 0;
146 left: 0;
147 width: 96%;
148 margin: 10px;
149 padding: 10px;
150 display: -webkit-box;
151 display: -moz-box;
152 pointer-events: none;
153 background-color: silver;
154 border-radius: 20px;
155 border: 1px solid gray;
156 box-shadow: 0 0 5px rgba(0, 0, 0, 0.75);
159 .pixel-zoom-container > * {
160 -webkit-box-flex: 1;
161 -moz-box-flex: 1;
162 border: 1px solid black;
163 margin: 4px;
164 overflow: hidden;
165 background-color: white;
168 .pixel-zoom-container .scaled-image-container {
169 position: relative;
170 overflow: hidden;
171 width: 100%;
172 height: 400px;
175 .scaled-image-container > img {
176 position: absolute;
177 top: 0;
178 left: 0;
179 image-rendering: -webkit-optimize-contrast;
182 #flagged-tests {
183 margin: 1px;
184 padding: 5px;
185 height: 100px;
188 #flagged-test-container h2 {
189 display: inline-block;
190 margin: 0 10px 0 0;
192 </style>
193 <style id="unexpected-pass-style"></style>
194 <style id="flaky-failures-style"></style>
195 <style id="stderr-style"></style>
196 <style id="unexpected-style"></style>
198 <script>
199 var g_state;
200 function globalState()
202 if (!g_state) {
203 g_state = {
204 crashTests: [],
205 leakTests: [],
206 flakyPassTests: [],
207 hasHttpTests: false,
208 hasImageFailures: false,
209 hasTextFailures: false,
210 missingResults: [],
211 results: {},
212 shouldToggleImages: true,
213 failingTests: [],
214 testsWithStderr: [],
215 timeoutTests: [],
216 unexpectedPassTests: []
219 return g_state;
222 function ADD_RESULTS(input)
224 globalState().results = input;
226 </script>
228 <script src="failing_results.json"></script>
230 <script>
231 function stripExtension(test)
233 var index = test.lastIndexOf('.');
234 return test.substring(0, index);
237 function matchesSelector(node, selector)
239 if (node.webkitMatchesSelector)
240 return node.webkitMatchesSelector(selector);
242 if (node.mozMatchesSelector)
243 return node.mozMatchesSelector(selector);
246 function parentOfType(node, selector)
248 while (node = node.parentNode) {
249 if (matchesSelector(node, selector))
250 return node;
252 return null;
255 function remove(node)
257 node.parentNode.removeChild(node);
260 function forEach(nodeList, handler)
262 Array.prototype.forEach.call(nodeList, handler);
265 function resultIframe(src)
267 // FIXME: use audio tags for AUDIO tests?
268 var layoutTestsIndex = src.indexOf('LayoutTests');
269 var name;
270 if (layoutTestsIndex != -1) {
271 var hasTrac = src.indexOf('trac.webkit.org') != -1;
272 var prefix = hasTrac ? 'trac.webkit.org/.../' : '';
273 name = prefix + src.substring(layoutTestsIndex + 'LayoutTests/'.length);
274 } else {
275 var lastDashIndex = src.lastIndexOf('-pretty');
276 if (lastDashIndex == -1)
277 lastDashIndex = src.lastIndexOf('-');
278 name = src.substring(lastDashIndex + 1);
281 var tagName = (src.lastIndexOf('.png') == -1) ? 'iframe' : 'img';
283 if (tagName != 'img')
284 src += '?format=txt';
285 return '<div class=result-container><div class=label>' + name + '</div><' + tagName + ' src="' + src + '"></' + tagName + '></div>';
288 function togglingImage(prefix)
290 return '<div class=result-container><div class="label imageText"></div><img class=animatedImage data-prefix="' +
291 prefix + '"></img></div>';
294 function toggleExpectations(element)
296 var expandLink = element;
297 if (expandLink.className != 'expand-button-text')
298 expandLink = expandLink.querySelector('.expand-button-text');
300 if (expandLink.textContent == '+')
301 expandExpectations(expandLink, true);
302 else
303 collapseExpectations(expandLink);
306 function collapseExpectations(expandLink)
308 expandLink.textContent = '+';
309 var existingResultsRow = parentOfType(expandLink, 'tbody').querySelector('.results-row');
310 if (existingResultsRow)
311 updateExpandedState(existingResultsRow, false);
314 function updateExpandedState(row, isExpanded)
316 row.setAttribute('data-expanded', isExpanded);
317 updateImageTogglingTimer();
320 function appendHTML(node, html)
322 if (node.insertAdjacentHTML)
323 node.insertAdjacentHTML('beforeEnd', html);
324 else
325 node.innerHTML += html;
328 function expandExpectations(expandLink, selectRow)
330 var row = parentOfType(expandLink, 'tr');
331 var parentTbody = row.parentNode;
332 var existingResultsRow = parentTbody.querySelector('.results-row');
334 var enDash = '\u2013';
335 expandLink.textContent = enDash;
336 if (existingResultsRow) {
337 updateExpandedState(existingResultsRow, true);
338 if (selectRow)
339 TestNavigator._setCurrentTest(parentTbody);
340 return;
343 var newRow = document.createElement('tr');
344 newRow.className = 'results-row';
345 var newCell = document.createElement('td');
346 newCell.colSpan = row.querySelectorAll('td').length;
348 var resultLinks = row.querySelectorAll('.result-link');
349 var hasTogglingImages = false;
350 for (var i = 0; i < resultLinks.length; i++) {
351 var link = resultLinks[i];
352 var result;
353 if (link.textContent == 'images') {
354 hasTogglingImages = true;
355 result = togglingImage(link.getAttribute('data-prefix'));
356 } else
357 result = resultIframe(link.href);
359 appendHTML(newCell, result);
362 newRow.appendChild(newCell);
363 parentTbody.appendChild(newRow);
365 updateExpandedState(newRow, true);
366 if (selectRow)
367 TestNavigator._setCurrentTest(parentTbody);
368 updateImageTogglingTimer();
371 function updateImageTogglingTimer()
373 var hasVisibleAnimatedImage = document.querySelector('.results-row[data-expanded="true"] .animatedImage');
374 if (!hasVisibleAnimatedImage) {
375 clearInterval(globalState().togglingImageInterval);
376 globalState().togglingImageInterval = null;
377 return;
380 if (!globalState().togglingImageInterval) {
381 toggleImages();
382 globalState().togglingImageInterval = setInterval(toggleImages, 2000);
386 function async(func, args)
388 setTimeout(function() { func.apply(null, args); }, 100);
391 function visibleTests(opt_container)
393 var container = opt_container || document;
394 if (onlyShowUnexpectedFailures())
395 return container.querySelectorAll('tbody:not(.expected)');
396 else
397 return container.querySelectorAll('tbody');
400 function visibleExpandLinks()
402 if (onlyShowUnexpectedFailures())
403 return document.querySelectorAll('tbody:not(.expected) .expand-button-text');
404 else
405 return document.querySelectorAll('.expand-button-text');
408 function expandAllExpectations()
410 var expandLinks = visibleExpandLinks();
411 for (var i = 0, len = expandLinks.length; i < len; i++)
412 async(expandExpectations, [expandLinks[i]]);
415 function collapseAllExpectations()
417 var expandLinks = visibleExpandLinks();
418 for (var i = 0, len = expandLinks.length; i < len; i++)
419 async(collapseExpectations, [expandLinks[i]]);
422 function shouldUseTracLinks()
424 return !globalState().results.layout_tests_dir || !location.toString().indexOf('file://') == 0;
427 function testLinkTarget(test)
429 var target;
430 if (shouldUseTracLinks()) {
431 var revision = globalState().results.revision;
432 target = 'http://src.chromium.org/viewvc/blink/trunk/LayoutTests/' + test;
433 if (revision)
434 target += '?pathrev=' + revision;
435 target += '#l1';
436 } else
437 target = globalState().results.layout_tests_dir + '/' + test;
438 return target;
441 function testLink(test)
443 var target = testLinkTarget(test);
444 return '<a class=test-link href="' + target + '">' + test + '</a><span class=flag onclick="unflag(this)"> \u2691</span>';
447 function unflag(flag)
449 var shouldFlag = false;
450 TestNavigator.flagTest(parentOfType(flag, 'tbody'), shouldFlag);
451 TestNavigator.updateFlaggedTestTextBox();
454 function testLinkWithExpandButton(test)
456 return '<span class=expand-button onclick="toggleExpectations(this)"><span class=expand-button-text>+</span></span>' + testLink(test);
459 function resultLink(testPrefix, suffix, contents)
461 return '<a class=result-link href="' + testPrefix + suffix + '" data-prefix="' + testPrefix + '">' + contents + '</a> ';
464 function processGlobalStateFor(testObject)
466 var test = testObject.name;
467 if (testObject.has_stderr)
468 globalState().testsWithStderr.push(testObject);
470 globalState().hasHttpTests = globalState().hasHttpTests || test.indexOf('http/') == 0;
472 var actual = testObject.actual;
473 var expected = testObject.expected || 'PASS';
475 if (actual == 'MISSING') {
476 // FIXME: make sure that new-run-webkit-tests spits out an -actual.txt file for
477 // tests with MISSING results.
478 globalState().missingResults.push(testObject);
479 return;
482 var actualTokens = actual.split(' ');
483 var passedWithImageOnlyFailureInRetry = actualTokens[0] == 'TEXT' && actualTokens[1] == 'IMAGE';
484 if (actualTokens[1] && actual.indexOf('PASS') != -1 || (!globalState().results.pixel_tests_enabled && passedWithImageOnlyFailureInRetry)) {
485 globalState().flakyPassTests.push(testObject);
486 return;
489 if (actual == 'PASS' && expected != 'PASS') {
490 if (expected != 'IMAGE' || (globalState().results.pixel_tests_enabled || testObject.reftest_type)) {
491 globalState().unexpectedPassTests.push(testObject);
493 return;
496 if (actual == 'CRASH') {
497 globalState().crashTests.push(testObject);
498 return;
501 if (actual == 'LEAK') {
502 globalState().leakTests.push(testObject);
503 return;
506 if (actual == 'TIMEOUT') {
507 globalState().timeoutTests.push(testObject);
508 return;
511 globalState().failingTests.push(testObject);
514 function toggleImages()
516 var images = document.querySelectorAll('.animatedImage');
517 var imageTexts = document.querySelectorAll('.imageText');
518 for (var i = 0, len = images.length; i < len; i++) {
519 var image = images[i];
520 var text = imageTexts[i];
521 if (text.textContent == 'Expected Image') {
522 text.textContent = 'Actual Image';
523 image.src = image.getAttribute('data-prefix') + '-actual.png';
524 } else {
525 text.textContent = 'Expected Image';
526 image.src = image.getAttribute('data-prefix') + '-expected.png';
531 function textResultLinks(test, prefix)
533 var html = resultLink(prefix, '-expected.txt', 'expected') +
534 resultLink(prefix, '-actual.txt', 'actual') +
535 resultLink(prefix, '-diff.txt', 'diff');
537 if (globalState().results.has_pretty_patch)
538 html += resultLink(prefix, '-pretty-diff.html', 'pretty diff');
540 if (globalState().results.has_wdiff)
541 html += resultLink(prefix, '-wdiff.html', 'wdiff');
543 return html;
546 function imageResultsCell(testObject, testPrefix, actual) {
547 var row = '';
549 if (actual.indexOf('IMAGE') != -1) {
550 globalState().hasImageFailures = true;
552 if (testObject.reftest_type && testObject.reftest_type.indexOf('!=') != -1) {
553 row += resultLink(testPrefix, '-expected-mismatch.html', 'ref mismatch html');
554 row += resultLink(testPrefix, '-actual.png', 'actual');
555 } else {
556 if (testObject.reftest_type && testObject.reftest_type.indexOf('==') != -1) {
557 row += resultLink(testPrefix, '-expected.html', 'ref html');
559 if (globalState().shouldToggleImages) {
560 row += resultLink(testPrefix, '-diffs.html', 'images');
561 } else {
562 row += resultLink(testPrefix, '-expected.png', 'expected');
563 row += resultLink(testPrefix, '-actual.png', 'actual');
566 row += resultLink(testPrefix, '-diff.png', 'diff');
570 if (actual.indexOf('MISSING') != -1 && testObject.is_missing_image)
571 row += resultLink(testPrefix, '-actual.png', 'png result');
573 return row;
576 function tableRow(testObject)
578 var row = '<tbody class="' + (testObject.is_unexpected ? '' : 'expected') + '"';
579 row += ' data-testname="' + testObject.name + '"';
580 if (testObject.reftest_type && testObject.reftest_type.indexOf('!=') != -1)
581 row += ' mismatchreftest=true';
582 row += '><tr>';
584 row += '<td>' + testLinkWithExpandButton(testObject.name) + '</td>';
586 var testPrefix = stripExtension(testObject.name);
587 row += '<td>';
589 var actual = testObject.actual;
590 if (actual.indexOf('TEXT') != -1) {
591 globalState().hasTextFailures = true;
592 if (testObject.is_testharness_test) {
593 row += resultLink(testPrefix, '-actual.txt', 'actual');
594 } else {
595 row += textResultLinks(testObject.name, testPrefix);
599 if (actual.indexOf('AUDIO') != -1) {
600 row += resultLink(testPrefix, '-expected.wav', 'expected audio');
601 row += resultLink(testPrefix, '-actual.wav', 'actual audio');
604 if (actual.indexOf('MISSING') != -1) {
605 if (testObject.is_missing_audio)
606 row += resultLink(testPrefix, '-actual.wav', 'audio result');
607 if (testObject.is_missing_text)
608 row += resultLink(testPrefix, '-actual.txt', 'result');
611 if (actual.indexOf('CRASH') != -1) {
612 row += resultLink(testPrefix, '-crash-log.txt', 'crash log');
613 row += resultLink(testPrefix, '-sample.txt', 'sample');
614 if (testObject.has_stderr)
615 row += resultLink(testPrefix, '-stderr.txt', 'stderr');
618 if (testObject.has_repaint_overlay)
619 row += resultLink(testPrefix, '-overlay.html?' + encodeURIComponent(testLinkTarget(testObject.name)), 'overlay');
621 var actualTokens = actual.split(/\s+/);
622 var cell = imageResultsCell(testObject, testPrefix, actualTokens[0]);
623 if (!cell && actualTokens.length > 1)
624 cell = imageResultsCell(testObject, 'retries/' + testPrefix, actualTokens[1]);
626 row += '</td><td>' + cell + '</td>' +
627 '<td>' + actual + '</td>' +
628 '<td>' + (actual.indexOf('MISSING') == -1 ? testObject.expected : '') + '</td>' +
629 '</tr></tbody>';
630 return row;
633 function forEachTest(handler, opt_tree, opt_prefix)
635 var tree = opt_tree || globalState().results.tests;
636 var prefix = opt_prefix || '';
638 for (var key in tree) {
639 var newPrefix = prefix ? (prefix + '/' + key) : key;
640 if ('actual' in tree[key]) {
641 var testObject = tree[key];
642 testObject.name = newPrefix;
643 handler(testObject);
644 } else
645 forEachTest(handler, tree[key], newPrefix);
649 function hasUnexpected(tests)
651 return tests.some(function (test) { return test.is_unexpected; });
654 function updateTestlistCounts()
656 forEach(document.querySelectorAll('.test-list-count'), function(count) {
657 var container = parentOfType(count, 'div');
658 var testContainers;
659 if (onlyShowUnexpectedFailures())
660 testContainers = container.querySelectorAll('tbody:not(.expected)');
661 else
662 testContainers = container.querySelectorAll('tbody');
664 count.textContent = testContainers.length;
668 function flagAll(headerLink)
670 var tests = visibleTests(parentOfType(headerLink, 'div'));
671 forEach(tests, function(test) {
672 TestNavigator.flagTest(test, true);
674 TestNavigator.updateFlaggedTestTextBox();
677 function unflagAll(headerLink)
679 var tests = visibleTests(parentOfType(headerLink, 'div'));
680 forEach(tests, function(test) {
681 TestNavigator.flagTest(test, false);
683 TestNavigator.updateFlaggedTestTextBox();
686 function testListHeaderHtml(header)
688 return '<h1>' + header + ' (<span class=test-list-count></span>): [<a href="#" class=flag-all onclick="flagAll(this)">flag all</a>] [<a href="#" class=flag-all onclick="unflagAll(this)">unflag all</a>]</h1>';
691 function testList(tests, header, tableId)
693 tests.sort();
695 var html = '<div' + ((!hasUnexpected(tests) && tableId != 'stderr-table') ? ' class=expected' : '') + ' id=' + tableId + '>' +
696 testListHeaderHtml(header) + '<table>';
698 // FIXME: Include this for all testLists.
699 if (tableId == 'passes-table')
700 html += '<thead><th>test</th><th>expected</th></thead>';
702 for (var i = 0; i < tests.length; i++) {
703 var testObject = tests[i];
704 var test = testObject.name;
705 html += '<tbody class="' + ((testObject.is_unexpected || tableId == 'stderr-table') ? '' : 'expected') + '" data-testname="' + test + '"><tr><td>' +
706 ((tableId == 'passes-table') ? testLink(test) : testLinkWithExpandButton(test)) +
707 '</td><td>';
709 if (tableId == 'stderr-table')
710 html += resultLink(stripExtension(test), '-stderr.txt', 'stderr');
711 else if (tableId == 'passes-table')
712 html += testObject.expected;
713 else if (tableId == 'crash-tests-table') {
714 html += resultLink(stripExtension(test), '-crash-log.txt', 'crash log');
715 html += resultLink(stripExtension(test), '-sample.txt', 'sample');
716 if (testObject.has_stderr)
717 html += resultLink(stripExtension(test), '-stderr.txt', 'stderr');
718 } else if (tableId == 'leak-tests-table')
719 html += resultLink(stripExtension(test), '-leak-log.txt', 'leak log');
720 else if (tableId == 'timeout-tests-table') {
721 // FIXME: only include timeout actual/diff results here if we actually spit out results for timeout tests.
722 html += textResultLinks(test, stripExtension(test));
725 if (testObject.has_repaint_overlay)
726 html += resultLink(stripExtension(test), '-overlay.html?' + encodeURIComponent(testLinkTarget(test)), 'overlay');
728 html += '</td></tr></tbody>';
730 html += '</table></div>';
731 return html;
734 function toArray(nodeList)
736 return Array.prototype.slice.call(nodeList);
739 function trim(string)
741 return string.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
744 // Just a namespace for code management.
745 var TableSorter = {};
747 TableSorter._forwardArrow = '<svg style="width:10px;height:10px"><polygon points="0,0 10,0 5,10" style="fill:#ccc"></svg>';
749 TableSorter._backwardArrow = '<svg style="width:10px;height:10px"><polygon points="0,10 10,10 5,0" style="fill:#ccc"></svg>';
751 TableSorter._sortedContents = function(header, arrow)
753 return arrow + ' ' + trim(header.textContent) + ' ' + arrow;
756 TableSorter._updateHeaderClassNames = function(newHeader)
758 var sortHeader = document.querySelector('.sortHeader');
759 if (sortHeader) {
760 if (sortHeader == newHeader) {
761 var isAlreadyReversed = sortHeader.classList.contains('reversed');
762 if (isAlreadyReversed)
763 sortHeader.classList.remove('reversed');
764 else
765 sortHeader.classList.add('reversed');
766 } else {
767 sortHeader.textContent = sortHeader.textContent;
768 sortHeader.classList.remove('sortHeader');
769 sortHeader.classList.remove('reversed');
773 newHeader.classList.add('sortHeader');
776 TableSorter._textContent = function(tbodyRow, column)
778 return tbodyRow.querySelectorAll('td')[column].textContent;
781 TableSorter._sortRows = function(newHeader, reversed)
783 var testsTable = document.getElementById('results-table');
784 var headers = toArray(testsTable.querySelectorAll('th'));
785 var sortColumn = headers.indexOf(newHeader);
787 var rows = toArray(testsTable.querySelectorAll('tbody'));
789 rows.sort(function(a, b) {
790 // Only need to support lexicographic sort for now.
791 var aText = TableSorter._textContent(a, sortColumn);
792 var bText = TableSorter._textContent(b, sortColumn);
794 // Forward sort equal values by test name.
795 if (sortColumn && aText == bText) {
796 var aTestName = TableSorter._textContent(a, 0);
797 var bTestName = TableSorter._textContent(b, 0);
798 if (aTestName == bTestName)
799 return 0;
800 return aTestName < bTestName ? -1 : 1;
803 if (reversed)
804 return aText < bText ? 1 : -1;
805 else
806 return aText < bText ? -1 : 1;
809 for (var i = 0; i < rows.length; i++)
810 testsTable.appendChild(rows[i]);
813 TableSorter.sortColumn = function(columnNumber)
815 var newHeader = document.getElementById('results-table').querySelectorAll('th')[columnNumber];
816 TableSorter._sort(newHeader);
819 TableSorter.handleClick = function(e)
821 var newHeader = e.target;
822 if (newHeader.localName != 'th')
823 return;
824 TableSorter._sort(newHeader);
827 TableSorter._sort = function(newHeader)
829 TableSorter._updateHeaderClassNames(newHeader);
831 var reversed = newHeader.classList.contains('reversed');
832 var sortArrow = reversed ? TableSorter._backwardArrow : TableSorter._forwardArrow;
833 newHeader.innerHTML = TableSorter._sortedContents(newHeader, sortArrow);
835 TableSorter._sortRows(newHeader, reversed);
838 var PixelZoomer = {};
840 PixelZoomer.showOnDelay = true;
841 PixelZoomer._zoomFactor = 6;
843 var kResultWidth = 800;
844 var kResultHeight = 600;
846 var kZoomedResultWidth = kResultWidth * PixelZoomer._zoomFactor;
847 var kZoomedResultHeight = kResultHeight * PixelZoomer._zoomFactor;
849 PixelZoomer._zoomImageContainer = function(url)
851 var container = document.createElement('div');
852 container.className = 'zoom-image-container';
854 var title = url.match(/\-([^\-]*)\.png/)[1];
856 var label = document.createElement('div');
857 label.className = 'label';
858 label.appendChild(document.createTextNode(title));
859 container.appendChild(label);
861 var imageContainer = document.createElement('div');
862 imageContainer.className = 'scaled-image-container';
864 var image = new Image();
865 image.src = url;
866 image.style.display = 'none';
868 var canvas = document.createElement('canvas');
870 imageContainer.appendChild(image);
871 imageContainer.appendChild(canvas);
872 container.appendChild(imageContainer);
874 return container;
877 PixelZoomer._createContainer = function(e)
879 var tbody = parentOfType(e.target, 'tbody');
880 var row = tbody.querySelector('tr');
881 var imageDiffLinks = row.querySelectorAll('a[href$=".png"]');
883 var container = document.createElement('div');
884 container.className = 'pixel-zoom-container';
886 var html = '';
888 var togglingImageLink = row.querySelector('a[href$="-diffs.html"]');
889 if (togglingImageLink) {
890 var prefix = togglingImageLink.getAttribute('data-prefix');
891 container.appendChild(PixelZoomer._zoomImageContainer(prefix + '-expected.png'));
892 container.appendChild(PixelZoomer._zoomImageContainer(prefix + '-actual.png'));
895 for (var i = 0; i < imageDiffLinks.length; i++)
896 container.appendChild(PixelZoomer._zoomImageContainer(imageDiffLinks[i].href));
898 document.body.appendChild(container);
899 PixelZoomer._drawAll();
902 PixelZoomer._draw = function(imageContainer)
904 var image = imageContainer.querySelector('img');
905 var canvas = imageContainer.querySelector('canvas');
907 if (!image.complete) {
908 image.onload = function() {
909 PixelZoomer._draw(imageContainer);
911 return;
914 canvas.width = imageContainer.clientWidth;
915 canvas.height = imageContainer.clientHeight;
917 var ctx = canvas.getContext('2d');
918 ctx.mozImageSmoothingEnabled = false;
919 ctx.imageSmoothingEnabled = false;
920 ctx.translate(imageContainer.clientWidth / 2, imageContainer.clientHeight / 2);
921 ctx.translate(-PixelZoomer._percentX * kZoomedResultWidth, -PixelZoomer._percentY * kZoomedResultHeight);
922 ctx.strokeRect(-1.5, -1.5, kZoomedResultWidth + 2, kZoomedResultHeight + 2);
923 ctx.scale(PixelZoomer._zoomFactor, PixelZoomer._zoomFactor);
924 ctx.drawImage(image, 0, 0);
927 PixelZoomer._drawAll = function()
929 forEach(document.querySelectorAll('.pixel-zoom-container .scaled-image-container'), PixelZoomer._draw);
932 PixelZoomer.handleMouseOut = function(e)
934 if (e.relatedTarget && e.relatedTarget.tagName != 'IFRAME')
935 return;
937 // If e.relatedTarget is null, we've moused out of the document.
938 var container = document.querySelector('.pixel-zoom-container');
939 if (container)
940 remove(container);
943 PixelZoomer.handleMouseMove = function(e) {
944 if (PixelZoomer._mouseMoveTimeout)
945 clearTimeout(PixelZoomer._mouseMoveTimeout);
947 if (parentOfType(e.target, '.pixel-zoom-container'))
948 return;
950 var container = document.querySelector('.pixel-zoom-container');
952 var resultContainer = (e.target.className == 'result-container') ?
953 e.target : parentOfType(e.target, '.result-container');
954 if (!resultContainer || !resultContainer.querySelector('img')) {
955 if (container)
956 remove(container);
957 return;
960 var targetLocation = e.target.getBoundingClientRect();
961 PixelZoomer._percentX = (e.clientX - targetLocation.left) / targetLocation.width;
962 PixelZoomer._percentY = (e.clientY - targetLocation.top) / targetLocation.height;
964 if (!container) {
965 if (PixelZoomer.showOnDelay) {
966 PixelZoomer._mouseMoveTimeout = setTimeout(function() {
967 PixelZoomer._createContainer(e);
968 }, 400);
969 return;
972 PixelZoomer._createContainer(e);
973 return;
976 PixelZoomer._drawAll();
979 document.addEventListener('mousemove', PixelZoomer.handleMouseMove, false);
980 document.addEventListener('mouseout', PixelZoomer.handleMouseOut, false);
982 var TestNavigator = {};
984 TestNavigator.reset = function() {
985 TestNavigator.currentTest = null;
986 TestNavigator.flaggedTests = {};
987 TestNavigator._createFlaggedTestContainer();
990 TestNavigator.handleKeyEvent = function(event)
992 if (event.metaKey || event.shiftKey || event.ctrlKey)
993 return;
995 switch (String.fromCharCode(event.charCode)) {
996 case 'i':
997 TestNavigator._scrollToFirstTest();
998 break;
999 case 'j':
1000 TestNavigator._scrollToNextTest();
1001 break;
1002 case 'k':
1003 TestNavigator._scrollToPreviousTest();
1004 break;
1005 case 'l':
1006 TestNavigator._scrollToLastTest();
1007 break;
1008 case 'e':
1009 TestNavigator._expandCurrentTest();
1010 break;
1011 case 'c':
1012 TestNavigator._collapseCurrentTest();
1013 break;
1014 case 't':
1015 TestNavigator._toggleCurrentTest();
1016 break;
1017 case 'f':
1018 TestNavigator._toggleCurrentTestFlagged();
1019 break;
1023 TestNavigator._scrollToFirstTest = function()
1025 var links = visibleTests();
1026 if (links.length == 0)
1027 return;
1028 if (TestNavigator._setCurrentTest(links[0]))
1029 TestNavigator._scrollToCurrentTest();
1032 TestNavigator._scrollToLastTest = function()
1034 var links = visibleTests();
1035 if (links.length == 0)
1036 return;
1037 if (TestNavigator._setCurrentTest(links[links.length - 1]))
1038 TestNavigator._scrollToCurrentTest();
1041 TestNavigator._scrollToNextTest = function()
1043 if (!TestNavigator.currentTest) {
1044 TestNavigator._scrollToFirstTest();
1045 return;
1047 var onlyUnexpected = onlyShowUnexpectedFailures();
1048 for (var tbody = TestNavigator.currentTest.nextElementSibling; tbody; tbody = tbody.nextElementSibling) {
1049 if (tbody.tagName.toLowerCase() != 'tbody')
1050 continue;
1051 if (onlyUnexpected && tbody.classList.contains('expected'))
1052 continue;
1053 if (TestNavigator._setCurrentTest(tbody))
1054 TestNavigator._scrollToCurrentTest();
1055 break;
1059 TestNavigator._scrollToPreviousTest = function()
1061 if (!TestNavigator.currentTest) {
1062 TestNavigator._scrollToLastTest();
1063 return;
1065 var onlyUnexpected = onlyShowUnexpectedFailures();
1066 for (var tbody = TestNavigator.currentTest.previousElementSibling; tbody; tbody = tbody.previousElementSibling) {
1067 if (tbody.tagName.toLowerCase() != 'tbody')
1068 continue;
1069 if (onlyUnexpected && tbody.classList.contains('expected'))
1070 continue;
1071 if (TestNavigator._setCurrentTest(tbody))
1072 TestNavigator._scrollToCurrentTest();
1073 break;
1077 TestNavigator._currentTestExpandLink = function()
1079 return TestNavigator.currentTest.querySelector('.expand-button-text');
1082 TestNavigator._expandCurrentTest = function()
1084 expandExpectations(TestNavigator._currentTestExpandLink());
1087 TestNavigator._collapseCurrentTest = function()
1089 collapseExpectations(TestNavigator._currentTestExpandLink());
1092 TestNavigator._toggleCurrentTest = function()
1094 toggleExpectations(TestNavigator._currentTestExpandLink());
1097 TestNavigator._toggleCurrentTestFlagged = function()
1099 var testLink = TestNavigator.currentTest;
1100 TestNavigator.flagTest(testLink, !testLink.classList.contains('flagged'));
1101 TestNavigator.updateFlaggedTestTextBox();
1104 // FIXME: Test navigator shouldn't know anything about flagging. It should probably call out to TestFlagger or something.
1105 TestNavigator.flagTest = function(testTbody, shouldFlag)
1107 var testName = testTbody.getAttribute('data-testname');
1109 if (shouldFlag) {
1110 testTbody.classList.add('flagged');
1111 TestNavigator.flaggedTests[testName] = 1;
1112 } else {
1113 testTbody.classList.remove('flagged');
1114 delete TestNavigator.flaggedTests[testName];
1118 TestNavigator._createFlaggedTestContainer = function()
1120 var flaggedTestContainer = document.createElement('div');
1121 flaggedTestContainer.id = 'flagged-test-container';
1122 flaggedTestContainer.innerHTML = '<h2>Flagged Tests</h2>' +
1123 '<label title="Use newlines instead of spaces to separate flagged tests">' +
1124 '<input id="use-newlines" type=checkbox checked onchange="handleToggleUseNewlines()">Use newlines</input>' +
1125 '</label>' +
1126 '<pre id="flagged-tests" contentEditable></pre>';
1127 document.body.appendChild(flaggedTestContainer);
1130 TestNavigator.updateFlaggedTestTextBox = function()
1132 var flaggedTestTextbox = document.getElementById('flagged-tests');
1133 var flaggedTests = Object.keys(this.flaggedTests);
1134 flaggedTests.sort();
1135 var separator = document.getElementById('use-newlines').checked ? '\n' : ' ';
1136 flaggedTestTextbox.innerHTML = flaggedTests.join(separator);
1137 document.getElementById('flagged-test-container').style.display = flaggedTests.length ? '' : 'none';
1140 TestNavigator._setCurrentTest = function(tbody)
1142 if (TestNavigator.currentTest)
1143 TestNavigator.currentTest.classList.remove('current');
1145 TestNavigator.currentTest = tbody;
1146 tbody.classList.add('current');
1148 return true;
1151 TestNavigator._scrollToCurrentTest = function()
1153 var targetLink = TestNavigator.currentTest;
1154 if (!targetLink)
1155 return;
1157 var rowRect = targetLink.getBoundingClientRect();
1158 var container = document.querySelector('.content-container');
1159 // rowRect is in client coords (i.e. relative to viewport), so we just want to add its top to the current scroll position.
1160 container.scrollTop += rowRect.top - 20;
1163 TestNavigator.onlyShowUnexpectedFailuresChanged = function()
1165 var currentTest = document.querySelector('.current');
1166 if (!currentTest)
1167 return;
1169 // If our currentTest became hidden, reset the currentTestIndex.
1170 if (onlyShowUnexpectedFailures() && currentTest.classList.contains('expected'))
1171 TestNavigator._scrollToFirstTest();
1174 document.addEventListener('keypress', TestNavigator.handleKeyEvent, false);
1177 function onlyShowUnexpectedFailures()
1179 return !document.getElementById('show-expected-failures').checked;
1182 function handleStderrChange()
1184 OptionWriter.save();
1185 document.getElementById('stderr-style').textContent = document.getElementById('show-stderr').checked ?
1186 '' : '#stderr-table { display: none; }';
1189 function handleUnexpectedPassesChange()
1191 OptionWriter.save();
1192 document.getElementById('unexpected-pass-style').textContent = document.getElementById('show-unexpected-passes').checked ?
1193 '' : '#passes-table { display: none; }';
1196 function handleFlakyFailuresChange()
1198 OptionWriter.save();
1199 document.getElementById('flaky-failures-style').textContent = document.getElementById('show-flaky-failures').checked ?
1200 '' : '.flaky { display: none; }';
1203 function handleUnexpectedResultsChange()
1205 OptionWriter.save();
1206 updateExpectedFailures();
1209 function updateExpectedFailures()
1211 document.getElementById('unexpected-style').textContent = onlyShowUnexpectedFailures() ?
1212 '.expected { display: none; }' : '';
1214 updateTestlistCounts();
1215 TestNavigator.onlyShowUnexpectedFailuresChanged();
1218 var OptionWriter = {};
1220 OptionWriter._key = 'run-webkit-tests-options';
1222 OptionWriter.save = function()
1224 var options = document.querySelectorAll('label input');
1225 var data = {};
1226 for (var i = 0, len = options.length; i < len; i++) {
1227 var option = options[i];
1228 data[option.id] = option.checked;
1230 localStorage.setItem(OptionWriter._key, JSON.stringify(data));
1233 OptionWriter.apply = function()
1235 var json = localStorage.getItem(OptionWriter._key);
1236 if (!json) {
1237 updateAllOptions();
1238 return;
1241 var data = JSON.parse(json);
1242 for (var id in data) {
1243 var input = document.getElementById(id);
1244 if (input)
1245 input.checked = data[id];
1247 updateAllOptions();
1250 function updateAllOptions()
1252 forEach(document.querySelectorAll('input'), function(input) { input.onchange(); });
1255 function handleToggleUseNewlines()
1257 OptionWriter.save();
1258 TestNavigator.updateFlaggedTestTextBox();
1261 function handleToggleImagesChange()
1263 OptionWriter.save();
1264 updateTogglingImages();
1267 function updateTogglingImages()
1269 var shouldToggle = document.getElementById('toggle-images').checked;
1270 globalState().shouldToggleImages = shouldToggle;
1272 if (shouldToggle) {
1273 forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) a[href$=".png"]'), convertToTogglingHandler(function(prefix) {
1274 return resultLink(prefix, '-diffs.html', 'images');
1275 }));
1276 forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) img[src$=".png"]'), convertToTogglingHandler(togglingImage));
1277 } else {
1278 forEach(document.querySelectorAll('a[href$="-diffs.html"]'), convertToNonTogglingHandler(resultLink));
1279 forEach(document.querySelectorAll('.animatedImage'), convertToNonTogglingHandler(function (absolutePrefix, suffix) {
1280 return resultIframe(absolutePrefix + suffix);
1281 }));
1284 updateImageTogglingTimer();
1287 function getResultContainer(node)
1289 return (node.tagName == 'IMG') ? parentOfType(node, '.result-container') : node;
1292 function convertToTogglingHandler(togglingImageFunction)
1294 return function(node) {
1295 var url = (node.tagName == 'IMG') ? node.src : node.href;
1296 if (url.match('-expected.png$'))
1297 remove(getResultContainer(node));
1298 else if (url.match('-actual.png$')) {
1299 var name = parentOfType(node, 'tbody').querySelector('.test-link').textContent;
1300 getResultContainer(node).outerHTML = togglingImageFunction(stripExtension(name));
1305 function convertToNonTogglingHandler(resultFunction)
1307 return function(node) {
1308 var prefix = node.getAttribute('data-prefix');
1309 getResultContainer(node).outerHTML = resultFunction(prefix, '-expected.png', 'expected') + resultFunction(prefix, '-actual.png', 'actual');
1313 function failingTestsTable(tests, title, id)
1315 if (!tests.length)
1316 return '';
1318 var numberofUnexpectedFailures = 0;
1319 var tableRowHtml = '';
1320 for (var i = 0; i < tests.length; i++){
1321 tableRowHtml += tableRow(tests[i]);
1322 if (tests[i].is_unexpected)
1323 numberofUnexpectedFailures++;
1326 var className = '';
1327 if (id)
1328 className += id.split('-')[0];
1329 if (!hasUnexpected(tests))
1330 className += ' expected';
1332 var header = '<div';
1333 if (className)
1334 header += ' class="' + className + '"';
1336 header += '>' + testListHeaderHtml(title) +
1337 '<table id="' + id + '"><thead><tr>' +
1338 '<th>test</th>' +
1339 '<th id="text-results-header">results</th>' +
1340 '<th id="image-results-header">image results</th>' +
1341 '<th>actual</th>' +
1342 '<th>expected</th>';
1344 if (id == 'flaky-tests-table')
1345 header += '<th>failures</th>';
1347 header += '</tr></thead>';
1350 return header + tableRowHtml + '</table></div>';
1353 function generatePage()
1355 forEachTest(processGlobalStateFor);
1357 var html = '<div class=content-container><div id=toolbar>' +
1358 '<div class="note">Use the i, j, k and l keys to navigate, e, c to expand and collapse, and f to flag</div>' +
1359 '<a href="dashboard.html" >Archived results&nbsp;</a>' +
1360 '<a href="javascript:void()" onclick="expandAllExpectations()">expand all</a> ' +
1361 '<a href="javascript:void()" onclick="collapseAllExpectations()">collapse all</a> ' +
1362 '<label><input id="toggle-images" type=checkbox checked onchange="handleToggleImagesChange()">Toggle images</label>' +
1363 '<div id=container>Show: '+
1364 '<label><input id="show-expected-failures" type=checkbox onchange="handleUnexpectedResultsChange()">expected failures</label>' +
1365 '<label><input id="show-flaky-failures" type=checkbox onchange="handleFlakyFailuresChange()">flaky failures</label>' +
1366 '<label><input id="show-unexpected-passes" type=checkbox onchange="handleUnexpectedPassesChange()">unexpected passes</label>' +
1367 '<label><input id="show-stderr" type=checkbox onchange="handleStderrChange()">stderr</label>' +
1368 '</div></div>';
1370 if (globalState().results.interrupted)
1371 html += "<p class='stopped-running-early-message'>Testing exited early.</p>"
1373 if (globalState().crashTests.length)
1374 html += testList(globalState().crashTests, 'Tests that crashed', 'crash-tests-table');
1376 if (globalState().leakTests.length)
1377 html += testList(globalState().leakTests, 'Tests that leaked', 'leak-tests-table');
1379 html += failingTestsTable(globalState().failingTests,
1380 'Tests that failed text/pixel/audio diff', 'results-table');
1382 html += failingTestsTable(globalState().missingResults,
1383 'Tests that had no expected results (probably new)', 'missing-table');
1385 if (globalState().timeoutTests.length)
1386 html += testList(globalState().timeoutTests, 'Tests that timed out', 'timeout-tests-table');
1388 if (globalState().testsWithStderr.length)
1389 html += testList(globalState().testsWithStderr, 'Tests that had stderr output', 'stderr-table');
1391 html += failingTestsTable(globalState().flakyPassTests,
1392 'Flaky tests (failed the first run and passed on retry)', 'flaky-tests-table');
1394 if (globalState().unexpectedPassTests.length)
1395 html += testList(globalState().unexpectedPassTests, 'Tests expected to fail but passed', 'passes-table');
1397 if (globalState().hasHttpTests) {
1398 html += '<p>httpd access log: <a href="access_log.txt">access_log.txt</a></p>' +
1399 '<p>httpd error log: <a href="error_log.txt">error_log.txt</a></p>';
1402 html += '</div>';
1404 document.body.innerHTML = html;
1406 if (document.getElementById('results-table')) {
1407 document.getElementById('results-table').addEventListener('click', TableSorter.handleClick, false);
1408 TableSorter.sortColumn(0);
1409 if (!globalState().hasTextFailures)
1410 document.getElementById('text-results-header').textContent = '';
1411 if (!globalState().hasImageFailures) {
1412 document.getElementById('image-results-header').textContent = '';
1413 parentOfType(document.getElementById('toggle-images'), 'label').style.display = 'none';
1417 updateTestlistCounts();
1419 TestNavigator.reset();
1420 OptionWriter.apply();
1422 </script>
1423 <!-- HACK: when json_results_test.js is included, loading this page runs the tests.
1424 It is not copied to the layout-test-results output directory. -->
1425 <script src="resources/results-test.js"></script>
1426 <body onload="generatePage()"></body>