Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / LayoutTests / fast / harness / results.html
1 <!DOCTYPE html>
2 <style>
3 html {
4     height: 100%;
5 }
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%;
13 }
14
15 body > * {
16     margin-left: 4px;
17     margin-top: 4px;
18 }
19
20 h1 {
21     font-size: 14pt;
22     margin-top: 1.5em;
23 }
24
25 p {
26     margin-bottom: 0.3em;
27 }
28
29 tr:not(.results-row) td {
30     white-space: nowrap;
31 }
32
33 tr:not(.results-row) td:first-of-type {
34     white-space: normal;
35 }
36
37 td:not(:first-of-type) {
38     text-transform: lowercase;
39 }
40
41 td {
42     padding: 1px 4px;
43 }
44
45 th:empty, td:empty {
46     padding: 0;
47 }
48
49 th {
50     -webkit-user-select: none;
51     -moz-user-select: none;
52 }
53
54 .content-container {
55     -webkit-flex: 1;
56     min-height: -webkit-min-content;
57     overflow: auto;
58 }
59
60 .note {
61     color: gray;
62     font-size: smaller;
63 }
64
65 .results-row {
66     background-color: white;
67 }
68
69 .results-row iframe, .results-row img {
70     width: 800px;
71     height: 600px;
72 }
73
74 .results-row[data-expanded="false"] {
75     display: none;
76 }
77
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;
87 }
88
89 .expand-button {
90     background-color: white;
91     width: 11px;
92     height: 12px;
93     border: 1px solid gray;
94     display: inline-block;
95     margin: 0 3px 0 0;
96     position: relative;
97     cursor: default;
98 }
99
100 .current {
101     color: red;
102 }
103
104 .current .expand-button {
105     border-color: red;
106 }
107
108 .expand-button-text {
109     position: absolute;
110     top: -0.3em;
111     left: 1px;
112 }
113
114 tbody .flag {
115     display: none;
116 }
117
118 tbody.flagged .flag {
119     display: inline;
120 }
121
122 .stopped-running-early-message {
123     border: 3px solid #d00;
124     font-weight: bold;
125     display: inline-block;
126     padding: 3px;
127 }
128
129 .result-container {
130     display: inline-block;
131     border: 1px solid gray;
132     margin: 4px;
133 }
134
135 .result-container iframe, .result-container img {
136     border: 0;
137     vertical-align: top;
138 }
139
140 .label {
141     padding-left: 3px;
142     font-weight: bold;
143     font-size: small;
144     background-color: silver;
145 }
146
147 .pixel-zoom-container {
148     position: fixed;
149     top: 0;
150     left: 0;
151     width: 96%;
152     margin: 10px;
153     padding: 10px;
154     display: -webkit-box;
155     display: -moz-box;
156     pointer-events: none;
157     background-color: silver;
158     border-radius: 20px;
159     border: 1px solid gray;
160     box-shadow: 0 0 5px rgba(0, 0, 0, 0.75);
161 }
162
163 .pixel-zoom-container > * {
164     -webkit-box-flex: 1;
165     -moz-box-flex: 1;
166     border: 1px solid black;
167     margin: 4px;
168     overflow: hidden;
169     background-color: white;
170 }
171
172 .pixel-zoom-container .scaled-image-container {
173     position: relative;
174     overflow: hidden;
175     width: 100%;
176     height: 400px;
177 }
178
179 .scaled-image-container > img {
180     position: absolute;
181     top: 0;
182     left: 0;
183     image-rendering: -webkit-optimize-contrast;
184 }
185
186 #flagged-tests {
187     margin: 1px;
188     padding: 5px;
189     height: 100px;
190 }
191
192 #flagged-test-container h2 {
193     display: inline-block;
194     margin: 0 10px 0 0;
195 }
196 </style>
197 <style id="unexpected-pass-style"></style>
198 <style id="flaky-failures-style"></style>
199 <style id="stderr-style"></style>
200 <style id="unexpected-style"></style>
201
202 <script>
203 var g_state;
204 function globalState()
205 {
206     if (!g_state) {
207         g_state = {
208             crashTests: [],
209             flakyPassTests: [],
210             hasHttpTests: false,
211             hasImageFailures: false,
212             hasTextFailures: false,
213             missingResults: [],
214             results: {},
215             shouldToggleImages: true,
216             failingTests: [],
217             testsWithStderr: [],
218             timeoutTests: [],
219             unexpectedPassTests: []
220         }
221     }
222     return g_state;
223 }
224
225 function ADD_RESULTS(input)
226 {
227     globalState().results = input;
228 }
229 </script>
230
231 <script src="failing_results.json"></script>
232
233 <script>
234 function stripExtension(test)
235 {
236     var index = test.lastIndexOf('.');
237     return test.substring(0, index);
238 }
239
240 function matchesSelector(node, selector)
241 {
242     if (node.webkitMatchesSelector)
243         return node.webkitMatchesSelector(selector);
244
245     if (node.mozMatchesSelector)
246         return node.mozMatchesSelector(selector);
247 }
248
249 function parentOfType(node, selector)
250 {
251     while (node = node.parentNode) {
252         if (matchesSelector(node, selector))
253             return node;
254     }
255     return null;
256 }
257
258 function remove(node)
259 {
260     node.parentNode.removeChild(node);
261 }
262
263 function forEach(nodeList, handler)
264 {
265     Array.prototype.forEach.call(nodeList, handler);
266 }
267
268 function resultIframe(src)
269 {
270     // FIXME: use audio tags for AUDIO tests?
271     var layoutTestsIndex = src.indexOf('LayoutTests');
272     var name;
273     if (layoutTestsIndex != -1) {
274         var hasTrac = src.indexOf('trac.webkit.org') != -1;
275         var prefix = hasTrac ? 'trac.webkit.org/.../' : '';
276         name = prefix + src.substring(layoutTestsIndex + 'LayoutTests/'.length);
277     } else {
278         var lastDashIndex = src.lastIndexOf('-pretty');
279         if (lastDashIndex == -1)
280             lastDashIndex = src.lastIndexOf('-');
281         name = src.substring(lastDashIndex + 1);
282     }
283
284     var tagName = (src.lastIndexOf('.png') == -1) ? 'iframe' : 'img';
285
286     if (tagName != 'img')
287         src += '?format=txt';
288     return '<div class=result-container><div class=label>' + name + '</div><' + tagName + ' src="' + src + '"></' + tagName + '></div>';
289 }
290
291 function togglingImage(prefix)
292 {
293     return '<div class=result-container><div class="label imageText"></div><img class=animatedImage data-prefix="' +
294         prefix + '"></img></div>';
295 }
296
297 function toggleExpectations(element)
298 {
299     var expandLink = element;
300     if (expandLink.className != 'expand-button-text')
301         expandLink = expandLink.querySelector('.expand-button-text');
302
303     if (expandLink.textContent == '+')
304         expandExpectations(expandLink);
305     else
306         collapseExpectations(expandLink);
307 }
308
309 function collapseExpectations(expandLink)
310 {
311     expandLink.textContent = '+';
312     var existingResultsRow = parentOfType(expandLink, 'tbody').querySelector('.results-row');
313     if (existingResultsRow)
314         updateExpandedState(existingResultsRow, false);
315 }
316
317 function updateExpandedState(row, isExpanded)
318 {
319     row.setAttribute('data-expanded', isExpanded);
320     updateImageTogglingTimer();
321 }
322
323 function appendHTML(node, html)
324 {
325     if (node.insertAdjacentHTML)
326         node.insertAdjacentHTML('beforeEnd', html);
327     else
328         node.innerHTML += html;
329 }
330
331 function expandExpectations(expandLink)
332 {
333     var row = parentOfType(expandLink, 'tr');
334     var parentTbody = row.parentNode;
335     var existingResultsRow = parentTbody.querySelector('.results-row');
336     
337     var enDash = '\u2013';
338     expandLink.textContent = enDash;
339     if (existingResultsRow) {
340         updateExpandedState(existingResultsRow, true);
341         return;
342     }
343     
344     var newRow = document.createElement('tr');
345     newRow.className = 'results-row';
346     var newCell = document.createElement('td');
347     newCell.colSpan = row.querySelectorAll('td').length;
348
349     var resultLinks = row.querySelectorAll('.result-link');
350     var hasTogglingImages = false;
351     for (var i = 0; i < resultLinks.length; i++) {
352         var link = resultLinks[i];
353         var result;
354         if (link.textContent == 'images') {
355             hasTogglingImages = true;
356             result = togglingImage(link.getAttribute('data-prefix'));
357         } else
358             result = resultIframe(link.href);
359
360         appendHTML(newCell, result);    
361     }
362
363     newRow.appendChild(newCell);
364     parentTbody.appendChild(newRow);
365
366     updateExpandedState(newRow, true);
367
368     updateImageTogglingTimer();
369 }
370
371 function updateImageTogglingTimer()
372 {
373     var hasVisibleAnimatedImage = document.querySelector('.results-row[data-expanded="true"] .animatedImage');
374     if (!hasVisibleAnimatedImage) {
375         clearInterval(globalState().togglingImageInterval);
376         globalState().togglingImageInterval = null;
377         return;
378     }
379
380     if (!globalState().togglingImageInterval) {
381         toggleImages();
382         globalState().togglingImageInterval = setInterval(toggleImages, 2000);
383     }
384 }
385
386 function async(func, args)
387 {
388     setTimeout(function() { func.apply(null, args); }, 100);
389 }
390
391 function visibleTests(opt_container)
392 {
393     var container = opt_container || document;
394     if (onlyShowUnexpectedFailures())
395         return container.querySelectorAll('tbody:not(.expected)');
396     else
397         return container.querySelectorAll('tbody');
398 }
399
400 function visibleExpandLinks()
401 {
402     if (onlyShowUnexpectedFailures())
403         return document.querySelectorAll('tbody:not(.expected) .expand-button-text');
404     else
405         return document.querySelectorAll('.expand-button-text');
406 }
407
408 function expandAllExpectations()
409 {
410     var expandLinks = visibleExpandLinks();
411     for (var i = 0, len = expandLinks.length; i < len; i++)
412         async(expandExpectations, [expandLinks[i]]);
413 }
414
415 function collapseAllExpectations()
416 {
417     var expandLinks = visibleExpandLinks();
418     for (var i = 0, len = expandLinks.length; i < len; i++)
419         async(collapseExpectations, [expandLinks[i]]);
420 }
421
422 function shouldUseTracLinks()
423 {
424     return !globalState().results.layout_tests_dir || !location.toString().indexOf('file://') == 0;
425 }
426
427 function testLink(test)
428 {
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 '<a class=test-link href="' + target + '">' + test + '</a><span class=flag onclick="unflag(this)"> \u2691</span>';
439 }
440
441 function unflag(flag)
442 {
443     var shouldFlag = false;
444     TestNavigator.flagTest(parentOfType(flag, 'tbody'), shouldFlag);
445 }
446
447 function testLinkWithExpandButton(test)
448 {
449     return '<span class=expand-button onclick="toggleExpectations(this)"><span class=expand-button-text>+</span></span>' + testLink(test);
450 }
451
452 function resultLink(testPrefix, suffix, contents)
453 {
454     return '<a class=result-link href="' + testPrefix + suffix + '" data-prefix="' + testPrefix + '">' + contents + '</a> ';
455 }
456
457 function processGlobalStateFor(testObject)
458 {
459     var test = testObject.name;
460     if (testObject.has_stderr)
461         globalState().testsWithStderr.push(testObject);
462
463     globalState().hasHttpTests = globalState().hasHttpTests || test.indexOf('http/') == 0;
464
465     var actual = testObject.actual;    
466     var expected = testObject.expected || 'PASS';
467
468     if (actual == 'MISSING') {
469         // FIXME: make sure that new-run-webkit-tests spits out an -actual.txt file for
470         // tests with MISSING results.
471         globalState().missingResults.push(testObject);
472         return;
473     }
474
475     var actualTokens = actual.split(' ');
476     var passedWithImageOnlyFailureInRetry = actualTokens[0] == 'TEXT' && actualTokens[1] == 'IMAGE';
477     if (actualTokens[1] && actual.indexOf('PASS') != -1 || (!globalState().results.pixel_tests_enabled && passedWithImageOnlyFailureInRetry)) {
478         globalState().flakyPassTests.push(testObject);
479         return;
480     }
481
482     if (actual == 'PASS' && expected != 'PASS') {
483         if (expected != 'IMAGE' || (globalState().results.pixel_tests_enabled || testObject.reftest_type)) {
484             globalState().unexpectedPassTests.push(testObject);
485         }
486         return;
487     }
488
489     if (actual == 'CRASH') {
490         globalState().crashTests.push(testObject);
491         return;
492     }
493
494     if (actual == 'TIMEOUT') {
495         globalState().timeoutTests.push(testObject);
496         return;
497     }
498     
499     globalState().failingTests.push(testObject);
500 }
501
502 function toggleImages()
503 {
504     var images = document.querySelectorAll('.animatedImage');
505     var imageTexts = document.querySelectorAll('.imageText');
506     for (var i = 0, len = images.length; i < len; i++) {
507         var image = images[i];
508         var text = imageTexts[i];
509         if (text.textContent == 'Expected Image') {
510             text.textContent = 'Actual Image';
511             image.src = image.getAttribute('data-prefix') + '-actual.png';
512         } else {
513             text.textContent = 'Expected Image';
514             image.src = image.getAttribute('data-prefix') + '-expected.png';
515         }
516     }
517 }
518
519 function textResultLinks(prefix)
520 {
521     var html = resultLink(prefix, '-expected.txt', 'expected') +
522         resultLink(prefix, '-actual.txt', 'actual') +
523         resultLink(prefix, '-diff.txt', 'diff');
524
525     if (globalState().results.has_pretty_patch)
526         html += resultLink(prefix, '-pretty-diff.html', 'pretty diff');
527
528     if (globalState().results.has_wdiff)
529         html += resultLink(prefix, '-wdiff.html', 'wdiff');
530
531     return html;
532 }
533
534 function imageResultsCell(testObject, testPrefix, actual) {
535     var row = '';
536
537     if (actual.indexOf('IMAGE') != -1) {
538         globalState().hasImageFailures = true;
539
540         if (testObject.reftest_type && testObject.reftest_type.indexOf('!=') != -1) {
541             row += resultLink(testPrefix, '-expected-mismatch.html', 'ref mismatch html');
542             row += resultLink(testPrefix, '-actual.png', 'actual');
543         } else {
544             if (testObject.reftest_type && testObject.reftest_type.indexOf('==') != -1) {
545                 row += resultLink(testPrefix, '-expected.html', 'ref html');
546             }
547             if (globalState().shouldToggleImages) {
548                 row += resultLink(testPrefix, '-diffs.html', 'images');
549             } else {
550                 row += resultLink(testPrefix, '-expected.png', 'expected');
551                 row += resultLink(testPrefix, '-actual.png', 'actual');
552             }
553
554             row += resultLink(testPrefix, '-diff.png', 'diff');
555         }
556     }
557
558     if (actual.indexOf('MISSING') != -1 && testObject.is_missing_image)
559         row += resultLink(testPrefix, '-actual.png', 'png result');
560
561     return row;
562 }
563
564 function tableRow(testObject)
565 {    
566     var row = '<tbody class="' + (testObject.is_unexpected ? '' : 'expected') + '"';
567     if (testObject.reftest_type && testObject.reftest_type.indexOf('!=') != -1)
568         row += ' mismatchreftest=true';
569     row += '><tr>';
570
571     row += '<td>' + testLinkWithExpandButton(testObject.name) + '</td>';
572
573     var testPrefix = stripExtension(testObject.name);
574     row += '<td>';
575     
576     var actual = testObject.actual;
577     if (actual.indexOf('TEXT') != -1) {
578         globalState().hasTextFailures = true;
579         if (testObject.is_testharness_test) {
580             row += resultLink(testPrefix, '-actual.txt', 'actual');
581         } else {
582             row += textResultLinks(testPrefix);
583         }
584     }
585     
586     if (actual.indexOf('AUDIO') != -1) {
587         row += resultLink(testPrefix, '-expected.wav', 'expected audio');
588         row += resultLink(testPrefix, '-actual.wav', 'actual audio');
589     }
590
591     if (actual.indexOf('MISSING') != -1) {
592         if (testObject.is_missing_audio)
593             row += resultLink(testPrefix, '-actual.wav', 'audio result');
594         if (testObject.is_missing_text)
595             row += resultLink(testPrefix, '-actual.txt', 'result');
596     }
597
598     var actualTokens = actual.split(/\s+/);
599     var cell = imageResultsCell(testObject, testPrefix, actualTokens[0]);
600     if (!cell && actualTokens.length > 1)
601         cell = imageResultsCell(testObject, 'retries/' + testPrefix, actualTokens[1]);
602
603     row += '</td><td>' + cell + '</td>' +
604         '<td>' + actual + '</td>' +
605         '<td>' + (actual.indexOf('MISSING') == -1 ? testObject.expected : '') + '</td>' +
606     '</tr></tbody>';
607     return row;
608 }
609
610 function forEachTest(handler, opt_tree, opt_prefix)
611 {
612     var tree = opt_tree || globalState().results.tests;
613     var prefix = opt_prefix || '';
614
615     for (var key in tree) {
616         var newPrefix = prefix ? (prefix + '/' + key) : key;
617         if ('actual' in tree[key]) {
618             var testObject = tree[key];
619             testObject.name = newPrefix;
620             handler(testObject);
621         } else
622             forEachTest(handler, tree[key], newPrefix);
623     }
624 }
625
626 function hasUnexpected(tests)
627 {
628     return tests.some(function (test) { return test.is_unexpected; });
629 }
630
631 function updateTestlistCounts()
632 {
633     forEach(document.querySelectorAll('.test-list-count'), function(count) {
634         var container = parentOfType(count, 'div');
635         var testContainers;
636         if (onlyShowUnexpectedFailures())
637             testContainers = container.querySelectorAll('tbody:not(.expected)');
638         else
639             testContainers = container.querySelectorAll('tbody');
640
641         count.textContent = testContainers.length;
642     })
643 }
644
645 function flagAll(headerLink)
646 {
647     var tests = visibleTests(parentOfType(headerLink, 'div'));
648     forEach(tests, function(test) {
649         var shouldFlag = true;
650         TestNavigator.flagTest(test, shouldFlag);
651     })
652 }
653
654 function testListHeaderHtml(header)
655 {
656     return '<h1>' + header + ' (<span class=test-list-count></span>): <a href="#" class=flag-all onclick="flagAll(this)">flag all</a></h1>';
657 }
658
659 function testList(tests, header, tableId)
660 {
661     tests.sort();
662
663     var html = '<div' + ((!hasUnexpected(tests) && tableId != 'stderr-table') ? ' class=expected' : '') + ' id=' + tableId + '>' +
664         testListHeaderHtml(header) + '<table>';
665
666     // FIXME: Include this for all testLists.
667     if (tableId == 'passes-table')
668         html += '<thead><th>test</th><th>expected</th></thead>';
669
670     for (var i = 0; i < tests.length; i++) {
671         var testObject = tests[i];
672         var test = testObject.name;
673         html += '<tbody class="' + ((testObject.is_unexpected || tableId == 'stderr-table') ? '' : 'expected') + '"><tr><td>' +
674             ((tableId == 'passes-table') ? testLink(test) : testLinkWithExpandButton(test)) +
675         '</td><td>';
676
677         if (tableId == 'stderr-table')
678             html += resultLink(stripExtension(test), '-stderr.txt', 'stderr');
679         else if (tableId == 'passes-table')
680             html += testObject.expected;
681         else if (tableId == 'crash-tests-table') {
682             html += resultLink(stripExtension(test), '-crash-log.txt', 'crash log');
683             html += resultLink(stripExtension(test), '-sample.txt', 'sample');
684         } else if (tableId == 'timeout-tests-table') {
685             // FIXME: only include timeout actual/diff results here if we actually spit out results for timeout tests.
686             html += textResultLinks(stripExtension(test));
687         }
688         
689         html += '</td></tr></tbody>';
690     }
691     html += '</table></div>';
692     return html;
693 }
694
695 function toArray(nodeList)
696 {
697     return Array.prototype.slice.call(nodeList);
698 }
699
700 function trim(string)
701 {
702     return string.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
703 }
704
705 // Just a namespace for code management.
706 var TableSorter = {};
707
708 TableSorter._forwardArrow = '<svg style="width:10px;height:10px"><polygon points="0,0 10,0 5,10" style="fill:#ccc"></svg>';
709
710 TableSorter._backwardArrow = '<svg style="width:10px;height:10px"><polygon points="0,10 10,10 5,0" style="fill:#ccc"></svg>';
711
712 TableSorter._sortedContents = function(header, arrow)
713 {
714     return arrow + ' ' + trim(header.textContent) + ' ' + arrow;
715 }
716
717 TableSorter._updateHeaderClassNames = function(newHeader)
718 {
719     var sortHeader = document.querySelector('.sortHeader');
720     if (sortHeader) {
721         if (sortHeader == newHeader) {
722             var isAlreadyReversed = sortHeader.classList.contains('reversed');
723             if (isAlreadyReversed)
724                 sortHeader.classList.remove('reversed');
725             else
726                 sortHeader.classList.add('reversed');
727         } else {
728             sortHeader.textContent = sortHeader.textContent;
729             sortHeader.classList.remove('sortHeader');
730             sortHeader.classList.remove('reversed');
731         }
732     }
733
734     newHeader.classList.add('sortHeader');
735 }
736
737 TableSorter._textContent = function(tbodyRow, column)
738 {
739     return tbodyRow.querySelectorAll('td')[column].textContent;
740 }
741
742 TableSorter._sortRows = function(newHeader, reversed)
743 {
744     var testsTable = document.getElementById('results-table');
745     var headers = toArray(testsTable.querySelectorAll('th'));
746     var sortColumn = headers.indexOf(newHeader);
747
748     var rows = toArray(testsTable.querySelectorAll('tbody'));
749
750     rows.sort(function(a, b) {
751         // Only need to support lexicographic sort for now.
752         var aText = TableSorter._textContent(a, sortColumn);
753         var bText = TableSorter._textContent(b, sortColumn);
754         
755         // Forward sort equal values by test name.
756         if (sortColumn && aText == bText) {
757             var aTestName = TableSorter._textContent(a, 0);
758             var bTestName = TableSorter._textContent(b, 0);
759             if (aTestName == bTestName)
760                 return 0;
761             return aTestName < bTestName ? -1 : 1;
762         }
763
764         if (reversed)
765             return aText < bText ? 1 : -1;
766         else
767             return aText < bText ? -1 : 1;
768     });
769
770     for (var i = 0; i < rows.length; i++)
771         testsTable.appendChild(rows[i]);
772 }
773
774 TableSorter.sortColumn = function(columnNumber)
775 {
776     var newHeader = document.getElementById('results-table').querySelectorAll('th')[columnNumber];
777     TableSorter._sort(newHeader);
778 }
779
780 TableSorter.handleClick = function(e)
781 {
782     var newHeader = e.target;
783     if (newHeader.localName != 'th')
784         return;
785     TableSorter._sort(newHeader);
786 }
787
788 TableSorter._sort = function(newHeader)
789 {
790     TableSorter._updateHeaderClassNames(newHeader);
791     
792     var reversed = newHeader.classList.contains('reversed');
793     var sortArrow = reversed ? TableSorter._backwardArrow : TableSorter._forwardArrow;
794     newHeader.innerHTML = TableSorter._sortedContents(newHeader, sortArrow);
795     
796     TableSorter._sortRows(newHeader, reversed);
797 }
798
799 var PixelZoomer = {};
800
801 PixelZoomer.showOnDelay = true;
802 PixelZoomer._zoomFactor = 6;
803
804 var kResultWidth = 800;
805 var kResultHeight = 600;
806
807 var kZoomedResultWidth = kResultWidth * PixelZoomer._zoomFactor;
808 var kZoomedResultHeight = kResultHeight * PixelZoomer._zoomFactor;
809
810 PixelZoomer._zoomImageContainer = function(url)
811 {
812     var container = document.createElement('div');
813     container.className = 'zoom-image-container';
814
815     var title = url.match(/\-([^\-]*)\.png/)[1];
816     
817     var label = document.createElement('div');
818     label.className = 'label';
819     label.appendChild(document.createTextNode(title));
820     container.appendChild(label);
821     
822     var imageContainer = document.createElement('div');
823     imageContainer.className = 'scaled-image-container';
824     
825     var image = new Image();
826     image.src = url;
827     image.style.display = 'none';
828
829     var canvas = document.createElement('canvas');
830
831     imageContainer.appendChild(image);
832     imageContainer.appendChild(canvas);
833     container.appendChild(imageContainer);
834     
835     return container;
836 }
837
838 PixelZoomer._createContainer = function(e)
839 {
840     var tbody = parentOfType(e.target, 'tbody');
841     var row = tbody.querySelector('tr');
842     var imageDiffLinks = row.querySelectorAll('a[href$=".png"]');
843     
844     var container = document.createElement('div');
845     container.className = 'pixel-zoom-container';
846     
847     var html = '';
848     
849     var togglingImageLink = row.querySelector('a[href$="-diffs.html"]');
850     if (togglingImageLink) {
851         var prefix = togglingImageLink.getAttribute('data-prefix');
852         container.appendChild(PixelZoomer._zoomImageContainer(prefix + '-expected.png'));
853         container.appendChild(PixelZoomer._zoomImageContainer(prefix + '-actual.png'));
854     }
855     
856     for (var i = 0; i < imageDiffLinks.length; i++)
857         container.appendChild(PixelZoomer._zoomImageContainer(imageDiffLinks[i].href));
858
859     document.body.appendChild(container);
860     PixelZoomer._drawAll();
861 }
862
863 PixelZoomer._draw = function(imageContainer)
864 {
865     var image = imageContainer.querySelector('img');
866     var canvas = imageContainer.querySelector('canvas');
867
868     if (!image.complete) {
869         image.onload = function() {
870             PixelZoomer._draw(imageContainer);
871         };
872         return;
873     }
874
875     canvas.width = imageContainer.clientWidth;
876     canvas.height = imageContainer.clientHeight;
877
878     var ctx = canvas.getContext('2d');
879     ctx.webkitImageSmoothingEnabled = false;
880     ctx.mozImageSmoothingEnabled = false;
881     ctx.imageSmoothingEnabled = false;
882     ctx.translate(imageContainer.clientWidth / 2, imageContainer.clientHeight / 2);
883     ctx.translate(-PixelZoomer._percentX * kZoomedResultWidth, -PixelZoomer._percentY * kZoomedResultHeight);
884     ctx.strokeRect(-1.5, -1.5, kZoomedResultWidth + 2, kZoomedResultHeight + 2);
885     ctx.scale(PixelZoomer._zoomFactor, PixelZoomer._zoomFactor);
886     ctx.drawImage(image, 0, 0);
887 }
888
889 PixelZoomer._drawAll = function()
890 {
891     forEach(document.querySelectorAll('.pixel-zoom-container .scaled-image-container'), PixelZoomer._draw);
892 }
893
894 PixelZoomer.handleMouseOut = function(e)
895 {
896     if (e.relatedTarget && e.relatedTarget.tagName != 'IFRAME')
897         return;
898
899     // If e.relatedTarget is null, we've moused out of the document.
900     var container = document.querySelector('.pixel-zoom-container');
901     if (container)
902         remove(container);
903 }
904
905 PixelZoomer.handleMouseMove = function(e) {
906     if (PixelZoomer._mouseMoveTimeout)
907         clearTimeout(PixelZoomer._mouseMoveTimeout);
908
909     if (parentOfType(e.target, '.pixel-zoom-container'))
910         return;
911
912     var container = document.querySelector('.pixel-zoom-container');
913     
914     var resultContainer = (e.target.className == 'result-container') ?
915         e.target : parentOfType(e.target, '.result-container');
916     if (!resultContainer || !resultContainer.querySelector('img')) {
917         if (container)
918             remove(container);
919         return;
920     }
921
922     var targetLocation = e.target.getBoundingClientRect();
923     PixelZoomer._percentX = (e.clientX - targetLocation.left) / targetLocation.width;
924     PixelZoomer._percentY = (e.clientY - targetLocation.top) / targetLocation.height;
925
926     if (!container) {
927         if (PixelZoomer.showOnDelay) {
928             PixelZoomer._mouseMoveTimeout = setTimeout(function() {
929                 PixelZoomer._createContainer(e);
930             }, 400);
931             return;
932         }
933
934         PixelZoomer._createContainer(e);
935         return;
936     }
937     
938     PixelZoomer._drawAll();
939 }
940
941 document.addEventListener('mousemove', PixelZoomer.handleMouseMove, false);
942 document.addEventListener('mouseout', PixelZoomer.handleMouseOut, false);
943
944 var TestNavigator = {};
945
946 TestNavigator.reset = function() {
947     TestNavigator.currentTestIndex = -1;
948     TestNavigator.flaggedTests = {};
949     TestNavigator._createFlaggedTestContainer();
950 }
951
952 TestNavigator.handleKeyEvent = function(event)
953 {
954     if (event.metaKey || event.shiftKey || event.ctrlKey)
955         return;
956
957     switch (String.fromCharCode(event.charCode)) {
958         case 'i':
959             TestNavigator._scrollToFirstTest();
960             break;
961         case 'j':
962             TestNavigator._scrollToNextTest();
963             break;
964         case 'k':
965             TestNavigator._scrollToPreviousTest();
966             break;
967         case 'l':
968             TestNavigator._scrollToLastTest();
969             break;
970         case 'e':
971             TestNavigator._expandCurrentTest();
972             break;
973         case 'c':
974             TestNavigator._collapseCurrentTest();
975             break;
976         case 't':
977             TestNavigator._toggleCurrentTest();
978             break;
979         case 'f':
980             TestNavigator._toggleCurrentTestFlagged();
981             break;
982     }
983 }
984
985 TestNavigator._scrollToFirstTest = function()
986 {
987     if (TestNavigator._setCurrentTest(0))
988         TestNavigator._scrollToCurrentTest();
989 }
990
991 TestNavigator._scrollToLastTest = function()
992 {
993     var links = visibleTests();
994     if (TestNavigator._setCurrentTest(links.length - 1))
995         TestNavigator._scrollToCurrentTest();
996 }
997
998 TestNavigator._scrollToNextTest = function()
999 {
1000     if (TestNavigator.currentTestIndex == -1)
1001         TestNavigator._scrollToFirstTest();
1002     else if (TestNavigator._setCurrentTest(TestNavigator.currentTestIndex + 1))
1003         TestNavigator._scrollToCurrentTest();
1004 }
1005
1006 TestNavigator._scrollToPreviousTest = function()
1007 {
1008     if (TestNavigator.currentTestIndex == -1)
1009         TestNavigator._scrollToLastTest();
1010     else if (TestNavigator._setCurrentTest(TestNavigator.currentTestIndex - 1))
1011         TestNavigator._scrollToCurrentTest();
1012 }
1013
1014 TestNavigator._currentTestLink = function()
1015 {
1016     var links = visibleTests();
1017     return links[TestNavigator.currentTestIndex];
1018 }
1019
1020 TestNavigator._currentTestExpandLink = function()
1021 {
1022     return TestNavigator._currentTestLink().querySelector('.expand-button-text');
1023 }
1024
1025 TestNavigator._expandCurrentTest = function()
1026 {
1027     expandExpectations(TestNavigator._currentTestExpandLink());
1028 }
1029
1030 TestNavigator._collapseCurrentTest = function()
1031 {
1032     collapseExpectations(TestNavigator._currentTestExpandLink());
1033 }
1034
1035 TestNavigator._toggleCurrentTest = function()
1036 {
1037     toggleExpectations(TestNavigator._currentTestExpandLink());
1038 }
1039
1040 TestNavigator._toggleCurrentTestFlagged = function()
1041 {
1042     var testLink = TestNavigator._currentTestLink();
1043     TestNavigator.flagTest(testLink, !testLink.classList.contains('flagged'));
1044 }
1045
1046 // FIXME: Test navigator shouldn't know anything about flagging. It should probably call out to TestFlagger or something.
1047 TestNavigator.flagTest = function(testTbody, shouldFlag)
1048 {
1049     var testName = testTbody.querySelector('.test-link').innerText;
1050     
1051     if (shouldFlag) {
1052         testTbody.classList.add('flagged');
1053         TestNavigator.flaggedTests[testName] = 1;
1054     } else {
1055         testTbody.classList.remove('flagged');
1056         delete TestNavigator.flaggedTests[testName];
1057     }
1058
1059     TestNavigator.updateFlaggedTests();
1060 }
1061
1062 TestNavigator._createFlaggedTestContainer = function()
1063 {
1064     var flaggedTestContainer = document.createElement('div');
1065     flaggedTestContainer.id = 'flagged-test-container';
1066     flaggedTestContainer.innerHTML = '<h2>Flagged Tests</h2>' +
1067         '<label title="Use newlines instead of spaces to separate flagged tests">' +
1068             '<input id="use-newlines" type=checkbox checked onchange="handleToggleUseNewlines()">Use newlines</input>' +
1069         '</label>' +
1070         '<pre id="flagged-tests" contentEditable></pre>';
1071     document.body.appendChild(flaggedTestContainer);
1072 }
1073
1074 TestNavigator.updateFlaggedTests = function()
1075 {
1076     var flaggedTestTextbox = document.getElementById('flagged-tests');
1077     var flaggedTests = Object.keys(this.flaggedTests);
1078     flaggedTests.sort();
1079     var separator = document.getElementById('use-newlines').checked ? '\n' : ' ';
1080     flaggedTestTextbox.innerHTML = flaggedTests.join(separator);
1081     document.getElementById('flagged-test-container').style.display = flaggedTests.length ? '' : 'none';
1082 }
1083
1084 TestNavigator._setCurrentTest = function(testIndex)
1085 {
1086     var links = visibleTests();
1087     if (testIndex < 0 || testIndex >= links.length)
1088         return false;
1089
1090     var currentTest = links[TestNavigator.currentTestIndex];
1091     if (currentTest)
1092         currentTest.classList.remove('current');
1093
1094     TestNavigator.currentTestIndex = testIndex;
1095
1096     currentTest = links[TestNavigator.currentTestIndex];
1097     currentTest.classList.add('current');
1098
1099     return true;
1100 }
1101
1102 TestNavigator._scrollToCurrentTest = function()
1103 {
1104     var targetLink = TestNavigator._currentTestLink();
1105     if (!targetLink)
1106         return;
1107
1108     var rowRect = targetLink.getBoundingClientRect();
1109     var container = document.querySelector('.content-container');
1110     // rowRect is in client coords (i.e. relative to viewport), so we just want to add its top to the current scroll position.
1111     container.scrollTop += rowRect.top - 20;
1112 }
1113
1114 TestNavigator.onlyShowUnexpectedFailuresChanged = function()
1115 {
1116     var currentTest = document.querySelector('.current');
1117     if (!currentTest)
1118         return;
1119
1120     // If our currentTest became hidden, reset the currentTestIndex.
1121     if (onlyShowUnexpectedFailures() && currentTest.classList.contains('expected'))
1122         TestNavigator._scrollToFirstTest();
1123     else {
1124         // Recompute TestNavigator.currentTestIndex
1125         var links = visibleTests();
1126         TestNavigator.currentTestIndex = links.indexOf(currentTest);
1127         window.console.log('TestNavigator.currentTestIndex is ', TestNavigator.currentTestIndex)
1128     }
1129 }
1130
1131 document.addEventListener('keypress', TestNavigator.handleKeyEvent, false);
1132
1133
1134 function onlyShowUnexpectedFailures()
1135 {
1136     return !document.getElementById('show-expected-failures').checked;
1137 }
1138
1139 function handleStderrChange()
1140 {
1141     OptionWriter.save();
1142     document.getElementById('stderr-style').textContent = document.getElementById('show-stderr').checked ?
1143         '' : '#stderr-table { display: none; }';
1144 }
1145
1146 function handleUnexpectedPassesChange()
1147 {
1148     OptionWriter.save();
1149     document.getElementById('unexpected-pass-style').textContent = document.getElementById('show-unexpected-passes').checked ?
1150         '' : '#passes-table { display: none; }';
1151 }
1152
1153 function handleFlakyFailuresChange()
1154 {
1155     OptionWriter.save();
1156     document.getElementById('flaky-failures-style').textContent = document.getElementById('show-flaky-failures').checked ?
1157         '' : '.flaky { display: none; }';
1158 }
1159
1160 function handleUnexpectedResultsChange()
1161 {
1162     OptionWriter.save();
1163     updateExpectedFailures();
1164 }
1165
1166 function updateExpectedFailures()
1167 {
1168     document.getElementById('unexpected-style').textContent = onlyShowUnexpectedFailures() ?
1169         '.expected { display: none; }' : '';
1170
1171     updateTestlistCounts();
1172     TestNavigator.onlyShowUnexpectedFailuresChanged();
1173 }
1174
1175 var OptionWriter = {};
1176
1177 OptionWriter._key = 'run-webkit-tests-options';
1178
1179 OptionWriter.save = function()
1180 {
1181     var options = document.querySelectorAll('label input');
1182     var data = {};
1183     for (var i = 0, len = options.length; i < len; i++) {
1184         var option = options[i];
1185         data[option.id] = option.checked;
1186     }
1187     localStorage.setItem(OptionWriter._key, JSON.stringify(data));
1188 }
1189
1190 OptionWriter.apply = function()
1191 {
1192     var json = localStorage.getItem(OptionWriter._key);
1193     if (!json) {
1194         updateAllOptions();
1195         return;
1196     }
1197
1198     var data = JSON.parse(json);
1199     for (var id in data) {
1200         var input = document.getElementById(id);
1201         if (input)
1202             input.checked = data[id];
1203     }
1204     updateAllOptions();
1205 }
1206
1207 function updateAllOptions()
1208 {
1209     forEach(document.querySelectorAll('input'), function(input) { input.onchange(); });
1210 }
1211
1212 function handleToggleUseNewlines()
1213 {
1214     OptionWriter.save();
1215     TestNavigator.updateFlaggedTests();
1216 }
1217
1218 function handleToggleImagesChange()
1219 {
1220     OptionWriter.save();
1221     updateTogglingImages();
1222 }
1223
1224 function updateTogglingImages()
1225 {
1226     var shouldToggle = document.getElementById('toggle-images').checked;
1227     globalState().shouldToggleImages = shouldToggle;
1228     
1229     if (shouldToggle) {
1230         forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) a[href$=".png"]'), convertToTogglingHandler(function(prefix) {
1231             return resultLink(prefix, '-diffs.html', 'images');
1232         }));
1233         forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) img[src$=".png"]'), convertToTogglingHandler(togglingImage));
1234     } else {
1235         forEach(document.querySelectorAll('a[href$="-diffs.html"]'), convertToNonTogglingHandler(resultLink));
1236         forEach(document.querySelectorAll('.animatedImage'), convertToNonTogglingHandler(function (absolutePrefix, suffix) {
1237             return resultIframe(absolutePrefix + suffix);
1238         }));
1239     }
1240
1241     updateImageTogglingTimer();
1242 }
1243
1244 function getResultContainer(node)
1245 {
1246     return (node.tagName == 'IMG') ? parentOfType(node, '.result-container') : node;
1247 }
1248
1249 function convertToTogglingHandler(togglingImageFunction)
1250 {
1251     return function(node) {
1252         var url = (node.tagName == 'IMG') ? node.src : node.href;
1253         if (url.match('-expected.png$'))
1254             remove(getResultContainer(node));
1255         else if (url.match('-actual.png$')) {
1256             var name = parentOfType(node, 'tbody').querySelector('.test-link').textContent;
1257             getResultContainer(node).outerHTML = togglingImageFunction(stripExtension(name));
1258         }
1259     }
1260 }
1261
1262 function convertToNonTogglingHandler(resultFunction)
1263 {
1264     return function(node) {
1265         var prefix = node.getAttribute('data-prefix');
1266         getResultContainer(node).outerHTML = resultFunction(prefix, '-expected.png', 'expected') + resultFunction(prefix, '-actual.png', 'actual');
1267     }
1268 }
1269
1270 function failingTestsTable(tests, title, id)
1271 {
1272     if (!tests.length)
1273         return '';
1274
1275     var numberofUnexpectedFailures = 0;
1276     var tableRowHtml = '';
1277     for (var i = 0; i < tests.length; i++){
1278         tableRowHtml += tableRow(tests[i]);
1279         if (tests[i].is_unexpected)
1280             numberofUnexpectedFailures++;
1281     }
1282
1283     var className = '';
1284     if (id)
1285         className += id.split('-')[0];
1286     if (!hasUnexpected(tests))
1287         className += ' expected';
1288
1289     var header = '<div';
1290     if (className)
1291         header += ' class="' + className + '"';
1292
1293     header += '>' + testListHeaderHtml(title) +
1294         '<table id="' + id + '"><thead><tr>' +
1295         '<th>test</th>' +
1296         '<th id="text-results-header">results</th>' +
1297         '<th id="image-results-header">image results</th>' +
1298         '<th>actual</th>' +
1299         '<th>expected</th>';
1300
1301     if (id == 'flaky-tests-table')
1302         header += '<th>failures</th>';
1303
1304     header += '</tr></thead>';
1305
1306
1307     return header + tableRowHtml + '</table></div>';
1308 }
1309
1310
1311 function generatePage()
1312 {
1313     forEachTest(processGlobalStateFor);
1314
1315     var html = '<div class=content-container><div id=toolbar>' +
1316         '<div class="note">Use the i, j, k and l keys to navigate, e, c to expand and collapse, and f to flag</div>' +
1317         '<a href="javascript:void()" onclick="expandAllExpectations()">expand all</a> ' +
1318         '<a href="javascript:void()" onclick="collapseAllExpectations()">collapse all</a> ' +
1319         '<label><input id="toggle-images" type=checkbox checked onchange="handleToggleImagesChange()">Toggle images</label>' +
1320         '<div id=container>Show: '+
1321         '<label><input id="show-expected-failures" type=checkbox onchange="handleUnexpectedResultsChange()">expected failures</label>' +
1322         '<label><input id="show-flaky-failures" type=checkbox onchange="handleFlakyFailuresChange()">flaky failures</label>' +
1323         '<label><input id="show-unexpected-passes" type=checkbox onchange="handleUnexpectedPassesChange()">unexpected passes</label>' +
1324         '<label><input id="show-stderr" type=checkbox onchange="handleStderrChange()">stderr</label>' +
1325         '</div></div>';
1326
1327     if (globalState().results.interrupted)
1328         html += "<p class='stopped-running-early-message'>Testing exited early.</p>"
1329
1330     if (globalState().crashTests.length)
1331         html += testList(globalState().crashTests, 'Tests that crashed', 'crash-tests-table');
1332
1333     html += failingTestsTable(globalState().failingTests,
1334         'Tests that failed text/pixel/audio diff', 'results-table');
1335
1336     html += failingTestsTable(globalState().missingResults,
1337         'Tests that had no expected results (probably new)', 'missing-table');
1338
1339     if (globalState().timeoutTests.length)
1340         html += testList(globalState().timeoutTests, 'Tests that timed out', 'timeout-tests-table');
1341
1342     if (globalState().testsWithStderr.length)
1343         html += testList(globalState().testsWithStderr, 'Tests that had stderr output', 'stderr-table');
1344
1345     html += failingTestsTable(globalState().flakyPassTests,
1346         'Flaky tests (failed the first run and passed on retry)', 'flaky-tests-table');
1347
1348     if (globalState().unexpectedPassTests.length)
1349         html += testList(globalState().unexpectedPassTests, 'Tests expected to fail but passed', 'passes-table');
1350
1351     if (globalState().hasHttpTests) {
1352         html += '<p>httpd access log: <a href="access_log.txt">access_log.txt</a></p>' +
1353             '<p>httpd error log: <a href="error_log.txt">error_log.txt</a></p>';
1354     }
1355
1356     html += '</div>';
1357
1358     document.body.innerHTML = html;
1359
1360     if (document.getElementById('results-table')) {
1361         document.getElementById('results-table').addEventListener('click', TableSorter.handleClick, false);
1362         TableSorter.sortColumn(0);
1363         if (!globalState().hasTextFailures)
1364             document.getElementById('text-results-header').textContent = '';
1365         if (!globalState().hasImageFailures) {
1366             document.getElementById('image-results-header').textContent = '';
1367             parentOfType(document.getElementById('toggle-images'), 'label').style.display = 'none';
1368         }
1369     }
1370
1371     updateTestlistCounts();
1372
1373     TestNavigator.reset();
1374     OptionWriter.apply();
1375 }
1376 </script>
1377 <!-- HACK: when json_results_test.js is included, loading this page runs the tests.
1378 It is not copied to the layout-test-results output directory. -->
1379 <script src="resources/results-test.js"></script>
1380 <body onload="generatePage()"></body>