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