layout/tools/reftest/reftest-analyzer.xhtml

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <!-- -*- Mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
     3 <!-- vim: set shiftwidth=2 tabstop=2 autoindent expandtab: -->
     4 <!-- This Source Code Form is subject to the terms of the Mozilla Public
     5    - License, v. 2.0. If a copy of the MPL was not distributed with this
     6    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
     7 <!--
     9 Features to add:
    10 * make the left and right parts of the viewer independently scrollable
    11 * make the test list filterable
    12 ** default to only showing unexpecteds
    13 * add other ways to highlight differences other than circling?
    14 * add zoom/pan to images
    15 * Add ability to load log via XMLHttpRequest (also triggered via URL param)
    16 * color the test list based on pass/fail and expected/unexpected/random/skip
    17 * ability to load multiple logs ?
    18 ** rename them by clicking on the name and editing
    19 ** turn the test list into a collapsing tree view
    20 ** move log loading into popup from viewer UI
    22 -->
    23 <!DOCTYPE html>
    24 <html lang="en-US" xml:lang="en-US" xmlns="http://www.w3.org/1999/xhtml">
    25 <head>
    26   <title>Reftest analyzer</title>
    27   <style type="text/css"><![CDATA[
    29   html, body { margin: 0; }
    30   html { padding: 0; }
    31   body { padding: 4px; }
    33   #pixelarea, #itemlist, #images { position: absolute; }
    34   #itemlist, #images { overflow: auto; }
    35   #pixelarea { top: 0; left: 0; width: 320px; height: 84px; overflow: visible }
    36   #itemlist { top: 84px; left: 0; width: 320px; bottom: 0; }
    37   #images { top: 0; bottom: 0; left: 320px; right: 0; }
    39   #leftpane { width: 320px; }
    40   #images { position: fixed; top: 10px; left: 340px; }
    42   form#imgcontrols { margin: 0; display: block; }
    44   #itemlist > table { border-collapse: collapse; }
    45   #itemlist > table > tbody > tr > td { border: 1px solid; padding: 1px; }
    47   /*
    48   #itemlist > table > tbody > tr.pass > td.url { background: lime; }
    49   #itemlist > table > tbody > tr.fail > td.url { background: red; }
    50   */
    52   #magnification > svg { display: block; width: 84px; height: 84px; }
    54   #pixelinfo { font: small sans-serif; position: absolute; width: 200px; left: 84px; }
    55   #pixelinfo table { border-collapse: collapse; }
    56   #pixelinfo table th { white-space: nowrap; text-align: left; padding: 0; }
    57   #pixelinfo table td { font-family: monospace; padding: 0 0 0 0.25em; }
    59   #pixelhint { display: inline; color: #88f; cursor: help; }
    60   #pixelhint > * { display: none; position: absolute; margin: 8px 0 0 8px; padding: 4px; width: 400px; background: #ffa; color: black; box-shadow: 3px 3px 2px #888; z-index: 1; }
    61   #pixelhint:hover { color: #000; }
    62   #pixelhint:hover > * { display: block; }
    63   #pixelhint p { margin: 0; }
    64   #pixelhint p + p { margin-top: 1em; }
    66   ]]></style>
    67   <script type="text/javascript"><![CDATA[
    69 var XLINK_NS = "http://www.w3.org/1999/xlink";
    70 var SVG_NS = "http://www.w3.org/2000/svg";
    71 var IMAGE_NOT_AVAILABLE = "data:image/png;base64,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";
    73 var gPhases = null;
    75 var gIDCache = {};
    77 var gMagPixPaths = [];     // 2D array of array-of-two <path> objects used in the pixel magnifier
    78 var gMagWidth = 5;         // number of zoomed in pixels to show horizontally
    79 var gMagHeight = 5;        // number of zoomed in pixels to show vertically
    80 var gMagZoom = 16;         // size of the zoomed in pixels
    81 var gImage1Data;           // ImageData object for the reference image
    82 var gImage2Data;           // ImageData object for the test output image
    83 var gFlashingPixels = [];  // array of <path> objects that should be flashed due to pixel color mismatch
    85 function ID(id) {
    86   if (!(id in gIDCache))
    87     gIDCache[id] = document.getElementById(id);
    88   return gIDCache[id];
    89 }
    91 function hash_parameters() {
    92   var result = { };
    93   var params = window.location.hash.substr(1).split(/[&;]/);
    94   for (var i = 0; i < params.length; i++) {
    95     var parts = params[i].split("=");
    96     result[parts[0]] = unescape(unescape(parts[1]));
    97   }
    98   return result;
    99 }
   101 function load() {
   102   gPhases = [ ID("entry"), ID("loading"), ID("viewer") ];
   103   build_mag();
   104   var params = hash_parameters();
   105   if (params.log) {
   106     ID("logentry").value = params.log;
   107     log_pasted();
   108   }
   109   window.addEventListener('keypress', maybe_load_image, false);
   110   ID("image1").addEventListener('error', image_load_error, false);
   111   ID("image2").addEventListener('error', image_load_error, false);
   112 }
   114 function image_load_error(e) {
   115   e.target.setAttributeNS(XLINK_NS, "xlink:href", IMAGE_NOT_AVAILABLE);
   116 }
   118 function build_mag() {
   119   var mag = ID("mag");
   121   var r = document.createElementNS(SVG_NS, "rect");
   122   r.setAttribute("x", gMagZoom * -gMagWidth / 2);
   123   r.setAttribute("y", gMagZoom * -gMagHeight / 2);
   124   r.setAttribute("width", gMagZoom * gMagWidth);
   125   r.setAttribute("height", gMagZoom * gMagHeight);
   126   mag.appendChild(r);
   128   mag.setAttribute("transform", "translate(" + (gMagZoom * (gMagWidth / 2) + 1) + "," + (gMagZoom * (gMagHeight / 2) + 1) + ")");
   130   for (var x = 0; x < gMagWidth; x++) {
   131     gMagPixPaths[x] = [];
   132     for (var y = 0; y < gMagHeight; y++) {
   133       var p1 = document.createElementNS(SVG_NS, "path");
   134       p1.setAttribute("d", "M" + ((x - gMagWidth / 2) + 1) * gMagZoom + "," + (y - gMagHeight / 2) * gMagZoom + "h" + -gMagZoom + "v" + gMagZoom);
   135       p1.setAttribute("stroke", "black");
   136       p1.setAttribute("stroke-width", "1px");
   137       p1.setAttribute("fill", "#aaa");
   139       var p2 = document.createElementNS(SVG_NS, "path");
   140       p2.setAttribute("d", "M" + ((x - gMagWidth / 2) + 1) * gMagZoom + "," + (y - gMagHeight / 2) * gMagZoom + "v" + gMagZoom + "h" + -gMagZoom);
   141       p2.setAttribute("stroke", "black");
   142       p2.setAttribute("stroke-width", "1px");
   143       p2.setAttribute("fill", "#888");
   145       mag.appendChild(p1);
   146       mag.appendChild(p2);
   147       gMagPixPaths[x][y] = [p1, p2];
   148     }
   149   }
   151   var flashedOn = false;
   152   setInterval(function() {
   153     flashedOn = !flashedOn;
   154     flash_pixels(flashedOn);
   155   }, 500);
   156 }
   158 function show_phase(phaseid) {
   159   for (var i in gPhases) {
   160     var phase = gPhases[i];
   161     phase.style.display = (phase.id == phaseid) ? "" : "none";
   162   }
   164   if (phase == "viewer")
   165     ID("images").style.display = "none";
   166 }
   168 function fileentry_changed() {
   169   show_phase("loading");
   170   var input = ID("fileentry");
   171   var files = input.files;
   172   if (files.length > 0) {
   173     // Only handle the first file; don't handle multiple selection.
   174     // The parts of the log we care about are ASCII-only.  Since we
   175     // can ignore lines we don't care about, best to read in as
   176     // iso-8859-1, which guarantees we don't get decoding errors.
   177     var fileReader = new FileReader();
   178     fileReader.onload = function(e) {
   179       var log = null;
   181       log = e.target.result;
   183       if (log)
   184         process_log(log);
   185       else
   186         show_phase("entry");
   187     }
   188     fileReader.readAsText(files[0], "iso-8859-1");
   189   }
   190   // So the user can process the same filename again (after
   191   // overwriting the log), clear the value on the form input so we
   192   // will always get an onchange event.
   193   input.value = "";
   194 }
   196 function log_pasted() {
   197   show_phase("loading");
   198   var entry = ID("logentry");
   199   var log = entry.value;
   200   entry.value = "";
   201   process_log(log);
   202 }
   204 var gTestItems;
   206 function process_log(contents) {
   207   var lines = contents.split(/[\r\n]+/);
   208   gTestItems = [];
   209   for (var j in lines) {
   210     var line = lines[j];
   211     var match = line.match(/^.*?REFTEST (.*)$/);
   212     if (!match)
   213       continue;
   214     line = match[1];
   215     match = line.match(/^(TEST-PASS|TEST-UNEXPECTED-PASS|TEST-KNOWN-FAIL|TEST-UNEXPECTED-FAIL|TEST-DEBUG-INFO)(\(EXPECTED RANDOM\)|) \| ([^\|]+) \|(.*)/);
   216     if (match) {
   217       var state = match[1];
   218       var random = match[2];
   219       var url = match[3];
   220       var extra = match[4];
   221       gTestItems.push(
   222         {
   223           pass: !state.match(/DEBUG-INFO$|FAIL$/),
   224           // only one of the following three should ever be true
   225           unexpected: !!state.match(/^TEST-UNEXPECTED/),
   226           random: (random == "(EXPECTED RANDOM)"),
   227           skip: (extra == " (SKIP)"),
   228           url: url,
   229           images: []
   230         });
   231       continue;
   232     }
   233     match = line.match(/^  IMAGE[^:]*: (.*)$/);
   234     if (match) {
   235       var item = gTestItems[gTestItems.length - 1];
   236       item.images.push(match[1]);
   237     }
   238   }
   240   build_viewer();
   241 }
   243 function build_viewer() {
   244   if (gTestItems.length == 0) {
   245     show_phase("entry");
   246     return;
   247   }
   249   var cell = ID("itemlist");
   250   while (cell.childNodes.length > 0)
   251     cell.removeChild(cell.childNodes[cell.childNodes.length - 1]);
   253   var table = document.createElement("table");
   254   var tbody = document.createElement("tbody");
   255   table.appendChild(tbody);
   257   for (var i in gTestItems) {
   258     var item = gTestItems[i];
   260     // XXX skip expected pass items until we have filtering UI
   261     if (item.pass && !item.unexpected)
   262       continue;
   264     var tr = document.createElement("tr");
   265     var rowclass = item.pass ? "pass" : "fail";
   266     var td;
   267     var text;
   269     td = document.createElement("td");
   270     text = "";
   271     if (item.unexpected) { text += "!"; rowclass += " unexpected"; }
   272     if (item.random) { text += "R"; rowclass += " random"; }
   273     if (item.skip) { text += "S"; rowclass += " skip"; }
   274     td.appendChild(document.createTextNode(text));
   275     tr.appendChild(td);
   277     td = document.createElement("td");
   278     td.className = "url";
   279     // Only display part of URL after "/mozilla/".
   280     var match = item.url.match(/\/mozilla\/(.*)/);
   281     text = document.createTextNode(match ? match[1] : item.url);
   282     if (item.images.length > 0) {
   283       var a = document.createElement("a");
   284       a.href = "javascript:show_images(" + i + ")";
   285       a.appendChild(text);
   286       td.appendChild(a);
   287     } else {
   288       td.appendChild(text);
   289     }
   290     tr.appendChild(td);
   292     tbody.appendChild(tr);
   293   }
   295   cell.appendChild(table);
   297   show_phase("viewer");
   298 }
   300 function get_image_data(src, whenReady) {
   301   var img = new Image();
   302   img.onload = function() {
   303     var canvas = document.createElement("canvas");
   304     canvas.width = 800;
   305     canvas.height = 1000;
   307     var ctx = canvas.getContext("2d");
   308     ctx.drawImage(img, 0, 0);
   310     whenReady(ctx.getImageData(0, 0, 800, 1000));
   311   };
   312   img.src = src;
   313 }
   315 function show_images(i) {
   316   var item = gTestItems[i];
   317   var cell = ID("images");
   319   ID("image1").style.display = "";
   320   ID("image2").style.display = "none";
   321   ID("diffrect").style.display = "none";
   322   ID("imgcontrols").reset();
   324   ID("image1").setAttributeNS(XLINK_NS, "xlink:href", item.images[0]);
   325   // Making the href be #image1 doesn't seem to work
   326   ID("feimage1").setAttributeNS(XLINK_NS, "xlink:href", item.images[0]);
   327   if (item.images.length == 1) {
   328     ID("imgcontrols").style.display = "none";
   329   } else {
   330     ID("imgcontrols").style.display = "";
   332     ID("image2").setAttributeNS(XLINK_NS, "xlink:href", item.images[1]);
   333     // Making the href be #image2 doesn't seem to work
   334     ID("feimage2").setAttributeNS(XLINK_NS, "xlink:href", item.images[1]);
   335   }
   337   cell.style.display = "";
   339   get_image_data(item.images[0], function(data) { gImage1Data = data });
   340   get_image_data(item.images[1], function(data) { gImage2Data = data });
   341 }
   343 function show_image(i) {
   344   if (i == 1) {
   345     ID("image1").style.display = "";
   346     ID("image2").style.display = "none";
   347   } else {
   348     ID("image1").style.display = "none";
   349     ID("image2").style.display = "";
   350   }
   351 }
   353 function maybe_load_image(event) {
   354   switch (event.charCode) {
   355   case 49: // "1" key
   356     document.getElementById("radio1").checked = true;
   357     show_image(1);
   358     break;
   359   case 50: // "2" key
   360     document.getElementById("radio2").checked = true;
   361     show_image(2);
   362     break;
   363   }
   364 }
   366 function show_differences(cb) {
   367   ID("diffrect").style.display = cb.checked ? "" : "none";
   368 }
   370 function flash_pixels(on) {
   371   var stroke = on ? "red" : "black";
   372   var strokeWidth = on ? "2px" : "1px";
   373   for (var i = 0; i < gFlashingPixels.length; i++) {
   374     gFlashingPixels[i].setAttribute("stroke", stroke);
   375     gFlashingPixels[i].setAttribute("stroke-width", strokeWidth);
   376   }
   377 }
   379 function cursor_point(evt) {
   380   var m = evt.target.getScreenCTM().inverse();
   381   var p = ID("svg").createSVGPoint();
   382   p.x = evt.clientX;
   383   p.y = evt.clientY;
   384   p = p.matrixTransform(m);
   385   return { x: Math.floor(p.x), y: Math.floor(p.y) };
   386 }
   388 function hex2(i) {
   389   return (i < 16 ? "0" : "") + i.toString(16);
   390 }
   392 function canvas_pixel_as_hex(data, x, y) {
   393   var offset = (y * data.width + x) * 4;
   394   var r = data.data[offset];
   395   var g = data.data[offset + 1];
   396   var b = data.data[offset + 2];
   397   return "#" + hex2(r) + hex2(g) + hex2(b);
   398 }
   400 function hex_as_rgb(hex) {
   401   return "rgb(" + [parseInt(hex.substring(1, 3), 16), parseInt(hex.substring(3, 5), 16), parseInt(hex.substring(5, 7), 16)] + ")";
   402 }
   404 function magnify(evt) {
   405   var { x: x, y: y } = cursor_point(evt);
   406   var centerPixelColor1, centerPixelColor2;
   408   var dx_lo = -Math.floor(gMagWidth / 2);
   409   var dx_hi = Math.floor(gMagWidth / 2);
   410   var dy_lo = -Math.floor(gMagHeight / 2);
   411   var dy_hi = Math.floor(gMagHeight / 2);
   413   flash_pixels(false);
   414   gFlashingPixels = [];
   415   for (var j = dy_lo; j <= dy_hi; j++) {
   416     for (var i = dx_lo; i <= dx_hi; i++) {
   417       var px = x + i;
   418       var py = y + j;
   419       var p1 = gMagPixPaths[i + dx_hi][j + dy_hi][0];
   420       var p2 = gMagPixPaths[i + dx_hi][j + dy_hi][1];
   421       if (px < 0 || py < 0 || px >= 800 || py >= 1000) {
   422         p1.setAttribute("fill", "#aaa");
   423         p2.setAttribute("fill", "#888");
   424       } else {
   425         var color1 = canvas_pixel_as_hex(gImage1Data, x + i, y + j);
   426         var color2 = canvas_pixel_as_hex(gImage2Data, x + i, y + j);
   427         p1.setAttribute("fill", color1);
   428         p2.setAttribute("fill", color2);
   429         if (color1 != color2) {
   430           gFlashingPixels.push(p1, p2);
   431           p1.parentNode.appendChild(p1);
   432           p2.parentNode.appendChild(p2);
   433         }
   434         if (i == 0 && j == 0) {
   435           centerPixelColor1 = color1;
   436           centerPixelColor2 = color2;
   437         }
   438       }
   439     }
   440   }
   441   flash_pixels(true);
   442   show_pixelinfo(x, y, centerPixelColor1, hex_as_rgb(centerPixelColor1), centerPixelColor2, hex_as_rgb(centerPixelColor2));
   443 }
   445 function show_pixelinfo(x, y, pix1rgb, pix1hex, pix2rgb, pix2hex) {
   446   var pixelinfo = ID("pixelinfo");
   447   ID("coords").textContent = [x, y];
   448   ID("pix1hex").textContent = pix1hex;
   449   ID("pix1rgb").textContent = pix1rgb;
   450   ID("pix2hex").textContent = pix2hex;
   451   ID("pix2rgb").textContent = pix2rgb;
   452 }
   454   ]]></script>
   456 </head>
   457 <body onload="load()">
   459 <div id="entry">
   461 <h1>Reftest analyzer: load reftest log</h1>
   463 <p>Either paste your log into this textarea:<br />
   464 <textarea cols="80" rows="10" id="logentry"/><br/>
   465 <input type="button" value="Process pasted log" onclick="log_pasted()" /></p>
   467 <p>... or load it from a file:<br/>
   468 <input type="file" id="fileentry" onchange="fileentry_changed()" />
   469 </p>
   470 </div>
   472 <div id="loading" style="display:none">Loading log...</div>
   474 <div id="viewer" style="display:none">
   475   <div id="pixelarea">
   476     <div id="pixelinfo">
   477       <table>
   478         <tbody>
   479           <tr><th>Pixel at:</th><td colspan="2" id="coords"/></tr>
   480           <tr><th>Image 1:</th><td id="pix1rgb"></td><td id="pix1hex"></td></tr>
   481           <tr><th>Image 2:</th><td id="pix2rgb"></td><td id="pix2hex"></td></tr>
   482         </tbody>
   483       </table>
   484       <div>
   485         <div id="pixelhint">★
   486           <div>
   487             <p>Move the mouse over the reftest image on the right to show
   488             magnified pixels on the left.  The color information above is for
   489             the pixel centered in the magnified view.</p>
   490             <p>Image 1 is shown in the upper triangle of each pixel and Image 2
   491             is shown in the lower triangle.</p>
   492           </div>
   493         </div>
   494       </div>
   495     </div>
   496     <div id="magnification">
   497       <svg xmlns="http://www.w3.org/2000/svg" width="84" height="84" shape-rendering="optimizeSpeed">
   498         <g id="mag"/>
   499       </svg>
   500     </div>
   501   </div>
   502   <div id="itemlist"></div>
   503   <div id="images" style="display:none">
   504     <form id="imgcontrols">
   505     <label title="1"><input id="radio1" type="radio" name="which" value="0" onchange="show_image(1)" checked="checked" />Image 1</label>
   506     <label title="2"><input id="radio2" type="radio" name="which" value="1" onchange="show_image(2)" />Image 2</label>
   507     <label><input type="checkbox" onchange="show_differences(this)" />Circle differences</label>
   508     </form>
   509     <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800px" height="1000px" viewBox="0 0 800 1000" id="svg">
   510       <defs>
   511         <!-- use sRGB to avoid loss of data -->
   512         <filter id="showDifferences" x="0%" y="0%" width="100%" height="100%"
   513                 style="color-interpolation-filters: sRGB">
   514           <feImage id="feimage1" result="img1" xlink:href="#image1" />
   515           <feImage id="feimage2" result="img2" xlink:href="#image2" />
   516           <!-- inv1 and inv2 are the images with RGB inverted -->
   517           <feComponentTransfer result="inv1" in="img1">
   518             <feFuncR type="linear" slope="-1" intercept="1" />
   519             <feFuncG type="linear" slope="-1" intercept="1" />
   520             <feFuncB type="linear" slope="-1" intercept="1" />
   521           </feComponentTransfer>
   522           <feComponentTransfer result="inv2" in="img2">
   523             <feFuncR type="linear" slope="-1" intercept="1" />
   524             <feFuncG type="linear" slope="-1" intercept="1" />
   525             <feFuncB type="linear" slope="-1" intercept="1" />
   526           </feComponentTransfer>
   527           <!-- w1 will have non-white pixels anywhere that img2
   528                is brighter than img1, and w2 for the reverse.
   529                It would be nice not to have to go through these
   530                intermediate states, but feComposite
   531                type="arithmetic" can't transform the RGB channels
   532                and leave the alpha channel untouched. -->
   533           <feComposite result="w1" in="img1" in2="inv2" operator="arithmetic" k2="1" k3="1" />
   534           <feComposite result="w2" in="img2" in2="inv1" operator="arithmetic" k2="1" k3="1" />
   535           <!-- c1 will have non-black pixels anywhere that img2
   536                is brighter than img1, and c2 for the reverse -->
   537           <feComponentTransfer result="c1" in="w1">
   538             <feFuncR type="linear" slope="-1" intercept="1" />
   539             <feFuncG type="linear" slope="-1" intercept="1" />
   540             <feFuncB type="linear" slope="-1" intercept="1" />
   541           </feComponentTransfer>
   542           <feComponentTransfer result="c2" in="w2">
   543             <feFuncR type="linear" slope="-1" intercept="1" />
   544             <feFuncG type="linear" slope="-1" intercept="1" />
   545             <feFuncB type="linear" slope="-1" intercept="1" />
   546           </feComponentTransfer>
   547           <!-- c will be nonblack (and fully on) for every pixel+component where there are differences -->
   548           <feComposite result="c" in="c1" in2="c2" operator="arithmetic" k2="255" k3="255" />
   549           <!-- a will be opaque for every pixel with differences and transparent for all others -->
   550           <feColorMatrix result="a" type="matrix" values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  1 1 1 0 0" />
   552           <!-- a, dilated by 1 pixel -->
   553           <feMorphology result="dila1" in="a" operator="dilate" radius="1" />
   554           <!-- a, dilated by 2 pixels -->
   555           <feMorphology result="dila2" in="dila1" operator="dilate" radius="1" />
   557           <!-- all the pixels in the 2-pixel dilation of a but not in the 1-pixel dilation, to highlight the diffs -->
   558           <feComposite result="highlight" in="dila2" in2="dila1" operator="out" />
   560           <feFlood result="red" flood-color="red" />
   561           <feComposite result="redhighlight" in="red" in2="highlight" operator="in" />
   562           <feFlood result="black" flood-color="black" flood-opacity="0.5" />
   563           <feMerge>
   564             <feMergeNode in="black" />
   565             <feMergeNode in="redhighlight" />
   566           </feMerge>
   567         </filter>
   568       </defs>
   569       <g onmousemove="magnify(evt)">
   570         <image x="0" y="0" width="100%" height="100%" id="image1" />
   571         <image x="0" y="0" width="100%" height="100%" id="image2" />
   572       </g>
   573       <rect id="diffrect" filter="url(#showDifferences)" pointer-events="none" x="0" y="0" width="100%" height="100%" />
   574     </svg>
   575   </div>
   576 </div>
   578 </body>
   579 </html>

mercurial